diff options
author | Johannes Hofmann <Johannes.Hofmann@gmx.de> | 2010-08-20 23:24:19 +0200 |
---|---|---|
committer | Johannes Hofmann <Johannes.Hofmann@gmx.de> | 2010-08-20 23:24:19 +0200 |
commit | f5c598b518d1f906148534d015f50075d3e8242d (patch) | |
tree | 21dd70add5b366c3dd80641b77f6b18e0baa009e /src | |
parent | e98d02a01ffeb18ede86af025e51ae1ec011c75a (diff) | |
parent | 5f0fc0e48b8cbee7e1795935da0abff6627fd498 (diff) |
merge
Diffstat (limited to 'src')
-rw-r--r-- | src/IO/IO.c | 49 | ||||
-rw-r--r-- | src/IO/IO.h | 3 | ||||
-rw-r--r-- | src/IO/Makefile.am | 2 | ||||
-rw-r--r-- | src/IO/Url.h | 6 | ||||
-rw-r--r-- | src/IO/about.c | 130 | ||||
-rw-r--r-- | src/IO/dpi.c | 497 | ||||
-rw-r--r-- | src/IO/http.c | 419 | ||||
-rw-r--r-- | src/IO/iowatch.cc | 6 | ||||
-rw-r--r-- | src/IO/mime.c | 12 | ||||
-rw-r--r-- | src/IO/mime.h | 21 | ||||
-rw-r--r-- | src/Makefile.am | 51 | ||||
-rw-r--r-- | src/auth.c | 539 | ||||
-rw-r--r-- | src/auth.h | 19 | ||||
-rw-r--r-- | src/bookmark.c | 3 | ||||
-rw-r--r-- | src/bw.c | 45 | ||||
-rw-r--r-- | src/bw.h | 16 | ||||
-rw-r--r-- | src/cache.c | 504 | ||||
-rw-r--r-- | src/cache.h | 6 | ||||
-rw-r--r-- | src/capi.c | 393 | ||||
-rw-r--r-- | src/capi.h | 7 | ||||
-rw-r--r-- | src/chain.c | 30 | ||||
-rw-r--r-- | src/chain.h | 1 | ||||
-rw-r--r-- | src/colors.c | 11 | ||||
-rw-r--r-- | src/cookies.c | 89 | ||||
-rw-r--r-- | src/cookies.h | 3 | ||||
-rw-r--r-- | src/css.cc | 617 | ||||
-rw-r--r-- | src/css.hh | 495 | ||||
-rw-r--r-- | src/cssparser.cc | 1476 | ||||
-rw-r--r-- | src/cssparser.hh | 54 | ||||
-rw-r--r-- | src/decode.c | 29 | ||||
-rw-r--r-- | src/dgif.h | 19 | ||||
-rw-r--r-- | src/dialog.cc | 107 | ||||
-rw-r--r-- | src/dialog.hh | 7 | ||||
-rw-r--r-- | src/dicache.c | 375 | ||||
-rw-r--r-- | src/dicache.h | 26 | ||||
-rw-r--r-- | src/dillo.cc | 319 | ||||
-rw-r--r-- | src/dir.c | 75 | ||||
-rw-r--r-- | src/dir.h | 20 | ||||
-rw-r--r-- | src/djpeg.h | 19 | ||||
-rw-r--r-- | src/dns.c | 68 | ||||
-rw-r--r-- | src/dns.h | 8 | ||||
-rw-r--r-- | src/doctree.hh | 72 | ||||
-rw-r--r-- | src/dpiapi.c | 13 | ||||
-rw-r--r-- | src/dpng.h | 19 | ||||
-rw-r--r-- | src/findbar.cc | 40 | ||||
-rw-r--r-- | src/findbar.hh | 23 | ||||
-rw-r--r-- | src/form.cc | 977 | ||||
-rw-r--r-- | src/form.hh | 16 | ||||
-rw-r--r-- | src/gif.c | 108 | ||||
-rw-r--r-- | src/history.c | 38 | ||||
-rw-r--r-- | src/history.h | 6 | ||||
-rw-r--r-- | src/html.cc | 2068 | ||||
-rw-r--r-- | src/html.hh | 3 | ||||
-rw-r--r-- | src/html_common.hh | 86 | ||||
-rw-r--r-- | src/image.cc | 139 | ||||
-rw-r--r-- | src/image.hh | 28 | ||||
-rw-r--r-- | src/imgbuf.cc | 138 | ||||
-rw-r--r-- | src/imgbuf.hh | 30 | ||||
-rw-r--r-- | src/jpeg.c | 121 | ||||
-rw-r--r-- | src/keys.cc | 363 | ||||
-rw-r--r-- | src/keys.hh | 68 | ||||
-rw-r--r-- | src/keysrc | 101 | ||||
-rw-r--r-- | src/klist.c | 3 | ||||
-rw-r--r-- | src/menu.cc | 315 | ||||
-rw-r--r-- | src/menu.hh | 21 | ||||
-rw-r--r-- | src/misc.c | 117 | ||||
-rw-r--r-- | src/misc.h | 2 | ||||
-rw-r--r-- | src/msg.h | 23 | ||||
-rw-r--r-- | src/nav.c | 177 | ||||
-rw-r--r-- | src/nav.h | 8 | ||||
-rw-r--r-- | src/paths.cc | 101 | ||||
-rw-r--r-- | src/paths.hh | 26 | ||||
-rw-r--r-- | src/pixmaps.h | 199 | ||||
-rw-r--r-- | src/plain.cc | 90 | ||||
-rw-r--r-- | src/png.c | 227 | ||||
-rw-r--r-- | src/prefs.c | 528 | ||||
-rw-r--r-- | src/prefs.h | 44 | ||||
-rw-r--r-- | src/prefsparser.cc | 208 | ||||
-rw-r--r-- | src/prefsparser.hh | 24 | ||||
-rw-r--r-- | src/styleengine.cc | 678 | ||||
-rw-r--r-- | src/styleengine.hh | 84 | ||||
-rw-r--r-- | src/table.cc | 251 | ||||
-rw-r--r-- | src/table.hh | 4 | ||||
-rw-r--r-- | src/timeout.cc | 2 | ||||
-rw-r--r-- | src/ui.cc | 375 | ||||
-rw-r--r-- | src/ui.hh | 37 | ||||
-rw-r--r-- | src/uicmd.cc | 586 | ||||
-rw-r--r-- | src/uicmd.hh | 24 | ||||
-rw-r--r-- | src/url.c | 174 | ||||
-rw-r--r-- | src/url.h | 17 | ||||
-rw-r--r-- | src/utf8.cc | 102 | ||||
-rw-r--r-- | src/utf8.hh | 33 | ||||
-rw-r--r-- | src/web.cc | 45 | ||||
-rw-r--r-- | src/web.hh | 5 | ||||
-rw-r--r-- | src/xembed.cc | 165 | ||||
-rw-r--r-- | src/xembed.hh | 23 |
96 files changed, 11306 insertions, 4445 deletions
diff --git a/src/IO/IO.c b/src/IO/IO.c index a0e18226..4b0285f2 100644 --- a/src/IO/IO.c +++ b/src/IO/IO.c @@ -13,14 +13,9 @@ * 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" @@ -64,13 +59,13 @@ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, /* IO API - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* - * Return a newly created, and initialized, 'io' struct + * Return a new, initialized, 'io' struct */ -static IOData_t *IO_new(int op, int fd) +static IOData_t *IO_new(int op) { IOData_t *io = dNew0(IOData_t, 1); io->Op = op; - io->FD = fd; + io->FD = -1; io->Flags = 0; io->Key = 0; io->Buf = dStr_sized_new(IOBufLen); @@ -138,7 +133,8 @@ static void IO_close_fd(IOData_t *io, int CloseCode) /* 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)) { + if (((io->Flags & IOFlag_ForceClose) || (CloseCode == IO_StopRdWr)) && + io->FD != -1) { do st = close(io->FD); while (st < 0 && errno == EINTR); @@ -152,10 +148,10 @@ static void IO_close_fd(IOData_t *io, int CloseCode) /* Stop the polling on this FD */ if (CloseCode & IO_StopRd) { events |= DIO_READ; - } + } if (CloseCode & IO_StopWr) { events |= DIO_WRITE; - } + } a_IOwatch_remove_fd(io->FD, events); _MSG(" end IO close (%d) <=====\n", io->FD); } @@ -314,6 +310,11 @@ static void IO_fd_write_cb(int fd, void *data) */ static void IO_submit(IOData_t *r_io) { + if (r_io->FD < 0) { + MSG_ERR("IO_submit: FD not initialized\n"); + return; + } + /* Insert this IO in ValidIOs */ IO_ins(r_io); @@ -351,14 +352,18 @@ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, /* Write data using select */ switch (Op) { case OpStart: - io = IO_new(IOWrite, *(int*)Data1); /* SockFD */ + io = IO_new(IOWrite); Info->LocalKey = io; break; case OpSend: io = Info->LocalKey; - dbuf = Data1; - dStr_append_l(io->Buf, dbuf->Buf, dbuf->Size); - IO_submit(io); + if (Data2 && !strcmp(Data2, "FD")) { + io->FD = *(int*)Data1; /* SockFD */ + } else { + dbuf = Data1; + dStr_append_l(io->Buf, dbuf->Buf, dbuf->Size); + IO_submit(io); + } break; case OpEnd: case OpAbort: @@ -376,7 +381,7 @@ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, MSG_WARN("Unused CCC\n"); break; } - } else { /* FWD */ + } else { /* 1 FWD */ /* Write-data status */ switch (Op) { default: @@ -390,10 +395,16 @@ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, /* This part catches the reader's messages */ switch (Op) { case OpStart: - io = IO_new(IORead, *(int*)Data2); /* SockFD */ + io = IO_new(IORead); Info->LocalKey = io; io->Info = Info; - IO_submit(io); + break; + case OpSend: + io = Info->LocalKey; + if (Data2 && !strcmp(Data2, "FD")) { + io->FD = *(int*)Data1; /* SockFD */ + IO_submit(io); + } break; case OpAbort: io = Info->LocalKey; @@ -405,7 +416,7 @@ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, MSG_WARN("Unused CCC\n"); break; } - } else { /* FWD */ + } else { /* 2 FWD */ /* Send read-data */ io = Data1; switch (Op) { diff --git a/src/IO/IO.h b/src/IO/IO.h index 65b032f5..b75488c2 100644 --- a/src/IO/IO.h +++ b/src/IO/IO.h @@ -1,9 +1,6 @@ #ifndef __IO_H__ #define __IO_H__ -#include <unistd.h> -#include <sys/uio.h> - #include "d_size.h" #include "../../dlib/dlib.h" #include "../chain.h" diff --git a/src/IO/Makefile.am b/src/IO/Makefile.am index bc2dea7e..b168073c 100644 --- a/src/IO/Makefile.am +++ b/src/IO/Makefile.am @@ -1,3 +1,4 @@ +AM_CPPFLAGS=-DDILLO_BINDIR='"$(bindir)/"' AM_CFLAGS = @LIBFLTK_CFLAGS@ AM_CXXFLAGS = @LIBFLTK_CXXFLAGS@ @@ -8,7 +9,6 @@ libDiof_a_SOURCES = \ mime.h \ about.c \ Url.h \ - proto.c \ http.c \ dpi.c \ IO.c \ diff --git a/src/IO/Url.h b/src/IO/Url.h index 698bc6ad..95919f11 100644 --- a/src/IO/Url.h +++ b/src/IO/Url.h @@ -16,19 +16,19 @@ extern void a_Http_freeall(void); int a_Http_init(void); int a_Http_proxy_auth(void); void a_Http_set_proxy_passwd(const char *str); +char *a_Http_make_connect_str(const DilloUrl *url); +const char *a_Http_get_proxy_urlstr(); Dstr *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_dillo_exit(void); void a_Dpi_init(void); diff --git a/src/IO/about.c b/src/IO/about.c index 4cd5e2c1..508bfd11 100644 --- a/src/IO/about.c +++ b/src/IO/about.c @@ -14,7 +14,7 @@ /* * HTML text for startup screen */ -const char *AboutSplash= +const char *const AboutSplash= "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n" "<html>\n" "<head>\n" @@ -75,7 +75,7 @@ const char *AboutSplash= " <tr>\n" " <td> \n" " <td>\n" -" <a href='http://cvs.auriga.wearlab.de/cgi-bin/cvsweb.cgi/dillo2/ChangeLog?rev=HEAD;cvsroot=dillo'>\n" +" <a href='http://hg.dillo.org/dillo/file/tip/ChangeLog'>\n" " ChangeLog</a>\n" " <tr>\n" " <td> \n" @@ -129,7 +129,7 @@ const char *AboutSplash= " <tr>\n" " <td> \n" " <td>\n" -" <a href='http://www.voltairenet.org/'>VoltaireNet</a>\n" +" <a href='http://www.voltairenet.org/en'>VoltaireNet</a>\n" " <tr>\n" " <td> \n" " <td>\n" @@ -191,7 +191,7 @@ const char *AboutSplash= " <tr><td> \n" " <td><a href='http://www.violence.de'>Peace&Violence</a>\n" " <tr><td> \n" -" <td><a href='http://www.gnu.org/philosophy/right-to-read.html'>" +" <td><a href='http://www.gnu.org/philosophy/right-to-read.html'>\n" " Right to Read</a>\n" " </table>\n" " </table>\n" @@ -217,7 +217,8 @@ const char *AboutSplash= " <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 GPL3.\n" +" Dillo is Free Software under the terms of version 3 of the\n" +" <a href='http://www.gnu.org/licenses/gpl.html'>GPL</a>.\n" " This means you have four basic freedoms:\n" " <ul>\n" " <li>Freedom to use the program any way you see fit.\n" @@ -225,10 +226,9 @@ const char *AboutSplash= " <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'>GPL3</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 GPL3.<br>\n" +" The GPL is the legal mechanism that gives you these freedoms.\n" +" It also protects you from having them taken away: any derivative work\n" +" based on the program must be under GPLv3 as well.<br>\n" " </table>\n" "</table>\n" "</table>\n" @@ -240,25 +240,19 @@ const char *AboutSplash= "<tr>\n" " <td bgcolor='#CCCCCC'>\n" " <h4>Release overview</h4>\n" -" October 14, 2008\n" +" February 11, 2010\n" "<tr>\n" " <td bgcolor='#FFFFFF'>\n" " <table border='0' cellspacing='0' cellpadding='5'>\n" " <tr>\n" " <td>\n" "<p>\n" -"This is a rewrite of dillo, using FLTK2, that comes\n" -"with lots of improvements and fixes.\n" +"This release features a major overhaul of the cookies subsystem,\n" +"a reimplementation of the DPI API, a configurable connection limit,\n" +"and various CSS improvements.\n" "<p>\n" -"Our users will surely enjoy this new release as it will give them\n" -"the same things they're accustomed plus tabbed browsing,\n" -"antialiasing, different\n" -"character sets, accepting compressed pages, control over image\n" -"loading, smaller footprint, fewer dependencies, better table\n" -"rendering, bugfixes, improved GUI, ... In brief, a better dillo.\n" -"<p>\n" -"Remember that dillo project uses a release model where every new\n" -"browser shall be better than the former.\n" +"Remember that the dillo project uses a release model where every new\n" +"version shall be better than the last.\n" "<EM>Keep up with the latest one!</EM>\n" " </table>\n" "</table>\n" @@ -272,7 +266,7 @@ const char *AboutSplash= " <td bgcolor='#CCCCCC'>\n" " <h4>ChangeLog highlights</h4>\n" " (Extracted from the\n" -" <a href='http://cvs.auriga.wearlab.de/cgi-bin/cvsweb.cgi/dillo2/ChangeLog?rev=HEAD;cvsroot=dillo'>full\n" +" <a href='http://hg.dillo.org/dillo/file/tip/ChangeLog'>full\n" " ChangeLog</a>)\n" "<tr>\n" " <td bgcolor='#FFFFFF'>\n" @@ -280,61 +274,27 @@ const char *AboutSplash= " <tr>\n" " <td>\n" "<ul>\n" -"<li>Ported Dillo from GTK1 to FLTK2." -"<li>Ported a susbstantial part of the code from C to C++ (FLTK2 is in C++)." -"<li>Wrote a new library: Dlib. With 'Dlib' Dillo doesn't need glib anymore." -"<li>Ported all the code to Dlib." -"<li>Made Dillo's UI Control Panel resizable on-the-fly." -"<li>Implemented a new, simpler, dillorc parser." -"<li>Reimplemented the Concomitant Callback chains into a uniform scheme!" -"<li>Removed threads from IO. Now it only uses select-based watches." -"<li>Simplified http.c by reusing the new non-blocking writes in IO." -"<li>Implemented Stop button to not only stop rendering but also networking." -"<li>Bound Ctrl+Space to toggle fullscreen mode." -"<li>Added a http_referer preference. See details in dillorc." -"<li>CCC: added reentrancy control to the OpEnd and OpAbort operations." -"<li>CCC: enhanced the debug function and implemented OpAbort for dpi." -"<li>Hooked a decoder for text/plain with charset." -"<li>Forbid dpi GET and POST from non dpi-generated urls." -"<li>Implemented tabbed browsing." -"<li>Added a image-loading toggle button to the UI." -"<li>Added line numbers and enabled wrapping in the 'View Source' window." -"<li>Added HTTP-1.1's chunked transfer support!" -"<li>Made the stop button sensitive when loading an image." -"<li>Added support for 'charset' in the HTTP header field for Content-Type." -"<li>Added support for 'charset' in the META element." -"<li>Added the multipart/form-data encoding method to form submission." -"<li>Made zlib a configure requirement, and cleaned up configure.in." -"<li>Enabled the file dpi to look inside gzipped files." -"<li>Added code for optional image loading (nice interface)!" -"<li>Fixed data guesser to detect ASCII, LATIN1, UTF8, KOI8-R, CP-1251 as" -" text." -"<li>Fixed void to int conversions for 64bit-arch." -"<li>Set the url resolver to escape illegal chars instead of stripping." -"<li>Big html.cc cleanup. New classes, form API, source split." -"<li>Added int32_t, EAI_NODATA and iconv tests for FreeBSD." -"<li>Replaced the findtext dialog with an in-window widget!" -"</ul>\n" -"Dw2:<br>\n" -"<ul>\n" -"<li>Enabled clipped redraws (avoids some flickering)." -"<li>Added combination of drawing rectangles into a larger one." -"<li>Made getWidgetAtPoint() a virtual method of widget and implemented a" -" custom one for TextBlock, reducing CPU usage on pages full of links." -"<li>Set FltkViewBase::draw to intersect with view area for expose." -"<li>Added double buffering for partial redraws!" -"<li>Reduced memory usage in 30% by reusing styles, reducing the size" -" of struct Content, and not preallocating in SimpleVector. !" -"<li>Moved highlighting information from struct Word into Textblock" -" to save memory." -"<li>Reduced memory usage 10% with a custom memory handler in Textblock." -"<li>Implemented selection of multibyte glyphs (UTF-8)." -"<li>Fixed a slithery BUG in lout::misc::Stringbuffer." -"<li>Added 'enter' and 'leave' signals into class Resource." -"<li>Enabled mouse wheel scrolling." -"<li>Added setDeleteCallback(DW_Callback_t func, void *data) to widget." -" This allows to hook a callback when the widget is destroyed." -"<li>Changed the table-apportion algorithms + bug fixes. Big work!" +"<li>Added keybindings for scrolling.\n" +"<li>Help button and local help file.\n" +"<li>Add support for multiple class names in CSS.\n" +"<li>Fix X11 coordinate overflows.\n" +"<li>Improve CSS font parsing.\n" +"<li>Enable font face setting via <font> element.\n" +"<li>Ignore XML comment markers in CSS.\n" +"<li>Fix user agent style for nested <ul>.\n" +"<li>Handle signed chars. Added dIsspace() and dIsalnum() to dlib.\n" +"<li>Changed the CCCs to build in one step (for both HTTP and DPI).\n" +"<li>Remove the empty cache entry lingering after connection abort.\n" +"<li>Fixed URL unescaping in the datauri DPI.\n" +"<li>Changed and reimplemented the DPI API.\n" +"<li>Allow linebreaks around Chinese/Japanese characters.\n" +"<li>Fix scrolling for text search.\n" +"<li>Tooltips.\n" +"<li>Enable popup menu below bottom of page content.\n" +"<li>Handle JPEGs with CMYK color space.\n" +"<li>General cookies overhaul.\n" +"<li>Fixed a bug in w3c_mode.\n" +"<li>Limit number of simultaneous connections.\n" "</ul>\n" " </table>\n" "</table>\n" @@ -355,20 +315,20 @@ const char *AboutSplash= "<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" +" (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" +" modify it to your taste.\n" +" <li> Documentation for developers is 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> The right mouse button brings up a context-sensitive menu\n" +" (available on pages, links, images, forms, the Back and Forward buttons,\n" +" and the bug meter).\n" " <li> Dillo behaves very nicely when browsing local files, images, and HTML.\n" " It's also very good for Internet searching.\n" -" <li> This release is mainly intended <strong>for developers</strong>\n" -" and <em>advanced users</em>.\n" +" <li> This release is mainly intended for <strong>developers</strong>\n" +" and <strong>advanced users</strong>.\n" " <li> Frames, Java and Javascript are not supported.\n" "</ul>\n" "<br>\n" diff --git a/src/IO/dpi.c b/src/IO/dpi.c index 7c4357da..6f46b2ba 100644 --- a/src/IO/dpi.c +++ b/src/IO/dpi.c @@ -21,16 +21,16 @@ #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 <fcntl.h> +#include <ctype.h> /* isxdigit */ -#include <stdio.h> #include <sys/socket.h> #include <sys/un.h> #include <netinet/in.h> +#include <netinet/tcp.h> #include <arpa/inet.h> #include <netdb.h> @@ -75,7 +75,7 @@ typedef struct { */ static Klist_t *ValidConns = NULL; /* Active connections list. It holds * pointers to dpi_conn_t structures. */ - +static char SharedKey[32]; /* * Initialize local data @@ -92,6 +92,7 @@ static void Dpi_close_fd(int fd) { int st; + dReturn_if (fd < 0); do st = close(fd); while (st < 0 && errno == EINTR); @@ -224,14 +225,14 @@ static void Dpi_parse_token(dpi_conn_t *conn) tag = dStrndup(Tok, (size_t)conn->TokSize); _MSG("Dpi_parse_token: {%s}\n", tag); - cmd = a_Dpip_get_attr(Tok, conn->TokSize, "cmd"); + cmd = a_Dpip_get_attr_l(Tok, conn->TokSize, "cmd"); if (strcmp(cmd, "send_status_message") == 0) { - msg = a_Dpip_get_attr(Tok, conn->TokSize, "msg"); + msg = a_Dpip_get_attr_l(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"); + msg = a_Dpip_get_attr_l(Tok, conn->TokSize, "msg"); a_Chain_fcb(OpSend, conn->InfoRecv, msg, cmd); dFree(msg); @@ -241,13 +242,13 @@ static void Dpi_parse_token(dpi_conn_t *conn) } else if (strcmp(cmd, "start_send_page") == 0) { conn->Send2EOF = 1; - urlstr = a_Dpip_get_attr(Tok, conn->TokSize, "url"); + urlstr = a_Dpip_get_attr_l(Tok, conn->TokSize, "url"); a_Chain_fcb(OpSend, conn->InfoRecv, urlstr, cmd); dFree(urlstr); - /* TODO: a_Dpip_get_attr(Tok, conn->TokSize, "send_mode") */ + /* TODO: a_Dpip_get_attr_l(Tok, conn->TokSize, "send_mode") */ } else if (strcmp(cmd, "reload_request") == 0) { - urlstr = a_Dpip_get_attr(Tok, conn->TokSize, "url"); + urlstr = a_Dpip_get_attr_l(Tok, conn->TokSize, "url"); a_Chain_fcb(OpSend, conn->InfoRecv, urlstr, cmd); dFree(urlstr); } @@ -260,6 +261,66 @@ static void Dpi_parse_token(dpi_conn_t *conn) /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* + * Write data into a file descriptor taking care of EINTR + * and possible data splits. + * Return value: 1 on success, -1 on error. + */ +static int Dpi_blocking_write(int fd, const char *msg, int msg_len) +{ + int st, sent = 0; + + while (sent < msg_len) { + st = write(fd, msg + sent, msg_len - sent); + if (st < 0) { + if (errno == EINTR) { + continue; + } else { + MSG_ERR("[Dpi_blocking_write] %s\n", dStrerror(errno)); + break; + } + } + sent += st; + } + + return (sent == msg_len) ? 1 : -1; +} + +/* + * Read all the available data from a filedescriptor. + * This is intended for short answers, i.e. when we know the server + * will write it all before being preempted. For answers that may come + * as an stream with delays, non-blocking is better. + * Return value: read data, or NULL on error and no data. + */ +static char *Dpi_blocking_read(int fd) +{ + int st; + const int buf_sz = 8*1024; + char buf[buf_sz], *msg = NULL; + Dstr *dstr = dStr_sized_new(buf_sz); + + do { + st = read(fd, buf, buf_sz); + if (st < 0) { + if (errno == EINTR) { + continue; + } else { + MSG_ERR("[Dpi_blocking_read] %s\n", dStrerror(errno)); + break; + } + } else if (st > 0) { + dStr_append_l(dstr, buf, st); + } + } while (st == buf_sz); + + msg = (dstr->len > 0) ? dstr->str : NULL; + dStr_free(dstr, (dstr->len > 0) ? FALSE : TRUE); + return msg; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* * Get a new data buffer (within a 'dbuf'), save it into local data, * split in tokens and parse the contents. */ @@ -290,8 +351,8 @@ static void Dpi_process_dbuf(int Op, void *Data1, dpi_conn_t *conn) static int Dpi_start_dpid(void) { pid_t pid; - int st_pipe[2], n, ret = 1; - char buf[16]; + int st_pipe[2], ret = 1; + char *answer; /* create a pipe to track our child's status */ if (pipe(st_pipe)) @@ -302,15 +363,19 @@ static int Dpi_start_dpid(void) /* 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) { + if (execl(path1, "dpid", (char*)NULL) == -1) { dFree(path1); - if (execlp("dpid", "dpid", NULL) == -1) { - MSG("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); + path1 = dStrconcat(DILLO_BINDIR, "dpid", NULL); + if (execl(path1, "dpid", (char*)NULL) == -1) { + dFree(path1); + if (execlp("dpid", "dpid", (char*)NULL) == -1) { + MSG("Dpi_start_dpid (child): %s\n", dStrerror(errno)); + if (Dpi_blocking_write(st_pipe[1], "ERROR", 5) == -1) { + MSG("Dpi_start_dpid (child): can't write to pipe.\n"); + } + Dpi_close_fd(st_pipe[1]); + _exit (EXIT_FAILURE); + } } } } else if (pid < 0) { @@ -323,89 +388,90 @@ static int Dpi_start_dpid(void) } 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); - _MSG("Dpi_start_dpid: n = %d\n", n); - if (n != 5) { - ret = 0; + if ((answer = Dpi_blocking_read(st_pipe[0])) != NULL) { + MSG("Dpi_start_dpid: can't start dpid\n"); + dFree(answer); } else { - MSG("Dpi_start_dpid: %s\n", dStrerror(errno)); + ret = 0; } + Dpi_close_fd(st_pipe[0]); } return ret; } /* - * Make a connection test for a UDS. - * Return: 0 OK, 1 Not working. + * Read dpid's communication keys from its saved file. + * Return value: 1 on success, -1 on error. */ -static int Dpi_check_uds(char *uds_name) +static int Dpi_read_comm_keys(int *port) { - 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) { - MSG("Dpi_check_uds: %s %s\n", dStrerror(errno), uds_name); - } else { - Dpi_close_fd(SockFD); - ret = 0; - } + FILE *In; + char *fname, *rcline = NULL, *tail; + int i, ret = -1; + + fname = dStrconcat(dGethomedir(), "/.dillo/dpid_comm_keys", NULL); + if ((In = fopen(fname, "r")) == NULL) { + MSG_ERR("[Dpi_read_comm_keys] %s\n", dStrerror(errno)); + } else if ((rcline = dGetline(In)) == NULL) { + MSG_ERR("[Dpi_read_comm_keys] empty file: %s\n", fname); + } else { + *port = strtol(rcline, &tail, 10); + for (i = 0; *tail && isxdigit(tail[i+1]); ++i) + SharedKey[i] = tail[i+1]; + SharedKey[i] = 0; + ret = 1; } + if (In) + fclose(In); + dFree(rcline); + dFree(fname); + return ret; } /* - * Return the directory where the UDS are in, - * NULL if it can't be found. + * Return a socket file descriptor */ -static char *Dpi_get_dpid_uds_dir(void) +static int Dpi_make_socket_fd() { - 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); - } - } + int fd, one = 1, ret = -1; - _MSG("Dpi_get_dpid_uds_dir: %s \n", dStrerror(errno)); - return p; + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) != -1) { + /* avoid delays when sending small pieces of data */ + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); + ret = fd; + } + return ret; } /* - * Return the dpid's UDS name, NULL on failure. + * Make a connection test for a IDS. + * Return: 1 OK, -1 Not working. */ -static char *Dpi_get_dpid_uds_name(void) +static int Dpi_check_dpid_ids() { - 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; + struct sockaddr_in sin; + const socklen_t sin_sz = sizeof(sin); + int sock_fd, dpid_port, ret = -1; + + /* socket connection test */ + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (Dpi_read_comm_keys(&dpid_port) != -1) { + sin.sin_port = htons(dpid_port); + if ((sock_fd = Dpi_make_socket_fd()) == -1) { + MSG("Dpi_check_dpid_ids: sock_fd=%d %s\n", sock_fd, dStrerror(errno)); + } else if (connect(sock_fd, (struct sockaddr *)&sin, sin_sz) == -1) { + MSG("Dpi_check_dpid_ids: %s\n", dStrerror(errno)); + } else { + Dpi_close_fd(sock_fd); + ret = 1; + } + } + return ret; } /* @@ -415,20 +481,16 @@ static char *Dpi_get_dpid_uds_name(void) 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); + check_st = Dpi_check_dpid_ids(); + _MSG("Dpi_check_dpid: check_st=%d\n", check_st); - if (check_st == 0) { + if (check_st == 1) { /* connection test with dpi server passed */ starting = 0; ret = 0; - } else if (!dpid_uds_name || check_st) { + } else { if (!starting) { /* start dpid */ if (Dpi_start_dpid() == 0) { @@ -436,6 +498,7 @@ static int Dpi_check_dpid(int num_tries) ret = 1; } } else if (++starting < num_tries) { + /* starting */ ret = 1; } else { /* we waited too much, report an error... */ @@ -443,7 +506,6 @@ static int Dpi_check_dpid(int num_tries) } } - dFree(dpid_uds_name); _MSG("Dpi_check_dpid:: %s\n", (ret == 0) ? "OK" : (ret == 1 ? "EAGAIN" : "ERROR")); return ret; @@ -467,132 +529,137 @@ static int Dpi_blocking_start_dpid(void) } /* - * Return the UDS name of a dpi server. + * Return the dpi server's port number, or -1 on error. * (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) +static int Dpi_get_server_port(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); - _MSG("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 */ + int sock_fd = -1, dpi_port = -1; + int dpid_port, ok = 0; + struct sockaddr_in sin; + char *cmd, *request, *rply = NULL, *port_str; + socklen_t sin_sz; + + dReturn_val_if_fail (server_name != NULL, dpi_port); + _MSG("Dpi_get_server_port:: server_name = [%s]\n", server_name); + + /* Read dpid's port from saved file */ + if (Dpi_read_comm_keys(&dpid_port) != -1) { + ok = 1; + } + if (ok) { + /* Connect a socket with dpid */ + ok = 0; + sin_sz = sizeof(sin); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = htons(dpid_port); + if ((sock_fd = Dpi_make_socket_fd()) == -1 || + connect(sock_fd, (struct sockaddr *)&sin, sin_sz) == -1) { + MSG("Dpi_get_server_port: %s\n", dStrerror(errno)); + } else { + ok = 1; + } + } + if (ok) { + /* ask dpid to check the dpi and send its port number back */ + ok = 0; request = a_Dpip_build_cmd("cmd=%s msg=%s", "check_server", server_name); _MSG("[%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 */ + if (Dpi_blocking_write(sock_fd, request, strlen(request)) == -1) { + MSG("Dpi_get_server_port: %s\n", dStrerror(errno)); + } else { + ok = 1; + } + dFree(request); + } + if (ok) { /* 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); + ok = 0; + if ((rply = Dpi_blocking_read(sock_fd)) == NULL) { + MSG("Dpi_get_server_port: can't read server port from dpid.\n"); + } else { + ok = 1; } - Dpi_close_fd(sock); - _MSG("rply = [%s]\n", rply); - + } + if (ok) { /* 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); + ok = 0; + cmd = a_Dpip_get_attr(rply, "cmd"); + if (strcmp(cmd, "send_data") == 0) { + port_str = a_Dpip_get_attr(rply, "msg"); + _MSG("Dpi_get_server_port: rply=%s\n", rply); + _MSG("Dpi_get_server_port: port_str=%s\n", port_str); + dpi_port = strtol(port_str, NULL, 10); + dFree(port_str); + ok = 1; } + dFree(cmd); } - dFree(dpid_uds_dir); - dFree(dpid_uds_name); - _MSG("Dpi_get_server_uds_name:: %s\n", server_uds_name); - return server_uds_name; + dFree(rply); + Dpi_close_fd(sock_fd); + + return ok ? dpi_port : -1; } /* * 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. + * We have to ask 'dpid' (dpi daemon) for the port 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); - _MSG("server_uds_name = [%s]\n", server_uds_name); + struct sockaddr_in sin; + int sock_fd, err, dpi_port, ret=-1; + char *cmd = NULL; - if (access(server_uds_name, F_OK) != 0) { - MSG("server socket was NOT found\n"); + /* Query dpid for the port number for this server */ + if ((dpi_port = Dpi_get_server_port(server_name)) == -1) { + _MSG("Dpi_connect_socket:: can't get port number for %s\n", server_name); return -1; } + _MSG("Dpi_connect_socket: server=%s port=%d\n", server_name, dpi_port); /* 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); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = htons(dpi_port); - if ((SockFD = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) + if ((sock_fd = Dpi_make_socket_fd()) == -1) { perror("[dpi::socket]"); - else if (connect(SockFD, (void*)&pun, D_SUN_LEN(&pun)) == -1) { + } else if (connect(sock_fd, (void*)&sin, sizeof(sin)) == -1) { err = errno; - SockFD = -1; + sock_fd = -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); + sock_fd = Dpi_connect_socket(server_name, FALSE); break; } } + + /* send authentication Key (the server closes sock_fd on error) */ + } else if (!(cmd = a_Dpip_build_cmd("cmd=%s msg=%s", "auth", SharedKey))) { + MSG_ERR("[Dpi_connect_socket] Can't make auth message.\n"); + } else if (Dpi_blocking_write(sock_fd, cmd, strlen(cmd)) == -1) { + MSG_ERR("[Dpi_connect_socket] Can't send auth message.\n"); + } else { + ret = sock_fd; } + dFree(cmd); - return SockFD; + return ret; } - /* * CCC function for the Dpi module */ @@ -616,13 +683,13 @@ void a_Dpi_ccc(int Op, int Branch, int Dir, ChainLink *Info, *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"); + a_Chain_bcb(OpStart, Info, NULL, NULL); } } if (st == 0 && SockFD != -1) { + a_Chain_bcb(OpSend, Info, &SockFD, "FD"); + a_Chain_fcb(OpSend, Info, &SockFD, "FD"); a_Chain_fcb(OpSend, Info, NULL, "DpidOK"); } else { MSG_ERR("dpi.c: can't start dpi daemon\n"); @@ -646,7 +713,7 @@ void a_Dpi_ccc(int Op, int Branch, int Dir, ChainLink *Info, MSG_WARN("Unused CCC\n"); break; } - } else { /* FWD */ + } else { /* 1 FWD */ /* Send commands to dpi-server (status) */ switch (Op) { case OpAbort: @@ -676,7 +743,7 @@ void a_Dpi_ccc(int Op, int Branch, int Dir, ChainLink *Info, MSG_WARN("Unused CCC\n"); break; } - } else { /* BCK */ + } else { /* 2 BCK */ switch (Op) { case OpStart: conn = Dpi_conn_new(Info); @@ -688,7 +755,12 @@ void a_Dpi_ccc(int Op, int Branch, int Dir, ChainLink *Info, } a_Chain_link_new(Info, a_Dpi_ccc, BCK, a_IO_ccc, 2, 2); - a_Chain_bcb(OpStart, Info, NULL, Data1); /* IORead, SockFD */ + a_Chain_bcb(OpStart, Info, NULL, NULL); /* IORead */ + break; + case OpSend: + if (Data2 && !strcmp(Data2, "FD")) { + a_Chain_bcb(OpSend, Info, Data1, Data2); + } break; case OpAbort: a_Chain_bcb(OpAbort, Info, NULL, NULL); @@ -703,77 +775,38 @@ void a_Dpi_ccc(int Op, int Branch, int Dir, ChainLink *Info, } } -/*! Send DpiBye to dpid - * Note: currently disabled. Maybe it'd be better to have a - * dpid_idle_timeout variable in the config file. +/*! Let dpid know dillo is no longer running. + * Note: currently disabled. It may serve to let the cookies dpi know + * when to expire session cookies. */ -void a_Dpi_bye_dpid() +void a_Dpi_dillo_exit() { - 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) { - MSG("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) { - MSG("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; + int cst, sock_fd; + char *ret = NULL; /* test the dpid, and wait a bit for it to start if necessary */ if ((cst = Dpi_blocking_start_dpid()) != 0) { - return retval; + return ret; } - 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]"); + if ((sock_fd = Dpi_connect_socket(server_name, TRUE)) == -1) { + MSG_ERR("[a_Dpi_send_blocking_cmd] Can't connect to server.\n"); + } else if (Dpi_blocking_write(sock_fd, cmd, strlen(cmd)) == -1) { + MSG_ERR("[a_Dpi_send_blocking_cmd] Can't send message.\n"); + } if ((ret = Dpi_blocking_read(sock_fd)) == NULL) { + MSG_ERR("[a_Dpi_send_blocking_cmd] Can't read message.\n"); } + Dpi_close_fd(sock_fd); - return retval; + return ret; } diff --git a/src/IO/http.c b/src/IO/http.c index 03bb4522..77a1be43 100644 --- a/src/IO/http.c +++ b/src/IO/http.c @@ -16,12 +16,12 @@ #include <config.h> +#include <ctype.h> /* isdigit */ #include <unistd.h> #include <errno.h> /* for errno */ #include <stdlib.h> -#include <signal.h> #include <fcntl.h> -#include <sys/wait.h> +#include <assert.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 */ @@ -33,6 +33,7 @@ #include "../dns.h" #include "../web.hh" #include "../cookies.h" +#include "../auth.h" #include "../prefs.h" #include "../misc.h" @@ -47,34 +48,70 @@ D_STMT_START { \ #define _MSG_BW(web, root, ...) +static const int HTTP_SOCKET_USE_PROXY = 0x1; +static const int HTTP_SOCKET_QUEUED = 0x4; +static const int HTTP_SOCKET_TO_BE_FREED = 0x8; + /* 'Url' and 'web' are just references (no need to deallocate them here). */ typedef struct { int SockFD; uint_t port; /* need a separate port in order to support PROXY */ - bool_t use_proxy; /* indicates whether to use proxy or not */ + uint_t flags; 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 */ + char *connected_to; /* Used for per-host connection limit */ } SocketData_t; +/* Data structures and functions to queue sockets that need to be + * delayed due to the per host connection limit. + */ +typedef struct SocketQueueEntry { + SocketData_t* sock; + struct SocketQueueEntry *next ; +} SocketQueueEntry_t; + +typedef struct { + SocketQueueEntry_t *head; + SocketQueueEntry_t *tail; +} SocketQueue_t; + +typedef struct { + char *host; + int active_connections; + SocketQueue_t queue; +} HostConnection_t; + +static void Http_socket_queue_init(SocketQueue_t *sq); +static void Http_socket_enqueue(SocketQueue_t *sq, SocketData_t* sock); +static SocketData_t* Http_socket_dequeue(SocketQueue_t *sq); +static HostConnection_t *Http_host_connection_get(const char *host); +static void Http_host_connection_remove(HostConnection_t *hc); +static int Http_connect_socket(ChainLink *Info); +static void Http_socket_free(int SKey); /* * 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; +static char *HTTP_Language_hdr = NULL; +static Dlist *host_connections; /* - * Initialize proxy vars. + * Initialize proxy vars and Accept-Language header */ int a_Http_init(void) { char *env_proxy = getenv("http_proxy"); + HTTP_Language_hdr = prefs.http_language ? + dStrconcat("Accept-Language: ", prefs.http_language, "\r\n", NULL) : + dStrdup(""); + if (env_proxy && strlen(env_proxy)) HTTP_Proxy = a_Url_new(env_proxy, NULL); if (!HTTP_Proxy && prefs.http_proxy) @@ -86,6 +123,9 @@ int a_Http_init(void) if (HTTP_Proxy && prefs.http_proxyuser && strchr(prefs.http_proxyuser, ':')) HTTP_Proxy_Auth_base64 = a_Misc_encode_base64(prefs.http_proxyuser); */ + + host_connections = dList_new(5); + return 0; } @@ -118,6 +158,31 @@ static int Http_sock_new(void) return a_Klist_insert(&ValidSocks, S); } +static void Http_connect_queued_sockets(HostConnection_t *hc) +{ + SocketData_t *sd; + while (hc->active_connections < prefs.http_max_conns && + (sd = Http_socket_dequeue(&hc->queue))) { + + sd->flags &= ~HTTP_SOCKET_QUEUED; + + if (sd->flags & HTTP_SOCKET_TO_BE_FREED) { + dFree(sd); + } else if (a_Web_valid(sd->web)) { + /* start connecting the socket */ + if (Http_connect_socket(sd->Info) < 0) { + MSG_BW(sd->web, 1, "ERROR: %s", dStrerror(sd->Err)); + a_Chain_bfcb(OpAbort, sd->Info, NULL, "Both"); + dFree(sd->Info); + Http_socket_free((int) sd->Info->LocalKey); + } else { + sd->connected_to = hc->host; + hc->active_connections++; + } + } + } +} + /* * Free SocketData_t struct */ @@ -127,7 +192,19 @@ static void Http_socket_free(int SKey) if ((S = a_Klist_get_data(ValidSocks, SKey))) { a_Klist_remove(ValidSocks, SKey); - dFree(S); + + if (S->flags & HTTP_SOCKET_QUEUED) { + S->flags |= HTTP_SOCKET_TO_BE_FREED; + } else { + if (S->connected_to) { + HostConnection_t *hc = Http_host_connection_get(S->connected_to); + hc->active_connections--; + Http_connect_queued_sockets(hc); + if (hc->active_connections == 0) + Http_host_connection_remove(hc); + } + dFree(S); + } } } @@ -167,7 +244,7 @@ static char *Http_get_referer(const DilloUrl *url) /* * Generate Content-Type header value for a POST query. */ -Dstr *Http_make_content_type(const DilloUrl *url) +static Dstr *Http_make_content_type(const DilloUrl *url) { Dstr *dstr; @@ -196,16 +273,12 @@ Dstr *Http_make_content_type(const DilloUrl *url) */ Dstr *a_Http_make_query_str(const DilloUrl *url, bool_t use_proxy) { + const char *auth; char *ptr, *cookies, *referer; - Dstr *s_port = dStr_new(""), - *query = dStr_new(""), + Dstr *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), @@ -224,6 +297,7 @@ Dstr *a_Http_make_query_str(const DilloUrl *url, bool_t use_proxy) } cookies = a_Cookies_get_query(url); + auth = a_Auth_get_auth_str(url); referer = Http_get_referer(url); if (URL_FLAGS(url) & URL_Post) { Dstr *content_type = Http_make_content_type(url); @@ -231,19 +305,22 @@ Dstr *a_Http_make_query_str(const DilloUrl *url, bool_t use_proxy) query, "POST %s HTTP/1.1\r\n" "Connection: close\r\n" + "Accept: text/*,image/*,*/*;q=0.2\r\n" "Accept-Charset: utf-8,*;q=0.8\r\n" "Accept-Encoding: gzip\r\n" - "Host: %s%s\r\n" + "%s" /* language */ + "%s" /* auth */ + "Host: %s\r\n" "%s" "%s" - "User-Agent: Dillo/%s\r\n" + "User-Agent: %s\r\n" "Content-Length: %ld\r\n" "Content-Type: %s\r\n" - "%s" + "%s" /* cookies */ "\r\n", - full_path->str, URL_HOST(url), s_port->str, - proxy_auth->str, referer, VERSION, - URL_DATA(url)->len, content_type->str, + full_path->str, HTTP_Language_hdr, auth ? auth : "", + URL_AUTHORITY(url), proxy_auth->str, referer, prefs.http_user_agent, + (long)URL_DATA(url)->len, content_type->str, cookies); dStr_append_l(query, URL_DATA(url)->str, URL_DATA(url)->len); dStr_free(content_type, TRUE); @@ -253,24 +330,26 @@ Dstr *a_Http_make_query_str(const DilloUrl *url, bool_t use_proxy) "GET %s HTTP/1.1\r\n" "%s" "Connection: close\r\n" + "Accept: text/*,image/*,*/*;q=0.2\r\n" "Accept-Charset: utf-8,*;q=0.8\r\n" "Accept-Encoding: gzip\r\n" - "Host: %s%s\r\n" - "%s" + "%s" /* language */ + "%s" /* auth */ + "Host: %s\r\n" "%s" - "User-Agent: Dillo/%s\r\n" "%s" + "User-Agent: %s\r\n" + "%s" /* cookies */ "\r\n", full_path->str, (URL_FLAGS(url) & URL_E2EQuery) ? "Cache-Control: no-cache\r\nPragma: no-cache\r\n" : "", - URL_HOST(url), s_port->str, - proxy_auth->str, referer, VERSION, cookies); + HTTP_Language_hdr, auth ? auth : "", URL_AUTHORITY(url), + proxy_auth->str, referer, prefs.http_user_agent, cookies); } dFree(referer); dFree(cookies); - dStr_free(s_port, TRUE); dStr_free(full_path, TRUE); dStr_free(proxy_auth, TRUE); _MSG("Query: {%s}\n", dStr_printable(query, 8192)); @@ -286,7 +365,7 @@ static void Http_send_query(ChainLink *Info, SocketData_t *S) DataBuf *dbuf; /* Create the query */ - query = a_Http_make_query_str(S->web->url, S->use_proxy); + query = a_Http_make_query_str(S->web->url, S->flags & HTTP_SOCKET_USE_PROXY); dbuf = a_Chain_dbuf_new(query->str, query->len, 0); /* actually this message is sent too early. @@ -294,14 +373,9 @@ static void Http_send_query(ChainLink *Info, SocketData_t *S) _MSG_BW(S->web, 1, "Sending query to %s...", URL_HOST_(S->web->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); dStr_free(query, 1); - - /* Tell the cache to start the receiving CCC for the answer */ - a_Chain_fcb(OpSend, Info, &S->SockFD, "SockFD"); } /* @@ -311,7 +385,7 @@ static void Http_send_query(ChainLink *Info, SocketData_t *S) */ static int Http_connect_socket(ChainLink *Info) { - int status; + int i, status; #ifdef ENABLE_IPV6 struct sockaddr_in6 name; #else @@ -324,61 +398,64 @@ static int Http_connect_socket(ChainLink *Info) 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; - MSG("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)) - MSG("Connecting to %s\n", inet_ntoa(sin->sin_addr)); - break; - } + for (i = 0; (dh = dList_nth_data(S->addr_list, i)); ++i) { + if ((S->SockFD = socket(dh->af, SOCK_STREAM, IPPROTO_TCP)) < 0) { + S->Err = errno; + MSG("Http_connect_socket ERROR: %s\n", dStrerror(errno)); + continue; + } + /* 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)) + MSG("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)) - MSG("Connecting to %s\n", buf); - break; - } + 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)) + MSG("Connecting to %s\n", buf); + break; + } #endif - }/*switch*/ - - 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); - MSG("Http_connect_socket ERROR: %s\n", dStrerror(S->Err)); - return -1; - } else { - Http_send_query(S->Info, S); + }/*switch*/ + + 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); + MSG("Http_connect_socket ERROR: %s\n", dStrerror(S->Err)); + } else { + a_Chain_bcb(OpSend, Info, &S->SockFD, "FD"); + a_Chain_fcb(OpSend, Info, &S->SockFD, "FD"); + Http_send_query(S->Info, S); + return 0; /* Success */ + } } - return 0; /* Success */ + return -1; } /* @@ -393,9 +470,15 @@ static int Http_must_use_proxy(const DilloUrl *url) if (HTTP_Proxy) { ret = 1; if (prefs.no_proxy) { + const char *host = URL_HOST(url); + size_t host_len = strlen(host); + np = dStrdup(prefs.no_proxy); for (p = np; (tok = dStrsep(&p, " ")); ) { - if (dStristr(URL_AUTHORITY(url), tok)) { + int start = host_len - strlen(tok); + + if (start >= 0 && dStrcasecmp(host + start, tok) == 0) { + /* no_proxy token is suffix of host string */ ret = 0; break; } @@ -403,42 +486,94 @@ static int Http_must_use_proxy(const DilloUrl *url) dFree(np); } } + _MSG("Http_must_use_proxy: %s\n %s\n", URL_STR(url), ret ? "YES":"NO"); return ret; } /* + * Return a new string for the request used to tunnel HTTPS through a proxy. + * As of 2009, the best reference appears to be section 5 of RFC 2817. + */ +char *a_Http_make_connect_str(const DilloUrl *url) +{ + Dstr *dstr; + const char *auth1; + int auth_len; + char *auth2, *proxy_auth, *retstr; + + dReturn_val_if_fail(Http_must_use_proxy(url), NULL); + + dstr = dStr_new(""); + auth1 = URL_AUTHORITY(url); + auth_len = strlen(auth1); + if (auth_len > 0 && !isdigit(auth1[auth_len - 1])) + /* if no port number, add HTTPS port */ + auth2 = dStrconcat(auth1, ":443", NULL); + else + auth2 = dStrdup(auth1); + proxy_auth = HTTP_Proxy_Auth_base64 ? + dStrconcat ("Proxy-Authorization: Basic ", + HTTP_Proxy_Auth_base64, "\r\n", NULL) : + dStrdup(""); + dStr_sprintfa( + dstr, + "CONNECT %s HTTP/1.1\r\n" + "Host: %s\r\n" + "%s" + "\r\n", + auth2, + auth2, + proxy_auth); + + dFree(auth2); + dFree(proxy_auth); + retstr = dstr->str; + dStr_free(dstr, 0); + return retstr; +} + +/* + * Return URL string of HTTP proxy, if any + */ +const char *a_Http_get_proxy_urlstr() +{ + return HTTP_Proxy ? URL_STR(HTTP_Proxy) : NULL; +} + +/* * Callback function for the DNS resolver. * Continue connecting the socket, or abort upon error condition. * S->web is checked to assert the operation wasn't aborted while waiting. */ -void a_Http_dns_cb(int Status, Dlist *addr_list, void *data) +static void Http_dns_cb(int Status, Dlist *addr_list, void *data) { int SKey = VOIDP2INT(data); SocketData_t *S; + HostConnection_t *hc; S = a_Klist_get_data(ValidSocks, SKey); if (S) { if (!a_Web_valid(S->web)) { - a_Chain_fcb(OpAbort, S->Info, NULL, NULL); + a_Chain_bfcb(OpAbort, S->Info, NULL, "Both"); dFree(S->Info); Http_socket_free(SKey); } else 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); - } - + S->flags |= HTTP_SOCKET_QUEUED; + if (S->flags & HTTP_SOCKET_USE_PROXY) + hc = Http_host_connection_get(URL_HOST(HTTP_Proxy)); + else + hc = Http_host_connection_get(URL_HOST(S->web->url)); + Http_socket_enqueue(&hc->queue, S); + Http_connect_queued_sockets(hc); } 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->web->url)); - a_Chain_fcb(OpAbort, S->Info, NULL, NULL); + (S->flags & HTTP_SOCKET_USE_PROXY) ? URL_HOST_(HTTP_Proxy) : + URL_HOST_(S->web->url)); + a_Chain_bfcb(OpAbort, S->Info, NULL, "Both"); dFree(S->Info); Http_socket_free(SKey); } @@ -467,11 +602,11 @@ static int Http_get(ChainLink *Info, void *Data1) if (Http_must_use_proxy(S->web->url)) { hostname = dStrdup(URL_HOST(HTTP_Proxy)); S->port = URL_PORT(HTTP_Proxy); - S->use_proxy = TRUE; + S->flags |= HTTP_SOCKET_USE_PROXY; } else { hostname = dStrdup(URL_HOST(S->web->url)); S->port = URL_PORT(S->web->url); - S->use_proxy = FALSE; + S->flags &= ~HTTP_SOCKET_USE_PROXY; } /* Let the user know what we'll do */ @@ -479,7 +614,7 @@ static int Http_get(ChainLink *Info, void *Data1) /* 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); + a_Dns_resolve(hostname, Http_dns_cb, Info->LocalKey); dFree(hostname); return 0; @@ -493,6 +628,8 @@ void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info, { int SKey = VOIDP2INT(Info->LocalKey); + (void)Data2; /* suppress unused parameter warning */ + dReturn_if_fail( a_Chain_check("a_Http_ccc", Op, Branch, Dir, Info) ); if (Branch == 1) { @@ -503,6 +640,10 @@ void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info, /* ( Data1 = Web ) */ SKey = Http_sock_new(); Info->LocalKey = INT2VOIDP(SKey); + /* link IO */ + a_Chain_link_new(Info, a_Http_ccc, BCK, a_IO_ccc, 1, 1); + a_Chain_bcb(OpStart, Info, NULL, NULL); + /* async. connection */ Http_get(Info, Data1); break; case OpEnd: @@ -518,7 +659,7 @@ void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info, dFree(Info); break; } - } else { /* FWD */ + } else { /* 1 FWD */ /* HTTP send-query status branch */ switch (Op) { default: @@ -530,6 +671,82 @@ void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info, } +static void Http_socket_queue_init(SocketQueue_t *sq) +{ + sq->head = NULL; + sq->tail = NULL; +} + +static void Http_socket_enqueue(SocketQueue_t *sq, SocketData_t* sock) +{ + SocketQueueEntry_t *se = dNew(SocketQueueEntry_t, 1); + + se->sock = sock; + se->next = NULL; + + if (sq->tail) + sq->tail->next = se; + sq->tail = se; + + if (! sq->head) + sq->head = se; +} + +static SocketData_t* Http_socket_dequeue(SocketQueue_t *sq) +{ + SocketQueueEntry_t *se = sq->head; + SocketData_t *sd = NULL; + + if (se) { + sq->head = se->next; + if (sq->tail == se) + sq->tail = NULL; + sd = se->sock; + dFree(se); + } + + return sd; +} + +static HostConnection_t *Http_host_connection_get(const char *host) +{ + int i; + HostConnection_t *hc; + + for (i = 0; i < dList_length(host_connections); i++) { + hc = (HostConnection_t*) dList_nth_data(host_connections, i); + + if (dStrcasecmp(host, hc->host) == 0) + return hc; + } + + hc = dNew0(HostConnection_t, 1); + Http_socket_queue_init(&hc->queue); + hc->host = dStrdup(host); + dList_append(host_connections, hc); + + return hc; +} + +static void Http_host_connection_remove(HostConnection_t *hc) +{ + assert(hc->queue.head == NULL); + dList_remove_fast(host_connections, hc); + dFree(hc->host); + dFree(hc); +} + +static void Http_host_connection_remove_all() +{ + HostConnection_t *hc; + + while (dList_length(host_connections) > 0) { + hc = (HostConnection_t*) dList_nth_data(host_connections, 0); + while (Http_socket_dequeue(&hc->queue)); + Http_host_connection_remove(hc); + } + dList_free(host_connections); +} /* * Deallocate memory used by http module @@ -537,7 +754,9 @@ void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info, */ void a_Http_freeall(void) { + Http_host_connection_remove_all(); a_Klist_free(&ValidSocks); a_Url_free(HTTP_Proxy); dFree(HTTP_Proxy_Auth_base64); + dFree(HTTP_Language_hdr); } diff --git a/src/IO/iowatch.cc b/src/IO/iowatch.cc index 0749e24c..f67b711f 100644 --- a/src/IO/iowatch.cc +++ b/src/IO/iowatch.cc @@ -22,7 +22,8 @@ using namespace fltk; // void a_IOwatch_add_fd(int fd, int when, FileHandler Callback, void *usr_data=0) { - add_fd(fd, when, Callback, usr_data); + if (fd >= 0) + add_fd(fd, when, Callback, usr_data); } // @@ -30,6 +31,7 @@ void a_IOwatch_add_fd(int fd, int when, FileHandler Callback, void *usr_data=0) // void a_IOwatch_remove_fd(int fd, int when) { - remove_fd(fd, when); + if (fd >= 0) + remove_fd(fd, when); } diff --git a/src/IO/mime.c b/src/IO/mime.c index 33ec3322..9bffd619 100644 --- a/src/IO/mime.c +++ b/src/IO/mime.c @@ -96,16 +96,16 @@ static Viewer_t Mime_major_type_fetch(const char *Key, uint_t Size) void a_Mime_init() { #ifdef ENABLE_GIF - Mime_add_minor_type("image/gif", a_Gif_image); + Mime_add_minor_type("image/gif", a_Dicache_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); + Mime_add_minor_type("image/jpeg", a_Dicache_jpeg_image); + Mime_add_minor_type("image/pjpeg", a_Dicache_jpeg_image); + Mime_add_minor_type("image/jpg", a_Dicache_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 */ + Mime_add_minor_type("image/png", a_Dicache_png_image); + Mime_add_minor_type("image/x-png", a_Dicache_png_image); /* deprecated */ #endif Mime_add_minor_type("text/html", a_Html_text); diff --git a/src/IO/mime.h b/src/IO/mime.h index 0f51a1e2..0f20cf6d 100644 --- a/src/IO/mime.h +++ b/src/IO/mime.h @@ -5,7 +5,7 @@ * * 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 + * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. */ @@ -13,7 +13,6 @@ #define __MIME_H__ #include <config.h> -#include <stddef.h> #ifdef __cplusplus extern "C" { @@ -31,18 +30,12 @@ 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 +void *a_Dicache_png_image (const char *Type,void *web, CA_Callback_t *Call, + void **Data); +void *a_Dicache_gif_image(const char *Type, void *Ptr, CA_Callback_t *Call, + void **Data); +void *a_Dicache_jpeg_image(const char *Type, void *Ptr, CA_Callback_t *Call, + void **Data); /* * Functions defined inside Mime module diff --git a/src/Makefile.am b/src/Makefile.am index d4d03eea..0f09b716 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,25 +1,29 @@ -AM_CPPFLAGS=-DDILLORC_SYS='"$(sysconfdir)/dillorc"' @LIBJPEG_CPPFLAGS@ +AM_CPPFLAGS= \ + -I$(top_srcdir) \ + -DDILLO_SYSCONF='"$(sysconfdir)/"' \ + -DDILLO_DOCDIR='"$(docdir)/"' \ + @LIBJPEG_CPPFLAGS@ AM_CFLAGS = @LIBPNG_CFLAGS@ -AM_CXXFLAGS = -I.. @LIBPNG_CFLAGS@ @LIBFLTK_CXXFLAGS@ +AM_CXXFLAGS = @LIBPNG_CFLAGS@ @LIBFLTK_CXXFLAGS@ SUBDIRS = IO bin_PROGRAMS = dillo dillo_LDADD = \ - ../dlib/libDlib.a \ - ../dpip/libDpip.a \ + $(top_builddir)/dlib/libDlib.a \ + $(top_builddir)/dpip/libDpip.a \ IO/libDiof.a \ - ../dw/libDw-widgets.a \ - ../dw/libDw-fltk.a \ - ../dw/libDw-core.a \ - ../lout/liblout.a \ + $(top_builddir)/dw/libDw-widgets.a \ + $(top_builddir)/dw/libDw-fltk.a \ + $(top_builddir)/dw/libDw-core.a \ + $(top_builddir)/lout/liblout.a \ @LIBJPEG_LIBS@ @LIBPNG_LIBS@ @LIBFLTK_LIBS@ @LIBZ_LIBS@ @LIBICONV_LIBS@ dillo_SOURCES = \ dillo.cc \ - dir.c \ - dir.h \ + paths.cc \ + paths.hh \ ui.cc \ ui.hh \ uicmd.cc \ @@ -28,6 +32,8 @@ dillo_SOURCES = \ bw.c \ cookies.c \ cookies.h \ + auth.c \ + auth.h \ colors.c \ colors.h \ binaryconst.h \ @@ -37,6 +43,10 @@ dillo_SOURCES = \ history.c \ prefs.c \ prefs.h \ + prefsparser.cc \ + prefsparser.hh \ + keys.cc \ + keys.hh \ msg.h \ list.h \ url.c \ @@ -47,6 +57,8 @@ dillo_SOURCES = \ klist.h \ chain.c \ chain.h \ + utf8.cc \ + utf8.hh \ timeout.cc \ timeout.hh \ dialog.cc \ @@ -65,6 +77,13 @@ dillo_SOURCES = \ dicache.h \ capi.c \ capi.h \ + css.cc \ + css.hh \ + cssparser.cc \ + cssparser.hh \ + doctree.hh \ + styleengine.cc \ + styleengine.hh \ plain.cc \ html.cc \ html.hh \ @@ -78,8 +97,13 @@ dillo_SOURCES = \ dns.c \ dns.h \ gif.c \ + dgif.h \ jpeg.c \ + djpeg.h \ png.c \ + dpng.h \ + imgbuf.cc \ + imgbuf.hh \ image.cc \ image.hh \ menu.hh \ @@ -88,6 +112,9 @@ dillo_SOURCES = \ dpiapi.h \ pixmaps.h \ findbar.cc \ - findbar.hh + findbar.hh \ + xembed.cc \ + xembed.hh -EXTRA_DIST = chg srch +EXTRA_DIST = chg srch keysrc +sysconf_DATA = keysrc diff --git a/src/auth.c b/src/auth.c new file mode 100644 index 00000000..91baca30 --- /dev/null +++ b/src/auth.c @@ -0,0 +1,539 @@ +/* + * File: auth.c + * + * Copyright 2008 Jeremy Henty <onepoint@starurchin.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* Handling of HTTP AUTH takes place here. + * This implementation aims to follow RFC 2617: + * http://www.ietf.org/rfc/rfc2617.txt + */ + + +#include <ctype.h> /* iscntrl */ +#include "auth.h" +#include "msg.h" +#include "misc.h" +#include "dialog.hh" +#include "../dlib/dlib.h" + + +typedef struct { + int ok; + const char *realm; +} AuthParse_t; + +typedef struct { + char *name; + Dlist *paths; /* stripped of any trailing '/', so the root path is "" */ + char *authorization; /* the authorization request header */ +} AuthRealm_t; + +typedef struct { + char *scheme; + char *authority; + Dlist *realms; +} AuthHost_t; + +typedef struct { + const char *realm_name; + const DilloUrl *url; +} AuthDialogData_t; + +/* + * Local data + */ +static Dlist *auth_hosts; + +/* + * Initialize the auth module. + */ +void a_Auth_init(void) +{ + auth_hosts = dList_new(1); +} + +static AuthParse_t *Auth_parse_new() +{ + AuthParse_t *auth_parse = dNew(AuthParse_t, 1); + auth_parse->ok = 0; + auth_parse->realm = NULL; + return auth_parse; +} + +static void Auth_parse_free(AuthParse_t *auth_parse) +{ + if (auth_parse) { + dFree((void *)auth_parse->realm); + dFree(auth_parse); + } +} + +static int Auth_path_is_inside(const char *path1, const char *path2, int len) +{ + /* + * path2 is effectively truncated to length len. Typically len will be + * strlen(path2), or 1 less when we want to ignore a trailing '/'. + */ + return + strncmp(path1, path2, len) == 0 && + (path1[len] == '\0' || path1[len] == '/'); +} + +/* + * Check valid chars. + * Return: 0 if invalid, 1 otherwise. + */ +static int Auth_is_token_char(char c) +{ + const char *invalid = "\"()<>@,;:\\[]?=/{} \t"; + return (strchr(invalid, c) || iscntrl((uchar_t)c)) ? 0 : 1; +} + +/* + * Unquote the content of a quoted string. + * Return: newly allocated unquoted content. + * + * Arguments: + * quoted: pointer to the first char *after* the initial double quote. + * size: the number of chars in the result, *not* including a final '\0'. + * + * Preconditions: + * quoted points to a correctly quoted and escaped string. + * size is the number of characters in the quoted string, *after* + * removing escape characters. + * + */ +static const char *Auth_unquote_value(const char *quoted, int size) +{ + char c, *value, *value_ptr; + value_ptr = value = dNew(char, size + 1); + while ((c = *quoted++) != '"') + *value_ptr++ = (c == '\\') ? *quoted++ : c; + *value_ptr = '\0'; + return value; +} + +/* + * Parse a quoted string. Save the result as the auth realm if required. + * Return: 1 if the parse succeeds, 0 otherwise. + */ +static int Auth_parse_quoted_string(AuthParse_t *auth_parse, int set_realm, + char **auth) +{ + char *value; + int size; + + /* parse the '"' */ + switch (*(*auth)++) { + case '"': + break; + case '\0': + case ',': + MSG("auth.c: missing Basic auth token value after '='\n"); + return 0; + break; + default: + MSG("auth.c: garbage in Basic auth after '='\n"); + return 0; + break; + } + + /* parse the rest */ + value = *auth; + size = 0; + while (1) { + switch (*(*auth)++) { + case '"': + if (set_realm) { + dFree((void *)auth_parse->realm); + auth_parse->realm = Auth_unquote_value(value, size); + auth_parse->ok = 1; + } + return 1; + break; + case '\0': + MSG("auth.c: auth string ended inside quoted string value\n"); + return 0; + break; + case '\\': + /* end of string? */ + if (!*(*auth)++) { + MSG("auth.c: " + "auth string ended inside quoted string value " + "immediately after \\\n"); + return 0; + } + /* fall through to the next case */ + default: + size++; + break; + } + } +} + +/* + * Parse a token-value pair. + * Return: 1 if the parse succeeds, 0 otherwise. + */ +static int Auth_parse_token_value(AuthParse_t *auth_parse, char **auth) +{ + char *token; + int token_size, set_realm; + static const char realm_token[] = "realm"; + + /* parse a token */ + token = *auth; + token_size = 0; + while (Auth_is_token_char(**auth)) { + (*auth)++; + token_size++; + } + if (token_size == 0) { + MSG("auth.c: Auth_parse_token_value: " + "missing Basic auth token\n"); + return 0; + } + + /* skip space characters */ + while (**auth == ' ') + (*auth)++; + + /* parse the '=' */ + switch (*(*auth)++) { + case '=': + break; + case '\0': + case ',': + MSG("auth.c: Auth_parse_token_value: " + "missing Basic auth token value\n"); + return 0; + break; + default: + MSG("auth.c: Auth_parse_token_value: " + "garbage after Basic auth token\n"); + return 0; + break; + } + + /* skip space characters */ + while (**auth == ' ') + (*auth)++; + + /* is this value the realm? */ + set_realm = + auth_parse->realm == NULL && + dStrncasecmp(realm_token,token,token_size) == 0 && + strlen(realm_token) == (size_t)token_size; + + return Auth_parse_quoted_string(auth_parse, set_realm, auth); +} + +static void Auth_parse_auth_basic(AuthParse_t *auth_parse, char **auth) +{ + int token_value_pairs_found; + + /* parse comma-separated token-value pairs */ + token_value_pairs_found = 0; + while (1) { + /* skip space and comma characters */ + while (**auth == ' ' || **auth == ',') + (*auth)++; + /* end of string? */ + if (!**auth) + break; + /* parse token-value pair */ + if (!Auth_parse_token_value(auth_parse, auth)) + break; + token_value_pairs_found = 1; + } + + if (!token_value_pairs_found) { + MSG("auth.c: Auth_parse_auth_basic: " + "missing Basic auth token-value pairs\n"); + return; + } + + if (!auth_parse->realm) { + MSG("auth.c: Auth_parse_auth_basic: " + "missing Basic auth realm\n"); + return; + } +} + +static void Auth_parse_auth(AuthParse_t *auth_parse, char *auth) +{ + _MSG("auth.c: Auth_parse_auth: auth = '%s'\n", auth); + if (dStrncasecmp(auth, "Basic ", 6) == 0) { + auth += 6; + Auth_parse_auth_basic(auth_parse, &auth); + } else { + MSG("auth.c: Auth_parse_auth: " + "unknown authorization scheme: auth = {%s}\n", + auth); + } +} + +/* + * Return the host that contains a URL, or NULL if there is no such host. + */ +static AuthHost_t *Auth_host_by_url(const DilloUrl *url) +{ + AuthHost_t *host; + int i; + + for (i = 0; (host = dList_nth_data(auth_hosts, i)); i++) + if (((dStrcasecmp(URL_SCHEME(url), host->scheme) == 0) && + (dStrcasecmp(URL_AUTHORITY(url), host->authority) == 0))) + return host; + + return NULL; +} + +/* + * Search all realms for the one with the given name. + */ +static AuthRealm_t *Auth_realm_by_name(const AuthHost_t *host, + const char *name) +{ + AuthRealm_t *realm; + int i; + + for (i = 0; (realm = dList_nth_data(host->realms, i)); i++) + if (strcmp(realm->name,name) == 0) + return realm; + + return NULL; +} + +/* + * Search all realms for the one with the best-matching path. + */ +static AuthRealm_t *Auth_realm_by_path(const AuthHost_t *host, + const char *path) +{ + AuthRealm_t *realm_best, *realm; + int i, j; + int match_length; + + match_length = 0; + realm_best = NULL; + for (i = 0; (realm = dList_nth_data(host->realms, i)); i++) { + char *realm_path; + + for (j = 0; (realm_path = dList_nth_data(realm->paths, j)); j++) { + int realm_path_length = strlen(realm_path); + if (Auth_path_is_inside(path, realm_path, realm_path_length) && + !(realm_best && match_length >= realm_path_length)) { + realm_best = realm; + match_length = realm_path_length; + } + } + } + + return realm_best; +} + +static int Auth_realm_includes_path(const AuthRealm_t *realm, const char *path) +{ + int i; + char *realm_path; + + for (i = 0; (realm_path = dList_nth_data(realm->paths, i)); i++) + if (Auth_path_is_inside(path, realm_path, strlen(realm_path))) + return 1; + + return 0; +} + +static void Auth_realm_add_path(AuthRealm_t *realm, const char *path) +{ + int len, i; + char *realm_path, *n_path; + + n_path = dStrdup(path); + len = strlen(n_path); + + /* remove trailing '/' */ + if (len && n_path[len - 1] == '/') + n_path[--len] = 0; + + /* delete existing paths that are inside the new one */ + for (i = 0; (realm_path = dList_nth_data(realm->paths, i)); i++) { + if (Auth_path_is_inside(realm_path, path, len)) { + dList_remove_fast(realm->paths, realm_path); + dFree(realm_path); + i--; /* reconsider this slot */ + } + } + + dList_append(realm->paths, n_path); +} + +/* + * Return the authorization header for an HTTP query. + */ +const char *a_Auth_get_auth_str(const DilloUrl *url) +{ + AuthHost_t *host; + AuthRealm_t *realm; + + return + ((host = Auth_host_by_url(url)) && + (realm = Auth_realm_by_path(host, URL_PATH(url)))) ? + realm->authorization : NULL; +} + +/* + * Determine whether the user needs to authenticate. + */ +static int Auth_do_auth_required(const char *realm_name, const DilloUrl *url) +{ + /* + * TO DO: I dislike the way that this code must decide whether we + * sent authentication during the request and trust us to resend it + * after the reload. Could it be more robust if every DilloUrl + * recorded its authentication, and whether it was accepted? (JCH) + */ + + AuthHost_t *host; + AuthRealm_t *realm; + + /* + * The size of the following comments reflects the concerns in the + * TO DO at the top of this function. It should not be so hard to + * explain why code is correct! (JCH) + */ + + /* + * If we have authentication but did not send it (because we did + * not know this path was in the realm) then we update the realm. + * We do not re-authenticate because our authentication is probably + * OK. Thanks to the updated realm the forthcoming reload will + * make us send the authentication. If our authentication is not + * OK the server will challenge us again after the reload and then + * we will re-authenticate. + */ + if ((host = Auth_host_by_url(url)) && + (realm = Auth_realm_by_name(host, realm_name)) && + (!Auth_realm_includes_path(realm, URL_PATH(url)))) { + _MSG("Auth_do_auth_required: updating realm '%s' with URL '%s'\n", + realm_name, URL_STR(url)); + Auth_realm_add_path(realm, URL_PATH(url)); + return 0; + } + + /* + * Either we had no authentication or we sent it and the server + * rejected it, so we must re-authenticate. + */ + return 1; +} + +static void Auth_do_auth_dialog_cb(const char *user, const char *password, + void *vData) +{ + AuthDialogData_t *data; + AuthHost_t *host; + AuthRealm_t *realm; + char *user_password, *response, *authorization, *authorization_old; + + data = (AuthDialogData_t *)vData; + + /* find or create the host */ + if (!(host = Auth_host_by_url(data->url))) { + /* create a new host */ + host = dNew(AuthHost_t, 1); + host->scheme = dStrdup(URL_SCHEME(data->url)); + host->authority = dStrdup(URL_AUTHORITY(data->url)); + host->realms = dList_new(1); + dList_append(auth_hosts, host); + } + + /* find or create the realm */ + if (!(realm = Auth_realm_by_name(host, data->realm_name))) { + /* create a new realm */ + realm = dNew(AuthRealm_t, 1); + realm->name = dStrdup(data->realm_name); + realm->paths = dList_new(1); + realm->authorization = NULL; + dList_append(host->realms, realm); + } + + Auth_realm_add_path(realm, URL_PATH(data->url)); + + /* create and set the authorization */ + user_password = dStrconcat(user, ":", password, NULL); + response = a_Misc_encode_base64(user_password); + authorization = + dStrconcat("Authorization: Basic ", response, "\r\n", NULL); + authorization_old = realm->authorization; + realm->authorization = authorization; + dFree(authorization_old); + dFree(user_password); + dFree(response); +} + +static int Auth_do_auth_dialog(const char *realm, const DilloUrl *url) +{ + int ret; + char *message; + AuthDialogData_t *data; + + _MSG("auth.c: Auth_do_auth_dialog: realm = '%s'\n", realm); + message = dStrconcat("Enter a user and password for \"", + realm, "\".", NULL); + data = dNew(AuthDialogData_t, 1); + data->realm_name = dStrdup(realm); + data->url = a_Url_dup(url); + ret = a_Dialog_user_password(message, Auth_do_auth_dialog_cb, data); + dFree(message); + dFree((void*)data->realm_name); + a_Url_free((void*)data->url); + dFree(data); + return ret; +} + +/* + * Do authorization for an auth string. + */ +static int Auth_do_auth(char *auth, const DilloUrl *url) +{ + int reload; + AuthParse_t *auth_parse; + + _MSG("auth.c: Auth_do_auth: auth={%s}\n", auth); + reload = 0; + auth_parse = Auth_parse_new(); + Auth_parse_auth(auth_parse, auth); + if (auth_parse->ok) + reload = + Auth_do_auth_required(auth_parse->realm, url) ? + Auth_do_auth_dialog(auth_parse->realm, url) + : 1; + Auth_parse_free(auth_parse); + + return reload; +} + +/* + * Do authorization for a set of auth strings. + */ +int a_Auth_do_auth(Dlist *auths, const DilloUrl *url) +{ + int reload, i; + char *auth; + + reload = 0; + for (i = 0; (auth = dList_nth_data(auths, i)); ++i) + if (Auth_do_auth(auth, url)) + reload = 1; + + return reload; +} + diff --git a/src/auth.h b/src/auth.h new file mode 100644 index 00000000..5f96f642 --- /dev/null +++ b/src/auth.h @@ -0,0 +1,19 @@ +#ifndef __AUTH_H__ +#define __AUTH_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include "url.h" + + +const char *a_Auth_get_auth_str(const DilloUrl *request_url); +int a_Auth_do_auth(Dlist *auth_string, const DilloUrl *url); +void a_Auth_init(void); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* !__AUTH_H__ */ diff --git a/src/bookmark.c b/src/bookmark.c index 588e51f8..9a594789 100644 --- a/src/bookmark.c +++ b/src/bookmark.c @@ -9,10 +9,7 @@ * (at your option) any later version. */ -#include <errno.h> -#include <stdio.h> #include <stdlib.h> -#include <string.h> #include "msg.h" #include "history.h" @@ -13,6 +13,7 @@ #include "bw.h" +#include "msg.h" #include "list.h" #include "capi.h" #include "uicmd.hh" @@ -58,11 +59,14 @@ BrowserWindow *a_Bw_new() bw->nav_expect_url = NULL; bw->redirect_level = 0; + bw->meta_refresh_status = 0; + bw->meta_refresh_url = NULL; bw->RootClients = dList_new(8); bw->ImageClients = dList_new(8); bw->NumImages = 0; bw->NumImagesGot = 0; + bw->NumPendingStyleSheets = 0; bw->PageUrls = dList_new(8); bw->Docs = dList_new(8); @@ -99,6 +103,8 @@ void a_Bw_free(BrowserWindow *bw) dFree(dList_nth_data(bw->nav_stack, j)); dList_free(bw->nav_stack); + a_Url_free(bw->meta_refresh_url); + dStr_free(bw->page_bugs, 1); dFree(bw); break; @@ -176,7 +182,7 @@ void a_Bw_stop_clients(BrowserWindow *bw, int flags) if (flags & BW_Root) { /* Remove root clients */ while ((data = dList_nth_data(bw->RootClients, 0))) { - a_Capi_stop_client(VOIDP2INT(data), (flags & Bw_Force)); + a_Capi_stop_client(VOIDP2INT(data), (flags & BW_Force)); dList_remove_fast(bw->RootClients, data); } } @@ -184,7 +190,7 @@ void a_Bw_stop_clients(BrowserWindow *bw, int flags) if (flags & BW_Img) { /* Remove image clients */ while ((data = dList_nth_data(bw->ImageClients, 0))) { - a_Capi_stop_client(VOIDP2INT(data), (flags & Bw_Force)); + a_Capi_stop_client(VOIDP2INT(data), (flags & BW_Force)); dList_remove_fast(bw->ImageClients, data); } } @@ -216,6 +222,38 @@ void a_Bw_add_doc(BrowserWindow *bw, void *vdoc) } /* + * Get current document. + */ +void *a_Bw_get_current_doc(BrowserWindow *bw) +{ + void *doc = NULL; + int len = dList_length(bw->Docs); + + if (len == 1) + doc = dList_nth_data(bw->Docs, 0); + else if (len > 1) + MSG("a_Bw_get_current_doc() multiple docs not implemented\n"); + + return doc; +} + +/* + * Get document by URL. + * + * This is currently used by popup menus that need to ensure that the + * page has not changed while the menu was popped up. + */ +void *a_Bw_get_url_doc(BrowserWindow *bw, const DilloUrl *url) +{ + void *doc = NULL; + + if (url && dList_find_custom(bw->PageUrls, url, (dCompareFunc)a_Url_cmp)) { + doc = a_Bw_get_current_doc(bw); + } + return doc; +} + +/* * Remove a document from the bw's list */ void a_Bw_remove_doc(BrowserWindow *bw, void *vdoc) @@ -253,6 +291,9 @@ void a_Bw_cleanup(BrowserWindow *bw) /* Zero image-progress data */ bw->NumImages = 0; bw->NumImagesGot = 0; + + /* Zero stylesheet counter */ + bw->NumPendingStyleSheets = 0; } /*--------------------------------------------------------------------------*/ @@ -1,8 +1,6 @@ #ifndef __BW_H__ #define __BW_H__ -#include <sys/types.h> - #include "url.h" /* for DilloUrl */ /* @@ -10,7 +8,7 @@ */ #define BW_Root (1) /* Root URLs */ #define BW_Img (2) /* Image URLs */ -#define Bw_Force (4) /* Stop connection too */ +#define BW_Force (4) /* Stop connection too */ typedef struct _BrowserWindow BrowserWindow; @@ -38,6 +36,8 @@ struct _BrowserWindow int NumImages; /* Number of images already loaded */ int NumImagesGot; + /* Number of not yet arrived style sheets */ + int NumPendingStyleSheets; /* List of all Urls requested by this page (and its types) */ Dlist *PageUrls; @@ -57,8 +57,11 @@ struct _BrowserWindow * redirection loops (accounts for WEB_RootUrl only) */ int redirect_level; - /* TODO: maybe this fits better in the linkblock. - * Although having it here avoids having a signal for handling it. */ + /* Url for zero-delay redirections in the META element */ + int meta_refresh_status; + DilloUrl *meta_refresh_url; + + /* HTML-bugs detected at parse time */ int num_page_bugs; Dstr *page_bugs; }; @@ -80,10 +83,13 @@ 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_doc(BrowserWindow *bw, void *vdoc); +void *a_Bw_get_current_doc(BrowserWindow *bw); +void *a_Bw_get_url_doc(BrowserWindow *bw, const DilloUrl *Url); void a_Bw_remove_doc(BrowserWindow *bw, void *vdoc); void a_Bw_add_url(BrowserWindow *bw, const DilloUrl *Url); void a_Bw_cleanup(BrowserWindow *bw); +typedef void (*BwCallback_t)(BrowserWindow *bw, const void *data); #ifdef __cplusplus } diff --git a/src/cache.c b/src/cache.c index 9ddb0abf..fcd27a05 100644 --- a/src/cache.c +++ b/src/cache.c @@ -16,11 +16,8 @@ #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 "IO/Url.h" @@ -32,6 +29,7 @@ #include "misc.h" #include "capi.h" #include "decode.h" +#include "auth.h" #include "timeout.hh" #include "uicmd.hh" @@ -52,8 +50,10 @@ typedef struct { char *TypeDet; /* MIME type string (detected from data) */ char *TypeHdr; /* MIME type string as from the HTTP Header */ char *TypeMeta; /* MIME type string from META HTTP-EQUIV */ + char *TypeNorm; /* MIME type string normalized */ Dstr *Header; /* HTTP header */ const DilloUrl *Location; /* New URI for redirects */ + Dlist *Auth; /* Authentication fields */ Dstr *Data; /* Pointer to raw data */ Dstr *UTF8Data; /* Data after charset translation */ int DataRefcount; /* Reference count */ @@ -85,9 +85,10 @@ static uint_t DelayedQueueIdleId = 0; /* * Forward declarations */ -static void Cache_process_queue(CacheEntry_t *entry); +static CacheEntry_t *Cache_process_queue(CacheEntry_t *entry); static void Cache_delayed_process_queue(CacheEntry_t *entry); - +static void Cache_auth_entry(CacheEntry_t *entry, BrowserWindow *bw); +static void Cache_entry_inject(const DilloUrl *Url, Dstr *data_ds); /* * Determine if two cache entries are equal (used by CachedURLs) @@ -123,7 +124,7 @@ void a_Cache_init(void) { DilloUrl *url = a_Url_new("about:splash", NULL); Dstr *ds = dStr_new(AboutSplash); - a_Cache_entry_inject(url, ds); + Cache_entry_inject(url, ds); dStr_free(ds, 1); a_Url_free(url); } @@ -132,18 +133,6 @@ void a_Cache_init(void) /* 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-field is just a reference (except 'Web'). * - Return a unique number for identifying the client. @@ -151,14 +140,18 @@ static int Cache_client_make_key(void) static int Cache_client_enqueue(const DilloUrl *Url, DilloWeb *Web, CA_Callback_t Callback, void *CbData) { - int ClientKey; + static int ClientKey = 0; /* Provide a primary key for each client */ CacheClient_t *NewClient; + if (++ClientKey <= 0) + ClientKey = 1; + NewClient = dNew(CacheClient_t, 1); - ClientKey = Cache_client_make_key(); NewClient->Key = ClientKey; NewClient->Url = Url; + NewClient->Version = 0; NewClient->Buf = NULL; + NewClient->BufSize = 0; NewClient->Callback = Callback; NewClient->CbData = CbData; NewClient->Web = Web; @@ -204,8 +197,10 @@ static void Cache_entry_init(CacheEntry_t *NewEntry, const DilloUrl *Url) NewEntry->TypeDet = NULL; NewEntry->TypeHdr = NULL; NewEntry->TypeMeta = NULL; + NewEntry->TypeNorm = NULL; NewEntry->Header = dStr_new(""); NewEntry->Location = NULL; + NewEntry->Auth = NULL; NewEntry->Data = dStr_sized_new(8*1024); NewEntry->UTF8Data = NULL; NewEntry->DataRefcount = 0; @@ -272,7 +267,7 @@ static CacheEntry_t *Cache_entry_add(const DilloUrl *Url) * 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) +static void Cache_entry_inject(const DilloUrl *Url, Dstr *data_ds) { CacheEntry_t *entry; @@ -288,6 +283,18 @@ void a_Cache_entry_inject(const DilloUrl *Url, Dstr *data_ds) } /* + * Free Authentication fields. + */ +static void Cache_auth_free(Dlist *auth) +{ + int i; + void *auth_field; + for (i = 0; (auth_field = dList_nth_data(auth, i)); ++i) + dFree(auth_field); + dList_free(auth); +} + +/* * Free the components of a CacheEntry_t struct. */ static void Cache_entry_free(CacheEntry_t *entry) @@ -296,8 +303,10 @@ static void Cache_entry_free(CacheEntry_t *entry) dFree(entry->TypeDet); dFree(entry->TypeHdr); dFree(entry->TypeMeta); + dFree(entry->TypeNorm); dStr_free(entry->Header, TRUE); a_Url_free((DilloUrl *)entry->Location); + Cache_auth_free(entry->Auth); dStr_free(entry->Data, 1); dStr_free(entry->UTF8Data, 1); if (entry->CharsetDecoder) @@ -353,14 +362,13 @@ void a_Cache_entry_remove_by_url(DilloUrl *url) * 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. + * - 'Web' is an auxiliary 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) { @@ -394,6 +402,15 @@ int a_Cache_open_url(void *web, CA_Callback_t Call, void *CbData) */ uint_t a_Cache_get_flags(const DilloUrl *url) { + CacheEntry_t *entry = Cache_entry_search(url); + return (entry ? entry->Flags : 0); +} + +/* + * Get cache entry status (following redirections). + */ +uint_t a_Cache_get_flags_with_redirection(const DilloUrl *url) +{ CacheEntry_t *entry = Cache_entry_search_with_redirect(url); return (entry ? entry->Flags : 0); } @@ -406,7 +423,9 @@ static void Cache_ref_data(CacheEntry_t *entry) if (entry) { entry->DataRefcount++; _MSG("DataRefcount++: %d\n", entry->DataRefcount); - if (entry->CharsetDecoder && entry->DataRefcount == 1) { + if (entry->CharsetDecoder && + (!entry->UTF8Data || entry->DataRefcount == 1)) { + dStr_free(entry->UTF8Data, 1); entry->UTF8Data = a_Decode_process(entry->CharsetDecoder, entry->Data->str, entry->Data->len); @@ -440,8 +459,8 @@ static void Cache_unref_data(CacheEntry_t *entry) */ static const char *Cache_current_content_type(CacheEntry_t *entry) { - return entry->TypeMeta ? entry->TypeMeta : entry->TypeHdr ? entry->TypeHdr : - entry->TypeDet; + return entry->TypeNorm ? entry->TypeNorm : entry->TypeMeta ? entry->TypeMeta + : entry->TypeHdr ? entry->TypeHdr : entry->TypeDet; } /* @@ -464,44 +483,53 @@ static Dstr *Cache_data(CacheEntry_t *entry) /* * Change Content-Type for cache entry found by url. + * from = { "http" | "meta" } * Return new content type. */ const char *a_Cache_set_content_type(const DilloUrl *url, const char *ctype, - bool_t force) + const char *from) { const char *curr; - CacheEntry_t *entry = Cache_entry_search_with_redirect(url); + char *major, *minor, *charset; + CacheEntry_t *entry = Cache_entry_search(url); - if (!entry) - return NULL; + dReturn_val_if_fail (entry != NULL, NULL); - curr = Cache_current_content_type(entry); - if (entry->TypeMeta && (force == FALSE)) { - /* it's already been set */ - return curr; - } - - if (a_Misc_content_type_cmp(curr, ctype)) { - char *charset; + _MSG("a_Cache_set_content_type {%s} {%s}\n", ctype, URL_STR(url)); - dFree(entry->TypeMeta); - curr = entry->TypeMeta = dStrdup(ctype); - - if (entry->CharsetDecoder) - a_Decode_free(entry->CharsetDecoder); - a_Misc_parse_content_type(ctype, NULL, NULL, &charset); - entry->CharsetDecoder = a_Decode_charset_init(charset); - dFree(charset); + curr = Cache_current_content_type(entry); + if (entry->TypeMeta || (*from == 'h' && entry->TypeHdr) ) { + /* Type is already been set. Do nothing. + * BTW, META overrides TypeHdr */ + } else { + if (*from == 'h') { + /* Content-Type from HTTP header */ + entry->TypeHdr = dStrdup(ctype); + } else { + /* Content-Type from META */ + entry->TypeMeta = dStrdup(ctype); + } + if (a_Misc_content_type_cmp(curr, ctype)) { + /* ctype gives one different from current */ + a_Misc_parse_content_type(ctype, &major, &minor, &charset); + if (*from == 'm' && charset && + ((!major || !*major) && (!minor || !*minor))) { + /* META only gives charset; use detected MIME type too */ + entry->TypeNorm = dStrconcat(entry->TypeDet, ctype, NULL); + } + if (charset) { + if (entry->CharsetDecoder) + a_Decode_free(entry->CharsetDecoder); + entry->CharsetDecoder = a_Decode_charset_init(charset); + curr = Cache_current_content_type(entry); - dStr_free(entry->UTF8Data, 1); - if (entry->CharsetDecoder && entry->DataRefcount > 0) - entry->UTF8Data = a_Decode_process(entry->CharsetDecoder, - entry->Data->str, - entry->Data->len); - else - entry->UTF8Data = NULL; + /* Invalidate UTF8Data */ + dStr_free(entry->UTF8Data, 1); + entry->UTF8Data = NULL; + } + dFree(major); dFree(minor); dFree(charset); + } } - return curr; } @@ -513,8 +541,9 @@ int a_Cache_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize) { CacheEntry_t *entry = Cache_entry_search_with_redirect(Url); if (entry) { + Dstr *data; Cache_ref_data(entry); - Dstr *data = Cache_data(entry); + data = Cache_data(entry); *PBuf = data->str; *BufSize = data->len; } else { @@ -556,14 +585,16 @@ static char *Cache_parse_field(const char *header, const char *fieldname) } i += j; - while (header[i] == ' ') i++; if (header[i] == ':') { /* Field found! */ while (header[++i] == ' ' || header[i] == '\t'); for (j = 0; header[i + j] != '\n'; j++); + while (j && (header[i + j - 1] == ' ' || header[i + j - 1] == '\t')) + j--; field = dStrndup(header + i, j); return field; } + while (header[i] != '\n') i++; } return NULL; } @@ -590,13 +621,16 @@ static Dlist *Cache_parse_multiple_fields(const char *header, } i += j; - for ( ; header[i] == ' '; i++); if (header[i] == ':') { /* Field found! */ while (header[++i] == ' ' || header[i] == '\t'); for (j = 0; header[i + j] != '\n'; j++); + while (j && (header[i + j - 1] == ' ' || header[i + j - 1] == '\t')) + j--; field = dStrndup(header + i, j); dList_append(fields, field); + } else { + while (header[i] != '\n') i++; } } @@ -614,15 +648,16 @@ static Dlist *Cache_parse_multiple_fields(const char *header, static void Cache_parse_header(CacheEntry_t *entry) { char *header = entry->Header->str; - char *Length, *Type, *location_str, *encoding, *charset; + char *Length, *Type, *location_str, *encoding; #ifndef DISABLE_COOKIES Dlist *Cookies; #endif - DilloUrl *location_url; Dlist *warnings; void *data; int i; + _MSG("Cache_parse_header\n"); + if (entry->Header->len > 12) { if (header[9] == '1' && header[10] == '0' && header[11] == '0') { /* 100: Continue. The "real" header has not come yet. */ @@ -634,26 +669,31 @@ static void Cache_parse_header(CacheEntry_t *entry) } 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 Temporary Redirect */ - - location_str = Cache_parse_field(header, "Location"); - location_url = a_Url_new(location_str, URL_STR_(entry->Url)); - if (URL_FLAGS(location_url) & (URL_Post + URL_Get) && - dStrcasecmp(URL_SCHEME(location_url), "dpi") == 0 && - dStrcasecmp(URL_SCHEME(entry->Url), "dpi") != 0) { - /* Forbid dpi GET and POST from non dpi-generated urls */ - MSG("Redirection Denied! '%s' -> '%s'\n", - URL_STR(entry->Url), URL_STR(location_url)); - a_Url_free(location_url); - } else { - entry->Location = location_url; + if ((location_str = Cache_parse_field(header, "Location"))) { + DilloUrl *location_url; + + 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 Temporary Redirect */ + + location_url = a_Url_new(location_str, URL_STR_(entry->Url)); + if (URL_FLAGS(location_url) & (URL_Post + URL_Get) && + dStrcasecmp(URL_SCHEME(location_url), "dpi") == 0 && + dStrcasecmp(URL_SCHEME(entry->Url), "dpi") != 0) { + /* Forbid dpi GET and POST from non dpi-generated urls */ + MSG("Redirection Denied! '%s' -> '%s'\n", + URL_STR(entry->Url), URL_STR(location_url)); + a_Url_free(location_url); + } else { + entry->Location = location_url; + } + dFree(location_str); } - dFree(location_str); - + } else if (strncmp(header + 9, "401", 3) == 0) { + entry->Auth = + Cache_parse_multiple_fields(header, "WWW-Authenticate"); } else if (strncmp(header + 9, "404", 3) == 0) { entry->Flags |= CA_NotFound; } @@ -667,16 +707,22 @@ static void Cache_parse_header(CacheEntry_t *entry) dList_free(warnings); } + /* + * Get Transfer-Encoding and initialize decoder + */ + encoding = Cache_parse_field(header, "Transfer-Encoding"); + entry->TransferDecoder = a_Decode_transfer_init(encoding); + + if ((Length = Cache_parse_field(header, "Content-Length")) != NULL) { - char *tmp; - if ((tmp = Cache_parse_field(header, "Transfer-Encoding"))) { + if (encoding) { /* - * BUG: Should test for _presence_ of headers, not whether they - * have content. + * If Transfer-Encoding is present, Content-Length must be ignored. + * If the Transfer-Encoding is non-identity, it is an error. */ - MSG_HTTP("Both Content-Length and Transfer-Encoding headers" - " received.\n"); - dFree(tmp); + if (dStrcasecmp(encoding, "identity")) + MSG_HTTP("Content-Length and non-identity Transfer-Encoding " + "headers both present.\n"); } else { entry->Flags |= CA_GotLength; entry->ExpectedSize = MAX(strtol(Length, NULL, 10), 0); @@ -684,27 +730,21 @@ static void Cache_parse_header(CacheEntry_t *entry) dFree(Length); } + dFree(encoding); /* free Transfer-Encoding */ + #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); + if ((Cookies = Cache_parse_multiple_fields(header, "Set-Cookie"))) { + char *server_date = Cache_parse_field(header, "Date"); + + a_Cookies_set(Cookies, entry->Url, server_date); for (i = 0; (data = dList_nth_data(Cookies, i)); ++i) dFree(data); dList_free(Cookies); + dFree(server_date); } #endif /* !DISABLE_COOKIES */ /* - * Get Transfer-Encoding and initialize decoder - */ - encoding = Cache_parse_field(header, "Transfer-Encoding"); - entry->TransferDecoder = a_Decode_transfer_init(encoding); - dFree(encoding); - - /* * Get Content-Encoding and initialize decoder */ encoding = Cache_parse_field(header, "Content-Encoding"); @@ -721,28 +761,17 @@ static void Cache_parse_header(CacheEntry_t *entry) dStr_free(entry->Data, 1); entry->Data = dStr_sized_new(MIN(entry->ExpectedSize, MAX_INIT_BUF)); } - Cache_ref_data(entry); /* Get Content-Type */ - if ((Type = Cache_parse_field(header, "Content-Type")) == NULL) { - if (!((entry->Flags & CA_GotLength) && (entry->ExpectedSize == 0))) { - /* unless the server sent Content-Length: 0 */ - MSG_HTTP("Server didn't send Content-Type in header.\n"); - } - } else { - entry->TypeHdr = Type; - _MSG("Content-Type {%s} {%s}\n", Type, URL_STR(entry->Url)); - /* This Content-Type is not trusted. It's checked against real data - * in Cache_process_queue(); only then CA_GotContentType becomes true. - */ - a_Misc_parse_content_type(Type, NULL, NULL, &charset); - if (charset) { - entry->CharsetDecoder = a_Decode_charset_init(charset); - if (entry->CharsetDecoder) - entry->UTF8Data = dStr_new(""); - dFree(charset); - } + if ((Type = Cache_parse_field(header, "Content-Type"))) { + /* This HTTP Content-Type is not trusted. It's checked against real data + * in Cache_process_queue(); only then CA_GotContentType becomes true. */ + a_Cache_set_content_type(entry->Url, Type, "http"); + _MSG("TypeHdr {%s} {%s}\n", Type, URL_STR(entry->Url)); + _MSG("TypeMeta {%s}\n", entry->TypeMeta); + dFree(Type); } + Cache_ref_data(entry); } /* @@ -762,7 +791,7 @@ static int Cache_get_header(CacheEntry_t *entry, continue; if (N == 1 && (buf[i] == ' ' || buf[i] == '\t')) { /* unfold multiple-line header */ - MSG("Multiple-line header!\n"); + _MSG("Multiple-line header!\n"); dStr_erase(hdr, hdr->len - 1, 1); } N = (buf[i] == '\n') ? N + 1 : 0; @@ -791,15 +820,64 @@ static int Cache_get_header(CacheEntry_t *entry, void a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, const DilloUrl *Url) { - int offset = 0; - int len; + int offset, len; const char *str; + Dstr *dstr1, *dstr2, *dstr3; CacheEntry_t *entry = Cache_entry_search(Url); /* Assert a valid entry (not aborted) */ dReturn_if_fail (entry != NULL); - if (Op == IOClose) { + _MSG("__a_Cache_process_dbuf__\n"); + + if (Op == IORead) { + /* + * Cache_get_header() will set CA_GotHeader if it has a full header, and + * Cache_parse_header() will unset it if the header ends being + * merely an informational response from the server (i.e., 100 Continue) + */ + for (offset = 0; !(entry->Flags & CA_GotHeader) && + (len = Cache_get_header(entry, buf + offset, buf_size - offset)); + Cache_parse_header(entry) ) { + offset += len; + } + + if (entry->Flags & CA_GotHeader) { + str = buf + offset; + len = buf_size - offset; + entry->TransferSize += len; + dstr1 = dstr2 = dstr3 = NULL; + + /* Decode arrived data (<= 3 stages) */ + if (entry->TransferDecoder) { + dstr1 = a_Decode_process(entry->TransferDecoder, str, len); + str = dstr1->str; + len = dstr1->len; + } + if (entry->ContentDecoder) { + dstr2 = a_Decode_process(entry->ContentDecoder, str, len); + str = dstr2->str; + len = dstr2->len; + } + dStr_append_l(entry->Data, str, len); + if (entry->CharsetDecoder && entry->UTF8Data) { + dstr3 = a_Decode_process(entry->CharsetDecoder, str, len); + dStr_append_l(entry->UTF8Data, dstr3->str, dstr3->len); + } + dStr_free(dstr1, 1); + dStr_free(dstr2, 1); + dStr_free(dstr3, 1); + + if (entry->Data->len) + entry->Flags &= ~CA_IsEmpty; + + entry = Cache_process_queue(entry); + } + } else if (Op == IOClose) { + if ((entry->ExpectedSize || entry->TransferSize) && + entry->TypeHdr == NULL) { + MSG_HTTP("Message with a body lacked Content-Type header.\n"); + } if ((entry->Flags & CA_GotLength) && (entry->ExpectedSize != entry->TransferSize)) { MSG_HTTP("Content-Length does NOT match message body,\n" @@ -818,65 +896,16 @@ void a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, entry->ContentDecoder = NULL; } dStr_fit(entry->Data); /* fit buffer size! */ - Cache_process_queue(entry); - if (entry->Flags & CA_GotHeader) { - Cache_unref_data(entry); + + if ((entry = Cache_process_queue(entry))) { + if (entry->Flags & CA_GotHeader) { + Cache_unref_data(entry); + } } - return; } else if (Op == IOAbort) { /* unused */ MSG("a_Cache_process_dbuf Op = IOAbort; not implemented!\n"); - return; - } - - /* - * Cache_get_header() will set CA_GotHeader if it has a full header, and - * Cache_parse_header() will unset it if the header turns out to have been - * merely an informational response from the server (i.e., 100 Continue) - */ - while (!(entry->Flags & CA_GotHeader) && - (len = Cache_get_header(entry, buf + offset, buf_size - offset))) { - offset += len; - /* Let's scan, allocate, and set things according to header info */ - Cache_parse_header(entry); } - - if (!(entry->Flags & CA_GotHeader)) - return; - - str = buf + offset; - len = buf_size - offset; - entry->TransferSize += len; - - if (entry->TransferDecoder) { - Dstr *dbuf = a_Decode_process(entry->TransferDecoder, str, len); - str = dbuf->str; - len = dbuf->len; - dStr_free(dbuf, 0); - } - if (entry->ContentDecoder) { - Dstr *dbuf = a_Decode_process(entry->ContentDecoder, str, len); - if (entry->TransferDecoder) - dFree((char *)str); - str = dbuf->str; - len = dbuf->len; - dStr_free(dbuf, 0); - } - dStr_append_l(entry->Data, str, len); - - if (entry->UTF8Data) { - Dstr *dbuf = a_Decode_process(entry->CharsetDecoder, str, len); - dStr_append_l(entry->UTF8Data, dbuf->str, dbuf->len); - dStr_free(dbuf, 1); - } - - if (entry->TransferDecoder || entry->ContentDecoder) - dFree((char *)str); - - if (entry->Data->len) - entry->Flags &= ~CA_IsEmpty; - - Cache_process_queue(entry); } /* @@ -912,7 +941,7 @@ static int Cache_redirect(CacheEntry_t *entry, int Flags, BrowserWindow *bw) NewUrl = a_Url_new(URL_STR_(entry->Location), URL_STR_(entry->Url)); if (entry->Flags & CA_TempRedirect) a_Url_set_flags(NewUrl, URL_FLAGS(NewUrl) | URL_E2EQuery); - a_Nav_push(bw, NewUrl); + a_Nav_push(bw, NewUrl, entry->Url); a_Url_free(NewUrl); } else { /* Sub entity redirection (most probably an image) */ @@ -926,11 +955,55 @@ static int Cache_redirect(CacheEntry_t *entry, int Flags, BrowserWindow *bw) return 0; } +typedef struct { + Dlist *auth; + DilloUrl *url; + BrowserWindow *bw; +} CacheAuthData_t; + +/* + * Ask for user/password and reload the page. + */ +static void Cache_auth_callback(void *vdata) +{ + CacheAuthData_t *data = (CacheAuthData_t *)vdata; + if (a_Auth_do_auth(data->auth, data->url)) + a_Nav_reload(data->bw); + Cache_auth_free(data->auth); + a_Url_free(data->url); + dFree(data); + Cache_auth_entry(NULL, NULL); + a_Timeout_remove(); +} + +/* + * Set a timeout function to ask for user/password. + */ +static void Cache_auth_entry(CacheEntry_t *entry, BrowserWindow *bw) +{ + static int busy = 0; + CacheAuthData_t *data; + + if (!entry) { + busy = 0; + } else if (busy) { + MSG_WARN("Cache_auth_entry: caught busy!\n"); + } else if (entry->Auth) { + busy = 1; + data = dNew(CacheAuthData_t, 1); + data->auth = entry->Auth; + data->url = a_Url_dup(entry->Url); + data->bw = bw; + entry->Auth = NULL; + a_Timeout_add(0.0, Cache_auth_callback, data); + } +} + /* * Check whether a URL scheme is downloadable. * Return: 1 enabled, 0 disabled. */ -static int Cache_download_enabled(const DilloUrl *url) +int a_Cache_download_enabled(const DilloUrl *url) { if (!dStrcasecmp(URL_SCHEME(url), "http") || !dStrcasecmp(URL_SCHEME(url), "https") || @@ -961,6 +1034,24 @@ static void Cache_null_client(int Op, CacheClient_t *Client) return; } +typedef struct { + BrowserWindow *bw; + DilloUrl *url; +} Cache_savelink_t; + +/* + * Save link from behind a timeout so that Cache_process_queue() can + * get on with its work. + */ +static void Cache_savelink_cb(void *vdata) +{ + Cache_savelink_t *data = (Cache_savelink_t*) vdata; + + a_UIcmd_save_link(data->bw, data->url); + a_Url_free(data->url); + dFree(data); +} + /* * Update cache clients for a single cache-entry * Tasks: @@ -969,9 +1060,11 @@ static void Cache_null_client(int Op, CacheClient_t *Client) * - Remove clients when done * - Call redirect handler * + * Return: Cache entry, which may be NULL if it has been removed. + * * TODO: Implement CA_Abort Op in client callback */ -static void Cache_process_queue(CacheEntry_t *entry) +static CacheEntry_t *Cache_process_queue(CacheEntry_t *entry) { uint_t i; int st; @@ -988,7 +1081,7 @@ static void Cache_process_queue(CacheEntry_t *entry) if (Busy) MSG_ERR("FATAL!: >>>> Cache_process_queue Caught busy!!! <<<<\n"); if (!(entry->Flags & CA_GotHeader)) - return; + return entry; if (!(entry->Flags & CA_GotContentType)) { st = a_Misc_get_content_type_from_data( entry->Data->str, entry->Data->len, &Type); @@ -1002,7 +1095,7 @@ static void Cache_process_queue(CacheEntry_t *entry) entry->TypeDet = dStrdup(Type); entry->Flags |= CA_GotContentType; } else - return; /* i.e., wait for more data */ + return entry; /* i.e., wait for more data */ } Busy = TRUE; @@ -1020,6 +1113,7 @@ static void Cache_process_queue(CacheEntry_t *entry) if (TypeMismatch) { a_UIcmd_set_msg(Client_bw,"HTTP warning: Content-Type '%s' " "doesn't match the real data.", entry->TypeHdr); + OfferDownload = TRUE; } if (entry->Flags & CA_Redirect) { if (!Client->Callback) { @@ -1046,13 +1140,13 @@ static void Cache_process_queue(CacheEntry_t *entry) if (TypeMismatch) { AbortEntry = TRUE; } else { - const char *content_type = Cache_current_content_type(entry); - st = a_Web_dispatch_by_type(content_type, ClientWeb, + const char *curr_type = Cache_current_content_type(entry); + st = a_Web_dispatch_by_type(curr_type, ClientWeb, &Client->Callback, &Client->CbData); if (st == -1) { /* MIME type is not viewable */ if (ClientWeb->flags & WEB_RootUrl) { - MSG("Content-Type '%s' not viewable.\n", content_type); + MSG("Content-Type '%s' not viewable.\n", curr_type); /* prepare a download offer... */ AbortEntry = OfferDownload = TRUE; } else { @@ -1081,8 +1175,10 @@ static void Cache_process_queue(CacheEntry_t *entry) if ((Client->BufSize = data->len) > 0) { Client->Buf = data->str; (Client->Callback)(CA_Send, Client); - if (ClientWeb->flags & WEB_RootUrl) - a_UIcmd_set_page_prog(Client_bw, data->len, 1); + if (ClientWeb->flags & WEB_RootUrl) { + /* show size of page received */ + a_UIcmd_set_page_prog(Client_bw, entry->Data->len, 1); + } } /* Remove client when done */ @@ -1107,11 +1203,20 @@ static void Cache_process_queue(CacheEntry_t *entry) /* 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); + entry = NULL; + if (OfferDownload) { + /* Remove entry when 'conn' is already done */ + Cache_entry_remove(NULL, url); + if (a_Cache_download_enabled(url)) { + Cache_savelink_t *data = dNew(Cache_savelink_t, 1); + data->bw = Client_bw; + data->url = a_Url_dup(url); + a_Timeout_add(0.0, Cache_savelink_cb, data); + } } a_Url_free(url); + } else if (entry->Auth && (entry->Flags & CA_GotData)) { + Cache_auth_entry(entry, Client_bw); } /* Trigger cleanup when there are no cache clients */ @@ -1122,21 +1227,19 @@ static void Cache_process_queue(CacheEntry_t *entry) Busy = FALSE; _MSG("QueueSize ====> %d\n", dList_length(ClientQueue)); + return entry; } /* * Callback function for Cache_delayed_process_queue. */ -static void Cache_delayed_process_queue_callback(void *data) +static void Cache_delayed_process_queue_callback() { CacheEntry_t *entry; while ((entry = (CacheEntry_t *)dList_nth_data(DelayedQueue, 0))) { Cache_ref_data(entry); - Cache_process_queue(entry); - if (entry != dList_nth_data(DelayedQueue, 0)) { - /* Cache_process_queue() has removed the entry! */ - } else { + if ((entry = Cache_process_queue(entry))) { Cache_unref_data(entry); dList_remove(DelayedQueue, entry); } @@ -1189,10 +1292,23 @@ CacheClient_t *a_Cache_client_get_if_unique(int Key) void a_Cache_stop_client(int Key) { CacheClient_t *Client; + CacheEntry_t *entry; + DICacheEntry *DicEntry; + /* The client can be in both queues at the same time */ if ((Client = dList_find_custom(ClientQueue, INT2VOIDP(Key), Cache_client_by_key_cmp))) { + /* Dicache */ + if ((DicEntry = a_Dicache_get_entry(Client->Url, Client->Version))) + a_Dicache_unref(Client->Url, Client->Version); + + /* DelayedQueue */ + if ((entry = Cache_entry_search(Client->Url))) + dList_remove(DelayedQueue, entry); + + /* Main queue */ Cache_client_dequeue(Client, NULLKey); + } else { _MSG("WARNING: Cache_stop_client, nonexistent client\n"); } @@ -1213,7 +1329,7 @@ void a_Cache_freeall(void) /* Remove every cache entry */ while ((data = dList_nth_data(CachedURLs, 0))) { - dList_remove(CachedURLs, data); + dList_remove_fast(CachedURLs, data); Cache_entry_free(data); } /* Remove the cache list */ diff --git a/src/cache.h b/src/cache.h index 6099447c..c01bec55 100644 --- a/src/cache.h +++ b/src/cache.h @@ -46,6 +46,7 @@ typedef void (*CA_Callback_t)(int Op, CacheClient_t *Client); struct _CacheClient { int Key; /* Primary Key for this client */ const DilloUrl *Url; /* Pointer to a cache entry Url */ + int Version; /* Dicache version of this Url (0 if not used) */ void *Buf; /* Pointer to cache-data */ uint_t BufSize; /* Valid size of cache-data */ CA_Callback_t Callback; /* Client function */ @@ -62,11 +63,12 @@ int a_Cache_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize); void a_Cache_unref_buf(const DilloUrl *Url); const char *a_Cache_get_content_type(const DilloUrl *url); const char *a_Cache_set_content_type(const DilloUrl *url, const char *ctype, - bool_t force); + const char *from); uint_t a_Cache_get_flags(const DilloUrl *url); +uint_t a_Cache_get_flags_with_redirection(const DilloUrl *url); 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); +int a_Cache_download_enabled(const DilloUrl *url); void a_Cache_entry_remove_by_url(DilloUrl *url); void a_Cache_freeall(void); CacheClient_t *a_Cache_client_get_if_unique(int Key); @@ -22,7 +22,6 @@ #include "IO/IO.h" /* for IORead &friends */ #include "IO/Url.h" #include "chain.h" -#include "list.h" #include "history.h" #include "nav.h" #include "dpiapi.h" @@ -56,10 +55,10 @@ enum { * Local data */ /* Data list for active dpi connections */ -static capi_conn_t **DpiConn = NULL; -static int DpiConnSize; -static int DpiConnMax = 4; - +static Dlist *CapiConns; /* Data list for active connections; it holds + * pointers to capi_conn_t structures. */ +/* Last URL asked for view source */ +static DilloUrl *CapiVsUrl = NULL; /* * Forward declarations @@ -75,7 +74,9 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, */ void a_Capi_init(void) { - /* nothing to do for capi yet, just for cache */ + /* create an empty list */ + CapiConns = dList_new(32); + /* init cache */ a_Cache_init(); } @@ -85,7 +86,7 @@ void a_Capi_init(void) * Create a new connection data structure */ static capi_conn_t * - Capi_conn_new(DilloUrl *url, void *bw, char *server, char *datastr) + Capi_conn_new(const DilloUrl *url, void *bw, char *server, char *datastr) { capi_conn_t *conn; @@ -103,15 +104,22 @@ static capi_conn_t * } /* + * Validate a capi_conn_t pointer. + * Return value: NULL if not valid, conn otherwise. + */ +static capi_conn_t *Capi_conn_valid(capi_conn_t *conn) +{ + return dList_find(CapiConns, 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++; + dList_append(CapiConns, (void *)conn); } _MSG(" Capi_conn_ref #%d %p\n", conn->Ref, conn); } @@ -121,26 +129,30 @@ static void Capi_conn_ref(capi_conn_t *conn) */ static void Capi_conn_unref(capi_conn_t *conn) { - int i, j; - _MSG(" Capi_conn_unref #%d %p\n", conn->Ref - 1, conn); + /* We may validate conn here, but it doesn't *seem* necessary */ 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; - } - } + /* remove conn preserving the list order */ + dList_remove(CapiConns, (void *)conn); + /* free dynamic memory */ + a_Url_free(conn->url); + dFree(conn->server); + dFree(conn->datastr); + dFree(conn); } + _MSG(" Capi_conn_unref CapiConns=%d\n", dList_length(CapiConns)); +} + +/* + * Compare function for searching a conn by server string + */ +static int Capi_conn_by_server_cmp(const void *v1, const void *v2) +{ + const capi_conn_t *node = v1; + const char *server = v2; + dReturn_val_if_fail(node && node->server && server, 1); + return strcmp(node->server, server); } /* @@ -148,13 +160,7 @@ static void Capi_conn_unref(capi_conn_t *conn) */ 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; + return dList_find_custom(CapiConns, (void*)server, Capi_conn_by_server_cmp); } /* @@ -164,12 +170,15 @@ static void Capi_conn_resume(void) { int i; DataBuf *dbuf; + capi_conn_t *conn; - for (i = 0; i < DpiConnSize; ++i) { - if (DpiConn[i]->Flags & PENDING) { - capi_conn_t *conn = DpiConn[i]; + for (i = 0; i < dList_length(CapiConns); ++i) { + conn = dList_nth_data (CapiConns, i); + if (conn->Flags & PENDING) { dbuf = a_Chain_dbuf_new(conn->datastr,(int)strlen(conn->datastr), 0); - a_Capi_ccc(OpSend, 1, BCK, conn->InfoSend, dbuf, NULL); + if (conn->InfoSend) { + a_Capi_ccc(OpSend, 1, BCK, conn->InfoSend, dbuf, NULL); + } dFree(dbuf); conn->Flags &= ~PENDING; } @@ -178,14 +187,18 @@ static void Capi_conn_resume(void) /* * Abort the connection for a given url, using its CCC. + * (OpAbort 2,BCK removes the cache entry) + * TODO: when conn is already done, the cache entry isn't removed. + * This may be wrong and needs a revision. */ void a_Capi_conn_abort_by_url(const DilloUrl *url) { int i; + capi_conn_t *conn; - for (i = 0; i < DpiConnSize; ++i) { - if (a_Url_cmp(DpiConn[i]->url, url) == 0) { - capi_conn_t *conn = DpiConn[i]; + for (i = 0; i < dList_length(CapiConns); ++i) { + conn = dList_nth_data (CapiConns, i); + if (a_Url_cmp(conn->url, url) == 0) { if (conn->InfoSend) { a_Capi_ccc(OpAbort, 1, BCK, conn->InfoSend, NULL, NULL); } @@ -200,21 +213,35 @@ void a_Capi_conn_abort_by_url(const DilloUrl *url) /* ------------------------------------------------------------------------- */ /* - * Safety test: only allow dpi-urls from dpi-generated pages. + * Store the last URL requested by "view source" */ -static int Capi_dpi_verify_request(DilloWeb *web) +void a_Capi_set_vsource_url(const DilloUrl *url) { - DilloUrl *referer; + a_Url_free(CapiVsUrl); + CapiVsUrl = a_Url_dup(url); +} + +/* + * Safety test: only allow GET|POST dpi-urls from dpi-generated pages. + */ +int a_Capi_dpi_verify_request(BrowserWindow *bw, DilloUrl *url) +{ + const DilloUrl *referer; int allow = FALSE; - /* test POST and GET */ - if (dStrcasecmp(URL_SCHEME(web->url), "dpi") == 0 && - URL_FLAGS(web->url) & (URL_Post + URL_Get)) { - /* only allow dpi requests from dpi-generated urls */ - if (a_Nav_stack_size(web->bw)) { - referer = a_History_get_url(NAV_TOP_UIDX(web->bw)); - if (dStrcasecmp(URL_SCHEME(referer), "dpi") == 0) { - allow = TRUE; + if (dStrcasecmp(URL_SCHEME(url), "dpi") == 0) { + if (!(URL_FLAGS(url) & (URL_Post + URL_Get))) { + allow = TRUE; + } else if (!(URL_FLAGS(url) & URL_Post) && + strncmp(URL_STR(url), "dpi:/vsource/", 13) == 0) { + allow = TRUE; + } else { + /* only allow GET&POST dpi-requests from dpi-generated urls */ + if (a_Nav_stack_size(bw)) { + referer = a_History_get_url(NAV_TOP_UIDX(bw)); + if (dStrcasecmp(URL_SCHEME(referer), "dpi") == 0) { + allow = TRUE; + } } } } else { @@ -222,10 +249,10 @@ static int Capi_dpi_verify_request(DilloWeb *web) } if (!allow) { - MSG("Capi_dpi_verify_request: Permission Denied!\n"); - MSG(" URL_STR : %s\n", URL_STR(web->url)); - if (URL_FLAGS(web->url) & URL_Post) { - MSG(" URL_DATA: %s\n", dStr_printable(URL_DATA(web->url), 1024)); + MSG("a_Capi_dpi_verify_request: Permission Denied!\n"); + MSG(" URL_STR : %s\n", URL_STR(url)); + if (URL_FLAGS(url) & URL_Post) { + MSG(" URL_DATA: %s\n", dStr_printable(URL_DATA(url), 1024)); } } return allow; @@ -265,7 +292,6 @@ static int Capi_url_uses_dpi(DilloUrl *url, char **server_ptr) /* * 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) { @@ -273,10 +299,20 @@ static char *Capi_dpi_build_cmd(DilloWeb *web, char *server) if (strcmp(server, "proto.https") == 0) { /* Let's be kind and make the HTTP query string for the dpi */ + char *proxy_connect = a_Http_make_connect_str(web->url); Dstr *http_query = a_Http_make_query_str(web->url, FALSE); /* BUG: embedded NULLs in query data will truncate message */ - cmd = a_Dpip_build_cmd("cmd=%s url=%s query=%s", - "open_url", URL_STR(web->url), http_query->str); + if (proxy_connect) { + const char *proxy_urlstr = a_Http_get_proxy_urlstr(); + cmd = a_Dpip_build_cmd("cmd=%s proxy_url=%s proxy_connect=%s " + "url=%s query=%s", "open_url", proxy_urlstr, + proxy_connect, URL_STR(web->url), + http_query->str); + } else { + cmd = a_Dpip_build_cmd("cmd=%s url=%s query=%s", + "open_url", URL_STR(web->url),http_query->str); + } + dFree(proxy_connect); dStr_free(http_query, 1); } else if (strcmp(server, "downloads") == 0) { @@ -292,6 +328,84 @@ static char *Capi_dpi_build_cmd(DilloWeb *web, char *server) } /* + * Send the requested URL's source to the "view source" dpi + */ +static void Capi_dpi_send_source(BrowserWindow *bw, DilloUrl *url) +{ + char *p, *buf, *cmd, size_str[32], *server="vsource"; + int buf_size; + + if (!(p = strchr(URL_STR(url), ':')) || !(p = strchr(p + 1, ':'))) + return; + + if (a_Capi_get_buf(CapiVsUrl, &buf, &buf_size)) { + /* send the page's source to this dpi connection */ + snprintf(size_str, 32, "%d", buf_size); + cmd = a_Dpip_build_cmd("cmd=%s url=%s data_size=%s", + "start_send_page", URL_STR(url), size_str); + a_Capi_dpi_send_cmd(NULL, bw, cmd, server, 0); + a_Capi_dpi_send_data(url, bw, buf, buf_size, server, 0); + } else { + cmd = a_Dpip_build_cmd("cmd=%s msg=%s", + "DpiError", "Page is NOT cached"); + a_Capi_dpi_send_cmd(NULL, bw, cmd, server, 0); + } + dFree(cmd); +} + +/* + * When dillo wants to open an URL, this can be either due to user action + * (e.g., typing in an URL, clicking a link), or automatic (HTTP header + * indicates redirection, META HTML tag with refresh attribute and 0 delay, + * and images and stylesheets on an HTML page when autoloading is enabled). + * + * For a user request, the action will be permitted. + * For an automatic request, permission to load depends on the filter set + * by the user. + */ +static bool_t Capi_filters_allow(const DilloUrl *wanted, + const DilloUrl *requester) +{ + bool_t ret; + + if (requester == NULL) { + /* request made by user */ + ret = TRUE; + } else { + switch (prefs.filter_auto_requests) { + case PREFS_FILTER_SAME_DOMAIN: + { + const char *req_host = URL_HOST(requester), + *want_host = URL_HOST(wanted), + *req_suffix, + *want_suffix; + if (want_host[0] == '\0') { + ret = (req_host[0] == '\0' || + !dStrcasecmp(URL_SCHEME(wanted), "data")) ? TRUE : FALSE; + } else { + /* This will regard "www.dillo.org" and "www.dillo.org." as + * different, but it doesn't seem worth caring about. + */ + req_suffix = a_Url_host_find_public_suffix(req_host); + want_suffix = a_Url_host_find_public_suffix(want_host); + + ret = dStrcasecmp(req_suffix, want_suffix) == 0; + } + + MSG("Capi_filters_allow: from %s to %s: %s\n", req_host, want_host, + ret ? "ALLOWED" : "DENIED"); + break; + } + case PREFS_FILTER_ALLOW_ALL: + default: + ret = TRUE; + break; + } + } + return ret; +} + +/* * Most used function for requesting a URL. * TODO: clean up the ad-hoc bindings with an API that allows dynamic * addition of new plugins. @@ -301,12 +415,15 @@ static char *Capi_dpi_build_cmd(DilloWeb *web, char *server) */ int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData) { - capi_conn_t *conn; int reload; char *cmd, *server; + capi_conn_t *conn = NULL; const char *scheme = URL_SCHEME(web->url); int safe = 0, ret = 0, use_cache = 0; + dReturn_val_if_fail((a_Capi_get_flags(web->url) & CAPI_IsCached) || + Capi_filters_allow(web->url, web->requester), 0); + /* reload test */ reload = (!(a_Capi_get_flags(web->url) & CAPI_IsCached) || (URL_FLAGS(web->url) & URL_E2EQuery)); @@ -314,35 +431,43 @@ int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData) if (web->flags & WEB_Download) { /* download request: if cached save from cache, else * for http, ftp or https, use the downloads dpi */ - if (a_Capi_get_flags(web->url) & CAPI_IsCached) { - 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); + if (a_Capi_get_flags_with_redirection(web->url) & CAPI_IsCached) { + if (web->filename) { + if ((web->stream = fopen(web->filename, "w"))) { + use_cache = 1; + } else { + MSG_WARN("Cannot open \"%s\" for writing.\n", web->filename); + } } + } else if (a_Cache_download_enabled(web->url)) { + 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 ((safe = a_Capi_dpi_verify_request(web->bw, web->url))) { if (dStrcasecmp(scheme, "dpi") == 0) { - /* make "dpi:/" prefixed urls always reload. */ - a_Url_set_flags(web->url, URL_FLAGS(web->url) | URL_E2EQuery); - reload = 1; + if (strcmp(server, "vsource") == 0) { + /* don't reload the "view source" page */ + } else { + /* make the other "dpi:/" prefixed urls always reload. */ + a_Url_set_flags(web->url, URL_FLAGS(web->url) | URL_E2EQuery); + reload = 1; + } } if (reload) { a_Capi_conn_abort_by_url(web->url); /* Send dpip command */ + _MSG("a_Capi_open_url, reload url='%s'\n", URL_STR(web->url)); cmd = Capi_dpi_build_cmd(web, server); a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1); dFree(cmd); + if (strcmp(server, "vsource") == 0) { + Capi_dpi_send_source(web->bw, web->url); + } } use_cache = 1; } @@ -354,6 +479,9 @@ int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData) 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"); + /* start the reception branch before the query one because the DNS + * may callback immediatly. This may avoid a race condition. */ + a_Capi_ccc(OpStart, 2, BCK, a_Chain_new(), conn, "http"); a_Capi_ccc(OpStart, 1, BCK, a_Chain_new(), conn, web); } use_cache = 1; @@ -364,7 +492,10 @@ int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData) } if (use_cache) { - ret = a_Cache_open_url(web, Call, CbData); + if (!conn || (conn && Capi_conn_valid(conn))) { + /* not aborted, let's continue... */ + ret = a_Cache_open_url(web, Call, CbData); + } } else { a_Web_free(web); } @@ -372,12 +503,11 @@ int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData) } /* - * Return status information of an URL's content-transfer process. + * Convert cache-defined flags to Capi ones. */ -int a_Capi_get_flags(const DilloUrl *Url) +static int Capi_map_cache_flags(uint_t flags) { int status = 0; - uint_t flags = a_Cache_get_flags(Url); if (flags) { status |= CAPI_IsCached; @@ -394,6 +524,26 @@ int a_Capi_get_flags(const DilloUrl *Url) } /* + * Return status information of an URL's content-transfer process. + */ +int a_Capi_get_flags(const DilloUrl *Url) +{ + uint_t flags = a_Cache_get_flags(Url); + int status = flags ? Capi_map_cache_flags(flags) : 0; + return status; +} + +/* + * Same as a_Capi_get_flags() but following redirections. + */ +int a_Capi_get_flags_with_redirection(const DilloUrl *Url) +{ + uint_t flags = a_Cache_get_flags_with_redirection(Url); + int status = flags ? Capi_map_cache_flags(flags) : 0; + return status; +} + +/* * Get the cache's buffer for the URL, and its size. * Return: 1 cached, 0 not cached. */ @@ -419,20 +569,21 @@ const char *a_Capi_get_content_type(const DilloUrl *url) } /* - * Set the Content-Type for the URL. + * Set the Content-Type for the URL. */ const char *a_Capi_set_content_type(const DilloUrl *url, const char *ctype, - bool_t force) + const char *from) { - return a_Cache_set_content_type(url, ctype, force); + return a_Cache_set_content_type(url, ctype, from); } /* - * Send a dpi cmd. - * (For instance: add_bookmark, open_url, send_preferences, ...) + * Send data to a dpi (e.g. add_bookmark, open_url, send_preferences, ...) + * Most of the time we send dpi commands, but it also serves for raw data + * as with "view source". */ -int a_Capi_dpi_send_cmd(DilloUrl *url, void *bw, char *cmd, char *server, - int flags) +int a_Capi_dpi_send_data(const DilloUrl *url, void *bw, + char *data, int data_sz, char *server, int flags) { capi_conn_t *conn; DataBuf *dbuf; @@ -441,8 +592,9 @@ int a_Capi_dpi_send_cmd(DilloUrl *url, void *bw, char *cmd, char *server, /* 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); + conn = Capi_conn_new(url, bw, server, data); /* start the CCC operations */ + a_Capi_ccc(OpStart, 2, BCK, a_Chain_new(), conn, server); a_Capi_ccc(OpStart, 1, BCK, a_Chain_new(), conn, server); } else { @@ -450,11 +602,11 @@ int a_Capi_dpi_send_cmd(DilloUrl *url, void *bw, char *cmd, char *server, conn = Capi_conn_find(server); if (conn) { /* found */ - dbuf = a_Chain_dbuf_new(cmd, (int)strlen(cmd), 0); + dbuf = a_Chain_dbuf_new(data, data_sz, 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"); + MSG(" ERROR: [a_Capi_dpi_send_data] No open connection found\n"); } } @@ -462,6 +614,16 @@ int a_Capi_dpi_send_cmd(DilloUrl *url, void *bw, char *cmd, char *server, } /* + * 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) +{ + return a_Capi_dpi_send_data(url, bw, cmd, strlen(cmd), server, flags); +} + +/* * Remove a client from the cache client queue. * force = also abort the CCC if this is the last client. */ @@ -469,7 +631,11 @@ void a_Capi_stop_client(int Key, int force) { CacheClient_t *Client; - if (force && (Client = a_Cache_client_get_if_unique(Key))) { + _MSG("a_Capi_stop_client: force=%d\n", force); + + Client = a_Cache_client_get_if_unique(Key); + if (Client && (force || Client->BufSize == 0)) { + /* remove empty entries too */ a_Capi_conn_abort_by_url(Client->Url); } a_Cache_stop_client(Key); @@ -525,18 +691,17 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, MSG_WARN("Unused CCC\n"); break; } - } else { /* FWD */ + } else { /* 1 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; + } else if (strcmp(Data2, "FD") == 0) { + conn = Info->LocalKey; conn->SockFD = *(int*)Data1; - a_Capi_ccc(OpStart, 2, BCK, a_Chain_new(), Info->LocalKey, - conn->server); + /* communicate the FD through the answer branch */ + a_Capi_ccc(OpSend, 2, BCK, conn->InfoRecv, &conn->SockFD, "FD"); } else if (strcmp(Data2, "DpidOK") == 0) { /* resume pending dpi requests */ Capi_conn_resume(); @@ -545,13 +710,19 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, 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 " - "(URL scheme = '%s')!", - URL_SCHEME(conn->url)); + if (Data2) { + if (!strcmp(Data2, "DpidERROR")) { + a_UIcmd_set_msg(conn->bw, + "ERROR: can't start dpid daemon " + "(URL scheme = '%s')!", + conn->url ? URL_SCHEME(conn->url) : ""); + } else if (!strcmp(Data2, "Both") && conn->InfoRecv) { + /* abort the other branch too */ + a_Capi_ccc(OpAbort, 2, BCK, conn->InfoRecv, NULL, NULL); + } + } + /* if URL == expect-url */ + a_Nav_cancel_expect_if_eq(conn->bw, conn->url); /* finish conn */ Capi_conn_unref(conn); dFree(Info); @@ -564,21 +735,29 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, } else if (Branch == 2) { if (Dir == BCK) { - /* Server listening branch (status) - * (Data1 = conn; Data2 = {"HttpFD" | "DpiFD"}) */ + /* Answer branch */ switch (Op) { case OpStart: + /* Data1 = conn; Data2 = {"http" | "<dpi server name>"} */ 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); + a_Chain_bcb(OpStart, Info, NULL, Data2); + break; + case OpSend: + /* Data1 = FD */ + if (Data2 && strcmp(Data2, "FD") == 0) { + a_Chain_bcb(OpSend, Info, Data1, Data2); + } break; case OpAbort: conn = Info->LocalKey; conn->InfoRecv = NULL; a_Chain_bcb(OpAbort, Info, NULL, NULL); + /* remove the cache entry for this URL */ + a_Cache_entry_remove_by_url(conn->url); Capi_conn_unref(conn); dFree(Info); break; @@ -586,7 +765,7 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, MSG_WARN("Unused CCC\n"); break; } - } else { /* FWD */ + } else { /* 2 FWD */ /* Server listening branch */ switch (Op) { case OpSend: @@ -27,10 +27,15 @@ int a_Capi_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize); void a_Capi_unref_buf(const DilloUrl *Url); const char *a_Capi_get_content_type(const DilloUrl *url); const char *a_Capi_set_content_type(const DilloUrl *url, const char *ctype, - bool_t force); + const char *from); int a_Capi_get_flags(const DilloUrl *Url); +int a_Capi_get_flags_with_redirection(const DilloUrl *Url); +int a_Capi_dpi_verify_request(BrowserWindow *bw, DilloUrl *url); +int a_Capi_dpi_send_data(const DilloUrl *url, void *bw, + char *data, int data_sz, char *server, int flags); int a_Capi_dpi_send_cmd(DilloUrl *url, void *bw, char *cmd, char *server, int flags); +void a_Capi_set_vsource_url(const DilloUrl *url); void a_Capi_stop_client(int Key, int force); void a_Capi_conn_abort_by_url(const DilloUrl *url); diff --git a/src/chain.c b/src/chain.c index 37f43a7f..d4098a2d 100644 --- a/src/chain.c +++ b/src/chain.c @@ -104,6 +104,7 @@ int a_Chain_fcb(int Op, ChainLink *Info, void *Data1, void *Data2) if (Info->Flags & (CCC_Ended + CCC_Aborted)) { /* CCC is not operative */ } else if (Info->Fcb) { + /* flag the caller */ if (Op == OpEnd) Info->Flags |= CCC_Ended; else if (Op == OpAbort) @@ -126,6 +127,7 @@ int a_Chain_bcb(int Op, ChainLink *Info, void *Data1, void *Data2) if (Info->Flags & (CCC_Ended + CCC_Aborted)) { /* CCC is not operative */ } else if (Info->Bcb) { + /* flag the caller */ if (Op == OpEnd) Info->Flags |= CCC_Ended; else if (Op == OpAbort) @@ -137,6 +139,28 @@ int a_Chain_bcb(int Op, ChainLink *Info, void *Data1, void *Data2) return ret; } +/* + * Issue the backward callback of the 'Info' link and then the + * forward callback (used for OpAbort and OpStop). + * Return value: 1 if OK, 0 if not operative. + */ +int a_Chain_bfcb(int Op, ChainLink *Info, void *Data1, void *Data2) +{ + int ret; + + ret = a_Chain_bcb(Op, Info, Data1, Data2); + if (ret == 1) { + /* we need to clear the flag to reuse this 'Info' ChainLink */ + if (Op == OpEnd) + Info->Flags &= ~CCC_Ended; + else if (Op == OpAbort) + Info->Flags &= ~CCC_Aborted; + + ret = a_Chain_fcb(Op, Info, Data1, Data2); + } + return ret; +} + /* * Allocate and initialize a new DataBuf structure @@ -153,7 +177,7 @@ DataBuf *a_Chain_dbuf_new(void *buf, int size, int code) /* * Check whether the CCC is operative. * Also used to hook debug information. - * + * * Return value: 1 if ready to use, 0 if not operative. */ int a_Chain_check(char *FuncStr, int Op, int Branch, int Dir, @@ -166,7 +190,9 @@ int a_Chain_check(char *FuncStr, int Op, int Branch, int Dir, if (Info->Flags & (CCC_Ended + CCC_Aborted)) { /* CCC is not operative */ - MSG_WARN("CCC: call on already finished chain.\n"); + MSG_WARN("CCC: call on already finished chain. Flags=%s%s\n", + Info->Flags & CCC_Ended ? "CCC_Ended " : "", + Info->Flags & CCC_Aborted ? "CCC_Aborted" : ""); } else { ret = 1; } diff --git a/src/chain.h b/src/chain.h index b6b41bd4..fd86557c 100644 --- a/src/chain.h +++ b/src/chain.h @@ -69,6 +69,7 @@ ChainLink *a_Chain_link_new(ChainLink *AInfo, ChainFunction_t AFunc, 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); +int a_Chain_bfcb(int Op, ChainLink *Info, void *Data1, void *Data2); int a_Chain_check(char *FuncStr, int Op, int Branch, int Dir, ChainLink *Info); diff --git a/src/colors.c b/src/colors.c index 9a4c8c8e..5b647bb2 100644 --- a/src/colors.c +++ b/src/colors.c @@ -204,7 +204,7 @@ static const struct key { #define NCOLORS (sizeof(color_keyword) / sizeof(struct key)) /* - * Parse a color in hex (RRGGBB) + * Parse a color in hex (RRGGBB) or (RGB) * * Return Value: * parsed color if successful (err = 0), @@ -219,7 +219,12 @@ static int32_t Color_parse_hex (const char *s, int32_t default_color, int *err) ret_color = strtol(s, &tail, 16); if (tail - s == 6) *err = 0; - else + else if (tail - s == 3) { /* #RGB as allowed by CSS */ + *err = 0; + ret_color = ((ret_color & 0xf00) << 12) | ((ret_color & 0xf00) << 8) | + ((ret_color & 0x0f0) << 8) | ((ret_color & 0x0f0) << 4) | + ((ret_color & 0x00f) << 4) | ((ret_color & 0x00f) << 0); + } else ret_color = default_color; return ret_color; @@ -241,7 +246,7 @@ int32_t a_Color_parse (const char *subtag, int32_t default_color, int *err) int ret, low, mid, high, st = 1; /* skip leading spaces */ - for (cp = subtag; isspace(*cp); cp++); + for (cp = subtag; dIsspace(*cp); cp++); ret_color = default_color; if (*cp == '#') { diff --git a/src/cookies.c b/src/cookies.c index 1b336b83..7b9062e2 100644 --- a/src/cookies.c +++ b/src/cookies.c @@ -10,10 +10,9 @@ * (at your option) any later version. */ -/* Handling of cookies takes place here. - * This implementation aims to follow RFC 2965: - * http://www.ietf.org/rfc/rfc2965.txt - */ +/* Handling of cookies takes place here. */ + +#include "msg.h" #ifdef DISABLE_COOKIES @@ -34,15 +33,13 @@ void a_Cookies_init(void) #include <unistd.h> #include <stdlib.h> #include <stdio.h> -#include <time.h> /* for time() and time_t */ #include <ctype.h> +#include <errno.h> -#include "msg.h" #include "IO/Url.h" #include "list.h" #include "cookies.h" #include "capi.h" -#include "dpiapi.h" #include "../dpip/dpip.h" @@ -80,14 +77,19 @@ static int Cookie_control_init(void); static FILE *Cookies_fopen(const char *filename, char *init_str) { FILE *F_in; - int fd; + int fd, rc; 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)); + if (init_str) { + rc = write(fd, init_str, strlen(init_str)); + if (rc == -1) { + MSG("Cookies: Could not write initial string to file %s: %s\n", + filename, dStrerror(errno)); + } + } close(fd); MSG("Cookies: Created file: %s\n", filename); @@ -134,10 +136,11 @@ void a_Cookies_freeall() /* * Set the value corresponding to the cookie string */ -void a_Cookies_set(Dlist *cookie_strings, const DilloUrl *set_url) +void a_Cookies_set(Dlist *cookie_strings, const DilloUrl *set_url, + const char *date) { CookieControlAction action; - char *cmd, *cookie_string, *dpip_tag, numstr[16]; + char *cmd, *cookie_string, *dpip_tag; const char *path; int i; @@ -152,10 +155,14 @@ void a_Cookies_set(Dlist *cookie_strings, const DilloUrl *set_url) 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); + if (date) + cmd = a_Dpip_build_cmd("cmd=%s cookie=%s host=%s path=%s date=%s", + "set_cookie", cookie_string, + URL_HOST_(set_url), path ? path : "/", date); + else + cmd = a_Dpip_build_cmd("cmd=%s cookie=%s host=%s path=%s", + "set_cookie", cookie_string, + URL_HOST_(set_url), path ? path : "/"); _MSG("Cookies.c: a_Cookies_set \n\t \"%s\" \n",cmd ); /* This call is commented because it doesn't guarantee the order @@ -174,7 +181,7 @@ void a_Cookies_set(Dlist *cookie_strings, const DilloUrl *set_url) */ char *a_Cookies_get_query(const DilloUrl *request_url) { - char *cmd, *dpip_tag, *query, numstr[16]; + char *cmd, *dpip_tag, *query; const char *path; CookieControlAction action; @@ -188,10 +195,9 @@ char *a_Cookies_get_query(const DilloUrl *request_url) } 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", + cmd = a_Dpip_build_cmd("cmd=%s scheme=%s host=%s path=%s", "get_cookie", URL_SCHEME(request_url), - URL_HOST(request_url), path ? path : "/", numstr); + URL_HOST(request_url), path ? path : "/"); /* Get the answer from cookies.dpi */ _MSG("cookies.c: a_Dpi_send_blocking_cmd cmd = {%s}\n", cmd); @@ -199,13 +205,11 @@ char *a_Cookies_get_query(const DilloUrl *request_url) _MSG("cookies.c: after a_Dpi_send_blocking_cmd resp={%s}\n", dpip_tag); dFree(cmd); - query = dStrdup("Cookie2: $Version=\"1\"\r\n"); - if (dpip_tag != NULL) { - char *cookie = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cookie"); + query = a_Dpip_get_attr(dpip_tag, "cookie"); dFree(dpip_tag); - query = dStrconcat(query, cookie, NULL); - dFree(cookie); + } else { + query = dStrdup(""); } return query; } @@ -226,11 +230,10 @@ static int Cookie_control_init(void) { CookieControl cc; FILE *stream; - char *filename; + char *filename, *rc; char line[LINE_MAXLEN]; char domain[LINE_MAXLEN]; char rule[LINE_MAXLEN]; - int i, j; bool_t enabled = FALSE; /* Get a file pointer */ @@ -244,28 +247,31 @@ static int Cookie_control_init(void) /* Get all lines in the file */ while (!feof(stream)) { line[0] = '\0'; - fgets(line, LINE_MAXLEN, stream); + rc = fgets(line, LINE_MAXLEN, stream); + if (!rc && ferror(stream)) { + MSG("Cookies1: Error while reading rule from cookiesrc: %s\n", + dStrerror(errno)); + return 2; /* bail out */ + } /* Remove leading and trailing whitespaces */ dStrstrip(line); if (line[0] != '\0' && line[0] != '#') { - i = 0; - j = 0; + int i = 0, j = 0; /* Get the domain */ - while (!isspace(line[i])) + while (line[i] != '\0' && !dIsspace(line[i])) domain[j++] = line[i++]; domain[j] = '\0'; /* Skip past whitespaces */ - i++; - while (isspace(line[i])) + while (dIsspace(line[i])) i++; /* Get the rule */ j = 0; - while (line[i] != '\0' && !isspace(line[i])) + while (line[i] != '\0' && !dIsspace(line[i])) rule[j++] = line[i++]; rule[j] = '\0'; @@ -287,8 +293,17 @@ static int Cookie_control_init(void) default_action = cc.action; dFree(cc.domain); } else { + int i; + uint_t len = strlen(cc.domain); + + /* Insert into list such that longest rules come first. */ a_List_add(ccontrol, num_ccontrol, num_ccontrol_max); - ccontrol[num_ccontrol++] = cc; + for (i = num_ccontrol++; + i > 0 && (len > strlen(ccontrol[i-1].domain)); + i--) { + ccontrol[i] = ccontrol[i-1]; + } + ccontrol[i] = cc; } if (cc.action != COOKIE_DENY) @@ -302,7 +317,9 @@ static int Cookie_control_init(void) } /* - * Check the rules for an appropriate action for this domain + * Check the rules for an appropriate action for this domain. + * The rules are ordered by domain length, with longest first, so the + * first match is the most specific. */ static CookieControlAction Cookies_control_check_domain(const char *domain) { diff --git a/src/cookies.h b/src/cookies.h index 6f9f77e0..482aa5ae 100644 --- a/src/cookies.h +++ b/src/cookies.h @@ -12,7 +12,8 @@ extern "C" { # define a_Cookies_freeall() ; #else char *a_Cookies_get_query(const DilloUrl *request_url); - void a_Cookies_set(Dlist *cookie_string, const DilloUrl *set_url); + void a_Cookies_set(Dlist *cookie_string, const DilloUrl *set_url, + const char *server_date); void a_Cookies_init( void ); void a_Cookies_freeall( void ); #endif diff --git a/src/css.cc b/src/css.cc new file mode 100644 index 00000000..988d0dc6 --- /dev/null +++ b/src/css.cc @@ -0,0 +1,617 @@ +/* + * File: css.cc + * + * Copyright 2008-2009 Johannes Hofmann <Johannes.Hofmann@gmx.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 3 of the License, or + * (at your option) any later version. + */ + +#include <stdio.h> +#include "../dlib/dlib.h" +#include "misc.h" +#include "msg.h" +#include "html_common.hh" +#include "css.hh" +#include "cssparser.hh" + +using namespace dw::core::style; + +void CssProperty::print () { + fprintf (stderr, "%s - %d\n", + CssParser::propertyNameString((CssPropertyName)name), + (int)value.intVal); +} + +CssPropertyList::~CssPropertyList () { + if (ownerOfStrings) + for (int i = 0; i < size (); i++) + getRef (i)->free (); +} + +/** + * \brief Set property to a given name and type. + */ +void CssPropertyList::set (CssPropertyName name, CssValueType type, + CssPropertyValue value) { + CssProperty *prop; + + for (int i = 0; i < size (); i++) { + prop = getRef (i); + + if (prop->name == name) { + if (ownerOfStrings) + prop->free (); + prop->type = type; + prop->value = value; + return; + } + } + + increase (); + prop = getRef (size () - 1); + prop->name = name; + prop->type = type; + prop->value = value; +} + +/** + * \brief Merge properties into argument property list. + */ +void CssPropertyList::apply (CssPropertyList *props) { + for (int i = 0; i < size (); i++) + props->set ((CssPropertyName) getRef (i)->name, + (CssValueType) getRef (i)->type, + getRef (i)->value); +} + +void CssPropertyList::print () { + for (int i = 0; i < size (); i++) + getRef (i)->print (); +} + +CssSelector::CssSelector () { + struct CombinatorAndSelector *cs; + + refCount = 0; + selectorList = new lout::misc::SimpleVector + <struct CombinatorAndSelector> (1); + selectorList->increase (); + cs = selectorList->getRef (selectorList->size () - 1); + + cs->notMatchingBefore = -1; + cs->selector = new CssSimpleSelector (); +}; + +CssSelector::~CssSelector () { + for (int i = selectorList->size () - 1; i >= 0; i--) + delete selectorList->getRef (i)->selector; + delete selectorList; +} + +/** + * \brief Return whether selector matches at a given node in the document tree. + */ +bool CssSelector::match (Doctree *docTree, const DoctreeNode *node) { + CssSimpleSelector *sel; + Combinator comb = CHILD; + int *notMatchingBefore; + const DoctreeNode *n; + + for (int i = selectorList->size () - 1; i >= 0; i--) { + struct CombinatorAndSelector *cs = selectorList->getRef (i); + + sel = cs->selector; + notMatchingBefore = &cs->notMatchingBefore; + + if (node == NULL) + return false; + + switch (comb) { + case CHILD: + if (!sel->match (node)) + return false; + break; + case DESCENDANT: + n = node; + + while (true) { + if (node == NULL || node->num <= *notMatchingBefore) { + *notMatchingBefore = n->num; + return false; + } + + if (sel->match (node)) + break; + + node = docTree->parent (node); + } + break; + default: + return false; // \todo implement other combinators + } + + comb = cs->combinator; + node = docTree->parent (node); + } + + return true; +} + +void CssSelector::addSimpleSelector (Combinator c) { + struct CombinatorAndSelector *cs; + + selectorList->increase (); + cs = selectorList->getRef (selectorList->size () - 1); + + cs->combinator = c; + cs->notMatchingBefore = -1; + cs->selector = new CssSimpleSelector (); +} + +/** + * \brief Return the specificity of the selector. + * + * The specificity of a CSS selector is defined in + * http://www.w3.org/TR/CSS21/cascade.html#specificity + */ +int CssSelector::specificity () { + int spec = 0; + + for (int i = 0; i < selectorList->size (); i++) + spec += selectorList->getRef (i)->selector->specificity (); + + return spec; +} + +void CssSelector::print () { + for (int i = 0; i < selectorList->size (); i++) { + selectorList->getRef (i)->selector->print (); + + if (i < selectorList->size () - 1) { + switch (selectorList->getRef (i + 1)->combinator) { + case CHILD: + fprintf (stderr, "> "); + break; + case DESCENDANT: + fprintf (stderr, "\" \" "); + break; + default: + fprintf (stderr, "? "); + break; + } + } + } + + fprintf (stderr, "\n"); +} + +CssSimpleSelector::CssSimpleSelector () { + element = ELEMENT_ANY; + klass = NULL; + id = NULL; + pseudo = NULL; +} + +CssSimpleSelector::~CssSimpleSelector () { + if (klass) { + for (int i = 0; i < klass->size (); i++) + dFree (klass->get (i)); + delete klass; + } + dFree (id); + dFree (pseudo); +} + +void CssSimpleSelector::setSelect (SelectType t, const char *v) { + switch (t) { + case SELECT_CLASS: + if (klass == NULL) + klass = new lout::misc::SimpleVector <char *> (1); + klass->increase (); + klass->set (klass->size () - 1, dStrdup (v)); + break; + case SELECT_PSEUDO_CLASS: + if (pseudo == NULL) + pseudo = dStrdup (v); + break; + case SELECT_ID: + if (id == NULL) + id = dStrdup (v); + break; + default: + break; + } +} + +/** + * \brief Return whether simple selector matches at a given node of + * the document tree. + */ +bool CssSimpleSelector::match (const DoctreeNode *n) { + if (element != ELEMENT_ANY && element != n->element) + return false; + if (pseudo != NULL && + (n->pseudo == NULL || dStrcasecmp (pseudo, n->pseudo) != 0)) + return false; + if (id != NULL && (n->id == NULL || dStrcasecmp (id, n->id) != 0)) + return false; + if (klass != NULL) { + for (int i = 0; i < klass->size (); i++) { + bool found = false; + if (n->klass != NULL) { + for (int j = 0; j < n->klass->size (); j++) { + if (dStrcasecmp (klass->get(i), n->klass->get(j)) == 0) { + found = true; + break; + } + } + } + if (! found) + return false; + } + } + + return true; +} + +/** + * \brief Return the specificity of the simple selector. + * + * The result is used in CssSelector::specificity (). + */ +int CssSimpleSelector::specificity () { + int spec = 0; + + if (id) + spec += 1 << 20; + if (klass) + spec += klass->size() << 10; + if (pseudo) + spec += 1 << 10; + if (element != ELEMENT_ANY) + spec += 1; + + return spec; +} + +void CssSimpleSelector::print () { + fprintf (stderr, "Element %d, pseudo %s, id %s ", + element, pseudo, id); + if (klass != NULL) { + fprintf (stderr, "class "); + for (int i = 0; i < klass->size (); i++) + fprintf (stderr, ".%s", klass->get (i)); + } +} + +CssRule::CssRule (CssSelector *selector, CssPropertyList *props, int pos) { + assert (selector->size () > 0); + + this->selector = selector; + this->selector->ref (); + this->props = props; + this->props->ref (); + this->pos = pos; + spec = selector->specificity (); +}; + +CssRule::~CssRule () { + selector->unref (); + props->unref (); +}; + +void CssRule::apply (CssPropertyList *props, + Doctree *docTree, const DoctreeNode *node) { + if (selector->match (docTree, node)) + this->props->apply (props); +} + +void CssRule::print () { + selector->print (); + props->print (); +} + +/* + * \brief Insert rule with increasing specificity. + * + * If two rules have the same specificity, the one that was added later + * will be added behind the others. + * This gives later added rules more weight. + */ +void CssStyleSheet::RuleList::insert (CssRule *rule) { + increase (); + int i = size () - 1; + + while (i > 0 && rule->specificity () < get (i - 1)->specificity ()) { + *getRef (i) = get (i - 1); + i--; + } + + *getRef (i) = rule; +} + +CssStyleSheet::CssStyleSheet () { + for (int i = 0; i < ntags; i++) + elementTable[i] = new RuleList (); + + idTable = new RuleMap (); + classTable = new RuleMap (); + anyTable = new RuleList (); +} + +CssStyleSheet::~CssStyleSheet () { + for (int i = 0; i < ntags; i++) + delete elementTable[i]; + delete idTable; + delete classTable; + delete anyTable; +} + +/** + * \brief Insert a rule into CssStyleSheet. + * + * To improve matching performance the rules are organized into + * rule lists based on the topmost simple selector of their selector. + */ +void CssStyleSheet::addRule (CssRule *rule) { + CssSimpleSelector *top = rule->selector->top (); + RuleList *ruleList = NULL; + lout::object::ConstString *string; + + if (top->getId ()) { + string = new lout::object::ConstString (top->getId ()); + ruleList = idTable->get (string); + if (ruleList == NULL) { + ruleList = new RuleList (); + idTable->put (string, ruleList); + } else { + delete string; + } + } else if (top->getClass () && top->getClass ()->size () > 0) { + string = new lout::object::ConstString (top->getClass ()->get (0)); + ruleList = classTable->get (string); + if (ruleList == NULL) { + ruleList = new RuleList; + classTable->put (string, ruleList); + } else { + delete string; + } + } else if (top->getElement () >= 0 && top->getElement () < ntags) { + ruleList = elementTable[top->getElement ()]; + } else if (top->getElement () == CssSimpleSelector::ELEMENT_ANY) { + ruleList = anyTable; + } + + if (ruleList) { + ruleList->insert (rule); + } else { + assert (top->getElement () == CssSimpleSelector::ELEMENT_NONE); + delete rule; + } +} + +/** + * \brief Apply a stylesheet to a property list. + * + * The properties are set as defined by the rules in the stylesheet that + * match at the given node in the document tree. + */ +void CssStyleSheet::apply (CssPropertyList *props, + Doctree *docTree, const DoctreeNode *node) { + static const int maxLists = 32; + RuleList *ruleList[maxLists]; + int numLists = 0, index[maxLists] = {0}; + + if (node->id) { + lout::object::ConstString idString (node->id); + + ruleList[numLists] = idTable->get (&idString); + if (ruleList[numLists]) + numLists++; + } + + if (node->klass) { + for (int i = 0; i < node->klass->size (); i++) { + if (i >= maxLists - 4) { + MSG_WARN("Maximum number of classes per element exceeded.\n"); + break; + } + + lout::object::ConstString classString (node->klass->get (i)); + + ruleList[numLists] = classTable->get (&classString); + if (ruleList[numLists]) + numLists++; + } + } + + ruleList[numLists] = elementTable[docTree->top ()->element]; + if (ruleList[numLists]) + numLists++; + + ruleList[numLists] = anyTable; + if (ruleList[numLists]) + numLists++; + + // Apply potentially matching rules from ruleList[0-numLists] with + // ascending specificity. + // If specificity is equal, rules are applied in order of appearance. + // Each ruleList is sorted already. + while (true) { + int minSpec = 1 << 30; + int minPos = 1 << 30; + int minSpecIndex = -1; + + for (int i = 0; i < numLists; i++) { + if (ruleList[i] && ruleList[i]->size () > index[i] && + (ruleList[i]->get(index[i])->specificity () < minSpec || + (ruleList[i]->get(index[i])->specificity () == minSpec && + ruleList[i]->get(index[i])->position () < minPos))) { + + minSpec = ruleList[i]->get(index[i])->specificity (); + minPos = ruleList[i]->get(index[i])->position (); + minSpecIndex = i; + } + } + + if (minSpecIndex >= 0) { + ruleList[minSpecIndex]->get (index[minSpecIndex])->apply + (props, docTree, node); + index[minSpecIndex]++; + } else { + break; + } + } +} + +CssStyleSheet *CssContext::userAgentStyle; +CssStyleSheet *CssContext::userStyle; +CssStyleSheet *CssContext::userImportantStyle; + +CssContext::CssContext () { + pos = 0; + + for (int o = CSS_PRIMARY_USER_AGENT; o < CSS_PRIMARY_LAST; o++) + sheet[o] = NULL; + + if (userAgentStyle == NULL) { + userAgentStyle = new CssStyleSheet (); + userStyle = new CssStyleSheet (); + userImportantStyle = new CssStyleSheet (); + + sheet[CSS_PRIMARY_USER_AGENT] = userAgentStyle; + sheet[CSS_PRIMARY_USER] = userStyle; + sheet[CSS_PRIMARY_USER_IMPORTANT] = userImportantStyle; + + buildUserAgentStyle (); + buildUserStyle (); + } + + sheet[CSS_PRIMARY_USER_AGENT] = userAgentStyle; + sheet[CSS_PRIMARY_USER] = userStyle; + sheet[CSS_PRIMARY_USER_IMPORTANT] = userImportantStyle; +} + +CssContext::~CssContext () { + for (int o = CSS_PRIMARY_USER_AGENT; o < CSS_PRIMARY_LAST; o++) + if (sheet[o] != userAgentStyle && sheet[o] != userStyle && + sheet[o] != userImportantStyle) + delete sheet[o]; +} + +/** + * \brief Apply a CSS context to a property list. + * + * The stylesheets in the context are applied one after the other + * in the ordering defined by CSS 2.1. + * Stylesheets that are applied later can overwrite properties set + * by previous stylesheets. + * This allows e.g. user styles to overwrite author styles. + */ +void CssContext::apply (CssPropertyList *props, Doctree *docTree, + CssPropertyList *tagStyle, CssPropertyList *nonCssHints) { + const DoctreeNode *node = docTree->top (); + + if (sheet[CSS_PRIMARY_USER_AGENT]) + sheet[CSS_PRIMARY_USER_AGENT]->apply (props, docTree, node); + + if (sheet[CSS_PRIMARY_USER]) + sheet[CSS_PRIMARY_USER]->apply (props, docTree, node); + + if (nonCssHints) + nonCssHints->apply (props); + + if (sheet[CSS_PRIMARY_AUTHOR]) + sheet[CSS_PRIMARY_AUTHOR]->apply (props, docTree, node); + + if (tagStyle) + tagStyle->apply (props); + + if (sheet[CSS_PRIMARY_AUTHOR_IMPORTANT]) + sheet[CSS_PRIMARY_AUTHOR_IMPORTANT]->apply (props, docTree, node); + + if (sheet[CSS_PRIMARY_USER_IMPORTANT]) + sheet[CSS_PRIMARY_USER_IMPORTANT]->apply (props, docTree, node); +} + +void CssContext::addRule (CssSelector *sel, CssPropertyList *props, + CssPrimaryOrder order) { + + if (props->size () > 0) { + CssRule *rule = new CssRule (sel, props, pos++); + + if (sheet[order] == NULL) + sheet[order] = new CssStyleSheet (); + + sheet[order]->addRule (rule); + } +} + +/** + * \brief Create the user agent style. + * + * The user agent style defines how dillo renders HTML in the absence of + * author or user styles. + */ +void CssContext::buildUserAgentStyle () { + const char *cssBuf = + "body {background-color: #e0e0a3; font-family: sans-serif; color: black;" + " margin: 5px}" + "big {font-size: 1.17em}" + "blockquote, dd {margin-left: 40px; margin-right: 40px}" + "center {text-align: center}" + "dt {font-weight: bolder}" + ":link {color: blue; text-decoration: underline; cursor: pointer}" + ":visited {color: #800080; text-decoration: underline; cursor: pointer}" + "h1, h2, h3, h4, h5, h6, b, strong {font-weight: bolder}" + "i, em, cite, address, var {font-style: italic}" + ":link img, :visited img {border: 1px solid}" + "frameset, ul, ol, dir {margin-left: 40px}" + "h1 {font-size: 2em; margin-top: .67em; margin-bottom: 0}" + "h2 {font-size: 1.5em; margin-top: .75em; margin-bottom: 0}" + "h3 {font-size: 1.17em; margin-top: .83em; margin-bottom: 0}" + "h4 {margin-top: 1.12em; margin-bottom: 0}" + "h5 {font-size: 0.83em; margin-top: 1.5em; margin-bottom: 0}" + "h6 {font-size: 0.75em; margin-top: 1.67em; margin-bottom: 0}" + "hr {width: 100%; border: 1px inset}" + "li {margin-top: 0.1em}" + "pre {white-space: pre}" + "ol {list-style-type: decimal}" + "ul {list-style-type: disc}" + "ul ul {list-style-type: circle}" + "ul ul ul {list-style-type: square}" + "ul ul ul ul {list-style-type: disc}" + "u {text-decoration: underline}" + "small, sub, sup {font-size: 0.83em}" + "sub {vertical-align: sub}" + "sup {vertical-align: super}" + "s, strike, del {text-decoration: line-through}" + "table {border-style: outset; border-spacing: 1px}" + "td, th {border-style: inset; padding: 2px}" + "thead, tbody, tfoot {vertical-align: middle}" + "th {font-weight: bolder; text-align: center}" + "code, tt, pre, samp, kbd {font-family: monospace}" + /* WORKAROUND: Reset font properties in tables as some + * some pages rely on it (e.g. gmail). + * http://developer.mozilla.org/En/Fixing_Table_Inheritance_in_Quirks_Mode + * has a detailed description of the issue. + */ + "table, caption {font-size: medium; font-weight: normal}"; + + CssParser::parse (NULL, NULL, this, cssBuf, strlen (cssBuf), + CSS_ORIGIN_USER_AGENT); +} + +void CssContext::buildUserStyle () { + Dstr *style; + char *filename = dStrconcat(dGethomedir(), "/.dillo/style.css", NULL); + + if ((style = a_Misc_file2dstr(filename))) { + CssParser::parse (NULL,NULL,this,style->str, style->len,CSS_ORIGIN_USER); + dStr_free (style, 1); + } + dFree (filename); +} diff --git a/src/css.hh b/src/css.hh new file mode 100644 index 00000000..b23eb9a3 --- /dev/null +++ b/src/css.hh @@ -0,0 +1,495 @@ +#ifndef __CSS_HH__ +#define __CSS_HH__ + +#include "dw/core.hh" +#include "doctree.hh" + +/* Origin and weight. Used only internally.*/ +typedef enum { + CSS_PRIMARY_USER_AGENT, + CSS_PRIMARY_USER, + CSS_PRIMARY_AUTHOR, + CSS_PRIMARY_AUTHOR_IMPORTANT, + CSS_PRIMARY_USER_IMPORTANT, + CSS_PRIMARY_LAST, +} CssPrimaryOrder; + +typedef enum { + CSS_ORIGIN_USER_AGENT, + CSS_ORIGIN_USER, + CSS_ORIGIN_AUTHOR, +} CssOrigin; + +typedef enum { + CSS_TYPE_INTEGER, /* This type is only used internally, for x-* + properties. */ + CSS_TYPE_ENUM, /* Value is i, if represented by + enum_symbols[i]. */ + CSS_TYPE_MULTI_ENUM, /* For all enum_symbols[i], 1 << i are + combined. */ + CSS_TYPE_LENGTH_PERCENTAGE, /* <length> or <percentage>. Represented by + CssLength. */ + CSS_TYPE_LENGTH, /* <length>, represented as CssLength. + Note: In some cases, CSS_TYPE_LENGTH is used + instead of CSS_TYPE_LENGTH_PERCENTAGE, + only because Dw cannot handle percentages + in this particular case (e.g. + 'margin-*-width'). */ + CSS_TYPE_SIGNED_LENGTH, /* As CSS_TYPE_LENGTH but may be negative. */ + CSS_TYPE_LENGTH_PERCENTAGE_NUMBER, /* <length> or <percentage>, or <number> */ + CSS_TYPE_COLOR, /* Represented as integer. */ + CSS_TYPE_FONT_WEIGHT, /* this very special and only used by + 'font-weight' */ + CSS_TYPE_STRING, /* <string> */ + CSS_TYPE_SYMBOL, /* Symbols, which are directly copied (as + opposed to CSS_TYPE_ENUM and + CSS_TYPE_MULTI_ENUM). Used for + 'font-family'. */ + CSS_TYPE_UNUSED /* Not yet used. Will itself get unused some + day. */ +} CssValueType; + +/* + * Lengths are represented as int in the following way: + * + * | <------ integer value ------> | + * + * +---+ - - - +---+---+- - - - - -+---+---+---+---+ + * | integer part | type | + * +---+ - - - +---+---+- - - - - -+---+---+---+---+ + * | integer part | decimal fraction | type | + * +---+ - - - +---+---+- - - - - -+---+---+---+---+ + * n-1 15 14 3 2 1 0 + * + * | <------ fixed point value ------> | + * + * where type is one of the CSS_LENGTH_TYPE_* values. + * CSS_LENGTH_TYPE_PX values are stored as + * 29 bit signed integer, all other types as fixed point values. + */ + +typedef int CssLength; + +typedef enum { + CSS_LENGTH_TYPE_NONE, + CSS_LENGTH_TYPE_PX, + CSS_LENGTH_TYPE_MM, /* "cm", "in", "pt" and "pc" are converted into + millimeters. */ + CSS_LENGTH_TYPE_EM, + CSS_LENGTH_TYPE_EX, + CSS_LENGTH_TYPE_PERCENTAGE, + CSS_LENGTH_TYPE_RELATIVE, /* This does not exist in CSS but + is used in HTML */ + CSS_LENGTH_TYPE_AUTO /* This can be used as a simple value. */ +} CssLengthType; + +inline CssLength CSS_CREATE_LENGTH (float v, CssLengthType t) { + static const int CSS_LENGTH_FRAC_MAX = (1 << (32 - 15 - 1)) - 1; + static const int CSS_LENGTH_INT_MAX = (1 << (32 - 4)) - 1; + int iv; + + switch (t) { + case CSS_LENGTH_TYPE_PX: + iv = (int) (v + 0.5); + if (iv > CSS_LENGTH_INT_MAX) + iv = CSS_LENGTH_INT_MAX; + else if (iv < -CSS_LENGTH_INT_MAX) + iv = -CSS_LENGTH_INT_MAX; + return iv << 3 | t; + case CSS_LENGTH_TYPE_NONE: + case CSS_LENGTH_TYPE_MM: + case CSS_LENGTH_TYPE_EM: + case CSS_LENGTH_TYPE_EX: + case CSS_LENGTH_TYPE_PERCENTAGE: + case CSS_LENGTH_TYPE_RELATIVE: + if (v > CSS_LENGTH_FRAC_MAX) + v = CSS_LENGTH_FRAC_MAX; + else if (v < -CSS_LENGTH_FRAC_MAX) + v = -CSS_LENGTH_FRAC_MAX; + return ((int) (v * (1 << 15)) & ~7 ) | t; + case CSS_LENGTH_TYPE_AUTO: + return t; + default: + assert(false); + return CSS_LENGTH_TYPE_AUTO; + } +} + +inline CssLengthType CSS_LENGTH_TYPE (CssLength l) { + return (CssLengthType) (l & 7); +} + +inline float CSS_LENGTH_VALUE (CssLength l) { + switch (CSS_LENGTH_TYPE(l)) { + case CSS_LENGTH_TYPE_PX: + return (float) (l >> 3); + case CSS_LENGTH_TYPE_NONE: + case CSS_LENGTH_TYPE_MM: + case CSS_LENGTH_TYPE_EM: + case CSS_LENGTH_TYPE_EX: + case CSS_LENGTH_TYPE_PERCENTAGE: + case CSS_LENGTH_TYPE_RELATIVE: + return ((float)(l & ~7)) / (1 << 15); + case CSS_LENGTH_TYPE_AUTO: + return 0.0; + default: + assert(false); + return 0.0; + } +} + +typedef enum { + CSS_PROPERTY_BACKGROUND_ATTACHMENT, + CSS_PROPERTY_BACKGROUND_COLOR, + CSS_PROPERTY_BACKGROUND_IMAGE, + CSS_PROPERTY_BACKGROUND_POSITION, + CSS_PROPERTY_BACKGROUND_REPEAT, + CSS_PROPERTY_BORDER_BOTTOM_COLOR, + CSS_PROPERTY_BORDER_BOTTOM_STYLE, + CSS_PROPERTY_BORDER_BOTTOM_WIDTH, + CSS_PROPERTY_BORDER_COLLAPSE, + CSS_PROPERTY_BORDER_LEFT_COLOR, + CSS_PROPERTY_BORDER_LEFT_STYLE, + CSS_PROPERTY_BORDER_LEFT_WIDTH, + CSS_PROPERTY_BORDER_RIGHT_COLOR, + CSS_PROPERTY_BORDER_RIGHT_STYLE, + CSS_PROPERTY_BORDER_RIGHT_WIDTH, + CSS_PROPERTY_BORDER_SPACING, + CSS_PROPERTY_BORDER_TOP_COLOR, + CSS_PROPERTY_BORDER_TOP_STYLE, + CSS_PROPERTY_BORDER_TOP_WIDTH, + CSS_PROPERTY_BOTTOM, + CSS_PROPERTY_CAPTION_SIDE, + CSS_PROPERTY_CLEAR, + CSS_PROPERTY_CLIP, + CSS_PROPERTY_COLOR, + CSS_PROPERTY_CONTENT, + CSS_PROPERTY_COUNTER_INCREMENT, + CSS_PROPERTY_COUNTER_RESET, + CSS_PROPERTY_CURSOR, + CSS_PROPERTY_DIRECTION, + CSS_PROPERTY_DISPLAY, + CSS_PROPERTY_EMPTY_CELLS, + CSS_PROPERTY_FLOAT, + CSS_PROPERTY_FONT_FAMILY, + CSS_PROPERTY_FONT_SIZE, + CSS_PROPERTY_FONT_SIZE_ADJUST, + CSS_PROPERTY_FONT_STRETCH, + CSS_PROPERTY_FONT_STYLE, + CSS_PROPERTY_FONT_VARIANT, + CSS_PROPERTY_FONT_WEIGHT, + CSS_PROPERTY_HEIGHT, + CSS_PROPERTY_LEFT, + CSS_PROPERTY_LETTER_SPACING, + CSS_PROPERTY_LINE_HEIGHT, + CSS_PROPERTY_LIST_STYLE_IMAGE, + CSS_PROPERTY_LIST_STYLE_POSITION, + CSS_PROPERTY_LIST_STYLE_TYPE, + CSS_PROPERTY_MARGIN_BOTTOM, + CSS_PROPERTY_MARGIN_LEFT, + CSS_PROPERTY_MARGIN_RIGHT, + CSS_PROPERTY_MARGIN_TOP, + CSS_PROPERTY_MARKER_OFFSET, + CSS_PROPERTY_MARKS, + CSS_PROPERTY_MAX_HEIGHT, + CSS_PROPERTY_MAX_WIDTH, + CSS_PROPERTY_MIN_HEIGHT, + CSS_PROPERTY_MIN_WIDTH, + CSS_PROPERTY_OUTLINE_COLOR, + CSS_PROPERTY_OUTLINE_STYLE, + CSS_PROPERTY_OUTLINE_WIDTH, + CSS_PROPERTY_OVERFLOW, + CSS_PROPERTY_PADDING_BOTTOM, + CSS_PROPERTY_PADDING_LEFT, + CSS_PROPERTY_PADDING_RIGHT, + CSS_PROPERTY_PADDING_TOP, + CSS_PROPERTY_POSITION, + CSS_PROPERTY_QUOTES, + CSS_PROPERTY_RIGHT, + CSS_PROPERTY_TEXT_ALIGN, + CSS_PROPERTY_TEXT_DECORATION, + CSS_PROPERTY_TEXT_INDENT, + CSS_PROPERTY_TEXT_SHADOW, + CSS_PROPERTY_TEXT_TRANSFORM, + CSS_PROPERTY_TOP, + CSS_PROPERTY_UNICODE_BIDI, + CSS_PROPERTY_VERTICAL_ALIGN, + CSS_PROPERTY_VISIBILITY, + CSS_PROPERTY_WHITE_SPACE, + CSS_PROPERTY_WIDTH, + CSS_PROPERTY_WORD_SPACING, + CSS_PROPERTY_Z_INDEX, + CSS_PROPERTY_X_LINK, + CSS_PROPERTY_X_COLSPAN, + CSS_PROPERTY_X_ROWSPAN, + PROPERTY_X_LINK, + PROPERTY_X_IMG, + PROPERTY_X_TOOLTIP, + CSS_PROPERTY_LAST +} CssPropertyName; + +typedef union { + int32_t intVal; + char *strVal; +} CssPropertyValue; + +typedef enum { + CSS_BORDER_WIDTH_THIN, + CSS_BORDER_WIDTH_MEDIUM, + CSS_BORDER_WIDTH_THICK, +} CssBorderWidthExtensions; + +typedef enum { + CSS_FONT_WEIGHT_BOLD, + CSS_FONT_WEIGHT_BOLDER, + CSS_FONT_WEIGHT_LIGHT, + CSS_FONT_WEIGHT_LIGHTER, + CSS_FONT_WEIGHT_NORMAL, +} CssFontWeightExtensions; + +typedef enum { + CSS_FONT_SIZE_LARGE, + CSS_FONT_SIZE_LARGER, + CSS_FONT_SIZE_MEDIUM, + CSS_FONT_SIZE_SMALL, + CSS_FONT_SIZE_SMALLER, + CSS_FONT_SIZE_XX_LARGE, + CSS_FONT_SIZE_XX_SMALL, + CSS_FONT_SIZE_X_LARGE, + CSS_FONT_SIZE_X_SMALL, +} CssFontSizeExtensions; + +typedef enum { + CSS_LETTER_SPACING_NORMAL +} CssLetterSpacingExtensions; + +typedef enum { + CSS_WORD_SPACING_NORMAL +} CssWordSpacingExtensions; + + +/** + * \brief This class holds a CSS property and value pair. + */ +class CssProperty { + public: + + short name; + short type; + CssPropertyValue value; + + inline void free () { + switch (type) { + case CSS_TYPE_STRING: + case CSS_TYPE_SYMBOL: + dFree (value.strVal); + break; + default: + break; + } + } + void print (); +}; + +/** + * \brief A list of CssProperty objects. + */ +class CssPropertyList : public lout::misc::SimpleVector <CssProperty> { + int refCount; + bool ownerOfStrings; + + public: + inline CssPropertyList(bool ownerOfStrings = false) : + lout::misc::SimpleVector <CssProperty> (1) { + refCount = 0; + this->ownerOfStrings = ownerOfStrings; + }; + inline CssPropertyList(const CssPropertyList &p) : + lout::misc::SimpleVector <CssProperty> (p) { + refCount = 0; + ownerOfStrings = false; + }; + ~CssPropertyList (); + + void set (CssPropertyName name, CssValueType type, + CssPropertyValue value); + inline void set (CssPropertyName name, CssValueType type, char *value) { + CssPropertyValue v; + v.strVal = value; + set (name, type, v); + }; + inline void set (CssPropertyName name, CssValueType type, int value) { + CssPropertyValue v; + v.intVal = value; + set (name, type, v); + }; + void apply (CssPropertyList *props); + void print (); + inline void ref () { refCount++; } + inline void unref () { if (--refCount == 0) delete this; } +}; + +class CssSimpleSelector { + private: + int element; + char *pseudo, *id; + lout::misc::SimpleVector <char *> *klass; + + public: + enum { + ELEMENT_NONE = -1, + ELEMENT_ANY = -2, + }; + + typedef enum { + SELECT_NONE, + SELECT_CLASS, + SELECT_PSEUDO_CLASS, + SELECT_ID, + } SelectType; + + CssSimpleSelector (); + ~CssSimpleSelector (); + inline void setElement (int e) { element = e; }; + void setSelect (SelectType t, const char *v); + inline lout::misc::SimpleVector <char *> *getClass () { return klass; }; + inline const char *getPseudoClass () { return pseudo; }; + inline const char *getId () { return id; }; + inline int getElement () { return element; }; + bool match (const DoctreeNode *node); + int specificity (); + void print (); +}; + +/** + * \brief CSS selector class. + * + * \todo Implement missing selector options. + */ +class CssSelector { + public: + typedef enum { + DESCENDANT, + CHILD, + ADJACENT_SIBLING, + } Combinator; + + private: + struct CombinatorAndSelector { + int notMatchingBefore; // used for optimizing CSS selector matching + Combinator combinator; + CssSimpleSelector *selector; + }; + + int refCount; + lout::misc::SimpleVector <struct CombinatorAndSelector> *selectorList; + + public: + CssSelector (); + ~CssSelector (); + void addSimpleSelector (Combinator c); + inline CssSimpleSelector *top () { + return selectorList->getRef (selectorList->size () - 1)->selector; + }; + inline int size () { return selectorList->size (); }; + bool match (Doctree *dt, const DoctreeNode *node); + int specificity (); + void print (); + inline void ref () { refCount++; } + inline void unref () { if (--refCount == 0) delete this; } +}; + +/** + * \brief A CssSelector CssPropertyList pair. + * + * The CssPropertyList is applied if the CssSelector matches. + */ +class CssRule { + private: + CssPropertyList *props; + int spec, pos; + + public: + CssSelector *selector; + + CssRule (CssSelector *selector, CssPropertyList *props, int pos); + ~CssRule (); + + void apply (CssPropertyList *props, + Doctree *docTree, const DoctreeNode *node); + inline int specificity () { return spec; }; + inline int position () { return pos; }; + void print (); +}; + +/** + * \brief A list of CssRules. + * + * In apply () all matching rules are applied. + */ +class CssStyleSheet { + private: + class RuleList : public lout::misc::SimpleVector <CssRule*>, + public lout::object::Object { + public: + RuleList () : lout::misc::SimpleVector <CssRule*> (1) {}; + ~RuleList () { + for (int i = 0; i < size (); i++) + delete get (i); + }; + + void insert (CssRule *rule); + inline bool equals (lout::object::Object *other) { + return this == other; + }; + inline int hashValue () { return (intptr_t) this; }; + }; + + class RuleMap : public lout::container::typed::HashTable + <lout::object::ConstString, RuleList > { + public: + RuleMap () : lout::container::typed::HashTable + <lout::object::ConstString, RuleList > (true, true, 256) {}; + }; + + static const int ntags = 90; // \todo replace 90 + RuleList *elementTable[ntags]; + + RuleMap *idTable; + RuleMap *classTable; + RuleList *anyTable; + + public: + CssStyleSheet(); + ~CssStyleSheet(); + void addRule (CssRule *rule); + void apply (CssPropertyList *props, + Doctree *docTree, const DoctreeNode *node); +}; + +/** + * \brief A set of CssStyleSheets. + */ +class CssContext { + private: + static CssStyleSheet *userAgentStyle; + static CssStyleSheet *userStyle; + static CssStyleSheet *userImportantStyle; + CssStyleSheet *sheet[CSS_PRIMARY_USER_IMPORTANT + 1]; + int pos; + + void buildUserAgentStyle (); + void buildUserStyle (); + + public: + CssContext (); + ~CssContext (); + + void addRule (CssSelector *sel, CssPropertyList *props, + CssPrimaryOrder order); + void apply (CssPropertyList *props, + Doctree *docTree, + CssPropertyList *tagStyle, CssPropertyList *nonCssHints); +}; + +#endif diff --git a/src/cssparser.cc b/src/cssparser.cc new file mode 100644 index 00000000..00ba7428 --- /dev/null +++ b/src/cssparser.cc @@ -0,0 +1,1476 @@ +/* + * File: cssparser.cc + * + * Copyright 2004 Sebastian Geerken <sgeerken@dillo.org> + * Copyright 2008-2009 Johannes Hofmann <Johannes.Hofmann@gmx.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 3 of the License, or + * (at your option) any later version. + */ + +/* + * This file is heavily based on the CSS parser of dillo-0.8.0-css-3 - + * a dillo1 based CSS prototype written by Sebastian Geerken. + */ + +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> + +#include "msg.h" +#include "colors.h" +#include "html_common.hh" +#include "css.hh" +#include "cssparser.hh" + +using namespace dw::core::style; + +#define DEBUG_MSG(A, B, ...) _MSG(B, __VA_ARGS__) +#define MSG_CSS(A, ...) MSG(A, __VA_ARGS__) +#define DEBUG_TOKEN_LEVEL 0 +#define DEBUG_PARSE_LEVEL 0 +#define DEBUG_CREATE_LEVEL 0 + +#define DEBUG_LEVEL 10 + +/* The last three ones are never parsed. */ +#define CSS_NUM_INTERNAL_PROPERTIES 3 +#define CSS_NUM_PARSED_PROPERTIES \ + (CSS_PROPERTY_LAST - CSS_NUM_INTERNAL_PROPERTIES) + + +typedef struct { + const char *symbol; + const CssValueType type[3]; + const char *const *enum_symbols; +} CssPropertyInfo; + +static const char *const Css_border_style_enum_vals[] = { + "none", "hidden", "dotted", "dashed", "solid", "double", "groove", + "ridge", "inset", "outset", NULL +}; + +static const char *const Css_border_width_enum_vals[] = { + "thin", "medium", "thick", NULL +}; + +static const char *const Css_cursor_enum_vals[] = { + "crosshair", "default", "pointer", "move", "e-resize", "ne-resize", + "nw-resize", "n-resize", "se-resize", "sw-resize", "s-resize", + "w-resize", "text", "wait", "help", NULL +}; + +static const char *const Css_display_enum_vals[] = { + "block", "inline", "list-item", "none", "table", "table-row-group", + "table-header-group", "table-footer-group", "table-row", + "table-cell", NULL +}; + +static const char *const Css_font_size_enum_vals[] = { + "large", "larger", "medium", "small", "smaller", "xx-large", "xx-small", + "x-large", "x-small", NULL +}; + +static const char *const Css_font_style_enum_vals[] = { + "normal", "italic", "oblique", NULL +}; + +static const char *const Css_font_weight_enum_vals[] = { + "bold", "bolder", "light", "lighter", "normal", NULL +}; + +static const char *const Css_letter_spacing_enum_vals[] = { + "normal", NULL +}; + +static const char *const Css_list_style_position_enum_vals[] = { + "inside", "outside", NULL +}; + +static const char *const Css_line_height_enum_vals[] = { + "normal", NULL +}; + +static const char *const Css_list_style_type_enum_vals[] = { + "disc", "circle", "square", "decimal", "decimal-leading-zero", + "lower-roman", "upper-roman", "lower-greek", "lower-alpha", + "lower-latin", "upper-alpha", "upper-latin", "hebrew", "armenian", + "georgian", "cjk-ideographic", "hiragana", "katakana", "hiragana-iroha", + "katakana-iroha", "none", NULL +}; + +static const char *const Css_text_align_enum_vals[] = { + "left", "right", "center", "justify", "string", NULL +}; + +static const char *const Css_text_decoration_enum_vals[] = { + "underline", "overline", "line-through", "blink", NULL +}; + +static const char *const Css_vertical_align_vals[] = { + "top", "bottom", "middle", "baseline", "sub", "super", "text-top", + "text-bottom", NULL +}; + +static const char *const Css_white_space_vals[] = { + "normal", "pre", "nowrap", "pre-wrap", "pre-line", NULL +}; + +static const char *const Css_word_spacing_enum_vals[] = { + "normal", NULL +}; + +const CssPropertyInfo Css_property_info[CSS_PROPERTY_LAST] = { + {"background-attachment", {CSS_TYPE_UNUSED}, NULL}, + {"background-color", {CSS_TYPE_COLOR, CSS_TYPE_UNUSED}, NULL}, + {"background-image", {CSS_TYPE_UNUSED}, NULL}, + {"background-position", {CSS_TYPE_UNUSED}, NULL}, + {"background-repeat", {CSS_TYPE_UNUSED}, NULL}, + {"border-bottom-color", {CSS_TYPE_COLOR, CSS_TYPE_UNUSED}, NULL}, + {"border-bottom-style", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, + Css_border_style_enum_vals}, + {"border-bottom-width", {CSS_TYPE_ENUM, CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, + Css_border_width_enum_vals}, + {"border-collapse", {CSS_TYPE_UNUSED}, NULL}, + {"border-left-color", {CSS_TYPE_COLOR, CSS_TYPE_UNUSED}, NULL}, + {"border-left-style", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, + Css_border_style_enum_vals}, + {"border-left-width", {CSS_TYPE_ENUM, CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, + Css_border_width_enum_vals}, + {"border-right-color", {CSS_TYPE_COLOR, CSS_TYPE_UNUSED}, NULL}, + {"border-right-style", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, + Css_border_style_enum_vals}, + {"border-rigth-width", {CSS_TYPE_ENUM, CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, + Css_border_width_enum_vals}, + {"border-spacing", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL}, + {"border-top-color", {CSS_TYPE_COLOR, CSS_TYPE_UNUSED}, NULL}, + {"border-top-style", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, + Css_border_style_enum_vals}, + {"border-top-width", {CSS_TYPE_ENUM, CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, + Css_border_width_enum_vals}, + {"bottom", {CSS_TYPE_UNUSED}, NULL}, + {"caption-side", {CSS_TYPE_UNUSED}, NULL}, + {"clear", {CSS_TYPE_UNUSED}, NULL}, + {"clip", {CSS_TYPE_UNUSED}, NULL}, + {"color", {CSS_TYPE_COLOR, CSS_TYPE_UNUSED}, NULL}, + {"content", {CSS_TYPE_STRING, CSS_TYPE_UNUSED}, NULL}, + {"counter-increment", {CSS_TYPE_UNUSED}, NULL}, + {"counter-reset", {CSS_TYPE_UNUSED}, NULL}, + {"cursor", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_cursor_enum_vals}, + {"direction", {CSS_TYPE_UNUSED}, NULL}, + {"display", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_display_enum_vals}, + {"empty-cells", {CSS_TYPE_UNUSED}, NULL}, + {"float", {CSS_TYPE_UNUSED}, NULL}, + {"font-family", {CSS_TYPE_SYMBOL, CSS_TYPE_UNUSED}, NULL}, + {"font-size", {CSS_TYPE_ENUM, CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_UNUSED}, + Css_font_size_enum_vals}, + {"font-size-adjust", {CSS_TYPE_UNUSED}, NULL}, + {"font-stretch", {CSS_TYPE_UNUSED}, NULL}, + {"font-style", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_font_style_enum_vals}, + {"font-variant", {CSS_TYPE_UNUSED}, NULL}, + {"font-weight", {CSS_TYPE_ENUM, CSS_TYPE_FONT_WEIGHT, CSS_TYPE_UNUSED}, + Css_font_weight_enum_vals}, + {"height", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_UNUSED}, NULL}, + {"left", {CSS_TYPE_UNUSED}, NULL}, + {"letter-spacing", {CSS_TYPE_ENUM, CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_UNUSED}, + Css_letter_spacing_enum_vals}, + {"line-height", + {CSS_TYPE_ENUM, CSS_TYPE_LENGTH_PERCENTAGE_NUMBER, CSS_TYPE_UNUSED}, + Css_line_height_enum_vals}, + {"list-style-image", {CSS_TYPE_UNUSED}, NULL}, + {"list-style-position", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, + Css_list_style_position_enum_vals}, + {"list-style-type", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, + Css_list_style_type_enum_vals}, + {"margin-bottom", {CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_UNUSED}, NULL}, + {"margin-left", {CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_UNUSED}, NULL}, + {"margin-right", {CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_UNUSED}, NULL}, + {"margin-top", {CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_UNUSED}, NULL}, + {"marker-offset", {CSS_TYPE_UNUSED}, NULL}, + {"marks", {CSS_TYPE_UNUSED}, NULL}, + {"max-height", {CSS_TYPE_UNUSED}, NULL}, + {"max-width", {CSS_TYPE_UNUSED}, NULL}, + {"min-height", {CSS_TYPE_UNUSED}, NULL}, + {"min-width", {CSS_TYPE_UNUSED}, NULL}, + {"outline-color", {CSS_TYPE_UNUSED}, NULL}, + {"outline-style", {CSS_TYPE_UNUSED}, NULL}, + {"outline-width", {CSS_TYPE_UNUSED}, NULL}, + {"overflow", {CSS_TYPE_UNUSED}, NULL}, + {"padding-bottom", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL}, + {"padding-left", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL}, + {"padding-right", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL}, + {"padding-top", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL}, + {"position", {CSS_TYPE_UNUSED}, NULL}, + {"quotes", {CSS_TYPE_UNUSED}, NULL}, + {"right", {CSS_TYPE_UNUSED}, NULL}, + {"text-align", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_text_align_enum_vals}, + {"text-decoration", {CSS_TYPE_MULTI_ENUM, CSS_TYPE_UNUSED}, + Css_text_decoration_enum_vals}, + {"text-indent", {CSS_TYPE_UNUSED}, NULL}, + {"text-shadow", {CSS_TYPE_UNUSED}, NULL}, + {"text-transform", {CSS_TYPE_UNUSED}, NULL}, + {"top", {CSS_TYPE_UNUSED}, NULL}, + {"unicode-bidi", {CSS_TYPE_UNUSED}, NULL}, + {"vertical-align",{CSS_TYPE_ENUM, CSS_TYPE_UNUSED},Css_vertical_align_vals}, + {"visibility", {CSS_TYPE_UNUSED}, NULL}, + {"white-space", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_white_space_vals}, + {"width", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_UNUSED}, NULL}, + {"word-spacing", {CSS_TYPE_ENUM, CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_UNUSED}, + Css_word_spacing_enum_vals}, + {"z-index", {CSS_TYPE_UNUSED}, NULL}, + + /* These are extensions, for internal used, and never parsed. */ + {"x-link", {CSS_TYPE_INTEGER, CSS_TYPE_UNUSED}, NULL}, + {"x-colspan", {CSS_TYPE_INTEGER, CSS_TYPE_UNUSED}, NULL}, + {"x-rowspan", {CSS_TYPE_INTEGER, CSS_TYPE_UNUSED}, NULL}, + {"last", {CSS_TYPE_UNUSED}, NULL}, +}; + +typedef struct { + const char *symbol; + enum { + CSS_SHORTHAND_MULTIPLE, /* [ p1 || p2 || ...], the property pi is + * determined by the type */ + CSS_SHORTHAND_DIRECTIONS, /* <t>{1,4} */ + CSS_SHORTHAND_BORDER, /* special, used for 'border' */ + CSS_SHORTHAND_FONT, /* special, used for 'font' */ + } type; + const CssPropertyName * properties;/* CSS_SHORTHAND_MULTIPLE: + * must be terminated by -1 + * CSS_SHORTHAND_DIRECTIONS: + * must have length 4 + * CSS_SHORTHAND_BORDERS: + * must have length 12 + * CSS_SHORTHAND_FONT: + * unused */ +} CssShorthandInfo; + +const CssPropertyName Css_background_properties[] = { + CSS_PROPERTY_BACKGROUND_COLOR, + CSS_PROPERTY_BACKGROUND_IMAGE, + CSS_PROPERTY_BACKGROUND_REPEAT, + CSS_PROPERTY_BACKGROUND_ATTACHMENT, + CSS_PROPERTY_BACKGROUND_POSITION, + (CssPropertyName) - 1 +}; + +const CssPropertyName Css_border_bottom_properties[] = { + CSS_PROPERTY_BORDER_BOTTOM_WIDTH, + CSS_PROPERTY_BORDER_BOTTOM_STYLE, + CSS_PROPERTY_BORDER_BOTTOM_COLOR, + (CssPropertyName) - 1 +}; + +const CssPropertyName Css_border_color_properties[4] = { + CSS_PROPERTY_BORDER_TOP_COLOR, + CSS_PROPERTY_BORDER_BOTTOM_COLOR, + CSS_PROPERTY_BORDER_LEFT_COLOR, + CSS_PROPERTY_BORDER_RIGHT_COLOR +}; + +const CssPropertyName Css_border_left_properties[] = { + CSS_PROPERTY_BORDER_LEFT_WIDTH, + CSS_PROPERTY_BORDER_LEFT_STYLE, + CSS_PROPERTY_BORDER_LEFT_COLOR, + (CssPropertyName) - 1 +}; + +const CssPropertyName Css_border_right_properties[] = { + CSS_PROPERTY_BORDER_RIGHT_WIDTH, + CSS_PROPERTY_BORDER_RIGHT_STYLE, + CSS_PROPERTY_BORDER_RIGHT_COLOR, + (CssPropertyName) - 1 +}; + +const CssPropertyName Css_border_style_properties[] = { + CSS_PROPERTY_BORDER_TOP_STYLE, + CSS_PROPERTY_BORDER_BOTTOM_STYLE, + CSS_PROPERTY_BORDER_LEFT_STYLE, + CSS_PROPERTY_BORDER_RIGHT_STYLE +}; + +const CssPropertyName Css_border_top_properties[] = { + CSS_PROPERTY_BORDER_TOP_WIDTH, + CSS_PROPERTY_BORDER_TOP_STYLE, + CSS_PROPERTY_BORDER_TOP_COLOR, + (CssPropertyName) - 1 +}; + +const CssPropertyName Css_border_width_properties[] = { + CSS_PROPERTY_BORDER_TOP_WIDTH, + CSS_PROPERTY_BORDER_BOTTOM_WIDTH, + CSS_PROPERTY_BORDER_LEFT_WIDTH, + CSS_PROPERTY_BORDER_RIGHT_WIDTH +}; + +const CssPropertyName Css_list_style_properties[] = { + CSS_PROPERTY_LIST_STYLE_TYPE, + CSS_PROPERTY_LIST_STYLE_POSITION, + CSS_PROPERTY_LIST_STYLE_IMAGE, + (CssPropertyName) - 1 +}; + +const CssPropertyName Css_margin_properties[] = { + CSS_PROPERTY_MARGIN_TOP, + CSS_PROPERTY_MARGIN_BOTTOM, + CSS_PROPERTY_MARGIN_LEFT, + CSS_PROPERTY_MARGIN_RIGHT +}; + +const CssPropertyName Css_outline_properties[] = { + CSS_PROPERTY_OUTLINE_COLOR, + CSS_PROPERTY_OUTLINE_STYLE, + CSS_PROPERTY_OUTLINE_WIDTH, + (CssPropertyName) - 1 +}; + +const CssPropertyName Css_padding_properties[] = { + CSS_PROPERTY_PADDING_TOP, + CSS_PROPERTY_PADDING_BOTTOM, + CSS_PROPERTY_PADDING_LEFT, + CSS_PROPERTY_PADDING_RIGHT +}; + +const CssPropertyName Css_border_properties[] = { + CSS_PROPERTY_BORDER_TOP_WIDTH, + CSS_PROPERTY_BORDER_TOP_STYLE, + CSS_PROPERTY_BORDER_TOP_COLOR, + CSS_PROPERTY_BORDER_BOTTOM_WIDTH, + CSS_PROPERTY_BORDER_BOTTOM_STYLE, + CSS_PROPERTY_BORDER_BOTTOM_COLOR, + CSS_PROPERTY_BORDER_LEFT_WIDTH, + CSS_PROPERTY_BORDER_LEFT_STYLE, + CSS_PROPERTY_BORDER_LEFT_COLOR, + CSS_PROPERTY_BORDER_RIGHT_WIDTH, + CSS_PROPERTY_BORDER_RIGHT_STYLE, + CSS_PROPERTY_BORDER_RIGHT_COLOR +}; + +const CssPropertyName Css_font_properties[] = { + CSS_PROPERTY_FONT_SIZE, + CSS_PROPERTY_FONT_STYLE, + CSS_PROPERTY_FONT_VARIANT, + CSS_PROPERTY_FONT_WEIGHT, + CSS_PROPERTY_FONT_FAMILY, + (CssPropertyName) - 1 +}; + +static const CssShorthandInfo Css_shorthand_info[] = { + {"background", CssShorthandInfo::CSS_SHORTHAND_MULTIPLE, + Css_background_properties}, + {"border", CssShorthandInfo::CSS_SHORTHAND_BORDER, + Css_border_properties}, + {"border-bottom", CssShorthandInfo::CSS_SHORTHAND_MULTIPLE, + Css_border_bottom_properties}, + {"border-color", CssShorthandInfo::CSS_SHORTHAND_DIRECTIONS, + Css_border_color_properties}, + {"border-left", CssShorthandInfo::CSS_SHORTHAND_MULTIPLE, + Css_border_left_properties}, + {"border-right", CssShorthandInfo::CSS_SHORTHAND_MULTIPLE, + Css_border_right_properties}, + {"border-style", CssShorthandInfo::CSS_SHORTHAND_DIRECTIONS, + Css_border_style_properties}, + {"border-top", CssShorthandInfo::CSS_SHORTHAND_MULTIPLE, + Css_border_top_properties}, + {"border-width", CssShorthandInfo::CSS_SHORTHAND_DIRECTIONS, + Css_border_width_properties}, + {"font", CssShorthandInfo::CSS_SHORTHAND_FONT, + Css_font_properties}, + {"list-style", CssShorthandInfo::CSS_SHORTHAND_MULTIPLE, + Css_list_style_properties}, + {"margin", CssShorthandInfo::CSS_SHORTHAND_DIRECTIONS, + Css_margin_properties}, + {"outline", CssShorthandInfo::CSS_SHORTHAND_MULTIPLE, + Css_outline_properties}, + {"padding", CssShorthandInfo::CSS_SHORTHAND_DIRECTIONS, + Css_padding_properties}, +}; + +#define CSS_SHORTHAND_NUM \ + (sizeof(Css_shorthand_info) / sizeof(CssShorthandInfo)) + +/* ---------------------------------------------------------------------- + * Parsing + * ---------------------------------------------------------------------- */ + +CssParser::CssParser(CssContext *context, CssOrigin origin, + const char *buf, int buflen) +{ + this->context = context; + this->origin = origin; + this->buf = buf; + this->buflen = buflen; + this->bufptr = 0; + this->spaceSeparated = false; + this->withinBlock = false; + + nextToken (); +} + +/* + * Gets the next character from the buffer, or EOF. + */ +int CssParser::getChar() +{ + int c; + + if (bufptr >= buflen) + c = EOF; + else + c = buf[bufptr]; + + /* The buffer pointer is increased in any case, so that ungetChar works + * correctly at the end of the buffer. */ + bufptr++; + return c; +} + +/* + * Undoes the last getChar(). + */ +void CssParser::ungetChar() +{ + bufptr--; +} + +/* + * Skip string str if it is found in the input buffer. + * If not wind back. The first char is passed as parameter c + * to avoid unnecessary getChar() / ungetChar() calls. + */ +inline bool CssParser::skipString(int c, const char *str) +{ + int n = 0; + + while (str[n]) { + if (str[n] != c) { + while (n--) + ungetChar(); + return false; + } + c = getChar(); + n++; + } + + return true; +} + +void CssParser::nextToken() +{ + int c, c1, d, j; + char hexbuf[5]; + int i = 0; + + ttype = CSS_TK_CHAR; /* init */ + spaceSeparated = false; + + while (true) { + c = getChar(); + if (isspace(c)) { // ignore whitespace + spaceSeparated = true; + } else if (skipString(c, "/*")) { // ignore comments + do { + c = getChar(); + } while (c != EOF && ! skipString(c, "*/")); + } else if (skipString(c, "<!--")) { // ignore XML comment markers + } else if (skipString(c, "-->")) { + } else { + break; + } + } + + // handle negative numbers + if (c == '-') { + if (i < maxStrLen - 1) + tval[i++] = c; + c = getChar(); + } + + if (isdigit(c)) { + ttype = CSS_TK_DECINT; + do { + if (i < maxStrLen - 1) { + tval[i++] = c; + } + /* else silently truncated */ + c = getChar(); + } while (isdigit(c)); + if (c != '.') + ungetChar(); + + /* ...but keep going to see whether it's really a float */ + } + + if (c == '.') { + c = getChar(); + if (isdigit(c)) { + ttype = CSS_TK_FLOAT; + if (i < maxStrLen - 1) + tval[i++] = '.'; + do { + if (i < maxStrLen - 1) + tval[i++] = c; + /* else silently truncated */ + c = getChar(); + } while (isdigit(c)); + + ungetChar(); + tval[i] = 0; + DEBUG_MSG(DEBUG_TOKEN_LEVEL, "token number %s\n", tval); + return; + } else { + ungetChar(); + if (ttype == CSS_TK_DECINT) { + ungetChar(); + } else { + c = '.'; + } + } + } + + if (ttype == CSS_TK_DECINT) { + tval[i] = 0; + DEBUG_MSG(DEBUG_TOKEN_LEVEL, "token number %s\n", tval); + return; + } + + if (i) { + ungetChar(); /* ungetChar '-' */ + i--; + c = getChar(); + } + + if (isalpha(c) || c == '_' || c == '-') { + ttype = CSS_TK_SYMBOL; + + tval[0] = c; + i = 1; + c = getChar(); + while (isalnum(c) || c == '_' || c == '-') { + if (i < maxStrLen - 1) { + tval[i] = c; + i++; + } /* else silently truncated */ + c = getChar(); + } + tval[i] = 0; + ungetChar(); + DEBUG_MSG(DEBUG_TOKEN_LEVEL, "token symbol '%s'\n", tval); + return; + } + + if (c == '"' || c == '\'') { + c1 = c; + ttype = CSS_TK_STRING; + + i = 0; + c = getChar(); + + while (c != EOF && c != c1) { + if (c == '\\') { + d = getChar(); + if (isxdigit(d)) { + /* Read hex Unicode char. (Actually, strings are yet only 8 + * bit.) */ + hexbuf[0] = d; + j = 1; + d = getChar(); + while (j < 4 && isxdigit(d)) { + hexbuf[j] = d; + j++; + d = getChar(); + } + hexbuf[j] = 0; + ungetChar(); + c = strtol(hexbuf, NULL, 16); + } else { + /* Take character literally. */ + c = d; + } + } + + if (i < maxStrLen - 1) { + tval[i] = c; + i++; + } /* else silently truncated */ + c = getChar(); + } + tval[i] = 0; + /* No ungetChar(). */ + DEBUG_MSG(DEBUG_TOKEN_LEVEL, "token string '%s'\n", tval); + return; + } + + /* + * Within blocks, '#' starts a color, outside, it is used in selectors. + */ + if (c == '#' && withinBlock) { + ttype = CSS_TK_COLOR; + + tval[0] = c; + i = 1; + c = getChar(); + while (isxdigit(c)) { + if (i < maxStrLen - 1) { + tval[i] = c; + i++; + } /* else silently truncated */ + c = getChar(); + } + tval[i] = 0; + ungetChar(); + DEBUG_MSG(DEBUG_TOKEN_LEVEL, "token color '%s'\n", tval); + return; + } + + if (c == EOF) { + DEBUG_MSG(DEBUG_TOKEN_LEVEL, "token %s\n", "EOF"); + ttype = CSS_TK_END; + return; + } + + ttype = CSS_TK_CHAR; + tval[0] = c; + tval[1] = 0; + DEBUG_MSG(DEBUG_TOKEN_LEVEL, "token char '%c'\n", c); +} + + +bool CssParser::tokenMatchesProperty(CssPropertyName prop, CssValueType *type) +{ + int i, err = 1; + CssValueType savedType = *type; + + for (int j = 0; Css_property_info[prop].type[j] != CSS_TYPE_UNUSED; j++) { + *type = Css_property_info[prop].type[j]; + + switch (Css_property_info[prop].type[j]) { + + case CSS_TYPE_ENUM: + if (ttype == CSS_TK_SYMBOL) { + for (i = 0; Css_property_info[prop].enum_symbols[i]; i++) + if (dStrcasecmp(tval, + Css_property_info[prop].enum_symbols[i]) == 0) + return true; + } + break; + + case CSS_TYPE_MULTI_ENUM: + if (ttype == CSS_TK_SYMBOL) { + if (dStrcasecmp(tval, "none") == 0) + return true; + else { + for (i = 0; Css_property_info[prop].enum_symbols[i]; i++) { + if (dStrcasecmp(tval, + Css_property_info[prop].enum_symbols[i]) == 0) + return true; + } + } + } + break; + + case CSS_TYPE_LENGTH_PERCENTAGE: + case CSS_TYPE_LENGTH_PERCENTAGE_NUMBER: + case CSS_TYPE_LENGTH: + if (tval[0] == '-') + return false; + // Fall Through + case CSS_TYPE_SIGNED_LENGTH: + if (ttype == CSS_TK_DECINT || + ttype == CSS_TK_FLOAT || + (ttype == CSS_TK_SYMBOL && dStrcasecmp(tval, "auto") == 0)) + return true; + break; + + case CSS_TYPE_COLOR: + if ((ttype == CSS_TK_COLOR || + ttype == CSS_TK_SYMBOL) && + (dStrcasecmp(tval, "rgb") == 0 || + a_Color_parse(tval, -1, &err) != -1)) + return true; + break; + + case CSS_TYPE_STRING: + if (ttype == CSS_TK_STRING) + return true; + break; + + case CSS_TYPE_SYMBOL: + if (ttype == CSS_TK_SYMBOL || + ttype == CSS_TK_STRING) + return true; + break; + + case CSS_TYPE_FONT_WEIGHT: + if (ttype == CSS_TK_DECINT) { + i = strtol(tval, NULL, 10); + if (i >= 100 && i <= 900) + return true; + } + break; + + case CSS_TYPE_UNUSED: + case CSS_TYPE_INTEGER: + /* Not used for parser values. */ + default: + assert(false); + break; + } + } + + *type = savedType; + return false; +} + +bool CssParser::parseRgbColorComponent(int32_t *cc, int *percentage) { + if (ttype != CSS_TK_DECINT) { + MSG_CSS("expected integer not found in %s color\n", "rgb"); + return false; + } + + *cc = strtol(tval, NULL, 10); + + nextToken(); + if (ttype == CSS_TK_CHAR && tval[0] == '%') { + if (*percentage == 0) { + MSG_CSS("'%s' unexpected in rgb color\n", "%"); + return false; + } + *percentage = 1; + *cc = *cc * 255 / 100; + nextToken(); + } else { + if (*percentage == 1) { + MSG_CSS("expected '%s' not found in rgb color\n", "%"); + return false; + } + *percentage = 0; + } + + if (*cc > 255) + *cc = 255; + if (*cc < 0) + *cc = 0; + + return true; +} + +bool CssParser::parseRgbColor(int32_t *c) { + int32_t cc; + int percentage = -1; + + *c = 0; + + if (ttype != CSS_TK_CHAR || tval[0] != '(') { + MSG_CSS("expected '%s' not found in rgb color\n", "("); + return false; + } + nextToken(); + + if (!parseRgbColorComponent(&cc, &percentage)) + return false; + *c |= cc << 16; + + if (ttype != CSS_TK_CHAR || tval[0] != ',') { + MSG_CSS("expected '%s' not found in rgb color\n", ","); + return false; + } + nextToken(); + + if (!parseRgbColorComponent(&cc, &percentage)) + return false; + *c |= cc << 8; + + if (ttype != CSS_TK_CHAR || tval[0] != ',') { + MSG_CSS("expected '%s' not found in rgb color\n", ","); + return false; + } + nextToken(); + + if (!parseRgbColorComponent(&cc, &percentage)) + return false; + *c |= cc; + + if (ttype != CSS_TK_CHAR || tval[0] != ')') { + MSG_CSS("expected '%s' not found in rgb color\n", ")"); + return false; + } + + return true; +} + +bool CssParser::parseValue(CssPropertyName prop, + CssValueType type, + CssPropertyValue * val) +{ + CssLengthType lentype; + bool found, ret = false; + float fval; + int i, ival, err = 1; + Dstr *dstr; + + switch (type) { + case CSS_TYPE_ENUM: + if (ttype == CSS_TK_SYMBOL) { + for (i = 0; Css_property_info[prop].enum_symbols[i]; i++) + if (dStrcasecmp(tval, + Css_property_info[prop].enum_symbols[i]) == 0) { + val->intVal = i; + ret = true; + break; + } + nextToken(); + } + break; + + case CSS_TYPE_MULTI_ENUM: + val->intVal = 0; + ret = true; + + while (ttype == CSS_TK_SYMBOL) { + if (dStrcasecmp(tval, "none") != 0) { + for (i = 0, found = false; + !found && Css_property_info[prop].enum_symbols[i]; i++) { + if (dStrcasecmp(tval, + Css_property_info[prop].enum_symbols[i]) == 0) + val->intVal |= (1 << i); + } + } + nextToken(); + } + break; + + case CSS_TYPE_LENGTH_PERCENTAGE: + case CSS_TYPE_LENGTH_PERCENTAGE_NUMBER: + case CSS_TYPE_LENGTH: + case CSS_TYPE_SIGNED_LENGTH: + if (ttype == CSS_TK_DECINT || ttype == CSS_TK_FLOAT) { + fval = atof(tval); + lentype = CSS_LENGTH_TYPE_NONE; + + nextToken(); + if (!spaceSeparated && ttype == CSS_TK_SYMBOL) { + ret = true; + + if (dStrcasecmp(tval, "px") == 0) { + lentype = CSS_LENGTH_TYPE_PX; + nextToken(); + } else if (dStrcasecmp(tval, "mm") == 0) { + lentype = CSS_LENGTH_TYPE_MM; + nextToken(); + } else if (dStrcasecmp(tval, "cm") == 0) { + lentype = CSS_LENGTH_TYPE_MM; + fval *= 10; + nextToken(); + } else if (dStrcasecmp(tval, "in") == 0) { + lentype = CSS_LENGTH_TYPE_MM; + fval *= 25.4; + nextToken(); + } else if (dStrcasecmp(tval, "pt") == 0) { + lentype = CSS_LENGTH_TYPE_MM; + fval *= (25.4 / 72); + nextToken(); + } else if (dStrcasecmp(tval, "pc") == 0) { + lentype = CSS_LENGTH_TYPE_MM; + fval *= (25.4 / 6); + nextToken(); + } else if (dStrcasecmp(tval, "em") == 0) { + lentype = CSS_LENGTH_TYPE_EM; + nextToken(); + } else if (dStrcasecmp(tval, "ex") == 0) { + lentype = CSS_LENGTH_TYPE_EX; + nextToken(); + } else { + ret = false; + } + } else if (!spaceSeparated && + (type == CSS_TYPE_LENGTH_PERCENTAGE || + type == CSS_TYPE_LENGTH_PERCENTAGE_NUMBER) && + ttype == CSS_TK_CHAR && + tval[0] == '%') { + fval /= 100; + lentype = CSS_LENGTH_TYPE_PERCENTAGE; + ret = true; + nextToken(); + } + + /* Allow numbers without unit only for 0 or + * CSS_TYPE_LENGTH_PERCENTAGE_NUMBER + */ + if (lentype == CSS_LENGTH_TYPE_NONE && + (type == CSS_TYPE_LENGTH_PERCENTAGE_NUMBER || fval == 0.0)) + ret = true; + + val->intVal = CSS_CREATE_LENGTH(fval, lentype); + } else if (ttype == CSS_TK_SYMBOL && dStrcasecmp(tval, "auto") == 0) { + ret = true; + val->intVal = CSS_LENGTH_TYPE_AUTO; + nextToken(); + } + break; + + case CSS_TYPE_COLOR: + if (ttype == CSS_TK_COLOR) { + val->intVal = a_Color_parse(tval, -1, &err); + if (err) + MSG_CSS("color is not in \"%s\" format\n", "#RRGGBB"); + else + ret = true; + nextToken(); + } else if (ttype == CSS_TK_SYMBOL) { + if (dStrcasecmp(tval, "rgb") == 0) { + nextToken(); + if (parseRgbColor(&val->intVal)) + ret = true; + else + MSG_CSS("Failed to parse %s color\n", "rgb(r,g,b)"); + } else { + val->intVal = a_Color_parse(tval, -1, &err); + if (err) + MSG_CSS("color is not in \"%s\" format\n", "#RRGGBB"); + else + ret = true; + } + nextToken(); + } + break; + + case CSS_TYPE_STRING: + if (ttype == CSS_TK_STRING) { + val->strVal = dStrdup(tval); + ret = true; + nextToken(); + } + break; + + case CSS_TYPE_SYMBOL: + /* Read comma separated list of font family names */ + dstr = dStr_new(""); + while (ttype == CSS_TK_SYMBOL || ttype == CSS_TK_STRING || + (ttype == CSS_TK_CHAR && tval[0] == ',')) { + if (spaceSeparated) + dStr_append_c(dstr, ' '); + dStr_append(dstr, tval); + ret = true; + nextToken(); + } + + if (ret) { + val->strVal = dStrstrip(dstr->str); + dStr_free(dstr, 0); + } else { + dStr_free(dstr, 1); + } + break; + + case CSS_TYPE_FONT_WEIGHT: + ival = 0; + if (ttype == CSS_TK_DECINT) { + ival = strtol(tval, NULL, 10); + if (ival < 100 || ival > 900) + /* invalid */ + ival = 0; + } + + if (ival != 0) { + val->intVal = ival; + ret = true; + nextToken(); + } + break; + + case CSS_TYPE_UNUSED: + /* nothing */ + break; + + case CSS_TYPE_INTEGER: + /* Not used for parser values. */ + default: + assert(false); /* not reached */ + } + + return ret; +} + +bool CssParser::parseWeight() +{ + if (ttype == CSS_TK_CHAR && tval[0] == '!') { + nextToken(); + if (ttype == CSS_TK_SYMBOL && + dStrcasecmp(tval, "important") == 0) { + nextToken(); + return true; + } + } + + return false; +} + +/* + * bsearch(3) compare function for searching properties + */ +static int Css_property_info_cmp(const void *a, const void *b) +{ + return dStrcasecmp(((CssPropertyInfo *) a)->symbol, + ((CssPropertyInfo *) b)->symbol); +} + + +/* + * bsearch(3) compare function for searching shorthands + */ +static int Css_shorthand_info_cmp(const void *a, const void *b) +{ + return dStrcasecmp(((CssShorthandInfo *) a)->symbol, + ((CssShorthandInfo *) b)->symbol); +} + +void CssParser::parseDeclaration(CssPropertyList * props, + CssPropertyList * importantProps) +{ + CssPropertyInfo pi = {NULL, {CSS_TYPE_UNUSED}, NULL}, *pip; + CssShorthandInfo si, *sip; + CssValueType type = CSS_TYPE_UNUSED; + + CssPropertyName prop; + CssPropertyValue val, dir_vals[4]; + CssValueType dir_types[4]; + bool found, weight; + int sh_index, i, j, n; + int dir_set[4][4] = { + /* 1 value */ {0, 0, 0, 0}, + /* 2 values */ {0, 0, 1, 1}, + /* 3 values */ {0, 2, 1, 1}, + /* 4 values */ {0, 2, 3, 1} + }; + + if (ttype == CSS_TK_SYMBOL) { + pi.symbol = tval; + pip = + (CssPropertyInfo *) bsearch(&pi, Css_property_info, + CSS_NUM_PARSED_PROPERTIES, + sizeof(CssPropertyInfo), + Css_property_info_cmp); + if (pip) { + prop = (CssPropertyName) (pip - Css_property_info); + nextToken(); + if (ttype == CSS_TK_CHAR && tval[0] == ':') { + nextToken(); + if (tokenMatchesProperty (prop, &type) && + parseValue(prop, type, &val)) { + weight = parseWeight(); + if (weight && importantProps) + importantProps->set(prop, type, val); + else + props->set(prop, type, val); + } + } + } else { + /* Try shorthands. */ + si.symbol = tval; + sip = + (CssShorthandInfo *) bsearch(&pi, Css_shorthand_info, + CSS_SHORTHAND_NUM, + sizeof(CssShorthandInfo), + Css_shorthand_info_cmp); + if (sip) { + sh_index = sip - Css_shorthand_info; + nextToken(); + if (ttype == CSS_TK_CHAR && tval[0] == ':') { + nextToken(); + + switch (Css_shorthand_info[sh_index].type) { + + case CssShorthandInfo::CSS_SHORTHAND_FONT: + /* \todo Implement details. */ + case CssShorthandInfo::CSS_SHORTHAND_MULTIPLE: + do { + for (found = false, i = 0; + !found && + Css_shorthand_info[sh_index].properties[i] != -1; + i++) + if (tokenMatchesProperty(Css_shorthand_info[sh_index]. + properties[i], &type)) { + found = true; + DEBUG_MSG(DEBUG_PARSE_LEVEL, + "will assign to '%s'\n", + Css_property_info + [Css_shorthand_info[sh_index] + .properties[i]].symbol); + if (parseValue(Css_shorthand_info[sh_index] + .properties[i], type, &val)) { + weight = parseWeight(); + if (weight && importantProps) + importantProps-> + set(Css_shorthand_info[sh_index]. + properties[i], type, val); + else + props->set(Css_shorthand_info[sh_index]. + properties[i], type, val); + } + } + } while (found); + break; + + case CssShorthandInfo::CSS_SHORTHAND_DIRECTIONS: + n = 0; + while (n < 4) { + if (tokenMatchesProperty(Css_shorthand_info[sh_index]. + properties[0], &type) && + parseValue(Css_shorthand_info[sh_index] + .properties[0], type, &val)) { + dir_vals[n] = val; + dir_types[n] = type; + n++; + } else + break; + } + + weight = parseWeight(); + if (n > 0) { + for (i = 0; i < 4; i++) + if (weight && importantProps) + importantProps->set(Css_shorthand_info[sh_index] + .properties[i], + dir_types[dir_set[n - 1][i]], + dir_vals[dir_set[n - 1][i]]); + else + props->set(Css_shorthand_info[sh_index] + .properties[i], + dir_types[dir_set[n - 1][i]], + dir_vals[dir_set[n - 1][i]]); + } else + MSG_CSS("no values for shorthand property '%s'\n", + Css_shorthand_info[sh_index].symbol); + + break; + + case CssShorthandInfo::CSS_SHORTHAND_BORDER: + do { + for (found = false, i = 0; + !found && i < 3; + i++) + if (tokenMatchesProperty(Css_shorthand_info[sh_index]. + properties[i], &type)) { + found = true; + if (parseValue(Css_shorthand_info[sh_index] + .properties[i], type, &val)) { + weight = parseWeight(); + for (j = 0; j < 4; j++) + if (weight && importantProps) + importantProps-> + set(Css_shorthand_info[sh_index]. + properties[j * 3 + i], type, val); + else + props->set(Css_shorthand_info[sh_index]. + properties[j * 3 + i], type, val); + } + } + } while (found); + break; + } + } + } + } + } + + /* Skip all tokens until the expected end. */ + while (!(ttype == CSS_TK_END || + (ttype == CSS_TK_CHAR && + (tval[0] == ';' || tval[0] == '}')))) + nextToken(); + + if (ttype == CSS_TK_CHAR && tval[0] == ';') + nextToken(); +} + +bool CssParser::parseSimpleSelector(CssSimpleSelector *selector) +{ + CssSimpleSelector::SelectType selectType; + + if (ttype == CSS_TK_SYMBOL) { + selector->setElement (a_Html_tag_index(tval)); + nextToken(); + if (spaceSeparated) + return true; + } else if (ttype == CSS_TK_CHAR && tval[0] == '*') { + selector->setElement (CssSimpleSelector::ELEMENT_ANY); + nextToken(); + if (spaceSeparated) + return true; + } else if (ttype == CSS_TK_CHAR && + (tval[0] == '#' || + tval[0] == '.' || + tval[0] == ':')) { + // nothing to be done in this case + } else { + return false; + } + + do { + selectType = CssSimpleSelector::SELECT_NONE; + if (ttype == CSS_TK_CHAR) { + switch (tval[0]) { + case '#': + selectType = CssSimpleSelector::SELECT_ID; + break; + case '.': + selectType = CssSimpleSelector::SELECT_CLASS; + break; + case ':': + selectType = CssSimpleSelector::SELECT_PSEUDO_CLASS; + if (selector->getPseudoClass ()) + // pseudo class has been set already. + // As dillo currently only supports :link and :visisted, a + // selector with more than one pseudo class will never match. + // By returning false, the whole CssRule will be dropped. + // \todo adapt this when supporting :hover, :active... + return false; + break; + } + } + + if (selectType != CssSimpleSelector::SELECT_NONE) { + nextToken(); + if (spaceSeparated) + return true; + + if (ttype == CSS_TK_SYMBOL) { + selector->setSelect (selectType, tval); + nextToken(); + } else { + return false; // don't accept classes or id's starting with integer + } + if (spaceSeparated) + return true; + } + } while (selectType != CssSimpleSelector::SELECT_NONE); + + DEBUG_MSG(DEBUG_PARSE_LEVEL, "end of simple selector (%s, %s, %s, %d)\n", + selector->id, selector->klass, + selector->pseudo, selector->element); + + return true; +} + +CssSelector *CssParser::parseSelector() +{ + CssSelector *selector = new CssSelector (); + + while (true) { + if (! parseSimpleSelector (selector->top ())) { + delete selector; + selector = NULL; + break; + } + + if (ttype == CSS_TK_CHAR && + (tval[0] == ',' || tval[0] == '{')) { + break; + } else if (ttype == CSS_TK_CHAR && tval[0] == '>') { + selector->addSimpleSelector (CssSelector::CHILD); + nextToken(); + } else if (ttype != CSS_TK_END && spaceSeparated) { + selector->addSimpleSelector (CssSelector::DESCENDANT); + } else { + delete selector; + selector = NULL; + break; + } + } + + while (ttype != CSS_TK_END && + (ttype != CSS_TK_CHAR || + (tval[0] != ',' && tval[0] != '{'))) + nextToken(); + + return selector; +} + +void CssParser::parseRuleset() +{ + lout::misc::SimpleVector < CssSelector * >*list; + CssPropertyList *props, *importantProps; + CssSelector *selector; + + list = new lout::misc::SimpleVector < CssSelector * >(1); + + while (true) { + selector = parseSelector(); + + if (selector) { + selector->ref(); + list->increase(); + list->set(list->size() - 1, selector); + } + + // \todo dump whole ruleset in case of parse error as required by CSS 2.1 + // however make sure we don't dump it if only dillo fails to parse + // valid CSS. + + if (ttype == CSS_TK_CHAR && tval[0] == ',') + /* To read the next token. */ + nextToken(); + else + /* No more selectors. */ + break; + } + + DEBUG_MSG(DEBUG_PARSE_LEVEL, "end of %s\n", "selectors"); + + props = new CssPropertyList(true); + props->ref(); + importantProps = new CssPropertyList(true); + importantProps->ref(); + + /* Read block. ('{' has already been read.) */ + if (ttype != CSS_TK_END) { + withinBlock = true; + nextToken(); + do + parseDeclaration(props, importantProps); + while (!(ttype == CSS_TK_END || + (ttype == CSS_TK_CHAR && tval[0] == '}'))); + withinBlock = false; + } + + for (int i = 0; i < list->size(); i++) { + CssSelector *s = list->get(i); + + if (origin == CSS_ORIGIN_USER_AGENT) { + context->addRule(s, props, CSS_PRIMARY_USER_AGENT); + } else if (origin == CSS_ORIGIN_USER) { + context->addRule(s, props, CSS_PRIMARY_USER); + context->addRule(s, importantProps, CSS_PRIMARY_USER_IMPORTANT); + } else if (origin == CSS_ORIGIN_AUTHOR) { + context->addRule(s, props, CSS_PRIMARY_AUTHOR); + context->addRule(s, importantProps, CSS_PRIMARY_AUTHOR_IMPORTANT); + } + + s->unref(); + } + + props->unref(); + importantProps->unref(); + + delete list; + + if (ttype == CSS_TK_CHAR && tval[0] == '}') + nextToken(); +} + +char * CssParser::parseUrl() +{ + Dstr *urlStr = NULL; + + if (ttype != CSS_TK_SYMBOL || + dStrcasecmp(tval, "url") != 0) + return NULL; + + nextToken(); + + if (ttype != CSS_TK_CHAR || tval[0] != '(') + return NULL; + + nextToken(); + + if (ttype == CSS_TK_STRING) { + urlStr = dStr_new(tval); + nextToken(); + } else { + urlStr = dStr_new(""); + while (ttype != CSS_TK_END && + (ttype != CSS_TK_CHAR || tval[0] != ')')) { + dStr_append(urlStr, tval); + nextToken(); + } + } + + if (ttype != CSS_TK_CHAR || tval[0] != ')') { + dStr_free(urlStr, 1); + urlStr = NULL; + } + + if (urlStr) { + char *url = urlStr->str; + dStr_free(urlStr, 0); + return url; + } else { + return NULL; + } +} + +void CssParser::parseImport(DilloHtml *html, DilloUrl *baseUrl) +{ + char *urlStr = NULL; + + if (html != NULL && + ttype == CSS_TK_SYMBOL && + dStrcasecmp(tval, "import") == 0) { + nextToken(); + + if (ttype == CSS_TK_SYMBOL && + dStrcasecmp(tval, "url") == 0) + urlStr = parseUrl(); + else if (ttype == CSS_TK_STRING) + urlStr = dStrdup (tval); + + /* Skip all tokens until the expected end. */ + while (!(ttype == CSS_TK_END || + (ttype == CSS_TK_CHAR && (tval[0] == ';')))) + nextToken(); + + nextToken(); + + if (urlStr) { + MSG("CssParser::parseImport(): @import %s\n", urlStr); + DilloUrl *url = a_Html_url_new (html, urlStr, a_Url_str(baseUrl), + baseUrl ? 1 : 0); + a_Html_load_stylesheet(html, url); + a_Url_free(url); + dFree (urlStr); + } + } +} + +const char * CssParser::propertyNameString(CssPropertyName name) +{ + return Css_property_info[name].symbol; +} + +void CssParser::parse(DilloHtml *html, DilloUrl *url, CssContext * context, + const char *buf, + int buflen, CssOrigin origin) +{ + CssParser parser (context, origin, buf, buflen); + + while (parser.ttype == CSS_TK_CHAR && parser.tval[0] == '@') { + parser.nextToken(); + parser.parseImport(html, url); + } + + while (parser.ttype != CSS_TK_END) + parser.parseRuleset(); +} + +CssPropertyList *CssParser::parseDeclarationBlock(const char *buf, int buflen) +{ + CssPropertyList *props = new CssPropertyList (true); + CssParser parser (NULL, CSS_ORIGIN_AUTHOR, buf, buflen); + + parser.withinBlock = true; + + do + parser.parseDeclaration(props, NULL); + while (!(parser.ttype == CSS_TK_END || + (parser.ttype == CSS_TK_CHAR && parser.tval[0] == '}'))); + + if (props->size () == 0) { + delete props; + props = NULL; + } + + return props; +} diff --git a/src/cssparser.hh b/src/cssparser.hh new file mode 100644 index 00000000..1e471c68 --- /dev/null +++ b/src/cssparser.hh @@ -0,0 +1,54 @@ +#ifndef __CSSPARSER_HH__ +#define __CSSPARSER_HH__ + +#include "css.hh" +#include "html_common.hh" + +class CssParser { + private: + typedef enum { + CSS_TK_DECINT, CSS_TK_FLOAT, CSS_TK_COLOR, CSS_TK_SYMBOL, + CSS_TK_STRING, CSS_TK_CHAR, CSS_TK_END + } CssTokenType; + + static const int maxStrLen = 256; + CssContext *context; + CssOrigin origin; + + const char *buf; + int buflen, bufptr; + + CssTokenType ttype; + char tval[maxStrLen]; + bool withinBlock; + bool spaceSeparated; /* used when parsing CSS selectors */ + + CssParser(CssContext *context, CssOrigin origin, + const char *buf, int buflen); + int getChar(); + void ungetChar(); + void nextToken(); + bool skipString(int c, const char *string); + bool tokenMatchesProperty(CssPropertyName prop, CssValueType * type); + bool parseValue(CssPropertyName prop, CssValueType type, + CssPropertyValue * val); + bool parseWeight(); + bool parseRgbColorComponent(int32_t *cc, int *percentage); + bool parseRgbColor(int32_t *c); + void parseDeclaration(CssPropertyList * props, + CssPropertyList * importantProps); + bool parseSimpleSelector(CssSimpleSelector *selector); + char *parseUrl(); + void parseImport(DilloHtml *html, DilloUrl *url); + CssSelector *parseSelector(); + void parseRuleset(); + + public: + static CssPropertyList *parseDeclarationBlock(const char *buf, + int buflen); + static void parse(DilloHtml *html, DilloUrl *url, CssContext *context, + const char *buf, int buflen, CssOrigin origin); + static const char *propertyNameString(CssPropertyName name); +}; + +#endif diff --git a/src/decode.c b/src/decode.c index ff4fcb27..24067318 100644 --- a/src/decode.c +++ b/src/decode.c @@ -15,6 +15,7 @@ #include <stdlib.h> /* strtol */ #include "decode.h" +#include "utf8.hh" #include "msg.h" static const int bufsize = 8*1024; @@ -106,13 +107,12 @@ static Dstr *Decode_gzip(Decode *dc, const char *instr, int inlen) rc = inflate(zs, Z_SYNC_FLUSH); + dStr_append_l(output, dc->buffer, zs->total_out); + if ((rc == Z_OK) || (rc == Z_STREAM_END)) { // Z_STREAM_END at end of file inputConsumed += zs->total_in; - - dStr_append_l(output, dc->buffer, zs->total_out); - zs->total_out = 0; zs->total_in = 0; } else if (rc == Z_DATA_ERROR) { @@ -126,6 +126,7 @@ static void Decode_gzip_free(Decode *dc) { (void)inflateEnd((z_stream *)dc->state); + dFree(dc->state); dFree(dc->buffer); } @@ -164,15 +165,8 @@ static Dstr *Decode_charset(Decode *dc, const char *instr, int inlen) if (rc == EILSEQ){ inPtr++; inLeft--; - /* - * U+FFFD: "used to replace an incoming character whose value is - * unknown or unrepresentable in Unicode." - */ - //dStr_append(output, "\ufffd"); - // \uxxxx is C99. UTF-8-specific: - dStr_append_c(output, 0xEF); - dStr_append_c(output, 0xBF); - dStr_append_c(output, 0xBD); + dStr_append_l(output, utf8_replacement_char, + sizeof(utf8_replacement_char) - 1); } } dStr_erase(dc->leftover, 0, dc->leftover->len - inLeft); @@ -182,6 +176,7 @@ static Dstr *Decode_charset(Decode *dc, const char *instr, int inlen) static void Decode_charset_free(Decode *dc) { + /* iconv_close() frees dc->state */ (void)iconv_close((iconv_t)(dc->state)); dFree(dc->buffer); @@ -195,7 +190,7 @@ Decode *a_Decode_transfer_init(const char *format) { Decode *dc = NULL; - if (format && !dStrncasecmp(format, "chunked", 7)) { + if (format && !dStrcasecmp(format, "chunked")) { int *chunk_remaining = dNew(int, 1); *chunk_remaining = 0; dc = dNew(Decode, 1); @@ -221,9 +216,9 @@ Decode *a_Decode_content_init(const char *format) if (format && *format) { if (!dStrcasecmp(format, "gzip") || !dStrcasecmp(format, "x-gzip")) { + z_stream *zs; _MSG("gzipped data!\n"); - z_stream *zs; dc = dNew(Decode, 1); dc->buffer = dNew(char, bufsize); dc->state = zs = dNew(z_stream, 1); @@ -242,7 +237,7 @@ Decode *a_Decode_content_init(const char *format) MSG("Content-Encoding '%s' not recognized.\n", format); } } - return dc; + return dc; } /* @@ -289,10 +284,10 @@ Decode *a_Decode_charset_init(const char *format) dc->decode = Decode_charset; dc->free = Decode_charset_free; } else { - MSG("Unable to convert from character encoding: '%s'\n", format); + MSG_WARN("Unable to convert from character encoding: '%s'\n", format); } } - return dc; + return dc; } /* diff --git a/src/dgif.h b/src/dgif.h new file mode 100644 index 00000000..ec36812d --- /dev/null +++ b/src/dgif.h @@ -0,0 +1,19 @@ +#ifndef __GIF_H__ +#define __GIF_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include "url.h" +#include "image.hh" + + +void *a_Gif_new(DilloImage *Image, DilloUrl *url, int version); +void a_Gif_callback(int Op, void *data); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* !__GIF_H__ */ diff --git a/src/dialog.cc b/src/dialog.cc index 69781f32..47af9921 100644 --- a/src/dialog.cc +++ b/src/dialog.cc @@ -11,7 +11,7 @@ // UI dialogs -#include <math.h> // for rint() +#include <math.h> // for rint() #include <fltk/Window.h> #include <fltk/ask.h> @@ -20,6 +20,9 @@ #include <fltk/ReturnButton.h> #include <fltk/TextDisplay.h> #include <fltk/HighlightButton.h> +#include <fltk/WordwrapOutput.h> +#include <fltk/Input.h> +#include <fltk/SecretInput.h> #include "msg.h" #include "dialog.hh" @@ -118,19 +121,19 @@ char *a_Dialog_open_file(const char *msg, } /* - * Make a new window with the provided text + * Show a new window with the provided text */ -void *a_Dialog_make_text_window(const char *txt, const char *title) +void a_Dialog_text_window(const char *txt, const char *title) { //int wh = 600, ww = 650, bh = 30; int wh = prefs.height, ww = prefs.width, bh = 30; int lines, line_num_width; - Font *textfont = font(prefs.fw_fontname, 0); - + Font *textfont = font(prefs.font_monospace, 0); + Window *window = new Window(ww, wh, title ? title : "Untitled"); window->callback(window_close_cb, window); window->begin(); - + TextDisplay *td = new TextDisplay(0,0,ww, wh-bh); td->buffer()->text(txt); @@ -156,15 +159,7 @@ void *a_Dialog_make_text_window(const char *txt, const char *title) window->resizable(td); window->end(); - return window; -} - -/* - * Show a window. - */ -void a_Dialog_show_text_window(void *vWindow) -{ - ((Window *)vWindow)->show(); + window->show(); } /*--------------------------------------------------------------------------*/ @@ -193,7 +188,7 @@ int a_Dialog_choice5(const char *QuestionTxt, txt[0] = txt[6] = NULL; txt[1] = alt1; txt[2] = alt2; txt[3] = alt3; txt[4] = alt4; txt[5] = alt5; - for (int i=1; txt[i]; ++i, ++nb); + for (int i=1; txt[i]; ++i, ++nb) ; Window *window = new Window(ww,wh,"Choice5"); window->begin(); @@ -214,7 +209,7 @@ int a_Dialog_choice5(const char *QuestionTxt, b = new HighlightButton(xpos, wh-bh, bw, bh, txt[i]); b->align(ALIGN_WRAP|ALIGN_CLIP); b->box(UP_BOX); - b->callback(choice5_cb, (void*)i); + b->callback(choice5_cb, INT2VOIDP(i)); xpos += bw + gap; } window->end(); @@ -227,3 +222,81 @@ int a_Dialog_choice5(const char *QuestionTxt, return choice5_answer; } + +/*--------------------------------------------------------------------------*/ +/* + * ret: 0 = Cancel, 1 = OK + */ +static void Dialog_user_password_cb(Widget *button, void *vIntPtr) +{ + int ret = VOIDP2INT(vIntPtr); + _MSG("Dialog_user_password_cb: %d\n", ret); + button->window()->make_exec_return(ret); +} + +/* + * Make a user/password dialog. + * Call the callback with the result (OK or not) and the given user and + * password if OK. + */ +int a_Dialog_user_password(const char *message, UserPasswordCB cb, void *vp) +{ + int ok, + window_w = 300, window_h = 280, + input_x = 80, input_w = 200, input_h = 30, + button_y = 230, button_h = 30; + + Window *window = + new Window(window_w,window_h,"User/Password"); + window->begin(); + + /* message */ + WordwrapOutput *message_output = + new WordwrapOutput(20,20,window_w-40,100); + message_output->box(DOWN_BOX); + message_output->text(message); + message_output->textfont(HELVETICA_BOLD_ITALIC); + message_output->textsize(14); + + /* inputs */ + Input *user_input = + new Input(input_x,140,input_w,input_h,"User"); + user_input->labelsize(14); + user_input->textsize(14); + SecretInput *password_input = + new SecretInput(input_x,180,input_w,input_h,"Password"); + password_input->labelsize(14); + password_input->textsize(14); + + /* "OK" button */ + Button *ok_button = + new Button(200,button_y,50,button_h,"OK"); + ok_button->labelsize(14); + ok_button->callback(Dialog_user_password_cb); + ok_button->user_data(INT2VOIDP(1)); + + /* "Cancel" button */ + Button *cancel_button = + new Button(50,button_y,100,button_h,"Cancel"); + cancel_button->labelsize(14); + cancel_button->callback(Dialog_user_password_cb); + cancel_button->user_data(INT2VOIDP(0)); + + window->end(); + window->size_range(window_w,window_h,window_w,window_h); + window->resizable(window); + + if ((ok = window->exec())) { + /* call the callback */ + const char *user, *password; + user = user_input->value(); + password = password_input->value(); + _MSG("a_Dialog_user_passwd: ok = %d\n", ok); + (*cb)(user, password, vp); + } + + delete window; + + return ok; +} + diff --git a/src/dialog.hh b/src/dialog.hh index 17c326cb..440e9bba 100644 --- a/src/dialog.hh +++ b/src/dialog.hh @@ -5,12 +5,16 @@ extern "C" { #endif /* __cplusplus */ +typedef void (*UserPasswordCB)(const char *user, const char *password, + void *vp); + void a_Dialog_msg(const char *msg); int a_Dialog_choice3(const char *msg, const char *b0, const char *b1, const char *b2); int a_Dialog_choice5(const char *QuestionTxt, const char *alt1, const char *alt2, const char *alt3, const char *alt4, const char *alt5); +int a_Dialog_user_password(const char *message, UserPasswordCB cb, void *vp); const char *a_Dialog_input(const char *msg); const char *a_Dialog_passwd(const char *msg); const char *a_Dialog_save_file(const char *msg, @@ -19,8 +23,7 @@ const char *a_Dialog_select_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_make_text_window(const char *txt, const char *title); -void a_Dialog_show_text_window(void *vWindow); +void a_Dialog_text_window(const char *txt, const char *title); #ifdef __cplusplus } diff --git a/src/dicache.c b/src/dicache.c index 97ed1915..e700f000 100644 --- a/src/dicache.c +++ b/src/dicache.c @@ -9,18 +9,26 @@ * (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 "msg.h" #include "image.hh" +#include "imgbuf.hh" #include "web.hh" #include "dicache.h" -#include "cache.h" +#include "dpng.h" +#include "dgif.h" +#include "djpeg.h" typedef struct _DICacheNode DICacheNode; +enum { + DIC_Gif, + DIC_Png, + DIC_Jpeg +}; + struct _DICacheNode { int valid; /* flag */ DilloUrl *url; /* primary "Key" for this dicache entry */ @@ -33,9 +41,9 @@ struct _DICacheNode { */ 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. */ +static uint_t 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 @@ -78,15 +86,18 @@ static DICacheEntry *Dicache_entry_new(void) 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->ScanNumber = 0; entry->BitVec = NULL; entry->State = DIC_Empty; - entry->version = 0; + entry->version = 1; + + entry->Decoder = NULL; + entry->DecoderData = NULL; + entry->DecodedSize = 0; + entry->next = NULL; return entry; @@ -96,7 +107,7 @@ static DICacheEntry *Dicache_entry_new(void) * Add a new entry in the dicache * (a single node (URL) may have several entries) */ -DICacheEntry *a_Dicache_add_entry(const DilloUrl *Url) +static DICacheEntry *Dicache_add_entry(const DilloUrl *Url) { DICacheEntry *entry; DICacheNode *node; @@ -127,41 +138,31 @@ DICacheEntry *a_Dicache_add_entry(const DilloUrl *Url) } /* - * 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. + * + * Notes: DIC_Last means last version of the image. + * version zero is not allowed. */ -static DICacheEntry *Dicache_get_entry_version(const DilloUrl *Url, - int version) +DICacheEntry *a_Dicache_get_entry(const DilloUrl *Url, int version) { DICacheNode *node; - DICacheEntry *entry; + DICacheEntry *entry = NULL; - node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp); - entry = (node) ? node->first : NULL; - - while (entry && entry->version != version) - entry = entry->next; + dReturn_val_if_fail(version != 0, NULL); + node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp); + if (node) { + if (version == DIC_Last) { + if (node->valid) { + entry = node->first; + for ( ; (entry && entry->next); entry = entry->next); + } + } else { + entry = node->first; + for ( ; entry && entry->version != version; entry = entry->next) ; + } + } return entry; } @@ -172,7 +173,7 @@ static void Dicache_remove(const DilloUrl *Url, int version) { DICacheNode *node; DICacheEntry *entry, *prev; - + _MSG("Dicache_remove url=%s\n", URL_STR(Url)); node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp); prev = entry = (node) ? node->first : NULL; @@ -182,11 +183,15 @@ static void Dicache_remove(const DilloUrl *Url, int version) } if (entry) { + _MSG("Dicache_remove Decoder=%p DecoderData=%p\n", + entry->Decoder, entry->DecoderData); /* Eliminate this dicache entry */ dFree(entry->cmap); - dFree(entry->linebuf); a_Bitvec_free(entry->BitVec); - a_Image_imgbuf_unref(entry->v_imgbuf); + a_Imgbuf_unref(entry->v_imgbuf); + if (entry->Decoder) { + entry->Decoder(CA_Abort, entry->DecoderData); + } dicache_size_total -= entry->TotalSize; if (node->first == entry) { @@ -206,14 +211,14 @@ static void Dicache_remove(const DilloUrl *Url, int version) /* * 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. + * this buffer, then we call Dicache_remove() to do the 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)) {*/ + _MSG("a_Dicache_unref\n"); + if ((entry = a_Dicache_get_entry(Url, version))) { if (--entry->RefCount == 0) { Dicache_remove(Url, version); } @@ -223,12 +228,11 @@ void a_Dicache_unref(const DilloUrl *Url, int 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))) { + if ((entry = a_Dicache_get_entry(Url, version))) { ++entry->RefCount; } return entry; @@ -236,7 +240,8 @@ DICacheEntry* a_Dicache_ref(const DilloUrl *Url, int version) /* * Invalidate this entry. This is used for the reloading mechanism. - * Can't erase current versions, but a_Dicache_get_entry must return NULL. + * Can't erase current versions, but a_Dicache_get_entry(url, DIC_Last) + * must return NULL. */ void a_Dicache_invalidate_entry(const DilloUrl *Url) { @@ -251,57 +256,6 @@ void a_Dicache_invalidate_entry(const DilloUrl *Url) /* ------------------------------------------------------------------------- */ /* - * 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' remains 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) { - if (DicEntry->ScanNumber == Image->ScanNumber) { - 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 { - for (i = 0; i < DicEntry->height; ++i) { - if (a_Bitvec_get_bit(DicEntry->BitVec, (int)i) || - !a_Bitvec_get_bit(Image->BitVec, (int)i) || - DicEntry->ScanNumber > Image->ScanNumber + 1) { - a_Image_write(Image, DicEntry->v_imgbuf, - DicEntry->linebuf, i, FALSE); - } - if (!a_Bitvec_get_bit(DicEntry->BitVec, (int)i)) - a_Bitvec_clear_bit(Image->BitVec, (int)i); - } - Image->ScanNumber = DicEntry->ScanNumber; - } - } - } 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) */ @@ -309,37 +263,29 @@ 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; + _MSG("a_Dicache_set_parms (%s)\n", URL_STR(url)); dReturn_if_fail ( Image != NULL && width && height ); /* Find the DicEntry for this Image */ - DicEntry = Dicache_get_entry_version(url, version); + DicEntry = a_Dicache_get_entry(url, version); dReturn_if_fail ( DicEntry != NULL ); + /* Parameters already set? */ + dReturn_if_fail ( DicEntry->State < DIC_SetParms ); - /* Initialize the DicEntry */ - DicEntry->linebuf = dNew(uchar_t, width * 3); - dReturn_if_fail ( DicEntry->linebuf != NULL ); + _MSG(" RefCount=%d version=%d\n", DicEntry->RefCount, DicEntry->version); /* 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); + DicEntry->v_imgbuf = a_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->TotalSize = width * height * 3; 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); + dicache_size_total += DicEntry->TotalSize; } /* @@ -349,8 +295,9 @@ 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); + DICacheEntry *DicEntry = a_Dicache_get_entry(url, version); + _MSG("a_Dicache_set_cmap\n"); dReturn_if_fail ( DicEntry != NULL ); dFree(DicEntry->cmap); @@ -362,24 +309,27 @@ void a_Dicache_set_cmap(DilloUrl *url, int version, DilloImage *Image, DicEntry->cmap[bg_index * 3 + 2] = (Image->bg_color) & 0xff; } - a_Image_set_cmap(Image, DicEntry->cmap); DicEntry->State = DIC_SetCmap; } /* * Reset for a new scan from a multiple-scan image. */ -void a_Dicache_new_scan(DilloImage *image, const DilloUrl *url, int version) +void a_Dicache_new_scan(const DilloUrl *url, int version) { DICacheEntry *DicEntry; + _MSG("a_Dicache_new_scan\n"); dReturn_if_fail ( url != NULL ); - DicEntry = Dicache_get_entry_version(url, version); + DicEntry = a_Dicache_get_entry(url, version); dReturn_if_fail ( DicEntry != NULL ); - + if (DicEntry->State < DIC_SetParms) { + MSG("a_Dicache_new_scan before DIC_SetParms\n"); + exit(1); + } a_Bitvec_clear(DicEntry->BitVec); DicEntry->ScanNumber++; - a_Image_new_scan(image, DicEntry->v_imgbuf); + a_Imgbuf_new_scan(DicEntry->v_imgbuf); } /* @@ -388,18 +338,19 @@ void a_Dicache_new_scan(DilloImage *image, const DilloUrl *url, int version) * buf: row buffer * Y : row number */ -void a_Dicache_write(DilloImage *Image, DilloUrl *url, int version, - const uchar_t *buf, uint_t Y) +void a_Dicache_write(DilloUrl *url, int version, const uchar_t *buf, uint_t Y) { DICacheEntry *DicEntry; - dReturn_if_fail ( Image != NULL ); - DicEntry = Dicache_get_entry_version(url, version); + _MSG("a_Dicache_write\n"); + DicEntry = a_Dicache_get_entry(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; + /* update the common buffer in the imgbuf */ + a_Imgbuf_update(DicEntry->v_imgbuf, buf, DicEntry->type, + DicEntry->cmap, DicEntry->width, DicEntry->height, Y); + a_Bitvec_set_bit(DicEntry->BitVec, (int)Y); DicEntry->State = DIC_Write; } @@ -410,19 +361,169 @@ void a_Dicache_write(DilloImage *Image, DilloUrl *url, int version, void a_Dicache_close(DilloUrl *url, int version, CacheClient_t *Client) { DilloWeb *Web = Client->Web; - DICacheEntry *DicEntry = Dicache_get_entry_version(url, version); + DICacheEntry *DicEntry = a_Dicache_get_entry(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_Dicache_unref() may free DicEntry */ + _MSG("a_Dicache_close RefCount=%d\n", DicEntry->RefCount - 1); + + if (DicEntry->State < DIC_Close) { + DicEntry->State = DIC_Close; + dFree(DicEntry->cmap); + DicEntry->cmap = NULL; + DicEntry->Decoder = NULL; + DicEntry->DecoderData = NULL; + } + a_Dicache_unref(url, version); + a_Bw_close_client(Web->bw, Client->Key); } +/* ------------------------------------------------------------------------- */ + +/* + * Generic MIME handler for GIF, JPEG and PNG. + * Sets a_Dicache_callback as the cache-client, + * and also sets the image decoder. + * + * Parameters: + * Type: MIME type + * Ptr: points to a Web structure + * Call: Dillo calls this with more data/eod + * Data: Decoding data structure + */ +static void *Dicache_image(int ImgType, const char *MimeType, void *Ptr, + CA_Callback_t *Call, void **Data) +{ + DilloWeb *web = Ptr; + DICacheEntry *DicEntry; + + dReturn_val_if_fail(MimeType && Ptr, NULL); + + if (!web->Image) { + web->Image = a_Image_new(NULL, web->bgColor); + a_Image_ref(web->Image); + } + + DicEntry = a_Dicache_get_entry(web->url, DIC_Last); + if (!DicEntry) { + /* Let's create an entry for this image... */ + DicEntry = Dicache_add_entry(web->url); + DicEntry->DecoderData = + (ImgType == DIC_Png) ? + a_Png_new(web->Image, DicEntry->url, DicEntry->version) : + (ImgType == DIC_Gif) ? + a_Gif_new(web->Image, DicEntry->url, DicEntry->version) : + (ImgType == DIC_Jpeg) ? + a_Jpeg_new(web->Image, DicEntry->url, DicEntry->version) : + NULL; + } else { + /* Repeated image */ + a_Dicache_ref(DicEntry->url, DicEntry->version); + } + DicEntry->Decoder = (ImgType == DIC_Png) ? (CA_Callback_t)a_Png_callback : + (ImgType == DIC_Gif) ? (CA_Callback_t)a_Gif_callback : + (ImgType == DIC_Jpeg) ? (CA_Callback_t)a_Jpeg_callback: + NULL; + *Data = DicEntry->DecoderData; + *Call = (CA_Callback_t) a_Dicache_callback; + + return (web->Image->dw); +} + +/* + * PNG wrapper for Dicache_image() + */ +void *a_Dicache_png_image(const char *Type, void *Ptr, CA_Callback_t *Call, + void **Data) +{ + return Dicache_image(DIC_Png, Type, Ptr, Call, Data); +} + +/* + * GIF wrapper for Dicache_image() + */ +void *a_Dicache_gif_image(const char *Type, void *Ptr, CA_Callback_t *Call, + void **Data) +{ + return Dicache_image(DIC_Gif, Type, Ptr, Call, Data); +} + +/* + * JPEG wrapper for Dicache_image() + */ +void *a_Dicache_jpeg_image(const char *Type, void *Ptr, CA_Callback_t *Call, + void **Data) +{ + return Dicache_image(DIC_Jpeg, Type, Ptr, Call, Data); +} + +/* + * This function is a cache client; (but feeds its clients from dicache) + */ +void a_Dicache_callback(int Op, CacheClient_t *Client) +{ + uint_t i; + DilloWeb *Web = Client->Web; + DilloImage *Image = Web->Image; + DICacheEntry *DicEntry = a_Dicache_get_entry(Web->url, DIC_Last); + + dReturn_if_fail ( DicEntry != NULL ); + + /* Copy the version number in the Client */ + if (Client->Version == 0) + Client->Version = DicEntry->version; + + /* Only call the decoder when necessary */ + if (Op == CA_Send && DicEntry->State < DIC_Close && + DicEntry->DecodedSize < Client->BufSize) { + DicEntry->Decoder(Op, Client); + DicEntry->DecodedSize = Client->BufSize; + } else if (Op == CA_Close || Op == CA_Abort) { + if (DicEntry->State < DIC_Close) { + DicEntry->Decoder(Op, Client); + } else { + a_Dicache_close(DicEntry->url, DicEntry->version, Client); + } + } + + /* when the data stream is not an image 'v_imgbuf' remains 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) { + if (DicEntry->ScanNumber == Image->ScanNumber) { + 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, i); + } else { + for (i = 0; i < DicEntry->height; ++i) { + if (a_Bitvec_get_bit(DicEntry->BitVec, (int)i) || + !a_Bitvec_get_bit(Image->BitVec, (int)i) || + DicEntry->ScanNumber > Image->ScanNumber + 1) { + a_Image_write(Image, i); + } + if (!a_Bitvec_get_bit(DicEntry->BitVec, (int)i)) + a_Bitvec_clear_bit(Image->BitVec, (int)i); + } + Image->ScanNumber = DicEntry->ScanNumber; + } + } + } else if (Op == CA_Close || Op == CA_Abort) { + a_Image_close(Image); + a_Bw_close_client(Web->bw, Client->Key); + } +} + +/* ------------------------------------------------------------------------- */ + /* * Free the imgbuf (RGB data) of unused entries. */ @@ -432,12 +533,13 @@ void a_Dicache_cleanup(void) DICacheNode *node; DICacheEntry *entry; + _MSG("a_Dicache_cleanup\n"); 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)) { + a_Imgbuf_last_reference(entry->v_imgbuf)) { /* free this unused entry */ if (entry->next) { Dicache_remove(node->url, entry->version); @@ -467,12 +569,11 @@ void a_Dicache_freeall(void) 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); + a_Imgbuf_unref(entry->v_imgbuf); dicache_size_total -= entry->TotalSize; } - dList_remove(CachedIMGs, node); + dList_remove_fast(CachedIMGs, node); a_Url_free(node->url); dFree(node); } diff --git a/src/dicache.h b/src/dicache.h index 6cbcf3a5..70adb6c0 100644 --- a/src/dicache.h +++ b/src/dicache.h @@ -10,6 +10,10 @@ extern "C" { #include "image.hh" #include "cache.h" +/* Symbolic name to request the last version of an image */ +#define DIC_Last -1 + + /* These will reflect the entry's "state" */ typedef enum { DIC_Empty, /* Just created the entry */ @@ -27,10 +31,8 @@ struct _DICacheEntry { 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 */ + uint_t TotalSize; /* Amount of memory the image takes up */ uint_t ScanNumber; /* Current decoding scan */ bitvec_t *BitVec; /* Bit vector for decoded rows */ DicEntryState State; /* Current status for this entry */ @@ -38,15 +40,24 @@ struct _DICacheEntry { int version; /* Version number, used for different versions of the same URL image */ + CA_Callback_t Decoder; /* Client function */ + void *DecoderData; /* Client function data */ + uint_t DecodedSize; /* Size of already decoded data */ + 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); +DICacheEntry *a_Dicache_get_entry(const DilloUrl *Url, int version); +void *a_Dicache_png_image(const char *Type, void *Ptr, CA_Callback_t *Call, + void **Data); +void *a_Dicache_gif_image(const char *Type, void *Ptr, CA_Callback_t *Call, + void **Data); +void *a_Dicache_jpeg_image(const char *Type, void *Ptr, CA_Callback_t *Call, + void **Data); void a_Dicache_callback(int Op, CacheClient_t *Client); void a_Dicache_set_parms(DilloUrl *url, int version, DilloImage *Image, @@ -54,9 +65,8 @@ void a_Dicache_set_parms(DilloUrl *url, int version, DilloImage *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); -void a_Dicache_new_scan(DilloImage *image, const DilloUrl *url, int version); -void a_Dicache_write(DilloImage *Image, DilloUrl *url, int version, - const uchar_t *buf, uint_t Y); +void a_Dicache_new_scan(const DilloUrl *url, int version); +void a_Dicache_write(DilloUrl *url, int version, const uchar_t *buf, uint_t Y); void a_Dicache_close(DilloUrl *url, int version, CacheClient_t *Client); void a_Dicache_invalidate_entry(const DilloUrl *Url); diff --git a/src/dillo.cc b/src/dillo.cc index 7e29ac32..a42b0dab 100644 --- a/src/dillo.cc +++ b/src/dillo.cc @@ -14,8 +14,7 @@ * 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. + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> @@ -26,16 +25,19 @@ #include <fltk/Window.h> #include <fltk/TabGroup.h> +#include <fltk/Font.h> #include <fltk/run.h> #include "msg.h" -#include "dir.h" +#include "paths.hh" #include "uicmd.hh" #include "prefs.h" +#include "prefsparser.hh" +#include "keys.hh" #include "bw.h" #include "misc.h" -#include "nav.h" +#include "history.h" #include "dns.h" #include "web.hh" @@ -44,37 +46,174 @@ #include "capi.h" #include "dicache.h" #include "cookies.h" +#include "auth.h" +/* + * Command line options structure + */ +typedef enum { + DILLO_CLI_NONE = 0, + DILLO_CLI_XID = 1 << 0, + DILLO_CLI_FULLWINDOW = 1 << 1, + DILLO_CLI_HELP = 1 << 2, + DILLO_CLI_VERSION = 1 << 3, + DILLO_CLI_LOCAL = 1 << 4, + DILLO_CLI_GEOMETRY = 1 << 5, + DILLO_CLI_ERROR = 1 << 15, +} OptID; + +typedef struct { + const char *shortopt; + const char *longopt; + int opt_argc; /* positive: mandatory, negative: optional */ + OptID id; + const char *help; +} CLI_options; + +static const CLI_options Options[] = { + {"-f", "--fullwindow", 0, DILLO_CLI_FULLWINDOW, + " -f, --fullwindow Start in full window mode: hide address bar,\n" + " navigation buttons, menu, and status bar."}, + {"-g", "-geometry", 1, DILLO_CLI_GEOMETRY, + " -g, -geometry GEO Set initial window position where GEO is\n" + " WxH[{+-}X{+-}Y]"}, + {"-h", "--help", 0, DILLO_CLI_HELP, + " -h, --help Display this help text and exit."}, + {"-l", "--local", 0, DILLO_CLI_LOCAL, + " -l, --local Don't load images for these URL(s)."}, + {"-v", "--version", 0, DILLO_CLI_VERSION, + " -v, --version Display version info and exit."}, + {"-x", "--xid", 1, DILLO_CLI_XID, + " -x, --xid XID Open first Dillo window in an existing\n" + " window whose window ID is XID."}, + {NULL, NULL, 0, DILLO_CLI_NONE, NULL} +}; + +/* + * Print help text generated from the options structure + */ +static void printHelp(const char *cmdname, const CLI_options *options) +{ + printf("Usage: %s [OPTION]... [--] [URL|FILE]...\n" + "Options:\n", cmdname); + while (options && options->help) { + printf("%s\n", options->help); + options++; + } + printf(" URL URL to browse.\n" + " FILE Local FILE to view.\n" + "\n"); +} + +/* + * Return the maximum number of option arguments + */ +static int numOptions(const CLI_options *options) +{ + int i, max; + + for (i = 0, max = 0; options[i].shortopt; i++) + if (abs(options[i].opt_argc) > max) + max = abs(options[i].opt_argc); + return max; +} + +/* + * Get next command line option. + */ +static OptID getCmdOption(const CLI_options *options, int argc, char **argv, + char **opt_argv, int *idx) +{ + typedef enum { O_SEARCH, O_FOUND, O_NOTFOUND, O_DONE } State; + OptID opt_id = DILLO_CLI_NONE; + int i = 0; + State state = O_SEARCH; + + if (*idx >= argc) { + state = O_DONE; + } else { + state = O_NOTFOUND; + for (i = 0; options[i].shortopt; i++) { + if (strcmp(options[i].shortopt, argv[*idx]) == 0 || + strcmp(options[i].longopt, argv[*idx]) == 0) { + state = O_FOUND; + ++*idx; + break; + } + } + } + if (state == O_FOUND) { + int n_arg = options[i].opt_argc; + opt_id = options[i].id; + /* Find the required/optional arguments of the option */ + for (i = 0; *idx < argc && i < abs(n_arg) && argv[*idx][0] != '-'; i++) + opt_argv[i] = argv[(*idx)++]; + opt_argv[i] = NULL; + + /* Optional arguments have opt_argc < 0 */ + if (i < n_arg) { + fprintf(stderr, "Option %s requires %d argument%s\n", + argv[*idx-i-1], n_arg, (n_arg == 1) ? "" : "s"); + opt_id = DILLO_CLI_ERROR; + } + } + if (state == O_NOTFOUND) { + if (strcmp(argv[*idx], "--") == 0) + (*idx)++; + else if (argv[*idx][0] == '-') { + fprintf(stderr, "Command line option \"%s\" not recognized.\n", + argv[*idx]); + opt_id = DILLO_CLI_ERROR; + } + } + return opt_id; +} + +/* + * Tell the user if default/pref fonts can't be found. + */ +static void checkFont(const char *name, const char *type) +{ + if (::fltk::font(name) == NULL) + MSG_WARN("preferred %s font \"%s\" not found.\n", type, name); +} + +static void checkPreferredFonts() +{ + checkFont(prefs.font_sans_serif, "sans-serif"); + checkFont(prefs.font_serif, "serif"); + checkFont(prefs.font_monospace, "monospace"); + checkFont(prefs.font_cursive, "cursive"); + checkFont(prefs.font_fantasy, "fantasy"); +} /* * Given a command line argument, build a DilloUrl for it. */ -static DilloUrl *Dillo_make_start_url(char *str) +static DilloUrl *makeStartUrl(char *str, bool local) { 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); + p = (*str == '/') ? dStrdup(str) : + dStrconcat(Paths::getOldWorkingDir(), "/", 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; + start_url = a_Url_new(url_str + 1, "file:/"); } 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:/"); - } else { start_url = a_Url_new(url_str, NULL); } + dFree(p); dFree(url_str); + if (local) + a_Url_set_flags(start_url, URL_FLAGS(start_url) | URL_SpamSafe); + return start_url; } @@ -83,15 +222,79 @@ static DilloUrl *Dillo_make_start_url(char *str) */ int main(int argc, char **argv) { + uint_t opt_id; + uint_t options_got = 0; + uint32_t xid = 0; + int idx = 1; + int xpos = PREFS_GEOMETRY_DEFAULT_XPOS, ypos = PREFS_GEOMETRY_DEFAULT_YPOS, + width = PREFS_GEOMETRY_DEFAULT_WIDTH, + height = PREFS_GEOMETRY_DEFAULT_HEIGHT; + char **opt_argv; + FILE *fp; + srand((uint_t)(time(0) ^ getpid())); // Some OSes exit dillo without this (not GNU/Linux). signal(SIGPIPE, SIG_IGN); - // Initialize internal modules - a_Dir_init(); + /* Handle command line options */ + opt_argv = dNew0(char*, numOptions(Options) + 1); + while ((opt_id = getCmdOption(Options, argc, argv, opt_argv, &idx))) { + options_got |= opt_id; + switch (opt_id) { + case DILLO_CLI_FULLWINDOW: + case DILLO_CLI_LOCAL: + break; + case DILLO_CLI_XID: + { + char *end; + xid = strtol(opt_argv[0], &end, 0); + if (*end) { + fprintf(stderr, "XID argument \"%s\" not valid.\n",opt_argv[0]); + return 2; + } + break; + } + case DILLO_CLI_GEOMETRY: + if (!a_Misc_parse_geometry(opt_argv[0],&xpos,&ypos,&width,&height)){ + fprintf(stderr, "geometry argument \"%s\" not valid. Must be of " + "the form WxH[{+-}X{+-}Y].\n", opt_argv[0]); + return 2; + } + break; + case DILLO_CLI_VERSION: + puts("Dillo version " VERSION); + return 0; + case DILLO_CLI_HELP: + printHelp(argv[0], Options); + return 0; + default: + printHelp(argv[0], Options); + return 2; + } + } + dFree(opt_argv); + + // set the default values for the preferences a_Prefs_init(); - a_Dir_check_dillorc_directory(); /* and create if not present */ + + // create ~/.dillo if not present + Paths::init(); + + // initialize default key bindings + Keys::init(); + + // parse dillorc + if ((fp = Paths::getPrefsFP(PATHS_RC_PREFS))) { + PrefsParser::parse(fp); + } + // parse keysrc + if ((fp = Paths::getPrefsFP(PATHS_RC_KEYS))) { + Keys::parse(fp); + } + dLib_show_messages(prefs.show_msg); + + // initialize internal modules a_Dpi_init(); a_Dns_init(); a_Web_init(); @@ -101,6 +304,17 @@ int main(int argc, char **argv) a_Dicache_init(); a_Bw_init(); a_Cookies_init(); + a_Auth_init(); + + /* command line options override preferences */ + if (options_got & DILLO_CLI_FULLWINDOW) + prefs.fullwindow_start = TRUE; + if (options_got & DILLO_CLI_GEOMETRY) { + prefs.width = width; + prefs.height = height; + prefs.xpos = xpos; + prefs.ypos = ypos; + } // Sets WM_CLASS hint on X11 fltk::Window::xclass("dillo"); @@ -108,9 +322,18 @@ int main(int argc, char **argv) // WORKAROUND: sometimes the default pager triggers redraw storms fltk::TabGroup::default_pager(fltk::PAGER_SHRINK); + checkPreferredFonts(); + /* use preferred font for UI */ + fltk::Font *dfont = fltk::font(prefs.font_sans_serif, 0); + if (dfont) { + fltk::Widget::default_style->textfont(dfont); + fltk::Widget::default_style->labelfont(dfont); + } + // Create a new UI/bw pair - BrowserWindow *bw = a_UIcmd_browser_window_new(0, 0, NULL); + BrowserWindow *bw = a_UIcmd_browser_window_new(0, 0, xid, NULL); + /* Proxy authentication */ if (prefs.http_proxyuser && !a_Http_proxy_auth()) { const char *passwd = a_UIcmd_get_passwd(prefs.http_proxyuser); if (passwd) { @@ -120,24 +343,50 @@ int main(int argc, char **argv) } } - if (argc == 2) { - DilloUrl *url = Dillo_make_start_url(argv[1]); - a_UIcmd_open_urlstr(bw, URL_STR(url)); - a_Url_free(url); - } else if (argc == 6) { - // WORKAROUND: sylpheed execs "dillo -l -f -x XID URL" - if (strcmp(argv[1], "-l") == 0 && strcmp(argv[2], "-f") == 0 && - strcmp(argv[3], "-x") == 0) { - a_UIcmd_set_images_enabled(bw, FALSE); - DilloUrl *url = Dillo_make_start_url(argv[5]); - a_Url_set_flags(url, URL_FLAGS(url) & URL_SpamSafe); - a_UIcmd_open_urlstr(bw, URL_STR(url)); - a_Url_free(url); - } + /* Open URLs/files */ + const bool local = options_got & DILLO_CLI_LOCAL; + + if (idx == argc) { + /* No URLs/files on cmdline. Send startup screen */ + a_UIcmd_open_url(bw, prefs.start_page); } else { - /* Send startup screen */ - a_Nav_push(bw, prefs.start_page); + for (int i = idx; i < argc; i++) { + DilloUrl *start_url = makeStartUrl(argv[i], local); + + if (i > idx) { + if (prefs.middle_click_opens_new_tab) { + /* user must prefer tabs */ + const int focus = 1; + a_UIcmd_open_url_nt(bw, start_url, focus); + } else { + a_UIcmd_open_url_nw(bw, start_url); + } + } else { + a_UIcmd_open_url(bw, start_url); + } + a_Url_free(start_url); + } } - return fltk::run(); + fltk::run(); + + /* + * Memory deallocating routines + * (This can be left to the OS, but we'll do it, with a view to test + * and fix our memory management) + */ + a_Cookies_freeall(); + a_Cache_freeall(); + a_Dicache_freeall(); + a_Http_freeall(); + a_Dns_freeall(); + a_History_freeall(); + a_Prefs_freeall(); + Keys::free(); + Paths::free(); + /* TODO: auth, css */ + + //a_Dpi_dillo_exit(); + MSG("Dillo: normal exit!\n"); + return 0; } diff --git a/src/dir.c b/src/dir.c deleted file mode 100644 index f7b2644d..00000000 --- a/src/dir.c +++ /dev/null @@ -1,75 +0,0 @@ -/* - * File: dir.c - * - * Copyright 2006-2007 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 <errno.h> -#include <sys/stat.h> - -#include "msg.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); -} - -/* - * Check if '~/.dillo' directory exists. - * If not, try to create it. - */ -void a_Dir_check_dillorc_directory(void) -{ - char *dir; - struct stat st; - - dir = dStrconcat(dGethomedir(), "/.dillo", NULL); - if (stat(dir, &st) == -1) { - if (errno == ENOENT) { - MSG("Dillo: creating directory %s.\n", dir); - if (mkdir(dir, 0700) < 0) { - MSG("Dillo: error creating directory %s: %s\n", dir, - dStrerror(errno)); - } - } else { - MSG("Dillo: error reading %s: %s\n", dir, dStrerror(errno)); - } - } - dFree(dir); -} - diff --git a/src/dir.h b/src/dir.h deleted file mode 100644 index 19d69713..00000000 --- a/src/dir.h +++ /dev/null @@ -1,20 +0,0 @@ -#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); -void a_Dir_check_dillorc_directory(void); - - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* __DIR_H__ */ - diff --git a/src/djpeg.h b/src/djpeg.h new file mode 100644 index 00000000..32419e4b --- /dev/null +++ b/src/djpeg.h @@ -0,0 +1,19 @@ +#ifndef __JPEG_H__ +#define __JPEG_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include "url.h" +#include "image.hh" + + +void *a_Jpeg_new(DilloImage *Image, DilloUrl *url, int version); +void a_Jpeg_callback(int Op, void *data); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* !__JPEG_H__ */ @@ -13,17 +13,26 @@ * Non blocking pthread-handled Dns scheme */ -#include <pthread.h> + +/* + * Uncomment the following line for debugging or gprof profiling. + */ +/* #undef D_DNS_THREADED */ + +#ifdef D_DNS_THREADED +# include <pthread.h> +#endif + #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> +#include <arpa/inet.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" @@ -31,11 +40,6 @@ #include "list.h" #include "timeout.hh" -/* - * Uncomment the following line for debugging or gprof profiling. - */ -/* #undef D_DNS_THREADED */ - /* Maximum dns resolving threads */ #ifdef D_DNS_THREADED @@ -84,7 +88,6 @@ 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; /* ---------------------------------------------------------------------- @@ -197,16 +200,14 @@ void a_Dns_init(void) #endif } - /* IPv6 test */ - ipv6_enabled = FALSE; #ifdef ENABLE_IPV6 + /* IPv6 test */ { /* 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 @@ -267,12 +268,15 @@ static void *Dns_server(void *data) int channel = VOIDP2INT(data); struct addrinfo hints, *res0; int error; + Dlist *hosts; + size_t length, i; + char addr_string[40]; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - Dlist *hosts = dList_new(2); + hosts = dList_new(2); _MSG("Dns_server: starting...\n ch: %d host: %s\n", channel, dns_server[channel].hostname); @@ -306,8 +310,18 @@ static void *Dns_server(void *data) } /* tell our findings */ - MSG("Dns_server [%d]: %s is %p\n", channel, - dns_server[channel].hostname, hosts); + MSG("Dns_server [%d]: %s is", channel, + dns_server[channel].hostname); + if ((length = dList_length(hosts))) { + for (i = 0; i < length; i++) { + a_Dns_dillohost_to_string(dList_nth_data(hosts, i), + addr_string, sizeof(addr_string)); + MSG(" %s", addr_string); + } + MSG("\n"); + } else { + MSG(" (nil)\n"); + } dns_server[channel].addr_list = hosts; dns_server[channel].ip_ready = TRUE; @@ -367,7 +381,7 @@ void a_Dns_resolve(const char *hostname, DnsCallback_t cb_func, void *cb_data) break; if (i < dns_cache_size) { - /* already resolved, call the Callback inmediately. */ + /* already resolved, call the Callback immediately. */ cb_func(0, dns_cache[i].addr_list, cb_data); } else if ((i = Dns_queue_find(hostname)) != -1) { @@ -469,15 +483,35 @@ static void Dns_timeout_client(void *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 + * 'dns_cache' is the only one that grows dynamically */ void a_Dns_freeall(void) { - int i; + int i, j; for ( i = 0; i < dns_cache_size; ++i ){ dFree(dns_cache[i].hostname); + for ( j = 0; j < dList_length(dns_cache[i].addr_list); ++j) + dFree(dList_nth_data(dns_cache[i].addr_list, j)); + dList_free(dns_cache[i].addr_list); } dFree(dns_cache); } +/* + * Writes a string representation of the given DilloHost + * into dst. dst will be \0 terminated. + * Please note that dst must be at least 40 bytes long for IPv6 + * addresses. + */ +void a_Dns_dillohost_to_string(DilloHost *host, char *dst, size_t size) +{ + if (!inet_ntop(host->af, host->data, dst, size)) { + switch (errno) { + case EAFNOSUPPORT: + snprintf(dst, size, "Unknown address family"); + case ENOSPC: + snprintf(dst, size, "Buffer too small"); + } + } +} @@ -14,7 +14,11 @@ 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 sizeof(struct in6_addr) +#ifdef ENABLE_IPV6 +# define DILLO_ADDR_MAX sizeof(struct in6_addr) +#else +# define DILLO_ADDR_MAX sizeof(struct in_addr) +#endif typedef struct _DilloHost { @@ -22,7 +26,7 @@ typedef struct _DilloHost int alen; char data[DILLO_ADDR_MAX]; } DilloHost; - +void a_Dns_dillohost_to_string(DilloHost *host, char *dst, size_t size); #ifdef __cplusplus } diff --git a/src/doctree.hh b/src/doctree.hh new file mode 100644 index 00000000..ef7faa7c --- /dev/null +++ b/src/doctree.hh @@ -0,0 +1,72 @@ +#ifndef __DOCTREE_HH__ +#define __DOCTREE_HH__ + +#include "lout/misc.hh" + +class DoctreeNode { + public: + DoctreeNode *parent; + int num; // unique ascending id + int element; + lout::misc::SimpleVector<char*> *klass; + const char *pseudo; + const char *id; + + DoctreeNode () { + parent = NULL; + klass = NULL; + pseudo = NULL; + id = NULL; + element = 0; + }; +}; + +/** + * \brief HTML document tree interface. + * + * The Doctree class defines the interface to the parsed HTML document tree + * as it is used for CSS selector matching. + * Currently the Doctree can be represented as stack, however to support + * CSS adjacent siblings or for future JavaScript support it may have to + * be extended to a real tree. + */ +class Doctree { + private: + DoctreeNode *topNode; + int num; + + public: + Doctree () { + topNode = NULL; + num = 0; + }; + ~Doctree () { while (top ()) pop (); }; + DoctreeNode *push () { + DoctreeNode *dn = new DoctreeNode (); + dn->parent = topNode; + dn->num = num++; + topNode = dn; + return dn; + }; + void pop () { + DoctreeNode *dn = topNode; + if (dn) { + dFree ((void*) dn->id); + if (dn->klass) { + for (int i = 0; i < dn->klass->size (); i++) + dFree (dn->klass->get(i)); + delete dn->klass; + } + topNode = dn->parent; + delete dn; + } + }; + inline DoctreeNode *top () { + return topNode; + }; + inline DoctreeNode *parent (const DoctreeNode *node) { + return node->parent; + }; +}; + +#endif diff --git a/src/dpiapi.c b/src/dpiapi.c index cf32ed3f..2068146c 100644 --- a/src/dpiapi.c +++ b/src/dpiapi.c @@ -41,6 +41,7 @@ static void Dpiapi_dialog_answer_cb(BrowserWindow *bw, int answer) /* Send answer */ a_Capi_dpi_send_cmd(NULL, bw, cmd, dialog_server, 0); + dFree(cmd); } /* @@ -60,12 +61,12 @@ void a_Dpiapi_dialog(BrowserWindow *bw, char *server, char *dpip_tag) /* 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"); + question = a_Dpip_get_attr_l(dpip_tag, dpip_tag_len, "msg"); + alt1 = a_Dpip_get_attr_l(dpip_tag, dpip_tag_len, "alt1"); + alt2 = a_Dpip_get_attr_l(dpip_tag, dpip_tag_len, "alt2"); + alt3 = a_Dpip_get_attr_l(dpip_tag, dpip_tag_len, "alt3"); + alt4 = a_Dpip_get_attr_l(dpip_tag, dpip_tag_len, "alt4"); + alt5 = a_Dpip_get_attr_l(dpip_tag, dpip_tag_len, "alt5"); ret = a_Dialog_choice5(question, alt1, alt2, alt3, alt4, alt5); /* As choice5 is modal, call the callback function directly. */ diff --git a/src/dpng.h b/src/dpng.h new file mode 100644 index 00000000..a9ee8820 --- /dev/null +++ b/src/dpng.h @@ -0,0 +1,19 @@ +#ifndef __PNG_H__ +#define __PNG_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include "url.h" +#include "image.hh" + + +void *a_Png_new(DilloImage *Image, DilloUrl *url, int version); +void a_Png_callback(int Op, CacheClient_t *Client); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* !__PNG_H__ */ diff --git a/src/findbar.cc b/src/findbar.cc index 5d1c6245..8cca52ba 100644 --- a/src/findbar.cc +++ b/src/findbar.cc @@ -18,6 +18,8 @@ #include "uicmd.hh" #include "bw.h" +using namespace fltk; + /* * Local sub class * (Used to handle escape in the findbar, may also avoid some shortcuts). @@ -63,7 +65,22 @@ void Findbar::search_cb(Widget *, void *vfb) if (key[0] != '\0') a_UIcmd_findtext_search(a_UIcmd_get_bw_by_widget(fb), - key, case_sens); + key, case_sens, false); +} + +/* + * Find previous occurrence of input key + */ +void Findbar::searchBackwards_cb(Widget *, void *vfb) +{ + Findbar *fb = (Findbar *)vfb; + const char *key = fb->i->text(); + bool case_sens = fb->check_btn->value(); + + if (key[0] != '\0') { + a_UIcmd_findtext_search(a_UIcmd_get_bw_by_widget(fb), + key, case_sens, true); + } } /* @@ -74,7 +91,7 @@ void Findbar::search_cb2(Widget *widget, void *vfb) /* * Somehow fltk even regards the first loss of focus for the * window as a WHEN_ENTER_KEY_ALWAYS event. - */ + */ if (event_key() == ReturnKey) search_cb(widget, vfb); } @@ -96,7 +113,7 @@ Findbar::Findbar(int width, int height) : int button_width = 70; int gap = 2; int border = 2; - int input_width = width - (2 * border + 3 * (button_width + gap)); + int input_width = width - (2 * border + 4 * (button_width + gap)); int x = border; height -= 2 * border; @@ -107,7 +124,6 @@ Findbar::Findbar(int width, int height) : hide_btn = new HighlightButton(x, border, 16, height, 0); hideImg = new xpmImage(new_s_xpm); hide_btn->image(hideImg); - hide_btn->tooltip("Hide"); x += 16 + gap; hide_btn->callback(hide_cb, this); hide_btn->clear_tab_to_focus(); @@ -121,21 +137,33 @@ Findbar::Findbar(int width, int height) : i->clear_tab_to_focus(); i->set_click_to_focus(); - // TODO: search previous would be nice next_btn = new HighlightButton(x, border, button_width, height, "Next"); x += button_width + gap; - next_btn->tooltip("Find next occurrence of the search phrase"); next_btn->add_shortcut(ReturnKey); next_btn->add_shortcut(KeypadEnter); next_btn->callback(search_cb, this); next_btn->clear_tab_to_focus(); + prev_btn= new HighlightButton(x, border, button_width, height, "Previous"); + prev_btn->add_shortcut(SHIFT+ReturnKey); + prev_btn->callback(searchBackwards_cb, this); + prev_btn->clear_tab_to_focus(); + x += button_width + gap; + check_btn = new CheckButton(x, border, 2*button_width, height, "Case-sensitive"); check_btn->clear_tab_to_focus(); x += 2 * button_width + gap; end(); + + if (prefs.show_tooltip) { + hide_btn->tooltip("Hide"); + next_btn->tooltip("Find next occurrence of the search phrase\n" + "shortcut: Enter"); + prev_btn->tooltip("Find previous occurrence of the search phrase\n" + "shortcut: Shift+Enter"); + } } Findbar::~Findbar() diff --git a/src/findbar.hh b/src/findbar.hh index 1e8c8d66..ba7ed8ed 100644 --- a/src/findbar.hh +++ b/src/findbar.hh @@ -9,21 +9,20 @@ #include <fltk/Group.h> #include <fltk/CheckButton.h> -using namespace fltk; - /* * Searchbar to find text in page. */ -class Findbar : public Group { - Button *clrb; - HighlightButton *hide_btn, *next_btn; - CheckButton *check_btn; - xpmImage *hideImg; - Input *i; - - static void search_cb (Widget *, void *); - static void search_cb2 (Widget *, void *); - static void hide_cb (Widget *, void *); +class Findbar : public fltk::Group { + fltk::Button *clrb; + fltk::HighlightButton *hide_btn, *next_btn, *prev_btn; + fltk::CheckButton *check_btn; + fltk::xpmImage *hideImg; + fltk::Input *i; + + static void search_cb (fltk::Widget *, void *); + static void searchBackwards_cb (fltk::Widget *, void *); + static void search_cb2 (fltk::Widget *, void *); + static void hide_cb (fltk::Widget *, void *); public: Findbar(int width, int height); diff --git a/src/form.cc b/src/form.cc index 78bc43e8..38afea8f 100644 --- a/src/form.cc +++ b/src/form.cc @@ -22,28 +22,28 @@ #include "misc.h" #include "msg.h" #include "prefs.h" -#include "nav.h" #include "uicmd.hh" +using namespace lout; using namespace dw; using namespace dw::core; using namespace dw::core::style; +using namespace dw::core::ui; /* - * Forward declarations + * Forward declarations */ class DilloHtmlReceiver; class DilloHtmlSelect; class DilloHtmlOption; -static dw::core::ui::Embed *Html_input_image(DilloHtml *html, - const char *tag, int tagsize); +static Embed *Html_input_image(DilloHtml *html, const char *tag, int tagsize); static void Html_option_finish(DilloHtml *html); /* - * Typedefs + * Typedefs */ typedef enum { @@ -67,7 +67,7 @@ typedef enum { } DilloHtmlInputType; /* - * Class declarations + * Class declarations */ class DilloHtmlForm { @@ -75,74 +75,74 @@ class DilloHtmlForm { friend class DilloHtmlInput; DilloHtml *html; - void eventHandler(dw::core::ui::Resource *resource); - void submit(DilloHtmlInput *input); - DilloUrl *buildQueryUrl(DilloHtmlInput *input); + bool showing_hiddens; + bool enabled; + void eventHandler(Resource *resource, EventButton *event); + DilloUrl *buildQueryUrl(DilloHtmlInput *active_input); Dstr *buildQueryData(DilloHtmlInput *active_submit); - char *makeMultipartBoundary(iconv_t encoder, DilloHtmlInput *active_submit); - Dstr *encodeText(iconv_t encoder, Dstr **input); - void urlencodeAppend(Dstr *str, const char *val); - void appendInputUrlencode(Dstr *data, + char *makeMultipartBoundary(iconv_t char_encoder, + DilloHtmlInput *active_submit); + Dstr *encodeText(iconv_t char_encoder, Dstr **input); + void strUrlencodeAppend(Dstr *dstr, const char *str); + void inputUrlencodeAppend(Dstr *data, const char *name, const char *value); + void inputMultipartAppend(Dstr *data, const char *boundary, const char *name, const char *value); - void appendInputMultipartFiles(Dstr* data, - const char *boundary, + void filesInputMultipartAppend(Dstr* data, const char *boundary, const char *name, Dstr *file, const char *filename); - void appendInputMultipart(Dstr *data, - const char *boundary, - const char *name, - const char *value); - void appendClickposUrlencode(Dstr *data, Dstr *name, Dstr *x, Dstr *y); - void appendClickposMultipart(Dstr *data, const char *boundary, Dstr *name, - Dstr *x, Dstr *y); + void imageInputUrlencodeAppend(Dstr *data, Dstr *name, Dstr *x, Dstr *y); + void imageInputMultipartAppend(Dstr *data, const char *boundary, Dstr *name, + Dstr *x, Dstr *y); public: //BUG: for now everything is public DilloHtmlMethod method; DilloUrl *action; - DilloHtmlEnc enc; + DilloHtmlEnc content_type; char *submit_charset; lout::misc::SimpleVector<DilloHtmlInput*> *inputs; int num_entry_fields; - int num_submit_buttons; DilloHtmlReceiver *form_receiver; public: - DilloHtmlForm (DilloHtml *html, + DilloHtmlForm (DilloHtml *html, DilloHtmlMethod method, const DilloUrl *action, - DilloHtmlEnc enc, const char *charset); + DilloHtmlEnc content_type, const char *charset, + bool enabled); ~DilloHtmlForm (); - DilloHtmlInput *getInput (dw::core::ui::Resource *resource); + DilloHtmlInput *getInput (Resource *resource); DilloHtmlInput *getRadioInput (const char *name); + void submit(DilloHtmlInput *active_input, EventButton *event); void reset (); + void display_hiddens(bool display); void addInput(DilloHtmlInput *input, DilloHtmlInputType type); + void setEnabled(bool enabled); }; class DilloHtmlReceiver: - public dw::core::ui::Resource::ActivateReceiver, - public dw::core::ui::ButtonResource::ClickedReceiver + public Resource::ActivateReceiver, + public Resource::ClickedReceiver { friend class DilloHtmlForm; DilloHtmlForm* form; DilloHtmlReceiver (DilloHtmlForm* form2) { form = form2; } ~DilloHtmlReceiver () { } - void activate (dw::core::ui::Resource *resource); - void enter (dw::core::ui::Resource *resource); - void leave (dw::core::ui::Resource *resource); - void clicked (dw::core::ui::ButtonResource *resource, - int buttonNo, int x, int y); + void activate (Resource *resource); + void enter (Resource *resource); + void leave (Resource *resource); + void clicked (Resource *resource, EventButton *event); }; class DilloHtmlInput { - // DilloHtmlForm::addInput() calls connectTo() + // DilloHtmlForm::addInput() calls connectTo() friend class DilloHtmlForm; public: //BUG: for now everything is public DilloHtmlInputType type; - dw::core::ui::Embed *embed; /* May be NULL (think: hidden input) */ + Embed *embed; /* May be NULL (think: hidden input) */ char *name; char *init_str; /* note: some overloading - for buttons, init_str is simply the value of the button; for text @@ -154,18 +154,16 @@ public: //BUG: for now everything is public private: void connectTo(DilloHtmlReceiver *form_receiver); - void activate(DilloHtmlForm *form, bool force_submit); + void activate(DilloHtmlForm *form, int num_entry_fields,EventButton *event); void readFile(BrowserWindow *bw); public: - DilloHtmlInput (DilloHtmlInputType type, - dw::core::ui::Embed *embed, - const char *name, - const char *init_str, - bool init_val); + DilloHtmlInput (DilloHtmlInputType type, Embed *embed, + const char *name, const char *init_str, bool init_val); ~DilloHtmlInput (); void appendValuesTo(Dlist *values, bool is_active_submit); void reset(); + void setEnabled(bool enabled) {if (embed) embed->setEnabled(enabled); }; }; class DilloHtmlSelect { @@ -178,9 +176,8 @@ public: DilloHtmlOption *getCurrentOption (); void addOption (char *value, bool selected, bool enabled); void ensureSelection (); - void addOptionsTo (dw::core::ui::SelectionResource *res); - void appendValuesTo (Dlist *values, - dw::core::ui::SelectionResource *res); + void addOptionsTo (SelectionResource *res); + void appendValuesTo (Dlist *values, SelectionResource *res); }; class DilloHtmlOption { @@ -190,20 +187,20 @@ public: bool selected, enabled; private: DilloHtmlOption (char *value, bool selected, bool enabled); - ~DilloHtmlOption (); + ~DilloHtmlOption (); }; /* - * Form API + * Form API */ -DilloHtmlForm *a_Html_form_new (DilloHtml *html, - DilloHtmlMethod method, +DilloHtmlForm *a_Html_form_new (DilloHtml *html, DilloHtmlMethod method, const DilloUrl *action, - DilloHtmlEnc enc, - const char *charset) + DilloHtmlEnc content_type, const char *charset, + bool enabled) { - return new DilloHtmlForm (html,method,action,enc,charset); + return new DilloHtmlForm (html, method, action, content_type, charset, + enabled); } void a_Html_form_delete (DilloHtmlForm *form) @@ -216,15 +213,30 @@ void a_Html_input_delete (DilloHtmlInput *input) delete input; } +void a_Html_form_submit2(void *vform) +{ + ((DilloHtmlForm *)vform)->submit(NULL, NULL); +} + +void a_Html_form_reset2(void *vform) +{ + ((DilloHtmlForm *)vform)->reset(); +} + +void a_Html_form_display_hiddens2(void *vform, bool display) +{ + ((DilloHtmlForm *)vform)->display_hiddens(display); +} + /* - * Form parsing functions + * Form parsing functions */ /* * Add an HTML control */ static void Html_add_input(DilloHtml *html, DilloHtmlInputType type, - dw::core::ui::Embed *embed, const char *name, + Embed *embed, const char *name, const char *init_str, bool init_val) { _MSG("name=[%s] init_str=[%s] init_val=[%d]\n", name, init_str, init_val); @@ -236,6 +248,8 @@ static void Html_add_input(DilloHtml *html, DilloHtmlInputType type, int ni = html->inputs_outside_form->size(); html->inputs_outside_form->increase(); html->inputs_outside_form->set(ni, input); + + input->setEnabled(false); } } @@ -244,18 +258,20 @@ static void Html_add_input(DilloHtml *html, DilloHtmlInputType type, */ static DilloHtmlInput *Html_get_radio_input(DilloHtml *html, const char *name) { - lout::misc::SimpleVector<DilloHtmlInput*>* inputs; + if (name) { + lout::misc::SimpleVector<DilloHtmlInput*>* inputs; - if (html->InFlags & IN_FORM) - inputs = html->getCurrentForm()->inputs; - else - inputs = html->inputs_outside_form; + if (html->InFlags & IN_FORM) + inputs = html->getCurrentForm()->inputs; + else + inputs = html->inputs_outside_form; - for (int idx = 0; idx < inputs->size(); idx++) { - DilloHtmlInput *input = inputs->get(idx); - if (input->type == DILLO_HTML_INPUT_RADIO && - input->name && !dStrcasecmp(input->name, name)) - return input; + for (int idx = 0; idx < inputs->size(); idx++) { + DilloHtmlInput *input = inputs->get(idx); + if (input->type == DILLO_HTML_INPUT_RADIO && + input->name && !dStrcasecmp(input->name, name)) + return input; + } } return NULL; } @@ -283,11 +299,11 @@ void Html_tag_open_form(DilloHtml *html, const char *tag, int tagsize) { DilloUrl *action; DilloHtmlMethod method; - DilloHtmlEnc enc; + DilloHtmlEnc content_type; char *charset, *first; const char *attrbuf; - DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + HT2TB(html)->addParbreak (9, html->styleEngine->wordStyle ()); if (html->InFlags & IN_FORM) { BUG_MSG("nested forms\n"); @@ -308,13 +324,15 @@ void Html_tag_open_form(DilloHtml *html, const char *tag, int tagsize) } if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "action"))) action = a_Html_url_new(html, attrbuf, NULL, 0); - else + else { + BUG_MSG("action attribute required for <form>\n"); action = a_Url_dup(html->base_url); - enc = DILLO_HTML_ENC_URLENCODING; + } + content_type = DILLO_HTML_ENC_URLENCODED; if ((method == DILLO_HTML_METHOD_POST) && ((attrbuf = a_Html_get_attr(html, tag, tagsize, "enctype")))) { if (!dStrcasecmp(attrbuf, "multipart/form-data")) - enc = DILLO_HTML_ENC_MULTIPART; + content_type = DILLO_HTML_ENC_MULTIPART; } charset = NULL; first = NULL; @@ -335,32 +353,19 @@ void Html_tag_open_form(DilloHtml *html, const char *tag, int tagsize) } if (!charset) charset = html->charset; - html->formNew(method, action, enc, charset); + html->formNew(method, action, content_type, charset); dFree(first); a_Url_free(action); } void Html_tag_close_form(DilloHtml *html, int TagIdx) { - static const char *SubmitTag = - "<input type='submit' value='?Submit?' alt='dillo-generated-button'>"; - DilloHtmlForm *form; +// DilloHtmlForm *form; // int i; - - if (html->InFlags & IN_FORM) { - form = html->getCurrentForm (); - /* 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) - BUG_MSG("FORM lacks a Submit button\n"); - if (prefs.generate_submit) { - BUG_MSG(" (added a submit button internally)\n"); - Html_tag_open_input(html, SubmitTag, strlen(SubmitTag)); - form->num_submit_buttons = 0; - } - } - +// +// if (html->InFlags & IN_FORM) { +// form = html->getCurrentForm (); +// // /* Make buttons sensitive again */ // for (i = 0; i < form->inputs->size(); i++) { // input_i = form->inputs->get(i); @@ -376,14 +381,31 @@ void Html_tag_close_form(DilloHtml *html, int TagIdx) // a_Dw_button_set_sensitive(DW_BUTTON(input_i->widget), TRUE); // } // } - } +// } html->InFlags &= ~IN_FORM; html->InFlags &= ~IN_SELECT; html->InFlags &= ~IN_OPTION; html->InFlags &= ~IN_TEXTAREA; +} - a_Html_pop_tag(html, TagIdx); +/* + * get size, restrict it to reasonable value + */ +static int Html_input_get_size(DilloHtml *html, const char *attrbuf) +{ + const int MAX_SIZE = 1024; + int size = 20; + + if (attrbuf) { + size = strtol(attrbuf, NULL, 10); + if (size < 1 || size > MAX_SIZE) { + int badSize = size; + size = (size < 1 ? 20 : MAX_SIZE); + BUG_MSG("input size=%d, using size=%d instead\n", badSize, size); + } + } + return size; } /* @@ -392,11 +414,13 @@ void Html_tag_close_form(DilloHtml *html, int TagIdx) void Html_tag_open_input(DilloHtml *html, const char *tag, int tagsize) { DilloHtmlInputType inp_type; - dw::core::ui::Embed *embed = NULL; + Resource *resource = NULL; + Embed *embed = NULL; char *value, *name, *type, *init_str; const char *attrbuf, *label; bool init_val = false; - + ResourceFactory *factory; + if (html->InFlags & IN_SELECT) { BUG_MSG("<input> element inside <select>\n"); return; @@ -405,59 +429,50 @@ void Html_tag_open_input(DilloHtml *html, const char *tag, int tagsize) BUG_MSG("<input> element inside <button>\n"); return; } - + + factory = HT2LT(html)->getResourceFactory(); + /* Get 'value', 'name' and 'type' */ value = a_Html_get_attr_wdef(html, tag, tagsize, "value", NULL); name = a_Html_get_attr_wdef(html, tag, tagsize, "name", NULL); type = a_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; - dw::core::ui::EntryResource *entryResource = - HT2LT(html)->getResourceFactory()->createEntryResource (10, true); - embed = new dw::core::ui::Embed (entryResource); - init_str = (value) ? value : NULL; + attrbuf = a_Html_get_attr(html, tag, tagsize, "size"); + int size = Html_input_get_size(html, attrbuf); + resource = factory->createEntryResource (size, true, NULL); + init_str = value; } else if (!dStrcasecmp(type, "checkbox")) { inp_type = DILLO_HTML_INPUT_CHECKBOX; - dw::core::ui::CheckButtonResource *check_b_r = - HT2LT(html)->getResourceFactory()->createCheckButtonResource(false); - embed = new dw::core::ui::Embed (check_b_r); + resource = factory->createCheckButtonResource(false); init_val = (a_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; - dw::core::ui::RadioButtonResource *rb_r = NULL; + RadioButtonResource *rb_r = NULL; DilloHtmlInput *input = Html_get_radio_input(html, name); if (input) - rb_r = - (dw::core::ui::RadioButtonResource*) - input->embed->getResource(); - rb_r = HT2LT(html)->getResourceFactory() - ->createRadioButtonResource(rb_r, false); - embed = new dw::core::ui::Embed (rb_r); + rb_r = (RadioButtonResource*) input->embed->getResource(); + resource = factory->createRadioButtonResource(rb_r, false); init_val = (a_Html_get_attr(html, tag, tagsize, "checked") != NULL); - init_str = (value) ? value : NULL; + init_str = value; } else if (!dStrcasecmp(type, "hidden")) { inp_type = DILLO_HTML_INPUT_HIDDEN; - if (value) - init_str = dStrdup(a_Html_get_attr(html, tag, tagsize, "value")); + init_str = value; + int size = Html_input_get_size(html, NULL); + resource = factory->createEntryResource(size, false, name); } else if (!dStrcasecmp(type, "submit")) { inp_type = DILLO_HTML_INPUT_SUBMIT; init_str = (value) ? value : dStrdup("submit"); - dw::core::ui::LabelButtonResource *label_b_r = - HT2LT(html)->getResourceFactory() - ->createLabelButtonResource(init_str); - embed = new dw::core::ui::Embed (label_b_r); + resource = factory->createLabelButtonResource(init_str); // gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */ } else if (!dStrcasecmp(type, "reset")) { inp_type = DILLO_HTML_INPUT_RESET; init_str = (value) ? value : dStrdup("Reset"); - dw::core::ui::LabelButtonResource *label_b_r = - HT2LT(html)->getResourceFactory() - ->createLabelButtonResource(init_str); - embed = new dw::core::ui::Embed (label_b_r); + resource = factory->createLabelButtonResource(init_str); // gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */ } else if (!dStrcasecmp(type, "image")) { if (URL_FLAGS(html->base_url) & URL_SpamSafe) { @@ -466,10 +481,7 @@ void Html_tag_open_input(DilloHtml *html, const char *tag, int tagsize) attrbuf = a_Html_get_attr(html, tag, tagsize, "alt"); label = attrbuf ? attrbuf : value ? value : name ? name : "Submit"; init_str = dStrdup(label); - dw::core::ui::LabelButtonResource *label_b_r = - HT2LT(html)->getResourceFactory() - ->createLabelButtonResource(init_str); - embed = new dw::core::ui::Embed (label_b_r); + resource = factory->createLabelButtonResource(init_str); // gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */ } else { inp_type = DILLO_HTML_INPUT_IMAGE; @@ -485,7 +497,7 @@ void Html_tag_open_input(DilloHtml *html, const char *tag, int tagsize) valid = false; BUG_MSG("Forms with file input MUST use HTTP POST method\n"); MSG("File input ignored in form not using HTTP POST method\n"); - } else if (form->enc != DILLO_HTML_ENC_MULTIPART) { + } else if (form->content_type != DILLO_HTML_ENC_MULTIPART) { valid = false; BUG_MSG("Forms with file input MUST use multipart/form-data" " encoding\n"); @@ -496,65 +508,60 @@ void Html_tag_open_input(DilloHtml *html, const char *tag, int tagsize) if (valid) { inp_type = DILLO_HTML_INPUT_FILE; init_str = dStrdup("File selector"); - dw::core::ui::LabelButtonResource *lbr = - HT2LT(html)->getResourceFactory()-> - createLabelButtonResource(init_str); - embed = new dw::core::ui::Embed (lbr); + resource = factory->createLabelButtonResource(init_str); } } else if (!dStrcasecmp(type, "button")) { inp_type = DILLO_HTML_INPUT_BUTTON; if (value) { init_str = value; - dw::core::ui::LabelButtonResource *label_b_r = - HT2LT(html)->getResourceFactory() - ->createLabelButtonResource(init_str); - embed = new dw::core::ui::Embed (label_b_r); + resource = factory->createLabelButtonResource(init_str); } } else if (!dStrcasecmp(type, "text") || !*type) { /* Text input, which also is the default */ inp_type = DILLO_HTML_INPUT_TEXT; - dw::core::ui::EntryResource *entryResource = - HT2LT(html)->getResourceFactory()->createEntryResource (10, false); - embed = new dw::core::ui::Embed (entryResource); - init_str = (value) ? value : NULL; + attrbuf = a_Html_get_attr(html, tag, tagsize, "size"); + int size = Html_input_get_size(html, attrbuf); + resource = factory->createEntryResource(size, false, NULL); + init_str = value; } else { /* Unknown input type */ BUG_MSG("Unknown input type: \"%s\"\n", type); } + if (resource) + embed = new Embed (resource); + if (inp_type != DILLO_HTML_INPUT_UNKNOWN) { Html_add_input(html, inp_type, embed, name, (init_str) ? init_str : "", init_val); } - - if (embed != NULL && inp_type != DILLO_HTML_INPUT_IMAGE && + + if (embed != NULL && inp_type != DILLO_HTML_INPUT_IMAGE && inp_type != DILLO_HTML_INPUT_UNKNOWN) { + if (inp_type == DILLO_HTML_INPUT_HIDDEN) { + /* TODO Perhaps do this with access to current form setting */ + embed->setDisplayed(false); + } if (inp_type == DILLO_HTML_INPUT_TEXT || inp_type == DILLO_HTML_INPUT_PASSWORD) { - dw::core::ui::EntryResource *entryres = - (dw::core::ui::EntryResource*)embed->getResource(); - /* Readonly or not? */ if (a_Html_get_attr(html, tag, tagsize, "readonly")) - entryres->setEditable(false); + ((EntryResource *) resource)->setEditable(false); -// /* Set width of the entry */ -// if ((attrbuf = a_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 = a_Html_get_attr(html, tag, tagsize, "maxlength"))) // gtk_entry_set_max_length(GTK_ENTRY(widget), // strtol(attrbuf, NULL, 10)); } - Color *bg; - if (prefs.standard_widget_colors) - bg = NULL; - else - bg = Color::createShaded(HT2LT(html), S_TOP(html)->current_bg_color); - HTML_SET_TOP_ATTR(html, backgroundColor, bg); - - DW2TB(html->dw)->addWidget (embed, S_TOP(html)->style); + if (prefs.show_tooltip && + (attrbuf = a_Html_get_attr(html, tag, tagsize, "title"))) { + CssPropertyList props; + char *tooltip_str = dStrdup(attrbuf); + + props.set (PROPERTY_X_TOOLTIP, CSS_TYPE_STRING, tooltip_str); + html->styleEngine->setNonCssHints (&props); + dFree(tooltip_str); + } + HT2TB(html)->addWidget (embed, html->styleEngine->backgroundStyle()); } dFree(type); dFree(name); @@ -570,7 +577,7 @@ void Html_tag_open_input(DilloHtml *html, const char *tag, int tagsize) void Html_tag_open_isindex(DilloHtml *html, const char *tag, int tagsize) { DilloUrl *action; - dw::core::ui::Embed *embed; + Embed *embed; const char *attrbuf; if (html->InFlags & IN_FORM) { @@ -582,28 +589,22 @@ void Html_tag_open_isindex(DilloHtml *html, const char *tag, int tagsize) action = a_Html_url_new(html, attrbuf, NULL, 0); else action = a_Url_dup(html->base_url); - - html->formNew(DILLO_HTML_METHOD_GET, action, DILLO_HTML_ENC_URLENCODING, + + html->formNew(DILLO_HTML_METHOD_GET, action, DILLO_HTML_ENC_URLENCODED, html->charset); html->InFlags |= IN_FORM; - - DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); - + + HT2TB(html)->addParbreak (9, html->styleEngine->wordStyle ()); + if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "prompt"))) - DW2TB(html->dw)->addText(attrbuf, S_TOP(html)->style); - - dw::core::ui::EntryResource *entryResource = - HT2LT(html)->getResourceFactory()->createEntryResource (10, false); - embed = new dw::core::ui::Embed (entryResource); + HT2TB(html)->addText(attrbuf, html->styleEngine->wordStyle ()); + + ResourceFactory *factory = HT2LT(html)->getResourceFactory(); + EntryResource *entryResource = factory->createEntryResource (20,false,NULL); + embed = new Embed (entryResource); Html_add_input(html, DILLO_HTML_INPUT_INDEX, embed, NULL, NULL, FALSE); - Color *bg; - if (prefs.standard_widget_colors) - bg = NULL; - else - bg = Color::createShaded(HT2LT(html), S_TOP(html)->current_bg_color); - HTML_SET_TOP_ATTR(html, backgroundColor, bg); - DW2TB(html->dw)->addWidget (embed, S_TOP(html)->style); + HT2TB(html)->addWidget (embed, html->styleEngine->backgroundStyle ()); a_Url_free(action); html->InFlags &= ~IN_FORM; @@ -620,7 +621,7 @@ void Html_tag_open_textarea(DilloHtml *html, const char *tag, int tagsize) char *name; const char *attrbuf; int cols, rows; - + if (html->InFlags & IN_TEXTAREA) { BUG_MSG("nested <textarea>\n"); html->ReqTagClose = TRUE; @@ -630,22 +631,28 @@ void Html_tag_open_textarea(DilloHtml *html, const char *tag, int tagsize) BUG_MSG("<textarea> element inside <select>\n"); return; } - + html->InFlags |= IN_TEXTAREA; a_Html_stash_init(html); S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM; - - cols = 20; - if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "cols"))) + + if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "cols"))) { cols = strtol(attrbuf, NULL, 10); + } else { + BUG_MSG("cols attribute is required for <textarea>\n"); + cols = 20; + } if (cols < 1 || cols > MAX_COLS) { int badCols = cols; cols = (cols < 1 ? 20 : MAX_COLS); BUG_MSG("textarea cols=%d, using cols=%d instead\n", badCols, cols); } - rows = 10; - if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "rows"))) + if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "rows"))) { rows = strtol(attrbuf, NULL, 10); + } else { + BUG_MSG("rows attribute is required for <textarea>\n"); + rows = 10; + } if (rows < 1 || rows > MAX_ROWS) { int badRows = rows; rows = (rows < 1 ? 2 : MAX_ROWS); @@ -655,24 +662,17 @@ void Html_tag_open_textarea(DilloHtml *html, const char *tag, int tagsize) if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "name"))) name = dStrdup(attrbuf); - dw::core::ui::MultiLineTextResource *textres = - HT2LT(html)->getResourceFactory()->createMultiLineTextResource (cols, - rows); + ResourceFactory *factory = HT2LT(html)->getResourceFactory(); + MultiLineTextResource *textres = + factory->createMultiLineTextResource (cols, rows); - dw::core::ui::Embed *embed = new dw::core::ui::Embed(textres); + Embed *embed = new Embed(textres); /* Readonly or not? */ if (a_Html_get_attr(html, tag, tagsize, "readonly")) textres->setEditable(false); Html_add_input(html, DILLO_HTML_INPUT_TEXTAREA, embed, name, NULL, false); - Color *bg; - if (prefs.standard_widget_colors) - bg = NULL; - else - bg = Color::createShaded(HT2LT(html), S_TOP(html)->current_bg_color); - HTML_SET_TOP_ATTR(html, backgroundColor, bg); - - DW2TB(html->dw)->addWidget (embed, S_TOP(html)->style); + HT2TB(html)->addWidget (embed, html->styleEngine->backgroundStyle ()); dFree(name); } @@ -692,7 +692,7 @@ void Html_tag_close_textarea(DilloHtml *html, int TagIdx) 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) { @@ -703,17 +703,15 @@ void Html_tag_close_textarea(DilloHtml *html, int TagIdx) html->Stash->str[i] = '\n'; } } - + /* The HTML3.2 spec says it can have "text and character entities". */ str = a_Html_parse_entities(html, html->Stash->str, html->Stash->len); input = Html_get_current_input(html); input->init_str = str; - ((dw::core::ui::MultiLineTextResource *)input->embed->getResource ()) - ->setText(str); + ((MultiLineTextResource *)input->embed->getResource ())->setText(str); html->InFlags &= ~IN_TEXTAREA; } - a_Html_pop_tag(html, TagIdx); } /* @@ -722,8 +720,8 @@ void Html_tag_close_textarea(DilloHtml *html, int TagIdx) /* The select tag is quite tricky, because of gorpy html syntax. */ void Html_tag_open_select(DilloHtml *html, const char *tag, int tagsize) { -// const char *attrbuf; -// int size, type, multi; + const char *attrbuf; + int rows = 0; if (html->InFlags & IN_SELECT) { BUG_MSG("nested <select>\n"); @@ -733,51 +731,32 @@ void Html_tag_open_select(DilloHtml *html, const char *tag, int tagsize) html->InFlags &= ~IN_OPTION; char *name = a_Html_get_attr_wdef(html, tag, tagsize, "name", NULL); - dw::core::ui::ResourceFactory *factory = - HT2LT(html)->getResourceFactory (); + ResourceFactory *factory = HT2LT(html)->getResourceFactory (); DilloHtmlInputType type; - dw::core::ui::SelectionResource *res; - if (a_Html_get_attr(html, tag, tagsize, "multiple")) { - type = DILLO_HTML_INPUT_SEL_LIST; - res = factory->createListResource ( - dw::core::ui::ListResource::SELECTION_MULTIPLE); - } else { - type = DILLO_HTML_INPUT_SELECT; - res = factory->createOptionMenuResource (); + SelectionResource *res; + bool multi = a_Html_get_attr(html, tag, tagsize, "multiple") != NULL; + + if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "size"))) { + rows = strtol(attrbuf, NULL, 10); + if (rows > 100) + rows = 100; } - dw::core::ui::Embed *embed = new dw::core::ui::Embed(res); + if (rows < 1) + rows = multi ? 10 : 1; - int bg; - if (prefs.standard_widget_colors) { - /* Valid colors required; SELECT can contain other elements (BUG) */ - HTML_SET_TOP_ATTR(html, color, Color::createSimple (HT2LT(html), 0)); - bg = 0xffffff; + if (rows == 1 && multi == false) { + type = DILLO_HTML_INPUT_SELECT; + res = factory->createOptionMenuResource (); } else { - bg = S_TOP(html)->current_bg_color; + type = DILLO_HTML_INPUT_SEL_LIST; + res = factory->createListResource (multi ? + ListResource::SELECTION_MULTIPLE : + ListResource::SELECTION_EXACTLY_ONE, + rows); } - HTML_SET_TOP_ATTR(html, backgroundColor, - Color::createShaded (HT2LT(html), bg)); - DW2TB(html->dw)->addWidget (embed, S_TOP(html)->style); + Embed *embed = new Embed(res); -// size = 0; -// if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "size"))) -// size = strtol(attrbuf, NULL, 10); -// -// multi = (a_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; -// } + HT2TB(html)->addWidget (embed, html->styleEngine->backgroundStyle ()); Html_add_input(html, type, embed, name, NULL, false); a_Html_stash_init(html); @@ -798,15 +777,12 @@ void Html_tag_close_select(DilloHtml *html, int TagIdx) DilloHtmlInput *input = Html_get_current_input(html); DilloHtmlSelect *select = input->select; - // BUG(?): should not do this for MULTI selections + // BUG(?): should not do this for MULTI selections select->ensureSelection (); - dw::core::ui::SelectionResource *res = - (dw::core::ui::SelectionResource*)input->embed->getResource(); + SelectionResource *res = (SelectionResource*)input->embed->getResource(); select->addOptionsTo (res); } - - a_Html_pop_tag(html, TagIdx); } /* @@ -826,13 +802,10 @@ void Html_tag_open_option(DilloHtml *html, const char *tag, int tagsize) if (input->type == DILLO_HTML_INPUT_SELECT || input->type == DILLO_HTML_INPUT_SEL_LIST) { - char *value = - a_Html_get_attr_wdef(html, tag, tagsize, "value", NULL); - bool selected = - (a_Html_get_attr(html, tag, tagsize, "selected") != NULL); - bool enabled = - (a_Html_get_attr(html, tag, tagsize, "disabled") == NULL); - input->select->addOption(value,selected,enabled); + char *value = a_Html_get_attr_wdef(html, tag, tagsize, "value", NULL); + bool selected = (a_Html_get_attr(html, tag, tagsize,"selected") != NULL); + bool enabled = (a_Html_get_attr(html, tag, tagsize, "disabled") == NULL); + input->select->addOption(value, selected, enabled); } a_Html_stash_init(html); @@ -876,37 +849,23 @@ void Html_tag_open_button(DilloHtml *html, const char *tag, int tagsize) if (inp_type != DILLO_HTML_INPUT_UNKNOWN) { /* Render the button */ - dw::core::style::StyleAttrs style_attrs; - dw::core::style::Style *style; - dw::core::Widget *page; - dw::core::ui::Embed *embed; + Widget *page; + Embed *embed; char *name, *value; - style_attrs = *S_TOP(html)->style; - style_attrs.margin.setVal(0); - style_attrs.borderWidth.setVal(0); - style_attrs.padding.setVal(0); - style_attrs.backgroundColor = - Color::createShaded(HT2LT(html), S_TOP(html)->current_bg_color); - style = Style::create (HT2LT(html), &style_attrs); - page = new Textblock (prefs.limit_text_width); - page->setStyle (style); + page->setStyle (html->styleEngine->backgroundStyle ()); - dw::core::ui::ComplexButtonResource *complex_b_r = - HT2LT(html)->getResourceFactory() - ->createComplexButtonResource(page, true); - embed = new dw::core::ui::Embed(complex_b_r); + ResourceFactory *factory = HT2LT(html)->getResourceFactory(); + Resource *resource = factory->createComplexButtonResource(page, true); + embed = new Embed(resource); // a_Dw_button_set_sensitive (DW_BUTTON (button), FALSE); - DW2TB(html->dw)->addParbreak (5, style); - DW2TB(html->dw)->addWidget (embed, style); - DW2TB(html->dw)->addParbreak (5, style); - style->unref (); + HT2TB(html)->addParbreak (5, html->styleEngine->wordStyle ()); + HT2TB(html)->addWidget (embed, html->styleEngine->style ()); + HT2TB(html)->addParbreak (5, html->styleEngine->wordStyle ()); S_TOP(html)->textblock = html->dw = page; - /* right button press for menus for button contents */ - html->connectSignals(page); value = a_Html_get_attr_wdef(html, tag, tagsize, "value", NULL); name = a_Html_get_attr_wdef(html, tag, tagsize, "name", NULL); @@ -924,39 +883,39 @@ void Html_tag_open_button(DilloHtml *html, const char *tag, int tagsize) void Html_tag_close_button(DilloHtml *html, int TagIdx) { html->InFlags &= ~IN_BUTTON; - a_Html_pop_tag(html, TagIdx); } /* - * Class implementations + * Class implementations */ /* - * DilloHtmlForm + * DilloHtmlForm */ /* - * Constructor + * Constructor */ DilloHtmlForm::DilloHtmlForm (DilloHtml *html2, DilloHtmlMethod method2, const DilloUrl *action2, - DilloHtmlEnc enc2, - const char *charset) + DilloHtmlEnc content_type2, + const char *charset, bool enabled) { html = html2; method = method2; action = a_Url_dup(action2); - enc = enc2; + content_type = content_type2; submit_charset = dStrdup(charset); inputs = new misc::SimpleVector <DilloHtmlInput*> (4); num_entry_fields = 0; - num_submit_buttons = 0; + showing_hiddens = false; + this->enabled = enabled; form_receiver = new DilloHtmlReceiver (this); } /* - * Destructor + * Destructor */ DilloHtmlForm::~DilloHtmlForm () { @@ -969,17 +928,18 @@ DilloHtmlForm::~DilloHtmlForm () delete(form_receiver); } -void DilloHtmlForm::eventHandler(dw::core::ui::Resource *resource) +void DilloHtmlForm::eventHandler(Resource *resource, EventButton *event) { MSG("DilloHtmlForm::eventHandler\n"); - DilloHtmlInput *input = getInput(resource); - if (input) { - bool force_submit = - prefs.enterpress_forces_submit || - num_entry_fields == 1; - input->activate (this, force_submit); + if (event && (event->button == 3)) { + a_UIcmd_form_popup(html->bw, html->page_url, this, showing_hiddens); } else { - MSG("DilloHtmlForm::eventHandler: ERROR, input not found!\n"); + DilloHtmlInput *input = getInput(resource); + if (input) { + input->activate (this, num_entry_fields, event); + } else { + MSG("DilloHtmlForm::eventHandler: ERROR, input not found!\n"); + } } } @@ -987,11 +947,21 @@ void DilloHtmlForm::eventHandler(dw::core::ui::Resource *resource) * Submit. * (Called by eventHandler()) */ -void DilloHtmlForm::submit(DilloHtmlInput *input) +void DilloHtmlForm::submit(DilloHtmlInput *active_input, EventButton *event) { - DilloUrl *url = buildQueryUrl(input); + DilloUrl *url = buildQueryUrl(active_input); if (url) { - a_Nav_push(html->bw, url); + if (event && event->button == 2) { + if (prefs.middle_click_opens_new_tab) { + int focus = prefs.focus_new_tab ? 1 : 0; + if (event->state == SHIFT_MASK) focus = !focus; + a_UIcmd_open_url_nt(html->bw, url, focus); + } else { + a_UIcmd_open_url_nw(html->bw, url); + } + } else { + a_UIcmd_open_url(html->bw, url); + } a_Url_free(url); } // /* now, make the rendered area have its focus back */ @@ -1002,7 +972,7 @@ void DilloHtmlForm::submit(DilloHtmlInput *input) * Build a new query URL. * (Called by submit()) */ -DilloUrl *DilloHtmlForm::buildQueryUrl(DilloHtmlInput *input) +DilloUrl *DilloHtmlForm::buildQueryUrl(DilloHtmlInput *active_input) { DilloUrl *new_url = NULL; @@ -1013,11 +983,11 @@ DilloUrl *DilloHtmlForm::buildQueryUrl(DilloHtmlInput *input) _MSG("DilloHtmlForm::buildQueryUrl: action=%s\n",URL_STR_(action)); - if (num_submit_buttons > 0) { - if ((input->type == DILLO_HTML_INPUT_SUBMIT) || - (input->type == DILLO_HTML_INPUT_IMAGE) || - (input->type == DILLO_HTML_INPUT_BUTTON_SUBMIT)) { - active_submit = input; + if (active_input) { + if ((active_input->type == DILLO_HTML_INPUT_SUBMIT) || + (active_input->type == DILLO_HTML_INPUT_IMAGE) || + (active_input->type == DILLO_HTML_INPUT_BUTTON_SUBMIT)) { + active_submit = active_input; } } @@ -1031,7 +1001,7 @@ DilloUrl *DilloHtmlForm::buildQueryUrl(DilloHtmlInput *input) /* new_url keeps the dStr and sets DataStr to NULL */ a_Url_set_data(new_url, &DataStr); a_Url_set_flags(new_url, URL_FLAGS(new_url) | URL_Post); - if (enc == DILLO_HTML_ENC_MULTIPART) + if (content_type == DILLO_HTML_ENC_MULTIPART) a_Url_set_flags(new_url, URL_FLAGS(new_url) | URL_MultipartEnc); } else { /* remove <fragment> and <query> sections if present */ @@ -1063,11 +1033,11 @@ Dstr *DilloHtmlForm::buildQueryData(DilloHtmlInput *active_submit) { Dstr *DataStr = NULL; char *boundary = NULL; - iconv_t encoder = (iconv_t) -1; + iconv_t char_encoder = (iconv_t) -1; if (submit_charset && dStrcasecmp(submit_charset, "UTF-8")) { - encoder = iconv_open(submit_charset, "UTF-8"); - if (encoder == (iconv_t) -1) { + char_encoder = iconv_open(submit_charset, "UTF-8"); + if (char_encoder == (iconv_t) -1) { MSG_WARN("Cannot convert to character encoding '%s'\n", submit_charset); } else { @@ -1075,95 +1045,95 @@ Dstr *DilloHtmlForm::buildQueryData(DilloHtmlInput *active_submit) } } - if (enc == DILLO_HTML_ENC_MULTIPART) { - if (!(boundary = makeMultipartBoundary(encoder, active_submit))) + if (content_type == DILLO_HTML_ENC_MULTIPART) { + if (!(boundary = makeMultipartBoundary(char_encoder, active_submit))) MSG_ERR("Cannot generate multipart/form-data boundary.\n"); } - if ((enc == DILLO_HTML_ENC_URLENCODING) || (boundary != NULL)) { + if ((content_type == DILLO_HTML_ENC_URLENCODED) || (boundary != NULL)) { Dlist *values = dList_new(5); DataStr = dStr_sized_new(4096); - for (int input_idx = 0; input_idx < inputs->size(); input_idx++) { - DilloHtmlInput *input = inputs->get (input_idx); + for (int i = 0; i < inputs->size(); i++) { + DilloHtmlInput *input = inputs->get (i); Dstr *name = dStr_new(input->name); bool is_active_submit = (input == active_submit); + int valcount; - name = encodeText(encoder, &name); + name = encodeText(char_encoder, &name); input->appendValuesTo(values, is_active_submit); - if (input->type == DILLO_HTML_INPUT_FILE && dList_length(values) >0) { - if (dList_length(values) > 1) - MSG_WARN("multiple files per form control not supported\n"); - Dstr *file = (Dstr *) dList_nth_data(values, 0); - dList_remove(values, file); - - /* Get filename and encode it. Do not encode file contents. */ - dw::core::ui::LabelButtonResource *lbr = - (dw::core::ui::LabelButtonResource*) - input->embed->getResource(); - const char *filename = lbr->getLabel(); - if (filename[0] && strcmp(filename, input->init_str)) { - char *p = strrchr(filename, '/'); - if (p) - filename = p + 1; /* don't reveal path */ - Dstr *dfilename = dStr_new(filename); - dfilename = encodeText(encoder, &dfilename); - appendInputMultipartFiles(DataStr, boundary, name->str, - file, dfilename->str); - dStr_free(dfilename, 1); - } - dStr_free(file, 1); - } else if (input->type == DILLO_HTML_INPUT_INDEX) { - Dstr *val = (Dstr *) dList_nth_data(values, 0); - dList_remove(values, val); - val = encodeText(encoder, &val); - urlencodeAppend(DataStr, val->str); - dStr_free(val, 1); - } else if (input->type == DILLO_HTML_INPUT_IMAGE) { - if (dList_length(values) > 0) { + if ((valcount = dList_length(values)) > 0) { + if (input->type == DILLO_HTML_INPUT_FILE) { + if (valcount > 1) + MSG_WARN("multiple files per form control not supported\n"); + Dstr *file = (Dstr *) dList_nth_data(values, 0); + dList_remove(values, file); + + /* Get filename and encode it. Do not encode file contents. */ + LabelButtonResource *lbr = + (LabelButtonResource*) input->embed->getResource(); + const char *filename = lbr->getLabel(); + if (filename[0] && strcmp(filename, input->init_str)) { + const char *p = strrchr(filename, '/'); + if (p) + filename = p + 1; /* don't reveal path */ + Dstr *dfilename = dStr_new(filename); + dfilename = encodeText(char_encoder, &dfilename); + filesInputMultipartAppend(DataStr, boundary, name->str, + file, dfilename->str); + dStr_free(dfilename, 1); + } + dStr_free(file, 1); + } else if (input->type == DILLO_HTML_INPUT_INDEX) { + /* no name */ + Dstr *val = (Dstr *) dList_nth_data(values, 0); + dList_remove(values, val); + val = encodeText(char_encoder, &val); + strUrlencodeAppend(DataStr, val->str); + dStr_free(val, 1); + } else if (input->type == DILLO_HTML_INPUT_IMAGE) { Dstr *x, *y; x = (Dstr *) dList_nth_data(values, 0); dList_remove(values, x); y = (Dstr *) dList_nth_data(values, 0); dList_remove(values, y); - if (enc == DILLO_HTML_ENC_URLENCODING) - appendClickposUrlencode(DataStr, name, x, y); - else if (enc == DILLO_HTML_ENC_MULTIPART) - appendClickposMultipart(DataStr, boundary, name, x, y); + if (content_type == DILLO_HTML_ENC_URLENCODED) + imageInputUrlencodeAppend(DataStr, name, x, y); + else if (content_type == DILLO_HTML_ENC_MULTIPART) + imageInputMultipartAppend(DataStr, boundary, name, x, y); dStr_free(x, 1); dStr_free(y, 1); - } - } else { - int length = dList_length(values), i; - for (i = 0; i < length; i++) { - Dstr *val = (Dstr *) dList_nth_data(values, 0); - dList_remove(values, val); - val = encodeText(encoder, &val); - if (enc == DILLO_HTML_ENC_URLENCODING) - appendInputUrlencode(DataStr, name->str, val->str); - else if (enc == DILLO_HTML_ENC_MULTIPART) - appendInputMultipart(DataStr, boundary, - name->str, val->str); - dStr_free(val, 1); + } else { + for (int j = 0; j < valcount; j++) { + Dstr *val = (Dstr *) dList_nth_data(values, 0); + dList_remove(values, val); + val = encodeText(char_encoder, &val); + if (content_type == DILLO_HTML_ENC_URLENCODED) + inputUrlencodeAppend(DataStr, name->str, val->str); + else if (content_type == DILLO_HTML_ENC_MULTIPART) + inputMultipartAppend(DataStr, boundary, name->str, + val->str); + dStr_free(val, 1); + } } } dStr_free(name, 1); } if (DataStr->len > 0) { - if (enc == DILLO_HTML_ENC_URLENCODING) { + if (content_type == DILLO_HTML_ENC_URLENCODED) { if (DataStr->str[DataStr->len - 1] == '&') dStr_truncate(DataStr, DataStr->len - 1); - } else if (enc == DILLO_HTML_ENC_MULTIPART) { + } else if (content_type == DILLO_HTML_ENC_MULTIPART) { dStr_append(DataStr, "--"); } } dList_free(values); } dFree(boundary); - if (encoder != (iconv_t) -1) - (void)iconv_close(encoder); + if (char_encoder != (iconv_t) -1) + (void)iconv_close(char_encoder); return DataStr; } @@ -1171,7 +1141,7 @@ Dstr *DilloHtmlForm::buildQueryData(DilloHtmlInput *active_submit) * Generate a boundary string for use in separating the parts of a * multipart/form-data submission. */ -char *DilloHtmlForm::makeMultipartBoundary(iconv_t encoder, +char *DilloHtmlForm::makeMultipartBoundary(iconv_t char_encoder, DilloHtmlInput *active_submit) { const int max_tries = 10; @@ -1181,25 +1151,25 @@ char *DilloHtmlForm::makeMultipartBoundary(iconv_t encoder, char *ret = NULL; /* fill DataStr with names, filenames, and values */ - for (int input_idx = 0; input_idx < inputs->size(); input_idx++) { + for (int i = 0; i < inputs->size(); i++) { Dstr *dstr; - DilloHtmlInput *input = inputs->get (input_idx); + DilloHtmlInput *input = inputs->get (i); bool is_active_submit = (input == active_submit); input->appendValuesTo(values, is_active_submit); if (input->name) { dstr = dStr_new(input->name); - dstr = encodeText(encoder, &dstr); + dstr = encodeText(char_encoder, &dstr); dStr_append_l(DataStr, dstr->str, dstr->len); dStr_free(dstr, 1); } if (input->type == DILLO_HTML_INPUT_FILE) { - dw::core::ui::LabelButtonResource *lbr = - (dw::core::ui::LabelButtonResource*)input->embed->getResource(); + LabelButtonResource *lbr = + (LabelButtonResource*)input->embed->getResource(); const char *filename = lbr->getLabel(); if (filename[0] && strcmp(filename, input->init_str)) { dstr = dStr_new(filename); - dstr = encodeText(encoder, &dstr); + dstr = encodeText(char_encoder, &dstr); dStr_append_l(DataStr, dstr->str, dstr->len); dStr_free(dstr, 1); } @@ -1209,7 +1179,7 @@ char *DilloHtmlForm::makeMultipartBoundary(iconv_t encoder, dstr = (Dstr *) dList_nth_data(values, 0); dList_remove(values, dstr); if (input->type != DILLO_HTML_INPUT_FILE) - dstr = encodeText(encoder, &dstr); + dstr = encodeText(char_encoder, &dstr); dStr_append_l(DataStr, dstr->str, dstr->len); dStr_free(dstr, 1); } @@ -1233,9 +1203,9 @@ char *DilloHtmlForm::makeMultipartBoundary(iconv_t encoder, /* * Pass input text through character set encoder. * Return value: same input Dstr if no encoding is needed. - new Dstr when encoding (input Dstr is freed). + * new Dstr when encoding (input Dstr is freed). */ -Dstr *DilloHtmlForm::encodeText(iconv_t encoder, Dstr **input) +Dstr *DilloHtmlForm::encodeText(iconv_t char_encoder, Dstr **input) { int rc = 0; Dstr *output; @@ -1245,7 +1215,7 @@ Dstr *DilloHtmlForm::encodeText(iconv_t encoder, Dstr **input) size_t inLeft, outRoom; bool bad_chars = false; - if ((encoder == (iconv_t) -1) || *input == NULL || (*input)->len == 0) + if ((char_encoder == (iconv_t) -1) || *input == NULL || (*input)->len == 0) return *input; output = dStr_new(""); @@ -1258,7 +1228,7 @@ Dstr *DilloHtmlForm::encodeText(iconv_t encoder, Dstr **input) outPtr = buffer; outRoom = bufsize; - rc = iconv(encoder, &inPtr, &inLeft, &outPtr, &outRoom); + rc = iconv(char_encoder, &inPtr, &inLeft, &outPtr, &outRoom); // iconv() on success, number of bytes converted // -1, errno == EILSEQ illegal byte sequence found @@ -1298,28 +1268,27 @@ Dstr *DilloHtmlForm::encodeText(iconv_t encoder, Dstr **input) return output; } - + /* - * Urlencode 'val' and append it to 'str' + * Urlencode 'str' and append it to 'dstr' */ -void DilloHtmlForm::urlencodeAppend(Dstr *str, const char *val) +void DilloHtmlForm::strUrlencodeAppend(Dstr *dstr, const char *str) { - char *enc_val = a_Url_encode_hex_str(val); - dStr_append(str, enc_val); - dFree(enc_val); + char *encoded = a_Url_encode_hex_str(str); + dStr_append(dstr, encoded); + dFree(encoded); } /* * Append a name-value pair to url data using url encoding. */ -void DilloHtmlForm::appendInputUrlencode(Dstr *data, - const char *name, +void DilloHtmlForm::inputUrlencodeAppend(Dstr *data, const char *name, const char *value) { if (name && name[0]) { - urlencodeAppend(data, name); + strUrlencodeAppend(data, name); dStr_append_c(data, '='); - urlencodeAppend(data, value); + strUrlencodeAppend(data, value); dStr_append_c(data, '&'); } } @@ -1328,7 +1297,7 @@ void DilloHtmlForm::appendInputUrlencode(Dstr *data, * Append files to URL data using multipart encoding. * Currently only accepts one file. */ -void DilloHtmlForm::appendInputMultipartFiles(Dstr* data, +void DilloHtmlForm::filesInputMultipartAppend(Dstr* data, const char *boundary, const char *name, Dstr *file, @@ -1379,7 +1348,7 @@ void DilloHtmlForm::appendInputMultipartFiles(Dstr* data, /* * Append a name-value pair to url data using multipart encoding. */ -void DilloHtmlForm::appendInputMultipart(Dstr *data, +void DilloHtmlForm::inputMultipartAppend(Dstr *data, const char *boundary, const char *name, const char *value) @@ -1403,13 +1372,13 @@ void DilloHtmlForm::appendInputMultipart(Dstr *data, /* * Append an image button click position to url data using url encoding. */ -void DilloHtmlForm::appendClickposUrlencode(Dstr *data, Dstr *name, Dstr *x, - Dstr *y) +void DilloHtmlForm::imageInputUrlencodeAppend(Dstr *data, Dstr *name, Dstr *x, + Dstr *y) { if (name->len) { - urlencodeAppend(data, name->str); + strUrlencodeAppend(data, name->str); dStr_sprintfa(data, ".x=%s&", x->str); - urlencodeAppend(data, name->str); + strUrlencodeAppend(data, name->str); dStr_sprintfa(data, ".y=%s&", y->str); } else dStr_sprintfa(data, "x=%s&y=%s&", x->str, y->str); @@ -1418,8 +1387,8 @@ void DilloHtmlForm::appendClickposUrlencode(Dstr *data, Dstr *name, Dstr *x, /* * Append an image button click position to url data using multipart encoding. */ -void DilloHtmlForm::appendClickposMultipart(Dstr *data, const char *boundary, - Dstr *name, Dstr *x, Dstr *y) +void DilloHtmlForm::imageInputMultipartAppend(Dstr *data, const char *boundary, + Dstr *name, Dstr *x, Dstr *y) { int orig_len = name->len; @@ -1427,10 +1396,10 @@ void DilloHtmlForm::appendClickposMultipart(Dstr *data, const char *boundary, dStr_append_c(name, '.'); dStr_append_c(name, 'x'); - appendInputMultipart(data, boundary, name->str, x->str); + inputMultipartAppend(data, boundary, name->str, x->str); dStr_truncate(name, name->len - 1); dStr_append_c(name, 'y'); - appendInputMultipart(data, boundary, name->str, y->str); + inputMultipartAppend(data, boundary, name->str, y->str); dStr_truncate(name, orig_len); } @@ -1446,11 +1415,33 @@ void DilloHtmlForm::reset () } /* + * Show/hide "hidden" form controls + */ +void DilloHtmlForm::display_hiddens(bool display) +{ + int size = inputs->size(); + for (int i = 0; i < size; i++) { + DilloHtmlInput *input = inputs->get(i); + if (input->type == DILLO_HTML_INPUT_HIDDEN) { + input->embed->setDisplayed(display); + } + } + showing_hiddens = display; +} + +void DilloHtmlForm::setEnabled(bool enabled) +{ + for (int i = 0; i < inputs->size(); i++) + inputs->get(i)->setEnabled(enabled); +} + +/* * Add a new input. */ void DilloHtmlForm::addInput(DilloHtmlInput *input, DilloHtmlInputType type) { input->connectTo (form_receiver); + input->setEnabled (enabled); int ni = inputs->size (); inputs->increase (); inputs->set (ni,input); @@ -1459,17 +1450,13 @@ void DilloHtmlForm::addInput(DilloHtmlInput *input, DilloHtmlInputType type) if (type == DILLO_HTML_INPUT_PASSWORD || type == DILLO_HTML_INPUT_TEXT) { num_entry_fields++; - } else if (type == DILLO_HTML_INPUT_SUBMIT || - type == DILLO_HTML_INPUT_BUTTON_SUBMIT || - type == DILLO_HTML_INPUT_IMAGE) { - num_submit_buttons++; } } /* * Return the input with a given resource. */ -DilloHtmlInput *DilloHtmlForm::getInput (dw::core::ui::Resource *resource) +DilloHtmlInput *DilloHtmlForm::getInput (Resource *resource) { for (int idx = 0; idx < inputs->size(); idx++) { DilloHtmlInput *input = inputs->get(idx); @@ -1495,22 +1482,22 @@ DilloHtmlInput *DilloHtmlForm::getRadioInput (const char *name) } /* - * DilloHtmlReceiver + * DilloHtmlReceiver * * TODO: Currently there's "clicked" for buttons, we surely need "enter" for * textentries, and maybe the "mouseover, ...." set for Javascript. */ -void DilloHtmlReceiver::activate (dw::core::ui::Resource *resource) +void DilloHtmlReceiver::activate (Resource *resource) { - form->eventHandler(resource); + form->eventHandler(resource, NULL); } /* * Enter a form control, as in "onmouseover". * For _pressing_ enter in a text control, see activate(). */ -void DilloHtmlReceiver::enter (dw::core::ui::Resource *resource) +void DilloHtmlReceiver::enter (Resource *resource) { DilloHtml *html = form->html; DilloHtmlInput *input = form->getInput(resource); @@ -1532,29 +1519,27 @@ void DilloHtmlReceiver::enter (dw::core::ui::Resource *resource) /* * Leave a form control, or "onmouseout". */ -void DilloHtmlReceiver::leave (dw::core::ui::Resource *resource) +void DilloHtmlReceiver::leave (Resource *resource) { DilloHtml *html = form->html; a_UIcmd_set_msg(html->bw, ""); } -void DilloHtmlReceiver::clicked (dw::core::ui::ButtonResource *resource, - int buttonNo, int x, int y) +void DilloHtmlReceiver::clicked (Resource *resource, + EventButton *event) { -// form->eventHandler(resource, x, y); + form->eventHandler(resource, event); } /* - * DilloHtmlInput + * DilloHtmlInput */ /* - * Constructor + * Constructor */ -DilloHtmlInput::DilloHtmlInput (DilloHtmlInputType type2, - dw::core::ui::Embed *embed2, - const char *name2, - const char *init_str2, +DilloHtmlInput::DilloHtmlInput (DilloHtmlInputType type2, Embed *embed2, + const char *name2, const char *init_str2, bool init_val2) { type = type2; @@ -1576,7 +1561,7 @@ DilloHtmlInput::DilloHtmlInput (DilloHtmlInputType type2, } /* - * Destructor + * Destructor */ DilloHtmlInput::~DilloHtmlInput () { @@ -1592,61 +1577,57 @@ DilloHtmlInput::~DilloHtmlInput () */ void DilloHtmlInput::connectTo(DilloHtmlReceiver *form_receiver) { - dw::core::ui::Resource *resource = NULL; - if (embed) - resource = embed->getResource (); - switch (type) { - case DILLO_HTML_INPUT_UNKNOWN: - case DILLO_HTML_INPUT_HIDDEN: - case DILLO_HTML_INPUT_CHECKBOX: - case DILLO_HTML_INPUT_RADIO: - case DILLO_HTML_INPUT_BUTTON: - case DILLO_HTML_INPUT_TEXTAREA: - case DILLO_HTML_INPUT_SELECT: - case DILLO_HTML_INPUT_SEL_LIST: - // do nothing - break; - case DILLO_HTML_INPUT_TEXT: - case DILLO_HTML_INPUT_PASSWORD: - case DILLO_HTML_INPUT_INDEX: - case DILLO_HTML_INPUT_SUBMIT: - case DILLO_HTML_INPUT_RESET: - case DILLO_HTML_INPUT_BUTTON_SUBMIT: - case DILLO_HTML_INPUT_BUTTON_RESET: - case DILLO_HTML_INPUT_IMAGE: - case DILLO_HTML_INPUT_FILE: - if (resource) + Resource *resource; + if (embed && (resource = embed->getResource())) { + resource->connectClicked (form_receiver); + if (type == DILLO_HTML_INPUT_SUBMIT || + type == DILLO_HTML_INPUT_RESET || + type == DILLO_HTML_INPUT_BUTTON_SUBMIT || + type == DILLO_HTML_INPUT_BUTTON_RESET || + type == DILLO_HTML_INPUT_IMAGE || + type == DILLO_HTML_INPUT_FILE || + type == DILLO_HTML_INPUT_TEXT || + type == DILLO_HTML_INPUT_PASSWORD || + type == DILLO_HTML_INPUT_INDEX) { resource->connectActivate (form_receiver); - break; + } } } /* - * Activate a form + * Activate a form */ -void DilloHtmlInput::activate(DilloHtmlForm *form, bool force_submit) +void DilloHtmlInput::activate(DilloHtmlForm *form, int num_entry_fields, + EventButton *event) { switch (type) { - case DILLO_HTML_INPUT_TEXT: - case DILLO_HTML_INPUT_PASSWORD: - if (force_submit) - form->submit (this); - break; case DILLO_HTML_INPUT_FILE: readFile (form->html->bw); break; case DILLO_HTML_INPUT_RESET: case DILLO_HTML_INPUT_BUTTON_RESET: - form->reset (); + form->reset(); + break; + case DILLO_HTML_INPUT_TEXT: + case DILLO_HTML_INPUT_PASSWORD: + if (!(prefs.enterpress_forces_submit || num_entry_fields == 1)) { + break; + } else { + /* fall through */ + } + case DILLO_HTML_INPUT_SUBMIT: + case DILLO_HTML_INPUT_BUTTON_SUBMIT: + case DILLO_HTML_INPUT_IMAGE: + case DILLO_HTML_INPUT_INDEX: + form->submit(this, event); break; default: - form->submit (this); break; } } /* - * Read a file into cache + * Read a file into cache */ void DilloHtmlInput::readFile (BrowserWindow *bw) { @@ -1657,8 +1638,7 @@ void DilloHtmlInput::readFile (BrowserWindow *bw) file_data = a_Misc_file2dstr(filename); if (file_data) { a_UIcmd_set_msg(bw, "File loaded."); - dw::core::ui::LabelButtonResource *lbr = - (dw::core::ui::LabelButtonResource*)embed->getResource(); + LabelButtonResource *lbr = (LabelButtonResource*)embed->getResource(); lbr->setLabel(filename); } else { a_UIcmd_set_msg(bw, "ERROR: can't load: %s", filename); @@ -1675,24 +1655,24 @@ void DilloHtmlInput::appendValuesTo(Dlist *values, bool is_active_submit) case DILLO_HTML_INPUT_TEXT: case DILLO_HTML_INPUT_PASSWORD: case DILLO_HTML_INPUT_INDEX: + case DILLO_HTML_INPUT_HIDDEN: { - dw::core::ui::EntryResource *entryres = - (dw::core::ui::EntryResource*)embed->getResource(); + EntryResource *entryres = (EntryResource*)embed->getResource(); dList_append(values, dStr_new(entryres->getText())); } break; case DILLO_HTML_INPUT_TEXTAREA: { - dw::core::ui::MultiLineTextResource *textres = - (dw::core::ui::MultiLineTextResource*)embed->getResource(); + MultiLineTextResource *textres = + (MultiLineTextResource*)embed->getResource(); dList_append(values, dStr_new(textres->getText())); } break; case DILLO_HTML_INPUT_CHECKBOX: case DILLO_HTML_INPUT_RADIO: { - dw::core::ui::ToggleButtonResource *cb_r = - (dw::core::ui::ToggleButtonResource*)embed->getResource(); + ToggleButtonResource *cb_r = + (ToggleButtonResource*)embed->getResource(); if (name && init_str && cb_r->isActivated()) { dList_append(values, dStr_new(init_str)); } @@ -1703,21 +1683,16 @@ void DilloHtmlInput::appendValuesTo(Dlist *values, bool is_active_submit) if (is_active_submit) dList_append(values, dStr_new(init_str)); break; - case DILLO_HTML_INPUT_HIDDEN: - dList_append(values, dStr_new(init_str)); - break; case DILLO_HTML_INPUT_SELECT: case DILLO_HTML_INPUT_SEL_LIST: - { // brackets for compiler happiness. - dw::core::ui::SelectionResource *sel_res = - (dw::core::ui::SelectionResource*)embed->getResource(); + { + SelectionResource *sel_res = (SelectionResource*)embed->getResource(); select->appendValuesTo (values, sel_res); } break; case DILLO_HTML_INPUT_FILE: { - dw::core::ui::LabelButtonResource *lbr = - (dw::core::ui::LabelButtonResource*)embed->getResource(); + LabelButtonResource *lbr = (LabelButtonResource*)embed->getResource(); const char *filename = lbr->getLabel(); if (filename[0] && strcmp(filename, init_str)) { if (file_data) { @@ -1732,8 +1707,8 @@ void DilloHtmlInput::appendValuesTo(Dlist *values, bool is_active_submit) break; case DILLO_HTML_INPUT_IMAGE: if (is_active_submit) { - dw::core::ui::ComplexButtonResource *cbr = - (dw::core::ui::ComplexButtonResource*)embed->getResource(); + ComplexButtonResource *cbr = + (ComplexButtonResource*)embed->getResource(); Dstr *strX = dStr_new(""); Dstr *strY = dStr_new(""); dStr_sprintf(strX, "%d", cbr->getClickX()); @@ -1755,17 +1730,18 @@ void DilloHtmlInput::reset () switch (type) { case DILLO_HTML_INPUT_TEXT: case DILLO_HTML_INPUT_PASSWORD: + case DILLO_HTML_INPUT_INDEX: + case DILLO_HTML_INPUT_HIDDEN: { - dw::core::ui::EntryResource *entryres = - (dw::core::ui::EntryResource*)embed->getResource(); + EntryResource *entryres = (EntryResource*)embed->getResource(); entryres->setText(init_str ? init_str : ""); } break; case DILLO_HTML_INPUT_CHECKBOX: case DILLO_HTML_INPUT_RADIO: { - dw::core::ui::ToggleButtonResource *tb_r = - (dw::core::ui::ToggleButtonResource*)embed->getResource(); + ToggleButtonResource *tb_r = + (ToggleButtonResource*)embed->getResource(); tb_r->setActivated(init_val); } break; @@ -1801,15 +1777,14 @@ void DilloHtmlInput::reset () break; case DILLO_HTML_INPUT_TEXTAREA: if (init_str != NULL) { - dw::core::ui::MultiLineTextResource *textres = - (dw::core::ui::MultiLineTextResource*)embed->getResource(); + MultiLineTextResource *textres = + (MultiLineTextResource*)embed->getResource(); textres->setText(init_str ? init_str : ""); } break; case DILLO_HTML_INPUT_FILE: { - dw::core::ui::LabelButtonResource *lbr = - (dw::core::ui::LabelButtonResource*)embed->getResource(); + LabelButtonResource *lbr = (LabelButtonResource*)embed->getResource(); lbr->setLabel(init_str); } break; @@ -1819,11 +1794,11 @@ void DilloHtmlInput::reset () } /* - * DilloHtmlSelect + * DilloHtmlSelect */ /* - * Constructor + * Constructor */ DilloHtmlSelect::DilloHtmlSelect () { @@ -1831,7 +1806,7 @@ DilloHtmlSelect::DilloHtmlSelect () } /* - * Destructor + * Destructor */ DilloHtmlSelect::~DilloHtmlSelect () { @@ -1872,7 +1847,7 @@ void DilloHtmlSelect::ensureSelection() } } -void DilloHtmlSelect::addOptionsTo (dw::core::ui::SelectionResource *res) +void DilloHtmlSelect::addOptionsTo (SelectionResource *res) { int size = options->size (); for (int i = 0; i < size; i++) { @@ -1881,8 +1856,7 @@ void DilloHtmlSelect::addOptionsTo (dw::core::ui::SelectionResource *res) } } -void DilloHtmlSelect::appendValuesTo (Dlist *values, - dw::core::ui::SelectionResource *res) +void DilloHtmlSelect::appendValuesTo (Dlist *values, SelectionResource *res) { int size = options->size (); for (int i = 0; i < size; i++) { @@ -1895,11 +1869,11 @@ void DilloHtmlSelect::appendValuesTo (Dlist *values, } /* - * DilloHtmlOption + * DilloHtmlOption */ /* - * Constructor + * Constructor */ DilloHtmlOption::DilloHtmlOption (char *value2, bool selected2, @@ -1912,7 +1886,7 @@ DilloHtmlOption::DilloHtmlOption (char *value2, } /* - * Destructor + * Destructor */ DilloHtmlOption::~DilloHtmlOption () { @@ -1921,48 +1895,38 @@ DilloHtmlOption::~DilloHtmlOption () } /* - * Utilities + * Utilities */ /* * Create input image for the form */ -static dw::core::ui::Embed *Html_input_image(DilloHtml *html, - const char *tag, int tagsize) +static Embed *Html_input_image(DilloHtml *html, const char *tag, int tagsize) { const char *attrbuf; - StyleAttrs style_attrs; DilloImage *Image; - dw::core::ui::Embed *button = NULL; + Embed *button = NULL; DilloUrl *url = NULL; - + if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "src")) && (url = a_Html_url_new(html, attrbuf, NULL, 0))) { - style_attrs = *S_TOP(html)->style; - style_attrs.cursor = CURSOR_POINTER; - style_attrs.backgroundColor = - style::Color::createShaded(HT2LT(html), S_TOP(html)->current_bg_color); + + html->styleEngine->setPseudoLink (); /* create new image and add it to the button */ - if ((Image = a_Html_add_new_image(html, tag, tagsize, url, &style_attrs, - false))) { - Style *style = Style::create (HT2LT(html), &style_attrs); - IM2DW(Image)->setStyle (style); - dw::core::ui::ComplexButtonResource *complex_b_r = - HT2LT(html)->getResourceFactory()->createComplexButtonResource( - IM2DW(Image), false); - button = new dw::core::ui::Embed(complex_b_r); - DW2TB(html->dw)->addWidget (button, style); + if ((Image = a_Html_image_new(html, tag, tagsize, url))) { + IM2DW(Image)->setStyle (html->styleEngine->backgroundStyle ()); + ResourceFactory *factory = HT2LT(html)->getResourceFactory(); + ComplexButtonResource *complex_b_r = + factory->createComplexButtonResource(IM2DW(Image), false); + button = new Embed(complex_b_r); + HT2TB(html)->addWidget (button, html->styleEngine->style ()); // gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */ - style->unref(); - /* a right button press brings up the image menu */ - html->connectSignals((Widget*)Image->dw); } else { a_Url_free(url); } } - if (!button) MSG("Html_input_image: unable to create image submit.\n"); return button; @@ -1978,7 +1942,6 @@ static void Html_option_finish(DilloHtml *html) input->type == DILLO_HTML_INPUT_SEL_LIST) { DilloHtmlOption *option = input->select->getCurrentOption (); - option->content = - a_Html_parse_entities(html, html->Stash->str, html->Stash->len); + option->content = dStrndup(html->Stash->str, html->Stash->len); } } diff --git a/src/form.hh b/src/form.hh index 8375ba50..a54cde56 100644 --- a/src/form.hh +++ b/src/form.hh @@ -4,7 +4,7 @@ #include "url.h" /* - * Typedefs + * Typedefs */ typedef enum { @@ -14,12 +14,12 @@ typedef enum { } DilloHtmlMethod; typedef enum { - DILLO_HTML_ENC_URLENCODING, + DILLO_HTML_ENC_URLENCODED, DILLO_HTML_ENC_MULTIPART } DilloHtmlEnc; /* - * Classes + * Classes */ class DilloHtmlForm; @@ -27,20 +27,24 @@ class DilloHtmlInput; class DilloHtml; /* - * Form API + * Form API */ DilloHtmlForm *a_Html_form_new(DilloHtml *html, DilloHtmlMethod method, const DilloUrl *action, DilloHtmlEnc enc, - const char *charset); + const char *charset, bool enabled); void a_Html_form_delete(DilloHtmlForm* form); void a_Html_input_delete(DilloHtmlInput* input); +void a_Html_form_submit2(void *v_form); +void a_Html_form_reset2(void *v_form); +void a_Html_form_display_hiddens2(void *v_form, bool display); + /* - * Form parsing functions + * Form parsing functions */ void Html_tag_open_form(DilloHtml *html, const char *tag, int tagsize); @@ -67,10 +67,8 @@ #include "msg.h" #include "image.hh" -#include "web.hh" #include "cache.h" #include "dicache.h" -#include "prefs.h" #define INTERLACE 0x40 #define LOCALCOLORMAP 0x80 @@ -147,54 +145,15 @@ 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) +void *a_Gif_new(DilloImage *Image, DilloUrl *url, int version) { DilloGif *gif = dMalloc(sizeof(DilloGif)); + _MSG("a_Gif_new: gif=%p\n", gif); gif->Image = Image; gif->url = url; @@ -216,15 +175,38 @@ static DilloGif *Gif_new(DilloImage *Image, DilloUrl *url, int version) } /* + * Free the gif-decoding data structure. + */ +static void Gif_free(DilloGif *gif) +{ + int i; + + _MSG("Gif_free: gif=%p\n", gif); + + 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); +} + +/* * 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) +void a_Gif_callback(int Op, void *data) { - if (Op) - Gif_close(Client->CbData, Client); - else + if (Op == CA_Send) { + CacheClient_t *Client = data; Gif_write(Client->CbData, Client->Buf, Client->BufSize); + } else if (Op == CA_Close) { + CacheClient_t *Client = data; + Gif_close(Client->CbData, Client); + } else if (Op == CA_Abort) { + Gif_free(data); + } } /* @@ -259,20 +241,9 @@ static void Gif_write(DilloGif *gif, void *Buf, uint_t BufSize) */ static void Gif_close(DilloGif *gif, CacheClient_t *Client) { - int i; - - _MSG("destroy gif %p\n", gif); - + _MSG("Gif_close: 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_free(gif); } @@ -418,7 +389,7 @@ static void Gif_lwz_init(DilloGif *gif) */ static void Gif_emit_line(DilloGif *gif, const uchar_t *linebuf) { - a_Dicache_write(gif->Image, gif->url, gif->version, linebuf, gif->y); + a_Dicache_write(gif->url, gif->version, linebuf, gif->y); if (gif->Flags & INTERLACE) { switch (gif->pass) { case 0: @@ -841,6 +812,16 @@ static size_t Gif_do_img_desc(DilloGif *gif, void *Buf, gif->Width = LM_to_uint(buf[4], buf[5]); gif->Height = LM_to_uint(buf[6], buf[7]); + + /* check max image size */ + if (gif->Width <= 0 || gif->Height <= 0 || + gif->Width > IMAGE_MAX_AREA / gif->Height) { + MSG("Gif_do_img_desc: suspicious image size request %ux%u\n", + gif->Width, gif->Height); + gif->state = 999; + return 0; + } + gif->linebuf = dMalloc(gif->Width); a_Dicache_set_parms(gif->url, gif->version, gif->Image, @@ -1045,4 +1026,9 @@ static size_t Gif_process_bytes(DilloGif *gif, const uchar_t *ibuf, return bufsize - tmp_bufsize; } +#else /* ENABLE_GIF */ + +void *a_Gif_new() { return 0; } +void a_Gif_callback() { return; } + #endif /* ENABLE_GIF */ diff --git a/src/history.c b/src/history.c index 4b8fb426..15ab19db 100644 --- a/src/history.c +++ b/src/history.c @@ -75,22 +75,9 @@ int a_History_add_url(DilloUrl *url) } /* - * 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 field (by index) */ -DilloUrl *a_History_get_url(int idx) +const DilloUrl *a_History_get_url(int idx) { _MSG("a_History_get_url: "); /* History_show(); */ @@ -137,11 +124,32 @@ const char *a_History_get_title_by_url(const DilloUrl *url, int force) return NULL; } +/* + * Set the page-title for a given URL + */ +void a_History_set_title_by_url(const DilloUrl *url, const char *title) +{ + int i; + + dReturn_if (url == NULL); + + for (i = history_size - 1; i >= 0; --i) + if (a_Url_cmp(url, history[i].url) == 0) + break; + + if (i >= 0) { + dFree(history[i].title); + history[i].title = dStrdup(title); + } else { + MSG_ERR("a_History_set_title_by_url: %s not found\n", URL_STR(url)); + } +} + /* * Free all the memory used by this module */ -void a_History_free() +void a_History_freeall() { int i; diff --git a/src/history.h b/src/history.h index da520c87..beb0cdf9 100644 --- a/src/history.h +++ b/src/history.h @@ -10,11 +10,11 @@ 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); +void a_History_set_title_by_url(const DilloUrl *url, const char *title); +const 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(const DilloUrl *url, int force); -void a_History_free(void); +void a_History_freeall(void); #ifdef __cplusplus diff --git a/src/html.cc b/src/html.cc index 70466979..49d06da3 100644 --- a/src/html.cc +++ b/src/html.cc @@ -20,20 +20,17 @@ #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 */ - #include "bw.h" /* for BrowserWindow */ #include "msg.h" #include "binaryconst.h" #include "colors.h" +#include "utf8.hh" #include "misc.h" #include "uicmd.hh" #include "history.h" -#include "nav.h" #include "menu.hh" #include "prefs.h" #include "capi.h" @@ -49,7 +46,7 @@ #include "dw/ruler.hh" /*----------------------------------------------------------------------------- - * Defines + * Defines *---------------------------------------------------------------------------*/ /* Define to 1 to ignore white space immediately after an open tag, @@ -61,6 +58,7 @@ /*----------------------------------------------------------------------------- * Name spaces *---------------------------------------------------------------------------*/ +using namespace lout; using namespace dw; using namespace dw::core; using namespace dw::core::ui; @@ -106,24 +104,15 @@ static const char *Html_get_attr2(DilloHtml *html, int tagsize, const char *attrname, int tag_parsing_flags); -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_load_image(BrowserWindow *bw, DilloUrl *url, - DilloImage *image); +static bool Html_load_image(BrowserWindow *bw, DilloUrl *url, + const DilloUrl *requester, DilloImage *image); static void Html_callback(int Op, CacheClient_t *Client); -static int Html_tag_index(const char *tag); static void Html_tag_cleanup_at_close(DilloHtml *html, int TagIdx); /*----------------------------------------------------------------------------- * Local Data *---------------------------------------------------------------------------*/ -/* The following array of font sizes has to be _strictly_ increasing */ -static const int FontSizes[] = {10, 12, 14, 18, 22, 28}; -static const int FontSizesNum = 6; -static const int FontSizesBase = 2; - /* Parsing table structure */ typedef struct { const char *name; /* element name */ @@ -210,7 +199,7 @@ static void Html_free(void *data) /* * Used by the "Load images" page menuitem. - */ + */ void a_Html_load_images(void *v_html, DilloUrl *pattern) { DilloHtml *html = (DilloHtml*)v_html; @@ -219,6 +208,58 @@ void a_Html_load_images(void *v_html, DilloUrl *pattern) } /* + * Search for form + */ +static bool Html_contains_form(DilloHtml *html, void *v_form) +{ + for (int i = 0; i < html->forms->size(); i++) { + if (html->forms->get(i) == v_form) { + return true; + } + } + return false; +} + +/* + * Used by the "Submit form" form menuitem. + */ +void a_Html_form_submit(void *v_html, void *v_form) +{ + DilloHtml *html = (DilloHtml*)v_html; + + if (Html_contains_form(html, v_form)) { + /* it's still valid */ + a_Html_form_submit2(v_form); + } +} + +/* + * Used by the "Reset form" form menuitem. + */ +void a_Html_form_reset(void *v_html, void *v_form) +{ + DilloHtml *html = (DilloHtml*)v_html; + + if (Html_contains_form(html, v_form)) { + /* it's still valid */ + a_Html_form_reset2(v_form); + } +} + +/* + * Used by the "Show/Hide hiddens" form menuitem. + */ +void a_Html_form_display_hiddens(void *v_html, void *v_form, bool_t display) +{ + DilloHtml *html = (DilloHtml*)v_html; + + if (Html_contains_form(html, v_form)) { + /* it's still valid */ + a_Html_form_display_hiddens2(v_form, (display != 0)); + } +} + +/* * Set the URL data for image maps. */ static void Html_set_link_coordinates(DilloHtml *html, int link, int x, int y) @@ -244,42 +285,20 @@ static int Html_set_new_link(DilloHtml *html, DilloUrl **url) } /* - * Add a new image. + * Add a new image to our list. + * image is NULL if dillo will try to load the image immediately. */ -static int Html_add_new_linkimage(DilloHtml *html, - DilloUrl **url, DilloImage *image) +static void Html_add_new_htmlimage(DilloHtml *html, + DilloUrl **url, DilloImage *image) { - DilloLinkImage *li = dNew(DilloLinkImage, 1); - li->url = *url; - li->image = image; + DilloHtmlImage *hi = dNew(DilloHtmlImage, 1); + hi->url = *url; + hi->image = image; + a_Image_ref(image); - int ni = html->images->size(); + int n = html->images->size(); html->images->increase(); - html->images->set(ni, li); - return ni; -} - -/* - * Set the font at the top of the stack. BImask specifies which - * attributes in BI should be changed. - */ -void a_Html_set_top_font(DilloHtml *html, const 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) ? FONT_STYLE_ITALIC : FONT_STYLE_NORMAL; - - HTML_SET_TOP_ATTR (html, font, - Font::create (HT2LT(html), &font_attrs)); + html->images->set(n, hi); } /* @@ -287,25 +306,26 @@ void a_Html_set_top_font(DilloHtml *html, const char *name, int size, * sets the style at the top of the stack. */ void a_Html_tag_set_align_attr(DilloHtml *html, + CssPropertyList *props, const char *tag, int tagsize) { - const char *align, *charattr; + const char *align; if ((align = a_Html_get_attr(html, tag, tagsize, "align"))) { - Style *old_style = S_TOP(html)->style; - StyleAttrs style_attrs = *old_style; + TextAlignType textAlignType = TEXT_ALIGN_LEFT; if (dStrcasecmp (align, "left") == 0) - style_attrs.textAlign = TEXT_ALIGN_LEFT; + textAlignType = TEXT_ALIGN_LEFT; else if (dStrcasecmp (align, "right") == 0) - style_attrs.textAlign = TEXT_ALIGN_RIGHT; + textAlignType = TEXT_ALIGN_RIGHT; else if (dStrcasecmp (align, "center") == 0) - style_attrs.textAlign = TEXT_ALIGN_CENTER; + textAlignType = TEXT_ALIGN_CENTER; else if (dStrcasecmp (align, "justify") == 0) - style_attrs.textAlign = TEXT_ALIGN_JUSTIFY; + textAlignType = TEXT_ALIGN_JUSTIFY; +#if 0 else if (dStrcasecmp (align, "char") == 0) { /* TODO: Actually not supported for <p> etc. */ - style_attrs.textAlign = TEXT_ALIGN_STRING; + v.textAlign = TEXT_ALIGN_STRING; if ((charattr = a_Html_get_attr(html, tag, tagsize, "char"))) { if (charattr[0] == 0) /* TODO: ALIGN=" ", and even ALIGN="&32;" will reult in @@ -319,8 +339,8 @@ void a_Html_tag_set_align_attr(DilloHtml *html, /* TODO: Examine LANG attr of <html>. */ style_attrs.textAlignChar = '.'; } - S_TOP(html)->style = Style::create (HT2LT(html), &style_attrs); - old_style->unref (); +#endif + props->set (CSS_PROPERTY_TEXT_ALIGN, CSS_TYPE_ENUM, textAlignType); } } @@ -329,19 +349,22 @@ void a_Html_tag_set_align_attr(DilloHtml *html, * sets the style in style_attrs. Returns true when set. */ bool a_Html_tag_set_valign_attr(DilloHtml *html, const char *tag, - int tagsize, StyleAttrs *style_attrs) + int tagsize, CssPropertyList *props) { const char *attr; + VAlignType valign; if ((attr = a_Html_get_attr(html, tag, tagsize, "valign"))) { if (dStrcasecmp (attr, "top") == 0) - style_attrs->valign = VALIGN_TOP; + valign = VALIGN_TOP; else if (dStrcasecmp (attr, "bottom") == 0) - style_attrs->valign = VALIGN_BOTTOM; + valign = VALIGN_BOTTOM; else if (dStrcasecmp (attr, "baseline") == 0) - style_attrs->valign = VALIGN_BASELINE; + valign = VALIGN_BASELINE; else - style_attrs->valign = VALIGN_MIDDLE; + valign = VALIGN_MIDDLE; + + props->set (CSS_PROPERTY_VERTICAL_ALIGN, CSS_TYPE_ENUM, valign); return true; } else return false; @@ -349,84 +372,17 @@ bool a_Html_tag_set_valign_attr(DilloHtml *html, const char *tag, /* - * 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. + * Create and add a new Textblock to the current Textblock */ -static void Html_add_indented_widget(DilloHtml *html, Widget *textblock, - int left, int right, int space) +static void Html_add_textblock(DilloHtml *html, 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); + Textblock *textblock = new Textblock (prefs.limit_text_width); - DW2TB(html->dw)->addParbreak (space, style); - DW2TB(html->dw)->addWidget (textblock, style); - DW2TB(html->dw)->addParbreak (space, style); + HT2TB(html)->addParbreak (space, html->styleEngine->wordStyle ()); + HT2TB(html)->addWidget (textblock, html->styleEngine->style ()); + HT2TB(html)->addParbreak (space, html->styleEngine->wordStyle ()); 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->connectSignals(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 (prefs.limit_text_width); - 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); } /* @@ -435,19 +391,19 @@ static int Html_level_to_fontsize(int level) DilloHtml::DilloHtml(BrowserWindow *p_bw, const DilloUrl *url, const char *content_type) { - /* Init event receiver */ - linkReceiver.html = this; - /* Init main variables */ bw = p_bw; page_url = a_Url_dup(url); base_url = a_Url_dup(url); dw = NULL; + /* Init event receiver */ + linkReceiver.html = this; + HT2LT(this)->connectLink (&linkReceiver); + a_Bw_add_doc(p_bw, this); /* Init for-parsing variables */ - Buf_Consumed = 0; Start_Buf = NULL; Start_Ofs = 0; @@ -466,10 +422,13 @@ DilloHtml::DilloHtml(BrowserWindow *p_bw, const DilloUrl *url, DocType = DT_NONE; /* assume Tag Soup 0.0! :-) */ DocTypeVersion = 0.0f; + styleEngine = new StyleEngine (HT2LT (this)); + + cssUrls = new misc::SimpleVector <DilloUrl*> (1); + stack = new misc::SimpleVector <DilloHtmlState> (16); stack->increase(); - stack->getRef(0)->style = NULL; - stack->getRef(0)->table_cell_style = NULL; + stack->getRef(0)->table_cell_props = NULL; stack->getRef(0)->parse_mode = DILLO_HTML_PARSE_MODE_INIT; stack->getRef(0)->table_mode = DILLO_HTML_TABLE_MODE_NONE; stack->getRef(0)->cell_text_align_set = false; @@ -479,7 +438,6 @@ DilloHtml::DilloHtml(BrowserWindow *p_bw, const DilloUrl *url, stack->getRef(0)->textblock = NULL; stack->getRef(0)->table = NULL; stack->getRef(0)->ref_list_item = NULL; - stack->getRef(0)->current_bg_color = prefs.bg_color; stack->getRef(0)->hand_over_break = false; InFlags = IN_NONE; @@ -490,29 +448,24 @@ DilloHtml::DilloHtml(BrowserWindow *p_bw, const DilloUrl *url, pre_column = 0; PreFirstChar = false; PrevWasCR = false; - PrevWasOpenTag = false; - PrevWasSPC = false; InVisitedLink = false; ReqTagClose = false; - CloseOneTag = false; TagSoup = true; - NameVal = NULL; + loadCssFromStash = false; Num_HTML = Num_HEAD = Num_BODY = Num_TITLE = 0; attr_data = dStr_sized_new(1024); - parse_finished = false; + non_css_link_color = -1; + non_css_visited_color = -1; + visited_color = -1; /* Init page-handling variables */ forms = new misc::SimpleVector <DilloHtmlForm*> (1); inputs_outside_form = new misc::SimpleVector <DilloHtmlInput*> (1); links = new misc::SimpleVector <DilloUrl*> (64); - images = new misc::SimpleVector <DilloLinkImage*> (16); - //a_Dw_image_map_list_init(&maps); - - link_color = prefs.link_color; - visited_color = prefs.visited_color; + images = new misc::SimpleVector <DilloHtmlImage*> (16); /* Initialize the main widget */ initDw(); @@ -521,34 +474,15 @@ DilloHtml::DilloHtml(BrowserWindow *p_bw, const DilloUrl *url, } /* - * Miscelaneous initializations for Dw + * Miscellaneous initializations for Dw */ void DilloHtml::initDw() { - StyleAttrs style_attrs; - FontAttrs font_attrs; - dReturn_if_fail (dw == NULL); /* Create the main widget */ dw = stack->getRef(0)->textblock = new Textblock (prefs.limit_text_width); - /* Create a dummy font, attribute, and tag for the bottom of the stack. */ - font_attrs.name = prefs.vw_fontname; - 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(this), &font_attrs); - style_attrs.color = Color::createSimple (HT2LT(this), prefs.text_color); - stack->getRef(0)->style = Style::create (HT2LT(this), &style_attrs); - - stack->getRef(0)->table_cell_style = NULL; - - /* Handle it when the user clicks on a link */ - connectSignals(dw); - bw->num_page_bugs = 0; dStr_truncate(bw->page_bugs, 0); } @@ -560,14 +494,17 @@ DilloHtml::~DilloHtml() { _MSG("::~DilloHtml(this=%p)\n", this); - if (!parse_finished) - freeParseData(); + freeParseData(); a_Bw_remove_doc(bw, this); a_Url_free(page_url); a_Url_free(base_url); + for (int i = 0; i < cssUrls->size(); i++) + a_Url_free(cssUrls->get(i)); + delete (cssUrls); + for (int i = 0; i < forms->size(); i++) a_Html_form_delete (forms->get(i)); delete(forms); @@ -577,28 +514,18 @@ DilloHtml::~DilloHtml() delete(inputs_outside_form); for (int i = 0; i < links->size(); i++) - if (links->get(i)) - a_Url_free(links->get(i)); + a_Url_free(links->get(i)); delete (links); for (int i = 0; i < images->size(); i++) { - DilloLinkImage *li = images->get(i); - a_Url_free(li->url); - if (li->image) - a_Image_unref(li->image); - dFree(li); + DilloHtmlImage *img = images->get(i); + a_Url_free(img->url); + a_Image_unref(img->image); + dFree(img); } delete (images); - //a_Dw_image_map_list_free(&maps); -} - -/* - * Connect all signals of a textblock or an image. - */ -void DilloHtml::connectSignals(Widget *dw) -{ - dw->connectLink (&linkReceiver); + delete styleEngine; } /* @@ -611,9 +538,19 @@ void DilloHtml::write(char *Buf, int BufSize, int Eof) char *buf = Buf + Start_Ofs; int bufsize = BufSize - Start_Ofs; - dReturn_if_fail (dw != NULL); + _MSG("DilloHtml::write BufSize=%d Start_Ofs=%d\n", BufSize, Start_Ofs); +#if 0 + char *aux = dStrndup(Buf, BufSize); + MSG(" {%s}\n", aux); + dFree(aux); +#endif + /* Update Start_Buf. It may be used after the parser is stopped */ Start_Buf = Buf; + + dReturn_if (dw == NULL); + dReturn_if (stop_parser == true); + token_start = Html_write_raw(this, buf, bufsize, Eof); Start_Ofs += token_start; } @@ -644,7 +581,9 @@ int DilloHtml::getCurTagLineNumber() */ void DilloHtml::freeParseData() { - (stack->getRef(0)->style)->unref (); /* template style */ + for (int i = stack->size () - 1; i >= 0; i--) + if (stack->getRef (i)->table_cell_props) + stack->getRef (i)->table_cell_props->unref (); delete(stack); dStr_free(Stash, TRUE); @@ -661,6 +600,8 @@ void DilloHtml::finishParsing(int ClientKey) { int si; + dReturn_if (stop_parser == true); + /* force the close of elements left open (TODO: not for XHTML) */ while ((si = stack->size() - 1)) { if (stack->getRef(si)->tag_idx != -1) { @@ -669,9 +610,6 @@ void DilloHtml::finishParsing(int ClientKey) } /* Remove this client from our active list */ a_Bw_close_client(bw, ClientKey); - - freeParseData(); - parse_finished = true; } /* @@ -680,7 +618,10 @@ void DilloHtml::finishParsing(int ClientKey) int DilloHtml::formNew(DilloHtmlMethod method, const DilloUrl *action, DilloHtmlEnc enc, const char *charset) { - DilloHtmlForm *form = a_Html_form_new (this,method,action,enc,charset); + // avoid data loss on repush after CSS stylesheets have been loaded + bool enabled = bw->NumPendingStyleSheets == 0; + DilloHtmlForm *form = a_Html_form_new (this, method, action, + enc, charset, enabled); int nf = forms->size (); forms->increase (); forms->set (nf, form); @@ -713,16 +654,36 @@ void DilloHtml::loadImages (const DilloUrl *pattern) { dReturn_if_fail (bw->nav_expecting == FALSE); + /* If the user asked for a specific URL, the user (NULL) is the requester, + * but if the user just asked for all URLs, use the page URL as the + * requester. If the possible patterns become more complex, it might be + * good to have the caller supply the requester instead. + */ + const DilloUrl *requester = pattern ? NULL : this->page_url; + for (int i = 0; i < images->size(); i++) { if (images->get(i)->image) { if ((!pattern) || (!a_Url_cmp(images->get(i)->url, pattern))) { - Html_load_image(bw, images->get(i)->url, images->get(i)->image); - images->get(i)->image = NULL; // web owns it now + if (Html_load_image(bw, images->get(i)->url, requester, + images->get(i)->image)) { + a_Image_unref (images->get(i)->image); + images->get(i)->image = NULL; // web owns it now + } } } } } +/* + * Save URL in a vector (may be loaded later). + */ +void DilloHtml::addCssUrl(const DilloUrl *url) +{ + int nu = cssUrls->size(); + cssUrls->increase(); + cssUrls->set(nu, a_Url_dup(url)); +} + bool DilloHtml::HtmlLinkReceiver::enter (Widget *widget, int link, int img, int x, int y) { @@ -732,10 +693,12 @@ bool DilloHtml::HtmlLinkReceiver::enter (Widget *widget, int link, int img, if (link == -1) { _MSG(" Link LEAVE notify...\n"); a_UIcmd_set_msg(bw, ""); + a_UIcmd_set_pointer_on_link(bw, FALSE); } else { _MSG(" Link ENTER notify...\n"); Html_set_link_coordinates(html, link, x, y); a_UIcmd_set_msg(bw, "%s", URL_STR(html->links->get(link))); + a_UIcmd_set_pointer_on_link(bw, TRUE); } return true; } @@ -758,14 +721,12 @@ bool DilloHtml::HtmlLinkReceiver::press (Widget *widget, int link, int img, if (link != -1) linkurl = html->links->get(link); const bool_t loaded_img = (html->images->get(img)->image == NULL); - a_UIcmd_image_popup( - bw, html->images->get(img)->url, loaded_img, linkurl); + a_UIcmd_image_popup(bw, html->images->get(img)->url, loaded_img, + html->page_url, linkurl); ret = true; } else { if (link == -1) { - a_UIcmd_page_popup(bw, a_History_get_url(NAV_TOP_UIDX(bw)), - bw->num_page_bugs != 0, - html->unloadedImages()); + a_UIcmd_page_popup(bw, bw->num_page_bugs != 0, html->cssUrls); ret = true; } else { a_UIcmd_link_popup(bw, html->links->get(link)); @@ -786,22 +747,12 @@ bool DilloHtml::HtmlLinkReceiver::click (Widget *widget, int link, int img, if ((img != -1) && (html->images->get(img)->image)) { // clicked an image that has not already been loaded - DilloUrl *pattern; - if (event->button == 1){ // load all instances of this image - pattern = html->images->get(img)->url; - } else { - if (event->button == 2){ - // load all images - pattern = NULL; - } else { - return false; - } + DilloUrl *pattern = html->images->get(img)->url; + html->loadImages(pattern); + return true; } - - html->loadImages(pattern); - return true; } if (link != -1) { @@ -892,7 +843,7 @@ static const Ent_t Entities[NumEnt] = { {"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}, + {"nbsp",0240}, {"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}, @@ -952,14 +903,14 @@ 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; + 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; } @@ -991,7 +942,7 @@ static int Html_parse_entity(DilloHtml *html, const char *token, /* strtol with base 16 accepts leading "0x" - we don't */ if (*s == '0' && s[1] == 'x') { s++; - isocode = 0; + isocode = 0; } else { isocode = strtol(s, &s, 16); } @@ -1015,7 +966,7 @@ static int Html_parse_entity(DilloHtml *html, const char *token, } else if (isalpha(*s)) { /* character entity reference */ - while (*++s && (isalnum(*s) || strchr(":_.-", *s))); + while (*++s && (isalnum(*s) || strchr(":_.-", *s))) ; c = *s; *s = 0; @@ -1066,7 +1017,7 @@ char *a_Html_parse_entities(DilloHtml *html, const char *token, int toksize) toksize-i, &entsize)) >= 0) { if (isocode >= 128) { /* multibyte encoding */ - n = utf8encode(isocode, buf); + n = a_Utf8_encode(isocode, buf); for (k = 0; k < n; ++k) new_str[j++] = buf[k]; } else { @@ -1082,9 +1033,32 @@ char *a_Html_parse_entities(DilloHtml *html, const char *token, int toksize) } /* + * For white-space: pre-line, we must break the line if encountering a newline. + * Otherwise, collapse whitespace as usual. + */ +static void Html_process_space_pre_line(DilloHtml *html, const char *space, + int spacesize) +{ + int i, breakCnt = 0; + + for (i = 0; i < spacesize; i++) { + /* Support for "\r", "\n" and "\r\n" line breaks */ + if (space[i] == '\r' || (space[i] == '\n' && !html->PrevWasCR)) { + breakCnt++; + html->PrevWasCR = (space[i] == '\r'); + + HT2TB(html)->addLinebreak (html->styleEngine->wordStyle ()); + } + } + if (breakCnt == 0) { + HT2TB(html)->addSpace(html->styleEngine->wordStyle ()); + } +} + +/* * Parse spaces */ -static void Html_process_space(DilloHtml *html, const char *space, +static void Html_process_space(DilloHtml *html, const char *space, int spacesize) { char *spc; @@ -1108,11 +1082,12 @@ static void Html_process_space(DilloHtml *html, const char *space, if (spaceCnt) { spc = dStrnfill(spaceCnt, ' '); - DW2TB(html->dw)->addText (spc, S_TOP(html)->style); + HT2TB(html)->addText (spc, spaceCnt, + html->styleEngine->wordStyle ()); dFree(spc); spaceCnt = 0; } - DW2TB(html->dw)->addLinebreak (S_TOP(html)->style); + HT2TB(html)->addLinebreak (html->styleEngine->wordStyle ()); html->pre_column = 0; } html->PreFirstChar = false; @@ -1140,16 +1115,18 @@ static void Html_process_space(DilloHtml *html, const char *space, if (spaceCnt) { spc = dStrnfill(spaceCnt, ' '); - DW2TB(html->dw)->addText (spc, S_TOP(html)->style); + HT2TB(html)->addText (spc, spaceCnt, html->styleEngine->wordStyle ()); dFree(spc); } } else { if (SGML_SPCDEL) { - /* SGML_SPCDEL ignores white space inmediately after an open tag */ - } else if (!html->PrevWasSPC) { - DW2TB(html->dw)->addSpace(S_TOP(html)->style); - html->PrevWasSPC = true; + /* SGML_SPCDEL ignores white space immediately after an open tag */ + } else if (html->styleEngine->wordStyle ()->whiteSpace == + WHITE_SPACE_PRE_LINE) { + Html_process_space_pre_line(html, space, spacesize); + } else { + HT2TB(html)->addSpace(html->styleEngine->wordStyle ()); } if (parse_mode == DILLO_HTML_PARSE_MODE_STASH_AND_BODY) @@ -1168,7 +1145,7 @@ static void Html_process_space(DilloHtml *html, const char *space, static void Html_process_word(DilloHtml *html, const char *word, int size) { int i, j, start; - char *Pword, ch; + char *Pword; DilloHtmlParseMode parse_mode = S_TOP(html)->parse_mode; if (parse_mode == DILLO_HTML_PARSE_MODE_STASH || @@ -1195,39 +1172,66 @@ static void Html_process_word(DilloHtml *html, const char *word, int size) Pword = a_Html_parse_entities(html, word, size); for (start = i = 0; Pword[i]; start = i) if (isspace(Pword[i])) { - while (Pword[++i] && isspace(Pword[i])); + while (Pword[++i] && isspace(Pword[i])) ; Html_process_space(html, Pword + start, i - start); } else { - while (Pword[++i] && !isspace(Pword[i])); - ch = Pword[i]; - Pword[i] = 0; - DW2TB(html->dw)->addText(Pword, S_TOP(html)->style); - Pword[i] = ch; + while (Pword[++i] && !isspace(Pword[i])) ; + HT2TB(html)->addText(Pword + start, i - start, + html->styleEngine->wordStyle ()); html->pre_column += i - start; html->PreFirstChar = false; } dFree(Pword); } else { + const char *word2, *word2_end; + + Pword = NULL; if (!memchr(word,'&', size)) { /* No entities */ - DW2TB(html->dw)->addText(word, S_TOP(html)->style); + word2 = word; + word2_end = word + size - 1; } else { /* Collapse white-space entities inside the word (except ) */ Pword = a_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); - dFree(Pword); + /* Collapse adjacent " \t\f\n\r" characters into a single space */ + for (i = j = 0; (Pword[i] = Pword[j]); ++i, ++j) { + if (strchr(" \t\f\n\r", Pword[i])) { + if (i == 0 || (i > 0 && Pword[i-1] != ' ')) + Pword[i] = ' '; + else + for (--i; Pword[j+1] && strchr(" \t\f\n\r", Pword[j+1]); ++j) + ; + } + } + word2 = Pword; + word2_end = word2 + strlen(word2) - 1; + } + for (start = i = 0; word2[i]; start = i) { + int len; + + if (isspace(word2[i])) { + while (word2[++i] && isspace(word2[i])) ; + Html_process_space(html, word2 + start, i - start); + } else if (!strncmp(word2+i, utf8_zero_width_space, 3)) { + i += 3; + } else if (a_Utf8_ideographic(word2+i, word2_end, &len)) { + i += len; + HT2TB(html)->addText(word2 + start, i - start, + html->styleEngine->wordStyle ()); + } else { + do { + i += len; + } while (word2[i] && !isspace(word2[i]) && + strncmp(word2+i, utf8_zero_width_space, 3) && + (!a_Utf8_ideographic(word2+i, word2_end, &len))); + HT2TB(html)->addText(word2 + start, i - start, + html->styleEngine->wordStyle ()); + } } + if (Pword == word2) + dFree(Pword); } - - html->PrevWasOpenTag = false; - html->PrevWasSPC = false; - if (html->InFlags & IN_LI) - html->WordAfterLI = true; } /* @@ -1250,14 +1254,14 @@ static bool Html_match_tag(const char *tagstr, char *tag, int tagsize) /* * This function is called after popping the stack, to - * handle nested DwPage widgets. + * handle nested Textblock widgets. */ static void Html_eventually_pop_dw(DilloHtml *html, bool 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 (); + HT2TB(html)->handOverBreak (html->styleEngine->style ()); + HT2TB(html)->flush (); html->dw = S_TOP(html)->textblock; } } @@ -1275,10 +1279,8 @@ static void Html_push_tag(DilloHtml *html, int tag_idx) * 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_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 (); + if (S_TOP(html)->table_cell_props) + S_TOP(html)->table_cell_props->ref (); html->dw = S_TOP(html)->textblock; } @@ -1288,6 +1290,7 @@ static void Html_push_tag(DilloHtml *html, int tag_idx) */ static void Html_force_push_tag(DilloHtml *html, int tag_idx) { + html->styleEngine->startElement (tag_idx); Html_push_tag(html, tag_idx); } @@ -1298,15 +1301,32 @@ static void Html_real_pop_tag(DilloHtml *html) { bool hand_over_break; - (S_TOP(html)->style)->unref (); - if (S_TOP(html)->table_cell_style) - (S_TOP(html)->table_cell_style)->unref (); + html->styleEngine->endElement (S_TOP(html)->tag_idx); + if (S_TOP(html)->table_cell_props) + S_TOP(html)->table_cell_props->unref (); hand_over_break = S_TOP(html)->hand_over_break; html->stack->setSize (html->stack->size() - 1); Html_eventually_pop_dw(html, hand_over_break); } /* + * Cleanup the stack to a given index. + */ +static void Html_tag_cleanup_to_idx(DilloHtml *html, int idx) +{ + int s_sz; + while ((s_sz = html->stack->size()) > idx) { + int toptag_idx = S_TOP(html)->tag_idx; + TagInfo toptag = Tags[toptag_idx]; + if (s_sz > idx + 1 && toptag.EndTag != 'O') + BUG_MSG(" - forcing close of open tag: <%s>\n", toptag.name); + _MSG("Close: %*s%s\n", size," ", toptag.name); + toptag.close(html, toptag_idx); + Html_real_pop_tag(html); + } +} + +/* * Default close function for tags. * (conditional cleanup of the stack) * There are several ways of doing it. Considering the HTML 4.01 spec @@ -1321,77 +1341,51 @@ static void Html_real_pop_tag(DilloHtml *html) * 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) +static void Html_tag_cleanup_at_close(DilloHtml *html, int new_idx) { 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; - } + int stack_idx, tag_idx, matched = 0, expected = 0; + TagInfo new_tag = Tags[new_idx]; /* 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].EndTag == 'O') || - 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') - BUG_MSG(" - 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); + stack_idx = html->stack->size(); + while (--stack_idx) { + tag_idx = html->stack->getRef(stack_idx)->tag_idx; + if (tag_idx == new_idx) { + /* matching tag found */ + matched = 1; + break; + } else if (Tags[tag_idx].EndTag == 'O') { + /* skip an optional tag */ + continue; + } else if (w3c_mode || Tags[tag_idx].TagLevel >= new_tag.TagLevel) { + /* this is the tag that should have been closed */ + expected = 1; + break; } + } + if (matched) { + Html_tag_cleanup_to_idx(html, stack_idx); + } else if (expected) { + BUG_MSG("unexpected closing tag: </%s> -- expected </%s>.\n", + new_tag.name, Tags[tag_idx].name); } else { - if (stack_idx == 0) { - BUG_MSG("unexpected closing tag: </%s>.\n", Tags[new_idx].name); - } else { - BUG_MSG("unexpected closing tag: </%s>. -- expected </%s>\n", - Tags[new_idx].name, - Tags[html->stack->getRef(stack_idx)->tag_idx].name); - } + BUG_MSG("unexpected closing tag: </%s>.\n", new_tag.name); } } /* - * Cleanup (conditional), and Pop the tag (if it matches) - */ -void a_Html_pop_tag(DilloHtml *html, int TagIdx) -{ - Html_tag_cleanup_at_close(html, TagIdx); -} - -/* * Some parsing routines. */ /* * Used by a_Html_parse_length */ -static Length Html_parse_length_or_multi_length (const char *attr, - char **endptr) +static CssLength Html_parse_length_or_multi_length (const char *attr, + char **endptr) { - Length l; + CssLength l; double v; char *end; @@ -1399,12 +1393,12 @@ static Length Html_parse_length_or_multi_length (const char *attr, switch (*end) { case '%': end++; - l = createPerLength (v / 100); + l = CSS_CREATE_LENGTH (v / 100, CSS_LENGTH_TYPE_PERCENTAGE); break; case '*': end++; - l = createRelLength (v); + l = CSS_CREATE_LENGTH (v, CSS_LENGTH_TYPE_RELATIVE); break; /* The "px" suffix seems not allowed by HTML4.01 SPEC. @@ -1413,7 +1407,7 @@ static Length Html_parse_length_or_multi_length (const char *attr, end += 2; */ default: - l = createAbsLength ((int)v); + l = CSS_CREATE_LENGTH (v, CSS_LENGTH_TYPE_PX); break; } @@ -1427,24 +1421,24 @@ static Length Html_parse_length_or_multi_length (const char *attr, * Returns a length or a percentage, or UNDEF_LENGTH in case * of an error, or if attr is NULL. */ -Length a_Html_parse_length (DilloHtml *html, const char *attr) +CssLength a_Html_parse_length (DilloHtml *html, const char *attr) { - Length l; + CssLength l; char *end; l = Html_parse_length_or_multi_length (attr, &end); - if (isRelLength (l)) + if (CSS_LENGTH_TYPE (l) == CSS_LENGTH_TYPE_RELATIVE) /* not allowed as &Length; */ - return LENGTH_AUTO; + l = CSS_CREATE_LENGTH(0.0, CSS_LENGTH_TYPE_AUTO); else { /* allow only whitespaces */ if (*end && !isspace (*end)) { BUG_MSG("Garbage after length: %s\n", attr); - return LENGTH_AUTO; + l = CSS_CREATE_LENGTH(0.0, CSS_LENGTH_TYPE_AUTO); } } - _MSG("a_Html_parse_length: \"%s\" %d\n", attr, absLengthVal(l)); + _MSG("a_Html_parse_length: \"%s\" %d\n", attr, CSS_LENGTH_VALUE(l)); return l; } @@ -1498,7 +1492,7 @@ static int * 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 * @@ -1507,7 +1501,7 @@ static int static void Html_parse_doctype(DilloHtml *html, const char *tag, int tagsize) { static const char HTML_sig [] = "<!DOCTYPE HTML PUBLIC "; - static const char HTML20 [] = "-//IETF//DTD HTML//EN"; + static const char HTML20 [] = "-//IETF//DTD HTML 2.0"; static const char HTML32 [] = "-//W3C//DTD HTML 3.2"; static const char HTML40 [] = "-//W3C//DTD HTML 4.0"; static const char HTML401 [] = "-//W3C//DTD HTML 4.01"; @@ -1524,9 +1518,9 @@ static void Html_parse_doctype(DilloHtml *html, const char *tag, int tagsize) * 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); + for (ntag[i++] = ' '; isspace(p[1]); ++p) ; } else if ((quote = *p) == '"' || *p == '\'') { - for (ntag[i++] = *p++; (ntag[i++] = *p) && *p != quote; ++p) { + for (ntag[i++] = *p++; (ntag[i] = *p) && ntag[i++] != quote; ++p) { if (*p == '\n' || *p == '\r') ntag[i - 1] = ' '; p += (p[0] == '\r' && p[1] == '\n') ? 1 : 0; @@ -1595,7 +1589,6 @@ static void Html_tag_close_html(DilloHtml *html, int TagIdx) /* beware of pages with multiple HTML close tags... :-P */ html->InFlags &= ~IN_HTML; } - a_Html_pop_tag(html, TagIdx); } /* @@ -1622,16 +1615,22 @@ static void Html_tag_open_head(DilloHtml *html, const char *tag, int tagsize) * 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. + * Note2: HEAD is parsed once completely got. */ static void Html_tag_close_head(DilloHtml *html, int TagIdx) { if (html->InFlags & IN_HEAD) { + _MSG("Closing HEAD section\n"); if (html->Num_TITLE == 0) BUG_MSG("HEAD section lacks the TITLE element\n"); - + html->InFlags &= ~IN_HEAD; + + /* charset is already set, load remote stylesheets now */ + for (int i = 0; i < html->cssUrls->size(); i++) { + a_Html_load_stylesheet(html, html->cssUrls->get(i)); + } } - a_Html_pop_tag(html, TagIdx); } /* @@ -1653,11 +1652,10 @@ 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->bw, html->Stash->str); - a_History_set_title(NAV_TOP_UIDX(html->bw),html->Stash->str); + a_History_set_title_by_url(html->page_url, html->Stash->str); } else { BUG_MSG("the TITLE element must be inside the HEAD section\n"); } - a_Html_pop_tag(html, TagIdx); } /* @@ -1677,16 +1675,33 @@ static void Html_tag_open_script(DilloHtml *html, const char *tag, int tagsize) static void Html_tag_close_script(DilloHtml *html, int TagIdx) { /* eventually the stash will be sent to an interpreter for parsing */ - a_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. + * Store contents in the stash where the style sheet interpreter can get it. */ static void Html_tag_open_style(DilloHtml *html, const char *tag, int tagsize) { + const char *attrbuf; + + html->loadCssFromStash = true; + + if (!(attrbuf = a_Html_get_attr(html, tag, tagsize, "type"))) { + BUG_MSG("type attribute is required for <style>\n"); + } else if (dStrcasecmp(attrbuf, "text/css")) { + html->loadCssFromStash = false; + } + if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "media")) && + dStrcasecmp(attrbuf, "all") && !dStristr(attrbuf, "screen")) { + /* HTML 4.01 sec. 6.13 says that media descriptors are case-sensitive, + * but sec. 14.2.3 says that the attribute is case-insensitive. + * TODO can be a comma-separated list. + * TODO handheld. + */ + html->loadCssFromStash = false; + } + a_Html_stash_init(html); S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM; } @@ -1696,8 +1711,9 @@ static void Html_tag_open_style(DilloHtml *html, const char *tag, int tagsize) */ static void Html_tag_close_style(DilloHtml *html, int TagIdx) { - /* eventually the stash will be sent to an interpreter for parsing */ - a_Html_pop_tag(html, TagIdx); + if (prefs.parse_embedded_css && html->loadCssFromStash) + html->styleEngine->parse(html, NULL, html->Stash->str, html->Stash->len, + CSS_ORIGIN_AUTHOR); } /* @@ -1707,9 +1723,9 @@ static void Html_tag_open_body(DilloHtml *html, const char *tag, int tagsize) { const char *attrbuf; Textblock *textblock; - StyleAttrs style_attrs; - Style *style; + CssPropertyList props; int32_t color; + int tag_index_a = a_Html_tag_index ("a"); if (!(html->InFlags & IN_BODY)) html->InFlags |= IN_BODY; @@ -1724,43 +1740,53 @@ static void Html_tag_open_body(DilloHtml *html, const char *tag, int tagsize) BUG_MSG("unclosed HEAD element\n"); } - textblock = DW2TB(html->dw); + textblock = HT2TB(html); - if (!prefs.force_my_colors) { - if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "bgcolor"))) { - color = a_Html_color_parse(html, attrbuf, prefs.bg_color); - if (color == 0xffffff && !prefs.allow_white_bg) - color = prefs.bg_color; + if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "bgcolor"))) { + color = a_Html_color_parse(html, attrbuf, -1); + if (color != -1) + props.set (CSS_PROPERTY_BACKGROUND_COLOR, CSS_TYPE_COLOR, color); + } - style_attrs = *html->dw->getStyle (); - style_attrs.backgroundColor = Color::createShaded(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 = a_Html_get_attr(html, tag, tagsize, "text"))) { + color = a_Html_color_parse(html, attrbuf, -1); + if (color != -1) + props.set (CSS_PROPERTY_COLOR, CSS_TYPE_COLOR, color); + } - if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "text"))) { - color = a_Html_color_parse(html, attrbuf, prefs.text_color); - HTML_SET_TOP_ATTR (html, color, - Color::createSimple (HT2LT(html),color)); - } + if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "link"))) + html->non_css_link_color = a_Html_color_parse(html, attrbuf, -1); - if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "link"))) - html->link_color = a_Html_color_parse(html,attrbuf,prefs.link_color); + if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "vlink"))) + html->non_css_visited_color = a_Html_color_parse(html, attrbuf, -1); - if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "vlink"))) - html->visited_color = a_Html_color_parse(html, attrbuf, - prefs.visited_color); + html->styleEngine->setNonCssHints (&props); + html->dw->setStyle (html->styleEngine->style ()); - if (prefs.contrast_visited_color) { - /* get a color that has a "safe distance" from text, link and bg */ - html->visited_color = - a_Color_vc(html->visited_color, - S_TOP(html)->style->color->getColor(), - html->link_color, - S_TOP(html)->current_bg_color); - } + /* Determine a color for visited links. + * This color is computed once per page and used for immediate feedback + * when clicking a link. + * On reload style including color for visited links is computed properly + * according to CSS. + */ + html->styleEngine->startElement (tag_index_a); + html->styleEngine->setPseudoVisited (); + if (html->non_css_visited_color != -1) { + CssPropertyList vprops; + vprops.set (CSS_PROPERTY_COLOR, CSS_TYPE_COLOR, + html->non_css_visited_color); + html->styleEngine->setNonCssHints (&vprops); + } + html->visited_color = html->styleEngine->style ()->color->getColor (); + html->styleEngine->endElement (tag_index_a); + + if (prefs.contrast_visited_color) { + /* get a color that has a "safe distance" from text, link and bg */ + html->visited_color = + a_Color_vc(html->visited_color, + html->styleEngine->style ()->color->getColor(), + html->non_css_link_color, + html->styleEngine->backgroundStyle()->backgroundColor->getColor()); } S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_BODY; @@ -1775,7 +1801,6 @@ static void Html_tag_close_body(DilloHtml *html, int TagIdx) /* some tag soup pages use multiple BODY tags... */ html->InFlags &= ~IN_BODY; } - a_Html_pop_tag(html, TagIdx); } /* @@ -1785,13 +1810,12 @@ static void Html_tag_close_body(DilloHtml *html, int TagIdx) */ static void Html_tag_open_p(DilloHtml *html, const char *tag, int tagsize) { - if ((html->InFlags & IN_LI) && !html->WordAfterLI) { - /* ignore first parbreak after an empty <LI> */ - html->WordAfterLI = true; - } else { - DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); - } - a_Html_tag_set_align_attr (html, tag, tagsize); + CssPropertyList props; + + a_Html_tag_set_align_attr (html, &props, tag, tagsize); + html->styleEngine->inheritBackgroundColor (); + html->styleEngine->setNonCssHints (&props); + HT2TB(html)->addParbreak (9, html->styleEngine->wordStyle ()); } /* @@ -1805,11 +1829,10 @@ static void Html_tag_open_frame (DilloHtml *html, const char *tag, int tagsize) char *src; DilloUrl *url; Textblock *textblock; - StyleAttrs style_attrs; - Style *link_style; Widget *bullet; + CssPropertyList props; - textblock = DW2TB(html->dw); + textblock = HT2TB(html); if (!(attrbuf = a_Html_get_attr(html, tag, tagsize, "src"))) return; @@ -1819,46 +1842,40 @@ static void Html_tag_open_frame (DilloHtml *html, const char *tag, int tagsize) src = dStrdup(attrbuf); - style_attrs = *(S_TOP(html)->style); - - if (a_Capi_get_flags(url) & CAPI_IsCached) { /* visited frame */ - style_attrs.color = - Color::createSimple (HT2LT(html), html->visited_color); - } else { /* unvisited frame */ - style_attrs.color = Color::createSimple (HT2LT(html), html->link_color); + if (a_Capi_get_flags_with_redirection(url) & CAPI_IsCached) { + /* visited frame */ + html->styleEngine->setPseudoVisited (); + } else { + /* unvisited frame */ + html->styleEngine->setPseudoLink (); } - style_attrs.textDecoration |= TEXT_DECORATION_UNDERLINE; - style_attrs.x_link = Html_set_new_link(html, &url); - style_attrs.cursor = CURSOR_POINTER; - link_style = Style::create (HT2LT(html), &style_attrs); - textblock->addParbreak (5, S_TOP(html)->style); + props.set (PROPERTY_X_LINK, CSS_TYPE_INTEGER, Html_set_new_link(html,&url)); + html->styleEngine->setNonCssHints (&props); + + textblock->addParbreak (5, html->styleEngine->wordStyle ()); - /* 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); + textblock->addWidget(bullet, html->styleEngine->wordStyle ()); + textblock->addSpace(html->styleEngine->wordStyle ()); 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 ("IFRAME", link_style); + textblock->addText ("IFRAME", html->styleEngine->wordStyle ()); } else { /* FRAME: * If 'name' tag is present use it, if not use 'src' value */ if (!(attrbuf = a_Html_get_attr(html, tag, tagsize, "name"))) { - textblock->addText (src, link_style); + textblock->addText (src, html->styleEngine->wordStyle ()); } else { - textblock->addText (attrbuf, link_style); + textblock->addText (attrbuf, html->styleEngine->wordStyle ()); } } - textblock->addParbreak (5, S_TOP(html)->style); + textblock->addParbreak (5, html->styleEngine->wordStyle ()); - link_style->unref (); dFree(src); } @@ -1870,9 +1887,9 @@ static void Html_tag_open_frame (DilloHtml *html, const char *tag, int tagsize) static void Html_tag_open_frameset (DilloHtml *html, const char *tag, int tagsize) { - DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); - DW2TB(html->dw)->addText("--FRAME--", S_TOP(html)->style); - Html_add_indented(html, 40, 0, 5); + HT2TB(html)->addParbreak (9, html->styleEngine->wordStyle ()); + HT2TB(html)->addText("--FRAME--", html->styleEngine->wordStyle ()); + Html_add_textblock(html, 5); } /* @@ -1880,53 +1897,26 @@ static void Html_tag_open_frameset (DilloHtml *html, */ static void Html_tag_open_h(DilloHtml *html, const char *tag, int tagsize) { - DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + CssPropertyList props; + - /* TODO: combining these two would be slightly faster */ - a_Html_set_top_font(html, prefs.vw_fontname, - Html_level_to_fontsize(FontSizesNum - (tag[2] - '0')), - 1, 3); - a_Html_tag_set_align_attr (html, tag, tagsize); + html->styleEngine->inheritBackgroundColor (); + a_Html_tag_set_align_attr (html, &props, tag, tagsize); + html->styleEngine->setNonCssHints (&props); + + HT2TB(html)->addParbreak (9, html->styleEngine->wordStyle ()); - /* 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')); a_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); - a_Html_pop_tag(html, TagIdx); -} - -/* - * <BIG> | <SMALL> - */ -static void Html_tag_open_big_small(DilloHtml *html, - const char *tag, int tagsize) -{ - int level; - - level = - Html_fontsize_to_level(S_TOP(html)->style->font->size) + - ((dStrncasecmp(tag+1, "big", 3)) ? -1 : 1); - a_Html_set_top_font(html, NULL, Html_level_to_fontsize(level), 0, 0); -} - -/* * <BR> */ static void Html_tag_open_br(DilloHtml *html, const char *tag, int tagsize) { - DW2TB(html->dw)->addLinebreak (S_TOP(html)->style); + HT2TB(html)->addLinebreak (html->styleEngine->wordStyle ()); } /* @@ -1934,39 +1924,29 @@ static void Html_tag_open_br(DilloHtml *html, const char *tag, int tagsize) */ static void Html_tag_open_font(DilloHtml *html, const char *tag, int tagsize) { - StyleAttrs style_attrs; - Style *old_style; - /*Font font;*/ const char *attrbuf; + char *fontFamily = NULL; int32_t color; + CssPropertyList props; - if (!prefs.force_my_colors) { - old_style = S_TOP(html)->style; - style_attrs = *old_style; - - if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "color"))) { - if (prefs.contrast_visited_color && html->InVisitedLink) { - color = html->visited_color; - } else { - /* use the tag-specified color */ - color = a_Html_color_parse(html, attrbuf, - style_attrs.color->getColor()); - style_attrs.color = Color::createSimple (HT2LT(html), color); - } + if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "color"))) { + if (prefs.contrast_visited_color && html->InVisitedLink) { + color = html->visited_color; + } else { + /* use the tag-specified color */ + color = a_Html_color_parse(html, attrbuf, -1); } + if (color != -1) + props.set (CSS_PROPERTY_COLOR, CSS_TYPE_COLOR, color); + } -#if 0 - //if ((attrbuf = a_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 (); + if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "face"))) { + fontFamily = dStrdup(attrbuf); + props.set (CSS_PROPERTY_FONT_FAMILY, CSS_TYPE_SYMBOL, fontFamily); } + + html->styleEngine->setNonCssHints (&props); + dFree(fontFamily); } /* @@ -1974,62 +1954,33 @@ static void Html_tag_open_font(DilloHtml *html, const char *tag, int tagsize) */ static void Html_tag_open_abbr(DilloHtml *html, const char *tag, int tagsize) { -// DwTooltip *tooltip; -// const char *attrbuf; -// -// if ((attrbuf = a_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, const char *tag, int tagsize) -{ - a_Html_set_top_font(html, NULL, 0, 1, 1); -} - -/* - * <STRONG> - */ -static void Html_tag_open_strong(DilloHtml *html, const char *tag, int tagsize) -{ - a_Html_set_top_font(html, NULL, 0, 1, 1); -} + const char *attrbuf; -/* - * <I> - */ -static void Html_tag_open_i(DilloHtml *html, const char *tag, int tagsize) -{ - a_Html_set_top_font(html, NULL, 0, 2, 2); -} + if (prefs.show_tooltip && + (attrbuf = a_Html_get_attr(html, tag, tagsize, "title"))) { + CssPropertyList props; + char *tooltip_str = dStrdup(attrbuf); -/* - * <EM> - */ -static void Html_tag_open_em(DilloHtml *html, const char *tag, int tagsize) -{ - a_Html_set_top_font(html, NULL, 0, 2, 2); + props.set (PROPERTY_X_TOOLTIP, CSS_TYPE_STRING, tooltip_str); + html->styleEngine->setNonCssHints (&props); + dFree(tooltip_str); + } } /* - * <CITE> + * <CENTER> */ -static void Html_tag_open_cite(DilloHtml *html, const char *tag, int tagsize) +static void Html_tag_open_center(DilloHtml *html, const char *tag, int tagsize) { - a_Html_set_top_font(html, NULL, 0, 2, 2); + HT2TB(html)->addParbreak (0, html->styleEngine->wordStyle ()); } /* - * <CENTER> + * </CENTER>, also used for </TABLE> */ -static void Html_tag_open_center(DilloHtml *html, const char *tag, int tagsize) +static void Html_tag_close_center(DilloHtml *html, int TagIdx) { - DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style); - HTML_SET_TOP_ATTR(html, textAlign, TEXT_ALIGN_CENTER); + HT2TB(html)->addParbreak (0, html->styleEngine->wordStyle ()); } /* @@ -2038,42 +1989,32 @@ static void Html_tag_open_center(DilloHtml *html, const char *tag, int tagsize) static void Html_tag_open_address(DilloHtml *html, const char *tag, int tagsize) { - DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); - a_Html_set_top_font(html, NULL, 0, 2, 2); -} - -/* - * <TT> - */ -static void Html_tag_open_tt(DilloHtml *html, const char *tag, int tagsize) -{ - a_Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0); + HT2TB(html)->addParbreak (9, html->styleEngine->wordStyle ()); } /* - * Read image-associated tag attributes, - * create new image and add it to the html page (if add is TRUE). + * Read image-associated tag attributes and create new image. */ -DilloImage *a_Html_add_new_image(DilloHtml *html, const char *tag, - int tagsize, DilloUrl *url, - dw::core::style::StyleAttrs *style_attrs, - bool add) +DilloImage *a_Html_image_new(DilloHtml *html, const char *tag, + int tagsize, DilloUrl *url) { - 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; + CssLength l_w = CSS_CREATE_LENGTH(0.0, CSS_LENGTH_TYPE_AUTO); + CssLength l_h = CSS_CREATE_LENGTH(0.0, CSS_LENGTH_TYPE_AUTO); + int space, border, w = 0, h = 0; bool load_now; + CssPropertyList props; + char *tooltip_str = NULL; -// if (prefs.show_tooltip && -// (attrbuf = a_Html_get_attr(html, tag, tagsize, "title"))) -// style_attrs->x_tooltip = a_Dw_tooltip_new_no_ref(attrbuf); - + if (prefs.show_tooltip && + (attrbuf = a_Html_get_attr(html, tag, tagsize, "title"))) { + tooltip_str = dStrdup(attrbuf); + props.set (PROPERTY_X_TOOLTIP, CSS_TYPE_STRING, tooltip_str); + } alt_ptr = a_Html_get_attr_wdef(html, tag, tagsize, "alt", NULL); - if ((!alt_ptr || !*alt_ptr) && !a_UIcmd_get_images_enabled(html->bw)) { + if ((!alt_ptr || !*alt_ptr) && !prefs.load_images) { dFree(alt_ptr); alt_ptr = dStrdup("[IMG]"); // Place holder for img_off mode } @@ -2083,17 +2024,35 @@ DilloImage *a_Html_add_new_image(DilloHtml *html, const char *tag, // TODO: the same for percentage and relative lengths. if (width_ptr) { l_w = a_Html_parse_length (html, width_ptr); - w = isAbsLength(l_w) ? absLengthVal(l_w) : 0; + w = (int) (CSS_LENGTH_TYPE(l_w) == CSS_LENGTH_TYPE_PX ? + CSS_LENGTH_VALUE(l_w) : 0); } if (height_ptr) { l_h = a_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) { + h = (int) (CSS_LENGTH_TYPE(l_h) == CSS_LENGTH_TYPE_PX ? + CSS_LENGTH_VALUE(l_h) : 0); + } + /* Check for suspicious image size request that would cause + * an excessive amount of memory to be allocated for the + * image buffer. + * Be careful to avoid integer overflows during the checks. + * There is an additional check in dw/image.cc to catch cases + * where only one dimension is given and the image is scaled + * preserving its original aspect ratio. + * Size requests passed via CSS are also checked there. + */ + if (w < 0 || h < 0 || + w > IMAGE_MAX_AREA || h > IMAGE_MAX_AREA || + (h > 0 && w > IMAGE_MAX_AREA / h)) { dFree(width_ptr); dFree(height_ptr); width_ptr = height_ptr = NULL; - MSG("a_Html_add_new_image: suspicious image size request %dx%d\n", w, h); + MSG("a_Html_image_new: suspicious image size request %dx%d\n", w, h); + } else { + if (CSS_LENGTH_TYPE(l_w) != CSS_LENGTH_TYPE_AUTO) + props.set (CSS_PROPERTY_WIDTH, CSS_TYPE_LENGTH_PERCENTAGE, l_w); + if (CSS_LENGTH_TYPE(l_h) != CSS_LENGTH_TYPE_AUTO) + props.set (CSS_PROPERTY_HEIGHT, CSS_TYPE_LENGTH_PERCENTAGE, l_h); } /* TODO: we should scale the image respecting its ratio. @@ -2106,34 +2065,72 @@ DilloImage *a_Html_add_new_image(DilloHtml *html, const char *tag, /* Spacing to the left and right */ if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "hspace"))) { space = strtol(attrbuf, NULL, 10); - if (space > 0) - style_attrs->margin.left = style_attrs->margin.right = space; + if (space > 0) { + space = CSS_CREATE_LENGTH(space, CSS_LENGTH_TYPE_PX); + props.set (CSS_PROPERTY_MARGIN_LEFT, CSS_TYPE_LENGTH_PERCENTAGE, + space); + props.set (CSS_PROPERTY_MARGIN_RIGHT, CSS_TYPE_LENGTH_PERCENTAGE, + space); + } } /* Spacing at the top and bottom */ if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "vspace"))) { space = strtol(attrbuf, NULL, 10); - if (space > 0) - style_attrs->margin.top = style_attrs->margin.bottom = space; + if (space > 0) { + space = CSS_CREATE_LENGTH(space, CSS_LENGTH_TYPE_PX); + props.set (CSS_PROPERTY_MARGIN_TOP, CSS_TYPE_LENGTH_PERCENTAGE, + space); + props.set (CSS_PROPERTY_MARGIN_BOTTOM, CSS_TYPE_LENGTH_PERCENTAGE, + space); + } + } + + /* Border */ + if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "border"))) { + border = strtol(attrbuf, NULL, 10); + if (border >= 0) { + border = CSS_CREATE_LENGTH(border, CSS_LENGTH_TYPE_PX); + props.set (CSS_PROPERTY_BORDER_TOP_WIDTH, CSS_TYPE_LENGTH_PERCENTAGE, + border); + props.set (CSS_PROPERTY_BORDER_BOTTOM_WIDTH, + CSS_TYPE_LENGTH_PERCENTAGE, border); + props.set (CSS_PROPERTY_BORDER_LEFT_WIDTH, + CSS_TYPE_LENGTH_PERCENTAGE, border); + props.set (CSS_PROPERTY_BORDER_RIGHT_WIDTH, + CSS_TYPE_LENGTH_PERCENTAGE, border); + + props.set (CSS_PROPERTY_BORDER_TOP_STYLE, CSS_TYPE_ENUM, + BORDER_SOLID); + props.set (CSS_PROPERTY_BORDER_BOTTOM_STYLE, CSS_TYPE_ENUM, + BORDER_SOLID); + props.set (CSS_PROPERTY_BORDER_LEFT_STYLE, CSS_TYPE_ENUM, + BORDER_SOLID); + props.set (CSS_PROPERTY_BORDER_RIGHT_STYLE, CSS_TYPE_ENUM, + BORDER_SOLID); + } } /* x_img is an index to a list of {url,image} pairs. - * We know Html_add_new_linkimage() will use size() as its next index */ - style_attrs->x_img = html->images->size(); + * We know Html_add_new_htmlimage() will use size() as its next index */ + props.set (PROPERTY_X_IMG, CSS_TYPE_INTEGER, html->images->size()); - /* 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); - } + html->styleEngine->setNonCssHints(&props); - load_now = a_UIcmd_get_images_enabled(html->bw) || - (a_Capi_get_flags(url) & CAPI_IsCached); - Html_add_new_linkimage(html, &url, load_now ? NULL : Image); + /* Add a new image widget to this page */ + Image = a_Image_new(alt_ptr, 0); + if (HT2TB(html)->getBgColor()) + Image->bg_color = HT2TB(html)->getBgColor()->getColor(); + + load_now = prefs.load_images || + !dStrcasecmp(URL_SCHEME(url), "data") || + (a_Capi_get_flags_with_redirection(url) & CAPI_IsCached); + bool loading = false; if (load_now) - Html_load_image(html->bw, url, Image); + loading = Html_load_image(html->bw, url, html->page_url, Image); + Html_add_new_htmlimage(html, &url, loading ? NULL : Image); + dFree(tooltip_str); dFree(width_ptr); dFree(height_ptr); dFree(alt_ptr); @@ -2143,21 +2140,23 @@ DilloImage *a_Html_add_new_image(DilloHtml *html, const char *tag, /* * Tell cache to retrieve image */ -static void Html_load_image(BrowserWindow *bw, DilloUrl *url, - DilloImage *Image) +static bool Html_load_image(BrowserWindow *bw, DilloUrl *url, + const DilloUrl *requester, DilloImage *Image) { DilloWeb *Web; int ClientKey; /* Fill a Web structure for the cache query */ - Web = a_Web_new(url); + Web = a_Web_new(url, requester); Web->bw = bw; Web->Image = Image; + a_Image_ref(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(bw, ClientKey, 0); a_Bw_add_url(bw, url); } + return ClientKey != 0; } /* @@ -2170,9 +2169,7 @@ static void Html_tag_open_img(DilloHtml *html, const 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->base_url) & URL_SpamSafe) @@ -2182,41 +2179,21 @@ static void Html_tag_open_img(DilloHtml *html, const char *tag, int tagsize) !(url = a_Html_url_new(html, attrbuf, NULL, 0))) return; - textblock = DW2TB(html->dw); + textblock = HT2TB(html); usemap_url = NULL; if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "usemap"))) /* TODO: usemap URLs outside of the document are not used. */ usemap_url = a_Html_url_new(html, attrbuf, NULL, 0); - /* Set the style attributes for this image */ - style_attrs = *S_TOP(html)->style; - if (S_TOP(html)->style->x_link != -1 || - usemap_url != NULL) { - /* Images within links */ - border = 1; - if ((attrbuf = a_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->link_color)); - } - style_attrs.setBorderStyle (BORDER_SOLID); - style_attrs.borderWidth.setVal (border); - } - - Image = a_Html_add_new_image(html, tag, tagsize, url, &style_attrs, true); + Image = a_Html_image_new(html, tag, tagsize, url); + HT2TB(html)->addWidget((Widget*)Image->dw, html->styleEngine->style()); /* Image maps */ if (a_Html_get_attr(html, tag, tagsize, "ismap")) { ((::dw::Image*)Image->dw)->setIsMap(); _MSG(" Html_tag_open_img: server-side map (ISMAP)\n"); - } else if (S_TOP(html)->style->x_link != -1 && + } else if (html->styleEngine->style ()->x_link != -1 && usemap_url == NULL) { /* For simple links, we have to suppress the "image_pressed" signal. * This is overridden for USEMAP images. */ @@ -2225,10 +2202,9 @@ static void Html_tag_open_img(DilloHtml *html, const char *tag, int tagsize) if (usemap_url) { ((::dw::Image*)Image->dw)->setUseMap(&html->maps, - new ::object::String(usemap_url->url_string->str)); + new ::object::String(URL_STR(usemap_url))); a_Url_free (usemap_url); } - html->connectSignals((Widget*)Image->dw); } /* @@ -2244,13 +2220,15 @@ static void Html_tag_open_map(DilloHtml *html, const char *tag, int tagsize) BUG_MSG("nested <map>\n"); } else { if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "name"))) { + html->InFlags |= IN_MAP; hash_name = dStrconcat("#", attrbuf, NULL); url = a_Html_url_new(html, hash_name, NULL, 0); - html->maps.startNewMap(new ::object::String(url->url_string->str)); + html->maps.startNewMap(new ::object::String(URL_STR(url))); a_Url_free (url); dFree(hash_name); + } else { + BUG_MSG("name attribute is required for <map>\n"); } - html->InFlags |= IN_MAP; } } @@ -2259,8 +2237,18 @@ static void Html_tag_open_map(DilloHtml *html, const char *tag, int tagsize) */ static void Html_tag_close_map(DilloHtml *html, int TagIdx) { + /* This is a hack for the perhaps frivolous feature of drawing image map + * shapes when there is no image to display. If this map is defined after + * an image that has not been loaded (img != NULL), tell the image to + * redraw. (It will only do so if it uses a map.) + */ + for (int i = 0; i < html->images->size(); i++) { + DilloImage *img = html->images->get(i)->image; + + if (img) + ((dw::Image*) img->dw)->forceMapRedraw(); + } html->InFlags &= ~IN_MAP; - a_Html_pop_tag(html, TagIdx); } /* @@ -2306,7 +2294,7 @@ static void Html_tag_open_area(DilloHtml *html, const char *tag, int tagsize) const char *attrbuf; int link = -1; Shape *shape = NULL; - + if (!(html->InFlags & IN_MAP)) { BUG_MSG("<area> element not inside <map>\n"); return; @@ -2368,7 +2356,7 @@ static void Html_tag_open_area(DilloHtml *html, const char *tag, int tagsize) dReturn_if_fail ( url != NULL ); if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "alt"))) a_Url_set_alt(url, attrbuf); - + link = Html_set_new_link(html, &url); } if (type == BACKGROUND) @@ -2384,45 +2372,30 @@ static void Html_tag_open_area(DilloHtml *html, const char *tag, int tagsize) */ static void Html_tag_open_object(DilloHtml *html, const char *tag, int tagsize) { - StyleAttrs style_attrs; - Style *style; DilloUrl *url, *base_url = NULL; const char *attrbuf; + CssPropertyList props; if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "codebase"))) { base_url = a_Html_url_new(html, attrbuf, NULL, 0); } - + if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "data"))) { url = a_Html_url_new(html, attrbuf, URL_STR(base_url), (base_url != NULL)); dReturn_if_fail ( url != NULL ); - style_attrs = *S_TOP(html)->style; - - if (a_Capi_get_flags(url) & CAPI_IsCached) { - style_attrs.color = Color::createSimple ( - HT2LT(html), - html->visited_color -/* - a_Color_vc(html->visited_color, - S_TOP(html)->style->color->getColor(), - html->link_color, - S_TOP(html)->style->backgroundColor->getColor()), -*/ - ); + if (a_Capi_get_flags_with_redirection(url) & CAPI_IsCached) { + html->styleEngine->setPseudoVisited (); } else { - style_attrs.color = Color::createSimple(HT2LT(html), - html->link_color); + html->styleEngine->setPseudoLink (); } - style_attrs.textDecoration |= TEXT_DECORATION_UNDERLINE; - style_attrs.x_link = Html_set_new_link(html, &url); - style_attrs.cursor = CURSOR_POINTER; + props.set(PROPERTY_X_LINK, CSS_TYPE_INTEGER, + Html_set_new_link(html, &url)); + html->styleEngine->setNonCssHints (&props); - style = Style::create (HT2LT(html), &style_attrs); - DW2TB(html->dw)->addText("[OBJECT]", style); - style->unref (); + HT2TB(html)->addText("[OBJECT]", html->styleEngine->wordStyle ()); } a_Url_free(base_url); } @@ -2456,7 +2429,7 @@ static const char* Html_get_javascript_link(DilloHtml *html) 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)) + if (!HT2TB(html)->addAnchor (name, html->styleEngine->style ())) BUG_MSG("Anchor names must be unique within the document\n"); /* * According to Sec. 12.2.1 of the HTML 4.01 spec, "anchor names that @@ -2473,9 +2446,9 @@ static void Html_add_anchor(DilloHtml *html, const char *name) */ static void Html_tag_open_a(DilloHtml *html, const char *tag, int tagsize) { - StyleAttrs style_attrs; - Style *old_style; DilloUrl *url; + char *tooltip_str = NULL; + CssPropertyList props; const char *attrbuf; /* TODO: add support for MAP with A HREF */ @@ -2490,44 +2463,51 @@ static void Html_tag_open_a(DilloHtml *html, const char *tag, int tagsize) url = a_Html_url_new(html, attrbuf, NULL, 0); dReturn_if_fail ( url != NULL ); - old_style = S_TOP(html)->style; - style_attrs = *old_style; - - if (a_Capi_get_flags(url) & CAPI_IsCached) { + if (a_Capi_get_flags_with_redirection(url) & CAPI_IsCached) { html->InVisitedLink = true; - style_attrs.color = Color::createSimple ( - HT2LT(html), - html->visited_color -/* - a_Color_vc(html->visited_color, - S_TOP(html)->style->color->getColor(), - html->link_color, - S_TOP(html)->current_bg_color), -*/ - ); + html->styleEngine->setPseudoVisited (); + if (html->non_css_visited_color != -1) + props.set (CSS_PROPERTY_COLOR, CSS_TYPE_COLOR, + html->non_css_visited_color); } else { - style_attrs.color = Color::createSimple(HT2LT(html), - html->link_color); + html->styleEngine->setPseudoLink (); + if (html->non_css_link_color != -1) + props.set (CSS_PROPERTY_COLOR, CSS_TYPE_COLOR, + html->non_css_link_color); } -// if ((attrbuf = a_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 (); + props.set (PROPERTY_X_LINK, CSS_TYPE_INTEGER, + Html_set_new_link(html, &url)); + } + if (prefs.show_tooltip && + (attrbuf = a_Html_get_attr(html, tag, tagsize, "title"))) { + tooltip_str = dStrdup(attrbuf); + props.set (PROPERTY_X_TOOLTIP, CSS_TYPE_STRING, tooltip_str); } + html->styleEngine->setNonCssHints (&props); + dFree(tooltip_str); + + html->styleEngine->inheritBackgroundColor (); if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "name"))) { + char *nameVal; + const char *id = html->styleEngine->getId (); + 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); + + nameVal = a_Url_decode_hex_str(attrbuf); + + if (nameVal) { + /* We compare the "id" value with the url-decoded "name" value */ + if (!id || strcmp(nameVal, id)) { + if (id) + BUG_MSG("'id' and 'name' attribute of <a> tag differ\n"); + Html_add_anchor(html, nameVal); + } + + dFree(nameVal); + } } } @@ -2537,49 +2517,40 @@ static void Html_tag_open_a(DilloHtml *html, const char *tag, int tagsize) static void Html_tag_close_a(DilloHtml *html, int TagIdx) { html->InVisitedLink = false; - a_Html_pop_tag(html, TagIdx); } /* - * Insert underlined text in the page. + * <BLOCKQUOTE> */ -static void Html_tag_open_u(DilloHtml *html, const char *tag, int tagsize) +static void Html_tag_open_blockquote(DilloHtml *html, + const 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 (); + Html_add_textblock(html, 9); } /* - * Insert strike-through text. Used by <S>, <STRIKE> and <DEL>. + * <Q> */ -static void Html_tag_open_strike(DilloHtml *html, const char *tag, int tagsize) +static void Html_tag_open_q(DilloHtml *html, const char *tag, int tagsize) { - Style *style; - StyleAttrs style_attrs; + /* + * Left Double Quotation Mark, which is wrong in many cases, but + * should at least be widely recognized. + */ + const char *U201C = "\xe2\x80\x9c"; - 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 (); + HT2TB(html)->addText (U201C, html->styleEngine->wordStyle ()); } /* - * <BLOCKQUOTE> + * </Q> */ -static void Html_tag_open_blockquote(DilloHtml *html, - const char *tag, int tagsize) +static void Html_tag_close_q(DilloHtml *html, int TagIdx) { - DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); - Html_add_indented(html, 40, 40, 9); + /* Right Double Quotation Mark */ + const char *U201D = "\xe2\x80\x9d"; + + HT2TB(html)->addText (U201D, html->styleEngine->wordStyle ()); } /* @@ -2590,47 +2561,27 @@ static void Html_tag_open_ul(DilloHtml *html, const 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 = a_Html_get_attr(html, tag, tagsize, "type"))) { + CssPropertyList props; + /* list_style_type explicitly defined */ - if (dStrncasecmp(attrbuf, "disc", 4) == 0) + if (dStrcasecmp(attrbuf, "disc") == 0) list_style_type = LIST_STYLE_TYPE_DISC; - else if (dStrncasecmp(attrbuf, "circle", 6) == 0) + else if (dStrcasecmp(attrbuf, "circle") == 0) list_style_type = LIST_STYLE_TYPE_CIRCLE; - else if (dStrncasecmp(attrbuf, "square", 6) == 0) + else if (dStrcasecmp(attrbuf, "square") == 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; - } + + props.set(CSS_PROPERTY_LIST_STYLE_TYPE, CSS_TYPE_ENUM, list_style_type); + html->styleEngine->setNonCssHints (&props); } - HTML_SET_TOP_ATTR(html, listStyleType, list_style_type); - S_TOP(html)->list_type = HTML_LIST_UNORDERED; + Html_add_textblock(html, 9); + S_TOP(html)->list_type = HTML_LIST_UNORDERED; S_TOP(html)->list_number = 0; S_TOP(html)->ref_list_item = NULL; } @@ -2641,11 +2592,8 @@ static void Html_tag_open_ul(DilloHtml *html, const char *tag, int tagsize) */ static void Html_tag_open_dir(DilloHtml *html, const char *tag, int tagsize) { - ListStyleType list_style_type = LIST_STYLE_TYPE_DISC; + HT2TB(html)->addParbreak (9, html->styleEngine->wordStyle ()); - 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; @@ -2668,28 +2616,29 @@ static void Html_tag_open_menu(DilloHtml *html, const char *tag, int tagsize) static void Html_tag_open_ol(DilloHtml *html, const 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 = a_Html_get_attr(html, tag, tagsize, "type"))) { + CssPropertyList props; + ListStyleType listStyleType = LIST_STYLE_TYPE_DECIMAL; + if (*attrbuf == '1') - list_style_type = LIST_STYLE_TYPE_DECIMAL; + listStyleType = LIST_STYLE_TYPE_DECIMAL; else if (*attrbuf == 'a') - list_style_type = LIST_STYLE_TYPE_LOWER_ALPHA; + listStyleType = LIST_STYLE_TYPE_LOWER_ALPHA; else if (*attrbuf == 'A') - list_style_type = LIST_STYLE_TYPE_UPPER_ALPHA; + listStyleType = LIST_STYLE_TYPE_UPPER_ALPHA; else if (*attrbuf == 'i') - list_style_type = LIST_STYLE_TYPE_LOWER_ROMAN; + listStyleType = LIST_STYLE_TYPE_LOWER_ROMAN; else if (*attrbuf == 'I') - list_style_type = LIST_STYLE_TYPE_UPPER_ROMAN; + listStyleType = LIST_STYLE_TYPE_UPPER_ROMAN; + + props.set (CSS_PROPERTY_LIST_STYLE_TYPE, CSS_TYPE_ENUM, listStyleType); + html->styleEngine->setNonCssHints (&props); } - HTML_SET_TOP_ATTR(html, listStyleType, list_style_type); + Html_add_textblock(html, 9); + S_TOP(html)->list_type = HTML_LIST_ORDERED; if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "start")) && @@ -2706,59 +2655,45 @@ static void Html_tag_open_ol(DilloHtml *html, const char *tag, int tagsize) */ static void Html_tag_open_li(DilloHtml *html, const char *tag, int tagsize) { - StyleAttrs style_attrs; - Style *item_style, *word_style; + Style *style = html->styleEngine->style (); + Style *wordStyle = html->styleEngine->wordStyle (); Widget **ref_list_item; ListItem *list_item; int *list_number; const char *attrbuf; char buf[16]; + if (S_TOP(html)->list_type == HTML_LIST_NONE) + BUG_MSG("<li> outside <ul> or <ol>\n"); + html->InFlags |= IN_LI; - html->WordAfterLI = false; /* 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::createShaded (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); + HT2TB(html)->addParbreak (0, wordStyle); list_item = new ListItem ((ListItem*)*ref_list_item,prefs.limit_text_width); - DW2TB(html->dw)->addWidget (list_item, item_style); - DW2TB(html->dw)->addParbreak (2, word_style); + HT2TB(html)->addWidget (list_item, style); + HT2TB(html)->addParbreak (0, wordStyle); *ref_list_item = list_item; S_TOP(html)->textblock = html->dw = list_item; - item_style->unref(); - /* Handle it when the user clicks on a link */ - html->connectSignals(list_item); - switch (S_TOP(html)->list_type) { - case HTML_LIST_ORDERED: + if (style->listStyleType == LIST_STYLE_TYPE_NONE) { + // none + } else if (style->listStyleType >= LIST_STYLE_TYPE_DECIMAL) { + // ordered if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "value")) && (*list_number = strtol(attrbuf, NULL, 10)) < 0) { BUG_MSG("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); - html->PrevWasSPC = true; - break; - case HTML_LIST_NONE: - BUG_MSG("<li> outside <ul> or <ol>\n"); - default: - list_item->initWithWidget (new Bullet(), word_style); - list_item->addSpace (word_style); - break; + numtostr((*list_number)++, buf, 16, style->listStyleType); + list_item->initWithText (buf, wordStyle); + } else { + // unordered + list_item->initWithWidget (new Bullet(), wordStyle); } } @@ -2768,9 +2703,7 @@ static void Html_tag_open_li(DilloHtml *html, const char *tag, int tagsize) static void Html_tag_close_li(DilloHtml *html, int TagIdx) { html->InFlags &= ~IN_LI; - html->WordAfterLI = false; ((ListItem *)html->dw)->flush (); - a_Html_pop_tag(html, TagIdx); } /* @@ -2779,58 +2712,55 @@ static void Html_tag_close_li(DilloHtml *html, int TagIdx) static void Html_tag_open_hr(DilloHtml *html, const char *tag, int tagsize) { Widget *hruler; - StyleAttrs style_attrs; - Style *style; + CssPropertyList props; char *width_ptr; const char *attrbuf; int32_t size = 0; - - style_attrs = *S_TOP(html)->style; - width_ptr = a_Html_get_attr_wdef(html, tag, tagsize, "width", "100%"); - style_attrs.width = a_Html_parse_length (html, width_ptr); - dFree(width_ptr); + width_ptr = a_Html_get_attr_wdef(html, tag, tagsize, "width", NULL); + if (width_ptr) { + props.set (CSS_PROPERTY_WIDTH, CSS_TYPE_LENGTH_PERCENTAGE, + a_Html_parse_length (html, width_ptr)); + dFree(width_ptr); + } if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "size"))) size = strtol(attrbuf, NULL, 10); - - if ((attrbuf = a_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; - } - + + a_Html_tag_set_align_attr(html, &props, tag, tagsize); + /* TODO: evaluate attribute */ if (a_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) + props.set (CSS_PROPERTY_BORDER_TOP_STYLE, CSS_TYPE_ENUM, BORDER_SOLID); + props.set (CSS_PROPERTY_BORDER_BOTTOM_STYLE,CSS_TYPE_ENUM,BORDER_SOLID); + props.set (CSS_PROPERTY_BORDER_LEFT_STYLE, CSS_TYPE_ENUM, BORDER_SOLID); + props.set (CSS_PROPERTY_BORDER_RIGHT_STYLE, CSS_TYPE_ENUM, BORDER_SOLID); + + if (size <= 0) 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); + } + + if (size > 0) { + CssLength size_top = CSS_CREATE_LENGTH ((size+1)/2, CSS_LENGTH_TYPE_PX); + CssLength size_bottom = CSS_CREATE_LENGTH (size / 2, CSS_LENGTH_TYPE_PX); + props.set (CSS_PROPERTY_BORDER_TOP_WIDTH, CSS_TYPE_LENGTH_PERCENTAGE, + size_top); + props.set (CSS_PROPERTY_BORDER_LEFT_WIDTH, CSS_TYPE_LENGTH_PERCENTAGE, + size_top); + props.set (CSS_PROPERTY_BORDER_BOTTOM_WIDTH, CSS_TYPE_LENGTH_PERCENTAGE, + size_bottom); + props.set (CSS_PROPERTY_BORDER_RIGHT_WIDTH, CSS_TYPE_LENGTH_PERCENTAGE, + size_bottom); + } + + html->styleEngine->setNonCssHints (&props); + + HT2TB(html)->addParbreak (5, html->styleEngine->wordStyle ()); + hruler = new Ruler(); - hruler->setStyle (style); - DW2TB(html->dw)->addWidget (hruler, style); - style->unref (); - DW2TB(html->dw)->addParbreak (5, S_TOP(html)->style); + hruler->setStyle (html->styleEngine->style ()); + HT2TB(html)->addWidget (hruler, html->styleEngine->style ()); + HT2TB(html)->addParbreak (5, html->styleEngine->wordStyle ()); } /* @@ -2839,7 +2769,7 @@ static void Html_tag_open_hr(DilloHtml *html, const char *tag, int tagsize) static void Html_tag_open_dl(DilloHtml *html, const char *tag, int tagsize) { /* may want to actually do some stuff here. */ - DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + HT2TB(html)->addParbreak (9, html->styleEngine->wordStyle ()); } /* @@ -2847,8 +2777,7 @@ static void Html_tag_open_dl(DilloHtml *html, const char *tag, int tagsize) */ static void Html_tag_open_dt(DilloHtml *html, const char *tag, int tagsize) { - DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); - a_Html_set_top_font(html, NULL, 0, 1, 1); + HT2TB(html)->addParbreak (9, html->styleEngine->wordStyle ()); } /* @@ -2856,8 +2785,7 @@ static void Html_tag_open_dt(DilloHtml *html, const char *tag, int tagsize) */ static void Html_tag_open_dd(DilloHtml *html, const char *tag, int tagsize) { - DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); - Html_add_indented(html, 40, 40, 9); + Html_add_textblock(html, 9); } /* @@ -2865,14 +2793,8 @@ static void Html_tag_open_dd(DilloHtml *html, const char *tag, int tagsize) */ static void Html_tag_open_pre(DilloHtml *html, const char *tag, int tagsize) { - DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); - a_Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0); + HT2TB(html)->addParbreak (9, html->styleEngine->wordStyle ()); - /* 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; } @@ -2882,8 +2804,7 @@ static void Html_tag_open_pre(DilloHtml *html, const char *tag, int tagsize) static void Html_tag_close_pre(DilloHtml *html, int TagIdx) { html->InFlags &= ~IN_PRE; - DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); - a_Html_pop_tag(html, TagIdx); + HT2TB(html)->addParbreak (9, html->styleEngine->wordStyle ()); } /* @@ -2899,7 +2820,7 @@ static int Html_tag_pre_excludes(int tag_idx) /* initialize array */ if (!ei_set[0]) for (i = 0; es_set[i]; ++i) - ei_set[i] = Html_tag_index(es_set[i]); + ei_set[i] = a_Html_tag_index(es_set[i]); for (i = 0; ei_set[i]; ++i) if (tag_idx == ei_set[i]) @@ -2909,12 +2830,12 @@ static int Html_tag_pre_excludes(int tag_idx) /* * Handle <META> - * We do not support http-equiv=refresh because it's non standard, - * (the HTML 4.01 SPEC recommends explicitly to avoid it), and it - * can be easily abused! - * + * We do not support http-equiv=refresh with delay>0 because it's + * non standard, (the HTML 4.01 SPEC recommends explicitly to avoid it). * More info at: * http://lists.w3.org/Archives/Public/www-html/2000Feb/thread.html#msg232 + * Instant client-side redirects (delay=0) are supported: + * http://www.w3.org/TR/2008/NOTE-WCAG20-TECHS-20081211/H76.html * * 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 @@ -2930,9 +2851,8 @@ static void Html_tag_open_meta(DilloHtml *html, const char *tag, int tagsize) " <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; + const char *p, *equiv, *content, *new_content; + char delay_str[64], *mr_url; int delay; /* only valid inside HEAD */ @@ -2943,49 +2863,178 @@ static void Html_tag_open_meta(DilloHtml *html, const char *tag, int tagsize) if ((equiv = a_Html_get_attr(html, tag, tagsize, "http-equiv"))) { if (!dStrcasecmp(equiv, "refresh") && - (content = a_Html_get_attr(html, tag, tagsize, "content"))) { + (content = a_Html_get_attr(html, tag, tagsize, "content"))) { /* Get delay, if present, and make a message with it */ - if ((delay = strtol(content, NULL, 0))) + if ((delay = strtol(content, NULL, 0))) { snprintf(delay_str, 64, " after %d second%s.", - delay, (delay > 1) ? "s" : ""); - else + 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; + while (*content && *(content++) != '=') ; + /* Handle the case of a quoted URL */ + if (*content == '"' || *content == '\'') { + if ((p = strchr(content + 1, *content))) + mr_url = dStrndup(content + 1, p - content - 1); + else + mr_url = dStrdup(content + 1); + } else { + mr_url = dStrdup(content); } - dStr_free(ds_msg, 1); + + if (delay == 0) { + /* zero-delay redirection */ + html->stop_parser = true; + DilloUrl *new_url = a_Url_new(mr_url, URL_STR(html->base_url)); + if (a_Capi_dpi_verify_request(html->bw, new_url)) + a_UIcmd_redirection0((void*)html->bw, new_url); + a_Url_free(new_url); + } else { + /* Send a custom HTML message. + * TODO: This is a hairy hack, + * It'd be much better to build a widget. */ + Dstr *ds_msg = dStr_sized_new(256); + dStr_sprintf(ds_msg, meta_template, mr_url, delay_str); + { + int o_InFlags = html->InFlags; + int o_TagSoup = html->TagSoup; + html->InFlags = IN_BODY; + html->TagSoup = false; + Html_write_raw(html, ds_msg->str, ds_msg->len, 0); + html->TagSoup = o_TagSoup; + html->InFlags = o_InFlags; + } + dStr_free(ds_msg, 1); + } + dFree(mr_url); } else if (!dStrcasecmp(equiv, "content-type") && (content = a_Html_get_attr(html, tag, tagsize, "content"))) { - if (a_Misc_content_type_cmp(html->content_type, content)) { - const bool_t force = FALSE; - const char *new_content = - a_Capi_set_content_type(html->page_url, content, force); - /* Cannot ask cache whether the content type was changed, as - * this code in another bw might have already changed it for us. - */ - if (a_Misc_content_type_cmp(html->content_type, new_content)) { - a_Nav_repush(html->bw); - html->stop_parser = true; + _MSG("Html_tag_open_meta: content={%s}\n", content); + /* Cannot ask cache whether the content type was changed, as + * this code in another bw might have already changed it for us. + */ + new_content = a_Capi_set_content_type(html->page_url,content,"meta"); + if (a_Misc_content_type_cmp(html->content_type, new_content)) { + html->stop_parser = true; /* The cache buffer is no longer valid */ + a_UIcmd_repush(html->bw); + } + } + } +} + +/* + * Called by the network engine when a stylesheet has new data. + */ +static void Html_css_load_callback(int Op, CacheClient_t *Client) +{ + _MSG("Html_css_load_callback: Op=%d\n", Op); + if (Op) { /* EOF */ + BrowserWindow *bw = ((DilloWeb *)Client->Web)->bw; + /* Repush when we've got them all */ + if (--bw->NumPendingStyleSheets == 0) + a_UIcmd_repush(bw); + } +} + +/* + * Tell cache to retrieve a stylesheet + */ +void a_Html_load_stylesheet(DilloHtml *html, DilloUrl *url) +{ + char *data; + int len; + + dReturn_if (url == NULL || ! prefs.load_stylesheets); + + _MSG("Html_load_stylesheet: "); + if (a_Capi_get_buf(url, &data, &len)) { + _MSG("cached URL=%s len=%d", URL_STR(url), len); + if (a_Capi_get_flags_with_redirection(url) & CAPI_Completed) { + if (strncmp("@charset \"", data, 10) == 0) { + char *endq = strchr(data+10, '"'); + + if (endq && (endq - data <= 51)) { + /* IANA limits charset names to 40 characters */ + const char *ignored; + char *content_type; + + *endq = '\0'; + content_type = dStrconcat("text/css; charset=", data+10, NULL); + *endq = '"'; + a_Capi_unref_buf(url); + ignored = a_Capi_set_content_type(url, content_type, "meta"); + dFree(content_type); + a_Capi_get_buf(url, &data, &len); } } - } + html->styleEngine->parse(html, url, data, len, CSS_ORIGIN_AUTHOR); + } + a_Capi_unref_buf(url); + } else { + /* Fill a Web structure for the cache query */ + int ClientKey; + DilloWeb *Web = a_Web_new(url, html->page_url); + Web->bw = html->bw; + if ((ClientKey = a_Capi_open_url(Web, Html_css_load_callback, NULL))) { + ++html->bw->NumPendingStyleSheets; + a_Bw_add_client(html->bw, ClientKey, 0); + a_Bw_add_url(html->bw, url); + MSG("NumPendingStyleSheets=%d", html->bw->NumPendingStyleSheets); + } + } + MSG("\n"); +} + +/* + * Parse the LINK element (Only CSS stylesheets by now). + * (If it either hits or misses, is not relevant here; that's up to the + * cache functions) + * + * TODO: How will we know when to use "handheld"? Ask the html->bw->ui for + * screen dimensions, or a dillorc preference. + */ +static void Html_tag_open_link(DilloHtml *html, const char *tag, int tagsize) +{ + DilloUrl *url; + const char *attrbuf; + + //char *tag_str = dStrndup(tag, tagsize); + //MSG("Html_tag_open_link(): %s\n", tag_str); + //dFree(tag_str); + + /* When viewing suspicious HTML email, don't load LINK */ + dReturn_if (URL_FLAGS(html->base_url) & URL_SpamSafe); + + /* Ignore LINK outside HEAD */ + if (!(html->InFlags & IN_HEAD)) { + BUG_MSG("the LINK element must be inside the HEAD section\n"); + return; } + /* Remote stylesheets enabled? */ + dReturn_if_fail (prefs.load_stylesheets); + /* CSS stylesheet link */ + if (!(attrbuf = a_Html_get_attr(html, tag, tagsize, "rel")) || + dStrcasecmp(attrbuf, "stylesheet")) + return; + + /* IMPLIED attributes? */ + if (((attrbuf = a_Html_get_attr(html, tag, tagsize, "type")) && + dStrcasecmp(attrbuf, "text/css")) || + ((attrbuf = a_Html_get_attr(html, tag, tagsize, "media")) && + !dStristr(attrbuf, "screen") && dStrcasecmp(attrbuf, "all"))) + return; + + if (!(attrbuf = a_Html_get_attr(html, tag, tagsize, "href")) || + !(url = a_Html_url_new(html, attrbuf, NULL, 0))) + return; + + MSG(" Html_tag_open_link(): addCssUrl %s\n", URL_STR(url)); + + html->addCssUrl(url); + a_Url_free(url); } /* @@ -3031,60 +3080,9 @@ static void Html_tag_open_base(DilloHtml *html, const char *tag, int tagsize) } } -/* - * <CODE> - */ -static void Html_tag_open_code(DilloHtml *html, const char *tag, int tagsize) -{ - a_Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0); -} - -/* - * <DFN> - */ -static void Html_tag_open_dfn(DilloHtml *html, const char *tag, int tagsize) -{ - a_Html_set_top_font(html, NULL, 0, 2, 3); -} - -/* - * <KBD> - */ -static void Html_tag_open_kbd(DilloHtml *html, const char *tag, int tagsize) -{ - a_Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0); -} - -/* - * <SAMP> - */ -static void Html_tag_open_samp(DilloHtml *html, const char *tag, int tagsize) -{ - a_Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0); -} - -/* - * <VAR> - */ -static void Html_tag_open_var(DilloHtml *html, const char *tag, int tagsize) -{ - a_Html_set_top_font(html, NULL, 0, 2, 2); -} - -/* - * <SUB> - */ -static void Html_tag_open_sub(DilloHtml *html, const char *tag, int tagsize) -{ - HTML_SET_TOP_ATTR (html, valign, VALIGN_SUB); -} - -/* - * <SUP> - */ -static void Html_tag_open_sup(DilloHtml *html, const char *tag, int tagsize) +static void Html_tag_open_default(DilloHtml *html,const char *tag,int tagsize) { - HTML_SET_TOP_ATTR (html, valign, VALIGN_SUPER); + html->styleEngine->inheritBackgroundColor(); } /* @@ -3092,25 +3090,18 @@ static void Html_tag_open_sup(DilloHtml *html, const char *tag, int tagsize) */ static void Html_tag_open_div(DilloHtml *html, const char *tag, int tagsize) { - DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style); - a_Html_tag_set_align_attr (html, tag, tagsize); -} + CssPropertyList props; -/* - * </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); - a_Html_pop_tag(html, TagIdx); + a_Html_tag_set_align_attr (html, &props, tag, tagsize); + html->styleEngine->setNonCssHints (&props); + Html_add_textblock(html, 0); } /* - * Default close for most tags - just pop the stack. + * Default close for most tags. */ static void Html_tag_close_default(DilloHtml *html, int TagIdx) { - a_Html_pop_tag(html, TagIdx); } /* @@ -3118,8 +3109,7 @@ static void Html_tag_close_default(DilloHtml *html, int TagIdx) */ static void Html_tag_close_par(DilloHtml *html, int TagIdx) { - DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); - a_Html_pop_tag(html, TagIdx); + HT2TB(html)->addParbreak (9, html->styleEngine->wordStyle ()); } @@ -3158,55 +3148,56 @@ const TagInfo Tags[] = { /* 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}, + {"b", B8(010101),'R',2, Html_tag_open_default, 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}, + {"big", B8(010101),'R',2, Html_tag_open_default, Html_tag_close_default}, + {"blockquote", B8(011110),'R',2, Html_tag_open_blockquote, + Html_tag_close_default}, {"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_button}, /* 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}, + {"center", B8(011110),'R',2, Html_tag_open_center, Html_tag_close_center}, + {"cite", B8(010101),'R',2, Html_tag_open_default, Html_tag_close_default}, + {"code", B8(010101),'R',2, Html_tag_open_default, 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}, + {"dd", B8(011110),'O',1, Html_tag_open_dd, Html_tag_close_default}, + {"del", B8(011101),'R',2, Html_tag_open_default, Html_tag_close_default}, + {"dfn", B8(010101),'R',2, Html_tag_open_default, Html_tag_close_default}, {"dir", B8(011010),'R',2, Html_tag_open_dir, Html_tag_close_par}, /* TODO: complete <div> support! */ - {"div", B8(011110),'R',2, Html_tag_open_div, Html_tag_close_div}, + {"div", B8(011110),'R',2, Html_tag_open_div, Html_tag_close_default}, {"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}, + {"em", B8(010101),'R',2, Html_tag_open_default, 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}, + {"h1", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_par}, + {"h2", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_par}, + {"h3", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_par}, + {"h4", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_par}, + {"h5", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_par}, + {"h6", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_par}, {"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}, + {"i", B8(010101),'R',2, Html_tag_open_default, 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}, + {"kbd", B8(010101),'R',2, Html_tag_open_default, Html_tag_close_default}, /* label 010101 */ /* legend 01?? */ {"li", B8(011110),'O',1, Html_tag_open_li, Html_tag_close_li}, - /* link 100000 'F' */ + {"link", B8(100001),'F',0, Html_tag_open_link, Html_tag_close_default}, {"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}, @@ -3214,25 +3205,25 @@ const TagInfo Tags[] = { /* noframes 1011 */ /* noscript 1011 */ {"object", B8(111101),'R',2, Html_tag_open_object, Html_tag_close_default}, - {"ol", B8(011010),'R',2, Html_tag_open_ol, Html_tag_close_par}, + {"ol", B8(011010),'R',2, Html_tag_open_ol, Html_tag_close_default}, /* 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}, + {"q", B8(010101),'R',2, Html_tag_open_q, Html_tag_close_q}, + {"s", B8(010101),'R',2, Html_tag_open_default, Html_tag_close_default}, + {"samp", B8(010101),'R',2, Html_tag_open_default, Html_tag_close_default}, {"script", B8(111001),'R',2, Html_tag_open_script, Html_tag_close_script}, {"select", B8(010101),'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}, + {"small", B8(010101),'R',2, Html_tag_open_default, Html_tag_close_default}, + {"span", B8(010101),'R',2, Html_tag_open_default, Html_tag_close_default}, + {"strike", B8(010101),'R',2, Html_tag_open_default, Html_tag_close_default}, + {"strong", B8(010101),'R',2, Html_tag_open_default, 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}, + {"sub", B8(010101),'R',2, Html_tag_open_default, Html_tag_close_default}, + {"sup", B8(010101),'R',2, Html_tag_open_default, Html_tag_close_default}, + {"table", B8(011010),'R',5, Html_tag_open_table, Html_tag_close_center}, /* 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}, @@ -3241,10 +3232,10 @@ const TagInfo Tags[] = { /* 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} + {"tt", B8(010101),'R',2, Html_tag_open_default, Html_tag_close_default}, + {"u", B8(010101),'R',2, Html_tag_open_default, Html_tag_close_default}, + {"ul", B8(011010),'R',2, Html_tag_open_ul, Html_tag_close_default}, + {"var", B8(010101),'R',2, Html_tag_open_default, Html_tag_close_default} }; #define NTAGS (sizeof(Tags)/sizeof(Tags[0])) @@ -3270,7 +3261,7 @@ static int Html_tag_compare(const char *p1, const char *p2) * Get 'tag' index * return -1 if tag is not handled yet */ -static int Html_tag_index(const char *tag) +int a_Html_tag_index(const char *tag) { int low, high, mid, cond; @@ -3301,17 +3292,17 @@ static int Html_needs_optional_close(int old_idx, int cur_idx) 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"); + i_P = a_Html_tag_index("p"), + i_LI = a_Html_tag_index("li"), + i_TD = a_Html_tag_index("td"), + i_TR = a_Html_tag_index("tr"), + i_TH = a_Html_tag_index("th"), + i_DD = a_Html_tag_index("dd"), + i_DT = a_Html_tag_index("dt"), + i_OPTION = a_Html_tag_index("option"); + // i_THEAD = a_Html_tag_index("thead"); + // i_TFOOT = a_Html_tag_index("tfoot"); + // i_COLGROUP = a_Html_tag_index("colgroup"); } if (old_idx == i_P || old_idx == i_DT) { @@ -3408,10 +3399,11 @@ static void Html_test_section(DilloHtml *html, int new_idx, int IsCloseTag) if (!(html->InFlags & IN_HTML)) { tag = "<html>"; - tag_idx = Html_tag_index(tag + 1); + tag_idx = a_Html_tag_index(tag + 1); if (tag_idx != new_idx || IsCloseTag) { /* implicit open */ Html_force_push_tag(html, tag_idx); + _MSG("Open : %*s%s\n", html->stack->size()," ",Tags[tag_idx].name); Tags[tag_idx].open (html, tag, strlen(tag)); } } @@ -3420,10 +3412,11 @@ static void Html_test_section(DilloHtml *html, int new_idx, int IsCloseTag) /* head element */ if (!(html->InFlags & IN_HEAD)) { tag = "<head>"; - tag_idx = Html_tag_index(tag + 1); + tag_idx = a_Html_tag_index(tag + 1); if (tag_idx != new_idx || IsCloseTag) { /* implicit open of the head element */ Html_force_push_tag(html, tag_idx); + _MSG("Open : %*s%s\n", html->stack->size()," ",Tags[tag_idx].name); Tags[tag_idx].open (html, tag, strlen(tag)); } } @@ -3432,20 +3425,57 @@ static void Html_test_section(DilloHtml *html, int new_idx, int IsCloseTag) /* body element */ if (html->InFlags & IN_HEAD) { tag = "</head>"; - tag_idx = Html_tag_index(tag + 2); - Tags[tag_idx].close (html, tag_idx); + tag_idx = a_Html_tag_index(tag + 2); + Html_tag_cleanup_at_close(html, tag_idx); } tag = "<body>"; - tag_idx = Html_tag_index(tag + 1); + tag_idx = a_Html_tag_index(tag + 1); if (tag_idx != new_idx || IsCloseTag) { /* implicit open */ Html_force_push_tag(html, tag_idx); + _MSG("Open : %*s%s\n", html->stack->size()," ",Tags[tag_idx].name); Tags[tag_idx].open (html, tag, strlen(tag)); } } } /* + * Parse attributes that can appear on any tag. + */ +static void Html_parse_common_attrs(DilloHtml *html, char *tag, int tagsize) +{ + const char *attrbuf; + + 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"); + + html->styleEngine->setId(attrbuf); + } + + if (tagsize >= 11 && (prefs.parse_embedded_css || prefs.load_stylesheets)) { + /* length of "<t class=i>" or "<t style=i>" */ + attrbuf = Html_get_attr2(html, tag, tagsize, "class", + HTML_LeftTrim | HTML_RightTrim); + if (attrbuf) + html->styleEngine->setClass (attrbuf); + + attrbuf = Html_get_attr2(html, tag, tagsize, "style", + HTML_LeftTrim | HTML_RightTrim); + if (attrbuf) + html->styleEngine->setStyle (attrbuf); + } + +} + +/* * 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. @@ -3453,11 +3483,12 @@ static void Html_test_section(DilloHtml *html, int new_idx, int IsCloseTag) 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); + dReturn_if (html->stop_parser == true); + + ni = a_Html_tag_index(start + IsCloseTag); if (ni == -1) { /* TODO: doctype parsing is a bit fuzzy, but enough for the time being */ if (!(html->InFlags & IN_HTML)) { @@ -3491,40 +3522,30 @@ static void Html_process_tag(DilloHtml *html, char *tag, int tagsize) /* Push the tag into the stack */ Html_push_tag(html, ni); + html->styleEngine->startElement (ni); + _MSG("Open : %*s%s\n", html->stack->size(), " ", Tags[ni].name); + + /* Parse attributes that can appear on any tag */ + Html_parse_common_attrs(html, tag, tagsize); + /* Call the open function for this tag */ + _MSG("Open : %s\n", Tags[ni].name); Tags[ni].open (html, tag, tagsize); if (html->stop_parser) break; - /* 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) - BUG_MSG("'id' and 'name' attribute of <a> tag differ\n"); - Html_add_anchor(html, attrbuf); - } + if (S_TOP(html)->parse_mode != DILLO_HTML_PARSE_MODE_PRE && + (html->styleEngine->style ()->whiteSpace == WHITE_SPACE_PRE || + html->styleEngine->style ()->whiteSpace == WHITE_SPACE_PRE_WRAP)) { + S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_PRE; + html->pre_column = 0; + html->PreFirstChar = true; } - /* Reset NameVal */ - if (html->NameVal) { - dFree(html->NameVal); - html->NameVal = NULL; - } - - /* let the parser know this was an open tag */ - html->PrevWasOpenTag = true; + if (html->styleEngine->getId ()) + Html_add_anchor(html, html->styleEngine->getId ()); - /* Request inmediate close for elements with forbidden close tag. */ + /* Request immediate 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') @@ -3538,13 +3559,13 @@ static void Html_process_tag(DilloHtml *html, char *tag, int tagsize) /* Test for </x>, ReqTagClose, <x /> and <x/> */ if (*start == '/' || /* </x> */ html->ReqTagClose || /* request */ - (tag[tagsize - 2] == '/' && /* XML: */ - (isspace(tag[tagsize - 3]) || /* <x /> */ + (tag[tagsize-2] == '/' && /* XML: */ + (strchr(" \"'", tag[tagsize-3]) || /* [ "']/> */ (size_t)tagsize == strlen(Tags[ni].name) + 3))) { /* <x/> */ - - Tags[ni].close (html, ni); + + _MSG("Close: %s\n", Tags[ni].name); + Html_tag_cleanup_at_close(html, ni); /* This was a close tag */ - html->PrevWasOpenTag = false; html->ReqTagClose = false; } } @@ -3623,7 +3644,7 @@ static const char *Html_get_attr2(DilloHtml *html, tagsize-i, &entsize)) >= 0) { if (isocode >= 128) { char buf[4]; - int k, n = utf8encode(isocode, buf); + int k, n = a_Utf8_encode(isocode, buf); for (k = 0; k < n; ++k) dStr_append_c(Buf, buf[k]); } else { @@ -3687,29 +3708,6 @@ char *a_Html_get_attr_wdef(DilloHtml *html, } /* - * 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 ? - a_Html_parse_length (html, width_str) : LENGTH_AUTO; - new_style_attrs.height = height_str ? - a_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. @@ -3739,13 +3737,13 @@ static int Html_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof) Textblock *textblock; int token_start, buf_index; - dReturn_val_if_fail ((textblock = DW2TB(html->dw)) != NULL, 0); + dReturn_val_if_fail ((textblock = HT2TB(html)) != 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) && (html->stop_parser == false)) { + while ((buf_index < bufsize) && !html->stop_parser) { /* invariant: buf_index == bufsize || token_start == buf_index */ if (S_TOP(html)->parse_mode == @@ -3774,7 +3772,7 @@ static int Html_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof) if (isspace(buf[buf_index])) { /* whitespace: group all available whitespace */ - while (++buf_index < bufsize && isspace(buf[buf_index])); + while (++buf_index < bufsize && isspace(buf[buf_index])) ; Html_process_space(html, buf + token_start, buf_index - token_start); token_start = buf_index; diff --git a/src/html.hh b/src/html.hh index 3e08f37a..5b18c1a8 100644 --- a/src/html.hh +++ b/src/html.hh @@ -11,6 +11,9 @@ extern "C" { * Exported functions */ void a_Html_load_images(void *v_html, DilloUrl *pattern); +void a_Html_form_submit(void *v_html, void *v_form); +void a_Html_form_reset(void *v_html, void *v_form); +void a_Html_form_display_hiddens(void *v_html, void *v_form, bool_t display); #ifdef __cplusplus } diff --git a/src/html_common.hh b/src/html_common.hh index c694123e..3cca82de 100644 --- a/src/html_common.hh +++ b/src/html_common.hh @@ -13,12 +13,14 @@ #include "form.hh" +#include "styleengine.hh" + /* - * Macros + * Macros */ -// Dw to Textblock -#define DW2TB(dw) ((Textblock*)dw) +// "html struct" to Textblock +#define HT2TB(html) ((Textblock*)(html->dw)) // "html struct" to "Layout" #define HT2LT(html) ((Layout*)html->bw->render_layout) // "Image" to "Dw Widget" @@ -33,32 +35,15 @@ } D_STMT_END /* - * Change one toplevel attribute. var should be an identifier. val is - * only evaluated once, so you can safely use a function call for it. + * Typedefs */ -#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) -/* - * Typedefs - */ - -typedef struct _DilloLinkImage DilloLinkImage; +typedef struct _DilloHtmlImage DilloHtmlImage; typedef struct _DilloHtmlState DilloHtmlState; typedef enum { - DT_NONE, - DT_HTML, + DT_NONE, + DT_HTML, DT_XHTML } DilloHtmlDocumentType; @@ -100,16 +85,16 @@ typedef enum { } DilloHtmlProcessingState; /* - * Data Structures + * Data Structures */ -struct _DilloLinkImage { +struct _DilloHtmlImage { DilloUrl *url; DilloImage *image; }; struct _DilloHtmlState { - dw::core::style::Style *style, *table_cell_style; + CssPropertyList *table_cell_props; DilloHtmlParseMode parse_mode; DilloHtmlTableMode table_mode; bool cell_text_align_set; @@ -124,10 +109,6 @@ struct _DilloHtmlState { /* 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). */ @@ -135,12 +116,12 @@ struct _DilloHtmlState { }; /* - * Classes + * Classes */ class DilloHtml { private: - class HtmlLinkReceiver: public dw::core::Widget::LinkReceiver { + class HtmlLinkReceiver: public dw::core::Layout::LinkReceiver { public: DilloHtml *html; @@ -161,7 +142,6 @@ public: //BUG: for now everything is public /* -------------------------------------------------------------------*/ /* Variables required at parsing time */ /* -------------------------------------------------------------------*/ - size_t Buf_Consumed; /* amount of source from cache consumed */ char *Start_Buf; int Start_Ofs; char *content_type, *charset; @@ -173,7 +153,11 @@ public: //BUG: for now everything is public DilloHtmlDocumentType DocType; /* as given by DOCTYPE tag */ float DocTypeVersion; /* HTML or XHTML version number */ + /* vector of remote CSS resources, as given by the LINK element */ + lout::misc::SimpleVector<DilloUrl*> *cssUrls; + lout::misc::SimpleVector<DilloHtmlState> *stack; + StyleEngine *styleEngine; int InFlags; /* tracks which elements we are in */ @@ -184,33 +168,30 @@ public: //BUG: for now everything is public bool PreFirstChar; /* used to skip the first CR or CRLF in PRE tags */ bool PrevWasCR; /* Flag to help parsing of "\r\n" in PRE tags */ bool PrevWasOpenTag; /* Flag to help deferred parsing of white space */ - bool PrevWasSPC; /* Flag to help handling collapsing white space */ bool InVisitedLink; /* used to 'contrast_visited_colors' */ bool ReqTagClose; /* Flag to help handling bad-formed HTML */ - bool CloseOneTag; /* Flag to help Html_tag_cleanup_at_close() */ - bool WordAfterLI; /* Flag to help ignoring the 1st <P> after <LI> */ bool TagSoup; /* Flag to enable the parser's cleanup functions */ - char *NameVal; /* used for validation of "NAME" and "ID" in <A> */ + bool loadCssFromStash; /* current stash content should be loaded as CSS */ /* element counters: used for validation purposes */ uchar_t Num_HTML, Num_HEAD, Num_BODY, Num_TITLE; Dstr *attr_data; /* Buffer for attribute value */ + int32_t non_css_link_color; /* as provided by link attribute in BODY */ + int32_t non_css_visited_color; /* as provided by vlink attribute in BODY */ + int32_t visited_color; /* as computed according to CSS */ + /* -------------------------------------------------------------------*/ /* Variables required after parsing (for page functionality) */ /* -------------------------------------------------------------------*/ lout::misc::SimpleVector<DilloHtmlForm*> *forms; lout::misc::SimpleVector<DilloHtmlInput*> *inputs_outside_form; lout::misc::SimpleVector<DilloUrl*> *links; - lout::misc::SimpleVector<DilloLinkImage*> *images; + lout::misc::SimpleVector<DilloHtmlImage*> *images; dw::ImageMapsList maps; - int32_t link_color; - int32_t visited_color; - private: - bool parse_finished; void freeParseData(); void initDw(); /* Used by the constructor */ @@ -227,12 +208,15 @@ public: DilloHtmlForm *getCurrentForm (); bool_t unloadedImages(); void loadImages (const DilloUrl *pattern); + void addCssUrl(const DilloUrl *url); }; /* - * Parser functions + * Parser functions */ +int a_Html_tag_index(const char *tag); + const char *a_Html_get_attr(DilloHtml *html, const char *tag, int tagsize, @@ -248,10 +232,8 @@ DilloUrl *a_Html_url_new(DilloHtml *html, const char *url_str, const char *base_url, int use_base_url); -DilloImage *a_Html_add_new_image(DilloHtml *html, const char *tag, - int tagsize, DilloUrl *url, - dw::core::style::StyleAttrs *style_attrs, - bool add); +DilloImage *a_Html_image_new(DilloHtml *html, const char *tag, + int tagsize, DilloUrl *url); char *a_Html_parse_entities(DilloHtml *html, const char *token, int toksize); void a_Html_pop_tag(DilloHtml *html, int TagIdx); @@ -260,12 +242,12 @@ int32_t a_Html_color_parse(DilloHtml *html, const char *subtag, int32_t default_color); dw::core::style::Length a_Html_parse_length (DilloHtml *html, const char *attr); -void a_Html_tag_set_align_attr(DilloHtml *html, +void a_Html_tag_set_align_attr(DilloHtml *html, CssPropertyList *props, const char *tag, int tagsize); bool a_Html_tag_set_valign_attr(DilloHtml *html, const char *tag, int tagsize, - dw::core::style::StyleAttrs *style_attrs); -void a_Html_set_top_font(DilloHtml *html, const char *name, int size, - int BI, int BImask); + CssPropertyList *props); + +void a_Html_load_stylesheet(DilloHtml *html, DilloUrl *url); #endif /* __HTML_COMMON_HH__ */ diff --git a/src/image.cc b/src/image.cc index 54eb4710..c499d977 100644 --- a/src/image.cc +++ b/src/image.cc @@ -15,9 +15,6 @@ * of data from an Image to a DwImage widget. */ -#include <stdio.h> -#include <string.h> - #include "msg.h" #include "image.hh" @@ -27,23 +24,13 @@ 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; +#define I2DW(Image) ((dw::Image*)(Image->dw)) /* * Create and initialize a new image structure. */ -DilloImage *a_Image_new(int width, - int height, - const char *alt_text, - int32_t bg_color) +DilloImage *a_Image_new(const char *alt_text, int32_t bg_color) { DilloImage *Image; @@ -51,15 +38,12 @@ DilloImage *a_Image_new(int width, 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->ScanNumber = 0; Image->BitVec = NULL; Image->State = IMG_Empty; - Image->RefCount = 1; + Image->RefCount = 0; return Image; } @@ -75,6 +59,7 @@ static void Image_free(DilloImage *Image) /* * Unref and free if necessary + * Do nothing if the argument is NULL */ void a_Image_unref(DilloImage *Image) { @@ -85,6 +70,7 @@ void a_Image_unref(DilloImage *Image) /* * Add a reference to an Image struct + * Do nothing if the argument is NULL */ void a_Image_ref(DilloImage *Image) { @@ -93,37 +79,6 @@ void a_Image_ref(DilloImage *Image) } /* - * 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, @@ -133,59 +88,27 @@ void a_Image_set_parms(DilloImage *Image, void *v_imgbuf, DilloUrl *url, _MSG("a_Image_set_parms: width=%d height=%d\n", width, height); bool resize = (Image->width != width || Image->height != height); - OI(Image)->setBuffer((Imgbuf*)v_imgbuf, resize); + I2DW(Image)->setBuffer((Imgbuf*)v_imgbuf, resize); 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; -} - -/* - * Begin a new scan for a multiple-scan image - */ -void a_Image_new_scan(DilloImage *Image, void *v_imgbuf) -{ - a_Bitvec_clear(Image->BitVec); - Image->ScanNumber++; - ((Imgbuf*)v_imgbuf)->newScan(); -} - -/* * Implement the write method */ -void a_Image_write(DilloImage *Image, void *v_imgbuf, - const uchar_t *buf, uint_t y, int decode) +void a_Image_write(DilloImage *Image, uint_t y) { - uchar_t *newbuf; - + _MSG("a_Image_write\n"); 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); - } + /* Update the row in DwImage */ + I2DW(Image)->drawRow(y); a_Bitvec_set_bit(Image->BitVec, y); Image->State = IMG_Write; - - /* Update the row in DwImage */ - OI(Image)->drawRow(y); } /* @@ -193,46 +116,6 @@ void a_Image_write(DilloImage *Image, void *v_imgbuf, */ 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; + _MSG("a_Image_close\n"); } diff --git a/src/image.hh b/src/image.hh index 73b0e5e7..a66edaae 100644 --- a/src/image.hh +++ b/src/image.hh @@ -12,12 +12,24 @@ extern "C" { #include "bitvec.h" #include "url.h" +/* + * Defines + */ + +/* Arbitrary maximum for image size (to avoid image size-crafting attacks). */ +#define IMAGE_MAX_AREA (6000 * 6000) + +/* + * Types + */ + typedef struct _DilloImage DilloImage; typedef enum { DILLO_IMG_TYPE_INDEXED, DILLO_IMG_TYPE_RGB, DILLO_IMG_TYPE_GRAY, + DILLO_IMG_TYPE_CMYK_INV, DILLO_IMG_TYPE_NOTSET /* Initial value */ } DilloImgType; @@ -38,11 +50,7 @@ struct _DilloImage { 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 */ uint_t ScanNumber; /* Current decoding scan */ ImageState State; /* Processing status */ @@ -54,24 +62,16 @@ struct _DilloImage { /* * Function prototypes */ -DilloImage *a_Image_new(int width, int height, - const char *alt_text, int32_t bg_color); +DilloImage *a_Image_new(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_new_scan(DilloImage *image, void *v_imgbuf); -void a_Image_write(DilloImage *Image, void *v_imgbuf, - const uchar_t *buf, uint_t y, int decode); +void a_Image_write(DilloImage *Image, uint_t y); 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 } diff --git a/src/imgbuf.cc b/src/imgbuf.cc new file mode 100644 index 00000000..51f86b74 --- /dev/null +++ b/src/imgbuf.cc @@ -0,0 +1,138 @@ +/* + * File: imgbuf.cc + * + * Copyright (C) 2008 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 "imgbuf.hh" +#include "dw/core.hh" +#include "dw/image.hh" + +using namespace dw::core; + +/* + * Local data + */ +static size_t linebuf_size = 0; +static uchar_t *linebuf = NULL; + + +/* + * Decode 'buf' (an image line) into RGB format. + */ +static uchar_t *Imgbuf_rgb_line(const uchar_t *buf, + DilloImgType type, uchar_t *cmap, + uint_t width, uint_t y) +{ + uint_t x; + + switch (type) { + case DILLO_IMG_TYPE_INDEXED: + if (cmap) { + for (x = 0; x < width; x++) + memcpy(linebuf + x * 3, cmap + buf[x] * 3, 3); + } else { + MSG_WARN("Gif:: image lacks a color map\n"); + } + break; + case DILLO_IMG_TYPE_GRAY: + for (x = 0; x < width; x++) + memset(linebuf + x * 3, buf[x], 3); + break; + case DILLO_IMG_TYPE_CMYK_INV: + /* + * We treat CMYK as if it were "RGBW", and it works. Everyone who is + * trying to handle CMYK jpegs is confused by this, and supposedly + * the issue is that Adobe CMYK is "wrong" but ubiquitous. + */ + for (x = 0; x < width; x++) { + uint_t white = buf[x * 4 + 3]; + linebuf[x * 3] = buf[x * 4] * white / 0x100; + linebuf[x * 3 + 1] = buf[x * 4 + 1] * white / 0x100; + linebuf[x * 3 + 2] = buf[x * 4 + 2] * white / 0x100; + } + break; + case DILLO_IMG_TYPE_RGB: + /* avoid a memcpy here! --Jcid */ + return (uchar_t *)buf; + case DILLO_IMG_TYPE_NOTSET: + MSG_ERR("Imgbuf_rgb_line: type not set...\n"); + break; + } + return linebuf; +} + +// Wrappers for Imgbuf ------------------------------------------------------- + +/* + * Increment reference count for an Imgbuf + */ +void a_Imgbuf_ref(void *v_imgbuf) +{ + ((Imgbuf*)v_imgbuf)->ref(); +} + +/* + * Decrement reference count for an Imgbuf + */ +void a_Imgbuf_unref(void *v_imgbuf) +{ + if (v_imgbuf) + ((Imgbuf*)v_imgbuf)->unref(); +} + +/* + * Create a new Imgbuf + */ +void *a_Imgbuf_new(void *v_dw, int img_type, uint_t width, uint_t height) +{ + Layout *layout = ((Widget*)v_dw)->getLayout(); + if (!layout) { + MSG_ERR("a_Imgbuf_new: layout is NULL.\n"); + exit(1); + } + // Assert linebuf is wide enough. + if (3 * width > linebuf_size) { + linebuf_size = 3 * width; + linebuf = (uchar_t*) dRealloc(linebuf, linebuf_size); + } + + return (void*)layout->createImgbuf(Imgbuf::RGB, width, height); +} + +/* + * Last reference for this Imgbuf? + */ +int a_Imgbuf_last_reference(void *v_imgbuf) +{ + return ((Imgbuf*)v_imgbuf)->lastReference () ? 1 : 0; +} + +/* + * Update the root buffer of an imgbuf. + */ +void a_Imgbuf_update(void *v_imgbuf, const uchar_t *buf, DilloImgType type, + uchar_t *cmap, uint_t width, uint_t height, uint_t y) + +{ + dReturn_if_fail ( y < height ); + + /* Decode 'buf' and copy it into the imgbuf */ + uchar_t *newbuf = Imgbuf_rgb_line(buf, type, cmap, width, y); + ((Imgbuf*)v_imgbuf)->copyRow(y, (byte *)newbuf); +} + +/* + * Reset for a new scan from a multiple-scan image. + */ +void a_Imgbuf_new_scan(void *v_imgbuf) +{ + ((Imgbuf*)v_imgbuf)->newScan(); +} + diff --git a/src/imgbuf.hh b/src/imgbuf.hh new file mode 100644 index 00000000..9a6e3ff7 --- /dev/null +++ b/src/imgbuf.hh @@ -0,0 +1,30 @@ +#ifndef __IMGBUF_HH__ +#define __IMGBUF_HH__ + +// Imgbuf wrappers + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#include "image.hh" + +/* + * Function prototypes + */ +void a_Imgbuf_ref(void *v_imgbuf); +void a_Imgbuf_unref(void *v_imgbuf); +void *a_Imgbuf_new(void *v_dw, int img_type, uint_t width, uint_t height); +int a_Imgbuf_last_reference(void *v_imgbuf); +void a_Imgbuf_update(void *v_imgbuf, const uchar_t *buf, DilloImgType type, + uchar_t *cmap, uint_t width, uint_t height, uint_t y); +void a_Imgbuf_new_scan(void *v_imgbuf); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __IMGBUF_HH__ */ + @@ -32,7 +32,6 @@ #endif #include "image.hh" -#include "web.hh" #include "cache.h" #include "dicache.h" #include "capi.h" /* get cache entry status */ @@ -81,57 +80,32 @@ typedef struct 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); + if (prefs.show_msg) { + DilloJpeg *jpeg = + ((my_source_mgr *) ((j_decompress_ptr) cinfo)->src)->jpeg; + MSG_WARN("\"%s\": ", URL_STR(jpeg->url)); + (*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) + * Free the jpeg-decoding data structure. */ -void *a_Jpeg_image(const char *Type, void *P, CA_Callback_t *Call, - void **Data) +static void Jpeg_free(DilloJpeg *jpeg) { - 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); + _MSG("Jpeg_free: jpeg=%p\n", jpeg); + jpeg_destroy_decompress(&(jpeg->cinfo)); + dFree(jpeg); } /* @@ -139,12 +113,17 @@ void *a_Jpeg_image(const char *Type, void *P, CA_Callback_t *Call, */ static void Jpeg_close(DilloJpeg *jpeg, CacheClient_t *Client) { + _MSG("Jpeg_close\n"); a_Dicache_close(jpeg->url, jpeg->version, Client); - jpeg_destroy_decompress(&(jpeg->cinfo)); - dFree(jpeg); + Jpeg_free(jpeg); } -static void init_source(j_decompress_ptr cinfo) +/* + * The proper signature is: + * static void init_source(j_decompress_ptr cinfo) + * (declaring it with no parameter avoids a compiler warning) + */ +static void init_source() { } @@ -196,14 +175,20 @@ static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) } } -static void term_source(j_decompress_ptr cinfo) +/* + * The proper signature is: + * static void term_source(j_decompress_ptr cinfo) + * (declaring it with no parameter avoids a compiler warning) + */ +static void term_source() { } -static DilloJpeg *Jpeg_new(DilloImage *Image, DilloUrl *url, int version) +void *a_Jpeg_new(DilloImage *Image, DilloUrl *url, int version) { my_source_mgr *src; DilloJpeg *jpeg = dMalloc(sizeof(*jpeg)); + _MSG("a_Jpeg_new: jpeg=%p\n", jpeg); jpeg->Image = Image; jpeg->url = url; @@ -236,12 +221,17 @@ static DilloJpeg *Jpeg_new(DilloImage *Image, DilloUrl *url, int version) return jpeg; } -static void Jpeg_callback(int Op, CacheClient_t *Client) +void a_Jpeg_callback(int Op, void *data) { - if (Op) - Jpeg_close(Client->CbData, Client); - else + if (Op == CA_Send) { + CacheClient_t *Client = data; Jpeg_write(Client->CbData, Client->Buf, Client->BufSize); + } else if (Op == CA_Close) { + CacheClient_t *Client = data; + Jpeg_close(Client->CbData, Client); + } else if (Op == CA_Abort) { + Jpeg_free(data); + } } /* @@ -278,14 +268,18 @@ static void Jpeg_write(DilloJpeg *jpeg, void *Buf, uint_t BufSize) /* 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) + if (jpeg->cinfo.num_components == 1) { type = DILLO_IMG_TYPE_GRAY; - else if (jpeg->cinfo.num_components == 3) + } else if (jpeg->cinfo.num_components == 3) { type = DILLO_IMG_TYPE_RGB; - else - _MSG("jpeg: can't handle %d component images\n", - jpeg->cinfo.num_components); - + } else { + MSG("4-component JPEG!\n"); + if (jpeg->cinfo.jpeg_color_space == JCS_YCCK) + MSG("YCCK. Are the colors wrong?\n"); + if (!jpeg->cinfo.saw_Adobe_marker) + MSG("No adobe marker! Is the image shown in reverse video?\n"); + type = DILLO_IMG_TYPE_CMYK_INV; + } /* * If a multiple-scan image is not completely in cache, * use progressive display, updating as it arrives. @@ -294,6 +288,17 @@ static void Jpeg_write(DilloJpeg *jpeg, void *Buf, uint_t BufSize) !(a_Capi_get_flags(jpeg->url) & CAPI_Completed)) jpeg->cinfo.buffered_image = TRUE; + /* check max image size */ + if (jpeg->cinfo.image_width <= 0 || jpeg->cinfo.image_height <= 0 || + jpeg->cinfo.image_width > + IMAGE_MAX_AREA / jpeg->cinfo.image_height) { + MSG("Jpeg_write: suspicious image size request %ux%u\n", + (uint_t)jpeg->cinfo.image_width, + (uint_t)jpeg->cinfo.image_height); + jpeg->state = DILLO_JPEG_ERROR; + return; + } + a_Dicache_set_parms(jpeg->url, jpeg->version, jpeg->Image, (uint_t)jpeg->cinfo.image_width, (uint_t)jpeg->cinfo.image_height, @@ -330,7 +335,7 @@ static void Jpeg_write(DilloJpeg *jpeg, void *Buf, uint_t BufSize) if (jpeg->state == DILLO_JPEG_READ_BEGIN_SCAN) { if (jpeg_start_output(&jpeg->cinfo, jpeg->cinfo.input_scan_number)) { - a_Dicache_new_scan(jpeg->Image, jpeg->url, jpeg->version); + a_Dicache_new_scan(jpeg->url, jpeg->version); jpeg->state = DILLO_JPEG_READ_IN_SCAN; } } @@ -346,8 +351,7 @@ static void Jpeg_write(DilloJpeg *jpeg, void *Buf, uint_t BufSize) /* out of input */ break; } - a_Dicache_write(jpeg->Image, jpeg->url, jpeg->version, - linebuf, jpeg->y); + a_Dicache_write(jpeg->url, jpeg->version, linebuf, jpeg->y); jpeg->y++; @@ -389,7 +393,7 @@ static void Jpeg_write(DilloJpeg *jpeg, void *Buf, uint_t BufSize) /* out of input */ break; } - a_Dicache_new_scan(jpeg->Image, jpeg->url, jpeg->version); + a_Dicache_new_scan(jpeg->url, jpeg->version); jpeg->state = DILLO_JPEG_READ_IN_SCAN; } } @@ -398,4 +402,9 @@ static void Jpeg_write(DilloJpeg *jpeg, void *Buf, uint_t BufSize) } } +#else /* ENABLE_JPEG */ + +void *a_Jpeg_new() { return 0; } +void a_Jpeg_callback() { return; } + #endif /* ENABLE_JPEG */ diff --git a/src/keys.cc b/src/keys.cc new file mode 100644 index 00000000..1a39f4c8 --- /dev/null +++ b/src/keys.cc @@ -0,0 +1,363 @@ +/* + * Key parser + * + * Copyright (C) 2009 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 <fltk/events.h> +#include <stdio.h> +#include <stdlib.h> /* strtol */ +#include <string.h> +#include <ctype.h> + +#include "dlib/dlib.h" +#include "keys.hh" +#include "msg.h" + +/* + * Local data types + */ +typedef struct { + const char *name; + KeysCommand_t cmd; + int modifier, key; +} KeyBinding_t; + +typedef struct { + const char *name; + const int value; +} Mapping_t; + + +/* + * Local data + */ +static const Mapping_t keyNames[] = { + { "Backspace", fltk::BackSpaceKey }, + { "Delete", fltk::DeleteKey }, + { "Down", fltk::DownKey }, + { "End", fltk::EndKey }, + { "Esc", fltk::EscapeKey }, + { "F1", fltk::F1Key }, + { "F2", fltk::F2Key }, + { "F3", fltk::F3Key }, + { "F4", fltk::F4Key }, + { "F5", fltk::F5Key }, + { "F6", fltk::F6Key }, + { "F7", fltk::F7Key }, + { "F8", fltk::F8Key }, + { "F9", fltk::F9Key }, + { "F10", fltk::F10Key }, + { "F11", fltk::F11Key }, + { "F12", fltk::F12Key }, + { "Home", fltk::HomeKey }, + { "Insert", fltk::InsertKey }, + { "Left", fltk::LeftKey }, + { "PageDown", fltk::PageDownKey }, + { "PageUp", fltk::PageUpKey }, + { "Print", fltk::PrintKey }, + { "Return", fltk::ReturnKey }, + { "Right", fltk::RightKey }, + { "Space", fltk::SpaceKey }, + { "Tab", fltk::TabKey }, + { "Up", fltk::UpKey } +}; + +static const Mapping_t modifierNames[] = { + { "Shift", fltk::SHIFT }, + { "Ctrl", fltk::CTRL }, + { "Alt", fltk::ALT }, + { "Meta", fltk::META }, + { "Button1", fltk::BUTTON1 }, + { "Button2", fltk::BUTTON2 }, + { "Button3", fltk::BUTTON3 } +}; + +static const KeyBinding_t default_keys[] = { + { "nop" , KEYS_NOP , 0 , 0 }, + { "open" , KEYS_OPEN , fltk::CTRL , 'o' }, + { "new-window" , KEYS_NEW_WINDOW , fltk::CTRL , 'n' }, + { "new-tab" , KEYS_NEW_TAB , fltk::CTRL , 't' }, + { "left-tab" , KEYS_LEFT_TAB , fltk::SHIFT , fltk::TabKey }, + { "right-tab" , KEYS_RIGHT_TAB , fltk::CTRL , fltk::TabKey }, + { "close-tab" , KEYS_CLOSE_TAB , fltk::CTRL , 'q' }, + { "find" , KEYS_FIND , fltk::CTRL , 'f' }, + { "websearch" , KEYS_WEBSEARCH , fltk::CTRL , 's' }, + { "bookmarks" , KEYS_BOOKMARKS , fltk::CTRL , 'b' }, + { "fullscreen" , KEYS_FULLSCREEN , fltk::CTRL , fltk::SpaceKey }, + { "reload" , KEYS_RELOAD , fltk::CTRL , 'r' }, + { "stop" , KEYS_STOP , 0 , 0 }, + { "save" , KEYS_SAVE , 0 , 0 }, + { "hide-panels" , KEYS_HIDE_PANELS , 0 , fltk::EscapeKey }, + { "file-menu" , KEYS_FILE_MENU , fltk::ALT , 'f' }, + { "close-all" , KEYS_CLOSE_ALL , fltk::ALT , 'q' }, + { "back" , KEYS_BACK , 0 , fltk::BackSpaceKey }, + { "back" , KEYS_BACK , 0 , ',' }, + { "forward" , KEYS_FORWARD , fltk::SHIFT , fltk::BackSpaceKey }, + { "forward" , KEYS_FORWARD , 0 , '.' }, + { "goto" , KEYS_GOTO , fltk::CTRL , 'l' }, + { "home" , KEYS_HOME , fltk::CTRL , 'h' }, + { "screen-up" , KEYS_SCREEN_UP , 0 , fltk::PageUpKey }, + { "screen-up" , KEYS_SCREEN_UP , 0 , 'b' }, + { "screen-down" , KEYS_SCREEN_DOWN , 0 , fltk::PageDownKey }, + { "screen-down" , KEYS_SCREEN_DOWN , 0 , fltk::SpaceKey }, + { "line-up" , KEYS_LINE_UP , 0 , fltk::UpKey }, + { "line-down" , KEYS_LINE_DOWN , 0 , fltk::DownKey }, + { "left" , KEYS_LEFT , 0 , fltk::LeftKey }, + { "right" , KEYS_RIGHT , 0 , fltk::RightKey }, + { "top" , KEYS_TOP , 0 , fltk::HomeKey }, + { "bottom" , KEYS_BOTTOM , 0 , fltk::EndKey }, +}; + +static Dlist *bindings; + + + +/* + * Initialize the bindings list + */ +void Keys::init() +{ + KeyBinding_t *node; + + // Fill our key bindings list + bindings = dList_new(32); + for (uint_t i = 0; i < sizeof(default_keys) / sizeof(KeyBinding_t); i++) { + if (default_keys[i].key) { + node = dNew(KeyBinding_t, 1); + node->name = dStrdup(default_keys[i].name); + node->cmd = default_keys[i].cmd; + node->modifier = default_keys[i].modifier; + node->key = default_keys[i].key; + dList_insert_sorted(bindings, node, nodeByKeyCmp); + } + } +} + +/* + * Free data + */ +void Keys::free() +{ + KeyBinding_t *node; + + while ((node = (KeyBinding_t*)dList_nth_data(bindings, 0))) { + dFree((char*)node->name); + dList_remove_fast(bindings, node); + dFree(node); + } + dList_free(bindings); +} + +/* + * Compare function by {key,modifier} pairs. + */ +int Keys::nodeByKeyCmp(const void *node, const void *key) +{ + KeyBinding_t *n = (KeyBinding_t*)node, *k = (KeyBinding_t*)key; + _MSG("Keys::nodeByKeyCmp modifier=%d\n", k->modifier); + return (n->key != k->key) ? (n->key - k->key) : (n->modifier - k->modifier); +} + +/* + * Look if the just pressed key is bound to a command. + * Return value: The command if found, KEYS_NOP otherwise. + */ +KeysCommand_t Keys::getKeyCmd() +{ + KeysCommand_t ret = KEYS_NOP; + KeyBinding_t keyNode; + // We're only interested in some flags + keyNode.modifier = fltk::event_state() & + (fltk::SHIFT | fltk::CTRL | fltk::ALT | fltk::META); + + if (keyNode.modifier == fltk::SHIFT && + ispunct(fltk::event_text()[0])) { + // Get key code for a shifted character + keyNode.key = fltk::event_text()[0]; + keyNode.modifier = 0; + } else { + keyNode.key = fltk::event_key(); + } + + _MSG("getKeyCmd: key=%d, mod=%d\n", keyNode.key, keyNode.modifier); + void *data = dList_find_sorted(bindings, &keyNode, nodeByKeyCmp); + if (data) + ret = ((KeyBinding_t*)data)->cmd; + return ret; +} + +/* + * Remove a key binding from the table. + */ +void Keys::delKeyCmd(int key, int mod) +{ + KeyBinding_t keyNode, *node; + keyNode.key = key; + keyNode.modifier = mod; + + node = (KeyBinding_t*) dList_find_sorted(bindings, &keyNode, nodeByKeyCmp); + if (node) { + dList_remove(bindings, node); + dFree((char*)node->name); + dFree(node); + } +} + +/* + * Takes a key name and looks it up in the mapping table. If + * found, its key code is returned. Otherwise -1 is given + * back. + */ +int Keys::getKeyCode(char *keyName) +{ + uint_t i; + for (i = 0; i < sizeof(keyNames) / sizeof(Mapping_t); i++) { + if (!dStrcasecmp(keyNames[i].name, keyName)) { + return keyNames[i].value; + } + } + + return -1; +} + +/* + * Takes a command name and searches it in the mapping table. + * Return value: command code if found, -1 otherwise + */ +KeysCommand_t Keys::getCmdCode(const char *commandName) +{ + uint_t i; + + for (i = 0; i < sizeof(default_keys) / sizeof(KeyBinding_t); i++) { + if (!dStrcasecmp(default_keys[i].name, commandName)) + return default_keys[i].cmd; + } + return KEYS_INVALID; +} + +/* + * Takes a modifier name and looks it up in the mapping table. If + * found, its key code is returned. Otherwise -1 is given back. + */ +int Keys::getModifier(char *modifierName) +{ + uint_t i; + for (i = 0; i < sizeof(modifierNames) / sizeof(Mapping_t); i++) { + if (!dStrcasecmp(modifierNames[i].name, modifierName)) { + return modifierNames[i].value; + } + } + + return -1; +} + +/* + * Given a keys command, return a shortcut for it, or 0 if there is none + * (e.g., for KEYS_NEW_WINDOW, return CTRL+'n'). + */ +int Keys::getShortcut(KeysCommand_t cmd) +{ + int len = dList_length(bindings); + + for (int i = 0; i < len; i++) { + KeyBinding_t *node = (KeyBinding_t*)dList_nth_data(bindings, i); + if (cmd == node->cmd) + return node->modifier + node->key; + } + return 0; +} + +/* + * Parse a key-combination/command-name pair, and + * insert it into the bindings list. + */ +void Keys::parseKey(char *key, char *commandName) +{ + char *p, *modstr, *keystr; + KeysCommand_t symcode; + int st, keymod = 0, keycode = 0; + + _MSG("Keys::parseKey key='%s' commandName='%s'\n", key, commandName); + + // Get command code + if ((symcode = getCmdCode(commandName)) == KEYS_INVALID) { + MSG("Keys::parseKey: Invalid command name: '%s'\n", commandName); + return; + } + + // Skip space + for ( ; isspace(*key); ++key) ; + // Get modifiers + while(*key == '<' && (p = strchr(key, '>'))) { + ++key; + modstr = dStrndup(key, p - key); + if ((st = getModifier(modstr)) == -1) { + MSG("Keys::parseKey unknown modifier: %s\n", modstr); + } else { + keymod |= st; + } + dFree(modstr); + key = p + 1; + } + // Allow trailing space after keyname + keystr = (*key && (p = strchr(key + 1, ' '))) ? dStrndup(key, p - key - 1) : + dStrdup(key); + // Get key code + if (!key[1]) { + keycode = *key; + } else if (key[0] == '0' && key[1] == 'x') { + /* keysym. For details on values reported, see fltk's fltk/events.h */ + keycode = strtol(key, NULL, 0x10); + } else if ((st = getKeyCode(keystr)) == -1) { + MSG("Keys::parseKey unknown keyname: %s\n", keystr); + } else { + keycode = st; + } + dFree(keystr); + + // Set binding + if (keycode) { + delKeyCmd(keycode, keymod); + if (symcode != KEYS_NOP) { + KeyBinding_t *node = dNew(KeyBinding_t, 1); + node->name = dStrdup(commandName); + node->cmd = symcode; + node->modifier = keymod; + node->key = keycode; + dList_insert_sorted(bindings, node, nodeByKeyCmp); + _MSG("parseKey: Adding key=%d, mod=%d\n", node->key, node->modifier); + } + } +} + +/* + * Parse the keysrc. + */ +void Keys::parse(FILE *fp) +{ + char *line, *keycomb, *command; + int st, lineno = 1; + + // scan the file line by line + while ((line = dGetline(fp)) != NULL) { + st = dParser_parse_rc_line(&line, &keycomb, &command); + + if (st == 0) { + _MSG("Keys::parse: keycomb=%s, command=%s\n", keycomb, command); + parseKey(keycomb, command); + } else if (st < 0) { + MSG("Keys::parse: Syntax error in keysrc line %d: " + "keycomb=\"%s\" command=\"%s\"\n", lineno, keycomb, command); + } + + dFree(line); + ++lineno; + } + fclose(fp); +} diff --git a/src/keys.hh b/src/keys.hh new file mode 100644 index 00000000..d234838c --- /dev/null +++ b/src/keys.hh @@ -0,0 +1,68 @@ +/* + * Key parser + * + * Copyright (C) 2009 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. + */ + +#ifndef __KEYS_HH__ +#define __KEYS_HH__ + + +typedef enum { + KEYS_INVALID = -1, + KEYS_NOP, /* No operation bound */ + KEYS_OPEN, + KEYS_NEW_WINDOW, + KEYS_NEW_TAB, + KEYS_LEFT_TAB, + KEYS_RIGHT_TAB, + KEYS_CLOSE_TAB, + KEYS_FIRST_TAB, + KEYS_LAST_TAB, + KEYS_FIND, + KEYS_WEBSEARCH, + KEYS_BOOKMARKS, + KEYS_FULLSCREEN, + KEYS_RELOAD, + KEYS_STOP, + KEYS_SAVE, + KEYS_HIDE_PANELS, + KEYS_FILE_MENU, + KEYS_CLOSE_ALL, + KEYS_BACK, + KEYS_FORWARD, + KEYS_GOTO, + KEYS_HOME, + KEYS_SCREEN_UP, + KEYS_SCREEN_DOWN, + KEYS_LINE_UP, + KEYS_LINE_DOWN, + KEYS_LEFT, + KEYS_RIGHT, + KEYS_TOP, + KEYS_BOTTOM +} KeysCommand_t; + +class Keys { +private: + static int nodeByKeyCmp(const void *node, const void *key); + static void delKeyCmd(int key, int mod); + static KeysCommand_t getCmdCode(const char *symbolName); + static int getKeyCode(char *keyName); + static int getModifier(char *modifierName); + static void parseKey(char *key, char *symbol); +public: + static void init(); + static void free(); + static void parse(FILE *fp); + static KeysCommand_t getKeyCmd(void); + static int getShortcut(KeysCommand_t cmd); +}; + + +#endif /* __KEYS_HH__ */ diff --git a/src/keysrc b/src/keysrc new file mode 100644 index 00000000..3f53674f --- /dev/null +++ b/src/keysrc @@ -0,0 +1,101 @@ +# keysrc +# Sample dillo key bindings file. +# +# The format is: "key = action" or "<modifier>key = action". +# Lines that begin with a '#' are comments. +# The commented-out bindings below show the defaults built into Dillo. +# +# Modifiers recognized: "Shift", "Ctrl", "Alt", "Meta". +# Key names recognized: "Backspace", "Delete", "Down", "End", "Esc", +# "F1" through "F12", "Home", "Insert", "Left", "PageDown", "PageUp", +# "Print", "Return", "Right", "Space", "Tab", "Up". +# +# If Dillo is running under X11, keys whose names are not recognized can +# be specified using their keysym value in hexadecimal. Use xev to get +# the keysym. Example rule: "0x1008ff27 = forward". +# +# The action "nop" (no operation) can be used to remove a binding. + +# "open" lets you browse your local files for one to open. +#<ctrl>o = open + +# "new-window" opens a new browser window. +#<ctrl>n = new-window + +# "new-tab" opens a new tab in the current browser window. +#<ctrl>t = new-tab + +# "close-tab" closes the current tab. +# Note that this closes the browser window if there is only one tab. +#<ctrl>q = close-tab + +# "close-all" closes all tabs/windows and exits. +#<alt>q = close-all + +# "left-tab" and "right-tab" switch to the left/right of the current tab. +# *** NOT HOOKED UP YET *** +# <shift>tab = left-tab +# <ctrl>tab = right-tab + +# "back" and "forward" move back/forward through the browser history. +#backspace = back +#<shift>backspace = forward +#, = back +#. = forward + +# "reload" the current page. +#<ctrl>r = reload + +# "home" goes to the homepage that you set in your dillorc. +#<ctrl>h = home + +# "find" lets you search for a text string on the current page. +#<ctrl>f = find + +# "hide-panels" hides the findbar. +#esc = hide-panels + +# "websearch" lets you send a text string to the search engine that you +# set in your dillorc. +#<ctrl>s = websearch + +# go to your "bookmarks". +#<ctrl>b = bookmarks + +# "fullscreen" hides/shows the panels at the top and bottom of a dillo window. +#<ctrl>space = fullscreen + +# "file-menu" pops up the file menu. +#<alt>f = file-menu + +# "goto" goes to the location bar at the top of the window. +#<ctrl>l = goto + +# "stop" loading the page. +#(stop has no default binding) + +# "save" the current page. +#(save has no default binding) + +#-------------------------------------------------------------------- +# MOTION COMMANDS +#-------------------------------------------------------------------- + +#pageup = screen-up +#b = screen-up + +#pagedown = screen-down +#space = screen-down + +#up = line-up + +#down = line-down + +#left = left + +#right = right + +#home = top + +#end = bottom + diff --git a/src/klist.c b/src/klist.c index a0652499..813269a3 100644 --- a/src/klist.c +++ b/src/klist.c @@ -117,9 +117,10 @@ void a_Klist_free(Klist_t **KlistPtr) while (dList_length(Klist->List) > 0) { node = dList_nth_data(Klist->List, 0); - dList_remove_fast(Klist->List, 0); + dList_remove_fast(Klist->List, node); dFree(node); } + dList_free(Klist->List); dFree(Klist); *KlistPtr = NULL; } diff --git a/src/menu.cc b/src/menu.cc index 6856143e..d802bddb 100644 --- a/src/menu.cc +++ b/src/menu.cc @@ -11,19 +11,20 @@ // Functions/Methods for menus -#include <stdio.h> -#include <stdarg.h> #include <fltk/events.h> #include <fltk/PopupMenu.h> #include <fltk/Item.h> +#include <fltk/ToggleItem.h> #include <fltk/Divider.h> +#include "lout/misc.hh" /* SimpleVector */ #include "msg.h" #include "menu.hh" #include "uicmd.hh" #include "history.h" #include "html.hh" #include "ui.hh" // for (UI *) +#include "keys.hh" #include "timeout.hh" using namespace fltk; @@ -38,9 +39,9 @@ static DilloUrl *popup_url = NULL; // Weak reference to the popup's bw static BrowserWindow *popup_bw = NULL; // Where to place the filemenu popup -int popup_x, popup_y; +static int popup_x, popup_y; // History popup direction (-1 = back, 1 = forward). -static int history_direction = -1; +static int history_direction = -1; // History popup, list of URL-indexes. static int *history_list = NULL; @@ -66,7 +67,7 @@ public: * TODO: erase the URL on popup close. */ void CustItem::draw() { - DilloUrl *url; + const DilloUrl *url; if (flags() & SELECTED) { url = a_History_get_url(history_list[(VOIDP2INT(user_data()))-1]); @@ -84,7 +85,7 @@ static void filemenu_cb(Widget *wid, void *data) { if (strcmp((char*)data, "nw") == 0) { UI *ui = (UI*)popup_bw->ui; - a_UIcmd_browser_window_new(ui->w(), ui->h(), popup_bw); + a_UIcmd_browser_window_new(ui->w(), ui->h(), 0, popup_bw); } else if (strcmp((char*)data, "nt") == 0) { a_UIcmd_open_url_nt(popup_bw, NULL, 1); } else if (strcmp((char*)data, "of") == 0) { @@ -108,22 +109,22 @@ static void Menu_copy_urlstr_cb(Widget *) static void Menu_link_cb(Widget*, void *user_data) { DilloUrl *url = (DilloUrl *) user_data ; - MSG("Menu_link_cb: click! :-)\n"); + _MSG("Menu_link_cb: click! :-)\n"); if (url) a_Menu_link_popup(popup_bw, url); } -/* +/* * Open URL */ static void Menu_open_url_cb(Widget* ) { - MSG("Open URL cb: click! :-)\n"); + _MSG("Open URL cb: click! :-)\n"); a_UIcmd_open_url(popup_bw, popup_url); } -/* +/* * Open URL in new window */ static void Menu_open_url_nw_cb(Widget* ) @@ -132,7 +133,7 @@ static void Menu_open_url_nw_cb(Widget* ) a_UIcmd_open_url_nw(popup_bw, popup_url); } -/* +/* * Open URL in new Tab */ static void Menu_open_url_nt_cb(Widget* ) @@ -142,7 +143,7 @@ static void Menu_open_url_nt_cb(Widget* ) a_UIcmd_open_url_nt(popup_bw, popup_url, focus); } -/* +/* * Add bookmark */ static void Menu_add_bookmark_cb(Widget* ) @@ -150,7 +151,7 @@ static void Menu_add_bookmark_cb(Widget* ) a_UIcmd_add_bookmark(popup_bw, popup_url); } -/* +/* * Find text */ static void Menu_find_text_cb(Widget* ) @@ -158,7 +159,7 @@ static void Menu_find_text_cb(Widget* ) ((UI *)popup_bw->ui)->set_findbar_visibility(1); } -/* +/* * Save link */ static void Menu_save_link_cb(Widget* ) @@ -166,7 +167,7 @@ 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* ) @@ -174,15 +175,15 @@ static void Menu_save_page_cb(Widget* ) a_UIcmd_save(popup_bw); } -/* - * Save current page +/* + * View current page source */ static void Menu_view_page_source_cb(Widget* ) { - a_UIcmd_view_page_source(popup_url); + a_UIcmd_view_page_source(popup_bw, popup_url); } -/* +/* * View current page's bugs */ static void Menu_view_page_bugs_cb(Widget* ) @@ -192,23 +193,58 @@ static void Menu_view_page_bugs_cb(Widget* ) /* * Load images on current page that match URL pattern - * - * BUG: assumes that the document is a DilloHtml. */ static void Menu_load_images_cb(Widget*, void *user_data) { - DilloUrl *pattern = (DilloUrl *) user_data ; + DilloUrl *page_url = (DilloUrl *) user_data; + void *doc = a_Bw_get_url_doc(popup_bw, page_url); - if (popup_bw && popup_bw->Docs) { - int i, n; - n = dList_length(popup_bw->Docs); - for (i = 0; i < n; i++) { - a_Html_load_images(dList_nth_data(popup_bw->Docs, i), pattern); - } - } + if (doc) + a_Html_load_images(doc, popup_url); +} + +/* + * Submit form + */ +static void Menu_form_submit_cb(Widget*, void *v_form) +{ + void *doc = a_Bw_get_url_doc(popup_bw, popup_url); + + if (doc) + a_Html_form_submit(doc, v_form); +} + +/* + * Reset form + */ +static void Menu_form_reset_cb(Widget*, void *v_form) +{ + void *doc = a_Bw_get_url_doc(popup_bw, popup_url); + + if (doc) + a_Html_form_reset(doc, v_form); +} + +/* + * Toggle display of 'hidden' form controls. + */ +static void Menu_form_hiddens_cb(Widget *w, void *user_data) +{ + void *v_form = w->parent()->user_data(); + bool visible = *((bool *) user_data); + void *doc = a_Bw_get_url_doc(popup_bw, popup_url); + + if (doc) + a_Html_form_display_hiddens(doc, v_form, !visible); +} + +static void Menu_stylesheet_cb(Widget *w, void *vUrl) +{ + const DilloUrl *url = (const DilloUrl *) vUrl; + a_UIcmd_open_url(popup_bw, url); } -/* +/* * Validate URL with the W3C */ static void Menu_bugmeter_validate_w3c_cb(Widget* ) @@ -221,7 +257,7 @@ static void Menu_bugmeter_validate_w3c_cb(Widget* ) dStr_free(dstr, 1); } -/* +/* * Validate URL with the WDG */ static void Menu_bugmeter_validate_wdg_cb(Widget* ) @@ -235,7 +271,7 @@ static void Menu_bugmeter_validate_wdg_cb(Widget* ) dStr_free(dstr, 1); } -/* +/* * Show info page for the bug meter */ static void Menu_bugmeter_about_cb(Widget* ) @@ -251,7 +287,7 @@ static void Menu_history_cb(Widget *wid, void *data) { int mb = ((CustItem*)wid)->button(); int offset = history_direction * VOIDP2INT(data); - DilloUrl *url = a_History_get_url(history_list[VOIDP2INT(data)-1]); + const DilloUrl *url = a_History_get_url(history_list[VOIDP2INT(data)-1]); if (mb == 2) { // Middle button, open in a new window/tab @@ -293,38 +329,41 @@ static void Menu_popup_cb2(void *data) /* * Page popup menu (construction & popup) */ -void a_Menu_page_popup(BrowserWindow *bw, const DilloUrl *url, - bool_t has_bugs, bool_t unloaded_imgs) +void a_Menu_page_popup(BrowserWindow *bw, const DilloUrl *url, + bool_t has_bugs, void *v_cssUrls) { + lout::misc::SimpleVector <DilloUrl*> *cssUrls = + (lout::misc::SimpleVector <DilloUrl*> *) v_cssUrls; + Item *i; + int j; // One menu for every browser window static PopupMenu *pm = 0; // Active/inactive control. - static Item *view_page_bugs_item = 0; - static Item *load_images_item = 0; + static Item *view_page_bugs_item = 0, *view_source_item = 0; + static ItemGroup *stylesheets = 0; popup_bw = bw; a_Url_free(popup_url); popup_url = a_Url_dup(url); if (!pm) { - Item *i; pm = new PopupMenu(0,0,0,0,"&PAGE OPTIONS"); pm->begin(); - i = new Item("View page Source"); + i = view_source_item = new Item("View page Source"); i->callback(Menu_view_page_source_cb); i = view_page_bugs_item = new Item("View page Bugs"); i->callback(Menu_view_page_bugs_cb); - i = load_images_item = new Item("Load images"); - i->callback(Menu_load_images_cb); + stylesheets = new ItemGroup("View Stylesheets"); + new Divider(); i = new Item("Bookmark this page"); i->callback(Menu_add_bookmark_cb); - new Divider(); + 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(); + new Divider(); i = new Item("Save page As..."); i->callback(Menu_save_page_cb); @@ -337,11 +376,49 @@ void a_Menu_page_popup(BrowserWindow *bw, const DilloUrl *url, else view_page_bugs_item->deactivate(); - if (unloaded_imgs == TRUE) { - load_images_item->activate(); - load_images_item->user_data(NULL); /* wildcard */ + if (strncmp(URL_STR(url), "dpi:/vsource/", 13) == 0) + view_source_item->deactivate(); + else + view_source_item->activate(); + + int n = stylesheets->children(); + for (j = 0; j < n; j++) { + /* get rid of the old ones */ + Widget *child = stylesheets->child(0); + dFree((char *)child->label()); + a_Url_free((DilloUrl *)child->user_data()); + delete child; + } + + if (cssUrls && cssUrls->size () > 0) { + stylesheets->activate(); + for (j = 0; j < cssUrls->size(); j++) { + /* may want ability to Load individual unloaded stylesheets as well */ + const char *action = "View "; + DilloUrl *url = cssUrls->get(j); + const char *url_str = URL_STR(url); + const uint_t head_length = 30, tail_length = 40, + url_len = strlen(url_str);; + char *label; + + if (url_len > head_length + tail_length + 3) { + /* trim long URLs when making the label */ + char *url_head = dStrndup(url_str, head_length); + const char *url_tail = url_str + (url_len - tail_length); + label = dStrconcat(action, url_head, "...", url_tail, NULL); + dFree(url_head); + } else { + label = dStrconcat(action, url_str, NULL); + } + + i = new Item(label); + i->set_flag(RAW_LABEL); + i->user_data(a_Url_dup(url)); + i->callback(Menu_stylesheet_cb); + stylesheets->add(i); + } } else { - load_images_item->deactivate(); + stylesheets->deactivate(); } a_Timeout_add(0.0, Menu_popup_cb, (void *)pm); @@ -367,12 +444,12 @@ void a_Menu_link_popup(BrowserWindow *bw, const DilloUrl *url) i->callback(Menu_open_url_nw_cb); i = new Item("Open Link in New Tab"); i->callback(Menu_open_url_nt_cb); - new Divider(); + new Divider(); i = new Item("Bookmark this Link"); i->callback(Menu_add_bookmark_cb); i = new Item("Copy Link location"); i->callback(Menu_copy_urlstr_cb); - new Divider(); + new Divider(); i = new Item("Save Link As..."); i->callback(Menu_save_link_cb); @@ -387,21 +464,25 @@ void a_Menu_link_popup(BrowserWindow *bw, const DilloUrl *url) * Image popup menu (construction & popup) */ void a_Menu_image_popup(BrowserWindow *bw, const DilloUrl *url, - bool_t loaded_img, DilloUrl *link_url) + bool_t loaded_img, DilloUrl *page_url, + DilloUrl *link_url) { // One menu for every browser window static PopupMenu *pm = 0; // Active/inactive control. static Item *link_menuitem = 0; static Item *load_img_menuitem = 0; + static DilloUrl *popup_page_url = NULL; static DilloUrl *popup_link_url = NULL; popup_bw = bw; a_Url_free(popup_url); popup_url = a_Url_dup(url); + a_Url_free(popup_page_url); + popup_page_url = a_Url_dup(page_url); a_Url_free(popup_link_url); popup_link_url = a_Url_dup(link_url); - + if (!pm) { Item *i; pm = new PopupMenu(0,0,0,0,"&IMAGE OPTIONS"); @@ -434,7 +515,7 @@ void a_Menu_image_popup(BrowserWindow *bw, const DilloUrl *url, load_img_menuitem->deactivate(); } else { load_img_menuitem->activate(); - load_img_menuitem->user_data(popup_url); + load_img_menuitem->user_data(popup_page_url); } if (link_url) { @@ -448,6 +529,39 @@ void a_Menu_image_popup(BrowserWindow *bw, const DilloUrl *url, } /* + * Form popup menu (construction & popup) + */ +void a_Menu_form_popup(BrowserWindow *bw, const DilloUrl *page_url, + void *formptr, bool_t hidvis) +{ + static PopupMenu *pm = 0; + static Item *hiddens_item = 0; + static bool hiddens_visible; + + popup_bw = bw; + a_Url_free(popup_url); + popup_url = a_Url_dup(page_url); + if (!pm) { + Item *i; + pm = new PopupMenu(0,0,0,0,"FORM OPTIONS"); + pm->add(i = new Item("Submit form")); + i->callback(Menu_form_submit_cb); + pm->add(i = new Item("Reset form")); + i->callback(Menu_form_reset_cb); + pm->add(hiddens_item = new Item("")); + hiddens_item->callback(Menu_form_hiddens_cb); + hiddens_item->user_data(&hiddens_visible); + pm->type(PopupMenu::POPUP123); + } + pm->user_data(formptr); + + hiddens_visible = hidvis; + hiddens_item->label(hiddens_visible ? "Hide hiddens": "Show hiddens"); + + a_Timeout_add(0.0, Menu_popup_cb, (void *)pm); +} + +/* * File popup menu (construction & popup) */ void a_Menu_file_popup(BrowserWindow *bw, void *v_wid) @@ -466,21 +580,29 @@ void a_Menu_file_popup(BrowserWindow *bw, void *v_wid) popup_url = NULL; if (!pm) { + int shortcut; Item *i; pm = new PopupMenu(0,0,0,0,"File"); pm->begin(); - i = new Item("New Window", CTRL+'n', filemenu_cb, (void*)"nw"); - i = new Item("New Tab", CTRL+'t', filemenu_cb, (void*)"nt"); + shortcut = Keys::getShortcut(KEYS_NEW_WINDOW); + i = new Item("New Window", shortcut, filemenu_cb, (void*)"nw"); + shortcut = Keys::getShortcut(KEYS_NEW_TAB); + i = new Item("New Tab", shortcut, filemenu_cb, (void*)"nt"); new Divider(); - i = new Item("Open File...", CTRL+'o', filemenu_cb, (void*)"of"); - i = new Item("Open URL...", CTRL+'l', filemenu_cb, (void*)"ou"); - i = new Item("Close", CTRL+'q', filemenu_cb, (void*)"cw"); + shortcut = Keys::getShortcut(KEYS_OPEN); + i = new Item("Open File...", shortcut, filemenu_cb, (void*)"of"); + shortcut = Keys::getShortcut(KEYS_GOTO); + i = new Item("Open URL...", shortcut, filemenu_cb, (void*)"ou"); + shortcut = Keys::getShortcut(KEYS_CLOSE_TAB); + i = new Item("Close", shortcut, filemenu_cb, (void*)"cw"); new Divider(); - i = new Item("Exit Dillo", ALT+'q', filemenu_cb, (void*)"ed"); + shortcut = Keys::getShortcut(KEYS_CLOSE_ALL); + i = new Item("Exit Dillo", shortcut, filemenu_cb, (void*)"ed"); pm->type(PopupMenu::POPUP123); pm->end(); } + pm->label(wid->visible() ? NULL : "File"); a_Timeout_add(0.0, Menu_popup_cb2, (void *)pm); } @@ -503,7 +625,7 @@ void a_Menu_bugmeter_popup(BrowserWindow *bw, const DilloUrl *url) i->callback(Menu_bugmeter_validate_w3c_cb); i = new Item("Validate URL with WDG"); i->callback(Menu_bugmeter_validate_wdg_cb); - new Divider(); + new Divider(); i = new Item("About Bug Meter..."); i->callback(Menu_bugmeter_about_cb); pm->type(PopupMenu::POPUP123); @@ -520,7 +642,6 @@ void a_Menu_bugmeter_popup(BrowserWindow *bw, const DilloUrl *url) */ void a_Menu_history_popup(BrowserWindow *bw, int direction) { - // One menu for every browser window static PopupMenu *pm = 0; Item *it; int i; @@ -547,7 +668,7 @@ void a_Menu_history_popup(BrowserWindow *bw, int direction) for (i = 0; history_list[i] != -1; i += 1) { // TODO: restrict title size it = new CustItem(a_History_get_title(history_list[i], 1)); - it->callback(Menu_history_cb, (void*)(i+1)); + it->callback(Menu_history_cb, INT2VOIDP(i+1)); } pm->type(PopupMenu::POPUP123); pm->end(); @@ -555,16 +676,70 @@ void a_Menu_history_popup(BrowserWindow *bw, int direction) pm->popup(); } +/* + * Toggle use of remote stylesheets + */ +static void Menu_remote_css_cb(Widget *wid) +{ + _MSG("Menu_remote_css_cb\n"); + prefs.load_stylesheets = wid->state() ? 1 : 0; + a_UIcmd_repush(popup_bw); +} -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) { } +/* + * Toggle use of embedded CSS style + */ +static void Menu_embedded_css_cb(Widget *wid) +{ + prefs.parse_embedded_css = wid->state() ? 1 : 0; + a_UIcmd_repush(popup_bw); +} + +/* + * Toggle loading of images -- and load them if enabling. + */ +static void Menu_imgload_toggle_cb(Widget *wid) +{ + if ((prefs.load_images = wid->state() ? 1 : 0)) { + void *doc = a_Bw_get_current_doc(popup_bw); -DilloUrl *a_Menu_popup_get_url(BrowserWindow *bw) { return NULL; } + if (doc) { + DilloUrl *pattern = NULL; + a_Html_load_images(doc, pattern); + } + } +} -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) { } +/* + * Tools popup menu (construction & popup) + */ +void a_Menu_tools_popup(BrowserWindow *bw, void *v_wid) +{ + // One menu shared by every browser window + static PopupMenu *pm = NULL; + Widget *wid = (Widget*)v_wid; + Item *it; + + popup_bw = bw; + + if (!pm) { + pm = new PopupMenu(0,0,0,0, "TOOLS"); + pm->begin(); + it = new ToggleItem("Use remote CSS"); + it->callback(Menu_remote_css_cb); + it->state(prefs.load_stylesheets); + it = new ToggleItem("Use embedded CSS"); + it->callback(Menu_embedded_css_cb); + it->state(prefs.parse_embedded_css); + new Divider(); + it = new ToggleItem("Load images"); + it->callback(Menu_imgload_toggle_cb); + it->state(prefs.load_images); + pm->type(PopupMenu::POPUP13); + pm->end(); + } + //pm->popup(); + pm->value(-1); + ((Menu*)pm)->popup(Rectangle(0,wid->h(),pm->w(),pm->h())); +} diff --git a/src/menu.hh b/src/menu.hh index 9e8a73c6..668de001 100644 --- a/src/menu.hh +++ b/src/menu.hh @@ -8,26 +8,17 @@ extern "C" { #endif /* __cplusplus */ void a_Menu_page_popup(BrowserWindow *bw, const DilloUrl *url, - bool_t has_bugs, bool_t unloaded_imgs); + bool_t has_bugs, void *v_cssUrls); void a_Menu_link_popup(BrowserWindow *bw, const DilloUrl *url); void a_Menu_image_popup(BrowserWindow *bw, const DilloUrl *url, - bool_t loaded_img, DilloUrl *link_url); + bool_t loaded_img, DilloUrl *page_url, + DilloUrl *link_url); +void a_Menu_form_popup(BrowserWindow *bw, const DilloUrl *page_url, + void *vform, bool_t showing_hiddens); void a_Menu_file_popup(BrowserWindow *bw, void *v_wid); void a_Menu_bugmeter_popup(BrowserWindow *bw, const 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); +void a_Menu_tools_popup(BrowserWindow *bw, void *v_wid); #ifdef __cplusplus @@ -12,21 +12,21 @@ #include <stdio.h> #include <stdlib.h> -#include <unistd.h> #include <string.h> #include <ctype.h> +#include <assert.h> +#include "utf8.hh" #include "msg.h" #include "misc.h" - /* * Escape characters as %XX sequences. * Return value: New string. */ char *a_Misc_escape_chars(const char *str, const char *esc_set) { - static const char *hex = "0123456789ABCDEF"; + static const char *const hex = "0123456789ABCDEF"; char *p = NULL; Dstr *dstr; int i; @@ -47,34 +47,54 @@ char *a_Misc_escape_chars(const char *str, const char *esc_set) return p; } - #define TAB_SIZE 8 /* * Takes a string and converts any tabs to spaces. */ -char *a_Misc_expand_tabs(const char *str, int len) +int +a_Misc_expand_tabs(char **start, char *end, char *buf, int buflen) { - Dstr *New = dStr_new(""); - int i, j, pos, old_pos; - char *val; - - if (len) { - 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++; - } + int j, pos = 0, written = 0, old_pos, char_len; + uint_t code; + static const int combining_char_space = 32; + + while (*start < end && written < buflen - TAB_SIZE - combining_char_space) { + code = a_Utf8_decode(*start, end, &char_len); + + if (code == '\t') { + /* Fill with whitespaces until the next tab. */ + old_pos = pos; + pos += TAB_SIZE - (pos % TAB_SIZE); + for (j = old_pos; j < pos; j++) + buf[written++] = ' '; + } else { + assert(char_len <= 4); + for (j = 0; j < char_len; j++) + buf[written++] = (*start)[j]; + pos++; } + + *start += char_len; } - val = New->str; - dStr_free(New, FALSE); - return val; + + /* If following chars are combining chars (e.g. accents) add them to the + * buffer. We have reserved combining_char_space bytes for this. + * If there should be more combining chars, we split nevertheless. + */ + while (*start < end && written < buflen - 4) { + code = a_Utf8_decode(*start, end, &char_len); + + if (! a_Utf8_combining_char(code)) + break; + + assert(char_len <= 4); + for (j = 0; j < char_len; j++) + buf[written++] = (*start)[j]; + + *start += char_len; + } + + return written; } /* TODO: could use dStr ADT! */ @@ -120,7 +140,7 @@ int a_Misc_get_content_type_from_data(void *Data, size_t Size, const char **PT) DetectedContentType Type = DT_OCTET_STREAM; /* default to binary */ /* HTML try */ - for (i = 0; i < Size && isspace(p[i]); ++i); + for (i = 0; i < Size && dIsspace(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)) || @@ -150,7 +170,7 @@ int a_Misc_get_content_type_from_data(void *Data, size_t Size, const char **PT) * All in the above set regard [00-31] as control characters. * LATIN1: [7F-9F] unused * CP-1251 {7F,98} unused (two characters). - * + * * We'll use [0-31] as indicators of non-text content. * Better heuristics are welcomed! :-) */ @@ -158,19 +178,23 @@ int a_Misc_get_content_type_from_data(void *Data, size_t Size, const char **PT) Size = MIN (Size, 256); for (i = 0; i < Size; i++) { int ch = (uchar_t) p[i]; - if (ch < 32 && !isspace(ch)) + if (ch < 32 && !dIsspace(ch)) ++bin_chars; if (ch > 126) ++non_ascci; if (ch > 190) ++non_ascci_text; } - if (bin_chars == 0) { + if (bin_chars == 0 && (non_ascci - non_ascci_text) <= Size/10) { /* Let's say text: if "rare" chars are <= 10% */ - if ((non_ascci - non_ascci_text) <= Size/10) + Type = DT_TEXT_PLAIN; + } else if (Size > 0) { + /* a special check for UTF-8 */ + Size = a_Utf8_end_of_char(p, Size - 1) + 1; + if (a_Utf8_test(p, Size) > 0) Type = DT_TEXT_PLAIN; } - if (Size == 256) + if (Size >= 256) st = 0; } @@ -180,11 +204,13 @@ int a_Misc_get_content_type_from_data(void *Data, size_t Size, const char **PT) /* * Parse Content-Type string, e.g., "text/html; charset=utf-8". + * Content-Type is defined in RFC 2045 section 5.1. */ -void a_Misc_parse_content_type(const char *str, char **major, char **minor, +void a_Misc_parse_content_type(const char *type, char **major, char **minor, char **charset) { - const char *s; + static const char tspecials_space[] = "()<>@,;:\\\"/[]?= "; + const char *str, *s; if (major) *major = NULL; @@ -192,20 +218,31 @@ void a_Misc_parse_content_type(const char *str, char **major, char **minor, *minor = NULL; if (charset) *charset = NULL; - if (!str) + if (!(str = type)) return; - for (s = str; isalnum(*s) || (*s == '-'); s++); + for (s = str; *s && !iscntrl((uchar_t)*s) && !strchr(tspecials_space, *s); + s++) ; if (major) *major = dStrndup(str, s - str); if (*s == '/') { - for (str = ++s; isalnum(*s) || (*s == '-'); s++); + for (str = ++s; + *s && !iscntrl((uchar_t)*s) && !strchr(tspecials_space, *s); s++) ; if (minor) *minor = dStrndup(str, s - str); } - - if (charset && *s) { + if (charset && *s && + (dStrncasecmp(type, "text/", 5) == 0 || + dStrncasecmp(type, "application/xhtml+xml", 21) == 0)) { + /* "charset" parameter defined for text media type in RFC 2046, + * application/xhtml+xml in RFC 3236. + * + * Note that RFC 3023 lists some main xml media types and provides + * the convention of using the "+xml" minor type suffix for other + * xml types, so it would be reasonable to check for that suffix if + * we have need to care about various xml types someday. + */ const char terminators[] = " ;\t"; const char key[] = "charset"; @@ -346,9 +383,9 @@ int a_Misc_parse_geometry(char *str, int *x, int *y, int *w, int *h) */ char *a_Misc_encode_base64(const char *in) { - static const char *base64_hex = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; + static const char *const base64_hex = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; char *out = NULL; int len, i = 0; @@ -10,7 +10,7 @@ extern "C" { char *a_Misc_escape_chars(const char *str, const char *esc_set); -char *a_Misc_expand_tabs(const char *str, int len); +int a_Misc_expand_tabs(char **start, char *end, char *buf, int buflen); 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); void a_Misc_parse_content_type(const char *str, char **major, char **minor, @@ -11,28 +11,17 @@ #define _MSG_WARN(...) #define _MSG_HTTP(...) - -#define MSG(...) \ +#define MSG_INNARDS(prefix, ...) \ D_STMT_START { \ if (prefs.show_msg){ \ - printf(__VA_ARGS__); \ + printf(prefix __VA_ARGS__); \ fflush (stdout); \ } \ } 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_HTTP(...) \ - printf("HTTP warning: " __VA_ARGS__) +#define MSG(...) MSG_INNARDS("", __VA_ARGS__) +#define MSG_WARN(...) MSG_INNARDS("** WARNING **: ", __VA_ARGS__) +#define MSG_ERR(...) MSG_INNARDS("** ERROR **: ", __VA_ARGS__) +#define MSG_HTTP(...) MSG_INNARDS("HTTP warning: ", __VA_ARGS__) #endif /* __MSG_H__ */ @@ -163,7 +163,7 @@ static void Nav_save_scroll_pos(BrowserWindow *bw, int idx, int posx, int posy) } /* - * Remove equal adyacent URLs at the top of the stack. + * Remove equal adjacent URLs at the top of the stack. * (It may happen with redirections) */ static void Nav_stack_clean(BrowserWindow *bw) @@ -191,25 +191,26 @@ static void Nav_stack_clean(BrowserWindow *bw) * 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) +static void Nav_open_url(BrowserWindow *bw, const DilloUrl *url, + const DilloUrl *requester, int offset) { - DilloUrl *old_url; - bool_t MustLoad, ForceReload; + const DilloUrl *old_url; + bool_t MustLoad, ForceReload, Repush, IgnoreScroll; int x, y, idx, ClientKey; DilloWeb *Web; MSG("Nav_open_url: new url='%s'\n", URL_STR_(url)); + Repush = (URL_FLAGS(url) & URL_ReloadFromCache) != 0; ForceReload = (URL_FLAGS(url) & (URL_E2EQuery + URL_ReloadFromCache)) != 0; + IgnoreScroll = (URL_FLAGS(url) & URL_IgnoreScroll) != 0; /* Get the url of the current page */ idx = a_Nav_stack_ptr(bw); old_url = a_History_get_url(NAV_UIDX(bw, idx)); _MSG("Nav_open_url: old_url='%s' idx=%d\n", URL_STR(old_url), idx); /* Record current scrolling position */ - if (URL_FLAGS(url) & URL_ReloadFromCache) { - /* Repush operation, don't change scroll position */ - } else if (old_url) { + if (old_url && !IgnoreScroll) { a_UIcmd_get_scroll_xy(bw, &x, &y); Nav_save_scroll_pos(bw, idx, x, y); _MSG("Nav_open_url: saved scroll of '%s' at x=%d y=%d\n", @@ -232,7 +233,7 @@ static void Nav_open_url(BrowserWindow *bw, const DilloUrl *url, int offset) // a_Menu_pagemarks_new(bw); - Web = a_Web_new(url); + Web = a_Web_new(url, requester); Web->bw = bw; Web->flags |= WEB_RootUrl; if ((ClientKey = a_Capi_open_url(Web, NULL, NULL)) != 0) { @@ -249,12 +250,21 @@ static void Nav_open_url(BrowserWindow *bw, const DilloUrl *url, int offset) 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; - } + a_Url_free(bw->nav_expect_url); + bw->nav_expect_url = NULL; bw->nav_expecting = FALSE; } + if (bw->meta_refresh_status > 0) + --bw->meta_refresh_status; +} + +/* + * Cancel the expect if 'url' matches. + */ +void a_Nav_cancel_expect_if_eq(BrowserWindow *bw, const DilloUrl *url) +{ + if (bw->nav_expecting && a_Url_cmp(url, bw->nav_expect_url) == 0) + a_Nav_cancel_expect(bw); } /* @@ -265,7 +275,7 @@ void a_Nav_cancel_expect(BrowserWindow *bw) */ void a_Nav_expect_done(BrowserWindow *bw) { - int url_idx, posx, posy, reload, repush, e2equery, goto_old_scroll = TRUE; + int m, url_idx, posx, posy, reload, repush, e2equery, goto_old_scroll=TRUE; DilloUrl *url; char *fragment = NULL; @@ -278,11 +288,10 @@ void a_Nav_expect_done(BrowserWindow *bw) e2equery = (URL_FLAGS(url) & URL_E2EQuery); fragment = a_Url_decode_hex_str(URL_FRAGMENT_(url)); - /* Unset E2EQuery, ReloadPage and ReloadFromCache + /* Unset E2EQuery, ReloadPage, ReloadFromCache and IgnoreScroll * before adding this url to history */ - a_Url_set_flags(url, URL_FLAGS(url) & ~URL_E2EQuery); - a_Url_set_flags(url, URL_FLAGS(url) & ~URL_ReloadPage); - a_Url_set_flags(url, URL_FLAGS(url) & ~URL_ReloadFromCache); + m = URL_E2EQuery|URL_ReloadPage|URL_ReloadFromCache|URL_IgnoreScroll; + a_Url_set_flags(url, URL_FLAGS(url) & ~m); url_idx = a_History_add_url(url); if (repush) { @@ -310,7 +319,7 @@ void a_Nav_expect_done(BrowserWindow *bw) } if (goto_old_scroll) { - /* Scroll to were we were in this page */ + /* Scroll to where we were in this page */ Nav_get_scroll_pos(bw, &posx, &posy); a_UIcmd_set_scroll_xy(bw, posx, posy); _MSG("Nav: expect_done scrolling to x=%d y=%d\n", posx, posy); @@ -333,7 +342,8 @@ void a_Nav_expect_done(BrowserWindow *bw) * - 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) +void a_Nav_push(BrowserWindow *bw, const DilloUrl *url, + const DilloUrl *requester) { dReturn_if_fail (bw != NULL); @@ -345,7 +355,7 @@ void a_Nav_push(BrowserWindow *bw, const DilloUrl *url) a_Nav_cancel_expect(bw); bw->nav_expect_url = a_Url_dup(url); bw->nav_expecting = TRUE; - Nav_open_url(bw, url, 0); + Nav_open_url(bw, url, requester, 0); } /* @@ -362,13 +372,14 @@ static void Nav_repush(BrowserWindow *bw) a_Url_set_flags(url, URL_FLAGS(url) | URL_ReloadFromCache); bw->nav_expect_url = a_Url_dup(url); bw->nav_expecting = TRUE; - Nav_open_url(bw, url, 0); + Nav_open_url(bw, url, NULL, 0); a_Url_free(url); } } static void Nav_repush_callback(void *data) { + _MSG(">>>> Nav_repush_callback <<<<\n"); Nav_repush(data); a_Timeout_remove(); } @@ -377,32 +388,50 @@ static void Nav_repush_callback(void *data) * Repush current URL: not an end-to-end reload but from cache. * - Currently used to switch to a charset decoder given by the META element. * - Delayed to let dillo finish the call flow into a known state. + * + * There's no need to stop the parser before calling this function: + * When the timeout activates, a_Bw_stop_clients will stop the data feed. */ void a_Nav_repush(BrowserWindow *bw) { dReturn_if_fail (bw != NULL); + MSG(">>> a_Nav_repush <<<<\n"); a_Timeout_add(0.0, Nav_repush_callback, (void*)bw); } /* - * Same as a_Nav_push() but in a new window. + * This one does a_Nav_redirection0's job. */ -void a_Nav_push_nw(BrowserWindow *bw, const DilloUrl *url) +static void Nav_redirection0_callback(void *data) { - int w, h; - BrowserWindow *newbw; + BrowserWindow *bw = (BrowserWindow *)data; + const DilloUrl *referer_url = a_History_get_url(NAV_TOP_UIDX(bw)); + _MSG(">>>> Nav_redirection0_callback <<<<\n"); - a_UIcmd_get_wh(bw, &w, &h); - newbw = a_UIcmd_browser_window_new(w, h, bw); - a_Nav_push(newbw, url); + if (bw->meta_refresh_status == 2) { + Nav_stack_move_ptr(bw, -1); + a_Nav_push(bw, bw->meta_refresh_url, referer_url); + } + a_Url_free(bw->meta_refresh_url); + bw->meta_refresh_url = NULL; + bw->meta_refresh_status = 0; + a_Timeout_remove(); } /* - * Wraps a_Nav_push to match 'DwPage->link' function type + * Handle a zero-delay URL redirection given by META */ -void a_Nav_vpush(void *vbw, const DilloUrl *url) +void a_Nav_redirection0(BrowserWindow *bw, const DilloUrl *new_url) { - a_Nav_push(vbw, url); + dReturn_if_fail (bw != NULL); + _MSG(">>> a_Nav_redirection0 <<<<\n"); + + a_Url_free(bw->meta_refresh_url); + bw->meta_refresh_url = a_Url_dup(new_url); + a_Url_set_flags(bw->meta_refresh_url, + URL_FLAGS(new_url)|URL_E2EQuery|URL_IgnoreScroll); + bw->meta_refresh_status = 2; + a_Timeout_add(0.0, Nav_redirection0_callback, (void*)bw); } /* @@ -415,7 +444,7 @@ void a_Nav_back(BrowserWindow *bw) a_Nav_cancel_expect(bw); if (--idx >= 0){ a_UIcmd_set_msg(bw, ""); - Nav_open_url(bw, a_History_get_url(NAV_UIDX(bw,idx)), -1); + Nav_open_url(bw, a_History_get_url(NAV_UIDX(bw,idx)), NULL, -1); } } @@ -429,7 +458,7 @@ void a_Nav_forw(BrowserWindow *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_UIDX(bw,idx)), +1); + Nav_open_url(bw, a_History_get_url(NAV_UIDX(bw,idx)), NULL, +1); } } @@ -438,56 +467,56 @@ void a_Nav_forw(BrowserWindow *bw) */ void a_Nav_home(BrowserWindow *bw) { - a_Nav_push(bw, prefs.home); + a_Nav_push(bw, prefs.home, NULL); } /* * This one does a_Nav_reload's job! */ -static void Nav_reload(BrowserWindow *bw) +static void Nav_reload_callback(void *data) { - DilloUrl *ReqURL; + BrowserWindow *bw = data; + const DilloUrl *h_url; + DilloUrl *r_url; + int choice, confirmed = 1; a_Nav_cancel_expect(bw); if (a_Nav_stack_size(bw)) { - ReqURL = a_Url_dup(a_History_get_url(NAV_TOP_UIDX(bw))); - /* Mark URL as reload to differentiate from push */ - a_Url_set_flags(ReqURL, URL_FLAGS(ReqURL) | URL_ReloadPage); - /* Let's make reload be end-to-end */ - a_Url_set_flags(ReqURL, URL_FLAGS(ReqURL) | URL_E2EQuery); - /* This is an explicit reload, so clear the SpamSafe flag */ - a_Url_set_flags(ReqURL, URL_FLAGS(ReqURL) & ~URL_SpamSafe); - bw->nav_expect_url = ReqURL; - bw->nav_expecting = TRUE; - Nav_open_url(bw, ReqURL, 0); + h_url = a_History_get_url(NAV_TOP_UIDX(bw)); + if (strncmp(URL_STR(h_url), "dpi:/vsource/", 13) == 0) { + /* disable reload for view source dpi */ + confirmed = 0; + } else if (URL_FLAGS(h_url) & URL_Post) { + /* Attempt to repost data, let's confirm... */ + choice = a_Dialog_choice3("Repost form data?", + "Yes", "*No", "Cancel"); + confirmed = (choice == 0); /* "Yes" */ + } + + if (confirmed) { + r_url = a_Url_dup(h_url); + /* Mark URL as reload to differentiate from push */ + a_Url_set_flags(r_url, URL_FLAGS(r_url) | URL_ReloadPage); + /* Let's make reload be end-to-end */ + a_Url_set_flags(r_url, URL_FLAGS(r_url) | URL_E2EQuery); + /* This is an explicit reload, so clear the SpamSafe flag */ + a_Url_set_flags(r_url, URL_FLAGS(r_url) & ~URL_SpamSafe); + bw->nav_expect_url = r_url; + bw->nav_expecting = TRUE; + Nav_open_url(bw, r_url, NULL, 0); + } } } /* * Implement the RELOAD button functionality. * (Currently it only reloads the page, not its images) + * Note: the timeout lets CCC operations end before making the request. */ 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_UIDX(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" */ - _MSG("Nav_reload_confirmed\n"); - Nav_reload(bw); - } - - } else { - Nav_reload(bw); - } - } + dReturn_if_fail (bw != NULL); + a_Timeout_add(0.0, Nav_reload_callback, (void*)bw); } /* @@ -498,10 +527,10 @@ 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_UIDX(bw,idx))); + a_UIcmd_open_url_nw(bw, a_History_get_url(NAV_UIDX(bw,idx))); } else { a_Nav_cancel_expect(bw); - Nav_open_url(bw, a_History_get_url(NAV_UIDX(bw,idx)), offset); + Nav_open_url(bw, a_History_get_url(NAV_UIDX(bw,idx)), NULL, offset); a_UIcmd_set_buttons_sens(bw); } } @@ -538,9 +567,9 @@ static void Nav_save_cb(int Op, CacheClient_t *Client) void a_Nav_save_url(BrowserWindow *bw, const DilloUrl *url, const char *filename) { - DilloWeb *Web = a_Web_new(url); + DilloWeb *Web = a_Web_new(url, NULL); Web->bw = bw; - Web->filename = dStrdup(filename); + Web->filename = dStrdup(filename); Web->flags |= WEB_Download; /* TODO: keep track of this client */ a_Capi_open_url(Web, Nav_save_cb, Web); @@ -559,5 +588,13 @@ int a_Nav_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize) */ void a_Nav_unref_buf(const DilloUrl *Url) { - return a_Capi_unref_buf(Url); + a_Capi_unref_buf(Url); +} + +/* + * Wrapper for a_Capi_set_vsource_url(). + */ +void a_Nav_set_vsource_url(const DilloUrl *Url) +{ + a_Capi_set_vsource_url(Url); } @@ -13,9 +13,9 @@ 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_redirection0(BrowserWindow *bw, const DilloUrl *new_url); +void a_Nav_push(BrowserWindow *bw, const DilloUrl *url, + const DilloUrl *requester); void a_Nav_repush(BrowserWindow *bw); void a_Nav_back(BrowserWindow *bw); void a_Nav_forw(BrowserWindow *bw); @@ -24,6 +24,7 @@ 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_cancel_expect_if_eq(BrowserWindow *bw, const DilloUrl *url); void a_Nav_expect_done(BrowserWindow *bw); int a_Nav_stack_ptr(BrowserWindow *bw); int a_Nav_stack_size(BrowserWindow *bw); @@ -34,6 +35,7 @@ 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); void a_Nav_unref_buf(const DilloUrl *Url); +void a_Nav_set_vsource_url(const DilloUrl *Url); #ifdef __cplusplus } diff --git a/src/paths.cc b/src/paths.cc new file mode 100644 index 00000000..6fccb89a --- /dev/null +++ b/src/paths.cc @@ -0,0 +1,101 @@ +/* + * File: paths.cc + * + * Copyright 2006-2009 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 <errno.h> +#include <sys/stat.h> + +#include "msg.h" +#include "../dlib/dlib.h" +#include "paths.hh" + +/* + * Local data + */ + +// Dillo works from an unmounted directory (/tmp) +static char* oldWorkingDir = NULL; + +/* + * Changes current working directory to /tmp and creates ~/.dillo + * if not exists. + */ +void Paths::init(void) +{ + char *path; + struct stat st; + int rc = 0; + + dFree(oldWorkingDir); + oldWorkingDir = dGetcwd(); + rc = chdir("/tmp"); + if (rc == -1) { + MSG("paths: error changing directory to /tmp: %s\n", + dStrerror(errno)); + } + + path = dStrconcat(dGethomedir(), "/.dillo", NULL); + if (stat(path, &st) == -1) { + if (errno == ENOENT) { + MSG("paths: creating directory %s.\n", path); + if (mkdir(path, 0700) < 0) { + MSG("paths: error creating directory %s: %s\n", + path, dStrerror(errno)); + } + } else { + MSG("Dillo: error reading %s: %s\n", path, dStrerror(errno)); + } + } + + dFree(path); +} + +/* + * Return the initial current working directory in a string. + */ +char *Paths::getOldWorkingDir(void) +{ + return oldWorkingDir; +} + +/* + * Free memory + */ +void Paths::free(void) +{ + dFree(oldWorkingDir); +} + +/* + * Examines the path for "rcFile" and assign its file pointer to "fp". + */ +FILE *Paths::getPrefsFP(const char *rcFile) +{ + FILE *fp; + char *path = dStrconcat(dGethomedir(), "/.dillo/", rcFile, NULL); + + if (!(fp = fopen(path, "r"))) { + MSG("paths: Cannot open file '%s'\n", path); + + char *path2 = dStrconcat(DILLO_SYSCONF, rcFile, NULL); + if (!(fp = fopen(path2, "r"))) { + MSG("paths: Cannot open file '%s'\n",path2); + MSG("paths: Using internal defaults...\n"); + } else { + MSG("paths: Using %s\n", path2); + } + dFree(path2); + } + + dFree(path); + return fp; +} + diff --git a/src/paths.hh b/src/paths.hh new file mode 100644 index 00000000..a9efc62b --- /dev/null +++ b/src/paths.hh @@ -0,0 +1,26 @@ +/* + * File: paths.hh + * + * Copyright 2006-2009 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. + */ + +#ifndef __PATHS_HH__ +#define __PATHS_HH__ + +#define PATHS_RC_PREFS "dillorc" +#define PATHS_RC_KEYS "keysrc" + +class Paths { +public: + static void init(void); + static void free(void); + static char *getOldWorkingDir(void); + static FILE *getPrefsFP(const char *rcFile); +}; + +#endif /* __PATHS_HH__ */ diff --git a/src/pixmaps.h b/src/pixmaps.h index 4e54ff71..83235dd2 100644 --- a/src/pixmaps.h +++ b/src/pixmaps.h @@ -6,7 +6,7 @@ * * 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 + * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. */ @@ -675,6 +675,7 @@ static const char *const stop_xpm[] = { " .KLLLLLLLK. ", " ......... ", " "}; + /* XPM */ static const char *const bm_xpm[] = { "22 22 86 1", @@ -787,6 +788,57 @@ static const char *const bm_xpm[] = { " .S. TU. ", " . . "}; +/* XPM */ +static const char *const tools_xpm[] = { +"22 22 25 1", +" c #None", +". c #696977777676", +"X c #96969B9B9B9B", +"o c #848490908F8F", +"O c #D7D7D7D7D7D7", +"+ c #B5B5B7B7B7B7", +"@ c #EAEAEAEAEAEA", +"# c #797987878787", +"$ c #F1F1EFEFEFEF", +"% c #CCCCCCCCCCCC", +"& c #8B8B97979696", +"* c #CACACACACACA", +"= c #787878787878", +"- c #AAAAB5B5B5B5", +"; c #F9F9F8F8F9F9", +": c #A6A6A8A8A8A8", +"> c #7F7F90907F7F", +", c #A4A4BDBDA4A4", +"< c #A5A58A8A5353", +"1 c #00008B8B0000", +"2 c #6C6C6C6C6C6C", +"3 c #BCBCC3C3C2C2", +"4 c #CACAD8D8D8D8", +"5 c #9999A8A8A8A8", +"6 c #848489898989", +" ", +" ", +" .XXo. ", +" oO $$+. ", +" oo@$$$@# ", +" #o@$$$O# ", +" #X$$$$%X ", +" &#% #X$$$@o ", +" #*o =-@$$$# ", +" oO*o #$@$$$$# ", +" oOOOX oO@;$$$$X ", +" #@O*OXo:>,<$$$$o ", +" XO@O*O*12>,<$$$# ", +" X$@O*+<12>,<$$& ", +" +$@O*+<12>,<;@ ", +" :$@O*+<12>,<$@ ", +" &34O*-<11>,<$$ ", +" -X&X&<12>,<$O ", +" 45<11>,<3 ", +" 45<1=>O# ", +" -&X6&=# ", +" "}; + /* Small icons here */ /* XPM */ @@ -1321,6 +1373,73 @@ static const char *const bm_s_xpm[] = { " .v. wx. "}; /* XPM */ +static const char *const tools_s_xpm[] = { +"16 16 45 1", +" c #7B127D1C7D08", +". c #82778EED8E15", +"X c #8F3F975396FF", +"o c #9A98A40CA38F", +"O c #833D8EC28E02", +"+ c #99C89EDF9EC2", +"@ c #B347BD91BD42", +"# c #7E4181A081AF", +"$ c #8744915990DA", +"% c #86728A968A7C", +"& c #8D56924B9226", +"* c #96DF9AC79AA6", +"= c #9E01A473A438", +"- c #AB41B55EB546", +"; c #ABD1B155B117", +": c #B5BFBC3DBC2F", +"> c #B917BCECBCAB", +", c #B558B98BB958", +"< c #CEA2CFDBCFD4", +"1 c #A699AC55ABF1", +"2 c #C2A5C5A8C57A", +"3 c #C8B2CA27CA1C", +"4 c #D80FD952D955", +"5 c #DF19E120E134", +"6 c #EAC7E9ECE9FE", +"7 c #F164EF95EF9D", +"8 c #F6E0F5E6F61C", +"9 c #F013EEAEEEBF", +"0 c #487651675130", +"q c #6E4D7BA97B06", +"w c #88F190679051", +"e c #92339C0E9BC1", +"r c #A1BBAB0FAA98", +"t c #A24AA7C4A7A7", +"y c #86C7935E92DC", +"u c #C8C4D721D725", +"i c #D0E6D516D518", +"p c #76A38450834A", +"a c #6B4B7B507A6D", +"s c #7C6D882B878D", +"d c #88F395099487", +"f c #DB34DCE9DCF5", +"g c #5B05673F66CD", +"h c #9402A0549FCC", +" c None", +/* pixels */ +" ", +" gO+Oq ", +" p*45<Xg ", +" h.>885oa ", +" p.3883Xg ", +" i$O s$488;s ", +" O>1p e;588;a ", +" O331sr;7778:s ", +" O343,**3678;a ", +" f,643;#%368:s ", +" t<64>= &374f ", +" t244>= &379ih", +" d@:;=& &475u", +" ssr-* *44r", +" u-%#*%0", +" feww0g" +}; + +/* XPM */ static const char *const new_s_xpm[] = { "11 11 35 1", " c None", @@ -1402,6 +1521,42 @@ static const char *const search_xpm[] = { " 11 "}; /* XPM */ +static const char *const help_xpm[] = { +"14 16 16 1", +" c None", +"1 c #DBDBDB", +"2 c #B6B6B6", +"3 c #929292", +"4 c #6D6D6D", +"5 c #F1EFEF", +"6 c #018B00", +"7 c #A48A53", +"8 c #A4BDA4", +"9 c #000000", +"A c #000000", +"B c #000000", +"C c #000000", +"D c #000000", +"E c #000000", +"F c #000000", +" 5555555 ", +" 544433355 ", +" 54555438455 ", +" 44555542835 ", +" 44445548245 ", +" 4444554274 ", +" 544554274 ", +" 55474 ", +" 5474 ", +" 544 ", +" 44 ", +" ", +" 5445 ", +" 4764 ", +" 4674 ", +" 5445 "}; + +/* XPM */ static const char *const full_screen_on_xpm[] = { "13 15 2 1", " c None", @@ -1496,48 +1651,6 @@ static const char *const mini_ok_xpm[] = { }; /* XPM */ -static const char *const imgload_on_xpm[] = { -"15 15 2 1", -" c None", -". c #00000000CF3C", -" ", -" . . . ... ", -" . .. .. . . ", -" . . . . . ", -" . . . . .. ", -" . . . . . ", -" . . . ... ", -" ", -" ... . . ", -" . . .. . ", -" . . . . . ", -" . . . .. ", -" . . . . ", -" ... . . ", -" "}; - -/* XPM */ -static const char *const imgload_off_xpm[] = { -"15 15 2 1", -" c None", -". c #CF3C00000000", -" ", -" . . . ... ", -" . .. .. . . ", -" . . . . . ", -" . . . . .. ", -" . . . . . ", -" . . . ... ", -" ", -" ... ... ... ", -" . . . . ", -" . . ... ... ", -" . . . . ", -" . . . . ", -" ... . . ", -" "}; - -/* XPM */ static const char *const left_i_xpm[] = { "22 22 3 1", " c None", diff --git a/src/plain.cc b/src/plain.cc index b75dbea5..4da618e4 100644 --- a/src/plain.cc +++ b/src/plain.cc @@ -13,15 +13,13 @@ * Module for decoding a text/plain object into a dw widget. */ -#include <string.h> /* for memcpy and memmove */ -#include <math.h> /* for rint() */ - #include "msg.h" #include "prefs.h" #include "cache.h" #include "bw.h" #include "web.hh" #include "misc.h" +#include "styleengine.hh" #include "uicmd.hh" @@ -37,23 +35,25 @@ using namespace dw::core; class DilloPlain { private: - class PlainEventReceiver: public dw::core::Widget::EventReceiver { + class PlainLinkReceiver: public dw::core::Layout::LinkReceiver { public: DilloPlain *plain; - bool buttonPress(dw::core::Widget *widget, dw::core::EventButton *event); + bool press(dw::core::Widget *widget, int link, int img, int x, int y, + dw::core::EventButton *event); }; - PlainEventReceiver plainReceiver; + PlainLinkReceiver plainReceiver; + + void addLine(char *Buf, uint_t BufSize); public: BrowserWindow *bw; - DilloUrl *url; Widget *dw; style::Style *widgetStyle; size_t Start_Ofs; /* Offset of where to start reading next */ int state; - DilloPlain(BrowserWindow *bw, const DilloUrl *url); + DilloPlain(BrowserWindow *bw); ~DilloPlain(); void write(void *Buf, uint_t BufSize, int Eof); @@ -83,38 +83,27 @@ void a_Plain_free(void *data); /* * Diplain constructor. */ -DilloPlain::DilloPlain(BrowserWindow *p_bw, const DilloUrl *p_url) +DilloPlain::DilloPlain(BrowserWindow *p_bw) { - style::StyleAttrs styleAttrs; - style::FontAttrs fontAttrs; - /* Init event receiver */ plainReceiver.plain = this; /* Init internal variables */ bw = p_bw; - url = a_Url_dup(p_url); dw = new Textblock (prefs.limit_text_width); Start_Ofs = 0; state = ST_SeekingEol; - /* Create the font and attribute for the page. */ - fontAttrs.name = prefs.fw_fontname; - fontAttrs.size = (int) rint(14.0 * prefs.font_factor); - fontAttrs.weight = 400; - fontAttrs.style = style::FONT_STYLE_NORMAL; - - 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, prefs.text_color); - styleAttrs.backgroundColor = - style::Color::createSimple (layout, prefs.bg_color); - widgetStyle = style::Style::create (layout, &styleAttrs); + Layout *layout = (Layout*) bw->render_layout; + StyleEngine styleEngine (layout); + + styleEngine.startElement ("body"); + styleEngine.startElement ("pre"); + widgetStyle = styleEngine.wordStyle (); + widgetStyle->ref (); /* The context menu */ - DW2TB(dw)->connectEvent (&plainReceiver); + layout->connectLink (&plainReceiver); /* Hook destructor to the dw delete call */ dw->setDeleteCallback(a_Plain_free, this); @@ -125,26 +114,44 @@ DilloPlain::DilloPlain(BrowserWindow *p_bw, const DilloUrl *p_url) */ DilloPlain::~DilloPlain() { - MSG("::~DilloPlain()\n"); - a_Url_free(url); + _MSG("::~DilloPlain()\n"); widgetStyle->unref(); } /* * Receive the mouse button press event */ -bool DilloPlain::PlainEventReceiver::buttonPress (Widget *widget, - EventButton *event) +bool DilloPlain::PlainLinkReceiver::press (Widget *widget, int, int, int, int, + EventButton *event) { - _MSG("DilloPlain::PlainEventReceiver::buttonPress\n"); + _MSG("DilloPlain::PlainLinkReceiver::buttonPress\n"); if (event->button == 3) { - a_UIcmd_page_popup(plain->bw, plain->url, FALSE, FALSE); + a_UIcmd_page_popup(plain->bw, FALSE, NULL); return true; } return false; } +void DilloPlain::addLine(char *Buf, uint_t BufSize) +{ + int len; + char buf[128]; + char *end = Buf + BufSize; + + if (BufSize > 0) { + // Limit word length to avoid X11 coordinate + // overflow with extremely long lines. + while ((len = a_Misc_expand_tabs(&Buf, end, buf, sizeof(buf)))) + DW2TB(dw)->addText(buf, len, widgetStyle); + } else { + // Add dummy word for empty lines - otherwise the parbreak is ignored. + DW2TB(dw)->addText("", 0, widgetStyle); + } + + DW2TB(dw)->addParbreak(0, widgetStyle); +} + /* * Here we parse plain text and put it into the page structure. * (This function is called by Plain_callback whenever there's new data) @@ -152,7 +159,6 @@ bool DilloPlain::PlainEventReceiver::buttonPress (Widget *widget, void DilloPlain::write(void *Buf, uint_t BufSize, int Eof) { char *Start; - char *data; uint_t i, len, MaxBytes; _MSG("DilloPlain::write Eof=%d\n", Eof); @@ -170,10 +176,7 @@ void DilloPlain::write(void *Buf, uint_t BufSize, int Eof) } break; case ST_Eol: - data = a_Misc_expand_tabs(Start + i - len, len); - DW2TB(dw)->addText(data, widgetStyle); - DW2TB(dw)->addParbreak(0, widgetStyle); - dFree(data); + addLine(Start + i - len, len); if (Start[i] == '\r' && Start[i + 1] == '\n') ++i; if (i < MaxBytes) ++i; state = ST_SeekingEol; @@ -183,10 +186,7 @@ void DilloPlain::write(void *Buf, uint_t BufSize, int Eof) } Start_Ofs += i - len; if (Eof && len) { - data = a_Misc_expand_tabs(Start + i - len, len); - DW2TB(dw)->addText(data, widgetStyle); - DW2TB(dw)->addParbreak(0, widgetStyle); - dFree(data); + addLine(Start + i - len, len); Start_Ofs += len; } @@ -199,7 +199,7 @@ void DilloPlain::write(void *Buf, uint_t BufSize, int Eof) void *a_Plain_text(const char *type, void *P, CA_Callback_t *Call, void **Data) { DilloWeb *web = (DilloWeb*)P; - DilloPlain *plain = new DilloPlain(web->bw, web->url); + DilloPlain *plain = new DilloPlain(web->bw); *Call = (CA_Callback_t)Plain_callback; *Data = (void*)plain; @@ -209,7 +209,7 @@ void *a_Plain_text(const char *type, void *P, CA_Callback_t *Call, void **Data) void a_Plain_free(void *data) { - MSG("a_Plain_free! %p\n", data); + _MSG("a_Plain_free! %p\n", data); delete ((DilloPlain *)data); } @@ -4,6 +4,7 @@ * * Geoff Lane nov 1999 zzassgl@twirl.mcc.ac.uk * Luca Rota, Jorge Arellano Cid, Eric Gaudet 2000 + * Jorge Arellano Cid 2009 * * "PNG: The Definitive Guide" by Greg Roelofs, O'Reilly * ISBN 1-56592-542-4 @@ -12,12 +13,8 @@ #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 @@ -26,10 +23,8 @@ #include "msg.h" #include "image.hh" -#include "web.hh" #include "cache.h" #include "dicache.h" -#include "prefs.h" enum prog_state { IS_finished, IS_init, IS_nextdata @@ -58,7 +53,7 @@ static char *prog_state_name[] = * 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 + * In the case of the libpng library, it maintains its own state in * png_ptr and into_ptr so the FSM is very simple - much simpler than the * ones for XBM and PNM are. */ @@ -67,19 +62,19 @@ typedef struct _DilloPng { DilloImage *Image; /* Image meta data */ DilloUrl *url; /* Primary Key for the dicache */ - int version; /* Secondary 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 */ + 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 */ + 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 error; /* error flag */ png_uint_32 previous_row; - int rowbytes; /* No. bytes in image row */ + int rowbytes; /* No. bytes in image row */ short passes; short channels; /* No. image channels */ @@ -92,13 +87,13 @@ struct _DilloPng { * ipbuf ipbufstart ipbufsize */ - uchar_t *ipbuf; /* image data in buffer */ - int ipbufstart; /* first valid image byte */ - int ipbufsize; /* size of valid data in */ + 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 */ + uchar_t *linebuf; /* o/p raster data */ } DilloPng; @@ -106,13 +101,6 @@ struct _DilloPng { #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) @@ -146,6 +134,15 @@ Png_datainfo_callback(png_structp png_ptr, png_infop info_ptr) png_get_IHDR(png_ptr, info_ptr, &png->width, &png->height, &bit_depth, &color_type, &interlace_type, NULL, NULL); + /* check max image size */ + if (png->width <= 0 || png->height <= 0 || + png->width > IMAGE_MAX_AREA / png->height) { + MSG("Png_datainfo_callback: suspicious image size request %ldx%ld\n", + png->width, png->height); + Png_error_handling(png_ptr, "Aborting..."); + return; /* not reached */ + } + _MSG("Png_datainfo_callback: png->width = %ld\n" "Png_datainfo_callback: png->height = %ld\n", png->width, png->height); @@ -185,7 +182,7 @@ Png_datainfo_callback(png_structp png_ptr, png_infop info_ptr) png->passes = png_set_interlace_handling(png_ptr); } - /* get libpng to update it's state */ + /* get libpng to update its state */ png_read_update_info(png_ptr, info_ptr); png_get_IHDR(png_ptr, info_ptr, &png->width, &png->height, @@ -230,14 +227,15 @@ static void png_progressive_combine_row(png_ptr, png->row_pointers[row_num], new_row); + _MSG("png: row_num=%u previous_row=%u\n", row_num, png->previous_row); if (row_num < png->previous_row) { - a_Dicache_new_scan(png->Image, png->url, png->version); + a_Dicache_new_scan(png->url, png->version); } png->previous_row = row_num; switch (png->channels) { case 3: - a_Dicache_write(png->Image, png->url, png->version, + a_Dicache_write(png->url, png->version, png->image_data + (row_num * png->rowbytes), (uint_t)row_num); break; @@ -275,79 +273,67 @@ static void data++; } } - a_Dicache_write(png->Image, png->url, png->version, - png->linebuf, (uint_t)row_num); + a_Dicache_write(png->url, png->version, png->linebuf, (uint_t)row_num); break; } default: - MSG("Png_datarow_callback: unexpected number of channels = %d\n", - png->channels); + MSG("Png_datarow_callback: unexpected number of channels=%d pass=%d\n", + png->channels, pass); abort(); } } -static void - Png_dataend_callback(png_structp png_ptr, png_infop info_ptr) +static void Png_dataend_callback(png_structp png_ptr, png_infop info_ptr) { DilloPng *png; _MSG("Png_dataend_callback:\n"); + if (!info_ptr) + MSG("Png_dataend_callback: info_ptr = NULL\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. + * Free up the resources for this image. */ -static void Png_callback(int Op, CacheClient_t *Client) +static void Png_free(DilloPng *png) { - 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; - } + _MSG("Png_free: png=%p\n", png); + + 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); +} - /* Let's make some sound if we have been called with no data */ - dReturn_if_fail ( Client->Buf != NULL && Client->BufSize > 0 ); +/* + * Finish the decoding process (and free the memory) + */ +static void Png_close(DilloPng *png, CacheClient_t *Client) +{ + _MSG("Png_close\n"); + /* Let dicache know decoding is over */ + a_Dicache_close(png->url, png->version, Client); + Png_free(png); +} - _MSG("Png_callback BufSize = %d\n", Client->BufSize); +/* + * Receive and process new chunks of PNG image data + */ +static void Png_write(DilloPng *png, void *Buf, uint_t BufSize) +{ + dReturn_if_fail ( Buf != NULL && BufSize > 0 ); /* 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; + png->ipbuf = Buf; + png->ipbufsize = BufSize; /* start/resume the FSM here */ while (png->state != IS_finished && DATASIZE) { @@ -359,8 +345,7 @@ static void Png_callback(int Op, CacheClient_t *Client) 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 */ + if (png_sig_cmp(png->ipbuf, 0, DATASIZE)) { MSG_WARN("\"%s\" is not a PNG file.\n", URL_STR(png->url)); png->state = IS_finished; break; @@ -408,11 +393,47 @@ static void Png_callback(int Op, CacheClient_t *Client) } /* + * 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 kept + * 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. + */ +void a_Png_callback(int Op, void *data) +{ + if (Op == CA_Send) { + CacheClient_t *Client = data; + Png_write(Client->CbData, Client->Buf, Client->BufSize); + } else if (Op == CA_Close) { + CacheClient_t *Client = data; + Png_close(Client->CbData, Client); + } else if (Op == CA_Abort) { + Png_free(data); + } +} + +/* * Create the image state data that must be kept between calls */ -static DilloPng *Png_new(DilloImage *Image, DilloUrl *url, int version) +void *a_Png_new(DilloImage *Image, DilloUrl *url, int version) { DilloPng *png = dNew0(DilloPng, 1); + _MSG("a_Png_new: png=%p\n", png); png->Image = Image; png->url = url; @@ -430,49 +451,9 @@ static DilloPng *Png_new(DilloImage *Image, DilloUrl *url, int version) 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 - */ +#else /* ENABLE_PNG */ - DilloWeb *web = Ptr; - DICacheEntry *DicEntry; - - _MSG("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); -} +void *a_Png_new() { return 0; } +void a_Png_callback() { return; } #endif /* ENABLE_PNG */ diff --git a/src/prefs.c b/src/prefs.c index 10e87d0c..5514c01a 100644 --- a/src/prefs.c +++ b/src/prefs.c @@ -1,490 +1,120 @@ /* - * Preferences for dillo + * Preferences * - * Copyright (C) 2006-2007 Jorge Arellano Cid <jcid@dillo.org> + * Copyright (C) 2006-2009 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 "dillorc" - -#define DILLO_START_PAGE "about:splash" -#define DILLO_HOME "http://www.dillo.org/" -#define D_GEOMETRY_DEFAULT_WIDTH 780 -#define D_GEOMETRY_DEFAULT_HEIGHT 580 -#define D_GEOMETRY_DEFAULT_XPOS -9999 -#define D_GEOMETRY_DEFAULT_YPOS -9999 - -#define D_VW_FONTNAME "DejaVu Sans" -#define D_FW_FONTNAME "DejaVu Sans Mono" -#define D_SEARCH_URL "http://www.google.com/search?ie=UTF-8&oe=UTF-8&q=%s" -#define D_SAVE_DIR "/tmp/" -#define DW_COLOR_DEFAULT_BGND 0xdcd1ba -#define DW_COLOR_DEFAULT_TEXT 0x000000 -#define DW_COLOR_DEFAULT_LINK 0x0000ff -#define DW_COLOR_DEFAULT_VLINK 0x800080 +#define PREFS_START_PAGE "about:splash" +#define PREFS_HOME "http://www.dillo.org/" +#define PREFS_FONT_SERIF "DejaVu Serif" +#define PREFS_FONT_SANS_SERIF "DejaVu Sans" +#define PREFS_FONT_CURSIVE "URW Chancery L" +#define PREFS_FONT_FANTASY "DejaVu Sans" /* TODO: find good default */ +#define PREFS_FONT_MONOSPACE "DejaVu Sans Mono" +#define PREFS_SEARCH_URL "http://www.google.com/search?ie=UTF-8&oe=UTF-8&q=%s" +#define PREFS_NO_PROXY "localhost 127.0.0.1" +#define PREFS_SAVE_DIR "/tmp/" +#define PREFS_HTTP_REFERER "host" +#define PREFS_HTTP_USER_AGENT "Dillo/" VERSION /*----------------------------------------------------------------------------- * Global Data *---------------------------------------------------------------------------*/ DilloPrefs prefs; -/*----------------------------------------------------------------------------- - * Local types - *---------------------------------------------------------------------------*/ - -/* 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_FOCUS_NEW_TAB, - 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_LOAD_IMAGES, - DRC_TOKEN_BUFFERED_DRAWING, - DRC_TOKEN_MIDDLE_CLICK_OPENS_NEW_TAB, - DRC_TOKEN_NOPROXY, - DRC_TOKEN_PANEL_SIZE, - DRC_TOKEN_PROXY, - DRC_TOKEN_PROXYUSER, - DRC_TOKEN_REFERER, - DRC_TOKEN_SAVE_DIR, - 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_FILEMENU, - 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_STANDARD_WIDGET_COLORS, - DRC_TOKEN_START_PAGE, - DRC_TOKEN_TEXT_COLOR, - DRC_TOKEN_VISITED_COLOR, - DRC_TOKEN_VW_FONT, - DRC_TOKEN_W3C_PLUS_HEURISTICS -} RcToken_t; - -typedef struct SymNode_ SymNode_t; - -struct SymNode_ { - char *name; - RcToken_t token; -}; - -/*----------------------------------------------------------------------------- - * Local data - *---------------------------------------------------------------------------*/ - -/* Symbol array, sorted alphabetically */ -static const SymNode_t symbols[] = { - { "allow_white_bg", DRC_TOKEN_ALLOW_WHITE_BG }, - { "bg_color", DRC_TOKEN_BG_COLOR }, - { "buffered_drawing", DRC_TOKEN_BUFFERED_DRAWING }, - { "contrast_visited_color", DRC_TOKEN_CONTRAST_VISITED_COLOR }, - { "enterpress_forces_submit", DRC_TOKEN_ENTERPRESS_FORCES_SUBMIT }, - { "focus_new_tab", DRC_TOKEN_FOCUS_NEW_TAB }, - { "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 }, - { "http_referer", DRC_TOKEN_REFERER }, - { "limit_text_width", DRC_TOKEN_LIMIT_TEXT_WIDTH }, - { "link_color", DRC_TOKEN_LINK_COLOR }, - { "load_images", DRC_TOKEN_LOAD_IMAGES }, - { "middle_click_opens_new_tab", DRC_TOKEN_MIDDLE_CLICK_OPENS_NEW_TAB }, - { "no_proxy", DRC_TOKEN_NOPROXY }, - { "panel_size", DRC_TOKEN_PANEL_SIZE }, - { "save_dir", DRC_TOKEN_SAVE_DIR }, - { "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_filemenu", DRC_TOKEN_SHOW_FILEMENU }, - { "show_forw", DRC_TOKEN_SHOW_FORW }, - { "show_home", DRC_TOKEN_SHOW_HOME }, - { "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 }, - { "standard_widget_colors", DRC_TOKEN_STANDARD_WIDGET_COLORS }, - { "start_page", DRC_TOKEN_START_PAGE }, - { "text_color", DRC_TOKEN_TEXT_COLOR }, - { "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 ------------------------------------------------------------- + * Sets the default settings. */ -/* - * 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); -} - -/* - * 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); - break; - case DRC_TOKEN_PROXYUSER: - dFree(prefs.http_proxyuser); - prefs.http_proxyuser = dStrdup(value); - break; - case DRC_TOKEN_REFERER: - dFree(prefs.http_referer); - prefs.http_referer = 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_STANDARD_WIDGET_COLORS: - prefs.standard_widget_colors = (strcmp(value, "YES") == 0); - break; - case DRC_TOKEN_PANEL_SIZE: - if (!dStrcasecmp(value, "tiny")) - prefs.panel_size = P_tiny; - else if (!dStrcasecmp(value, "small")) - prefs.panel_size = P_small; - else if (!dStrcasecmp(value, "medium")) - prefs.panel_size = P_medium; - else /* default to "large" */ - prefs.panel_size = P_large; - 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); - break; - case DRC_TOKEN_HOME: - a_Url_free(prefs.home); - prefs.home = a_Url_new(value, NULL); - break; - case DRC_TOKEN_SHOW_TOOLTIP: - prefs.show_tooltip = (strcmp(value, "YES") == 0); - break; - case DRC_TOKEN_FOCUS_NEW_TAB: - prefs.focus_new_tab = (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_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_FILEMENU: - prefs.show_filemenu = (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_LOAD_IMAGES: - prefs.load_images = (strcmp(value, "YES") == 0); - break; - case DRC_TOKEN_BUFFERED_DRAWING: - prefs.buffered_drawing = atoi(value); - 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_MIDDLE_CLICK_OPENS_NEW_TAB: - prefs.middle_click_opens_new_tab = (strcmp(value, "YES") == 0); - break; - case DRC_TOKEN_SEARCH_URL: - dFree(prefs.search_url); - prefs.search_url = dStrdup(value); - break; - case DRC_TOKEN_SAVE_DIR: - dFree(prefs.save_dir); - prefs.save_dir = dStrdup(value); - 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 (dParser_get_rc_pair(&line, &name, &value) == 0) { - _MSG("{%s}, {%s}\n", name, value); - Prefs_parse_pair(name, value); - } else if (line[0] && line[0] != '#' && (!name || !value)) { - MSG("prefs: Syntax error in %s: name=\"%s\" value=\"%s\"\n", - RCNAME, 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.http_referer = dStrdup("host"); - prefs.no_proxy = NULL; - prefs.link_color = DW_COLOR_DEFAULT_LINK; - prefs.visited_color = DW_COLOR_DEFAULT_VLINK; - prefs.bg_color = DW_COLOR_DEFAULT_BGND; - prefs.text_color = DW_COLOR_DEFAULT_TEXT; - prefs.start_page = a_Url_new(DILLO_START_PAGE, NULL); - prefs.home = a_Url_new(DILLO_HOME, NULL); prefs.allow_white_bg = TRUE; - prefs.force_my_colors = FALSE; + prefs.buffered_drawing = 1; prefs.contrast_visited_color = TRUE; - prefs.standard_widget_colors = FALSE; - prefs.show_tooltip = TRUE; - prefs.panel_size = P_large; - prefs.small_icons = FALSE; - prefs.limit_text_width = FALSE; - prefs.w3c_plus_heuristics = TRUE; + prefs.enterpress_forces_submit = FALSE; + prefs.filter_auto_requests = PREFS_FILTER_SAME_DOMAIN; prefs.focus_new_tab = TRUE; + prefs.font_cursive = dStrdup(PREFS_FONT_CURSIVE); prefs.font_factor = 1.0; - 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_filemenu=TRUE; - prefs.show_clear_url=TRUE; - prefs.show_url=TRUE; - prefs.show_search=TRUE; - prefs.show_progress_box=TRUE; - prefs.fullwindow_start=FALSE; + prefs.font_max_size = 100; + prefs.font_min_size = 6; + prefs.font_fantasy = dStrdup(PREFS_FONT_FANTASY); + prefs.font_monospace = dStrdup(PREFS_FONT_MONOSPACE); + prefs.font_sans_serif = dStrdup(PREFS_FONT_SANS_SERIF); + prefs.font_serif = dStrdup(PREFS_FONT_SERIF); + prefs.fullwindow_start = FALSE; + + /* these four constitute the geometry */ + prefs.width = PREFS_GEOMETRY_DEFAULT_WIDTH; + prefs.height = PREFS_GEOMETRY_DEFAULT_HEIGHT; + prefs.xpos = PREFS_GEOMETRY_DEFAULT_XPOS; + prefs.ypos = PREFS_GEOMETRY_DEFAULT_YPOS; + + prefs.home = a_Url_new(PREFS_HOME, NULL); + prefs.http_language = NULL; + prefs.http_proxy = NULL; + prefs.http_max_conns = 6; + prefs.http_proxyuser = NULL; + prefs.http_referer = dStrdup(PREFS_HTTP_REFERER); + prefs.http_user_agent = dStrdup(PREFS_HTTP_USER_AGENT); + prefs.limit_text_width = FALSE; prefs.load_images=TRUE; - prefs.buffered_drawing=1; - prefs.vw_fontname = dStrdup(D_VW_FONTNAME); - prefs.fw_fontname = dStrdup(D_FW_FONTNAME); - prefs.generate_submit = FALSE; - prefs.enterpress_forces_submit = FALSE; + prefs.load_stylesheets=TRUE; + prefs.middle_click_drags_page = TRUE; prefs.middle_click_opens_new_tab = TRUE; - prefs.search_url = dStrdup(D_SEARCH_URL); - prefs.save_dir = dStrdup(D_SAVE_DIR); - prefs.show_msg = TRUE; + prefs.no_proxy = dStrdup(PREFS_NO_PROXY); + prefs.panel_size = P_medium; + prefs.parse_embedded_css=TRUE; + prefs.save_dir = dStrdup(PREFS_SAVE_DIR); + prefs.search_url = dStrdup(PREFS_SEARCH_URL); + prefs.show_back = TRUE; + prefs.show_bookmarks = TRUE; + prefs.show_clear_url = 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); - + prefs.show_filemenu=TRUE; + prefs.show_forw = TRUE; + prefs.show_help = TRUE; + prefs.show_home = TRUE; + prefs.show_msg = TRUE; + prefs.show_progress_box = TRUE; + prefs.show_reload = TRUE; + prefs.show_save = TRUE; + prefs.show_search = TRUE; + prefs.show_stop = TRUE; + prefs.show_tools = TRUE; + prefs.show_tooltip = TRUE; + prefs.show_url = TRUE; + prefs.small_icons = FALSE; + prefs.start_page = a_Url_new(PREFS_START_PAGE, NULL); + prefs.w3c_plus_heuristics = TRUE; } /* - * Preferences memory-deallocation + * memory-deallocation * (Call this one at exit time) */ void a_Prefs_freeall(void) { + dFree(prefs.font_cursive); + dFree(prefs.font_fantasy); + dFree(prefs.font_monospace); + dFree(prefs.font_sans_serif); + dFree(prefs.font_serif); + a_Url_free(prefs.home); + dFree(prefs.http_language); + a_Url_free(prefs.http_proxy); dFree(prefs.http_proxyuser); dFree(prefs.http_referer); + dFree(prefs.http_user_agent); 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); dFree(prefs.save_dir); + dFree(prefs.search_url); + a_Url_free(prefs.start_page); } diff --git a/src/prefs.h b/src/prefs.h index 7a409631..684262ed 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -1,3 +1,14 @@ +/* + * Preferences + * + * Copyright (C) 2006-2009 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. + */ + #ifndef __PREFS_H__ #define __PREFS_H__ @@ -7,9 +18,17 @@ extern "C" { #endif /* __cplusplus */ +#define PREFS_GEOMETRY_DEFAULT_WIDTH 780 +#define PREFS_GEOMETRY_DEFAULT_HEIGHT 580 +#define PREFS_GEOMETRY_DEFAULT_XPOS -9999 +#define PREFS_GEOMETRY_DEFAULT_YPOS -9999 + /* Panel sizes */ enum { P_tiny = 0, P_small, P_medium, P_large }; +enum {PREFS_FILTER_ALLOW_ALL, + PREFS_FILTER_SAME_DOMAIN}; + typedef struct _DilloPrefs DilloPrefs; struct _DilloPrefs { @@ -17,20 +36,17 @@ struct _DilloPrefs { int height; int xpos; int ypos; + char *http_language; + int32_t http_max_conns; DilloUrl *http_proxy; char *http_proxyuser; char *http_referer; + char *http_user_agent; 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 force_my_colors; bool_t contrast_visited_color; - bool_t standard_widget_colors; bool_t show_tooltip; int panel_size; bool_t small_icons; @@ -38,6 +54,8 @@ struct _DilloPrefs { bool_t w3c_plus_heuristics; bool_t focus_new_tab; double font_factor; + int32_t font_max_size; + int32_t font_min_size; bool_t show_back; bool_t show_forw; bool_t show_home; @@ -45,23 +63,31 @@ struct _DilloPrefs { bool_t show_save; bool_t show_stop; bool_t show_bookmarks; + bool_t show_tools; bool_t show_filemenu; bool_t show_clear_url; bool_t show_url; bool_t show_search; + bool_t show_help; bool_t show_progress_box; bool_t fullwindow_start; bool_t load_images; + bool_t load_stylesheets; + bool_t parse_embedded_css; + int filter_auto_requests; int32_t buffered_drawing; - char *vw_fontname; - char *fw_fontname; - bool_t generate_submit; + char *font_serif; + char *font_sans_serif; + char *font_cursive; + char *font_fantasy; + char *font_monospace; bool_t enterpress_forces_submit; bool_t middle_click_opens_new_tab; char *search_url; char *save_dir; bool_t show_msg; bool_t show_extra_warnings; + bool_t middle_click_drags_page; }; /* Global Data */ diff --git a/src/prefsparser.cc b/src/prefsparser.cc new file mode 100644 index 00000000..78cade0b --- /dev/null +++ b/src/prefsparser.cc @@ -0,0 +1,208 @@ +/* + * Preferences parser + * + * Copyright (C) 2006-2009 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/types.h> +#include <stdlib.h> +#include <locale.h> /* for setlocale */ + +#include "prefs.h" +#include "misc.h" +#include "msg.h" + +#include "prefsparser.hh" + +typedef enum { + PREFS_BOOL, + PREFS_STRING, + PREFS_URL, + PREFS_INT32, + PREFS_DOUBLE, + PREFS_GEOMETRY, + PREFS_FILTER, + PREFS_PANEL_SIZE +} PrefType_t; + +typedef struct SymNode_ { + const char *name; + void *pref; + PrefType_t type; +} SymNode_t; + +/* + * Parse a name/value pair and set preferences accordingly. + */ +int PrefsParser::parseOption(char *name, char *value) +{ + const SymNode_t *node; + uint_t i; + + /* Symbol array, sorted alphabetically */ + const SymNode_t symbols[] = { + { "allow_white_bg", &prefs.allow_white_bg, PREFS_BOOL }, + { "buffered_drawing", &prefs.buffered_drawing, PREFS_INT32 }, + { "contrast_visited_color", &prefs.contrast_visited_color, PREFS_BOOL }, + { "enterpress_forces_submit", &prefs.enterpress_forces_submit, + PREFS_BOOL }, + { "filter_auto_requests", &prefs.filter_auto_requests, PREFS_FILTER }, + { "focus_new_tab", &prefs.focus_new_tab, PREFS_BOOL }, + { "font_cursive", &prefs.font_cursive, PREFS_STRING }, + { "font_factor", &prefs.font_factor, PREFS_DOUBLE }, + { "font_fantasy", &prefs.font_fantasy, PREFS_STRING }, + { "font_max_size", &prefs.font_max_size, PREFS_INT32 }, + { "font_min_size", &prefs.font_min_size, PREFS_INT32 }, + { "font_monospace", &prefs.font_monospace, PREFS_STRING }, + { "font_sans_serif", &prefs.font_sans_serif, PREFS_STRING }, + { "font_serif", &prefs.font_serif, PREFS_STRING }, + { "fullwindow_start", &prefs.fullwindow_start, PREFS_BOOL }, + { "geometry", NULL, PREFS_GEOMETRY }, + { "home", &prefs.home, PREFS_URL }, + { "http_language", &prefs.http_language, PREFS_STRING }, + { "http_max_conns", &prefs.http_max_conns, PREFS_INT32 }, + { "http_proxy", &prefs.http_proxy, PREFS_URL }, + { "http_proxyuser", &prefs.http_proxyuser, PREFS_STRING }, + { "http_referer", &prefs.http_referer, PREFS_STRING }, + { "http_user_agent", &prefs.http_user_agent, PREFS_STRING }, + { "limit_text_width", &prefs.limit_text_width, PREFS_BOOL }, + { "load_images", &prefs.load_images, PREFS_BOOL }, + { "load_stylesheets", &prefs.load_stylesheets, PREFS_BOOL }, + { "middle_click_drags_page", &prefs.middle_click_drags_page, + PREFS_BOOL }, + { "middle_click_opens_new_tab", &prefs.middle_click_opens_new_tab, + PREFS_BOOL }, + { "no_proxy", &prefs.no_proxy, PREFS_STRING }, + { "panel_size", &prefs.panel_size, PREFS_PANEL_SIZE }, + { "parse_embedded_css", &prefs.parse_embedded_css, PREFS_BOOL }, + { "save_dir", &prefs.save_dir, PREFS_STRING }, + { "search_url", &prefs.search_url, PREFS_STRING }, + { "show_back", &prefs.show_back, PREFS_BOOL }, + { "show_bookmarks", &prefs.show_bookmarks, PREFS_BOOL }, + { "show_clear_url", &prefs.show_clear_url, PREFS_BOOL }, + { "show_extra_warnings", &prefs.show_extra_warnings, PREFS_BOOL }, + { "show_filemenu", &prefs.show_filemenu, PREFS_BOOL }, + { "show_forw", &prefs.show_forw, PREFS_BOOL }, + { "show_help", &prefs.show_help, PREFS_BOOL }, + { "show_home", &prefs.show_home, PREFS_BOOL }, + { "show_msg", &prefs.show_msg, PREFS_BOOL }, + { "show_progress_box", &prefs.show_progress_box, PREFS_BOOL }, + { "show_reload", &prefs.show_reload, PREFS_BOOL }, + { "show_save", &prefs.show_save, PREFS_BOOL }, + { "show_search", &prefs.show_search, PREFS_BOOL }, + { "show_stop", &prefs.show_stop, PREFS_BOOL }, + { "show_tools", &prefs.show_tools, PREFS_BOOL }, + { "show_tooltip", &prefs.show_tooltip, PREFS_BOOL }, + { "show_url", &prefs.show_url, PREFS_BOOL }, + { "small_icons", &prefs.small_icons, PREFS_BOOL }, + { "start_page", &prefs.start_page, PREFS_URL }, + { "w3c_plus_heuristics", &prefs.w3c_plus_heuristics, PREFS_BOOL } + }; + + node = NULL; + for (i = 0; i < sizeof(symbols) / sizeof(SymNode_t); i++) { + if (!strcmp(symbols[i].name, name)) { + node = & (symbols[i]); + break; + } + } + + if (!node) { + MSG("prefs: {%s} is not a recognized token.\n", name); + return -1; + } + + switch (node->type) { + case PREFS_BOOL: + *(bool_t *)node->pref = (!dStrcasecmp(value, "yes") || + !dStrcasecmp(value, "true")); + break; + case PREFS_STRING: + dFree(*(char **)node->pref); + *(char **)node->pref = dStrdup(value); + break; + case PREFS_URL: + a_Url_free(*(DilloUrl **)node->pref); + *(DilloUrl **)node->pref = a_Url_new(value, NULL); + break; + case PREFS_INT32: + *(int32_t *)node->pref = strtol(value, NULL, 10); + break; + case PREFS_DOUBLE: + *(double *)node->pref = strtod(value, NULL); + break; + case PREFS_GEOMETRY: + a_Misc_parse_geometry(value, &prefs.xpos, &prefs.ypos, + &prefs.width, &prefs.height); + break; + case PREFS_FILTER: + if (!dStrcasecmp(value, "same_domain")) + prefs.filter_auto_requests = PREFS_FILTER_SAME_DOMAIN; + else { + if (dStrcasecmp(value, "allow_all")) + MSG_WARN("prefs: unrecognized value for filter_auto_requests\n"); + prefs.filter_auto_requests = PREFS_FILTER_ALLOW_ALL; + } + break; + case PREFS_PANEL_SIZE: + if (!dStrcasecmp(value, "tiny")) + prefs.panel_size = P_tiny; + else if (!dStrcasecmp(value, "small")) + prefs.panel_size = P_small; + else if (!dStrcasecmp(value, "large")) + prefs.panel_size = P_large; + else /* default to "medium" */ + prefs.panel_size = P_medium; + break; + default: + MSG_WARN("prefs: {%s} IS recognized but not handled!\n", name); + break; /* Not reached */ + } + + if (prefs.limit_text_width) { + /* BUG: causes 100% CPU usage with <button> or <input type="image"> */ + MSG_WARN("Disabling limit_text_width preference (currently broken).\n"); + prefs.limit_text_width = FALSE; + } + + return 0; +} + +/* + * Parses the dillorc and sets the values in the prefs structure. + */ +void PrefsParser::parse(FILE *fp) +{ + char *line, *name, *value, *oldLocale; + int st; + + // changing the LC_NUMERIC locale (temporarily) to C + // avoids parsing problems with float numbers + oldLocale = dStrdup(setlocale(LC_NUMERIC, NULL)); + setlocale(LC_NUMERIC, "C"); + + // scan the file line by line + while ((line = dGetline(fp)) != NULL) { + st = dParser_parse_rc_line(&line, &name, &value); + + if (st == 0) { + _MSG("prefsparser: name=%s, value=%s\n", name, value); + parseOption(name, value); + } else if (st < 0) { + MSG_ERR("prefsparser: Syntax error in dillorc:" + " name=\"%s\" value=\"%s\"\n", name, value); + } + + dFree(line); + } + fclose(fp); + + // restore the old numeric locale + setlocale(LC_NUMERIC, oldLocale); + dFree(oldLocale); +} diff --git a/src/prefsparser.hh b/src/prefsparser.hh new file mode 100644 index 00000000..d10c43c4 --- /dev/null +++ b/src/prefsparser.hh @@ -0,0 +1,24 @@ +/* + * Preferences parser + * + * Copyright (C) 2006-2009 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. + */ + +#ifndef __PREFS_HH__ +#define __PREFS_HH__ + +#ifdef __cplusplus +class PrefsParser { +public: + static int parseOption(char *name, char *value); + static int parseLine(char *line, char *name, char *value); + static void parse(FILE *fp); +}; +#endif /* __cplusplus */ + +#endif /* __PREFS_HH__ */ diff --git a/src/styleengine.cc b/src/styleengine.cc new file mode 100644 index 00000000..906f47ee --- /dev/null +++ b/src/styleengine.cc @@ -0,0 +1,678 @@ +/* + * File: styleengine.cc + * + * Copyright 2008-2009 Johannes Hofmann <Johannes.Hofmann@gmx.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 3 of the License, or + * (at your option) any later version. + */ + +#include "../dlib/dlib.h" +#include "msg.h" +#include "prefs.h" +#include "html_common.hh" +#include "styleengine.hh" + +using namespace dw::core::style; + +StyleEngine::StyleEngine (dw::core::Layout *layout) { + StyleAttrs style_attrs; + FontAttrs font_attrs; + + doctree = new Doctree (); + stack = new lout::misc::SimpleVector <Node> (1); + cssContext = new CssContext (); + this->layout = layout; + importDepth = 0; + + stack->increase (); + Node *n = stack->getRef (stack->size () - 1); + + /* Create a dummy font, attribute, and tag for the bottom of the stack. */ + font_attrs.name = prefs.font_sans_serif; + font_attrs.size = (int) (14 * prefs.font_factor + 0.5); + if (font_attrs.size < prefs.font_min_size) + font_attrs.size = prefs.font_min_size; + if (font_attrs.size > prefs.font_max_size) + font_attrs.size = prefs.font_max_size; + font_attrs.weight = 400; + font_attrs.style = FONT_STYLE_NORMAL; + font_attrs.letterSpacing = 0; + + style_attrs.initValues (); + style_attrs.font = Font::create (layout, &font_attrs); + style_attrs.color = Color::create (layout, 0); + style_attrs.backgroundColor = Color::create (layout, 0xffffff); + + n->style = Style::create (layout, &style_attrs); + n->wordStyle = NULL; + n->styleAttribute = NULL; + n->inheritBackgroundColor = false; +} + +StyleEngine::~StyleEngine () { + while (doctree->top ()) + endElement (doctree->top ()->element); + delete stack; + delete doctree; + delete cssContext; +} + +/** + * \brief tell the styleEngine that a new html element has started. + */ +void StyleEngine::startElement (int element) { + if (stack->getRef (stack->size () - 1)->style == NULL) + style0 (); + + stack->increase (); + Node *n = stack->getRef (stack->size () - 1); + n->style = NULL; + n->wordStyle = NULL; + n->styleAttribute = NULL; + n->inheritBackgroundColor = false; + + DoctreeNode *dn = doctree->push (); + dn->element = element; +} + +void StyleEngine::startElement (const char *tagname) { + startElement (a_Html_tag_index (tagname)); +} + +void StyleEngine::setId (const char *id) { + DoctreeNode *dn = doctree->top (); + assert (dn->id == NULL); + dn->id = dStrdup (id); +}; + +/** + * \brief split a string at sep chars and return a SimpleVector of strings + */ +static lout::misc::SimpleVector<char *> *splitStr (const char *str, char sep) { + const char *p1 = NULL; + lout::misc::SimpleVector<char *> *list = + new lout::misc::SimpleVector<char *> (1); + + for (;; str++) { + if (*str != '\0' && *str != sep) { + if (!p1) + p1 = str; + } else if (p1) { + list->increase (); + list->set (list->size () - 1, dStrndup (p1, str - p1)); + p1 = NULL; + } + + if (*str == '\0') + break; + } + + return list; +} + +void StyleEngine::setClass (const char *klass) { + DoctreeNode *dn = doctree->top (); + assert (dn->klass == NULL); + dn->klass = splitStr (klass, ' '); +}; + +void StyleEngine::setStyle (const char *style) { + Node *n = stack->getRef (stack->size () - 1); + assert (n->styleAttribute == NULL); + n->styleAttribute = dStrdup (style); +}; + +/** + * \brief set properties that were definded using (mostly deprecated) HTML + * attributes (e.g. bgColor). + */ +void StyleEngine::setNonCssHints (CssPropertyList *nonCssHints) { + if (stack->getRef (stack->size () - 1)->style) + stack->getRef (stack->size () - 1)->style->unref (); + style0 (nonCssHints); // evaluate now, so caller can free nonCssHints +} + +/** + * \brief Use of the background color of the parent style as default. + * This is only used in table code to allow for colors specified for + * table rows as table rows are currently no widgets and therefore + * don't draw any background. + */ +void StyleEngine::inheritBackgroundColor () { + stack->getRef (stack->size () - 1)->inheritBackgroundColor = true; +} + +/** + * \brief set the CSS pseudo class :link. + */ +void StyleEngine::setPseudoLink () { + DoctreeNode *dn = doctree->top (); + dn->pseudo = "link"; +} + +/** + * \brief set the CSS pseudo class :visited. + */ +void StyleEngine::setPseudoVisited () { + DoctreeNode *dn = doctree->top (); + dn->pseudo = "visited"; +} + +/** + * \brief tell the styleEngine that a html element has ended. + */ +void StyleEngine::endElement (int element) { + assert (stack->size () > 0); + assert (element == doctree->top ()->element); + + Node *n = stack->getRef (stack->size () - 1); + + if (n->style) + n->style->unref (); + if (n->wordStyle) + n->wordStyle->unref (); + if (n->styleAttribute) + dFree ((void*) n->styleAttribute); + + doctree->pop (); + stack->setSize (stack->size () - 1); +} + +/** + * \brief Make changes to StyleAttrs attrs according to CssPropertyList props. + */ +void StyleEngine::apply (StyleAttrs *attrs, CssPropertyList *props) { + FontAttrs fontAttrs = *attrs->font; + Font *parentFont = stack->get (stack->size () - 2).style->font; + char *c, *fontName; + int lineHeight; + + /* Determine font first so it can be used to resolve relative lenths. */ + for (int i = 0; i < props->size (); i++) { + CssProperty *p = props->getRef (i); + + switch (p->name) { + case CSS_PROPERTY_FONT_FAMILY: + // Check font names in comma separated list. + // Note, that p->value.strVal is modified, so that in future calls + // the matching font name can be used directly. + fontName = NULL; + while (p->value.strVal) { + if ((c = strchr(p->value.strVal, ','))) + *c = '\0'; + dStrstrip(p->value.strVal); + + if (strcmp (p->value.strVal, "serif") == 0) + fontName = prefs.font_serif; + else if (strcmp (p->value.strVal, "sans-serif") == 0) + fontName = prefs.font_sans_serif; + else if (strcmp (p->value.strVal, "cursive") == 0) + fontName = prefs.font_cursive; + else if (strcmp (p->value.strVal, "fantasy") == 0) + fontName = prefs.font_fantasy; + else if (strcmp (p->value.strVal, "monospace") == 0) + fontName = prefs.font_monospace; + else if (Font::exists(layout, p->value.strVal)) + fontName = p->value.strVal; + + if (fontName) { // font found + fontAttrs.name = fontName; + break; + } else if (c) { // try next from list + memmove(p->value.strVal, c + 1, strlen(c + 1) + 1); + } else { // no font found + break; + } + } + + break; + case CSS_PROPERTY_FONT_SIZE: + if (p->type == CSS_TYPE_ENUM) { + switch (p->value.intVal) { + case CSS_FONT_SIZE_XX_SMALL: + fontAttrs.size = (int) (11.0 * prefs.font_factor + 0.5); + break; + case CSS_FONT_SIZE_X_SMALL: + fontAttrs.size = (int) (12.0 * prefs.font_factor + 0.5); + break; + case CSS_FONT_SIZE_SMALL: + fontAttrs.size = (int) (13.0 * prefs.font_factor + 0.5); + break; + case CSS_FONT_SIZE_MEDIUM: + fontAttrs.size = (int) (14.0 * prefs.font_factor + 0.5); + break; + case CSS_FONT_SIZE_LARGE: + fontAttrs.size = (int) (15.0 * prefs.font_factor + 0.5); + break; + case CSS_FONT_SIZE_X_LARGE: + fontAttrs.size = (int) (16.0 * prefs.font_factor + 0.5); + break; + case CSS_FONT_SIZE_XX_LARGE: + fontAttrs.size = (int) (17.0 * prefs.font_factor + 0.5); + break; + case CSS_FONT_SIZE_SMALLER: + fontAttrs.size -= (int) (1.0 * prefs.font_factor + 0.5); + break; + case CSS_FONT_SIZE_LARGER: + fontAttrs.size += (int) (1.0 * prefs.font_factor + 0.5); + break; + default: + assert(false); // invalid font-size enum + } + } else { + computeValue (&fontAttrs.size, p->value.intVal, parentFont, + parentFont->size); + } + + if (fontAttrs.size < prefs.font_min_size) + fontAttrs.size = prefs.font_min_size; + if (fontAttrs.size > prefs.font_max_size) + fontAttrs.size = prefs.font_max_size; + + break; + case CSS_PROPERTY_FONT_STYLE: + fontAttrs.style = (FontStyle) p->value.intVal; + break; + case CSS_PROPERTY_FONT_WEIGHT: + + if (p->type == CSS_TYPE_ENUM) { + switch (p->value.intVal) { + case CSS_FONT_WEIGHT_BOLD: + fontAttrs.weight = 700; + break; + case CSS_FONT_WEIGHT_BOLDER: + fontAttrs.weight += 300; + break; + case CSS_FONT_WEIGHT_LIGHT: + fontAttrs.weight = 100; + break; + case CSS_FONT_WEIGHT_LIGHTER: + fontAttrs.weight -= 300; + break; + case CSS_FONT_WEIGHT_NORMAL: + fontAttrs.weight = 400; + break; + default: + assert(false); // invalid font weight value + break; + } + } else { + fontAttrs.weight = p->value.intVal; + } + + if (fontAttrs.weight < 100) + fontAttrs.weight = 100; + if (fontAttrs.weight > 900) + fontAttrs.weight = 900; + + break; + case CSS_PROPERTY_LETTER_SPACING: + if (p->type == CSS_TYPE_ENUM) { + if (p->value.intVal == CSS_LETTER_SPACING_NORMAL) { + fontAttrs.letterSpacing = 0; + } + } else { + computeValue (&fontAttrs.letterSpacing, p->value.intVal, + parentFont, parentFont->size); + } + + /* Limit letterSpacing to reasonable values to avoid overflows e.g, + * when measuring word width. + */ + if (fontAttrs.letterSpacing > 1000) + fontAttrs.letterSpacing = 1000; + else if (fontAttrs.letterSpacing < -1000) + fontAttrs.letterSpacing = -1000; + break; + default: + break; + } + } + + attrs->font = Font::create (layout, &fontAttrs); + + for (int i = 0; i < props->size (); i++) { + CssProperty *p = props->getRef (i); + + switch (p->name) { + /* \todo missing cases */ + case CSS_PROPERTY_BACKGROUND_COLOR: + if (prefs.allow_white_bg || p->value.intVal != 0xffffff) + attrs->backgroundColor = Color::create(layout, p->value.intVal); + else + //attrs->backgroundColor = Color::create(layout, 0xdcd1ba); + attrs->backgroundColor = Color::create(layout, 0xe0e0a3); + break; + case CSS_PROPERTY_BORDER_TOP_COLOR: + attrs->borderColor.top = + Color::create (layout, p->value.intVal); + break; + case CSS_PROPERTY_BORDER_BOTTOM_COLOR: + attrs->borderColor.bottom = + Color::create (layout, p->value.intVal); + break; + case CSS_PROPERTY_BORDER_LEFT_COLOR: + attrs->borderColor.left = + Color::create (layout, p->value.intVal); + break; + case CSS_PROPERTY_BORDER_RIGHT_COLOR: + attrs->borderColor.right = + Color::create (layout, p->value.intVal); + break; + case CSS_PROPERTY_BORDER_BOTTOM_STYLE: + attrs->borderStyle.bottom = (BorderStyle) p->value.intVal; + break; + case CSS_PROPERTY_BORDER_LEFT_STYLE: + attrs->borderStyle.left = (BorderStyle) p->value.intVal; + break; + case CSS_PROPERTY_BORDER_RIGHT_STYLE: + attrs->borderStyle.right = (BorderStyle) p->value.intVal; + break; + case CSS_PROPERTY_BORDER_TOP_STYLE: + attrs->borderStyle.top = (BorderStyle) p->value.intVal; + break; + case CSS_PROPERTY_BORDER_BOTTOM_WIDTH: + computeBorderWidth (&attrs->borderWidth.bottom, p, attrs->font); + break; + case CSS_PROPERTY_BORDER_LEFT_WIDTH: + computeBorderWidth (&attrs->borderWidth.left, p, attrs->font); + break; + case CSS_PROPERTY_BORDER_RIGHT_WIDTH: + computeBorderWidth (&attrs->borderWidth.right, p, attrs->font); + break; + case CSS_PROPERTY_BORDER_TOP_WIDTH: + computeBorderWidth (&attrs->borderWidth.top, p, attrs->font); + break; + case CSS_PROPERTY_BORDER_SPACING: + computeValue (&attrs->hBorderSpacing, p->value.intVal,attrs->font); + computeValue (&attrs->vBorderSpacing, p->value.intVal,attrs->font); + break; + case CSS_PROPERTY_COLOR: + attrs->color = Color::create (layout, p->value.intVal); + break; + case CSS_PROPERTY_CURSOR: + attrs->cursor = (Cursor) p->value.intVal; + break; + case CSS_PROPERTY_DISPLAY: + attrs->display = (DisplayType) p->value.intVal; + break; + case CSS_PROPERTY_LINE_HEIGHT: + if (p->type == CSS_TYPE_ENUM) { //only valid enum value is "normal" + attrs->lineHeight = dw::core::style::LENGTH_AUTO; + } else if (p->type == CSS_TYPE_LENGTH_PERCENTAGE_NUMBER) { + if (CSS_LENGTH_TYPE (p->value.intVal) == CSS_LENGTH_TYPE_NONE) { + attrs->lineHeight = + createPerLength(CSS_LENGTH_VALUE(p->value.intVal)); + } else { + computeValue (&lineHeight, p->value.intVal, attrs->font, + attrs->font->size); + attrs->lineHeight = createAbsLength(lineHeight); + } + } + break; + case CSS_PROPERTY_LIST_STYLE_POSITION: + attrs->listStylePosition = (ListStylePosition) p->value.intVal; + break; + case CSS_PROPERTY_LIST_STYLE_TYPE: + attrs->listStyleType = (ListStyleType) p->value.intVal; + break; + case CSS_PROPERTY_MARGIN_BOTTOM: + computeValue (&attrs->margin.bottom, p->value.intVal, attrs->font); + if (attrs->margin.bottom < 0) // \todo fix negative margins in dw/* + attrs->margin.bottom = 0; + break; + case CSS_PROPERTY_MARGIN_LEFT: + computeValue (&attrs->margin.left, p->value.intVal, attrs->font); + if (attrs->margin.left < 0) // \todo fix negative margins in dw/* + attrs->margin.left = 0; + break; + case CSS_PROPERTY_MARGIN_RIGHT: + computeValue (&attrs->margin.right, p->value.intVal, attrs->font); + if (attrs->margin.right < 0) // \todo fix negative margins in dw/* + attrs->margin.right = 0; + break; + case CSS_PROPERTY_MARGIN_TOP: + computeValue (&attrs->margin.top, p->value.intVal, attrs->font); + if (attrs->margin.top < 0) // \todo fix negative margins in dw/* + attrs->margin.top = 0; + break; + case CSS_PROPERTY_PADDING_TOP: + computeValue (&attrs->padding.top, p->value.intVal, attrs->font); + break; + case CSS_PROPERTY_PADDING_BOTTOM: + computeValue (&attrs->padding.bottom, p->value.intVal,attrs->font); + break; + case CSS_PROPERTY_PADDING_LEFT: + computeValue (&attrs->padding.left, p->value.intVal, attrs->font); + break; + case CSS_PROPERTY_PADDING_RIGHT: + computeValue (&attrs->padding.right, p->value.intVal, attrs->font); + break; + case CSS_PROPERTY_TEXT_ALIGN: + attrs->textAlign = (TextAlignType) p->value.intVal; + break; + case CSS_PROPERTY_TEXT_DECORATION: + attrs->textDecoration |= p->value.intVal; + break; + case CSS_PROPERTY_VERTICAL_ALIGN: + attrs->valign = (VAlignType) p->value.intVal; + break; + case CSS_PROPERTY_WHITE_SPACE: + attrs->whiteSpace = (WhiteSpace) p->value.intVal; + break; + case CSS_PROPERTY_WIDTH: + computeLength (&attrs->width, p->value.intVal, attrs->font); + break; + case CSS_PROPERTY_HEIGHT: + computeLength (&attrs->height, p->value.intVal, attrs->font); + break; + case CSS_PROPERTY_WORD_SPACING: + if (p->type == CSS_TYPE_ENUM) { + if (p->value.intVal == CSS_WORD_SPACING_NORMAL) { + attrs->wordSpacing = 0; + } + } else { + computeValue(&attrs->wordSpacing, p->value.intVal, attrs->font); + } + + /* Limit to reasonable values to avoid overflows */ + if (attrs->wordSpacing > 1000) + attrs->wordSpacing = 1000; + else if (attrs->wordSpacing < -1000) + attrs->wordSpacing = -1000; + break; + case PROPERTY_X_LINK: + attrs->x_link = p->value.intVal; + break; + case PROPERTY_X_IMG: + attrs->x_img = p->value.intVal; + break; + case PROPERTY_X_TOOLTIP: + attrs->x_tooltip = Tooltip::create(layout, p->value.strVal); + break; + default: + break; + } + } + + /* make sure border colors are set */ + if (attrs->borderColor.top == NULL) + attrs->borderColor.top = attrs->color; + if (attrs->borderColor.bottom == NULL) + attrs->borderColor.bottom = attrs->color; + if (attrs->borderColor.left == NULL) + attrs->borderColor.left = attrs->color; + if (attrs->borderColor.right == NULL) + attrs->borderColor.right = attrs->color; + +} + +/** + * \brief Resolve relative lengths to absolute values. + */ +bool StyleEngine::computeValue (int *dest, CssLength value, Font *font) { + static float dpmm; + + if (dpmm == 0.0) + dpmm = layout->dpiX () / 25.4; /* assume dpiX == dpiY */ + + switch (CSS_LENGTH_TYPE (value)) { + case CSS_LENGTH_TYPE_PX: + *dest = (int) CSS_LENGTH_VALUE (value); + return true; + case CSS_LENGTH_TYPE_MM: + *dest = (int) (CSS_LENGTH_VALUE (value) * dpmm + 0.5); + return true; + case CSS_LENGTH_TYPE_EM: + *dest = (int) (CSS_LENGTH_VALUE (value) * font->size + 0.5); + return true; + case CSS_LENGTH_TYPE_EX: + *dest = (int) (CSS_LENGTH_VALUE(value) * font->xHeight + 0.5); + return true; + default: + break; + } + + return false; +} + +bool StyleEngine::computeValue (int *dest, CssLength value, Font *font, + int percentageBase) { + if (CSS_LENGTH_TYPE (value) == CSS_LENGTH_TYPE_PERCENTAGE) { + *dest = (int) (CSS_LENGTH_VALUE (value) * percentageBase + 0.5); + return true; + } else + return computeValue (dest, value, font); +} + +bool StyleEngine::computeLength (dw::core::style::Length *dest, + CssLength value, Font *font) { + int v; + + if (CSS_LENGTH_TYPE (value) == CSS_LENGTH_TYPE_PERCENTAGE) { + *dest = createPerLength (CSS_LENGTH_VALUE (value)); + return true; + } else if (CSS_LENGTH_TYPE (value) == CSS_LENGTH_TYPE_AUTO) { + *dest = dw::core::style::LENGTH_AUTO; + return true; + } else if (computeValue (&v, value, font)) { + *dest = createAbsLength (v); + return true; + } + + return false; +} + +void StyleEngine::computeBorderWidth (int *dest, CssProperty *p, + dw::core::style::Font *font) { + if (p->type == CSS_TYPE_ENUM) { + switch (p->value.intVal) { + case CSS_BORDER_WIDTH_THIN: + *dest = 1; + break; + case CSS_BORDER_WIDTH_MEDIUM: + *dest = 2; + break; + case CSS_BORDER_WIDTH_THICK: + *dest = 3; + break; + default: + assert(false); + } + } else { + computeValue (dest, p->value.intVal, font); + } +} + +/** + * \brief Similar to StyleEngine::style(), but with backgroundColor set. + * A normal style might have backgroundColor == NULL to indicate a transparent + * background. This method ensures that backgroundColor is set. + */ +Style * StyleEngine::backgroundStyle () { + StyleAttrs attrs = *style (); + + for (int i = stack->size () - 1; i >= 0 && ! attrs.backgroundColor; i--) + attrs.backgroundColor = stack->getRef (i)->style->backgroundColor; + + assert (attrs.backgroundColor); + return Style::create (layout, &attrs); +} + +/** + * \brief Create a new style object based on the previously opened / closed + * HTML elements and the nonCssProperties that have been set. + * This method is private. Call style() to get a current style object. + */ +Style * StyleEngine::style0 (CssPropertyList *nonCssProperties) { + CssPropertyList props, *styleAttributeProps = NULL; + const char *styleAttribute = + stack->getRef (stack->size () - 1)->styleAttribute; + // get previous style from the stack + StyleAttrs attrs = *stack->getRef (stack->size () - 2)->style; + + // Ensure that StyleEngine::style0() has not been called before for + // this element. + // Style computation is expensive so limit it as much as possible. + // If this assertion is hit, you need to rearrange the code that is + // doing styleEngine calls to call setNonCssHints() before calling + // style() or wordStyle() for each new element. + assert (stack->getRef (stack->size () - 1)->style == NULL); + + // reset values that are not inherited according to CSS + attrs.resetValues (); + + if (stack->getRef (stack->size () - 2)->inheritBackgroundColor) { + attrs.backgroundColor = + stack->getRef (stack->size () - 2)->style->backgroundColor; + + attrs.valign = stack->getRef (stack->size () - 2)->style->valign; + } + + // parse style information from style="" attribute, if it exists + if (styleAttribute && prefs.parse_embedded_css) + styleAttributeProps = + CssParser::parseDeclarationBlock (styleAttribute, + strlen (styleAttribute)); + + // merge style information + cssContext->apply (&props, doctree, styleAttributeProps, nonCssProperties); + + // apply style + apply (&attrs, &props); + + stack->getRef (stack->size () - 1)->style = Style::create (layout, &attrs); + + if (styleAttributeProps) + delete styleAttributeProps; + + return stack->getRef (stack->size () - 1)->style; +} + +Style * StyleEngine::wordStyle0 (CssPropertyList *nonCssProperties) { + StyleAttrs attrs = *style (); + attrs.resetValues (); + + if (stack->getRef (stack->size () - 1)->inheritBackgroundColor) + attrs.backgroundColor = style ()->backgroundColor; + + attrs.valign = style ()->valign; + + stack->getRef(stack->size() - 1)->wordStyle = Style::create(layout, &attrs); + return stack->getRef (stack->size () - 1)->wordStyle; +} + +void StyleEngine::parse (DilloHtml *html, DilloUrl *url, const char *buf, + int buflen, CssOrigin origin) { + if (importDepth > 10) { // avoid looping with recursive @import directives + MSG_WARN("Maximum depth of CSS @import reached--ignoring stylesheet.\n"); + return; + } + + importDepth++; + CssParser::parse (html, url, cssContext, buf, buflen, origin); + importDepth--; +} diff --git a/src/styleengine.hh b/src/styleengine.hh new file mode 100644 index 00000000..66f28cee --- /dev/null +++ b/src/styleengine.hh @@ -0,0 +1,84 @@ +#ifndef __STYLEENGINE_HH__ +#define __STYLEENGINE_HH__ + +class StyleEngine; + +#include "dw/core.hh" +#include "doctree.hh" +#include "css.hh" +#include "cssparser.hh" + +/** + * \brief This class provides the glue between HTML parser and CSS subsystem. + * + * It maintains a document tree and creates and caches style objects for use + * by the HTML parser. + * The HTML parser in turn informs StyleEngine about opened or closed + * HTML elements and their attributes via the startElement() / endElement() + * methods. + */ +class StyleEngine { + private: + class Node { + public: + dw::core::style::Style *style; + dw::core::style::Style *wordStyle; + const char *styleAttribute; + bool inheritBackgroundColor; + }; + + dw::core::Layout *layout; + lout::misc::SimpleVector <Node> *stack; + CssContext *cssContext; + Doctree *doctree; + int importDepth; + + dw::core::style::Style *style0 (CssPropertyList *nonCssHints = NULL); + dw::core::style::Style *wordStyle0 (CssPropertyList *nonCssHints = NULL); + void apply (dw::core::style::StyleAttrs *attrs, CssPropertyList *props); + bool computeValue (int *dest, CssLength value, + dw::core::style::Font *font); + bool computeValue (int *dest, CssLength value, + dw::core::style::Font *font, int percentageBase); + bool computeLength (dw::core::style::Length *dest, CssLength value, + dw::core::style::Font *font); + void computeBorderWidth (int *dest, CssProperty *p, + dw::core::style::Font *font); + + public: + StyleEngine (dw::core::Layout *layout); + ~StyleEngine (); + + void parse (DilloHtml *html, DilloUrl *url, const char *buf, int buflen, + CssOrigin origin); + void startElement (int tag); + void startElement (const char *tagname); + void setId (const char *id); + const char * getId () { return doctree->top ()->id; }; + void setClass (const char *klass); + void setStyle (const char *style); + void endElement (int tag); + void setPseudoLink (); + void setPseudoVisited (); + void setNonCssHints (CssPropertyList *nonCssHints); + void inheritBackgroundColor (); /* \todo get rid of this somehow */ + dw::core::style::Style *backgroundStyle (); + + inline dw::core::style::Style *style () { + dw::core::style::Style *s = stack->getRef (stack->size () - 1)->style; + if (s) + return s; + else + return style0 (); + }; + + inline dw::core::style::Style *wordStyle () { + dw::core::style::Style *s = stack->getRef(stack->size()-1)->wordStyle; + if (s) + return s; + else + return wordStyle0 (); + }; +}; + +#endif diff --git a/src/table.cc b/src/table.cc index 589f7e1e..43304206 100644 --- a/src/table.cc +++ b/src/table.cc @@ -18,40 +18,31 @@ #include "prefs.h" #include "msg.h" - -/* Undefine if you want to unroll tables. For instance for PDAs */ -#define USE_TABLES - -#define dillo_dbg_rendering 0 +#include "css.hh" using namespace dw; using namespace dw::core; using namespace dw::core::style; /* - * Forward declarations + * Forward declarations */ static void Html_tag_open_table_cell(DilloHtml *html, const char *tag, int tagsize, - dw::core::style::TextAlignType text_align); + dw::core::style::TextAlignType text_align); /* * <TABLE> */ void Html_tag_open_table(DilloHtml *html, const char *tag, int tagsize) { -#ifdef USE_TABLES dw::core::Widget *table; - dw::core::style::StyleAttrs style_attrs; - dw::core::style::Style *cell_style, *old_style; + CssPropertyList props, *table_cell_props; const char *attrbuf; - int32_t border = 0, cellspacing = 1, cellpadding = 2, bgcolor; -#endif + int32_t border = -1, cellspacing = -1, cellpadding = -1, bgcolor = -1; + CssLength cssLength; - DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style); - -#ifdef USE_TABLES if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "border"))) border = isdigit(attrbuf[0]) ? strtol (attrbuf, NULL, 10) : 1; if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "cellspacing"))) @@ -59,73 +50,85 @@ void Html_tag_open_table(DilloHtml *html, const char *tag, int tagsize) if ((attrbuf = a_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); + if (border != -1) { + cssLength = CSS_CREATE_LENGTH (border, CSS_LENGTH_TYPE_PX); + props.set (CSS_PROPERTY_BORDER_TOP_WIDTH, CSS_TYPE_LENGTH_PERCENTAGE, + cssLength); + props.set (CSS_PROPERTY_BORDER_BOTTOM_WIDTH, CSS_TYPE_LENGTH_PERCENTAGE, + cssLength); + props.set (CSS_PROPERTY_BORDER_LEFT_WIDTH, CSS_TYPE_LENGTH_PERCENTAGE, + cssLength); + props.set (CSS_PROPERTY_BORDER_RIGHT_WIDTH, CSS_TYPE_LENGTH_PERCENTAGE, + cssLength); + } - style_attrs.setBorderColor ( - Color::createShaded(HT2LT(html), S_TOP(html)->current_bg_color)); - style_attrs.setBorderStyle (BORDER_OUTSET); - style_attrs.hBorderSpacing = cellspacing; - style_attrs.vBorderSpacing = cellspacing; + if (cellspacing != -1) { + cssLength = CSS_CREATE_LENGTH (cellspacing, CSS_LENGTH_TYPE_PX); + props.set (CSS_PROPERTY_BORDER_SPACING, CSS_TYPE_LENGTH_PERCENTAGE, + cssLength); + } if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "width"))) - style_attrs.width = a_Html_parse_length (html, attrbuf); + props.set (CSS_PROPERTY_WIDTH, CSS_TYPE_LENGTH_PERCENTAGE, + a_Html_parse_length (html, attrbuf)); if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "align"))) { if (dStrcasecmp (attrbuf, "left") == 0) - style_attrs.textAlign = dw::core::style::TEXT_ALIGN_LEFT; + props.set (CSS_PROPERTY_TEXT_ALIGN, CSS_TYPE_ENUM, TEXT_ALIGN_LEFT); else if (dStrcasecmp (attrbuf, "right") == 0) - style_attrs.textAlign = dw::core::style::TEXT_ALIGN_RIGHT; + props.set (CSS_PROPERTY_TEXT_ALIGN, CSS_TYPE_ENUM, TEXT_ALIGN_RIGHT); else if (dStrcasecmp (attrbuf, "center") == 0) - style_attrs.textAlign = dw::core::style::TEXT_ALIGN_CENTER; + props.set (CSS_PROPERTY_TEXT_ALIGN, CSS_TYPE_ENUM, TEXT_ALIGN_CENTER); } - if (!prefs.force_my_colors && - (attrbuf = a_Html_get_attr(html, tag, tagsize, "bgcolor"))) { + if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "bgcolor"))) { bgcolor = a_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::createShaded (HT2LT(html), bgcolor); - } + if (bgcolor != -1) + props.set (CSS_PROPERTY_BACKGROUND_COLOR, CSS_TYPE_COLOR, bgcolor); } + html->styleEngine->setNonCssHints (&props); + + HT2TB(html)->addParbreak (0, html->styleEngine->wordStyle ()); + /* The style for the cells */ - cell_style = Style::create (HT2LT(html), &style_attrs); - 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 (cell_style->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_cell_props = new CssPropertyList (); + if (border > 0) { + cssLength = CSS_CREATE_LENGTH (1, CSS_LENGTH_TYPE_PX); + table_cell_props->set (CSS_PROPERTY_BORDER_TOP_WIDTH, + CSS_TYPE_LENGTH_PERCENTAGE, cssLength); + table_cell_props->set (CSS_PROPERTY_BORDER_BOTTOM_WIDTH, + CSS_TYPE_LENGTH_PERCENTAGE, cssLength); + table_cell_props->set (CSS_PROPERTY_BORDER_LEFT_WIDTH, + CSS_TYPE_LENGTH_PERCENTAGE, cssLength); + table_cell_props->set (CSS_PROPERTY_BORDER_RIGHT_WIDTH, + CSS_TYPE_LENGTH_PERCENTAGE, cssLength); + } + + if (cellpadding != -1) { + cssLength = CSS_CREATE_LENGTH (cellpadding, CSS_LENGTH_TYPE_PX); + table_cell_props->set (CSS_PROPERTY_PADDING_TOP, + CSS_TYPE_LENGTH_PERCENTAGE, cssLength); + table_cell_props->set (CSS_PROPERTY_PADDING_BOTTOM, + CSS_TYPE_LENGTH_PERCENTAGE, cssLength); + table_cell_props->set (CSS_PROPERTY_PADDING_LEFT, + CSS_TYPE_LENGTH_PERCENTAGE, cssLength); + table_cell_props->set (CSS_PROPERTY_PADDING_RIGHT, + CSS_TYPE_LENGTH_PERCENTAGE, cssLength); + } + + if (S_TOP(html)->table_cell_props) + S_TOP(html)->table_cell_props->unref (); + + S_TOP(html)->table_cell_props = table_cell_props; + S_TOP(html)->table_cell_props->ref (); table = new dw::Table(prefs.limit_text_width); - DW2TB(html->dw)->addWidget (table, cell_style); - cell_style->unref (); + HT2TB(html)->addWidget (table, html->styleEngine->style ()); S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TOP; S_TOP(html)->cell_text_align_set = FALSE; S_TOP(html)->table = table; -#endif } /* @@ -134,12 +137,10 @@ void Html_tag_open_table(DilloHtml *html, const char *tag, int tagsize) void Html_tag_open_tr(DilloHtml *html, const char *tag, int tagsize) { const char *attrbuf; - dw::core::style::StyleAttrs style_attrs; - dw::core::style::Style *style, *old_style; int32_t bgcolor = -1; bool new_style = false; + CssPropertyList props, *table_cell_props; -#ifdef USE_TABLES switch (S_TOP(html)->table_mode) { case DILLO_HTML_TABLE_MODE_NONE: _MSG("Invalid HTML syntax: <tr> outside <table>\n"); @@ -148,44 +149,37 @@ void Html_tag_open_tr(DilloHtml *html, const char *tag, int tagsize) 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 = a_Html_get_attr(html, tag, tagsize, "bgcolor"))) { + if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "bgcolor"))) { bgcolor = a_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::createShaded (HT2LT(html), bgcolor); - style = Style::create (HT2LT(html), &style_attrs); - S_TOP(html)->current_bg_color = bgcolor; - } + if (bgcolor != -1) + props.set (CSS_PROPERTY_BACKGROUND_COLOR, CSS_TYPE_COLOR, bgcolor); } - ((dw::Table*)S_TOP(html)->table)->addRow (style); - if (style) - style->unref (); - if (a_Html_get_attr (html, tag, tagsize, "align")) { S_TOP(html)->cell_text_align_set = TRUE; - a_Html_tag_set_align_attr (html, tag, tagsize); + a_Html_tag_set_align_attr (html, &props, tag, tagsize); } - style_attrs = *S_TOP(html)->table_cell_style; + html->styleEngine->inheritBackgroundColor (); + html->styleEngine->setNonCssHints (&props); + + ((dw::Table*)S_TOP(html)->table)->addRow (html->styleEngine->style ()); + + table_cell_props = new CssPropertyList (*S_TOP(html)->table_cell_props); if (bgcolor != -1) { - style_attrs.backgroundColor =Color::createShaded(HT2LT(html),bgcolor); + table_cell_props->set (CSS_PROPERTY_BACKGROUND_COLOR, + CSS_TYPE_COLOR, bgcolor); new_style = true; } - if (a_Html_tag_set_valign_attr (html, tag, tagsize, &style_attrs)) + if (a_Html_tag_set_valign_attr (html, tag, tagsize, table_cell_props)) new_style = true; if (new_style) { - old_style = S_TOP(html)->table_cell_style; - S_TOP(html)->table_cell_style = - Style::create (HT2LT(html), &style_attrs); - old_style->unref (); + S_TOP(html)->table_cell_props->unref (); + S_TOP(html)->table_cell_props = table_cell_props; + S_TOP(html)->table_cell_props->ref (); + } else { + delete table_cell_props; } break; default: @@ -193,9 +187,6 @@ void Html_tag_open_tr(DilloHtml *html, const char *tag, int tagsize) } S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TR; -#else - DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style); -#endif } /* @@ -212,13 +203,12 @@ void Html_tag_open_td(DilloHtml *html, const char *tag, int tagsize) */ void Html_tag_open_th(DilloHtml *html, const char *tag, int tagsize) { - a_Html_set_top_font(html, NULL, 0, 1, 1); Html_tag_open_table_cell (html, tag, tagsize, dw::core::style::TEXT_ALIGN_CENTER); } /* - * Utilities + * Utilities */ /* @@ -228,12 +218,9 @@ static void Html_tag_open_table_cell(DilloHtml *html, const char *tag, int tagsize, dw::core::style::TextAlignType text_align) { -#ifdef USE_TABLES Widget *col_tb; int colspan = 1, rowspan = 1; const char *attrbuf; - dw::core::style::StyleAttrs style_attrs; - dw::core::style::Style *style, *old_style; int32_t bgcolor; bool_t new_style; @@ -258,66 +245,53 @@ static void Html_tag_open_table_cell(DilloHtml *html, if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "rowspan"))) rowspan = MAX(1, strtol (attrbuf, NULL, 10)); + CssPropertyList *props; + // \todo any shorter way to do this? + if (S_TOP(html)->table_cell_props != NULL) + props = new CssPropertyList (*S_TOP(html)->table_cell_props); + else + props = new CssPropertyList (); + /* 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 (!S_TOP(html)->cell_text_align_set) { + props->set (CSS_PROPERTY_TEXT_ALIGN, CSS_TYPE_ENUM, text_align); + } if (a_Html_get_attr(html, tag, tagsize, "nowrap")) - style_attrs.whiteSpace = WHITE_SPACE_NOWRAP; + props->set(CSS_PROPERTY_WHITE_SPACE,CSS_TYPE_ENUM,WHITE_SPACE_NOWRAP); else - style_attrs.whiteSpace = WHITE_SPACE_NORMAL; - - S_TOP(html)->style = - Style::create (HT2LT(html), &style_attrs); - old_style->unref (); - a_Html_tag_set_align_attr (html, tag, tagsize); + props->set(CSS_PROPERTY_WHITE_SPACE,CSS_TYPE_ENUM,WHITE_SPACE_NORMAL); - /* cell style */ - style_attrs = *S_TOP(html)->table_cell_style; - new_style = FALSE; + a_Html_tag_set_align_attr (html, props, tag, tagsize); if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "width"))) { - style_attrs.width = a_Html_parse_length (html, attrbuf); - new_style = TRUE; + props->set (CSS_PROPERTY_WIDTH, CSS_TYPE_LENGTH_PERCENTAGE, + a_Html_parse_length (html, attrbuf)); } - if (a_Html_tag_set_valign_attr (html, tag, tagsize, &style_attrs)) + if (a_Html_tag_set_valign_attr (html, tag, tagsize, props)) new_style = TRUE; - if (!prefs.force_my_colors && - (attrbuf = a_Html_get_attr(html, tag, tagsize, "bgcolor"))) { + if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "bgcolor"))) { bgcolor = a_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::createShaded (HT2LT(html), bgcolor); - S_TOP(html)->current_bg_color = bgcolor; - } + if (bgcolor != -1) + props->set (CSS_PROPERTY_BACKGROUND_COLOR, CSS_TYPE_COLOR,bgcolor); } - if (S_TOP(html)->style->textAlign + html->styleEngine->setNonCssHints (props); + delete props; + + if (html->styleEngine->style ()->textAlign == TEXT_ALIGN_STRING) - col_tb = new dw::TableCell (((dw::Table*)S_TOP(html)->table)->getCellRef (), - prefs.limit_text_width); + col_tb = new dw::TableCell ( + ((dw::Table*)S_TOP(html)->table)->getCellRef (), + prefs.limit_text_width); else col_tb = new Textblock (prefs.limit_text_width); - if (new_style) { - style = dw::core::style::Style::create (HT2LT(html), &style_attrs); - col_tb->setStyle (style); - style->unref (); - } else - col_tb->setStyle (S_TOP(html)->table_cell_style); + col_tb->setStyle (html->styleEngine->style ()); ((dw::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->connectSignals(col_tb); break; default: @@ -326,5 +300,4 @@ static void Html_tag_open_table_cell(DilloHtml *html, } S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TD; -#endif } diff --git a/src/table.hh b/src/table.hh index 8068c184..aca717ba 100644 --- a/src/table.hh +++ b/src/table.hh @@ -2,13 +2,13 @@ #define __TABLE_HH__ /* - * Classes + * Classes */ class DilloHtml; /* - * Table parsing functions + * Table parsing functions */ void Html_tag_open_table(DilloHtml *html, const char *tag, int tagsize); diff --git a/src/timeout.cc b/src/timeout.cc index 356134cc..80eb6425 100644 --- a/src/timeout.cc +++ b/src/timeout.cc @@ -29,7 +29,7 @@ void a_Timeout_add(float t, TimeoutCb_t cb, void *cbdata) } /* - * To be called from iside the 'cb' function when it wants to keep running + * To be called from inside the 'cb' function when it wants to keep running */ void a_Timeout_repeat(float t, TimeoutCb_t cb, void *cbdata) { @@ -11,7 +11,7 @@ // UI for Dillo -#include <stdlib.h> +#include <unistd.h> #include <stdio.h> #include <fltk/HighlightButton.h> @@ -25,9 +25,11 @@ #include <fltk/Item.h> #include <fltk/Divider.h> +#include "keys.hh" #include "ui.hh" #include "msg.h" #include "timeout.hh" +#include "utf8.hh" using namespace fltk; @@ -38,8 +40,9 @@ using namespace fltk; struct iconset { Image *ImgMeterOK, *ImgMeterBug, - *ImgHome, *ImgReload, *ImgSave, *ImgBook, *ImgClear, *ImgSearch; - MultiImage *ImgLeftMulti, *ImgRightMulti, *ImgStopMulti, *ImgImageLoadMulti; + *ImgHome, *ImgReload, *ImgSave, *ImgBook, *ImgTools, + *ImgClear,*ImgSearch, *ImgHelp; + MultiImage *ImgLeftMulti, *ImgRightMulti, *ImgStopMulti; }; static struct iconset standard_icons = { @@ -49,16 +52,16 @@ static struct iconset standard_icons = { new xpmImage(reload_xpm), new xpmImage(save_xpm), new xpmImage(bm_xpm), + new xpmImage(tools_xpm), new xpmImage(new_s_xpm), new xpmImage(search_xpm), + new xpmImage(help_xpm), new MultiImage(*new xpmImage(left_xpm), INACTIVE_R, *new xpmImage(left_i_xpm)), new MultiImage(*new xpmImage(right_xpm), INACTIVE_R, *new xpmImage(right_i_xpm)), new MultiImage(*new xpmImage(stop_xpm), INACTIVE_R, *new xpmImage(stop_i_xpm)), - new MultiImage(*new xpmImage(imgload_off_xpm), STATE, - *new xpmImage(imgload_on_xpm)) }; static struct iconset small_icons = { @@ -68,15 +71,16 @@ static struct iconset small_icons = { new xpmImage(reload_s_xpm), new xpmImage(save_s_xpm), new xpmImage(bm_s_xpm), + new xpmImage(tools_s_xpm), new xpmImage(new_s_xpm), standard_icons.ImgSearch, + standard_icons.ImgHelp, new MultiImage(*new xpmImage(left_s_xpm), INACTIVE_R, *new xpmImage(left_si_xpm)), new MultiImage(*new xpmImage(right_s_xpm), INACTIVE_R, *new xpmImage(right_si_xpm)), new MultiImage(*new xpmImage(stop_s_xpm), INACTIVE_R, *new xpmImage(stop_si_xpm)), - standard_icons.ImgImageLoadMulti }; @@ -99,7 +103,7 @@ public: }; /* - * Disable: UpKey, DownKey, PageUpKey, PageDownKey and + * Disable: UpKey, DownKey, PageUpKey, PageDownKey and * CTRL+{o,r,HomeKey,EndKey} */ int CustInput::handle(int e) @@ -167,11 +171,11 @@ int CustHighlightButton::handle(int e) * Used to resize the progress boxes automatically. */ class CustProgressBox : public InvisibleBox { + int padding; public: CustProgressBox(int x, int y, int w, int h, const char *l=0) : - InvisibleBox(x,y,w,h,l) {}; + InvisibleBox(x,y,w,h,l) { padding = 0; }; void update_label(const char *lbl) { - static int padding = 0; int w,h; if (!padding) { copy_label("W"); @@ -189,7 +193,7 @@ public: // Toolbar buttons ----------------------------------------------------------- // //static const char *button_names[] = { -// "Back", "Forward", "Home", "Reload", "Save", "Stop", "Bookmarks", +// "Back", "Forward", "Home", "Reload", "Save", "Stop", "Bookmarks", "Tools", // "Clear", "Search" //}; @@ -215,6 +219,26 @@ static void search_cb(Widget *wid, void *data) } /* + * Callback for the help button. + */ +static void help_cb(Widget *w, void *) +{ + char *path = dStrconcat(DILLO_DOCDIR, "user_help.html", NULL); + BrowserWindow *bw = a_UIcmd_get_bw_by_widget(w); + + if (access(path, R_OK) == 0) { + char *urlstr = dStrconcat("file:", path, NULL); + a_UIcmd_open_urlstr(bw, urlstr); + dFree(urlstr); + } else { + MSG("Can't read local help file at \"%s\"." + " Getting remote help...\n", path); + a_UIcmd_open_urlstr(bw, "http://www.dillo.org/dillo2-help.html"); + } + dFree(path); +} + +/* * Callback for the File menu button. */ static void filemenu_cb(Widget *wid, void *) @@ -324,6 +348,11 @@ static void b1_cb(Widget *wid, void *cb_data) a_UIcmd_book(a_UIcmd_get_bw_by_widget(wid)); } break; + case UI_TOOLS: + if (k == 1 || k == 3) { + a_UIcmd_tools(a_UIcmd_get_bw_by_widget(wid), wid); + } + break; default: break; } @@ -370,50 +399,58 @@ PackedGroup *UI::make_toolbar(int tw, int th) p1->begin(); Back = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Back" : 0); b->image(icons->ImgLeftMulti); - b->tooltip("Previous page"); b->callback(b1_cb, (void *)UI_BACK); b->clear_tab_to_focus(); HighlightButton::default_style->highlight_color(CuteColor); Forw = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Forw" : 0); b->image(icons->ImgRightMulti); - b->tooltip("Next page"); b->callback(b1_cb, (void *)UI_FORW); b->clear_tab_to_focus(); - + Home = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Home" : 0); b->image(icons->ImgHome); - b->tooltip("Go to the Home page"); b->callback(b1_cb, (void *)UI_HOME); b->clear_tab_to_focus(); Reload = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Reload" : 0); b->image(icons->ImgReload); - b->tooltip("Reload"); b->callback(b1_cb, (void *)UI_RELOAD); b->clear_tab_to_focus(); - + Save = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Save" : 0); b->image(icons->ImgSave); - b->tooltip("Save this page"); b->callback(b1_cb, (void *)UI_SAVE); b->clear_tab_to_focus(); - + Stop = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Stop" : 0); b->image(icons->ImgStopMulti); - b->tooltip("Stop loading"); b->callback(b1_cb, (void *)UI_STOP); b->clear_tab_to_focus(); Bookmarks = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Book" : 0); b->image(icons->ImgBook); - b->tooltip("View bookmarks"); b->callback(b1_cb, (void *)UI_BOOK); b->clear_tab_to_focus(); + Tools = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Tools" : 0); + b->image(icons->ImgTools); + b->callback(b1_cb, (void *)UI_TOOLS); + b->clear_tab_to_focus(); + p1->type(PackedGroup::ALL_CHILDREN_VERTICAL); p1->end(); + if (prefs.show_tooltip) { + Back->tooltip("Previous page"); + Forw->tooltip("Next page"); + Home->tooltip("Go to the Home page"); + Reload->tooltip("Reload"); + Save->tooltip("Save this page"); + Stop->tooltip("Stop loading"); + Bookmarks->tooltip("View bookmarks"); + Tools->tooltip("Settings"); + } return p1; } @@ -427,12 +464,10 @@ PackedGroup *UI::make_location() pg->begin(); Clear = b = new CustHighlightButton(2,2,16,22,0); b->image(icons->ImgClear); - b->tooltip("Clear the URL box.\nMiddle-click to paste a URL."); b->callback(clear_cb, this); b->clear_tab_to_focus(); Input *i = Location = new CustInput(0,0,0,0,0); - i->tooltip("Location"); i->color(CuteColor); i->when(WHEN_ENTER_KEY); i->callback(location_cb, this); @@ -440,14 +475,24 @@ PackedGroup *UI::make_location() Search = b = new HighlightButton(0,0,16,22,0); b->image(icons->ImgSearch); - b->tooltip("Search the Web"); b->callback(search_cb, this); b->clear_tab_to_focus(); + Help = b = new HighlightButton(0,0,16,22,0); + b->image(icons->ImgHelp); + b->callback(help_cb, this); + b->clear_tab_to_focus(); + pg->type(PackedGroup::ALL_CHILDREN_VERTICAL); pg->resizable(i); pg->end(); + if (prefs.show_tooltip) { + Clear->tooltip("Clear the URL box.\nMiddle-click to paste a URL."); + Location->tooltip("Location"); + Search->tooltip("Search the Web"); + Help->tooltip("Help"); + } return pg; } @@ -494,7 +539,8 @@ Widget *UI::make_filemenu_button() _MSG("UI::make_filemenu_button w=%d h=%d padding=%d\n", w, h, padding); btn->box(PanelSize == P_large ? FLAT_BOX : THIN_UP_BOX); btn->callback(filemenu_cb, this); - btn->tooltip("File menu"); + if (prefs.show_tooltip) + btn->tooltip("File menu"); btn->clear_tab_to_focus(); if (!prefs.show_filemenu && PanelSize != P_large) btn->hide(); @@ -520,7 +566,7 @@ Group *UI::make_panel(int ww) icons = &small_icons; else icons = &standard_icons; - + if (PanelSize == P_tiny) { if (Small_Icons) xpos = 0, bw = 22, bh = 22, fh = 0, lh = 22, lbl = 0; @@ -584,7 +630,7 @@ Group *UI::make_panel(int ww) g2->resizable(pg); g2->end(); - + // Toolbar g3 = new Group(0,fh+lh,ww,bh); g3->begin(); @@ -600,12 +646,12 @@ Group *UI::make_panel(int ww) 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(); } @@ -614,16 +660,45 @@ Group *UI::make_panel(int ww) } /* + * Create the status panel + */ +Group *UI::make_status_panel(int ww) +{ + const int s_h = 20, bm_w = 16; + Group *g = new Group(0, 0, ww, s_h, 0); + + // Status box + Status = new Output(0, 0, ww-bm_w, s_h, 0); + Status->value(""); + Status->box(THIN_DOWN_BOX); + Status->clear_click_to_focus(); + Status->clear_tab_to_focus(); + Status->color(GRAY80); + g->add(Status); + //Status->throw_focus(); + + // Bug Meter + BugMeter = new HighlightButton(ww-bm_w,0,bm_w,s_h,0); + BugMeter->image(icons->ImgMeterOK); + BugMeter->box(THIN_DOWN_BOX); + BugMeter->align(ALIGN_INSIDE|ALIGN_CLIP|ALIGN_LEFT); + if (prefs.show_tooltip) + BugMeter->tooltip("Show HTML bugs\n(right-click for menu)"); + BugMeter->callback(bugmeter_cb, this); + BugMeter->clear_tab_to_focus(); + g->add(BugMeter); + + g->resizable(Status); + return g; +} + +/* * User Interface constructor - */ + */ UI::UI(int x, int y, int ww, int wh, const char* label, const UI *cur_ui) : Group(x, y, ww, wh, label) { - int s_h = 20; - - Font *f = font(prefs.vw_fontname, 0); - if (f) - this->labelfont(f); + PointerOnLink = FALSE; Tabs = NULL; TabTooltip = NULL; @@ -631,12 +706,16 @@ UI::UI(int x, int y, int ww, int wh, const char* label, const UI *cur_ui) : add(TopGroup); resizable(TopGroup); set_flag(RAW_LABEL); - + if (cur_ui) { PanelSize = cur_ui->PanelSize; CuteColor = cur_ui->CuteColor; Small_Icons = cur_ui->Small_Icons; - Panelmode = cur_ui->Panelmode; + if (cur_ui->Panelmode == UI_HIDDEN || + cur_ui->Panelmode == UI_TEMPORARILY_SHOW_PANELS) + Panelmode = UI_HIDDEN; + else + Panelmode = UI_NORMAL; } else { // Set some default values //PanelSize = P_tiny, CuteColor = 26, Small_Icons = 0; @@ -646,12 +725,10 @@ UI::UI(int x, int y, int ww, int wh, const char* label, const UI *cur_ui) : Panelmode = (UIPanelmode) prefs.fullwindow_start; } - // Control panel Panel = make_panel(ww); TopGroup->add(Panel); - // Render area Main = new Widget(0,0,1,1,"Welcome..."); Main->box(FLAT_BOX); @@ -666,47 +743,11 @@ UI::UI(int x, int y, int ww, int wh, const char* label, const UI *cur_ui) : // Find text bar findbar = new Findbar(ww, 28); - TopGroup->add(findbar); + TopGroup->add(findbar); // Status Panel - StatusPanel = new Group(0, 0, ww, s_h, 0); - // Status box - int il_w = 16; - int bm_w = 16; - Status = new Output(0, 0, ww-bm_w-il_w, s_h, 0); - Status->value(""); - Status->box(THIN_DOWN_BOX); - Status->clear_click_to_focus(); - Status->clear_tab_to_focus(); - Status->color(GRAY80); - StatusPanel->add(Status); - //Status->throw_focus(); - - // Image loading indicator - ImageLoad = new HighlightButton(ww-il_w-bm_w,0,il_w,s_h,0); - ImageLoad->type(Button::TOGGLE); - ImageLoad->state(prefs.load_images); - ImageLoad->image(icons->ImgImageLoadMulti); - - ImageLoad->box(THIN_DOWN_BOX); - ImageLoad->align(ALIGN_INSIDE|ALIGN_CLIP|ALIGN_LEFT); - ImageLoad->tooltip("Toggle image loading"); - ImageLoad->clear_tab_to_focus(); - StatusPanel->add(ImageLoad); - - // Bug Meter - BugMeter = new HighlightButton(ww-bm_w,0,bm_w,s_h,0); - BugMeter->image(icons->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, this); - BugMeter->clear_tab_to_focus(); - StatusPanel->add(BugMeter); - - StatusPanel->resizable(Status); - - TopGroup->add(StatusPanel); + StatusPanel = make_status_panel(ww); + TopGroup->add(StatusPanel); // Make the full screen button (to be attached to the viewport later) // TODO: attach to the viewport @@ -721,8 +762,6 @@ UI::UI(int x, int y, int ww, int wh, const char* label, const UI *cur_ui) : Panel->hide(); StatusPanel->hide(); } - - //show(); } /* @@ -742,69 +781,102 @@ int UI::handle(int event) _MSG("UI::handle event=%d (%d,%d)\n", event, event_x(), event_y()); _MSG("Panel->h()=%d Main->h()=%d\n", Panel->h() , Main->h()); - int ret = 0, k = event_key(); - - // We're only interested in some flags - unsigned modifier = event_state() & (SHIFT | CTRL | ALT); + int ret = 0; if (event == KEY) { return 0; // Receive as shortcut - } else if (event == SHORTCUT) { - // Handle keyboard shortcuts here. - if (modifier == CTRL) { - if (k == 'b') { - a_UIcmd_book(a_UIcmd_get_bw_by_widget(this)); - ret = 1; - } else if (k == 'f') { - set_findbar_visibility(1); - ret = 1; - } else if (k == 'l') { - focus_location(); - ret = 1; - } else if (k == 'n') { - a_UIcmd_browser_window_new(w(),h(),a_UIcmd_get_bw_by_widget(this)); - ret = 1; - } else if (k == 'o') { - a_UIcmd_open_file(a_UIcmd_get_bw_by_widget(this)); - ret = 1; - } else if (k == 'q') { - a_UIcmd_close_bw(a_UIcmd_get_bw_by_widget(this)); - ret = 1; - } else if (k == 'r') { - a_UIcmd_reload(a_UIcmd_get_bw_by_widget(this)); - ret = 1; - } else if (k == 's') { - a_UIcmd_search_dialog(a_UIcmd_get_bw_by_widget(this)); - ret = 1; - } else if (k == 't') { - a_UIcmd_open_url_nt(a_UIcmd_get_bw_by_widget(this), NULL, 1); - ret = 1; - } else if (k == ' ') { - panelmode_cb_i(); - ret = 1; - } - } else if (modifier == ALT) { - if (k == 'f') { - a_UIcmd_file_popup(a_UIcmd_get_bw_by_widget(this), FileButton); - } else if (k == 'q' && event_key_state(LeftAltKey)) { - a_Timeout_add(0.0, a_UIcmd_close_all_bw, NULL); + KeysCommand_t cmd = Keys::getKeyCmd(); + if (cmd == KEYS_NOP) { + // Do nothing + } else if (cmd == KEYS_SCREEN_UP || cmd == KEYS_SCREEN_DOWN || + cmd == KEYS_LINE_UP || cmd == KEYS_LINE_DOWN || + cmd == KEYS_LEFT || cmd == KEYS_RIGHT || + cmd == KEYS_TOP || cmd == KEYS_BOTTOM) { + a_UIcmd_scroll(a_UIcmd_get_bw_by_widget(this), cmd); + ret = 1; + } else if (cmd == KEYS_BACK) { + a_UIcmd_back(a_UIcmd_get_bw_by_widget(this)); + ret = 1; + } else if (cmd == KEYS_FORWARD) { + a_UIcmd_forw(a_UIcmd_get_bw_by_widget(this)); + ret = 1; + } else if (cmd == KEYS_BOOKMARKS) { + a_UIcmd_book(a_UIcmd_get_bw_by_widget(this)); + ret = 1; + } else if (cmd == KEYS_FIND) { + set_findbar_visibility(1); + ret = 1; + } else if (cmd == KEYS_WEBSEARCH) { + a_UIcmd_search_dialog(a_UIcmd_get_bw_by_widget(this)); + ret = 1; + } else if (cmd == KEYS_GOTO) { + focus_location(); + ret = 1; + } else if (cmd == KEYS_NEW_TAB) { + a_UIcmd_open_url_nt(a_UIcmd_get_bw_by_widget(this), NULL, 1); + ret = 1; + } else if (cmd == KEYS_CLOSE_TAB) { + a_UIcmd_close_bw(a_UIcmd_get_bw_by_widget(this)); + ret = 1; + } else if (cmd == KEYS_HIDE_PANELS && + get_panelmode() == UI_TEMPORARILY_SHOW_PANELS) { + set_panelmode(UI_HIDDEN); + ret = 1; + } else if (cmd == KEYS_NEW_WINDOW) { + a_UIcmd_browser_window_new(w(),h(),0,a_UIcmd_get_bw_by_widget(this)); + ret = 1; + } else if (cmd == KEYS_OPEN) { + a_UIcmd_open_file(a_UIcmd_get_bw_by_widget(this)); + ret = 1; + } else if (cmd == KEYS_HOME) { + a_UIcmd_home(a_UIcmd_get_bw_by_widget(this)); + ret = 1; + } else if (cmd == KEYS_RELOAD) { + a_UIcmd_reload(a_UIcmd_get_bw_by_widget(this)); + ret = 1; + } else if (cmd == KEYS_STOP) { + a_UIcmd_stop(a_UIcmd_get_bw_by_widget(this)); + ret = 1; + } else if (cmd == KEYS_SAVE) { + a_UIcmd_save(a_UIcmd_get_bw_by_widget(this)); + ret = 1; + } else if (cmd == KEYS_FULLSCREEN) { + panelmode_cb_i(); + ret = 1; + } else if (cmd == KEYS_FILE_MENU) { + a_UIcmd_file_popup(a_UIcmd_get_bw_by_widget(this), FileButton); + ret = 1; + } else if (cmd == KEYS_CLOSE_ALL) { + a_Timeout_add(0.0, a_UIcmd_close_all_bw, NULL); + ret = 1; + } + } else if (event == PUSH) { + if (prefs.middle_click_drags_page == 0 && + event_button() == MiddleButton && + !a_UIcmd_pointer_on_link(a_UIcmd_get_bw_by_widget(this))) { + if (Main->Rectangle::contains (event_x (), event_y ())) { + /* Offer the event to Main's children (form widgets) */ + int save_x = e_x, save_y = e_y; + + e_x -= Main->x(); + e_y -= Main->y(); + ret = ((Group *)Main)->Group::handle(event); + e_x = save_x; + e_y = save_y; } - } else { - // Back and Forward navigation shortcuts - if (modifier == 0 && (k == BackSpaceKey || k == ',')) { - a_UIcmd_back(a_UIcmd_get_bw_by_widget(this)); - ret = 1; - } else if ((modifier == 0 && k == '.') || - (modifier == SHIFT && k == BackSpaceKey)) { - a_UIcmd_forw(a_UIcmd_get_bw_by_widget(this)); + if (!ret) { + /* middle click was not on a link or a form widget */ + paste_url(); ret = 1; } } } - if (!ret) + if (!ret) { ret = Group::handle(event); + } + return ret; } @@ -898,7 +970,7 @@ void UI::set_img_prog(int n_img, int t_img, int cmd) } else { IProg->activate(); if (cmd == 1) { - snprintf(str, 32, "%s%d of %d", + snprintf(str, 32, "%s%d of %d", (PanelSize == 0) ? "" : "Images\n", n_img, t_img); } else if (cmd == 2) { str[0] = '\0'; @@ -926,9 +998,7 @@ void UI::set_bug_prog(int n_bug) BugMeter->redraw_label(); new_w = strlen(str)*8 + 20; } - Status->resize(0,0,StatusPanel->w()-ImageLoad->w()-new_w,Status->h()); - ImageLoad->resize(StatusPanel->w()-ImageLoad->w()-new_w, 0, ImageLoad->w(), - ImageLoad->h()); + Status->resize(0,0,StatusPanel->w()-new_w,Status->h()); BugMeter->resize(StatusPanel->w()-new_w, 0, new_w, BugMeter->h()); StatusPanel->init_sizes(); } @@ -954,12 +1024,16 @@ void UI::customize(int flags) Stop->hide(); if ( !prefs.show_bookmarks ) Bookmarks->hide(); + if ( !prefs.show_tools ) + Tools->hide(); if ( !prefs.show_clear_url ) Clear->hide(); if ( !prefs.show_url ) Location->hide(); if ( !prefs.show_search ) Search->hide(); + if ( !prefs.show_help ) + Help->hide(); if ( !prefs.show_progress_box ) ProgBox->hide(); } @@ -987,7 +1061,8 @@ void UI::panel_cb_i() */ void UI::color_change_cb_i() { - static int ncolor = 0, cols[] = {7,17,26,51,140,156,205,206,215,-1}; + const int cols[] = {7,17,26,51,140,156,205,206,215,-1}; + static int ncolor = 0; ncolor = (cols[ncolor+1] < 0) ? 0 : ncolor + 1; CuteColor = cols[ncolor]; @@ -1048,32 +1123,28 @@ void UI::set_render_layout(Widget &nw) } /* - * Set the window title + * Set the tab title */ -void UI::set_page_title(const char *label) +void UI::set_tab_title(const char *label) { char title[128]; dReturn_if_fail(label != NULL); - snprintf(title, 128, "Dillo: %s", label); - this->window()->copy_label(title); - this->window()->redraw_label(); + if (*label) { + // Make a label for this tab + size_t tab_chars = 18, label_len = strlen(label); - if (tabs() && *label) { - size_t tab_chars = 18; + if (label_len > tab_chars) + tab_chars = a_Utf8_end_of_char(label, tab_chars - 1) + 1; snprintf(title, tab_chars + 1, "%s", label); - if (strlen(label) > tab_chars) { - while (label[tab_chars] & 0x80 && !(label[tab_chars] & 0x40) && - tab_chars < 23) { - // In the middle of a multibyte UTF-8 character. - title[tab_chars] = label[tab_chars]; - tab_chars++; - } + if (label_len > tab_chars) snprintf(title + tab_chars, 4, "..."); + // Avoid unnecessary redraws + if (strcmp(this->label(), title)) { + this->copy_label(title); + this->redraw_label(); } - this->copy_label(title); - this->redraw_label(); // Disabled because of a bug in fltk::Tabgroup //dFree(TabTooltip); @@ -16,8 +16,6 @@ #include "findbar.hh" -using namespace fltk; - typedef enum { UI_BACK = 0, UI_FORW, @@ -26,6 +24,7 @@ typedef enum { UI_SAVE, UI_STOP, UI_BOOK, + UI_TOOLS, UI_CLEAR, UI_SEARCH } UIButton; @@ -47,15 +46,15 @@ class UI : public fltk::Group { CustTabGroup *Tabs; char *TabTooltip; - Group *TopGroup; - Button *Back, *Forw, *Home, *Reload, *Save, *Stop, *Bookmarks, - *Clear, *Search, *FullScreen, *ImageLoad, *BugMeter, *FileButton; - Input *Location; - PackedGroup *ProgBox; + fltk::Group *TopGroup; + fltk::Button *Back, *Forw, *Home, *Reload, *Save, *Stop, *Bookmarks, *Tools, + *Clear, *Search, *Help, *FullScreen, *BugMeter, *FileButton; + fltk::Input *Location; + fltk::PackedGroup *ProgBox; CustProgressBox *PProg, *IProg; - Group *Panel, *StatusPanel; - Widget *Main; - Output *Status; + fltk::Group *Panel, *StatusPanel; + fltk::Widget *Main; + fltk::Output *Status; int MainIdx; // Panel customization variables @@ -64,13 +63,15 @@ class UI : public fltk::Group { UIPanelmode Panelmode; Findbar *findbar; + int PointerOnLink; - PackedGroup *make_toolbar(int tw, int th); - PackedGroup *make_location(); - PackedGroup *make_progress_bars(int wide, int thin_up); + fltk::PackedGroup *make_toolbar(int tw, int th); + fltk::PackedGroup *make_location(); + fltk::PackedGroup *make_progress_bars(int wide, int thin_up); void make_menubar(int x, int y, int w, int h); - Widget *make_filemenu_button(); - Group *make_panel(int ww); + fltk::Widget *make_filemenu_button(); + fltk::Group *make_panel(int ww); + fltk::Group *make_status_panel(int ww); public: @@ -89,20 +90,20 @@ public: 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 set_tab_title(const char *label); void customize(int flags); void button_set_sens(UIButton btn, int sens); void paste_url(); void set_panelmode(UIPanelmode mode); UIPanelmode get_panelmode(); void set_findbar_visibility(bool visible); - bool images_enabled() { return ImageLoad->state();} - void images_enabled(int flag) { ImageLoad->state(flag);} Widget *fullscreen_button() { return FullScreen; } void fullscreen_toggle() { FullScreen->do_callback(); } CustTabGroup *tabs() { return Tabs; } void tabs(CustTabGroup *tabs) { Tabs = tabs; } + int pointerOnLink() { return PointerOnLink; } + void pointerOnLink(int flag) { PointerOnLink = flag; } // Hooks to method callbacks void panel_cb_i(); diff --git a/src/uicmd.cc b/src/uicmd.cc index 564e9957..93836735 100644 --- a/src/uicmd.cc +++ b/src/uicmd.cc @@ -15,15 +15,22 @@ #include <stdio.h> #include <stdarg.h> #include <math.h> /* for rint */ + +#include <fltk/draw.h> +#include <fltk/damage.h> #include <fltk/Widget.h> #include <fltk/TabGroup.h> +#include <fltk/Tooltip.h> -#include "dir.h" +#include "paths.hh" +#include "keys.hh" #include "ui.hh" #include "uicmd.hh" #include "timeout.hh" +#include "utf8.hh" #include "menu.hh" #include "dialog.hh" +#include "xembed.hh" #include "bookmark.h" #include "history.h" #include "msg.h" @@ -33,6 +40,8 @@ #include "nav.h" +#define DEFAULT_TAB_LABEL "Dillo" + // Handy macro #define BW2UI(bw) ((UI*)((bw)->ui)) @@ -49,20 +58,198 @@ static char *save_dir = NULL; using namespace fltk; -// -// For custom handling of keyboard -// + +//---------------------------------------------------------------------------- +#define BTN_W 25 +#define BTN_H 20 + +static int btn_x; + +/* + * Adds a tab-close button at the rightmost part + */ +class CustShrinkTabPager : public TabGroupPager { + bool btn_hl; + TabGroup *tg; +public: + int update_positions( + TabGroup *g, int numchildren, int &selected, + int &cumulated_width, int &available_width, + int *tab_pos, int *tab_width); + virtual int which(TabGroup* g, int m_x,int m_y); + virtual TabGroupPager* clone() const; + virtual const char * mode_name() const {return "Shrink";} + virtual int id() const {return PAGER_SHRINK;} + virtual int available_width(TabGroup *g) const; + virtual bool draw_tabs(TabGroup* g, int selected, int* tab_pos, + int* tab_width) { + if (!tg) tg = g; + if (g->children() > 1) { + fltk::Rectangle r(btn_x,0,BTN_W,BTN_H); + setcolor(btn_hl ? 206 : GRAY75); + fillrect(r); + if (btn_hl) { + setcolor(WHITE); + strokerect(r); + } + setcolor(GRAY10); + //fltk::setfont(fltk::getfont()->bold(), fltk::getsize()); + r.h(r.h()-2); + drawtext("X", r, ALIGN_CENTER); + return false; + } else { + // WORKAROUND: for http://fltk.org/str.php?L2062 + // By returning true we avoid a call to TabGroup::draw_tab() + // in TabGroup::draw() in case we don't show the tabs. + return true; + } + } + + void btn_highlight(bool flag) { + if (btn_hl != flag) { + btn_hl = flag; + if (tg) + tg->redraw(DAMAGE_VALUE); + } + }; + bool btn_highlight() { return btn_hl; }; + + CustShrinkTabPager() : TabGroupPager() { + noclip(true); + btn_hl = false; + tg = NULL; + } +}; + +int CustShrinkTabPager::available_width(TabGroup *g) const +{ + _MSG("CustShrinkTabPager::available_width\n"); + int w = MAX (g->w() - this->slope()-1 - BTN_W, 0); + btn_x = w + 6; + return w; +} + +int CustShrinkTabPager::which(TabGroup* g, int event_x,int event_y) +{ + int H = g->tab_height(); + if (!H) return -1; + if (H < 0) { + if (event_y > g->h() || event_y < g->h()+H) return -1; + } else { + if (event_y > H || event_y < 0) return -1; + } + if (event_x < 0) return -1; + int p[128], w[128]; + int selected = g->tab_positions(p, w); + int d = (event_y-(H>=0?0:g->h()))*slope()/H; + for (int i=0; i<g->children(); i++) { + if (event_x < p[i+1]+(i<selected ? slope() - d : d)) return i; + } + return -1; +} + +/* + * Prevents tabs from going over the close-tab button. + * Modified from fltk-2.0.x-r6525. + */ +int CustShrinkTabPager::update_positions( + TabGroup *g, int numchildren, int &selected, + int &cumulated_width, int &available_width, + int *tab_pos, int *tab_width) +{ + available_width-=BTN_W; + + // uh oh, they are too big, we must move them: + // special case when the selected tab itself is too big, make it fill + // cumulated_width: + int i; + + if (tab_width[selected] >= available_width) { + tab_width[selected] = available_width; + for (i = 0; i <= selected; i++) + tab_pos[i] = 0; + for (i = selected + 1; i <= numchildren; i++) + tab_pos[i] = available_width; + return selected; + } + + int w2[128]; + + for (i = 0; i < numchildren; i++) + w2[i] = tab_width[i]; + i = numchildren - 1; + int j = 0; + + int minsize = 5; + + bool right = true; + + while (cumulated_width > available_width) { + int n; // which one to shrink + + if (j < selected && (!right || i <= selected)) { // shrink a left one + n = j++; + right = true; + } else if (i > selected) { // shrink a right one + n = i--; + right = false; + } else { // no more space, start making them zero + minsize = 0; + i = numchildren - 1; + j = 0; + right = true; + continue; + } + cumulated_width -= w2[n] - minsize; + w2[n] = minsize; + if (cumulated_width < available_width) { + w2[n] = available_width - cumulated_width + minsize; + cumulated_width = available_width; + break; + } + } + // re-sum the positions: + cumulated_width = 0; + for (i = 0; i < numchildren; i++) { + cumulated_width += w2[i]; + tab_pos[i+1] = cumulated_width; + } + return selected; +} + +TabGroupPager* CustShrinkTabPager::clone() const { + return new CustShrinkTabPager(*this); +} + +//---------------------------------------------------------------------------- + +/* + * For custom handling of keyboard + */ class CustTabGroup : public fltk::TabGroup { + Tooltip *toolTip; + bool tooltipEnabled; + bool buttonPushed; public: CustTabGroup (int x, int y, int ww, int wh, const char *lbl=0) : - TabGroup(x,y,ww,wh,lbl) {}; + TabGroup(x,y,ww,wh,lbl) { + // The parameter pager is cloned, so free it. + CustShrinkTabPager *cp = new CustShrinkTabPager(); + this->pager(cp); + delete cp; + toolTip = new Tooltip; + tooltipEnabled = false; + buttonPushed = false; + }; + ~CustTabGroup() { delete toolTip; } int handle(int e) { // Don't focus with arrow keys _MSG("CustTabGroup::handle %d\n", e); - int k = event_key(); - // We're only interested in some flags - unsigned modifier = event_state() & (SHIFT | CTRL | ALT); + fltk::Rectangle r(btn_x,0,BTN_W,BTN_H); if (e == KEY) { + int k = event_key(); + // We're only interested in some flags + unsigned modifier = event_state() & (SHIFT | CTRL | ALT); if (k == UpKey || k == DownKey || k == TabKey) { return 0; } else if (k == LeftKey || k == RightKey) { @@ -74,20 +261,77 @@ public: return 1; } // Avoid focus change. - return 0; + return 0; + } + } else if (e == FOCUS_CHANGE) { + // Update the window title + BrowserWindow *bw = a_UIcmd_get_bw_by_widget(selected_child()); + const char *title = a_History_get_title(NAV_TOP_UIDX(bw), 1); + a_UIcmd_set_page_title(bw, title ? title : ""); + } else if (e == MOVE) { + CustShrinkTabPager *cstp = (CustShrinkTabPager *) pager(); + if (event_inside(r) && children() > 1) { + /* We're inside the button area */ + cstp->btn_highlight(true); + if (prefs.show_tooltip) { + /* Prepare the tooltip for pop-up */ + tooltipEnabled = true; + /* We use parent() if available because we are returning 0. + * Returning without having TabGroup processing makes the + * popup event never reach 'this', but it reaches parent() */ + toolTip->enter(parent() ?parent():this, r, "Close current Tab"); + } + return 0; // Change focus + } else { + cstp->btn_highlight(false); + + if (prefs.show_tooltip) { + /* Hide the tooltip or enable it again.*/ + if (tooltipEnabled) { + tooltipEnabled = false; + toolTip->exit(); + } else { + toolTip->enable(); + } + } + } + } else if (e == PUSH && event_inside(r) && + event_button() == 1 && children() > 1) { + buttonPushed = true; + return 1; /* non-zero */ + } else if (e == RELEASE) { + if (event_inside(r) && event_button() == 1 && + children() > 1 && buttonPushed) { + a_UIcmd_close_bw(a_UIcmd_get_bw_by_widget(selected_child())); + } else { + CustShrinkTabPager *cstp = (CustShrinkTabPager *) pager(); + cstp->btn_highlight(false); } + buttonPushed = false; + } else if (e == DRAG) { + /* Ignore this event */ + return 1; } - return TabGroup::handle(e); + int ret = TabGroup::handle(e); + + if (e == PUSH) { + /* WORKAROUND: FLTK raises the window on unhandled clicks, + * which we do not want. + */ + ret = 1; + } + return ret; } void remove (Widget *w) { TabGroup::remove (w); /* fixup resizable in case we just removed it */ - if (resizable () == w) + if (resizable () == w) { if (children () > 0) resizable (child (children () - 1)); else resizable (NULL); + } if (children () < 2) hideLabels (); @@ -110,11 +354,12 @@ public: } }; +//---------------------------------------------------------------------------- + static void win_cb (fltk::Widget *w, void *cb_data) { - CustTabGroup *tabs; int choice = 0; + CustTabGroup *tabs = (CustTabGroup*) cb_data; - tabs = BW2UI((BrowserWindow*) cb_data)->tabs(); if (tabs->children () > 1) choice = a_Dialog_choice3 ("Window contains more than one tab.", "Close all tabs", "Cancel", NULL); @@ -155,10 +400,12 @@ void a_UIcmd_send_event_to_tabs_by_wid(int e, void *v_wid) * Create a new UI and its associated BrowserWindow data structure. * Use style from v_ui. If non-NULL it must be of type UI*. */ -BrowserWindow *a_UIcmd_browser_window_new(int ww, int wh, const void *vbw) +BrowserWindow *a_UIcmd_browser_window_new(int ww, int wh, + uint32_t xid, const void *vbw) { BrowserWindow *old_bw = (BrowserWindow*)vbw; BrowserWindow *new_bw = NULL; + Window *win; if (ww <= 0 || wh <= 0) { // Set default geometry from dillorc. @@ -166,7 +413,11 @@ BrowserWindow *a_UIcmd_browser_window_new(int ww, int wh, const void *vbw) wh = prefs.height; } - Window *win = new Window(ww, wh); + if (xid) + win = new Xembed(xid, ww, wh); + else + win = new Window(ww, wh); + win->shortcut(0); // Ignore Escape if (prefs.buffered_drawing != 2) win->clear_double_buffer(); @@ -177,7 +428,8 @@ BrowserWindow *a_UIcmd_browser_window_new(int ww, int wh, const void *vbw) win->add(DilloTabs); // Create and set the UI - UI *new_ui = new UI(0, 0, ww, wh, "Label", old_bw ? BW2UI(old_bw) : NULL); + UI *new_ui = new UI(0, 0, ww, wh, DEFAULT_TAB_LABEL, + old_bw ? BW2UI(old_bw) : NULL); new_ui->set_status("http://www.dillo.org/"); new_ui->tabs(DilloTabs); @@ -203,7 +455,7 @@ BrowserWindow *a_UIcmd_browser_window_new(int ww, int wh, const void *vbw) viewport->setBufferedDrawing (true); else viewport->setBufferedDrawing (false); - + layout->attachView (viewport); new_ui->set_render_layout(*viewport); @@ -217,7 +469,9 @@ BrowserWindow *a_UIcmd_browser_window_new(int ww, int wh, const void *vbw) // Copy the layout pointer into the bw data new_bw->render_layout = (void*)layout; - win->callback(win_cb, new_bw); + win->callback(win_cb, DilloTabs); + + new_ui->focus_location(); return new_bw; } @@ -226,7 +480,7 @@ BrowserWindow *a_UIcmd_browser_window_new(int ww, int wh, const void *vbw) * Create a new Tab. * i.e the new UI and its associated BrowserWindow data structure. */ -BrowserWindow *UIcmd_tab_new(const void *vbw) +static BrowserWindow *UIcmd_tab_new(const void *vbw) { _MSG(" UIcmd_tab_new vbw=%p\n", vbw); @@ -239,10 +493,10 @@ BrowserWindow *UIcmd_tab_new(const void *vbw) // WORKAROUND: limit the number of tabs because of a fltk bug if (ui->tabs()->children() >= 127) return a_UIcmd_browser_window_new(ui->window()->w(), ui->window()->h(), - vbw); + 0, vbw); // Create and set the UI - UI *new_ui = new UI(0, 0, ui->w(), ui->h(), "Label", ui); + UI *new_ui = new UI(0, 0, ui->w(), ui->h(), DEFAULT_TAB_LABEL, ui); new_ui->tabs(ui->tabs()); new_ui->tabs()->add(new_ui); @@ -255,7 +509,7 @@ BrowserWindow *UIcmd_tab_new(const void *vbw) Layout *layout = new Layout (platform); FltkViewport *viewport = new FltkViewport (0, 0, 1, 1); - + layout->attachView (viewport); new_ui->set_render_layout(*viewport); @@ -282,10 +536,11 @@ void a_UIcmd_close_bw(void *vbw) Layout *layout = (Layout*)bw->render_layout; MSG("a_UIcmd_close_bw\n"); - a_Bw_stop_clients(bw, BW_Root + BW_Img + Bw_Force); + a_Bw_stop_clients(bw, BW_Root + BW_Img + BW_Force); delete(layout); if (ui->tabs()) { ui->tabs()->remove(ui); + ui->tabs()->value(ui->tabs()->children() - 1); if (ui->tabs()->value() != -1) ui->tabs()->selected_child()->take_focus(); else @@ -332,7 +587,7 @@ void a_UIcmd_open_urlstr(void *vbw, const char *urlstr) /* file URI */ ch = new_urlstr[5]; if (!ch || ch == '.') { - url = a_Url_new(a_Dir_get_owd(), "file:"); + url = a_Url_new(Paths::getOldWorkingDir(), "file:"); } else if (ch == '~') { url = a_Url_new(dGethomedir(), "file:"); } else { @@ -346,13 +601,10 @@ void a_UIcmd_open_urlstr(void *vbw, const char *urlstr) dFree(new_urlstr); if (url) { - a_Nav_push(bw, url); + a_UIcmd_open_url(bw, url); a_Url_free(url); } } - - /* let the rendered area have focus */ - //gtk_widget_grab_focus(GTK_BIN(bw->render_main_scroll)->child); } /* @@ -360,15 +612,37 @@ void a_UIcmd_open_urlstr(void *vbw, const char *urlstr) */ void a_UIcmd_open_url(BrowserWindow *bw, const DilloUrl *url) { - a_Nav_push(bw, url); + a_Nav_push(bw, url, NULL); + if (BW2UI(bw)->get_panelmode() == UI_TEMPORARILY_SHOW_PANELS) + BW2UI(bw)->set_panelmode(UI_HIDDEN); + a_UIcmd_focus_main_area(bw); +} + +static void UIcmd_open_url_nbw(BrowserWindow *new_bw, const DilloUrl *url) +{ + /* When opening a new BrowserWindow (tab or real window) we focus + * Location if we don't yet have an URL, main otherwise. + */ + if (url) { + a_Nav_push(new_bw, url, NULL); + BW2UI(new_bw)->focus_main(); + } else { + BW2UI(new_bw)->focus_location(); + } } /* - * Open a new URL in the given browser window + * Open a new URL in a new browser window */ void a_UIcmd_open_url_nw(BrowserWindow *bw, const DilloUrl *url) { - a_Nav_push_nw(bw, url); + int w, h; + BrowserWindow *new_bw; + + a_UIcmd_get_wh(bw, &w, &h); + new_bw = a_UIcmd_browser_window_new(w, h, 0, bw); + + UIcmd_open_url_nbw(new_bw, url); } /* @@ -377,12 +651,11 @@ void a_UIcmd_open_url_nw(BrowserWindow *bw, const DilloUrl *url) void a_UIcmd_open_url_nt(void *vbw, const DilloUrl *url, int focus) { BrowserWindow *new_bw = UIcmd_tab_new(vbw); - if (url) - a_Nav_push(new_bw, url); - if (focus) { + + if (focus) BW2UI(new_bw)->tabs()->selected_child(BW2UI(new_bw)); - BW2UI(new_bw)->tabs()->selected_child()->take_focus(); - } + + UIcmd_open_url_nbw(new_bw, url); } /* @@ -422,7 +695,7 @@ void a_UIcmd_forw_popup(void *vbw) */ void a_UIcmd_home(void *vbw) { - a_Nav_home((BrowserWindow*)vbw); + a_UIcmd_open_url((BrowserWindow*)vbw, prefs.home); } /* @@ -434,19 +707,44 @@ void a_UIcmd_reload(void *vbw) } /* + * Repush current URL + */ +void a_UIcmd_repush(void *vbw) +{ + a_Nav_repush((BrowserWindow*)vbw); +} + +/* + * Zero-delay URL redirection. + */ +void a_UIcmd_redirection0(void *vbw, const DilloUrl *url) +{ + a_Nav_redirection0((BrowserWindow*)vbw, url); +} + +/* * Return a suitable filename for a given URL path. */ static char *UIcmd_make_save_filename(const char *pathstr) { size_t MaxLen = 64; - char *FileName, *name; - const char *dir = a_UIcmd_get_save_dir(); + char *FileName, *newname, *o, *n; + const char *name, *dir = a_UIcmd_get_save_dir(); if ((name = strrchr(pathstr, '/'))) { if (strlen(++name) > MaxLen) { name = name + strlen(name) - MaxLen; } - FileName = dStrconcat(dir ? dir : "", name, NULL); + /* Replace %20 and ' ' with '_' in Filename */ + o = n = newname = dStrdup(name); + for (int i = 0; o[i]; i++) { + *n++ = (o[i] == ' ') ? '_' : + (o[i] == '%' && o[i+1] == '2' && o[i+2] == '0') ? + i+=2, '_' : o[i]; + } + *n = 0; + FileName = dStrconcat(dir ? dir : "", newname, NULL); + dFree(newname); } else { FileName = dStrconcat(dir ? dir : "", pathstr, NULL); } @@ -466,7 +764,7 @@ const char *a_UIcmd_get_save_dir() */ void a_UIcmd_set_save_dir(const char *dir) { - char *p; + const char *p; if (dir && (p = strrchr(dir, '/'))) { dFree(save_dir); @@ -481,24 +779,21 @@ void a_UIcmd_set_save_dir(const char *dir) void a_UIcmd_save(void *vbw) { const char *name; - char *SuggestedName, *urlstr; - DilloUrl *url; + char *SuggestedName; + BrowserWindow *bw = (BrowserWindow *)vbw; + const DilloUrl *url = a_History_get_url(NAV_TOP_UIDX(bw)); - a_UIcmd_set_save_dir(prefs.save_dir); + if (url) { + a_UIcmd_set_save_dir(prefs.save_dir); + SuggestedName = UIcmd_make_save_filename(URL_PATH(url)); + name = a_Dialog_save_file("Save Page as File", NULL, SuggestedName); + MSG("a_UIcmd_save: %s\n", name); + dFree(SuggestedName); - urlstr = a_UIcmd_get_location_text((BrowserWindow*)vbw); - url = a_Url_new(urlstr, NULL); - SuggestedName = UIcmd_make_save_filename(URL_PATH(url)); - 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); + if (name) { + a_Nav_save_url(bw, url, name); + } } - - a_Url_free(url); } /* @@ -519,11 +814,19 @@ void a_UIcmd_stop(void *vbw) MSG("a_UIcmd_stop()\n"); a_Nav_cancel_expect(bw); - a_Bw_stop_clients(bw, BW_Root + BW_Img + Bw_Force); + a_Bw_stop_clients(bw, BW_Root + BW_Img + BW_Force); a_UIcmd_set_buttons_sens(bw); } /* + * Popup the tools menu + */ +void a_UIcmd_tools(void *vbw, void *v_wid) +{ + a_Menu_tools_popup((BrowserWindow*)vbw, v_wid); +} + +/* * Open URL with dialog chooser */ void a_UIcmd_open_file(void *vbw) @@ -535,7 +838,7 @@ void a_UIcmd_open_file(void *vbw) if (name) { url = a_Url_new(name, "file:"); - a_Nav_push((BrowserWindow*)vbw, url); + a_UIcmd_open_url((BrowserWindow*)vbw, url); a_Url_free(url); dFree(name); } @@ -579,11 +882,12 @@ static char *UIcmd_make_search_str(const char *str) */ void a_UIcmd_search_dialog(void *vbw) { - const char *query, *url_str; + const char *query; if ((query = a_Dialog_input("Search the Web:"))) { - url_str = UIcmd_make_search_str(query); + char *url_str = UIcmd_make_search_str(query); a_UIcmd_open_urlstr(vbw, url_str); + dFree(url_str); } } @@ -625,7 +929,7 @@ void a_UIcmd_save_link(BrowserWindow *bw, const DilloUrl *url) void a_UIcmd_book(void *vbw) { DilloUrl *url = a_Url_new("dpi:/bm/", NULL); - a_Nav_push((BrowserWindow*)vbw, url); + a_UIcmd_open_url((BrowserWindow*)vbw, url); a_Url_free(url); } @@ -641,10 +945,11 @@ void a_UIcmd_add_bookmark(BrowserWindow *bw, const DilloUrl *url) /* * Popup the page menu */ -void a_UIcmd_page_popup(void *vbw, const DilloUrl *url, - bool_t has_bugs, bool_t unloaded_imgs) +void a_UIcmd_page_popup(void *vbw, bool_t has_bugs, void *v_cssUrls) { - a_Menu_page_popup((BrowserWindow*)vbw, url, has_bugs, unloaded_imgs); + BrowserWindow *bw = (BrowserWindow*)vbw; + const DilloUrl *url = a_History_get_url(NAV_TOP_UIDX(bw)); + a_Menu_page_popup(bw, url, has_bugs, v_cssUrls); } /* @@ -659,9 +964,18 @@ void a_UIcmd_link_popup(void *vbw, const DilloUrl *url) * Pop up the image menu */ void a_UIcmd_image_popup(void *vbw, const DilloUrl *url, bool_t loaded_img, - DilloUrl *link_url) + DilloUrl *page_url, DilloUrl *link_url) { - a_Menu_image_popup((BrowserWindow*)vbw, url, loaded_img, link_url); + a_Menu_image_popup((BrowserWindow*)vbw, url, loaded_img, page_url,link_url); +} + +/* + * Pop up the form menu + */ +void a_UIcmd_form_popup(void *vbw, const DilloUrl *url, void *vform, + bool_t showing_hiddens) +{ + a_Menu_form_popup((BrowserWindow*)vbw, url, vform, showing_hiddens); } /* @@ -682,17 +996,32 @@ void a_UIcmd_copy_urlstr(BrowserWindow *bw, const char *urlstr) } /* - * Show a text window with the URL's source + * Ask the vsource dpi to show this URL's source */ -void a_UIcmd_view_page_source(const DilloUrl *url) +void a_UIcmd_view_page_source(BrowserWindow *bw, const DilloUrl *url) { char *buf; int buf_size; + Dstr *dstr_url; + DilloUrl *vs_url; + static int post_id = 0; + char tag[8]; if (a_Nav_get_buf(url, &buf, &buf_size)) { - void *vWindow = a_Dialog_make_text_window(buf, "View Page source"); + a_Nav_set_vsource_url(url); + dstr_url = dStr_new("dpi:/vsource/:"); + dStr_append(dstr_url, URL_STR(url)); + if (URL_FLAGS(url) & URL_Post) { + /* append a custom string to differentiate POST URLs */ + post_id = (post_id < 9999) ? post_id + 1 : 0; + snprintf(tag, 8, "_%.4d", post_id); + dStr_append(dstr_url, tag); + } + vs_url = a_Url_new(dstr_url->str, NULL); + a_UIcmd_open_url_nt(bw, vs_url, 1); + a_Url_free(vs_url); + dStr_free(dstr_url, 1); a_Nav_unref_buf(url); - a_Dialog_show_text_window(vWindow); } } @@ -704,9 +1033,7 @@ void a_UIcmd_view_page_bugs(void *vbw) BrowserWindow *bw = (BrowserWindow*)vbw; if (bw->num_page_bugs > 0) { - void *vWindow = a_Dialog_make_text_window(bw->page_bugs->str, - "Detected HTML errors"); - a_Dialog_show_text_window(vWindow); + a_Dialog_text_window(bw->page_bugs->str, "Detected HTML errors"); } else { a_Dialog_msg("Zero detected HTML errors!"); } @@ -805,6 +1132,40 @@ void a_UIcmd_set_scroll_by_fragment(BrowserWindow *bw, const char *f) } /* + * Pass scrolling command to dw. + */ +void a_UIcmd_scroll(BrowserWindow *bw, int icmd) +{ + Layout *layout = (Layout*)bw->render_layout; + + if (layout) { + typedef struct { + KeysCommand_t keys_cmd; + ScrollCommand dw_cmd; + } mapping_t; + + const mapping_t map[] = { + {KEYS_SCREEN_UP, SCREEN_UP_CMD}, + {KEYS_SCREEN_DOWN, SCREEN_DOWN_CMD}, + {KEYS_LINE_UP, LINE_UP_CMD}, + {KEYS_LINE_DOWN, LINE_DOWN_CMD}, + {KEYS_LEFT, LEFT_CMD}, + {KEYS_RIGHT, RIGHT_CMD}, + {KEYS_TOP, TOP_CMD}, + {KEYS_BOTTOM, BOTTOM_CMD}, + }; + KeysCommand_t keycmd = (KeysCommand_t)icmd; + + for (uint_t i = 0; i < (sizeof(map)/sizeof(mapping_t)); i++) { + if (keycmd == map[i].keys_cmd) { + layout->scroll(map[i].dw_cmd); + break; + } + } + } +} + +/* * Get location's text */ char *a_UIcmd_get_location_text(BrowserWindow *bw) @@ -852,12 +1213,24 @@ void a_UIcmd_set_bug_prog(BrowserWindow *bw, int n_bug) } /* - * Set the page title. - * now it goes to the window titlebar (maybe to TAB label in the future). + * Set the page title in the window titlebar and tab label. + * (Update window titlebar for the current tab only) */ void a_UIcmd_set_page_title(BrowserWindow *bw, const char *label) { - BW2UI(bw)->set_page_title(label); + const int size = 128; + char title[size]; + + if (a_UIcmd_get_bw_by_widget(BW2UI(bw)->tabs()->selected_child()) == bw) { + // This is the focused bw, set window title + if (snprintf(title, size, "Dillo: %s", label) >= size) { + uint_t i = MIN(size - 4, 1 + a_Utf8_end_of_char(title, size - 8)); + snprintf(title + i, 4, "..."); + } + BW2UI(bw)->window()->copy_label(title); + BW2UI(bw)->window()->redraw_label(); + } + BW2UI(bw)->set_tab_title(label); } /* @@ -877,22 +1250,6 @@ void a_UIcmd_set_msg(BrowserWindow *bw, const char *format, ...) } /* - * Check whether the UI has automatic image loading enabled. - */ -bool_t a_UIcmd_get_images_enabled(BrowserWindow *bw) -{ - return BW2UI(bw)->images_enabled(); -} - -/* - * Enable/Disable automatic image loading. - */ -void a_UIcmd_set_images_enabled(BrowserWindow *bw, int flag) -{ - BW2UI(bw)->images_enabled(flag); -} - -/* * Set the sensitivity of back/forw/stop buttons. */ void a_UIcmd_set_buttons_sens(BrowserWindow *bw) @@ -912,6 +1269,22 @@ void a_UIcmd_set_buttons_sens(BrowserWindow *bw) } /* + * Keep track of mouse pointer over a link. + */ +void a_UIcmd_set_pointer_on_link(BrowserWindow *bw, int flag) +{ + BW2UI(bw)->pointerOnLink(flag); +} + +/* + * Is the mouse pointer over a link? + */ +int a_UIcmd_pointer_on_link(BrowserWindow *bw) +{ + return BW2UI(bw)->pointerOnLink(); +} + +/* * Toggle control panel (aka. fullscreen) */ void a_UIcmd_fullscreen_toggle(BrowserWindow *bw) @@ -920,23 +1293,24 @@ void a_UIcmd_fullscreen_toggle(BrowserWindow *bw) } /* - * Search for next occurrence of key. + * Search for next/previous occurrence of key. */ -void a_UIcmd_findtext_search(BrowserWindow *bw, const char *key, int case_sens) +void a_UIcmd_findtext_search(BrowserWindow *bw, const char *key, + int case_sens, int backwards) { Layout *l = (Layout *)bw->render_layout; - - switch (l->search(key, case_sens)) { - case FindtextState::RESTART: - a_UIcmd_set_msg(bw, "No further occurrences of \"%s\". " - "Restarting from the top.", key); - break; - case FindtextState::NOT_FOUND: - a_UIcmd_set_msg(bw, "\"%s\" not found.", key); - break; - case FindtextState::SUCCESS: - default: - a_UIcmd_set_msg(bw, ""); + + switch (l->search(key, case_sens, backwards)) { + case FindtextState::RESTART: + a_UIcmd_set_msg(bw, "No further occurrences of \"%s\". " + "Restarting from the top.", key); + break; + case FindtextState::NOT_FOUND: + a_UIcmd_set_msg(bw, "\"%s\" not found.", key); + break; + case FindtextState::SUCCESS: + default: + a_UIcmd_set_msg(bw, ""); } } diff --git a/src/uicmd.hh b/src/uicmd.hh index 02dbae8a..c8fea9e7 100644 --- a/src/uicmd.hh +++ b/src/uicmd.hh @@ -8,7 +8,8 @@ extern "C" { #endif /* __cplusplus */ -BrowserWindow *a_UIcmd_browser_window_new(int ww, int wh, const void *v_bw); +BrowserWindow *a_UIcmd_browser_window_new(int ww, int wh, + uint32_t xid, const void *v_bw); BrowserWindow *a_UIcmd_get_bw_by_widget(void *v_wid); void a_UIcmd_send_event_to_tabs_by_wid(int e, void *v_wid); void a_UIcmd_open_urlstr(void *vbw, const char *urlstr); @@ -21,8 +22,11 @@ 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_repush(void *vbw); +void a_UIcmd_redirection0(void *vbw, const DilloUrl *url); void a_UIcmd_save(void *vbw); void a_UIcmd_stop(void *vbw); +void a_UIcmd_tools(void *vbw, void *v_wid); void a_UIcmd_save_link(BrowserWindow *bw, const DilloUrl *url); void a_UIcmd_open_file(void *vbw); const char *a_UIcmd_select_file(); @@ -32,18 +36,20 @@ void a_UIcmd_book(void *vbw); void a_UIcmd_add_bookmark(BrowserWindow *bw, const DilloUrl *url); void a_UIcmd_fullscreen_toggle(BrowserWindow *bw); void a_UIcmd_findtext_dialog(BrowserWindow *bw); -void a_UIcmd_findtext_search(BrowserWindow *bw,const char *key,int case_sens); +void a_UIcmd_findtext_search(BrowserWindow *bw,const char *key,int case_sens, + int backwards); void a_UIcmd_findtext_reset(BrowserWindow *bw); void a_UIcmd_focus_main_area(BrowserWindow *bw); void a_UIcmd_focus_location(void *vbw); -void a_UIcmd_page_popup(void *vbw, const DilloUrl *url, - bool_t has_bugs, bool_t unloaded_imgs); +void a_UIcmd_page_popup(void *vbw, bool_t has_bugs, void *v_cssUrls); void a_UIcmd_link_popup(void *vbw, const DilloUrl *url); void a_UIcmd_image_popup(void *vbw, const DilloUrl *url, bool_t loaded_img, - DilloUrl *link_url); + DilloUrl *page_url, DilloUrl *link_url); +void a_UIcmd_form_popup(void *vbw, const DilloUrl *url, void *vform, + bool_t showing_hiddens); void a_UIcmd_file_popup(void *vbw, void *v_wid); void a_UIcmd_copy_urlstr(BrowserWindow *bw, const char *urlstr); -void a_UIcmd_view_page_source(const DilloUrl *url); +void a_UIcmd_view_page_source(BrowserWindow *bw, const 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); @@ -62,6 +68,7 @@ void a_UIcmd_get_wh(BrowserWindow *bw, int *w, int *h); void a_UIcmd_get_scroll_xy(BrowserWindow *bw, int *x, int *y); void a_UIcmd_set_scroll_xy(BrowserWindow *bw, int x, int y); void a_UIcmd_set_scroll_by_fragment(BrowserWindow *bw, const char *f); +void a_UIcmd_scroll(BrowserWindow *bw, int icmd); 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); @@ -69,11 +76,10 @@ 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, ...); -bool_t a_UIcmd_get_images_enabled(BrowserWindow *bw); -void a_UIcmd_set_images_enabled(BrowserWindow *bw, int flag); void a_UIcmd_set_buttons_sens(BrowserWindow *bw); void a_UIcmd_fullscreen_toggle(BrowserWindow *bw); - +void a_UIcmd_set_pointer_on_link(BrowserWindow *bw, int flag); +int a_UIcmd_pointer_on_link(BrowserWindow *bw); #ifdef __cplusplus } @@ -2,7 +2,7 @@ * File: url.c * * Copyright (C) 2001 Livio Baldini Soares <livio@linux.ime.usp.br> - * Copyright (C) 2001-2007 Jorge Arellano Cid <jcid@dillo.org> + * Copyright (C) 2001-2009 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 @@ -18,7 +18,7 @@ */ /* - * Regular Expression as given in RFC2396 for URL parsing. + * Regular Expression as given in RFC3986 for URL parsing. * * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? * 12 3 4 5 6 7 8 9 @@ -42,8 +42,6 @@ * - path is never "undefined" though it may be "empty". */ - -#include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> @@ -114,7 +112,7 @@ const char *a_Url_hostname(const DilloUrl *u) if ((p = strchr(url->authority, ':'))) { url->port = strtol(p + 1, NULL, 10); url->hostname = dStrndup(url->authority, - (uint_t)(p - url->authority)); + (uint_t)(p - url->authority)); } else { url->hostname = url->authority; } @@ -190,6 +188,7 @@ static DilloUrl *Url_object_new(const char *uri_str) /* * Free a DilloUrl + * Do nothing if the argument is NULL */ void a_Url_free(DilloUrl *url) { @@ -206,7 +205,7 @@ void a_Url_free(DilloUrl *url) } /* - * Resolve the URL as RFC2396 suggests. + * Resolve the URL as RFC3986 suggests. */ static Dstr *Url_resolve_relative(const char *RelStr, DilloUrl *BaseUrlPar, @@ -230,14 +229,19 @@ static Dstr *Url_resolve_relative(const char *RelStr, 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) { + /* path empty && scheme and authority undefined */ + if (!RelUrl->path && !RelUrl->scheme && !RelUrl->authority) { dStr_append(SolvedUrl, BaseStr); - + if ((p = strchr(SolvedUrl->str, '#'))) + dStr_truncate(SolvedUrl, p - SolvedUrl->str); + + if (RelUrl->query) { /* query */ + if (BaseUrl->query) + dStr_truncate(SolvedUrl, BaseUrl->query - BaseUrl->buffer - 1); + dStr_append_c(SolvedUrl, '?'); + dStr_append(SolvedUrl, RelUrl->query); + } if (RelUrl->fragment) { /* fragment */ - if (BaseUrl->fragment) - dStr_truncate(SolvedUrl, BaseUrl->fragment-BaseUrl->buffer-1); dStr_append_c(SolvedUrl, '#'); dStr_append(SolvedUrl, RelUrl->fragment); } @@ -252,14 +256,12 @@ static Dstr *Url_resolve_relative(const char *RelStr, 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) { + if (RelUrl->path && RelUrl->path[0] == '/') { /* absolute path */ + ; /* Ignore BaseUrl path */ + } else if (BaseUrl->path) { /* relative path */ dStr_append(Path, BaseUrl->path); - for (i = Path->len; --i >= 0 && Path->str[i] != '/'; ); + for (i = Path->len; --i >= 0 && Path->str[i] != '/'; ) ; if (Path->str[i] == '/') dStr_truncate(Path, ++i); } @@ -278,14 +280,10 @@ static Dstr *Url_resolve_relative(const char *RelStr, // 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; + if (p[3] == '/' || !p[3]) { // "/../" | "/.." + for (e = p + 3 ; p > s && p[-1] != '/'; --p) ; + dStr_erase(Path, p - Path->str, e - p + (p > s && *e != 0)); + p -= (p > Path->str); } else p += 3; } @@ -578,7 +576,7 @@ char *a_Url_decode_hex_str(const char *str) */ char *a_Url_encode_hex_str(const char *str) { - static const char *verbatim = "-_.*"; + static const char *const verbatim = "-_.*"; char *newstr, *c; if (!str) @@ -587,7 +585,7 @@ char *a_Url_encode_hex_str(const char *str) newstr = dNew(char, 6*strlen(str)+1); for (c = newstr; *str; str++) - if ((isalnum(*str) && !(*str & 0x80)) || strchr(verbatim, *str)) + if ((dIsalnum(*str) && !(*str & 0x80)) || strchr(verbatim, *str)) /* we really need isalnum for the "C" locale */ *c++ = *str; else if (*str == ' ') @@ -611,7 +609,7 @@ char *a_Url_encode_hex_str(const char *str) /* - * RFC-2396 suggests this stripping when "importing" URLs from other media. + * RFC-3986 suggests this stripping when "importing" URLs from other media. * Strip: "URL:", enclosing < >, and embedded whitespace. * (We also strip illegal chars: 00-1F and 7F) */ @@ -636,3 +634,121 @@ char *a_Url_string_strip_delimiters(const char *str) } return new_str; } + +/* + * Is the provided hostname an IP address? + */ +static bool_t Url_host_is_ip(const char *host) +{ + uint_t len; + + if (!host || !*host) + return FALSE; + + len = strlen(host); + + if (len == strspn(host, "0123456789.")) { + _MSG("an IPv4 address\n"); + return TRUE; + } + if (*host == '[' && + (len == strspn(host, "0123456789abcdefABCDEF:.[]"))) { + /* The precise format is shown in section 3.2.2 of rfc 3986 */ + _MSG("an IPv6 address\n"); + return TRUE; + } + return FALSE; +} + +/* + * How many internal dots are in the public portion of this hostname? + * e.g., for "www.dillo.org", it is one because everything under "dillo.org", + * as a .org domain, is part of one organization. + * + * Of course this is only a simple and imperfect approximation of + * organizational boundaries. + */ +static uint_t Url_host_public_internal_dots(const char *host) +{ + uint_t ret = 1; + + if (host) { + int start, after, tld_len; + + /* We may be able to trust the format of the host string more than + * I am here. Trailing dots and no dots are real possibilities, though. + */ + after = strlen(host); + if (after > 0 && host[after - 1] == '.') + after--; + start = after; + while (start > 0 && host[start - 1] != '.') + start--; + tld_len = after - start; + + if (tld_len > 0) { + /* These TLDs were chosen by examining the current publicsuffix list + * in January 2010 and picking out those where it was simplest for + * them to describe the situation by beginning with a "*.[tld]" rule. + */ + const char *const tlds[] = {"ar","au","bd","bn","bt","ck","cy","do", + "eg","er","et","fj","fk","gt","gu","id", + "il","jm","ke","kh","kw","ml","mm","mt", + "mz","ni","np","nz","om","pg","py","qa", + "sv","tr","uk","uy","ve","ye","yu","za", + "zm","zw"}; + uint_t i, tld_num = sizeof(tlds) / sizeof(tlds[0]); + + for (i = 0; i < tld_num; i++) { + if (strlen(tlds[i]) == (uint_t) tld_len && + !dStrncasecmp(tlds[i], host + start, tld_len)) { + _MSG("TLD code matched %s\n", tlds[i]); + ret++; + break; + } + } + } + } + return ret; +} + +/* + * Given a URL host string, return the portion that is public, i.e., the + * domain that is in a registry outside the organization. + * For 'www.dillo.org', that would be 'dillo.org'. + */ +const char *a_Url_host_find_public_suffix(const char *host) +{ + const char *s; + uint_t dots; + + if (!host || !*host || Url_host_is_ip(host)) + return host; + + s = host; + + while (s[1]) + s++; + + if (s > host && *s == '.') { + /* don't want to deal with trailing dot */ + s--; + } + + dots = Url_host_public_internal_dots(host); + + /* With a proper host string, we should not be pointing to a dot now. */ + + while (s > host) { + if (s[-1] == '.') { + if (dots == 0) + break; + else + dots--; + } + s--; + } + + _MSG("public suffix of %s is %s\n", host, s); + return s; +} @@ -10,7 +10,6 @@ #ifndef __URL_H__ #define __URL_H__ -#include <string.h> /* for strcmp */ #include "d_size.h" #include "../dlib/dlib.h" @@ -39,7 +38,7 @@ #define URL_ReloadPage (1 << 7) #define URL_ReloadFromCache (1 << 8) -#define URL_ReloadIncomplete (1 << 9) +#define URL_IgnoreScroll (1 << 9) #define URL_SpamSafe (1 << 10) #define URL_MultipartEnc (1 << 11) @@ -95,12 +94,12 @@ extern "C" { 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; // + 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; Dstr *data; /* POST */ @@ -124,7 +123,7 @@ 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); - +const char *a_Url_host_find_public_suffix(const char *host); #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/utf8.cc b/src/utf8.cc new file mode 100644 index 00000000..0138c616 --- /dev/null +++ b/src/utf8.cc @@ -0,0 +1,102 @@ +/* + * File: utf8.c + * + * Copyright (C) 2009 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 <fltk/utf.h> + +#include "../dlib/dlib.h" /* TRUE/FALSE */ +#include "utf8.hh" + +// C++ functions with C linkage ---------------------------------------------- + +/* + * Return index of the last byte of the UTF-8-encoded character that str + i + * points to or into. + */ +uint_t a_Utf8_end_of_char(const char *str, uint_t i) +{ + /* We can almost get what we want from utf8fwd(p+1,...)-1, but that + * does not work for the last character in a string, and the fn makes some + * assumptions that do not suit us. + * Here's something very simpleminded instead: + */ + if (str && *str && (str[i] & 0x80)) { + int internal_bytes = (str[i] & 0x40) ? 0 : 1; + + while (((str[i + 1] & 0xc0) == 0x80) && (++internal_bytes < 4)) + i++; + } + return i; +} + +/* + * Decode a single UTF-8-encoded character starting at p. + * The resulting Unicode value (in the range 0-0x10ffff) is returned, + * and len is set to the number of bytes in the UTF-8 encoding. + * Note that utf8decode(), if given non-UTF-8 data, will interpret + * it as ISO-8859-1 or CP1252 if possible. + */ +uint_t a_Utf8_decode(const char* str, const char* end, int* len) +{ + return utf8decode(str, end, len); +} + +/* + * Write UTF-8 encoding of ucs into buf and return number of bytes written. + */ +int a_Utf8_encode(unsigned int ucs, char *buf) +{ + return utf8encode(ucs, buf); +} + +/* + * Examine first srclen bytes of src. + * Return 0 if not legal UTF-8, 1 if all ASCII, 2 if all below 0x800, + * 3 if all below 0x10000, and 4 otherwise. + */ +int a_Utf8_test(const char* src, unsigned int srclen) +{ + return utf8test(src, srclen); +} + +/* + * Does s point to a UTF-8-encoded ideographic character? + * + * This is based on http://unicode.org/reports/tr14/#ID plus some guesses + * for what might make the most sense for Dillo. Surprisingly, they include + * Hangul Compatibility Jamo, but they're the experts, so I'll follow along. + */ +bool_t a_Utf8_ideographic(const char *s, const char *end, int *len) +{ + bool_t ret = FALSE; + + if ((uchar_t)*s >= 0xe2) { + /* Unicode char >= U+2000. */ + unsigned unicode = a_Utf8_decode(s, end, len); + + if (unicode >= 0x2e80 && + ((unicode <= 0xa4cf) || + (unicode >= 0xf900 && unicode <= 0xfaff) || + (unicode >= 0xff00 && unicode <= 0xff9f))) { + ret = TRUE; + } + } else { + *len = 1 + (int)a_Utf8_end_of_char(s, 0); + } + return ret; +} + +bool_t a_Utf8_combining_char(int unicode) +{ + return ((unicode >= 0x0300 && unicode <= 0x036f) || + (unicode >= 0x1dc0 && unicode <= 0x1dff) || + (unicode >= 0x20d0 && unicode <= 0x20ff) || + (unicode >= 0xfe20 && unicode <= 0xfe2f)); +} diff --git a/src/utf8.hh b/src/utf8.hh new file mode 100644 index 00000000..4ded50b8 --- /dev/null +++ b/src/utf8.hh @@ -0,0 +1,33 @@ +#ifndef __UTF8_HH__ +#define __UTF8_HH__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#include "d_size.h" + +/* + * Unicode replacement character U+FFFD + * "used to replace an incoming character whose value is unknown or otherwise + * unrepresentable in Unicode" + */ +static const char utf8_replacement_char[] = "\xEF\xBF\xBD"; + +/* Unicode zero width space U+200B */ +static const char utf8_zero_width_space[] = "\xE2\x80\x8B"; + +uint_t a_Utf8_end_of_char(const char *str, uint_t i); +uint_t a_Utf8_decode(const char*, const char* end, int* len); +int a_Utf8_encode(unsigned int ucs, char *buf); +int a_Utf8_test(const char* src, unsigned int srclen); +bool_t a_Utf8_ideographic(const char *s, const char *end, int *len); +bool_t a_Utf8_combining_char(int unicode); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __UTF8_HH__ */ + @@ -9,10 +9,6 @@ * (at your option) any later version. */ -#include <stdio.h> -#include <stdlib.h> -#include <math.h> /* for rint */ - #include "msg.h" #include "nav.h" @@ -22,7 +18,7 @@ #include "IO/mime.h" #include "dw/core.hh" -#include "prefs.h" +#include "styleengine.hh" #include "web.hh" // Platform independent part @@ -54,9 +50,6 @@ 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; _MSG("a_Web_dispatch_by_type\n"); @@ -67,25 +60,17 @@ int a_Web_dispatch_by_type (const char *Type, DilloWeb *Web, if (Web->flags & WEB_RootUrl) { /* We have RootUrl! */ + + /* Set a style for the widget */ + StyleEngine styleEngine (layout); + styleEngine.startElement ("body"); + Web->bgColor= styleEngine.backgroundStyle()->backgroundColor->getColor(); + dw = (Widget*) a_Mime_set_viewer(Type, Web, Call, Data); if (dw == NULL) return -1; - /* Set a style for the widget */ - fontAttrs.name = prefs.vw_fontname; - fontAttrs.size = (int) rint(14.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, prefs.bg_color); - widgetStyle = style::Style::create (layout, &styleAttrs); - dw->setStyle (widgetStyle); - widgetStyle->unref (); + dw->setStyle (styleEngine.style ()); /* This method frees the old dw if any */ layout->setWidget(dw); @@ -118,19 +103,21 @@ int a_Web_dispatch_by_type (const char *Type, DilloWeb *Web, /* * Allocate and set safe values for a DilloWeb structure */ -DilloWeb* a_Web_new(const DilloUrl *url) +DilloWeb* a_Web_new(const DilloUrl *url, const DilloUrl *requester) { DilloWeb *web= dNew(DilloWeb, 1); _MSG(" a_Web_new: ValidWebs ==> %d\n", dList_length(ValidWebs)); web->url = a_Url_dup(url); + web->requester = a_Url_dup(requester); web->bw = NULL; web->flags = 0; web->Image = NULL; web->filename = NULL; web->stream = NULL; web->SavedBytes = 0; - + web->bgColor = 0x000000; /* Dummy value will be overwritten + * in a_Web_dispatch_by_type. */ dList_append(ValidWebs, (void *)web); return web; } @@ -149,12 +136,12 @@ int a_Web_valid(DilloWeb *web) 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); + a_Url_free(web->url); + a_Url_free(web->requester); + a_Image_unref(web->Image); dFree(web->filename); dList_remove(ValidWebs, (void *)web); + _MSG("a_Web_free: ValidWebs=%d\n", dList_length(ValidWebs)); dFree(web); } @@ -22,18 +22,21 @@ typedef struct _DilloWeb DilloWeb; struct _DilloWeb { DilloUrl *url; /* Requested URL */ + DilloUrl *requester; /* URL that caused this request, or + * NULL if user-initiated. */ BrowserWindow *bw; /* The requesting browser window [reference] */ int flags; /* Additional info */ DilloImage *Image; /* For image urls [reference] */ + int32_t bgColor; /* for image backgrounds */ char *filename; /* Variables for Local saving */ FILE *stream; int SavedBytes; }; void a_Web_init(void); -DilloWeb* a_Web_new (const DilloUrl* url); +DilloWeb* a_Web_new (const DilloUrl* url, const DilloUrl *requester); int a_Web_valid(DilloWeb *web); void a_Web_free (DilloWeb*); int a_Web_dispatch_by_type (const char *Type, DilloWeb *web, diff --git a/src/xembed.cc b/src/xembed.cc new file mode 100644 index 00000000..04a4362a --- /dev/null +++ b/src/xembed.cc @@ -0,0 +1,165 @@ +/* + * File: xembed.cc + * + * Copyright (C) 2009 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 <ctype.h> + +#include <fltk/Window.h> +#include <fltk/run.h> +#include <fltk/events.h> +#include <fltk/x.h> + +#include "xembed.hh" + +#if USE_X11 + +typedef enum { + XEMBED_EMBEDDED_NOTIFY = 0, + XEMBED_WINDOW_ACTIVATE = 1, + XEMBED_WINDOW_DEACTIVATE = 2, + XEMBED_REQUEST_FOCUS = 3, + XEMBED_FOCUS_IN = 4, + XEMBED_FOCUS_OUT = 5, + XEMBED_FOCUS_NEXT = 6, + XEMBED_FOCUS_PREV = 7, + XEMBED_GRAB_KEY = 8, + XEMBED_UNGRAB_KEY = 9, + XEMBED_MODALITY_ON = 10, + XEMBED_MODALITY_OFF = 11, +} XEmbedMessageType; + +void +Xembed::setXembedInfo(unsigned long flags) +{ + unsigned long buffer[2]; + + Atom xembed_info_atom = XInternAtom (fltk::xdisplay, "_XEMBED_INFO", false); + + buffer[0] = 1; + buffer[1] = flags; + + XChangeProperty (fltk::xdisplay, + xid, + xembed_info_atom, xembed_info_atom, 32, + PropModeReplace, + (unsigned char *)buffer, 2); +} + +void +Xembed::sendXembedEvent(uint32_t message) { + XClientMessageEvent xclient; + + memset (&xclient, 0, sizeof (xclient)); + xclient.window = xid; + xclient.type = ClientMessage; + xclient.message_type = XInternAtom (fltk::xdisplay, "_XEMBED", false); + xclient.format = 32; + xclient.data.l[0] = fltk::event_time; + xclient.data.l[1] = message; + + XSendEvent(fltk::xdisplay, xid, False, NoEventMask, (XEvent *)&xclient); + XSync(fltk::xdisplay, False); +} + +int +Xembed::handle(int e) { + if (e == fltk::PUSH) + sendXembedEvent(XEMBED_REQUEST_FOCUS); + + return Window::handle(e); +} + +static int event_handler(int e, fltk::Window *w) { + Atom xembed_atom = XInternAtom (fltk::xdisplay, "_XEMBED", false); + + if (fltk::xevent.type == ClientMessage) { + if (fltk::xevent.xclient.message_type == xembed_atom) { + long message = fltk::xevent.xclient.data.l[1]; + + switch (message) { + case XEMBED_WINDOW_ACTIVATE: + // Force a ConfigureNotify message so fltk can get the new + // coordinates after a move of the embedder window. + w->resize(0, 0, w->w(), w->h()); + break; + case XEMBED_WINDOW_DEACTIVATE: + break; + default: + break; + } + } + } + + return 0; +} + +// TODO: Implement more XEMBED support; + +void Xembed::create() { + createInternal(xid); + setXembedInfo(1); + fltk::add_event_handler(event_handler); +} + +void Xembed::createInternal(uint32_t parent) { + fltk::Window *window = this; + Colormap colormap = fltk::xcolormap; + + XSetWindowAttributes attr; + attr.border_pixel = 0; + attr.colormap = colormap; + attr.bit_gravity = 0; // StaticGravity; + int mask = CWBorderPixel|CWColormap|CWEventMask|CWBitGravity; + + int W = window->w(); + if (W <= 0) W = 1; // X don't like zero... + int H = window->h(); + if (H <= 0) H = 1; // X don't like zero... + int X = window->x(); + int Y = window->y(); + + attr.event_mask = + ExposureMask | StructureNotifyMask + | KeyPressMask | KeyReleaseMask | KeymapStateMask | FocusChangeMask + | ButtonPressMask | ButtonReleaseMask + | EnterWindowMask | LeaveWindowMask + | PointerMotionMask; + + fltk::CreatedWindow::set_xid(window, + XCreateWindow(fltk::xdisplay, + parent, + X, Y, W, H, + 0, // borderwidth + fltk::xvisual->depth, + InputOutput, + fltk::xvisual->visual, + mask, &attr)); +} + +#else // USE_X11 + +void +Xembed::setXembedInfo(unsigned long flags) {}; + +void +Xembed::sendXembedEvent(uint32_t message) {}; + +int +Xembed::handle(int e) { + return Window::handle(e); +} + +void +Xembed::create() { + Window::create(); +} + +#endif diff --git a/src/xembed.hh b/src/xembed.hh new file mode 100644 index 00000000..70d79c5f --- /dev/null +++ b/src/xembed.hh @@ -0,0 +1,23 @@ +#ifndef __XEMBED_HH__ +#define __XEMBED_HH__ + +#include <fltk/Window.h> + +#include "d_size.h" + +class Xembed : public fltk::Window { + private: + uint32_t xid; + void createInternal(uint32_t parent); + void setXembedInfo(unsigned long flags); + void sendXembedEvent(uint32_t message); + + public: + Xembed(uint32_t xid, int _w, int _h) : fltk::Window(_w, _h) { + this->xid = xid; + }; + void create(); + int handle(int event); +}; + +#endif |