diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/IO/IO.c | 34 | ||||
-rw-r--r-- | src/IO/Makefile.am | 7 | ||||
-rw-r--r-- | src/IO/Url.h | 5 | ||||
-rw-r--r-- | src/IO/about.c | 2 | ||||
-rw-r--r-- | src/IO/http.c | 670 | ||||
-rw-r--r-- | src/IO/tls.c | 1258 | ||||
-rw-r--r-- | src/IO/tls.h | 49 | ||||
-rw-r--r-- | src/Makefile.am | 9 | ||||
-rw-r--r-- | src/cache.c | 140 | ||||
-rw-r--r-- | src/cache.h | 3 | ||||
-rw-r--r-- | src/capi.c | 225 | ||||
-rw-r--r-- | src/colors.c | 2 | ||||
-rw-r--r-- | src/cookies.h | 3 | ||||
-rw-r--r-- | src/css.cc | 2 | ||||
-rw-r--r-- | src/css.hh | 2 | ||||
-rw-r--r-- | src/cssparser.cc | 68 | ||||
-rw-r--r-- | src/decode.c | 26 | ||||
-rw-r--r-- | src/decode.h | 16 | ||||
-rw-r--r-- | src/dialog.cc | 31 | ||||
-rw-r--r-- | src/dillo.cc | 26 | ||||
-rw-r--r-- | src/form.cc | 4 | ||||
-rw-r--r-- | src/gif.c | 4 | ||||
-rw-r--r-- | src/hsts.c | 364 | ||||
-rw-r--r-- | src/hsts.h | 19 | ||||
-rwxr-xr-x | src/hsts_preload | 2037 | ||||
-rw-r--r-- | src/html.cc | 529 | ||||
-rw-r--r-- | src/html_charrefs.h | 2138 | ||||
-rw-r--r-- | src/klist.c | 2 | ||||
-rw-r--r-- | src/menu.cc | 35 | ||||
-rw-r--r-- | src/nav.c | 1 | ||||
-rw-r--r-- | src/paths.hh | 1 | ||||
-rw-r--r-- | src/png.c | 2 | ||||
-rw-r--r-- | src/prefs.c | 4 | ||||
-rw-r--r-- | src/prefs.h | 4 | ||||
-rw-r--r-- | src/prefsparser.cc | 5 | ||||
-rw-r--r-- | src/styleengine.cc | 46 | ||||
-rw-r--r-- | src/styleengine.hh | 1 | ||||
-rw-r--r-- | src/table.cc | 5 | ||||
-rw-r--r-- | src/tipwin.cc | 2 | ||||
-rw-r--r-- | src/uicmd.cc | 2 | ||||
-rw-r--r-- | src/url.c | 84 | ||||
-rw-r--r-- | src/url.h | 21 | ||||
-rw-r--r-- | src/web.cc | 2 |
43 files changed, 7145 insertions, 745 deletions
diff --git a/src/IO/IO.c b/src/IO/IO.c index a0a8bba5..0cdb9499 100644 --- a/src/IO/IO.c +++ b/src/IO/IO.c @@ -21,6 +21,7 @@ #include "../klist.h" #include "IO.h" #include "iowatch.hh" +#include "tls.h" /* * Symbolic defines for shutdown() function @@ -162,6 +163,7 @@ static bool_t IO_read(IOData_t *io) ssize_t St; bool_t ret = FALSE; int io_key = io->Key; + void *conn = a_Tls_connection(io->FD); _MSG(" IO_read\n"); @@ -170,7 +172,8 @@ static bool_t IO_read(IOData_t *io) io->Status = 0; while (1) { - St = read(io->FD, Buf, IOBufLen); + St = conn ? a_Tls_read(conn, Buf, IOBufLen) + : read(io->FD, Buf, IOBufLen); if (St > 0) { dStr_append_l(io->Buf, Buf, St); continue; @@ -214,12 +217,14 @@ static bool_t IO_write(IOData_t *io) { ssize_t St; bool_t ret = FALSE; + void *conn = a_Tls_connection(io->FD); _MSG(" IO_write\n"); io->Status = 0; while (1) { - St = write(io->FD, io->Buf->str, io->Buf->len); + St = conn ? a_Tls_write(conn, io->Buf->str, io->Buf->len) + : write(io->FD, io->Buf->str, io->Buf->len); if (St < 0) { /* Error */ if (errno == EINTR) { @@ -298,6 +303,8 @@ static void IO_fd_write_cb(int fd, void *data) } else { if (IO_callback(io) == 0) a_IOwatch_remove_fd(fd, DIO_WRITE); + if (io->Status) + a_IO_ccc(OpAbort, 1, FWD, io->Info, NULL, NULL); } } @@ -350,6 +357,7 @@ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, switch (Op) { case OpStart: io = IO_new(IOWrite); + io->Info = Info; Info->LocalKey = io; break; case OpSend: @@ -369,8 +377,8 @@ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, char *newline = memchr(io->Buf->str, '\n', io->Buf->len); int msglen = newline ? newline - io->Buf->str : 2048; - MSG_WARN("IO_write, closing with pending data not sent: " - "\"%s\"\n", dStr_printable(io->Buf, msglen)); + MSG("IO_write, closing with pending data not sent: \"%s\"\n", + dStr_printable(io->Buf, msglen)); } /* close FD, remove from ValidIOs and remove its watch */ IO_close_fd(io, Op == OpEnd ? IO_StopWr : IO_StopRdWr); @@ -378,14 +386,21 @@ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, dFree(Info); break; default: - MSG_WARN("Unused CCC\n"); + MSG_WARN("Unused CCC IO 1B\n"); break; } } else { /* 1 FWD */ /* Write-data status */ switch (Op) { + case OpAbort: + io = Info->LocalKey; + IO_close_fd(io, IO_StopRdWr); + IO_free(io); + a_Chain_fcb(OpAbort, Info, NULL, NULL); + dFree(Info); + break; default: - MSG_WARN("Unused CCC\n"); + MSG_WARN("Unused CCC IO 1F\n"); break; } } @@ -406,14 +421,15 @@ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, IO_submit(io); } break; + case OpEnd: case OpAbort: io = Info->LocalKey; - IO_close_fd(io, IO_StopRdWr); + IO_close_fd(io, Op == OpEnd ? IO_StopRd : IO_StopRdWr); IO_free(io); dFree(Info); break; default: - MSG_WARN("Unused CCC\n"); + MSG_WARN("Unused CCC IO 2B\n"); break; } } else { /* 2 FWD */ @@ -432,7 +448,7 @@ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, dFree(Info); break; default: - MSG_WARN("Unused CCC\n"); + MSG_WARN("Unused CCC IO 2F\n"); break; } } diff --git a/src/IO/Makefile.am b/src/IO/Makefile.am index c889dae8..d8fed40a 100644 --- a/src/IO/Makefile.am +++ b/src/IO/Makefile.am @@ -1,6 +1,9 @@ AM_CPPFLAGS = \ -I$(top_srcdir) \ - -DDILLO_BINDIR='"$(bindir)/"' + -DDILLO_BINDIR='"$(bindir)/"' \ + -DCA_CERTS_FILE='"@CA_CERTS_FILE@"' \ + -DCA_CERTS_DIR='"@CA_CERTS_DIR@"' + AM_CFLAGS = @LIBFLTK_CFLAGS@ AM_CXXFLAGS = @LIBFLTK_CXXFLAGS@ @@ -12,6 +15,8 @@ libDiof_a_SOURCES = \ about.c \ Url.h \ http.c \ + tls.h \ + tls.c \ dpi.c \ IO.c \ iowatch.cc \ diff --git a/src/IO/Url.h b/src/IO/Url.h index d9333d67..3f5a559b 100644 --- a/src/IO/Url.h +++ b/src/IO/Url.h @@ -16,10 +16,7 @@ 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, const DilloUrl *requester, - int web_flags, bool_t use_proxy); +void a_Http_connect_done(int fd, bool_t success); void a_Http_ccc (int Op, int Branch, int Dir, ChainLink *Info, void *Data1, void *Data2); diff --git a/src/IO/about.c b/src/IO/about.c index f9966c3d..07dbbb01 100644 --- a/src/IO/about.c +++ b/src/IO/about.c @@ -305,6 +305,8 @@ const char *const AboutSplash= "</table>\n" "</table>\n" "\n" +"<table border='0' width='100%' cellpadding='0' cellspacing='0'><tr><td height='10'></table>\n" +"\n" "\n" "<!-- the main layout table, a small vertical spacer -->\n" "\n" diff --git a/src/IO/http.c b/src/IO/http.c index a0021a9e..379d51c1 100644 --- a/src/IO/http.c +++ b/src/IO/http.c @@ -27,6 +27,7 @@ #include <arpa/inet.h> /* for inet_ntop */ #include "IO.h" +#include "tls.h" #include "Url.h" #include "../msg.h" #include "../klist.h" @@ -49,46 +50,47 @@ 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; +static const int HTTP_SOCKET_QUEUED = 0x2; +static const int HTTP_SOCKET_TO_BE_FREED = 0x4; +static const int HTTP_SOCKET_TLS = 0x8; -/* 'Url' and 'web' are just references (no need to deallocate them here). */ +/* 'web' is just a reference (no need to deallocate it here). */ typedef struct { int SockFD; - uint_t port; /* need a separate port in order to support PROXY */ uint_t flags; DilloWeb *web; /* reference to client's web structure */ + DilloUrl *url; 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 */ + char *connected_to; /* Used for per-server connection limit */ + uint_t connect_port; + Dstr *https_proxy_reply; } 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; + char *host; + uint_t port; + bool_t https; + + int active_conns; + int running_the_queue; + Dlist *queue; +} Server_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); + int fd; + int skey; +} FdMapEntry_t; + +static void Http_socket_enqueue(Server_t *srv, SocketData_t* sock); +static Server_t *Http_server_get(const char *host, uint_t port, bool_t https); +static void Http_server_remove(Server_t *srv); +static void Http_connect_socket(ChainLink *Info); +static char *Http_get_connect_str(const DilloUrl *url); +static void Http_send_query(SocketData_t *S); static void Http_socket_free(int SKey); /* @@ -99,7 +101,12 @@ static Klist_t *ValidSocks = NULL; /* Active sockets list. It holds pointers to static DilloUrl *HTTP_Proxy = NULL; static char *HTTP_Proxy_Auth_base64 = NULL; static char *HTTP_Language_hdr = NULL; -static Dlist *host_connections; +static Dlist *servers; + +/* TODO: If fd_map will stick around in its present form (FDs and SocketData_t) + * then consider whether having both this and ValidSocks is necessary. + */ +static Dlist *fd_map; /* * Initialize proxy vars and Accept-Language header @@ -124,7 +131,8 @@ int a_Http_init(void) HTTP_Proxy_Auth_base64 = a_Misc_encode_base64(prefs.http_proxyuser); */ - host_connections = dList_new(5); + servers = dList_new(5); + fd_map = dList_new(20); return 0; } @@ -155,33 +163,127 @@ void a_Http_set_proxy_passwd(const char *str) static int Http_sock_new(void) { SocketData_t *S = dNew0(SocketData_t, 1); + S->SockFD = -1; return a_Klist_insert(&ValidSocks, S); } -static void Http_connect_queued_sockets(HostConnection_t *hc) +/* + * Compare by FD. + */ +static int Http_fd_map_cmp(const void *v1, const void *v2) +{ + int fd = VOIDP2INT(v2); + const FdMapEntry_t *e = v1; + + return (fd != e->fd); +} + +static void Http_fd_map_add_entry(SocketData_t *sd) +{ + FdMapEntry_t *e = dNew0(FdMapEntry_t, 1); + e->fd = sd->SockFD; + e->skey = VOIDP2INT(sd->Info->LocalKey); + + if (dList_find_custom(fd_map, INT2VOIDP(e->fd), Http_fd_map_cmp)) { + MSG_ERR("FD ENTRY ALREADY FOUND FOR %d\n", e->fd); + assert(0); + } + + dList_append(fd_map, e); +} + +/* + * Remove and free entry from fd_map. + */ +static void Http_fd_map_remove_entry(int fd) +{ + void *data = dList_find_custom(fd_map, INT2VOIDP(fd), Http_fd_map_cmp); + + if (data) { + dList_remove_fast(fd_map, data); + dFree(data); + } else { + MSG("FD ENTRY NOT FOUND FOR %d\n", fd); + } +} + +void a_Http_connect_done(int fd, bool_t success) +{ + SocketData_t *sd; + FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd), + Http_fd_map_cmp); + + if (fme && (sd = a_Klist_get_data(ValidSocks, fme->skey))) { + ChainLink *info = sd->Info; + bool_t valid_web = a_Web_valid(sd->web); + + if (success && valid_web) { + a_Chain_bfcb(OpSend, info, &sd->SockFD, "FD"); + Http_send_query(sd); + } else { + if (valid_web) + MSG_BW(sd->web, 1, "Could not establish connection."); + MSG("fd %d is done and failed\n", sd->SockFD); + dClose(fd); + Http_socket_free(VOIDP2INT(info->LocalKey)); /* free sd */ + a_Chain_bfcb(OpAbort, info, NULL, "Both"); + dFree(info); + } + } else { + MSG("**** but no luck with fme %p or sd\n", fme); + } +} + +static void Http_socket_activate(Server_t *srv, SocketData_t *sd) +{ + dList_remove(srv->queue, sd); + sd->flags &= ~HTTP_SOCKET_QUEUED; + srv->active_conns++; + sd->connected_to = srv->host; +} + +static void Http_connect_queued_sockets(Server_t *srv) { SocketData_t *sd; - while (hc->active_connections < prefs.http_max_conns && - (sd = Http_socket_dequeue(&hc->queue))) { + int i; + + srv->running_the_queue++; - sd->flags &= ~HTTP_SOCKET_QUEUED; + for (i = 0; + (i < dList_length(srv->queue) && + srv->active_conns < prefs.http_max_conns); + i++) { + sd = dList_nth_data(srv->queue, i); 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) { - ChainLink *Info = sd->Info; - MSG_BW(sd->web, 1, "ERROR: %s", dStrerror(sd->Err)); - a_Chain_bfcb(OpAbort, Info, NULL, "Both"); - Http_socket_free(VOIDP2INT(Info->LocalKey)); /* free sd */ - dFree(Info); - } else { - sd->connected_to = hc->host; - hc->active_connections++; + dList_remove(srv->queue, sd); + dFree(sd); + i--; + } else { + int connect_ready = TLS_CONNECT_READY; + + if (sd->flags & HTTP_SOCKET_TLS) + connect_ready = a_Tls_connect_ready(sd->url); + + if (connect_ready == TLS_CONNECT_NEVER || !a_Web_valid(sd->web)) { + int SKey = VOIDP2INT(sd->Info->LocalKey); + + Http_socket_free(SKey); + } else if (connect_ready == TLS_CONNECT_READY) { + i--; + Http_socket_activate(srv, sd); + Http_connect_socket(sd->Info); } } } + + _MSG("Queue http%s://%s:%u len %d\n", srv->https ? "s" : "", srv->host, + srv->port, dList_length(srv->queue)); + + if (--srv->running_the_queue == 0) { + if (srv->active_conns == 0) + Http_server_remove(srv); + } } /* @@ -194,16 +296,24 @@ static void Http_socket_free(int SKey) if ((S = a_Klist_get_data(ValidSocks, SKey))) { a_Klist_remove(ValidSocks, SKey); + dStr_free(S->https_proxy_reply, 1); + if (S->flags & HTTP_SOCKET_QUEUED) { S->flags |= HTTP_SOCKET_TO_BE_FREED; + a_Url_free(S->url); } else { + if (S->SockFD != -1) + Http_fd_map_remove_entry(S->SockFD); + a_Tls_reset_server_state(S->url); 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); + a_Tls_close_by_fd(S->SockFD); + + Server_t *srv = Http_server_get(S->connected_to, S->connect_port, + (S->flags & HTTP_SOCKET_TLS)); + srv->active_conns--; + Http_connect_queued_sockets(srv); } + a_Url_free(S->url); dFree(S); } } @@ -261,20 +371,23 @@ static Dstr *Http_make_content_type(const DilloUrl *url) /* * Make the http query string */ -Dstr *a_Http_make_query_str(const DilloUrl *url, const DilloUrl *requester, - int web_flags, bool_t use_proxy) +static Dstr *Http_make_query_str(DilloWeb *web, bool_t use_proxy) { char *ptr, *cookies, *referer, *auth; + const DilloUrl *url = web->url; Dstr *query = dStr_new(""), *request_uri = dStr_new(""), *proxy_auth = dStr_new(""); /* BUG: dillo doesn't actually understand application/xml yet */ const char *accept_hdr_value = - web_flags & WEB_Image ? "image/png,image/*;q=0.8,*/*;q=0.5" : - web_flags & WEB_Stylesheet ? "text/css,*/*;q=0.1" : + web->flags & WEB_Image ? "image/png,image/*;q=0.8,*/*;q=0.5" : + web->flags & WEB_Stylesheet ? "text/css,*/*;q=0.1" : "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"; + const char *connection_hdr_val = + (prefs.http_persistent_conns == TRUE) ? "keep-alive" : "close"; + if (use_proxy) { dStr_sprintfa(request_uri, "%s%s", URL_STR(url), @@ -292,7 +405,7 @@ Dstr *a_Http_make_query_str(const DilloUrl *url, const DilloUrl *requester, (URL_PATH_(url) || URL_QUERY_(url)) ? "" : "/"); } - cookies = a_Cookies_get_query(url, requester); + cookies = a_Cookies_get_query(url, web->requester); auth = a_Auth_get_auth_str(url, request_uri->str); referer = Http_get_referer(url); if (URL_FLAGS(url) & URL_Post) { @@ -309,15 +422,15 @@ Dstr *a_Http_make_query_str(const DilloUrl *url, const DilloUrl *requester, "DNT: 1\r\n" "%s" /* proxy auth */ "%s" /* referer */ - "Connection: close\r\n" + "Connection: %s\r\n" "Content-Type: %s\r\n" "Content-Length: %ld\r\n" "%s" /* cookies */ "\r\n", request_uri->str, URL_AUTHORITY(url), prefs.http_user_agent, accept_hdr_value, HTTP_Language_hdr, auth ? auth : "", - proxy_auth->str, referer, content_type->str, (long)URL_DATA(url)->len, - cookies); + proxy_auth->str, referer, connection_hdr_val, content_type->str, + (long)URL_DATA(url)->len, cookies); dStr_append_l(query, URL_DATA(url)->str, URL_DATA(url)->len); dStr_free(content_type, TRUE); } else { @@ -333,13 +446,13 @@ Dstr *a_Http_make_query_str(const DilloUrl *url, const DilloUrl *requester, "DNT: 1\r\n" "%s" /* proxy auth */ "%s" /* referer */ - "Connection: close\r\n" + "Connection: %s\r\n" "%s" /* cache control */ "%s" /* cookies */ "\r\n", request_uri->str, URL_AUTHORITY(url), prefs.http_user_agent, accept_hdr_value, HTTP_Language_hdr, auth ? auth : "", - proxy_auth->str, referer, + proxy_auth->str, referer, connection_hdr_val, (URL_FLAGS(url) & URL_E2EQuery) ? "Pragma: no-cache\r\nCache-Control: no-cache\r\n" : "", cookies); @@ -357,14 +470,13 @@ Dstr *a_Http_make_query_str(const DilloUrl *url, const DilloUrl *requester, /* * Create and submit the HTTP query to the IO engine */ -static void Http_send_query(ChainLink *Info, SocketData_t *S) +static void Http_send_query(SocketData_t *S) { Dstr *query; DataBuf *dbuf; /* Create the query */ - query = a_Http_make_query_str(S->web->url, S->web->requester, S->web->flags, - S->flags & HTTP_SOCKET_USE_PROXY); + query = Http_make_query_str(S->web, S->flags & HTTP_SOCKET_USE_PROXY); dbuf = a_Chain_dbuf_new(query->str, query->len, 0); /* actually this message is sent too early. @@ -372,26 +484,49 @@ 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_bcb(OpSend, Info, dbuf, NULL); + a_Chain_bcb(OpSend, S->Info, dbuf, NULL); dFree(dbuf); dStr_free(query, 1); } /* - * This function gets called after the DNS succeeds solving a hostname. + * Prepare an HTTPS connection. If necessary, tunnel it through a proxy. + * Then perform the TLS handshake. + */ +static void Http_connect_tls(ChainLink *info) +{ + int SKey = VOIDP2INT(info->LocalKey); + SocketData_t *S = a_Klist_get_data(ValidSocks, SKey); + + if (S->flags & HTTP_SOCKET_USE_PROXY) { + char *connect_str = Http_get_connect_str(S->url); + DataBuf *dbuf = a_Chain_dbuf_new(connect_str, strlen(connect_str), 0); + + a_Chain_bfcb(OpSend, info, &S->SockFD, "FD"); + S->https_proxy_reply = dStr_new(NULL); + a_Chain_bcb(OpSend, info, dbuf, NULL); + + dFree(dbuf); + dFree(connect_str); + } else { + a_Tls_handshake(S->SockFD, S->url); + } +} + +/* + * This function is called after the DNS succeeds in solving a hostname. * Task: Finish socket setup and start connecting the socket. - * Return value: 0 on success; -1 on error. */ -static int Http_connect_socket(ChainLink *Info) +static void Http_connect_socket(ChainLink *Info) { int i, status; + SocketData_t *S; + DilloHost *dh; #ifdef ENABLE_IPV6 struct sockaddr_in6 name; #else struct sockaddr_in name; #endif - SocketData_t *S; - DilloHost *dh; socklen_t socket_len = 0; S = a_Klist_get_data(ValidSocks, VOIDP2INT(Info->LocalKey)); @@ -399,10 +534,11 @@ static int Http_connect_socket(ChainLink *Info) /* TODO: iterate this address list until success, or end-of-list */ 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; } + Http_fd_map_add_entry(S); + /* 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)); @@ -416,10 +552,11 @@ static int Http_connect_socket(ChainLink *Info) 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); + sin->sin_port = htons(S->connect_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)); + MSG("Connecting to %s:%u\n", inet_ntoa(sin->sin_addr), + S->connect_port); break; } #ifdef ENABLE_IPV6 @@ -429,39 +566,34 @@ static int Http_connect_socket(ChainLink *Info) 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); + sin6->sin6_port = htons(S->connect_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); + MSG("Connecting to %s:%u\n", buf, S->connect_port); 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; - dClose(S->SockFD); - MSG("Http_connect_socket ERROR: %s\n", dStrerror(S->Err)); + MSG("Http_connect_socket ERROR: %s\n", dStrerror(errno)); + a_Http_connect_done(S->SockFD, FALSE); + } else if (S->flags & HTTP_SOCKET_TLS) { + Http_connect_tls(Info); } 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 */ + a_Http_connect_done(S->SockFD, TRUE); } + return; } - - return -1; } /* * Test proxy settings and check the no_proxy domains list * Return value: whether to use proxy or not. */ -static int Http_must_use_proxy(const DilloUrl *url) +static int Http_must_use_proxy(const char *hostname) { char *np, *p, *tok; int ret = 0; @@ -469,14 +601,13 @@ 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); + size_t host_len = strlen(hostname); np = dStrdup(prefs.no_proxy); for (p = np; (tok = dStrsep(&p, " ")); ) { int start = host_len - strlen(tok); - if (start >= 0 && dStrAsciiCasecmp(host + start, tok) == 0) { + if (start >= 0 && dStrAsciiCasecmp(hostname + start, tok) == 0) { /* no_proxy token is suffix of host string */ ret = 0; break; @@ -485,22 +616,21 @@ 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"); + _MSG("Http_must_use_proxy: %s\n %s\n", hostname, 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) +static char *Http_get_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); + dReturn_val_if_fail(Http_must_use_proxy(URL_HOST(url)), NULL); dstr = dStr_new(""); auth1 = URL_AUTHORITY(url); @@ -532,14 +662,6 @@ char *a_Http_make_connect_str(const DilloUrl *url) } /* - * 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. @@ -547,34 +669,35 @@ const char *a_Http_get_proxy_urlstr() static void Http_dns_cb(int Status, Dlist *addr_list, void *data) { int SKey = VOIDP2INT(data); + bool_t clean_up = TRUE; SocketData_t *S; - HostConnection_t *hc; + Server_t *srv; S = a_Klist_get_data(ValidSocks, SKey); if (S) { - if (!a_Web_valid(S->web)) { - a_Chain_bfcb(OpAbort, S->Info, NULL, "Both"); - dFree(S->Info); - Http_socket_free(SKey); + const char *host = URL_HOST((S->flags & HTTP_SOCKET_USE_PROXY) ? + HTTP_Proxy : S->url); + if (a_Web_valid(S->web)) { + if (Status == 0 && addr_list) { + + /* Successful DNS answer; save the IP */ + S->addr_list = addr_list; + clean_up = FALSE; + srv = Http_server_get(host, S->connect_port, + (S->flags & HTTP_SOCKET_TLS)); + Http_socket_enqueue(srv, S); + Http_connect_queued_sockets(srv); + } else { + /* DNS wasn't able to resolve the hostname */ + MSG_BW(S->web, 0, "ERROR: DNS can't resolve %s", host); + } + } + if (clean_up) { + ChainLink *info = S->Info; - } else if (Status == 0 && addr_list) { - /* Successful DNS answer; save the IP */ - S->addr_list = addr_list; - 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->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); + a_Chain_bfcb(OpAbort, info, NULL, "Both"); + dFree(info); } } } @@ -590,6 +713,7 @@ static int Http_get(ChainLink *Info, void *Data1) { SocketData_t *S; char *hostname; + const DilloUrl *url; S = a_Klist_get_data(ValidSocks, VOIDP2INT(Info->LocalKey)); /* Reference Web data */ @@ -598,18 +722,20 @@ static int Http_get(ChainLink *Info, void *Data1) S->Info = Info; /* Proxy support */ - if (Http_must_use_proxy(S->web->url)) { - hostname = dStrdup(URL_HOST(HTTP_Proxy)); - S->port = URL_PORT(HTTP_Proxy); + if (Http_must_use_proxy(URL_HOST(S->web->url))) { + url = HTTP_Proxy; S->flags |= HTTP_SOCKET_USE_PROXY; } else { - hostname = dStrdup(URL_HOST(S->web->url)); - S->port = URL_PORT(S->web->url); - S->flags &= ~HTTP_SOCKET_USE_PROXY; + url = S->web->url; } + hostname = dStrdup(URL_HOST(url)); + S->connect_port = URL_PORT(url); + S->url = a_Url_dup(S->web->url); + if (!dStrAsciiCasecmp(URL_SCHEME(S->url), "https")) + S->flags |= HTTP_SOCKET_TLS; /* Let the user know what we'll do */ - MSG_BW(S->web, 1, "DNS resolving %s", URL_HOST_(S->web->url)); + MSG_BW(S->web, 1, "DNS resolving %s", hostname); /* Let the DNS engine resolve the hostname, and when done, * we'll try to connect the socket from the callback function */ @@ -620,14 +746,75 @@ static int Http_get(ChainLink *Info, void *Data1) } /* + * Can the old socket's fd be reused for the new socket? + * + * NOTE: old and new must come from the same Server_t. + * This is not built to accept arbitrary sockets. + */ +static bool_t Http_socket_reuse_compatible(SocketData_t *old, + SocketData_t *new) +{ + /* + * If we are using TLS through a proxy, we need to ensure that old and new + * are going through to the same host:port. + */ + if (a_Web_valid(new->web) && + ((old->flags & HTTP_SOCKET_TLS) == 0 || + (old->flags & HTTP_SOCKET_USE_PROXY) == 0 || + ((URL_PORT(old->url) == URL_PORT(new->url)) && + !dStrAsciiCasecmp(URL_HOST(old->url), URL_HOST(new->url))))) + return TRUE; + return FALSE; +} + +/* + * If any entry in the socket data queue can reuse our connection, set it up + * and send off a new query. + */ +static void Http_socket_reuse(int SKey) +{ + SocketData_t *new_sd, *old_sd = a_Klist_get_data(ValidSocks, SKey); + + if (old_sd) { + Server_t *srv = Http_server_get(old_sd->connected_to, + old_sd->connect_port, + (old_sd->flags & HTTP_SOCKET_TLS)); + int i, n = dList_length(srv->queue); + + for (i = 0; i < n; i++) { + new_sd = dList_nth_data(srv->queue, i); + + if (!(new_sd->flags & HTTP_SOCKET_TO_BE_FREED) && + Http_socket_reuse_compatible(old_sd, new_sd)) { + const bool_t success = TRUE; + + new_sd->SockFD = old_sd->SockFD; + + old_sd->connected_to = NULL; + srv->active_conns--; + Http_socket_free(SKey); + + MSG("Reusing fd %d for %s\n", new_sd->SockFD,URL_STR(new_sd->url)); + Http_socket_activate(srv, new_sd); + Http_fd_map_add_entry(new_sd); + a_Http_connect_done(new_sd->SockFD, success); + return; + } + } + dClose(old_sd->SockFD); + Http_socket_free(SKey); + } +} + +/* * CCC function for the HTTP module */ void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info, void *Data1, void *Data2) { int SKey = VOIDP2INT(Info->LocalKey); - - (void)Data2; /* suppress unused parameter warning */ + SocketData_t *sd; + DataBuf *dbuf; dReturn_if_fail( a_Chain_check("a_Http_ccc", Op, Branch, Dir, Info) ); @@ -648,103 +835,207 @@ void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info, case OpEnd: /* finished the HTTP query branch */ a_Chain_bcb(OpEnd, Info, NULL, NULL); - Http_socket_free(SKey); dFree(Info); break; case OpAbort: - /* something bad happened... */ - a_Chain_bcb(OpAbort, Info, NULL, NULL); + MSG("ABORT 1B\n"); Http_socket_free(SKey); + a_Chain_bcb(OpAbort, Info, NULL, NULL); dFree(Info); break; + default: + MSG_WARN("Unused CCC 1B Op %d\n", Op); + break; } } else { /* 1 FWD */ /* HTTP send-query status branch */ switch (Op) { + case OpAbort: + MSG("ABORT 1F\n"); + if ((sd = a_Klist_get_data(ValidSocks, SKey))) + MSG_BW(sd->web, 1, "Can't get %s", URL_STR(sd->url)); + Http_socket_free(SKey); + a_Chain_fcb(OpAbort, Info, NULL, "Both"); + dFree(Info); + break; default: - MSG_WARN("Unused CCC\n"); + MSG_WARN("Unused CCC 1F Op %d\n", Op); + break; + } + } + } else if (Branch == 2) { + if (Dir == FWD) { + sd = a_Klist_get_data(ValidSocks, SKey); + assert(sd); + /* Receiving from server */ + switch (Op) { + case OpSend: + if (sd->https_proxy_reply) { + dbuf = Data1; + dStr_append(sd->https_proxy_reply, dbuf->Buf); + if (strstr(sd->https_proxy_reply->str, "\r\n\r\n")) { + if (sd->https_proxy_reply->len >= 12 && + sd->https_proxy_reply->str[9] == '2') { + /* e.g. "HTTP/1.1 200 Connection established[...]" */ + MSG("CONNECT through proxy succeeded. Reply:\n%s\n", + sd->https_proxy_reply->str); + dStr_free(sd->https_proxy_reply, 1); + sd->https_proxy_reply = NULL; + a_Tls_handshake(sd->SockFD, sd->url); + } else { + MSG_BW(sd->web, 1, "Can't connect through proxy to %s", + URL_HOST(sd->url)); + MSG("CONNECT through proxy failed. Server sent:\n%s\n", + sd->https_proxy_reply->str); + Http_socket_free(SKey); + a_Chain_bfcb(OpAbort, Info, NULL, "Both"); + dFree(Info); + } + } + } else { + /* Data1 = dbuf */ + a_Chain_fcb(OpSend, Info, Data1, "send_page_2eof"); + } + break; + case OpEnd: + if (sd->https_proxy_reply) { + MSG("CONNECT through proxy failed. " + "Full reply not received:\n%s\n", + sd->https_proxy_reply->len ? sd->https_proxy_reply->str : + "(nothing)"); + Http_socket_free(SKey); + a_Chain_bfcb(OpAbort, Info, NULL, "Both"); + } else { + Http_socket_free(SKey); + a_Chain_fcb(OpEnd, Info, NULL, NULL); + } + dFree(Info); + break; + default: + MSG_WARN("Unused CCC 2F Op %d\n", Op); + break; + } + } else { /* 2 BCK */ + switch (Op) { + case OpStart: + a_Chain_link_new(Info, a_Http_ccc, BCK, a_IO_ccc, 2, 2); + a_Chain_bcb(OpStart, Info, NULL, NULL); /* IORead */ + break; + case OpSend: + if (Data2) { + if (!strcmp(Data2, "FD")) { + int fd = *(int*)Data1; + FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd), + Http_fd_map_cmp); + Info->LocalKey = INT2VOIDP(fme->skey); + a_Chain_bcb(OpSend, Info, Data1, Data2); + } else if (!strcmp(Data2, "reply_complete")) { + a_Chain_bfcb(OpEnd, Info, NULL, NULL); + Http_socket_reuse(SKey); + dFree(Info); + } + } + break; + case OpAbort: + Http_socket_free(SKey); + a_Chain_bcb(OpAbort, Info, NULL, NULL); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC 2B Op %d\n", Op); break; } } } } - -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) +/* + * Add socket data to the queue. Pages/stylesheets/etc. have higher priority + * than images. + */ +static void Http_socket_enqueue(Server_t *srv, SocketData_t* sock) { - SocketQueueEntry_t *se = dNew(SocketQueueEntry_t, 1); - - se->sock = sock; - se->next = NULL; + sock->flags |= HTTP_SOCKET_QUEUED; - if (sq->tail) - sq->tail->next = se; - sq->tail = se; + if ((sock->web->flags & WEB_Image) == 0) { + int i, n = dList_length(srv->queue); - if (! sq->head) - sq->head = se; -} + for (i = 0; i < n; i++) { + SocketData_t *curr = dList_nth_data(srv->queue, i); -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); + if (a_Web_valid(curr->web) && (curr->web->flags & WEB_Image)) { + dList_insert_pos(srv->queue, sock, i); + return; + } + } } - - return sd; + dList_append(srv->queue, sock); } -static HostConnection_t *Http_host_connection_get(const char *host) +static Server_t *Http_server_get(const char *host, uint_t port, bool_t https) { int i; - HostConnection_t *hc; + Server_t *srv; - for (i = 0; i < dList_length(host_connections); i++) { - hc = (HostConnection_t*) dList_nth_data(host_connections, i); + for (i = 0; i < dList_length(servers); i++) { + srv = (Server_t*) dList_nth_data(servers, i); - if (dStrAsciiCasecmp(host, hc->host) == 0) - return hc; + if (port == srv->port && https == srv->https && + !dStrAsciiCasecmp(host, srv->host)) + return srv; } - hc = dNew0(HostConnection_t, 1); - Http_socket_queue_init(&hc->queue); - hc->host = dStrdup(host); - dList_append(host_connections, hc); + srv = dNew0(Server_t, 1); + srv->queue = dList_new(10); + srv->running_the_queue = 0; + srv->host = dStrdup(host); + srv->port = port; + srv->https = https; + dList_append(servers, srv); - return hc; + return srv; } -static void Http_host_connection_remove(HostConnection_t *hc) +static void Http_server_remove(Server_t *srv) { - assert(hc->queue.head == NULL); - dList_remove_fast(host_connections, hc); - dFree(hc->host); - dFree(hc); + SocketData_t *sd; + + while ((sd = dList_nth_data(srv->queue, 0))) { + dList_remove_fast(srv->queue, sd); + dFree(sd); + } + dList_free(srv->queue); + dList_remove_fast(servers, srv); + dFree(srv->host); + dFree(srv); +} + +static void Http_servers_remove_all() +{ + Server_t *srv; + SocketData_t *sd; + + while (dList_length(servers) > 0) { + srv = (Server_t*) dList_nth_data(servers, 0); + while ((sd = dList_nth_data(srv->queue, 0))) { + dList_remove(srv->queue, sd); + dFree(sd); + } + Http_server_remove(srv); + } + dList_free(servers); } -static void Http_host_connection_remove_all() +static void Http_fd_map_remove_all() { - HostConnection_t *hc; + FdMapEntry_t *fme; + int i, n = dList_length(fd_map); - 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); + for (i = 0; i < n; i++) { + fme = (FdMapEntry_t *) dList_nth_data(fd_map, i); + dFree(fme); } - dList_free(host_connections); + dList_free(fd_map); } /* @@ -753,7 +1044,8 @@ static void Http_host_connection_remove_all() */ void a_Http_freeall(void) { - Http_host_connection_remove_all(); + Http_servers_remove_all(); + Http_fd_map_remove_all(); a_Klist_free(&ValidSocks); a_Url_free(HTTP_Proxy); dFree(HTTP_Proxy_Auth_base64); diff --git a/src/IO/tls.c b/src/IO/tls.c new file mode 100644 index 00000000..dfe76744 --- /dev/null +++ b/src/IO/tls.c @@ -0,0 +1,1258 @@ +/* + * File: tls.c + * + * Copyright 2004 Garrett Kajmowicz <gkajmowi@tbaytel.net> + * (for some bits derived from the https dpi, e.g., certificate handling) + * Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, + * 2009, 2010, 2011, 2012 Free Software Foundation, Inc. + * (for the certificate hostname checking from wget) + * Copyright (C) 2011 Benjamin Johnson <obeythepenguin@users.sourceforge.net> + * (for the https code offered from dplus browser that formed the basis...) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * As a special exception, permission is granted to link Dillo with the OpenSSL + * or LibreSSL library, and distribute the linked executables without + * including the source code for OpenSSL or LibreSSL in the source + * distribution. You must obey the GNU General Public License, version 3, in + * all respects for all of the code used other than OpenSSL or LibreSSL. + */ + +/* https://www.ssllabs.com/ssltest/viewMyClient.html + * https://github.com/lgarron/badssl.com + */ + +/* + * Using TLS in Applications: http://datatracker.ietf.org/wg/uta/documents/ + * TLS: http://datatracker.ietf.org/wg/tls/documents/ + */ + +#include "config.h" +#include "../msg.h" + +#ifndef ENABLE_SSL + +void a_Tls_init() +{ + MSG("TLS: Disabled at compilation time.\n"); +} + +#else + +#include <assert.h> + +#include <sys/stat.h> +#include <sys/types.h> + +#include <ctype.h> /* tolower for wget stuff */ +#include <stdio.h> +#include <errno.h> +#include "../../dlib/dlib.h" +#include "../dialog.hh" +#include "../klist.h" +#include "iowatch.hh" +#include "tls.h" +#include "Url.h" + +#include <openssl/ssl.h> +#include <openssl/rand.h> +#include <openssl/err.h> +#include <openssl/x509v3.h> /* for hostname checking */ + +#define CERT_STATUS_NONE 0 +#define CERT_STATUS_RECEIVING 1 +#define CERT_STATUS_CLEAN 2 +#define CERT_STATUS_BAD 3 +#define CERT_STATUS_USER_ACCEPTED 4 + +typedef struct { + char *hostname; + int port; + int cert_status; +} Server_t; + +typedef struct { + int fd; + int connkey; +} FdMapEntry_t; + +/* + * Data type for TLS connection information + */ +typedef struct { + int fd; + DilloUrl *url; + SSL *ssl; + bool_t connecting; +} Conn_t; + +/* List of active TLS connections */ +static Klist_t *conn_list = NULL; + +/* + * If ssl_context is still NULL, this corresponds to TLS being disabled. + */ +static SSL_CTX *ssl_context; +static Dlist *servers; +static Dlist *fd_map; + +static void Tls_connect_cb(int fd, void *vconnkey); + +/* + * Compare by FD. + */ +static int Tls_fd_map_cmp(const void *v1, const void *v2) +{ + int fd = VOIDP2INT(v2); + const FdMapEntry_t *e = v1; + + return (fd != e->fd); +} + +static void Tls_fd_map_add_entry(int fd, int connkey) +{ + FdMapEntry_t *e = dNew0(FdMapEntry_t, 1); + e->fd = fd; + e->connkey = connkey; + + if (dList_find_custom(fd_map, INT2VOIDP(e->fd), Tls_fd_map_cmp)) { + MSG_ERR("TLS FD ENTRY ALREADY FOUND FOR %d\n", e->fd); + assert(0); + } + + dList_append(fd_map, e); +//MSG("ADD ENTRY %d %s\n", e->fd, URL_STR(sd->url)); +} + +/* + * Remove and free entry from fd_map. + */ +static void Tls_fd_map_remove_entry(int fd) +{ + void *data = dList_find_custom(fd_map, INT2VOIDP(fd), Tls_fd_map_cmp); + +//MSG("REMOVE ENTRY %d\n", fd); + if (data) { + dList_remove_fast(fd_map, data); + dFree(data); + } else { + MSG("TLS FD ENTRY NOT FOUND FOR %d\n", fd); + } +} + +/* + * Return TLS connection information for a given file + * descriptor, or NULL if no TLS connection was found. + */ +void *a_Tls_connection(int fd) +{ + Conn_t *conn; + + if (fd_map) { + FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd), + Tls_fd_map_cmp); + + if (fme && (conn = a_Klist_get_data(conn_list, fme->connkey))) + return conn; + } + return NULL; +} + +/* + * Add a new TLS connection information node. + */ +static int Tls_conn_new(int fd, const DilloUrl *url, SSL *ssl) +{ + int key; + + Conn_t *conn = dNew0(Conn_t, 1); + conn->fd = fd; + conn->url = a_Url_dup(url); + conn->ssl = ssl; + conn->connecting = TRUE; + + key = a_Klist_insert(&conn_list, conn); + + Tls_fd_map_add_entry(fd, key); + + return key; +} + +/* + * Let's monitor for TLS alerts. + */ +static void Tls_info_cb(const SSL *ssl, int where, int ret) +{ + if (where & SSL_CB_ALERT) { + const char *str = SSL_alert_desc_string_long(ret); + + if (strcmp(str, "close notify")) + MSG("TLS ALERT on %s: %s\n", (where & SSL_CB_READ) ? "read" : "write", + str); + } +} + +/* + * Load trusted certificates. + * This is like using SSL_CTX_load_verify_locations() but permitting more + * than one bundle and more than one directory. Due to the notoriously + * abysmal openssl documentation, this was worked out from reading discussion + * on the web and then reading openssl source to see what it normally does. + */ +static void Tls_load_certificates() +{ + /* curl-7.37.1 says that the following bundle locations are used on "Debian + * systems", "Redhat and Mandriva", "old(er) Redhat", "FreeBSD", and + * "OpenBSD", respectively -- and that the /etc/ssl/certs/ path is needed on + * "SUSE". No doubt it's all changed some over time, but this gives us + * something to work with. + */ + uint_t u; + char *userpath; + static const char *const ca_files[] = { + "/etc/ssl/certs/ca-certificates.crt", + "/etc/pki/tls/certs/ca-bundle.crt", + "/usr/share/ssl/certs/ca-bundle.crt", + "/usr/local/share/certs/ca-root.crt", + "/etc/ssl/cert.pem", + CA_CERTS_FILE + }; + + static const char *const ca_paths[] = { + "/etc/ssl/certs/", + CA_CERTS_DIR + }; + + X509_STORE *store = SSL_CTX_get_cert_store(ssl_context); + X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); + + for (u = 0; u < sizeof(ca_files) / sizeof(ca_files[0]); u++) { + if (*ca_files[u]) + X509_LOOKUP_load_file(lookup, ca_files[u], X509_FILETYPE_PEM); + } + + lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()); + for (u = 0; u < sizeof(ca_paths)/sizeof(ca_paths[0]); u++) { + if (*ca_paths[u]) + X509_LOOKUP_add_dir(lookup, ca_paths[u], X509_FILETYPE_PEM); + } + + userpath = dStrconcat(dGethomedir(), "/.dillo/certs/", NULL); + X509_LOOKUP_add_dir(lookup, userpath, X509_FILETYPE_PEM); + dFree(userpath); + + /* Clear out errors in the queue (file not found, etc.) */ + while(ERR_get_error()) + ; +} + +/* + * Initialize the OpenSSL library. + */ +void a_Tls_init(void) +{ + SSL_library_init(); + SSL_load_error_strings(); + if (RAND_status() != 1) { + /* The standard solution is to provide it with more entropy, but this + * involves knowing very well that you are doing exactly the right thing. + */ + MSG_ERR("Disabling HTTPS: Insufficient entropy for openssl.\n"); + return; + } + + /* Create SSL context */ + ssl_context = SSL_CTX_new(SSLv23_client_method()); + if (ssl_context == NULL) { + MSG_ERR("Disabling HTTPS: Error creating SSL context.\n"); + return; + } + + SSL_CTX_set_info_callback(ssl_context, Tls_info_cb); + + /* Don't want: eNULL, which has no encryption; aNULL, which has no + * authentication; LOW, which as of 2014 use 64 or 56-bit encryption; + * EXPORT40, which uses 40-bit encryption; RC4, for which methods were + * found in 2013 to defeat it somewhat too easily. + */ + SSL_CTX_set_cipher_list(ssl_context, + "ALL:!aNULL:!eNULL:!LOW:!EXPORT40:!RC4"); + + /* SSL2 has been known to be insecure forever, disabling SSL3 is in response + * to POODLE, and disabling compression is in response to CRIME. + */ + SSL_CTX_set_options(ssl_context, + SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_COMPRESSION); + + /* This lets us deal with self-signed certificates */ + SSL_CTX_set_verify(ssl_context, SSL_VERIFY_NONE, NULL); + + Tls_load_certificates(); + + fd_map = dList_new(20); + servers = dList_new(8); +} + +/* + * Save certificate with a hashed filename. + * Return: 0 on success, 1 on failure. + */ +static int Tls_save_certificate_home(X509 * cert) +{ + char buf[4096]; + + FILE * fp = NULL; + uint_t i = 0; + int ret = 1; + + /* Attempt to create .dillo/certs blindly - check later */ + snprintf(buf, 4096, "%s/.dillo/", dGethomedir()); + mkdir(buf, 01777); + snprintf(buf, 4096, "%s/.dillo/certs/", dGethomedir()); + mkdir(buf, 01777); + + do { + snprintf(buf, 4096, "%s/.dillo/certs/%lx.%u", + dGethomedir(), X509_subject_name_hash(cert), i); + + fp=fopen(buf, "r"); + if (fp == NULL){ + /* File name doesn't exist so we can use it safely */ + fp=fopen(buf, "w"); + if (fp == NULL){ + MSG("Unable to open cert save file in home dir\n"); + break; + } else { + PEM_write_X509(fp, cert); + fclose(fp); + MSG("Wrote certificate\n"); + ret = 0; + break; + } + } else { + fclose(fp); + } + i++; + /* Don't loop too many times - just give up */ + } while (i < 1024); + + return ret; +} + +/* + * Ordered comparison of servers. + */ +static int Tls_servers_cmp(const void *v1, const void *v2) +{ + const Server_t *s1 = (const Server_t *)v1, *s2 = (const Server_t *)v2; + int cmp = dStrAsciiCasecmp(s1->hostname, s2->hostname); + + if (!cmp) + cmp = s1->port - s2->port; + return cmp; +} +/* + * Ordered comparison of server with URL. + */ +static int Tls_servers_by_url_cmp(const void *v1, const void *v2) +{ + const Server_t *s = (const Server_t *)v1; + const DilloUrl *url = (const DilloUrl *)v2; + + int cmp = dStrAsciiCasecmp(s->hostname, URL_HOST(url)); + + if (!cmp) + cmp = s->port - URL_PORT(url); + return cmp; +} + +/* + * The purpose here is to permit a single initial connection to a server. + * Once we have the certificate, know whether we like it -- and whether the + * user accepts it -- HTTP can run through queued sockets as normal. + * + * Return: TLS_CONNECT_READY or TLS_CONNECT_NOT_YET or TLS_CONNECT_NEVER. + */ +int a_Tls_connect_ready(const DilloUrl *url) +{ + Server_t *s; + int ret = TLS_CONNECT_READY; + + dReturn_val_if_fail(ssl_context, TLS_CONNECT_NEVER); + + if ((s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp))) { + if (s->cert_status == CERT_STATUS_RECEIVING) + ret = TLS_CONNECT_NOT_YET; + else if (s->cert_status == CERT_STATUS_BAD) + ret = TLS_CONNECT_NEVER; + + if (s->cert_status == CERT_STATUS_NONE) + s->cert_status = CERT_STATUS_RECEIVING; + } else { + s = dNew(Server_t, 1); + + s->hostname = dStrdup(URL_HOST(url)); + s->port = URL_PORT(url); + s->cert_status = CERT_STATUS_RECEIVING; + dList_insert_sorted(servers, s, Tls_servers_cmp); + } + return ret; +} + +static int Tls_cert_status(const DilloUrl *url) +{ + Server_t *s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp); + + return s ? s->cert_status : CERT_STATUS_NONE; +} + +/* + * Did we find problems with the certificate, and did the user proceed to + * reject the connection? + */ +static int Tls_user_said_no(const DilloUrl *url) +{ + return Tls_cert_status(url) == CERT_STATUS_BAD; +} + +/* + * Did everything seem proper with the certificate -- no warnings to + * click through? + */ +int a_Tls_certificate_is_clean(const DilloUrl *url) +{ + return Tls_cert_status(url) == CERT_STATUS_CLEAN; +} + +/******************** BEGINNING OF STUFF DERIVED FROM wget-1.16.3 */ + +#define ASTERISK_EXCLUDES_DOT /* mandated by rfc2818 */ + +/* Return true is STRING (case-insensitively) matches PATTERN, false + otherwise. The recognized wildcard character is "*", which matches + any character in STRING except ".". Any number of the "*" wildcard + may be present in the pattern. + + This is used to match of hosts as indicated in rfc2818: "Names may + contain the wildcard character * which is considered to match any + single domain name component or component fragment. E.g., *.a.com + matches foo.a.com but not bar.foo.a.com. f*.com matches foo.com but + not bar.com [or foo.bar.com]." + + If the pattern contain no wildcards, pattern_match(a, b) is + equivalent to !strcasecmp(a, b). */ + +static bool_t pattern_match (const char *pattern, const char *string) +{ + + const char *p = pattern, *n = string; + char c; + for (; (c = tolower (*p++)) != '\0'; n++) + if (c == '*') + { + for (c = tolower (*p); c == '*'; c = tolower (*++p)) + ; + for (; *n != '\0'; n++) + if (tolower (*n) == c && pattern_match (p, n)) + return TRUE; +#ifdef ASTERISK_EXCLUDES_DOT + else if (*n == '.') + return FALSE; +#endif + return c == '\0'; + } + else + { + if (c != tolower (*n)) + return FALSE; + } + return *n == '\0'; +} + +/* + * Check that the certificate corresponds to the site it's presented for. + * + * Return TRUE if the hostname matched or the user indicated acceptance. + * FALSE on failure. + */ +static bool_t Tls_check_cert_hostname(X509 *cert, const char *host, + int *choice) +{ + dReturn_val_if_fail(cert && host, FALSE); + + char *msg; + GENERAL_NAMES *subjectAltNames; + bool_t success = TRUE, alt_name_checked = FALSE;; + char common_name[256]; + + /* Check that HOST matches the common name in the certificate. + #### The following remains to be done: + + - When matching against common names, it should loop over all + common names and choose the most specific one, i.e. the last + one, not the first one, which the current code picks. + + - Ensure that ASN1 strings from the certificate are encoded as + UTF-8 which can be meaningfully compared to HOST. */ + + subjectAltNames = X509_get_ext_d2i (cert, NID_subject_alt_name, NULL, NULL); + + if (subjectAltNames) + { + /* Test subject alternative names */ + + Dstr *err = dStr_new(""); + dStr_sprintf(err, "Hostname %s does not match any of certificate's " + "Subject Alternative Names: ", host); + + /* Do we want to check for dNSNAmes or ipAddresses (see RFC 2818)? + * Signal it by host_in_octet_string. */ + ASN1_OCTET_STRING *host_in_octet_string = a2i_IPADDRESS (host); + + int numaltnames = sk_GENERAL_NAME_num (subjectAltNames); + int i; + for (i=0; i < numaltnames; i++) + { + const GENERAL_NAME *name = + sk_GENERAL_NAME_value (subjectAltNames, i); + if (name) + { + if (host_in_octet_string) + { + if (name->type == GEN_IPADD) + { + /* Check for ipAddress */ + /* TODO: Should we convert between IPv4-mapped IPv6 + * addresses and IPv4 addresses? */ + alt_name_checked = TRUE; + if (!ASN1_STRING_cmp (host_in_octet_string, + name->d.iPAddress)) + break; + dStr_sprintfa(err, "%s ", name->d.iPAddress); + } + } + else if (name->type == GEN_DNS) + { + /* dNSName should be IA5String (i.e. ASCII), however who + * does trust CA? Convert it into UTF-8 for sure. */ + unsigned char *name_in_utf8 = NULL; + + /* Check for dNSName */ + alt_name_checked = TRUE; + + if (0 <= ASN1_STRING_to_UTF8 (&name_in_utf8, name->d.dNSName)) + { + /* Compare and check for NULL attack in ASN1_STRING */ + if (pattern_match ((char *)name_in_utf8, host) && + (strlen ((char *)name_in_utf8) == + (size_t)ASN1_STRING_length (name->d.dNSName))) + { + OPENSSL_free (name_in_utf8); + break; + } + dStr_sprintfa(err, "%s ", name_in_utf8); + OPENSSL_free (name_in_utf8); + } + } + } + } + sk_GENERAL_NAME_pop_free(subjectAltNames, GENERAL_NAME_free); + if (host_in_octet_string) + ASN1_OCTET_STRING_free(host_in_octet_string); + + if (alt_name_checked == TRUE && i >= numaltnames) + { + success = FALSE; + *choice = a_Dialog_choice("Dillo TLS security warning", + err->str, "Continue", "Cancel", NULL); + + switch (*choice){ + case 1: + success = TRUE; + break; + case 2: + break; + default: + break; + } + } + dStr_free(err, 1); + } + + if (alt_name_checked == FALSE) + { + /* Test commomName */ + X509_NAME *xname = X509_get_subject_name(cert); + common_name[0] = '\0'; + X509_NAME_get_text_by_NID (xname, NID_commonName, common_name, + sizeof (common_name)); + + if (!pattern_match (common_name, host)) + { + success = FALSE; + msg = dStrconcat("Certificate common name ", common_name, + " doesn't match requested host name ", host, NULL); + *choice = a_Dialog_choice("Dillo TLS security warning", + msg, "Continue", "Cancel", NULL); + dFree(msg); + + switch (*choice){ + case 1: + success = TRUE; + break; + case 2: + break; + default: + break; + } + } + else + { + /* We now determine the length of the ASN1 string. If it + * differs from common_name's length, then there is a \0 + * before the string terminates. This can be an instance of a + * null-prefix attack. + * + * https://www.blackhat.com/html/bh-usa-09/bh-usa-09-archives.html#Marlinspike + * */ + + int i = -1, j; + X509_NAME_ENTRY *xentry; + ASN1_STRING *sdata; + + if (xname) { + for (;;) + { + j = X509_NAME_get_index_by_NID (xname, NID_commonName, i); + if (j == -1) break; + i = j; + } + } + + xentry = X509_NAME_get_entry(xname,i); + sdata = X509_NAME_ENTRY_get_data(xentry); + if (strlen (common_name) != (size_t)ASN1_STRING_length (sdata)) + { + success = FALSE; + msg = dStrconcat("Certificate common name is invalid (contains a NUL " + "character). This may be an indication that the " + "host is not who it claims to be -- that is, not " + "the real ", host, NULL); + *choice = a_Dialog_choice("Dillo TLS security warning", + msg, "Continue", "Cancel", NULL); + dFree(msg); + + switch (*choice){ + case 1: + success = TRUE; + break; + case 2: + break; + default: + break; + } + } + } + } + return success; +} + +/******************** END OF STUFF DERIVED FROM wget-1.16.3 */ + +/* + * Get the certificate at the end of the chain, or NULL on failure. + * + * Rumor has it that the stack can be NULL if a connection has been reused + * and that the stack can then be reconstructed if necessary, but it doesn't + * sound like a case we'll encounter. + */ +static X509 *Tls_get_end_of_chain(SSL *ssl) +{ + STACK_OF(X509) *sk = SSL_get_peer_cert_chain(ssl); + + return sk ? sk_X509_value(sk, sk_X509_num(sk) - 1) : NULL; +} + +static void Tls_get_issuer_name(X509 *cert, char *buf, uint_t buflen) +{ + if (cert) { + X509_NAME_oneline(X509_get_issuer_name(cert), buf, buflen); + } else { + strncpy(buf, "(unknown)", buflen); + buf[buflen-1] = '\0'; + } +} + +static void Tls_get_expiration_str(X509 *cert, char *buf, uint_t buflen) +{ + ASN1_TIME *exp_date = X509_get_notAfter(cert); + BIO *b = BIO_new(BIO_s_mem()); + int rc = ASN1_TIME_print(b, exp_date); + + if (rc > 0) { + rc = BIO_gets(b, buf, buflen); + } + if (rc <= 0) { + strncpy(buf, "(unknown)", buflen); + buf[buflen-1] = '\0'; + } + BIO_free(b); +} + +/* + * Examine the certificate, and, if problems are detected, ask the user what + * to do. + * Return: -1 if connection should be canceled, or 0 if it should continue. + */ +static int Tls_examine_certificate(SSL *ssl, Server_t *srv,const char *host) +{ + X509 *remote_cert; + long st; + const uint_t buflen = 4096; + char buf[buflen], *cn, *msg; + int choice = -1, ret = -1; + char *title = dStrconcat("Dillo TLS security warning: ", host, NULL); + + remote_cert = SSL_get_peer_certificate(ssl); + if (remote_cert == NULL){ + /* Inform user that remote system cannot be trusted */ + choice = a_Dialog_choice(title, + "The remote system is not presenting a certificate. " + "This site cannot be trusted. Sending data is not safe.", + "Continue", "Cancel", NULL); + + /* Abort on anything but "Continue" */ + if (choice == 1){ + ret = 0; + } + + } else if (Tls_check_cert_hostname(remote_cert, host, &choice)) { + /* Figure out if (and why) the remote system can't be trusted */ + st = SSL_get_verify_result(ssl); + switch (st) { + case X509_V_OK: + ret = 0; + break; + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + /* Either self signed and untrusted */ + /* Extract CN from certificate name information */ + if ((cn = strstr(remote_cert->name, "/CN=")) == NULL) { + strcpy(buf, "(no CN given)"); + } else { + char *cn_end; + + cn += 4; + + if ((cn_end = strstr(cn, "/")) == NULL ) + cn_end = cn + strlen(cn); + + strncpy(buf, cn, (size_t) (cn_end - cn)); + buf[cn_end - cn] = '\0'; + } + msg = dStrconcat("The remote certificate is self-signed and " + "untrusted. For address: ", buf, NULL); + choice = a_Dialog_choice(title, + msg, "Continue", "Cancel", "Save Certificate", NULL); + dFree(msg); + + switch (choice){ + case 1: + ret = 0; + break; + case 2: + break; + case 3: + /* Save certificate to a file here and recheck the chain */ + /* Potential security problems because we are writing + * to the filesystem */ + Tls_save_certificate_home(remote_cert); + ret = 1; + break; + default: + break; + } + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + choice = a_Dialog_choice(title, + "The issuer for the remote certificate cannot be found. " + "The authenticity of the remote certificate cannot be trusted.", + "Continue", "Cancel", NULL); + + if (choice == 1) { + ret = 0; + } + break; + + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + case X509_V_ERR_CRL_SIGNATURE_FAILURE: + choice = a_Dialog_choice(title, + "The remote certificate signature could not be read " + "or is invalid and should not be trusted", + "Continue", "Cancel", NULL); + + if (choice == 1) { + ret = 0; + } + break; + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_CRL_NOT_YET_VALID: + choice = a_Dialog_choice(title, + "Part of the remote certificate is not yet valid. " + "Certificates usually have a range of dates over which " + "they are to be considered valid, and the certificate " + "presented has a starting validity after today's date " + "You should be cautious about using this site", + "Continue", "Cancel", NULL); + + if (choice == 1) { + ret = 0; + } + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_CRL_HAS_EXPIRED: + Tls_get_expiration_str(remote_cert, buf, buflen); + msg = dStrconcat("The remote certificate expired on: ", buf, + ". This site can no longer be trusted.", NULL); + + choice = a_Dialog_choice(title, msg, "Continue", "Cancel", NULL); + if (choice == 1) { + ret = 0; + } + dFree(msg); + break; + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: + case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: + choice = a_Dialog_choice(title, + "There was an error in the certificate presented. " + "Some of the certificate data was improperly formatted " + "making it impossible to determine if the certificate " + "is valid. You should not trust this certificate.", + "Continue", "Cancel", NULL); + if (choice == 1) { + ret = 0; + } + break; + case X509_V_ERR_INVALID_CA: + case X509_V_ERR_INVALID_PURPOSE: + case X509_V_ERR_CERT_UNTRUSTED: + case X509_V_ERR_CERT_REJECTED: + case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: + choice = a_Dialog_choice(title, + "One of the certificates in the chain is being used " + "incorrectly (possibly due to configuration problems " + "with the remote system. The connection should not " + "be trusted", + "Continue", "Cancel", NULL); + if (choice == 1) { + ret = 0; + } + break; + case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: + case X509_V_ERR_AKID_SKID_MISMATCH: + case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: + choice = a_Dialog_choice(title, + "Some of the information presented by the remote system " + "does not match other information presented. " + "This may be an attempt to eavesdrop on communications", + "Continue", "Cancel", NULL); + if (choice == 1) { + ret = 0; + } + break; + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + Tls_get_issuer_name(Tls_get_end_of_chain(ssl), buf, buflen); + msg = dStrconcat("Certificate chain led to a self-signed certificate " + "instead of a trusted root. Name: ", buf , NULL); + choice = a_Dialog_choice(title, msg, "Continue", "Cancel", NULL); + if (choice == 1) { + ret = 0; + } + dFree(msg); + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + Tls_get_issuer_name(Tls_get_end_of_chain(ssl), buf, buflen); + msg = dStrconcat("The issuer certificate of an untrusted certificate " + "cannot be found. Issuer: ", buf, NULL); + choice = a_Dialog_choice(title, msg, "Continue", "Cancel", NULL); + if (choice == 1) { + ret = 0; + } + dFree(msg); + break; + default: /* Need to add more options later */ + snprintf(buf, 80, + "The remote certificate cannot be verified (code %ld)", st); + choice = a_Dialog_choice(title, + buf, "Continue", "Cancel", NULL); + /* abort on anything but "Continue" */ + if (choice == 1){ + ret = 0; + } + } + X509_free(remote_cert); + remote_cert = 0; + } + dFree(title); + + if (choice == 2) + srv->cert_status = CERT_STATUS_BAD; + else if (choice == -1) + srv->cert_status = CERT_STATUS_CLEAN; + else + srv->cert_status = CERT_STATUS_USER_ACCEPTED; + + return ret; +} + +/* + * If the connection was closed before we got the certificate, we need to + * reset state so that we'll try again. + */ +void a_Tls_reset_server_state(const DilloUrl *url) +{ + if (servers) { + Server_t *s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp); + + if (s && s->cert_status == CERT_STATUS_RECEIVING) + s->cert_status = CERT_STATUS_NONE; + } +} + +/* + * Close an open TLS connection. + */ +static void Tls_close_by_key(int connkey) +{ + Conn_t *c; + + if ((c = a_Klist_get_data(conn_list, connkey))) { + a_Tls_reset_server_state(c->url); + if (c->connecting) { + a_IOwatch_remove_fd(c->fd, -1); + dClose(c->fd); + } + SSL_shutdown(c->ssl); + SSL_free(c->ssl); + + a_Url_free(c->url); + Tls_fd_map_remove_entry(c->fd); + a_Klist_remove(conn_list, connkey); + dFree(c); + } +} + +static void Tls_print_cert_chain(SSL *ssl) +{ + STACK_OF(X509) *sk = SSL_get_peer_cert_chain(ssl); + + if (sk) { + const uint_t buflen = 4096; + char buf[buflen]; + int rc, i, n = sk_X509_num(sk); + X509 *cert = NULL; + EVP_PKEY *public_key; + int key_type, key_bits; + const char *type_str; + BIO *b; + + for (i = 0; i < n; i++) { + cert = sk_X509_value(sk, i); + public_key = X509_get_pubkey(cert); + + /* We are trying to find a way to get the hash function used + * with a certificate. This way, which is not very pleasant, puts + * a string such as "sha256WithRSAEncryption" in our buffer and we + * then trim off the "With..." part. + */ + b = BIO_new(BIO_s_mem()); + rc = i2a_ASN1_OBJECT(b, cert->sig_alg->algorithm); + + if (rc > 0) { + rc = BIO_gets(b, buf, buflen); + } + if (rc <= 0) { + strcpy(buf, "(unknown)"); + buf[buflen-1] = '\0'; + } else { + char *s = strstr(buf, "With"); + + if (s) { + *s = '\0'; + if (!strcmp(buf, "sha1")) { + MSG_WARN("In 2015, browsers have begun to deprecate SHA1 " + "certificates.\n"); + } else if (!strncmp(buf, "md", 2)) { + MSG_ERR("Browsers stopped accepting MD5 certificates around " + "2012.\n"); + } + } + } + BIO_free(b); + MSG("%s ", buf); + + + key_type = EVP_PKEY_type(public_key->type); + type_str = key_type == EVP_PKEY_RSA ? "RSA" : + key_type == EVP_PKEY_DSA ? "DSA" : + key_type == EVP_PKEY_DH ? "DH" : + key_type == EVP_PKEY_EC ? "EC" : "???"; + key_bits = EVP_PKEY_bits(public_key); + X509_NAME_oneline(X509_get_subject_name(cert), buf, buflen); + buf[buflen-1] = '\0'; + MSG("%d-bit %s: %s\n", key_bits, type_str, buf); + EVP_PKEY_free(public_key); + + if (key_type == EVP_PKEY_RSA && key_bits <= 1024) { + /* TODO: Gather warnings into one popup. */ + MSG_WARN("In 2014/5, browsers have been deprecating 1024-bit RSA " + "keys.\n"); + } + } + + if (cert) { + X509_NAME_oneline(X509_get_issuer_name(cert), buf, buflen); + buf[buflen-1] = '\0'; + MSG("root: %s\n", buf); + } + } +} + +/* + * Connect, set a callback if it's still not completed. If completed, check + * the certificate and report back to http. + */ +static void Tls_connect(int fd, int connkey) +{ + int ret; + bool_t ongoing = FALSE, failed = TRUE; + Conn_t *conn; + + if (!(conn = a_Klist_get_data(conn_list, connkey))) { + MSG("Tls_connect: conn for fd %d not valid\n", fd); + return; + } + + assert(!ERR_get_error()); + + ret = SSL_connect(conn->ssl); + + if (ret <= 0) { + int err1_ret = SSL_get_error(conn->ssl, ret); + if (err1_ret == SSL_ERROR_WANT_READ || + err1_ret == SSL_ERROR_WANT_WRITE) { + int want = err1_ret == SSL_ERROR_WANT_READ ? DIO_READ : DIO_WRITE; + + _MSG("iowatching fd %d for tls -- want %s\n", fd, + err1_ret == SSL_ERROR_WANT_READ ? "read" : "write"); + a_IOwatch_remove_fd(fd, -1); + a_IOwatch_add_fd(fd, want, Tls_connect_cb, INT2VOIDP(connkey)); + ongoing = TRUE; + failed = FALSE; + } else if (err1_ret == SSL_ERROR_SYSCALL || err1_ret == SSL_ERROR_SSL) { + unsigned long err2_ret = ERR_get_error(); + + if (err2_ret) { + do { + MSG("SSL_connect() failed: %s\n", + ERR_error_string(err2_ret, NULL)); + } while ((err2_ret = ERR_get_error())); + } else { + /* nothing in the error queue */ + if (ret == 0) { + MSG("TLS connect error: \"an EOF was observed that violates " + "the protocol\"\n"); + /* + * I presume we took too long on our side and the server grew + * impatient. + */ + } else if (ret == -1) { + MSG("TLS connect error: %s\n", dStrerror(errno)); + + /* If the following can happen, I'll add code to handle it, but + * I don't want to add code blindly if it isn't getting used + */ + assert(errno != EAGAIN && errno != EINTR); + } else { + MSG_ERR("According to the man page for SSL_get_error(), this " + "was not a possibility (ret %d).\n", ret); + } + } + } else { + MSG("SSL_get_error() returned %d on a connect.\n", err1_ret); + } + } else { + Server_t *srv = dList_find_sorted(servers, conn->url, + Tls_servers_by_url_cmp); + + if (srv->cert_status == CERT_STATUS_RECEIVING) { + /* Making first connection with the server. Show some information. */ + SSL *ssl = conn->ssl; + const char *version = SSL_get_version(ssl); + const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); + + MSG("%s: %s, cipher %s\n", URL_AUTHORITY(conn->url), version, + SSL_CIPHER_get_name(cipher)); + Tls_print_cert_chain(ssl); + } + + if (srv->cert_status == CERT_STATUS_USER_ACCEPTED || + (Tls_examine_certificate(conn->ssl, srv, URL_HOST(conn->url))!=-1)) { + failed = FALSE; + } + } + + /* + * If there were problems with the certificate, the connection may have + * been closed by the server if the user responded too slowly to a popup. + */ + + if (!ongoing) { + if (a_Klist_get_data(conn_list, connkey)) { + conn->connecting = FALSE; + if (failed) { + Tls_close_by_key(connkey); + } + a_IOwatch_remove_fd(fd, DIO_READ|DIO_WRITE); + a_Http_connect_done(fd, failed ? FALSE : TRUE); + } else { + MSG("Connection disappeared. Too long with a popup popped up?\n"); + } + } +} + +static void Tls_connect_cb(int fd, void *vconnkey) +{ + Tls_connect(fd, VOIDP2INT(vconnkey)); +} + +/* + * Perform the TLS handshake on an open socket. + */ +void a_Tls_handshake(int fd, const DilloUrl *url) +{ + SSL *ssl; + bool_t success = TRUE; + int connkey = -1; + + if (!ssl_context) + success = FALSE; + + if (success && Tls_user_said_no(url)) { + success = FALSE; + } + + assert(!ERR_get_error()); + + if (success && !(ssl = SSL_new(ssl_context))) { + unsigned long err_ret = ERR_get_error(); + do { + MSG("SSL_new() failed: %s\n", ERR_error_string(err_ret, NULL)); + } while ((err_ret = ERR_get_error())); + success = FALSE; + } + + /* assign TLS connection to this file descriptor */ + if (success && !SSL_set_fd(ssl, fd)) { + unsigned long err_ret = ERR_get_error(); + do { + MSG("SSL_set_fd() failed: %s\n", ERR_error_string(err_ret, NULL)); + } while ((err_ret = ERR_get_error())); + success = FALSE; + } + + if (success) + connkey = Tls_conn_new(fd, url, ssl); + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + /* Server Name Indication. From the openssl changelog, it looks like this + * came along in 2010. + */ + if (success && !a_Url_host_is_ip(URL_HOST(url))) + SSL_set_tlsext_host_name(ssl, URL_HOST(url)); +#endif + + if (!success) { + a_Tls_reset_server_state(url); + a_Http_connect_done(fd, success); + } else { + Tls_connect(fd, connkey); + } +} + +/* + * Read data from an open TLS connection. + */ +int a_Tls_read(void *conn, void *buf, size_t len) +{ + Conn_t *c = (Conn_t*)conn; + return SSL_read(c->ssl, buf, len); +} + +/* + * Write data to an open TLS connection. + */ +int a_Tls_write(void *conn, void *buf, size_t len) +{ + Conn_t *c = (Conn_t*)conn; + return SSL_write(c->ssl, buf, len); +} + +void a_Tls_close_by_fd(int fd) +{ + FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd), + Tls_fd_map_cmp); + + if (fme) { + Tls_close_by_key(fme->connkey); + } +} + +static void Tls_servers_freeall() +{ + if (servers) { + Server_t *s; + int i, n = dList_length(servers); + + for (i = 0; i < n; i++) { + s = (Server_t *) dList_nth_data(servers, i); + dFree(s->hostname); + dFree(s); + } + dList_free(servers); + } +} + +static void Tls_fd_map_remove_all() +{ + if (fd_map) { + FdMapEntry_t *fme; + int i, n = dList_length(fd_map); + + for (i = 0; i < n; i++) { + fme = (FdMapEntry_t *) dList_nth_data(fd_map, i); + dFree(fme); + } + dList_free(fd_map); + } +} + +/* + * Clean up the OpenSSL library + */ +void a_Tls_freeall(void) +{ + if (ssl_context) + SSL_CTX_free(ssl_context); + Tls_fd_map_remove_all(); + Tls_servers_freeall(); +} + +#endif /* ENABLE_SSL */ diff --git a/src/IO/tls.h b/src/IO/tls.h new file mode 100644 index 00000000..9bc89de5 --- /dev/null +++ b/src/IO/tls.h @@ -0,0 +1,49 @@ +#ifndef __TLS_H__ +#define __TLS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../url.h" + +#define TLS_CONNECT_NEVER -1 +#define TLS_CONNECT_NOT_YET 0 +#define TLS_CONNECT_READY 1 + +void a_Tls_init(); + + +#ifdef ENABLE_SSL +int a_Tls_certificate_is_clean(const DilloUrl *url); +int a_Tls_connect_ready(const DilloUrl *url); +void a_Tls_reset_server_state(const DilloUrl *url); + +/* Use to initiate a TLS connection. */ +void a_Tls_handshake(int fd, const DilloUrl *url); + +void *a_Tls_connection(int fd); + +void a_Tls_freeall(); + +void a_Tls_close_by_fd(int fd); +int a_Tls_read(void *conn, void *buf, size_t len); +int a_Tls_write(void *conn, void *buf, size_t len); +#else + +#define a_Tls_certificate_is_clean(host) 0 +#define a_Tls_connect_ready(url) TLS_CONNECT_NEVER +#define a_Tls_reset_server_state(url) ; +#define a_Tls_handshake(fd, url) ; +#define a_Tls_connection(fd) NULL +#define a_Tls_freeall() ; +#define a_Tls_close_by_fd(fd) ; +#define a_Tls_read(conn, buf, len) 0 +#define a_Tls_write(conn, buf, len) 0 +#endif +#ifdef __cplusplus +} +#endif + +#endif /* __TLS_H__ */ + diff --git a/src/Makefile.am b/src/Makefile.am index 65a42cad..425f8614 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,7 +2,9 @@ AM_CPPFLAGS= \ -I$(top_srcdir) \ -DDILLO_SYSCONF='"$(sysconfdir)/"' \ -DDILLO_DOCDIR='"$(docdir)/"' \ + -DCUR_WORKING_DIR='"@BASE_CUR_WORKING_DIR@/src"' \ @LIBJPEG_CPPFLAGS@ + AM_CFLAGS = @LIBPNG_CFLAGS@ AM_CXXFLAGS = @LIBPNG_CFLAGS@ @LIBFLTK_CXXFLAGS@ @@ -19,7 +21,7 @@ dillo_LDADD = \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ @LIBJPEG_LIBS@ @LIBPNG_LIBS@ @LIBFLTK_LIBS@ @LIBZ_LIBS@ \ - @LIBICONV_LIBS@ @LIBPTHREAD_LIBS@ @LIBX11_LIBS@ + @LIBICONV_LIBS@ @LIBPTHREAD_LIBS@ @LIBX11_LIBS@ @LIBSSL_LIBS@ dillo_SOURCES = \ dillo.cc \ @@ -35,6 +37,8 @@ dillo_SOURCES = \ bw.c \ cookies.c \ cookies.h \ + hsts.c \ + hsts.h \ auth.c \ auth.h \ md5.c \ @@ -96,6 +100,7 @@ dillo_SOURCES = \ plain.cc \ html.cc \ html.hh \ + html_charrefs.h \ html_common.hh \ form.cc \ form.hh \ @@ -125,5 +130,5 @@ dillo_SOURCES = \ xembed.cc \ xembed.hh -dist_sysconf_DATA = domainrc keysrc +dist_sysconf_DATA = domainrc keysrc hsts_preload EXTRA_DIST = chg srch diff --git a/src/cache.c b/src/cache.c index a2ad357b..b082ef89 100644 --- a/src/cache.c +++ b/src/cache.c @@ -26,6 +26,7 @@ #include "dicache.h" #include "nav.h" #include "cookies.h" +#include "hsts.h" #include "misc.h" #include "capi.h" #include "decode.h" @@ -55,7 +56,7 @@ typedef struct { Dstr *Data; /* Pointer to raw data */ Dstr *UTF8Data; /* Data after charset translation */ int DataRefcount; /* Reference count */ - Decode *TransferDecoder; /* Transfer decoder (e.g., chunked) */ + DecodeTransfer *TransferDecoder; /* Transfer decoder (e.g., chunked) */ Decode *ContentDecoder; /* Data decoder (e.g., gzip) */ Decode *CharsetDecoder; /* Translates text to UTF-8 encoding */ int ExpectedSize; /* Goal size of the HTTP transfer (0 if unknown)*/ @@ -205,7 +206,7 @@ static void Cache_entry_init(CacheEntry_t *NewEntry, const DilloUrl *Url) NewEntry->CharsetDecoder = NULL; NewEntry->ExpectedSize = 0; NewEntry->TransferSize = 0; - NewEntry->Flags = CA_IsEmpty; + NewEntry->Flags = CA_IsEmpty | CA_KeepAlive; } /* @@ -308,7 +309,7 @@ static void Cache_entry_free(CacheEntry_t *entry) if (entry->CharsetDecoder) a_Decode_free(entry->CharsetDecoder); if (entry->TransferDecoder) - a_Decode_free(entry->TransferDecoder); + a_Decode_transfer_free(entry->TransferDecoder); if (entry->ContentDecoder) a_Decode_free(entry->ContentDecoder); dFree(entry); @@ -498,7 +499,7 @@ const char *a_Cache_set_content_type(const DilloUrl *url, const char *ctype, _MSG("a_Cache_set_content_type {%s} {%s}\n", ctype, URL_STR(url)); curr = Cache_current_content_type(entry); - if (entry->TypeMeta || (*from == 'h' && entry->TypeHdr) ) { + if (entry->TypeMeta || (*from == 'h' && entry->TypeHdr) ) { /* Type is already been set. Do nothing. * BTW, META overrides TypeHdr */ } else { @@ -652,7 +653,8 @@ 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; + bool_t server1point0 = !strncmp(entry->Header->str, "HTTP/1.0", 8); + char *Length, *Type, *location_str, *encoding, *connection, *hsts; #ifndef DISABLE_COOKIES Dlist *Cookies; #endif @@ -709,6 +711,25 @@ static void Cache_parse_header(CacheEntry_t *entry) dList_free(warnings); } + if (server1point0) + entry->Flags &= ~CA_KeepAlive; + + if ((connection = Cache_parse_field(header, "Connection"))) { + if (!dStrAsciiCasecmp(connection, "close")) + entry->Flags &= ~CA_KeepAlive; + else if (server1point0 && !dStrAsciiCasecmp(connection, "keep-alive")) + entry->Flags |= CA_KeepAlive; + dFree(connection); + } + + if (prefs.http_strict_transport_security && + !dStrAsciiCasecmp(URL_SCHEME(entry->Url), "https") && + !a_Url_host_is_ip(URL_HOST(entry->Url)) && + (hsts = Cache_parse_field(header, "Strict-Transport-Security"))) { + a_Hsts_set(hsts, entry->Url); + dFree(hsts); + } + /* * Get Transfer-Encoding and initialize decoder */ @@ -824,6 +845,43 @@ static int Cache_get_header(CacheEntry_t *entry, return 0; } +static void Cache_finish_msg(CacheEntry_t *entry) +{ + if (entry->Flags & CA_GotData) { + /* already finished */ + return; + } + + 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 at\n" + "%s\n", URL_STR_(entry->Url)); + MSG("Expected size: %d, Transfer size: %d\n", + entry->ExpectedSize, entry->TransferSize); + } + entry->Flags |= CA_GotData; + entry->Flags &= ~CA_Stopped; /* it may catch up! */ + if (entry->TransferDecoder) { + a_Decode_transfer_free(entry->TransferDecoder); + entry->TransferDecoder = NULL; + } + if (entry->ContentDecoder) { + a_Decode_free(entry->ContentDecoder); + entry->ContentDecoder = NULL; + } + dStr_fit(entry->Data); /* fit buffer size! */ + + if ((entry = Cache_process_queue(entry))) { + if (entry->Flags & CA_GotHeader) { + Cache_unref_data(entry); + } + } +} + /* * Receive new data, update the reception buffer (for next read), update the * cache, and service the client queue. @@ -832,16 +890,17 @@ static int Cache_get_header(CacheEntry_t *entry, * 'Op' is the operation to perform * 'VPtr' is a (void) pointer to the IO control structure */ -void a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, - const DilloUrl *Url) +bool_t a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, + const DilloUrl *Url) { int offset, len; const char *str; Dstr *dstr1, *dstr2, *dstr3; + bool_t done = FALSE; CacheEntry_t *entry = Cache_entry_search(Url); /* Assert a valid entry (not aborted) */ - dReturn_if_fail (entry != NULL); + dReturn_val_if_fail (entry != NULL, FALSE); _MSG("__a_Cache_process_dbuf__\n"); @@ -865,7 +924,8 @@ void a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, /* Decode arrived data (<= 3 stages) */ if (entry->TransferDecoder) { - dstr1 = a_Decode_process(entry->TransferDecoder, str, len); + dstr1 = a_Decode_transfer_process(entry->TransferDecoder, str,len); + done = a_Decode_transfer_finished(entry->TransferDecoder); str = dstr1->str; len = dstr1->len; } @@ -886,51 +946,37 @@ void a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, if (entry->Data->len) entry->Flags &= ~CA_IsEmpty; + if ((entry->Flags & CA_GotLength) && + (entry->TransferSize >= entry->ExpectedSize)) { + done = TRUE; + } + if (!(entry->Flags & CA_KeepAlive)) { + /* Let IOClose finish it later */ + done = FALSE; + } + entry = Cache_process_queue(entry); + + if (entry && done) + Cache_finish_msg(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 at\n" - "%s\n", URL_STR_(entry->Url)); - MSG("Expected size: %d, Transfer size: %d\n", - entry->ExpectedSize, entry->TransferSize); - } - if (!entry->TransferSize && !(entry->Flags & CA_Redirect) && - (entry->Flags & WEB_RootUrl)) { - char *eol = strchr(entry->Header->str, '\n'); - if (eol) { - char *status_line = dStrndup(entry->Header->str, - eol - entry->Header->str); - MSG_HTTP("Body was empty. Server sent status: %s\n", status_line); - dFree(status_line); - } - } - entry->Flags |= CA_GotData; - entry->Flags &= ~CA_Stopped; /* it may catch up! */ - if (entry->TransferDecoder) { - a_Decode_free(entry->TransferDecoder); - entry->TransferDecoder = NULL; - } - if (entry->ContentDecoder) { - a_Decode_free(entry->ContentDecoder); - entry->ContentDecoder = NULL; - } - dStr_fit(entry->Data); /* fit buffer size! */ + Cache_finish_msg(entry); + } else if (Op == IOAbort) { + int i; + CacheClient_t *Client; - if ((entry = Cache_process_queue(entry))) { - if (entry->Flags & CA_GotHeader) { - Cache_unref_data(entry); + for (i = 0; (Client = dList_nth_data(ClientQueue, i)); ++i) { + if (Client->Url == entry->Url) { + DilloWeb *web = (DilloWeb *)Client->Web; + + a_Bw_remove_client(web->bw, Client->Key); + Cache_client_dequeue(Client); + --i; /* Keep the index value in the next iteration */ } } - } else if (Op == IOAbort) { - /* unused */ - MSG("a_Cache_process_dbuf Op = IOAbort; not implemented!\n"); } + return done; } /* diff --git a/src/cache.h b/src/cache.h index c39e4600..f3b064f2 100644 --- a/src/cache.h +++ b/src/cache.h @@ -33,6 +33,7 @@ extern "C" { #define CA_InternalUrl 0x800 /* URL content is generated by dillo */ #define CA_HugeFile 0x1000 /* URL content is too big */ #define CA_IsEmpty 0x2000 /* True until a byte of content arrives */ +#define CA_KeepAlive 0x4000 typedef struct CacheClient CacheClient_t; @@ -67,7 +68,7 @@ const char *a_Cache_set_content_type(const DilloUrl *url, const char *ctype, 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, +bool_t a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, const DilloUrl *Url); int a_Cache_download_enabled(const DilloUrl *url); void a_Cache_entry_remove_by_url(DilloUrl *url); @@ -16,7 +16,9 @@ */ #include <string.h> +#include <errno.h> +#include "config.h" #include "msg.h" #include "capi.h" #include "IO/IO.h" /* for IORead &friends */ @@ -268,6 +270,7 @@ static int Capi_url_uses_dpi(DilloUrl *url, char **server_ptr) Dstr *tmp; if ((dStrnAsciiCasecmp(url_str, "http:", 5) == 0) || + (dStrnAsciiCasecmp(url_str, "https:", 6) == 0) || (dStrnAsciiCasecmp(url_str, "about:", 6) == 0)) { /* URL doesn't use dpi (server = NULL) */ } else if (dStrnAsciiCasecmp(url_str, "dpi:/", 5) == 0) { @@ -298,39 +301,7 @@ static char *Capi_dpi_build_cmd(DilloWeb *web, char *server) { char *cmd; - 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, web->requester, - web->flags, FALSE); - - if ((uint_t) http_query->len > strlen(http_query->str)) { - /* Can't handle NULLs embedded in query data */ - MSG_ERR("HTTPS query truncated!\n"); - } - - /* BUG: WORKAROUND: request to only check the root URL's certificate. - * This avoids the dialog bombing that stems from loading multiple - * https images/resources in a single page. A proper fix would take - * either to implement the https-dpi as a server (with state), - * or to move back https handling into dillo. */ - 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 check_cert=%s", - "open_url", proxy_urlstr, - proxy_connect, URL_STR(web->url), - http_query->str, - (web->flags & WEB_RootUrl) ? "true" : "false"); - } else { - cmd = a_Dpip_build_cmd("cmd=%s url=%s query=%s check_cert=%s", - "open_url", URL_STR(web->url),http_query->str, - (web->flags & WEB_RootUrl) ? "true" : "false"); - } - dFree(proxy_connect); - dStr_free(http_query, 1); - - } else if (strcmp(server, "downloads") == 0) { + if (strcmp(server, "downloads") == 0) { /* let the downloads server get it */ cmd = a_Dpip_build_cmd("cmd=%s url=%s destination=%s", "download", URL_STR(web->url), web->filename); @@ -345,7 +316,7 @@ 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) +static void Capi_dpi_send_source(BrowserWindow *bw, DilloUrl *url) { char *p, *buf, *cmd, size_str[32], *server="vsource"; int buf_size; @@ -385,81 +356,92 @@ int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData) int safe = 0, ret = 0, use_cache = 0; /* web->requester is NULL if the action is initiated by user */ - if (!(a_Capi_get_flags(web->url) & CAPI_IsCached || - web->requester == NULL || - a_Domain_permit(web->requester, web->url))) { - return 0; - } - - /* reload test */ - reload = (!(a_Capi_get_flags(web->url) & CAPI_IsCached) || - (URL_FLAGS(web->url) & URL_E2EQuery)); - - 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_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); + if (a_Capi_get_flags(web->url) & CAPI_IsCached || + web->requester == NULL || + a_Domain_permit(web->requester, web->url)) { + + /* reload test */ + reload = (!(a_Capi_get_flags(web->url) & CAPI_IsCached) || + (URL_FLAGS(web->url) & URL_E2EQuery)); + + 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_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: %s.\n", + web->filename, dStrerror(errno)); + } } + } 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 { + MSG_WARN("Ignoring download request for '%s': " + "not in cache and not downloadable.\n", + URL_STR(web->url)); } - } 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 { - MSG_WARN("Ignoring download request for '%s': " - "not in cache and not downloadable.\n", - URL_STR(web->url)); - } - - } else if (Capi_url_uses_dpi(web->url, &server)) { - /* dpi request */ - if ((safe = a_Capi_dpi_verify_request(web->bw, web->url))) { - if (dStrAsciiCasecmp(scheme, "dpi") == 0) { - if (strcmp(server, "vsource") == 0) { - /* allow "view source" reload upon user request */ - } else { - /* make the other "dpi:/" prefixed urls always reload. */ - a_Url_set_flags(web->url, URL_FLAGS(web->url) | URL_E2EQuery); - reload = 1; + + } else if (Capi_url_uses_dpi(web->url, &server)) { + /* dpi request */ + if ((safe = a_Capi_dpi_verify_request(web->bw, web->url))) { + if (dStrAsciiCasecmp(scheme, "dpi") == 0) { + if (strcmp(server, "vsource") == 0) { + /* allow "view source" reload upon user request */ + } 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; } + dFree(server); + + } else if (!dStrAsciiCasecmp(scheme, "http") || + !dStrAsciiCasecmp(scheme, "https")) { + /* http request */ + +#ifndef ENABLE_SSL + if (!dStrAsciiCasecmp(scheme, "https")) { + if (web->flags & WEB_RootUrl) + a_UIcmd_set_msg(web->bw, + "HTTPS was disabled at compilation time"); + a_Web_free(web); + return 0; + } +#endif 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); - } + /* 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 immediately. 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; - } - dFree(server); - - } else if (!dStrAsciiCasecmp(scheme, "http")) { - /* http request */ - if (reload) { - a_Capi_conn_abort_by_url(web->url); - /* create a new connection and start the CCC operations */ - conn = Capi_conn_new(web->url, web->bw, "http", "none"); - /* start the reception branch before the query one because the DNS - * may callback immediately. 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; - } else if (!dStrAsciiCasecmp(scheme, "about")) { - /* internal request */ - use_cache = 1; + } else if (!dStrAsciiCasecmp(scheme, "about")) { + /* internal request */ + use_cache = 1; + } } if (use_cache) { @@ -632,7 +614,8 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, Capi_conn_ref(conn); Info->LocalKey = conn; conn->InfoSend = Info; - if (strcmp(conn->server, "http") == 0) { + if (strcmp(conn->server, "http") == 0 || + strcmp(conn->server, "https") == 0) { a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Http_ccc, 1, 1); a_Chain_bcb(OpStart, Info, Data2, NULL); } else { @@ -659,7 +642,7 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, dFree(Info); break; default: - MSG_WARN("Unused CCC\n"); + MSG_WARN("Unused CCC Capi 1B\n"); break; } } else { /* 1 FWD */ @@ -681,6 +664,7 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, case OpAbort: conn = Info->LocalKey; conn->InfoSend = NULL; + a_Cache_process_dbuf(IOAbort, NULL, 0, conn->url); if (Data2) { if (!strcmp(Data2, "DpidERROR")) { a_UIcmd_set_msg(conn->bw, @@ -699,7 +683,7 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, dFree(Info); break; default: - MSG_WARN("Unused CCC\n"); + MSG_WARN("Unused CCC Capi 1F\n"); break; } } @@ -714,7 +698,10 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, Capi_conn_ref(conn); Info->LocalKey = conn; conn->InfoRecv = Info; - a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 2, 2); + if (strcmp(conn->server, "http") == 0) + a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Http_ccc, 2, 2); + else + a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 2, 2); a_Chain_bcb(OpStart, Info, NULL, Data2); break; case OpSend: @@ -733,7 +720,7 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, dFree(Info); break; default: - MSG_WARN("Unused CCC\n"); + MSG_WARN("Unused CCC Capi 2B\n"); break; } } else { /* 2 FWD */ @@ -744,7 +731,15 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, if (strcmp(Data2, "send_page_2eof") == 0) { /* Data1 = dbuf */ DataBuf *dbuf = Data1; - a_Cache_process_dbuf(IORead, dbuf->Buf, dbuf->Size, conn->url); + bool_t finished = a_Cache_process_dbuf(IORead, dbuf->Buf, + dbuf->Size, conn->url); + if (finished && Capi_conn_valid(conn) && conn->InfoRecv) { + /* If we have a persistent connection where cache tells us + * that we've received the full response, and cache didn't + * trigger an abort and tear everything down, tell upstream. + */ + a_Chain_bcb(OpSend, conn->InfoRecv, NULL, "reply_complete"); + } } else if (strcmp(Data2, "send_status_message") == 0) { a_UIcmd_set_msg(conn->bw, "%s", Data1); } else if (strcmp(Data2, "chat") == 0) { @@ -775,8 +770,24 @@ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, Capi_conn_unref(conn); dFree(Info); break; + case OpAbort: + conn = Info->LocalKey; + conn->InfoRecv = NULL; + a_Cache_process_dbuf(IOAbort, NULL, 0, conn->url); + if (Data2) { + if (!strcmp(Data2, "Both") && conn->InfoSend) { + /* abort the other branch too */ + a_Capi_ccc(OpAbort, 1, BCK, conn->InfoSend, NULL, NULL); + } + } + /* if URL == expect-url */ + a_Nav_cancel_expect_if_eq(conn->bw, conn->url); + /* finish conn */ + Capi_conn_unref(conn); + dFree(Info); + break; default: - MSG_WARN("Unused CCC\n"); + MSG_WARN("Unused CCC Capi 2F\n"); break; } } diff --git a/src/colors.c b/src/colors.c index d4cc60c5..3e194339 100644 --- a/src/colors.c +++ b/src/colors.c @@ -310,7 +310,7 @@ int32_t a_Color_parse (const char *str, int32_t default_color, int *err) static int Color_distance(long c1, long c2) { return (labs((c1 & 0x0000ff) - (c2 & 0x0000ff)) + - labs(((c1 & 0x00ff00) - (c2 & 0x00ff00)) >> 8) + + labs(((c1 & 0x00ff00) - (c2 & 0x00ff00)) >> 8) + labs(((c1 & 0xff0000) - (c2 & 0xff0000)) >> 16)) / 75; } #endif diff --git a/src/cookies.h b/src/cookies.h index 1cdb82ac..5e4d8c59 100644 --- a/src/cookies.h +++ b/src/cookies.h @@ -5,18 +5,17 @@ extern "C" { #endif /* __cplusplus */ +void a_Cookies_init( void ); #ifdef DISABLE_COOKIES # define a_Cookies_get_query(url, requester) dStrdup("") # define a_Cookies_set() ; -# define a_Cookies_init() ; # define a_Cookies_freeall() ; #else char *a_Cookies_get_query(const DilloUrl *query_url, const DilloUrl *requester); 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 @@ -544,7 +544,7 @@ void CssContext::addRule (CssSelector *sel, CssPropertyList *props, if (order == CSS_PRIMARY_USER_AGENT) { userAgentSheet.addRule (rule); - } else { + } else { sheet[order].addRule (rule); } } @@ -133,7 +133,7 @@ inline float CSS_LENGTH_VALUE (CssLength l) { case CSS_LENGTH_TYPE_EX: case CSS_LENGTH_TYPE_PERCENTAGE: case CSS_LENGTH_TYPE_RELATIVE: - return ((float)(l & ~7)) / (1 << 15); + return ((float)(l & ~7)) / (1 << 15); case CSS_LENGTH_TYPE_AUTO: return 0.0; default: diff --git a/src/cssparser.cc b/src/cssparser.cc index 369dd67f..1487a605 100644 --- a/src/cssparser.cc +++ b/src/cssparser.cc @@ -72,6 +72,10 @@ static const char *const Css_border_width_enum_vals[] = { "thin", "medium", "thick", NULL }; +static const char *const Css_clear_enum_vals[] = { + "left", "right", "both", "none", 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", @@ -84,6 +88,10 @@ static const char *const Css_display_enum_vals[] = { "table-cell", NULL }; +static const char *const Css_float_enum_vals[] = { + "none", "left", "right", NULL +}; + static const char *const Css_font_size_enum_vals[] = { "large", "larger", "medium", "small", "smaller", "xx-large", "xx-small", "x-large", "x-small", NULL @@ -121,6 +129,14 @@ static const char *const Css_list_style_type_enum_vals[] = { "katakana-iroha", "none", NULL }; +static const char *const Css_overflow_enum_vals[] = { + "visible", "hidden", "scroll", "auto", NULL +}; + +static const char *const Css_position_enum_vals[] = { + "static", "relative", "absolute", "fixed", NULL +}; + static const char *const Css_text_align_enum_vals[] = { "left", "right", "center", "justify", "string", NULL }; @@ -182,9 +198,9 @@ const CssPropertyInfo Css_property_info[CSS_PROPERTY_LAST] = { 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}, + {"bottom", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_UNUSED}, NULL}, {"caption-side", {CSS_TYPE_UNUSED}, NULL}, - {"clear", {CSS_TYPE_UNUSED}, NULL}, + {"clear", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_clear_enum_vals}, {"clip", {CSS_TYPE_UNUSED}, NULL}, {"color", {CSS_TYPE_COLOR, CSS_TYPE_UNUSED}, NULL}, {"content", {CSS_TYPE_STRING, CSS_TYPE_UNUSED}, NULL}, @@ -194,7 +210,7 @@ const CssPropertyInfo Css_property_info[CSS_PROPERTY_LAST] = { {"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}, + {"float", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_float_enum_vals}, {"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}, @@ -227,21 +243,25 @@ const CssPropertyInfo Css_property_info[CSS_PROPERTY_LAST] = { {CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_AUTO, 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}, + {"max-height", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, + NULL}, + {"max-width", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, + NULL}, + {"min-height", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, + NULL}, + {"min-width", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, 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}, + {"overflow", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_overflow_enum_vals}, {"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}, + {"position", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_position_enum_vals}, {"quotes", {CSS_TYPE_UNUSED}, NULL}, - {"right", {CSS_TYPE_UNUSED}, NULL}, + {"right", {CSS_TYPE_LENGTH_PERCENTAGE, 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}, @@ -249,7 +269,7 @@ const CssPropertyInfo Css_property_info[CSS_PROPERTY_LAST] = { {"text-shadow", {CSS_TYPE_UNUSED}, NULL}, {"text-transform", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_text_transform_enum_vals}, - {"top", {CSS_TYPE_UNUSED}, NULL}, + {"top", {CSS_TYPE_LENGTH_PERCENTAGE, 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}, @@ -277,7 +297,7 @@ typedef struct { } type; const CssPropertyName *properties; /* CSS_SHORTHAND_MULTIPLE: * must be terminated by - * CSS_PROPERTY_END + * CSS_PROPERTY_END * CSS_SHORTHAND_DIRECTIONS: * must have length 4 * CSS_SHORTHAND_BORDERS: @@ -720,7 +740,9 @@ bool CssParser::tokenMatchesProperty(CssPropertyName prop, CssValueType *type) dStrAsciiCasecmp(tval, "top") == 0 || dStrAsciiCasecmp(tval, "bottom") == 0)) return true; - // Fall Through (lenght and percentage) + if (ttype == CSS_TK_DECINT || ttype == CSS_TK_FLOAT) + return true; + break; case CSS_TYPE_LENGTH_PERCENTAGE: case CSS_TYPE_LENGTH_PERCENTAGE_NUMBER: case CSS_TYPE_LENGTH: @@ -766,7 +788,8 @@ bool CssParser::tokenMatchesProperty(CssPropertyName prop, CssValueType *type) case CSS_TYPE_URI: if (ttype == CSS_TK_SYMBOL && - dStrAsciiCasecmp(tval, "url") == 0) + (dStrAsciiCasecmp(tval, "url") == 0 || + dStrAsciiCasecmp(tval, "none") == 0)) return true; break; @@ -1044,12 +1067,16 @@ bool CssParser::parseValue(CssPropertyName prop, break; case CSS_TYPE_URI: - if (ttype == CSS_TK_SYMBOL && - dStrAsciiCasecmp(tval, "url") == 0) { - val->strVal = parseUrl(); - nextToken(); - if (val->strVal) + if (ttype == CSS_TK_SYMBOL) { + if (dStrAsciiCasecmp(tval, "url") == 0) { + val->strVal = parseUrl(); + if (val->strVal) + ret = true; + } else if (dStrAsciiCasecmp(tval, "none") == 0) { + val->strVal = NULL; ret = true; + } + nextToken(); } break; @@ -1106,6 +1133,9 @@ bool CssParser::parseValue(CssPropertyName prop, if (parseValue(prop, CSS_TYPE_LENGTH_PERCENTAGE, &valTmp)) { pos[i] = valTmp.intVal; ret = true; + } else if (parseValue(prop, CSS_TYPE_SIGNED_LENGTH, &valTmp)) { + pos[i] = valTmp.intVal; + ret = true; } else // ... but something may still fail. h[i] = v[i] = false; diff --git a/src/decode.c b/src/decode.c index 53a0d621..6d838d41 100644 --- a/src/decode.c +++ b/src/decode.c @@ -21,9 +21,10 @@ static const int bufsize = 8*1024; /* - * Decode chunked data + * Decode 'Transfer-Encoding: chunked' data */ -static Dstr *Decode_chunked(Decode *dc, const char *instr, int inlen) +Dstr *a_Decode_transfer_process(DecodeTransfer *dc, const char *instr, + int inlen) { char *inputPtr, *eol; int inputRemaining; @@ -66,6 +67,7 @@ static Dstr *Decode_chunked(Decode *dc, const char *instr, int inlen) } if (!(chunkRemaining = strtol(inputPtr, NULL, 0x10))) { + dc->finished = TRUE; break; /* A chunk length of 0 means we're done! */ } inputRemaining -= (eol - inputPtr) + 1; @@ -80,10 +82,16 @@ static Dstr *Decode_chunked(Decode *dc, const char *instr, int inlen) return output; } -static void Decode_chunked_free(Decode *dc) +bool_t a_Decode_transfer_finished(DecodeTransfer *dc) +{ + return dc->finished; +} + +void a_Decode_transfer_free(DecodeTransfer *dc) { dFree(dc->state); dStr_free(dc->leftover, 1); + dFree(dc); } static void Decode_compression_free(Decode *dc) @@ -208,7 +216,7 @@ static Dstr *Decode_deflate(Decode *dc, const char *instr, int inlen) dStr_free(output, 1); (void)inflateEnd(zs); dFree(dc->state); - dc->state = zs = dNew(z_stream, 1);; + dc->state = zs = dNew(z_stream, 1); zs->zalloc = NULL; zs->zfree = NULL; zs->next_in = NULL; @@ -280,19 +288,17 @@ static void Decode_charset_free(Decode *dc) /* * Initialize transfer decoder. Currently handles "chunked". */ -Decode *a_Decode_transfer_init(const char *format) +DecodeTransfer *a_Decode_transfer_init(const char *format) { - Decode *dc = NULL; + DecodeTransfer *dc = NULL; if (format && !dStrAsciiCasecmp(format, "chunked")) { int *chunk_remaining = dNew(int, 1); *chunk_remaining = 0; - dc = dNew(Decode, 1); + dc = dNew(DecodeTransfer, 1); dc->leftover = dStr_new(""); dc->state = chunk_remaining; - dc->decode = Decode_chunked; - dc->free = Decode_chunked_free; - dc->buffer = NULL; /* not used */ + dc->finished = FALSE; _MSG("chunked!\n"); } return dc; diff --git a/src/decode.h b/src/decode.h index 279807a6..06c987f6 100644 --- a/src/decode.h +++ b/src/decode.h @@ -15,7 +15,21 @@ typedef struct Decode { void (*free) (struct Decode *dc); } Decode; -Decode *a_Decode_transfer_init(const char *format); +/* I'm not going to shoehorn the decoders into the same form anymore. They + * can evolve independently. + */ +typedef struct DecodeTransfer { + Dstr *leftover; + void *state; + bool_t finished; /* has the terminating chunk been seen? */ +} DecodeTransfer; + +DecodeTransfer *a_Decode_transfer_init(const char *format); +Dstr *a_Decode_transfer_process(DecodeTransfer *dc, const char *instr, + int inlen); +bool_t a_Decode_transfer_finished(DecodeTransfer *dc); +void a_Decode_transfer_free(DecodeTransfer *dc); + Decode *a_Decode_content_init(const char *format); Decode *a_Decode_charset_init(const char *format); Dstr *a_Decode_process(Decode *dc, const char *instr, int inlen); diff --git a/src/dialog.cc b/src/dialog.cc index 2c2781b1..03949a1c 100644 --- a/src/dialog.cc +++ b/src/dialog.cc @@ -325,6 +325,7 @@ static void choice_cb(Fl_Widget *button, void *number) { choice_answer = VOIDP2INT(number); _MSG("choice_cb: %d\n", choice_answer); + button->window()->hide(); } @@ -354,31 +355,19 @@ int a_Dialog_choice(const char *title, const char *msg, ...) int gap = 8; int ww = 140 + n * 60, wh = 120; int bw = (ww - gap) / n - gap, bh = 45; - int ih = 50; Fl_Window *window = new Fl_Window(ww, wh, title); window->set_modal(); window->begin(); - Fl_Group *ib = new Fl_Group(0, 0, window->w(), window->h()); - ib->begin(); - window->resizable(ib); - - /* '?' Icon */ - Fl_Box *o = new Fl_Box(10, (wh - bh - ih) / 2, ih, ih); - o->box(FL_THIN_UP_BOX); - o->labelfont(FL_TIMES_BOLD); - o->labelsize(34); - o->color(FL_WHITE); - o->labelcolor(FL_BLUE); - o->label("?"); - o->show(); - if (msg != NULL){ - Fl_Box *box = new Fl_Box(60, 0, ww - 60, wh - bh, msg); - box->labelfont(FL_HELVETICA); - box->labelsize(14); - box->align(FL_ALIGN_WRAP); - } + Fl_Text_Buffer *buf = new Fl_Text_Buffer(); + buf->text(msg); + Fl_Text_Display *td = new Fl_Text_Display(0, 0, ww, wh - bh); + td->buffer(buf); + td->textsize((int) rint(14.0 * prefs.font_factor)); + td->wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 0); + + window->resizable(td); int xpos = gap; va_start(ap, msg); @@ -397,6 +386,8 @@ int a_Dialog_choice(const char *title, const char *msg, ...) while (window->shown()) Fl::wait(); _MSG("Dialog_choice answer = %d\n", answer); + td->buffer(NULL); + delete buf; delete window; return choice_answer; diff --git a/src/dillo.cc b/src/dillo.cc index 20b2796e..24271103 100644 --- a/src/dillo.cc +++ b/src/dillo.cc @@ -45,18 +45,22 @@ #include "dns.h" #include "web.hh" +#include "IO/tls.h" #include "IO/Url.h" #include "IO/mime.h" #include "capi.h" #include "dicache.h" #include "cookies.h" +#include "hsts.h" #include "domain.h" #include "auth.h" #include "styleengine.hh" #include "lout/debug.hh" #include "dw/fltkcore.hh" +#include "dw/widget.hh" #include "dw/textblock.hh" +#include "dw/table.hh" /* * Command line options structure @@ -377,10 +381,18 @@ static DilloUrl *makeStartUrl(char *str, bool local) */ int main(int argc, char **argv) { - DBG_OBJ_COLOR("#c0ff80", "dw::*"); - DBG_OBJ_COLOR("#c0c0ff", "dw::fltk::*"); - DBG_OBJ_COLOR("#ffa0a0", "dw::core::*"); - DBG_OBJ_COLOR("#ffe0a0", "dw::core::style::*"); + DBG_OBJ_COLOR ("dw::*", "#c0ff80"); + DBG_OBJ_COLOR ("dw::fltk::*", "#c0c0ff"); + DBG_OBJ_COLOR ("dw::core::*", "#ffa0a0"); + DBG_OBJ_COLOR ("dw::core::style::*", "#ffe0a0"); + + DBG_OBJ_COLOR ("dw::Image", "#80ffa0"); + DBG_OBJ_COLOR ("dw::Textblock", "#f0ff80"); + DBG_OBJ_COLOR ("dw::OutOfFlowMgr", "#d0ff80"); + DBG_OBJ_COLOR ("dw::AlignedTextblock", "#e0ff80"); + DBG_OBJ_COLOR ("dw::ListItem", "#b0ff80"); + DBG_OBJ_COLOR ("dw::TableCell", "#80ff80"); + DBG_OBJ_COLOR ("dw::Table", "#80ffc0"); uint_t opt_id; uint_t options_got = 0; @@ -466,15 +478,19 @@ int main(int argc, char **argv) a_Dns_init(); a_Web_init(); a_Http_init(); + a_Tls_init(); a_Mime_init(); a_Capi_init(); a_Dicache_init(); a_Bw_init(); a_Cookies_init(); + a_Hsts_init(Paths::getPrefsFP(PATHS_HSTS_PRELOAD)); a_Auth_init(); a_UIcmd_init(); StyleEngine::init(); + dw::core::Widget::setAdjustMinWidth (prefs.adjust_min_width); + dw::Table::setAdjustTableMinWidth (prefs.adjust_table_min_width); dw::Textblock::setPenaltyHyphen (prefs.penalty_hyphen); dw::Textblock::setPenaltyHyphen2 (prefs.penalty_hyphen_2); dw::Textblock::setPenaltyEmDashLeft (prefs.penalty_em_dash_left); @@ -582,9 +598,11 @@ int main(int argc, char **argv) */ a_Domain_freeall(); a_Cookies_freeall(); + a_Hsts_freeall(); a_Cache_freeall(); a_Dicache_freeall(); a_Http_freeall(); + a_Tls_freeall(); a_Dns_freeall(); a_History_freeall(); a_Prefs_freeall(); diff --git a/src/form.cc b/src/form.cc index 3316d313..92ee3a42 100644 --- a/src/form.cc +++ b/src/form.cc @@ -658,7 +658,7 @@ void Html_tag_content_textarea(DilloHtml *html, const char *tag, int tagsize) } else { if (html->DocType != DT_HTML || html->DocTypeVersion <= 4.01f) BUG_MSG("<textarea> requires rows attribute."); - rows = 10; + rows = 2; } if (rows < 1 || rows > MAX_ROWS) { int badRows = rows; @@ -951,9 +951,7 @@ void Html_tag_open_button(DilloHtml *html, const char *tag, int tagsize) embed = new Embed(resource); // a_Dw_button_set_sensitive (DW_BUTTON (button), FALSE); - HT2TB(html)->addParbreak (5, html->wordStyle ()); HT2TB(html)->addWidget (embed, html->backgroundStyle ()); - HT2TB(html)->addParbreak (5, html->wordStyle ()); S_TOP(html)->textblock = html->dw = page; @@ -800,8 +800,8 @@ static size_t Gif_do_img_desc(DilloGif *gif, void *Buf, if (bsize < 10) return 0; - gif->Width = LM_to_uint(buf[4], buf[5]); - gif->Height = LM_to_uint(buf[6], buf[7]); + 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 || diff --git a/src/hsts.c b/src/hsts.c new file mode 100644 index 00000000..ecbd9765 --- /dev/null +++ b/src/hsts.c @@ -0,0 +1,364 @@ +/* + * File: hsts.c + * HTTP Strict Transport Security + * + * Copyright 2015 corvid + * + * 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. + * + */ + +/* To preload hosts, as of 2015, chromium is the list keeper: + * https://src.chromium.org/viewvc/chrome/trunk/src/net/http/transport_security_state_static.json + * although mozilla's is easier to work from (and they trim it based on + * criteria such as max-age must be at least some number of months) + * https://mxr.mozilla.org/mozilla-central/source/security/manager/ssl/nsSTSPreloadList.inc?raw=1 + */ + +#include <time.h> +#include <errno.h> +#include <limits.h> /* for INT_MAX */ +#include <ctype.h> /* for isspace */ +#include <stdlib.h> /* for strtol */ + +#include "hsts.h" +#include "msg.h" +#include "../dlib/dlib.h" +#include "IO/tls.h" + +typedef struct { + char *host; + time_t expires_at; + bool_t subdomains; +} HstsData_t; + +/* When there is difficulty in representing future dates, use the (by far) + * most likely latest representable time of January 19, 2038. + */ +static time_t hsts_latest_representable_time; +static Dlist *domains; + +static void Hsts_free_policy(HstsData_t *p) +{ + dFree(p->host); + dFree(p); +} + +void a_Hsts_freeall() +{ + if (prefs.http_strict_transport_security) { + HstsData_t *policy; + int i, n = dList_length(domains); + + for (i = 0; i < n; i++) { + policy = dList_nth_data(domains, i); + Hsts_free_policy(policy); + } + dList_free(domains); + } +} + +/* + * Compare function for searching a domain node by domain string + */ +static int Domain_node_domain_str_cmp(const void *v1, const void *v2) +{ + const HstsData_t *node = v1; + const char *host = v2; + + return dStrAsciiCasecmp(node->host, host); +} + +static HstsData_t *Hsts_get_policy(const char *host) +{ + return dList_find_sorted(domains, host, Domain_node_domain_str_cmp); +} + +static void Hsts_remove_policy(HstsData_t *policy) +{ + if (policy) { + _MSG("HSTS: removed policy for %s\n", policy->host); + Hsts_free_policy(policy); + dList_remove(domains, policy); + } +} + +/* + * Return the time_t for a future time. + */ +static time_t Hsts_future_time(long seconds_from_now) +{ + time_t ret, now = time(NULL); + struct tm *tm = gmtime(&now); + + if (seconds_from_now > INT_MAX - tm->tm_sec) + tm->tm_sec = INT_MAX; + else + tm->tm_sec += seconds_from_now; + + ret = mktime(tm); + if (ret == (time_t) -1) + ret = hsts_latest_representable_time; + + return ret; +} + +/* + * Compare function for searching domains. + */ +static int Domain_node_cmp(const void *v1, const void *v2) +{ + const HstsData_t *node1 = v1, *node2 = v2; + + return dStrAsciiCasecmp(node1->host, node2->host); +} + +static void Hsts_set_policy(const char *host, long max_age, bool_t subdomains) +{ + time_t exp = Hsts_future_time(max_age); + HstsData_t *policy = Hsts_get_policy(host); + + _MSG("HSTS: %s %s%s: until %s", (policy ? "modify" : "add"), host, + (subdomains ? " and subdomains" : ""), ctime(&exp)); + + if (policy == NULL) { + policy = dNew0(HstsData_t, 1); + policy->host = dStrdup(host); + dList_insert_sorted(domains, policy, Domain_node_cmp); + } + policy->subdomains = subdomains; + policy->expires_at = exp; +} + +/* + * Read the next attribute. + */ +static char *Hsts_parse_attr(const char **header_str) +{ + const char *str; + uint_t len; + + while (dIsspace(**header_str)) + (*header_str)++; + + str = *header_str; + /* find '=' at end of attr, ';' after attr/val pair, '\0' end of string */ + len = strcspn(str, "=;"); + *header_str += len; + + while (len && (str[len - 1] == ' ' || str[len - 1] == '\t')) + len--; + return dStrndup(str, len); +} + +/* + * Get the value in *header_str. + */ +static char *Hsts_parse_value(const char **header_str) +{ + uint_t len; + const char *str; + + if (**header_str == '=') { + (*header_str)++; + while (dIsspace(**header_str)) + (*header_str)++; + + str = *header_str; + /* finds ';' after attr/val pair or '\0' at end of string */ + len = strcspn(str, ";"); + *header_str += len; + + while (len && (str[len - 1] == ' ' || str[len - 1] == '\t')) + len--; + } else { + str = *header_str; + len = 0; + } + return dStrndup(str, len); +} + +/* + * Advance past any value. + */ +static void Hsts_eat_value(const char **str) +{ + if (**str == '=') + *str += strcspn(*str, ";"); +} + +/* + * The reponse for this url had an HSTS header, so let's take action. + */ +void a_Hsts_set(const char *header, const DilloUrl *url) +{ + long max_age; + const char *host = URL_HOST(url); + bool_t max_age_valid = FALSE, subdomains = FALSE; + + _MSG("HSTS header for %s: %s\n", host, header); + + if (!a_Tls_certificate_is_clean(url)) { + /* RFC 6797 gives rationale in section 14.3. */ + _MSG("But there were certificate warnings, so ignore it (!)\n"); + return; + } + + /* Iterate until there is nothing left of the string */ + while (*header) { + char *attr; + char *value; + + /* Get attribute */ + attr = Hsts_parse_attr(&header); + + /* Get the value for the attribute and store it */ + if (dStrAsciiCasecmp(attr, "max-age") == 0) { + value = Hsts_parse_value(&header); + if (isdigit(*value)) { + errno = 0; + max_age = strtol(value, NULL, 10); + if (errno == ERANGE) + max_age = INT_MAX; + max_age_valid = TRUE; + } + dFree(value); + } else if (dStrAsciiCasecmp(attr, "includeSubDomains") == 0) { + subdomains = TRUE; + Hsts_eat_value(&header); + } else if (dStrAsciiCasecmp(attr, "preload") == 0) { + /* 'preload' is not part of the RFC, but what does google care for + * standards? They require that 'preload' be specified by a domain + * in order to be added to their preload list. + */ + } else { + MSG("HSTS: header contains unknown attribute: '%s'\n", attr); + Hsts_eat_value(&header); + } + + dFree(attr); + + if (*header == ';') + header++; + } + if (max_age_valid) { + if (max_age > 0) + Hsts_set_policy(host, max_age, subdomains); + else + Hsts_remove_policy(Hsts_get_policy(host)); + } +} + +static bool_t Hsts_expired(HstsData_t *policy) +{ + time_t now = time(NULL); + bool_t ret = (now > policy->expires_at) ? TRUE : FALSE; + + if (ret) { + _MSG("HSTS: expired\n"); + } + return ret; +} + +bool_t a_Hsts_require_https(const char *host) +{ + bool_t ret = FALSE; + + if (host) { + HstsData_t *policy = Hsts_get_policy(host); + + if (policy) { + _MSG("HSTS: matched host %s\n", host); + if (Hsts_expired(policy)) + Hsts_remove_policy(policy); + else + ret = TRUE; + } + if (!ret) { + const char *domain_str; + + for (domain_str = strchr(host+1, '.'); + domain_str != NULL && *domain_str; + domain_str = strchr(domain_str+1, '.')) { + policy = Hsts_get_policy(domain_str+1); + + if (policy && policy->subdomains) { + _MSG("HSTS: matched %s under %s subdomain rule\n", host, + policy->host); + if (Hsts_expired(policy)) { + Hsts_remove_policy(policy); + } else { + ret = TRUE; + break; + } + } + } + } + } + return ret; +} + +static void Hsts_preload(FILE *stream) +{ + const int LINE_MAXLEN = 4096; + const long ONE_YEAR = 60 * 60 * 24 * 365; + + char *rc, *subdomains; + char line[LINE_MAXLEN]; + char domain[LINE_MAXLEN]; + + /* Get all lines in the file */ + while (!feof(stream)) { + line[0] = '\0'; + rc = fgets(line, LINE_MAXLEN, stream); + if (!rc && ferror(stream)) { + MSG_WARN("HSTS: Error while reading preload entries: %s\n", + dStrerror(errno)); + return; /* bail out */ + } + + /* Remove leading and trailing whitespace */ + dStrstrip(line); + + if (line[0] != '\0' && line[0] != '#') { + int i = 0, j = 0; + + /* Get the domain */ + while (line[i] != '\0' && !dIsspace(line[i])) + domain[j++] = line[i++]; + domain[j] = '\0'; + + /* Skip past whitespace */ + while (dIsspace(line[i])) + i++; + + subdomains = line + i; + + if (dStrAsciiCasecmp(subdomains, "true") == 0) + Hsts_set_policy(domain, ONE_YEAR, TRUE); + else if (dStrAsciiCasecmp(subdomains, "false") == 0) + Hsts_set_policy(domain, ONE_YEAR, FALSE); + else { + MSG_WARN("HSTS: format of line not recognized. Ignoring '%s'.\n", + line); + } + } + } +} + +void a_Hsts_init(FILE *preload_file) +{ + if (prefs.http_strict_transport_security) { + struct tm future_tm = {7, 14, 3, 19, 0, 138, 0, 0, 0, 0, 0}; + + hsts_latest_representable_time = mktime(&future_tm); + domains = dList_new(32); + + if (preload_file) + Hsts_preload(preload_file); + } +} + diff --git a/src/hsts.h b/src/hsts.h new file mode 100644 index 00000000..693aec10 --- /dev/null +++ b/src/hsts.h @@ -0,0 +1,19 @@ +#ifndef __HSTS_H__ +#define __HSTS_H__ + +#include "d_size.h" +#include "url.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void a_Hsts_init(FILE *fp); +void a_Hsts_set(const char *header, const DilloUrl *url); +bool_t a_Hsts_require_https(const char *host); +void a_Hsts_freeall( void ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* !__HSTS_H__ */ diff --git a/src/hsts_preload b/src/hsts_preload new file mode 100755 index 00000000..22e3aa3c --- /dev/null +++ b/src/hsts_preload @@ -0,0 +1,2037 @@ +# This HTTP Strict Transport Security preload file was created on 2015-06-28 +# from the list in +# https://mxr.mozilla.org/mozilla-central/source/security/manager/ssl/nsSTSPreloadList.inc +# Format: domain include_subdomains + +007sascha.de true +0x0a.net true +1000minds.com true +17hats.com true +188trafalgar.ca true +18f.gsa.gov true +1a-diamantscheiben.de true +1a-jva.de true +1a-vermessung.at true +1a-werkstattgeraete.de true +2048game.co.uk true +2600hq.com true +301.website true +302.nyc true +314chan.org true +3do3dont.com true +47ronin.com false +4g-server.eu true +4sqsu.eu true +5apps.com false +7183.org true +8ack.de true +9point6.com true +abecodes.net false +abiapp.net true +abmahnhelfer.de true +accounts.firefox.com true +accounts.google.com true +aclu.org false +acuica.co.uk false +acus.gov true +adambyers.com true +adamkostecki.de true +adamstas.com true +addvocate.com true +adlershop.ch true +admin.fedoraproject.org true +admin.google.com true +admin.stg.fedoraproject.org true +adorai.tk true +adsfund.org true +advanced-online.eu true +aerolog.co true +aes256.ru true +aeyoun.com true +afp548.com true +afrodigital.uk true +agrios.de true +ahoyconference.com true +ahwatukeefoothillsmontessori.com true +aids.gov true +aie.de true +aircomms.com true +airlea.com true +aiticon.com true +ajouin.com true +akachanikuji.com true +akselinurmio.fi true +al-shami.net true +aladdinschools.appspot.com true +alainwolf.net true +alaninkenya.org true +alanrickmanflipstable.com true +alecvannoten.be true +alethearose.com true +alexgaynor.net true +alexsexton.com true +alexyang.me true +allinonecyprus.com true +alza.cz true +alza.de true +alza.sk true +alzashop.com true +amaforums.org false +amdouglas.uk true +anadoluefessk.org true +anadoluefessporkulubu.org true +anakros.me true +andere-gedanken.net true +andreasbreitenlohner.de true +andrewimeson.com true +andymartin.cc true +anetaben.nl true +angularjs.org true +anime.my false +animurecs.com true +ankakaak.com true +ankarakart.com.tr true +annahmeschluss.de true +annevankesteren.com true +annevankesteren.nl true +annevankesteren.org true +anonym-surfen.de true +ansdell.net true +antipolygraph.org true +antoniomarques.eu true +anycoin.me true +apachehaus.de false +apadvantage.com true +api.intercom.io false +api.lookout.com false +api.simple.com false +api.xero.com false +apis.google.com true +apn-einstellungen.de true +aponow.de true +app.lookout.com false +app.manilla.com true +app.recurly.com true +app.simpletax.ca false +app.yinxiang.com false +appengine.google.com true +apple-watch-zubehoer.de true +apps-for-fishing.com true +apps.facebook.com false +apps.fedoraproject.org true +apps.stg.fedoraproject.org true +aprz.de true +aranycsillag.net true +arbitrary.ch true +archlinux.de true +areafiftylan.nl true +arendburgers.nl true +arguggi.co.uk true +arivo.com.br true +arlen.io true +armytricka.cz true +aroonchande.com true +arteseideias.com.pt true +arty.name true +ask.fedoraproject.org true +ask.stg.fedoraproject.org true +atc.io true +athenelive.com true +atishchenko.com true +atlantischild.hu true +atlassian.net true +atte.fi true +auf-feindgebiet.de true +aurainfosec.com true +aurainfosec.com.au true +auraredeye.com true +auraredshield.com true +authentication.io true +autoledky.sk true +axka.com false +azirevpn.com true +badges.fedoraproject.org true +badges.stg.fedoraproject.org true +baer.im true +baff.lu true +bagelsbakery.com true +balcan-underground.net true +balikonos.cz true +bank.simple.com false +bardiharborow.com true +barslecht.com true +barslecht.nl true +baruch.me true +bassh.net true +bautied.de true +bayrisch-fuer-anfaenger.de true +bccx.com true +bcrook.com false +beach-inspector.com true +beamitapp.com true +beastowner.com true +beastowner.li true +bedeta.de true +bedreid.dk true +beercandle.com true +ben-energy.com true +benchling.com true +beneathvt.com true +benjamin.pe true +benjamins.com true +bentertain.de true +best-wedding-quotes.com true +bfelob.gov true +bgneuesheim.de true +bhatia.at true +biathloncup.ru true +biddl.com true +big-andy.co.uk true +bigbrownpromotions.com.au true +bigdinosaur.org true +billigssl.dk true +bit-sentinel.com true +bit.voyage true +bitbucket.org false +bitchan.it true +bitcoin.de true +bitcoinx.ro true +bitfactory.ws true +bitmex.com true +bitmon.net true +bitnet.io true +bitpod.de true +bjornjohansen.no true +bl4ckb0x.com true +bl4ckb0x.de true +bl4ckb0x.info true +bl4ckb0x.net true +bl4ckb0x.org true +blablacar.co.uk true +blablacar.com true +blablacar.com.tr true +blablacar.com.ua true +blablacar.de true +blablacar.es true +blablacar.fr true +blablacar.hr true +blablacar.hu true +blablacar.in true +blablacar.it true +blablacar.mx true +blablacar.nl true +blablacar.pl true +blablacar.pt true +blablacar.ro true +blablacar.rs true +blablacar.ru true +blackberrycentral.com true +blessnet.jp true +blockchain.info true +blocksatz-medien.de true +bloemendal.me true +blog.cyveillance.com true +blog.gparent.org true +blog.linode.com false +blog.torproject.org false +blubbablasen.de true +bodo-wolff.de false +bohramt.de true +boiseonlinemall.com true +bonitabrazilian.co.nz true +bookingapp.nl true +bownty.dk true +boxcryptor.com true +boypoint.de true +bradkovach.com true +brage.info false +braineet.com true +brainfork.ml true +braintreegateway.com true +brakemanpro.com true +bran.cc true +branchtrack.com false +brandbuilderwebsites.com true +breeswish.org true +brks.xyz true +broeselei.at true +brossmanit.com true +brunosouza.org true +buddhistische-weisheiten.org true +bugzil.la true +bugzilla.mozilla.org true +buiko.com true +buildkite.com true +bulktrade.de true +bundaberg.com true +burtrum.org true +business.facebook.com false +business.lookout.com false +businesshosting.nl true +bustimes.org true +buzzconcert.com true +bygningsregistrering.dk true +bysymphony.com true +bytepark.de false +bzv-fr.eu true +ca.gparent.org true +cackette.com true +call.me true +calomel.org true +calories.org true +calvin.me true +camolist.com true +canhazip.com true +cao.gov true +capitaltg.com true +cardoni.net true +caremad.io true +carezone.com false +carlosalves.info true +cartouche24.eu true +cartucce24.it true +casa-su.casa true +catnapstudios.com true +cbhq.net true +cdlcenter.com true +cdnb.co true +cdt.org true +certible.com true +certly.io true +cfo.gov true +chahub.com true +chainmonitor.com true +chartstoffarm.de false +chatbot.me true +check.torproject.org false +checkout.google.com true +cheesetart.my false +chrisirwin.ca true +chrisjean.com true +chrome-devtools-frontend.appspot.com true +chrome.com false +chrome.google.com true +chromiumcodereview.appspot.com false +chroniclesofgeorge.com true +chulado.com true +cio.gov true +cklie.de true +ckliemann.com true +ckliemann.net true +cktennis.com true +clan-ww.com true +clapping-rhymes.com true +classdojo.com true +clerkendweller.uk true +clevisto.com true +climateinteractive.org true +clintwilson.technology true +cloud.google.com true +cloudcert.org true +cloudns.com.au true +cloudpebble.net true +cloudsecurityalliance.org true +cloudstoragemaus.com true +cloudup.com true +code-poets.co.uk true +code.facebook.com false +code.google.com true +codepoints.net true +codepref.com true +codepx.com true +codereview.appspot.com false +codereview.chromium.org true +coinapult.com true +coinbase.com true +coindam.com false +collinmbarrett.com true +coloradocomputernetworking.net true +comdurav.com true +commencepayments.com true +completionist.audio true +comssa.org.au true +config.schokokeks.org false +conformal.com true +conrad-kostecki.de true +console.support true +consumersentinel.gov true +contributor.google.com true +controlcenter.gigahost.dk true +cor-ser.es true +cordial-restaurant.com true +costablancavoorjou.com true +cotonea.de true +courtlistener.com true +covenantoftheriver.org true +covoiturage.fr true +cpvmatch.eu true +cracker.in.th true +crm.onlime.ch false +crowdjuris.com true +crute.me true +crypto.cat false +crypto.graphics true +cryptobin.org true +cryptography.io true +cryptopartyatx.org true +cryptopush.com true +csacongress.org true +cspbuilder.info true +csuw.net true +cube.de true +cupcake.io true +cupcake.is true +curiosity-driven.org true +curlybracket.co.uk true +curtacircuitos.com.br false +cyanogenmod.xxx true +cybershambles.com true +cybozu.com true +cybozulive.com true +cycleluxembourg.lu true +cyon.ch true +cyphertite.com true +cyprus-company-service.com true +czakey.net true +czbix.com true +czk.mk true +d42.no true +daknob.net true +danielalvarez.net true +danonsecurity.com true +danskoferie.dk true +danw.io true +daphne.informatik.uni-freiburg.de true +darchoods.net false +darkengine.io true +darknode.in true +darkpony.ru true +darkserver.fedoraproject.org true +darkserver.stg.fedoraproject.org true +darlo.co.uk true +darom.jp true +dash-board.jp false +data-abundance.com true +data.qld.gov.au false +datasnitch.co.uk true +datenkeks.de true +daveoc64.co.uk true +davidlyness.com true +davidmcevoy.org.uk true +davidnoren.com true +daylightpirates.org true +dbgamestudio.com true +dccode.gov true +deadbeef.ninja true +dealcruiser.nl true +debtkit.co.uk true +dedimax.de true +dee.pe true +defcon.org true +dekasan.ru true +deliverance.co.uk false +denh.am true +depechemode-live.com true +derevtsov.com false +derhil.de true +desmaakvanplanten.be true +detectify.com false +developer.mydigipass.com false +developers.facebook.com false +devinfo.net false +devklog.net true +diamante.ro true +die-besten-weisheiten.de true +digital1st.co.uk true +dillonkorman.com true +dinamoelektrik.com true +disking.co.uk true +dist.torproject.org false +dixmag.com false +dl.google.com true +dlc.viasinc.com true +dm.lookout.com false +dm.mylookout.com false +dn42.us true +dnmlab.it true +dnsman.se true +doc.python.org true +docs.google.com true +docs.python.org true +dohosting.ru true +domainkauf.de true +domains.google.com true +donmez.uk true +donmez.ws true +donotcall.gov true +doridian.com true +doridian.de true +doridian.net true +doridian.org true +dpsg-roden.de true +dragons-of-highlands.cz true +dreadbyte.com true +dreamsforabetterworld.com.au true +drive.google.com true +dropbox.com true +dropboxer.net true +drtroyhendrickson.com true +drumbandesperanto.nl true +dubrovskiy.net true +ducohosting.com true +dyeager.org true +dylanscott.com.au true +dynaloop.net true +dzlibs.io true +e-kontakti.fi true +e.mail.ru true +earmarks.gov true +easysimplecrm.com false +eatsleeprepeat.net true +ebanking.indovinabank.com.vn true +ecake.in true +ecdn.cz true +ecfs.link true +ecg.fr false +ecosystem.atlassian.net true +ectora.com true +ed.gs true +edge-cloud.net true +edit.yahoo.com false +edix.ru true +eduid.se true +eduroam.no true +edyou.eu true +ef.gy true +eff.org true +egfl.org.uk true +egit.co true +ego4u.com true +ego4u.de true +eksisozluk.com true +electronic-ignition-system.com true +ellegaard.dk true +elliquiy.com true +emailprivacytester.com true +emptypath.com true +encircleapp.com true +encryptallthethings.net true +encrypted.google.com true +energy-drink-magazin.de true +enigmail.net true +enorekcah.com true +enskat.de true +enskatson-sippe.de true +entropia.de false +erisrenee.com true +eromixx.com true +erotische-aanbiedingen.nl true +errors.zenpayroll.com false +eru.me true +esoa.net true +espra.com true +ethack.org true +ethercalc.com true +ethercalc.org true +ethitter.com true +etoprekrasno.ru true +eurotramp.com true +eva.cz true +evalesc.com true +everhome.de true +eveshamglass.co.uk true +evstatus.com true +exiahost.com false +exon.io true +expatads.com true +explodie.org true +expoundite.net true +extendwings.com true +ezequiel-garzon.com true +ezequiel-garzon.net true +f-droid.org true +f2f.cash true +fa-works.com true +fabhub.io true +facebook.com false +factor.cc false +fairbill.com true +fakturoid.cz true +falconvintners.com true +fangs.ink true +fant.dk true +faq.lookout.com false +fassadenverkleidung24.de true +fastcomcorp.net true +fatherhood.gov true +faucetbox.com true +federalregister.gov true +fedorahosted.org true +fedorapeople.org true +feedbin.com false +feedthebot.com true +feen.us true +feminists.co true +ferienhaus-polchow-ruegen.de false +fewo-thueringer-wald.de true +ffbans.org true +fidelapp.com true +fiftyshadesofluca.ml true +fiken.no true +filedir.com false +filip-prochazka.com true +finn.io false +firebaseio-demo.com true +firebaseio.com true +firebirdrangecookers.com true +firefart.at true +firemail.io true +firma-offshore.com true +firstlook.org true +fischer-its.com true +fish-hook.ru true +fitkram.cz true +fj.simple.com false +flagspot.net true +flamer-scene.com true +fleximus.org false +floobits.com true +florian-lillpopp.de true +florianlillpopp.de true +florianmitrea.uk true +floweslawncare.com true +flushstudios.com true +fluxfingers.net true +flynn.io true +fniephaus.com true +food4health.guide true +foodwise.marketing true +forewordreviews.com true +forgix.com true +forodeespanol.com true +forum.linode.com false +forum.quantifiedself.com true +foxelbox.com true +fralef.me false +frederik-braun.com true +freenetproject.org true +freeshell.de true +freesounding.com true +freesounding.ru true +freethought.org.au true +fretscha.com true +froggstack.de true +fronteers.nl true +fruchthof24.de true +frusky.de false +frusky.net true +ftccomplaintassistant.gov true +fteproxy.org true +fundingempire.com true +futos.de true +fuzzing-project.org true +fx5.de true +g2g.com true +gallery44.org true +gambit.pro true +gambitnash.co.uk true +gambitnash.com true +gambitprint.com true +gamercredo.com true +gamercredo.net true +gameserver-sponsor.de true +garron.net true +gavick.com true +gaytorrent.ru true +gc.net true +ge3k.net true +gemeinfreie-lieder.de true +genuxation.com true +genuxtsg.com true +geoip.fedoraproject.org true +geoip.stg.fedoraproject.org true +gerardozamudio.mx true +gernert-server.de true +get.zenpayroll.com false +getable.com true +getbambu.com false +getcloak.com false +getcolor.com true +getdigitized.net true +getfedora.org true +getfittedstore.com true +getmango.com true +getnikola.com true +getsello.com true +getssl.uz true +gheorghesarcov.ga true +giacomopelagatti.it true +github.com true +github.party false +gizzo.sk true +glass.google.com true +globalittech.com false +globuli-info.de true +glossopnorthendafc.co.uk true +gmail.com false +gmantra.org true +gmcd.co true +gnetwork.eu true +go-zh.org true +go.xero.com false +gocardless.com true +gokmenguresci.com true +goldendata.io true +golfscape.com false +google true +googlemail.com false +googleplex.com true +gopay.cz true +goshop.cz true +gothamlimo.com true +goto.google.com true +gotspot.com true +gplintegratedit.com true +gpsfix.cz true +gra2.com true +grandcapital.id true +grandcapital.ru true +grc.com false +greensolid.biz true +gregorytlee.me true +grepular.com true +grigalanzsoftware.com true +grimm-gastrobedarf.de true +grocock.me.uk true +groetzner.net true +groszek.pl true +groups.google.com true +gtraxapp.com true +gudini.net true +gugga.dk false +guidetoiceland.is true +gunnarhafdal.com true +guphi.net true +guru-naradi.cz true +gurusupe.com true +guthabenkarten-billiger.de true +gvt2.com true +gvt3.com true +gw2treasures.com true +gwijaya.com true +h2check.org true +haber1903.com true +hachre.de false +hack.li true +hackerone-user-content.com true +hackerone.com true +haircrazy.com true +hangouts.google.com true +hansvaneijsden.com true +happylifestyle.com true +happyteamlabs.com true +harvestapp.com true +hash-list.com true +hasilocke.de true +haste.ch true +haufschild.de true +hausverbrauch.de true +haveibeenpwned.com true +hboeck.de true +healthcare.gov false +heartlandrentals.com true +heavystresser.com true +heftkaufen.de true +heha.co false +heid.ws true +heijblok.com true +helichat.de true +help.simpletax.ca false +helpadmin.net true +helpium.de true +hemlockhillscabinrentals.com true +henriknoerr.com true +heppler.net true +herbert.io true +herocentral.de true +herzbotschaft.de true +heute-kaufen.de true +hex2013.com true +hexony.com true +hg.python.org true +hicn.gq true +hicoria.com true +history.google.com false +hiv.gov true +hledejpravnika.cz true +hobbyspeed.com true +holymoly.lu true +honeybadger.io false +horza.org true +hostedtalkgadget.google.com true +hostinginnederland.nl true +hostix.de true +howrandom.org true +howsmyssl.com true +howsmytls.com true +hozana.si true +hpac-portal.com true +hrackydomino.cz true +hs-group.net true +hsmr.cc true +hsr.gov true +hstsfail.appspot.com true +html5.org true +httpswatch.com true +hushfile.it true +i10z.com true +i5y.co.uk true +iamcarrico.com true +ian.sh true +iban.is true +id-co.in true +id-conf.com true +id.atlassian.com true +id.mayfirst.org false +ideaweb.de true +ieval.ro true +ihrlotto.de true +ijohan.nl true +ikkatsu-satei.jp true +ilbuongiorno.it true +ilikerainbows.co true +ilikerainbows.co.uk false +imaginary.ca true +imagr.io true +imgg.es true +imouto.my false +impex.com.bd true +in.xero.com false +inb4.us true +inbox.google.com true +indiecert.net true +indovinabank.com.vn true +influxus.com true +infogrfx.com true +informnapalm.org true +iniiter.com true +initrd.net true +inkbunny.net true +inleaked.com true +innophate-security.com true +innophate-security.nl true +insighti.org true +insouciant.org true +instasex.ch true +integromat.com true +interasistmen.se true +interserved.com true +iostips.ru true +ipomue.com false +ipsec.pl true +iqualtech.com true +iranianlawschool.com true +iridiumbrowser.de true +irische-segenswuensche.info true +irmag.ru true +ironfistdesign.com true +isimonbrown.co.uk true +isitchristmas.com true +isogram.nl true +it-schwerin.de true +itdashboard.gov true +itriskltd.com true +itsamurai.ru true +itshost.ru true +ix8.ru true +izdiwho.com true +j-lsolutions.com true +jackyyf.com false +jacobparry.ca false +jacuzziprozone.com true +jahliveradio.com false +jakub-boucek.cz true +jamesbywater.co.uk true +jamesbywater.com true +jamesbywater.me true +jamesbywater.uk true +jamielinux.com true +janoberst.com true +jbn.mx true +jelmer.co.uk true +jelmer.uk true +jeremyness.com true +jetaprices.com true +jettshome.org true +jfreitag.de true +jh-media.eu false +jimshaver.net true +jira.com true +jitsi.org false +jkb.pics true +jkbuster.com true +jmdekker.it true +jmedved.com true +jogorama.com.br true +johannes.io true +johners.me true +johnmichel.org true +jonas-keidel.de true +jonaswitmer.ch true +jonathan.ir true +jondevin.com true +jonnybarnes.uk true +jonpads.com true +jpbike.cz true +jrc9.ca true +julianmeyer.de true +juliansimioni.com true +jwilsson.com true +jwilsson.me true +jwnotifier.org true +k-dev.de true +kaheim.de true +kalevlamps.co.uk true +kalmar.com true +kaneo-gmbh.de true +kanzashi.com true +karaoketonight.com true +kardize24.pl true +karmaspa.se true +kartonmodellbau.org true +kaufberatung.community true +kavovary-kava.cz true +kdex.de true +kdyby.org true +kedarastudios.com true +keeleysam.com true +keeleysam.me true +keepa.com true +keepclean.me true +keeperapp.com true +keepersecurity.com true +kernel-error.de true +kevincox.ca true +keybase.io true +keycdn.com true +keycom.co.uk true +keyerror.com true +khanovaskola.cz true +khipu.com true +khmath.com true +ki-on.net true +kinderbuecher-kostenlos.de true +kinganywhere.eu true +kingmanhall.org true +kinogb.net false +kinsights.com false +kintone.com true +kirei.se true +kissflow.com true +kitsta.com true +klarmobil-empfehlen.de true +klatschreime.de true +klausbrinch.dk true +klaxn.com true +kleidertauschpartys.de true +kliemann.me true +klingeletest.de true +knip.ch true +knowledgehook.com true +koen.io true +koenrouwhorst.nl true +koenvdheuvel.me true +kojipkgs.fedoraproject.org true +kollawat.me true +komandakovalchuk.com false +konklone.com true +koop-bremen.de true +koordinate.net true +korinar.com true +kosho.org true +kpdyer.com true +kpebetka.net true +kraken.io true +kredite.sale true +kredite24.de true +ks-watch.de true +kuppingercole.com true +kupschke.net true +kura.io true +labaia.info true +laf.in.net true +lagerauftrag.info true +lancejames.com true +lapetition.be true +lasst-uns-beten.de true +lastpass.com false +laukstein.com true +launchkey.com true +lavalite.de true +lavval.com true +lb-toner.de true +leadbook.ru true +leakedminecraft.net true +leanclub.org true +ledhouse.sk true +legoutdesplantes.be true +leibniz-remscheid.de true +leifdreizler.com true +lellyboi.ml true +lence.net true +leninalbertop.com.ve true +leonardcamacho.me true +leonax.net true +leonklingele.de true +les-corsaires.net true +libfte.org true +libraryfreedomproject.org true +lichtspot.de true +liebel.org true +light.mail.ru true +lighting-centres.co.uk true +lillpopp.eu true +lilpwny.com true +limitededitioncomputers.com true +limitededitionsolutions.com true +limpid.nl true +lingolia.com true +linode.com false +linorman1997.me true +linux-admin-california.com true +linx.li true +linx.net true +lists.mayfirst.org false +lists.stg.fedoraproject.org true +livej.am true +livekaarten.nl true +ljs.io true +lloyd-day.me true +lmmtfy.io true +lnx.li true +lobste.rs true +lockify.com true +lodash.com true +loenshotel.de true +loftboard.eu true +logentries.com false +login.corp.google.com true +login.launchpad.net true +login.persona.org true +login.sapo.pt true +login.ubuntu.com true +login.xero.com false +login.yahoo.com false +lolicore.ch true +lookout.com false +lookyman.net true +lookzook.com true +lore.azurewebsites.net true +ludwig.im true +luelistan.net true +lumi.do false +luneta.nearbuysystems.com false +luxwatch.com true +lymia.moe true +lyst.co.uk true +m.facebook.com false +m.mail.ru true +m0wef.uk true +maartenvandekamp.nl true +mach-politik.ch true +madars.org true +madeitwor.se true +mafamane.com true +maff.scot false +magneticanvil.com true +mahamed91.pw true +mail-settings.google.com true +mail.de true +mail.google.com true +mail.yahoo.com false +mailbox.org true +mailmag.net true +makeitdynamic.com true +makeyourlaws.org true +mall.cz true +mall.hu true +mall.pl true +mall.sk true +malnex.de true +malwre.io true +mammaw.com true +man3s.jp true +manage.zenpayroll.com false +manageprojects.com true +manager.linode.com false +mandala-ausmalbilder.de true +manicode.com true +markayapilandirma.com true +market.android.com true +markhaehnel.de true +markusueberallassetmanagement.de true +marshut.net true +massivum.de false +masters.black true +matatall.com false +mathiasbynens.be true +matteomarescotti.it true +mattfin.ch true +mattmccutchen.net true +mattsvensson.com true +max.gov true +maximelouet.me true +mbasic.facebook.com false +mbp.banking.co.at false +mcard.vn true +mccrypto.de true +mcnext.net true +md5file.com true +mdfnet.se false +me.net.nz true +meamod.com true +mebio.us true +medallia.io true +mediacru.sh true +medium.com true +medovea.ru true +medtehnika.ua true +meetfinch.com true +meetings2.com true +mega.co.nz true +megaplan.cz true +megaplan.ru true +mehmetince.net true +meinebo.it true +members.mayfirst.org false +members.nearlyfreespeech.net false +mercuryamericas.com true +meritz.rocks true +mertcangokgoz.com true +metrobriefs.com true +mevs.cz true +mh-bloemen.co.jp true +miasarafina.de true +michalspacek.cz true +miconcinemas.com true +mig5.net true +mijn-email.org true +mike-bland.com true +miketabor.com true +mikewest.org true +miku.hatsune.my false +mim.properties true +mimeit.de true +mimovrste.com true +mindcoding.ro true +mindoktor.se true +minecraftvoter.com true +mineover.es true +minez-nightswatch.com false +minikneet.com true +minnesotadata.com true +mironet.cz true +miskatonic.org true +miss-inventory.co.uk true +mister.hosting true +mitell.jp false +mittenhacks.com true +mjanja.ch true +mkcert.org true +mkw.st true +mnsure.org true +mobilcom-debitel-empfehlen.de true +mobile.usaa.com false +mobilux.lv true +mobobe.com true +modeldimension.com true +mokote.com true +mondwandler.de true +morethanadream.lv true +moriz.de true +moriz.net true +mothereff.in true +mountainmusicpromotions.com true +mountainroseherbs.com true +movlib.org true +mp3juices.is true +mpreserver.com true +mqas.net true +mr-hosting.com true +msa-aesch.ch true +msc-seereisen.net true +mtau.com true +mthode.org true +mths.be true +mtouch.facebook.com false +mudcrab.us true +mujadin.se true +multigamecard.com true +munich-rage.de true +munki.org true +munuc.org true +musi.cx true +musicgamegalaxy.de true +musmann.io true +mustika.cf true +mutamatic.com true +mutantmonkey.in true +mutantmonkey.info true +mutantmonkey.sexy true +mvno.io true +mvsecurity.nl true +mwe.st false +my.onlime.ch false +my.xero.com false +myaccount.google.com true +mygadgetguardian.lookout.com false +mygretchen.de true +mykontool.de true +mylookout.com false +myni.io true +mynigma.org true +myplaceonline.com true +myprintcard.de true +myvirtualserver.com true +nachsendeauftrag.net true +nachsenden.info true +naiharngym.com true +nameid.org true +namepros.com true +nan.zone true +nanderson.me true +narodniki.com true +nationalpriorities.org true +nayahe.ru true +nbl.org.tw true +nctx.co.uk true +ndarville.com true +nectarleaf.com true +neg9.org false +neilwynne.com false +neko.li true +nella-project.org true +nellacms.com true +nellacms.org true +nellafw.org true +nerven.se true +net-safe.info true +netbox.cc true +netera.se true +netrelay.email true +netrider.net.au true +newstarnootropics.com true +nextend.net true +ng-security.com true +nginxnudes.com true +nicolaw.uk true +nieselregen.com true +niloxy.com true +nmctest.net true +nmd.so true +nodari.com.ar true +noemax.com true +noob-box.net true +nopex.no true +northernmuscle.ca true +nos-oignons.net true +nostraforma.com false +notalone.gov true +nouvelle-vague-saint-cast.fr true +novacoast.com true +nowhere.dk true +npw.net true +nsboutique.com true +nu3.at true +nu3.ch true +nu3.co.uk true +nu3.com true +nu3.de true +nu3.dk true +nu3.fi true +nu3.fr true +nu3.no true +nu3.se true +null.tips true +nutsandboltsmedia.com true +nuvini.com true +nwa.xyz true +nwgh.org true +nymphetomania.net true +oakslighting.co.uk true +ocrami.us true +offshore-firma.org true +oguya.ch true +ohling.org true +ohnemusik.com true +okmx.de true +olivierlemoal.fr true +omitech.co.uk true +onedot.nl true +onedrive.com true +onedrive.live.com false +onsitemassageco.com true +ooonja.de true +openacademies.com true +oplop.appspot.com true +opsmate.com false +optimus.io true +orbograph-hrcm.com true +orcahq.com true +orhideous.name true +oscarvk.ch true +osquery.io true +osterkraenzchen.de true +otakuworld.de true +ouvirmusica.com.br true +ovenapp.io true +oversight.io true +ownmovies.fr true +p.linode.com false +packagist.org false +pactf.com true +pajonzeck.de true +palava.tv true +pap.la false +parent5446.us true +partyvan.eu true +partyvan.it true +partyvan.nl true +partyvan.se true +passphrase.today true +passport.yandex.by true +passport.yandex.com true +passport.yandex.com.tr true +passport.yandex.kz true +passport.yandex.ru true +passport.yandex.ua true +passwd.io true +password.codes true +passwords.google.com true +pasta-factory.co.il true +paste.linode.com false +pastebin.linode.com false +patechmasters.com true +patriksimek.cz true +patt.us true +pauladamsmith.com true +paulschreiber.com true +pay.gigahost.dk true +paymentaccuracy.gov true +payments-reference.org true +paymill.com true +paymill.de true +paypal.com false +payroll.xero.com false +pbprint.ru false +pclob.gov true +pdf.yt true +peercraft.com true +pentesterlab.com true +perfectionis.me true +personaldatabasen.no true +pestici.de true +petplum.com true +petrolplus.ru true +pharmaboard.de true +phil.tw true +philosopherswool.com true +phoenix.dj true +phoenixlogan.com true +phryanjr.com false +phurl.de true +pi-supply.com true +picksin.club true +picsto.re true +pieperhome.de true +pierre-schmitz.com true +pieterhordijk.com true +pijuice.com true +piratedb.com true +piratedot.com true +pirateproxy.sx true +pixel.facebook.com false +pixi.me true +play.google.com true +plothost.com true +plus.google.com false +plus.sandbox.google.com false +plzenskybarcamp.cz true +pmg-offshore-company.com true +pmg-purchase.com true +pmg-purchase.net true +poedgirl.com true +pollpodium.nl true +polymathematician.com true +polypho.nyc true +ponythread.com true +portal.tirol.gv.at true +posteo.de false +postfinance.ch true +posttigo.com true +prakharprasad.com true +prefontaine.name true +preissler.co.uk true +preloaded-hsts.badssl.com true +presidentials2016.com true +privategiant.com true +profiles.google.com true +progressiveplanning.com true +projectascension.io true +projektzentrisch.de true +prontolight.com true +proofwiki.org true +propagandism.org true +prospo.co true +prowhisky.de true +proximato.com true +proxybay.club true +proxybay.co true +proxybay.info true +ptn.moscow true +puac.de true +pubkey.is true +publications.qld.gov.au false +puiterwijk.org true +pult.co false +purewebmasters.com false +pwd.ovh true +pypa.io true +pypi.python.org true +python.org false +qa.fedoraproject.org true +qa.stg.fedoraproject.org true +qetesh.de true +qualityhomesystems.com true +quebecmailbox.com true +quli.nl true +quuz.org true +r3s1stanc3.me true +rad-route.de true +radiormi.com true +rafaelcz.de true +ragingserenity.com true +railgun.ac true +raiseyourflag.com true +ramsor-gaming.de true +rasing.me true +raspass.me true +ravchat.com true +rawstorieslondon.com true +raydobe.me false +raymii.org true +reaconverter.com true +red-t-shirt.ru true +redirect.fedoraproject.org true +redirect.stg.fedoraproject.org true +redletter.link true +redlink.de true +redteam-pentesting.de true +reedloden.com true +refundo.cz true +refundo.sk true +reg.ru false +release-monitoring.org true +reliable-mail.de true +renem.net true +report-uri.io true +research.facebook.com false +research.md true +residentsinsurance.co.uk true +resources.flowfinity.com true +reviews.anime.my true +riccy.org true +richiemail.net true +ricochet.im true +riesenmagnete.de true +rika.me true +rippleunion.com true +rischard.org true +rlalique.com true +rmmanfredi.com true +robertof.ovh true +robinadr.com true +robinsonyu.com true +robteix.com true +robtex.com true +rodosto.com true +roeper.party true +roland.io true +romab.com true +roman-pavlik.cz true +romans-place.me.uk true +romulusapp.com false +room-checkin24.de true +roosterpgplus.nl true +roots.io true +roquecenter.org true +rosenkeller.org true +rotunneling.net true +roundcube.mayfirst.org false +royalacademy.org.uk true +rpy.xyz true +rssr.se true +ru-sprachstudio.ch true +rubecodeberg.com true +rubendv.be true +rubyshop.nl true +rudloff.pro true +rusadmin.biz true +ruudkoot.nl true +rws-vertriebsportal.de true +ryan-goldstein.com true +s-c.se true +sabahattin-gucukoglu.com true +safescan.com true +sagerus.com true +sageth.com true +saintsrobotics.com true +sakaki.anime.my true +salaervergleich.com true +sale4ru.ru true +salserocafe.com true +samba.org true +samfunnet.no false +samizdat.cz true +samuelkeeley.com true +sanatfilan.com false +sandbox.mydigipass.com false +sarahlicity.co.uk true +saulchristie.com true +save.gov true +saveaward.gov true +savvytime.com true +schachburg.de true +schokokeks.org true +schreiber-netzwerk.eu true +schreibnacht.de true +schwarzer.it true +sciencex.com true +scotthel.me true +scotthelme.co.uk true +scoutdb.ch true +scrambl.is true +scrambler.in false +scrap.tf true +screenlight.tv true +scribe.systems true +script.google.com true +sdsl-speedtest.de true +search-one.de true +sec.gd true +secretserveronline.com true +secure.facebook.com false +securedrop.org true +securesuisse.ch true +securify.nl true +security-carpet.com true +security.google.com true +securityheaders.com true +securitysnobs.com false +secuvera.de true +seifried.org true +sellocdn.com true +servergno.me true +servertastic.com true +servethecity-karlsruhe.de false +setuid.io true +seyahatsagliksigortalari.com true +sh-network.de true +shaaaaaaaaaaaaa.com true +shadex.net true +shakepeers.org true +shamka.ru true +shanewadleigh.com true +shasso.com true +shellsec.pw true +shenyuqi.com true +sherbers.de true +shiinko.com false +shipard.com true +shodan.io true +shopontarget.com true +shortdiary.me true +sidium.de true +siewert-kau.de true +sigterm.sh true +sikayetvar.com true +silentcircle.com false +simbolo.co.uk false +simple.com false +simpletax.ca false +simplia.cz true +simplystudio.com true +siraweb.org true +siriad.com true +sites.google.com true +sitesko.de true +sitesten.com true +sizzle.co.uk true +sjoorm.com true +skeeley.com true +skhosting.eu true +skogsbruket.fi true +skogskultur.fi true +skydrive.live.com false +slack-files.com true +slack.com true +slattery.co true +sleio.com true +slever.cz true +slevomat.cz true +slidebatch.com true +slope.haus true +slse.ca true +smartcleaningcenter.nl true +smartcoin.com.br true +smartlend.se true +smartship.co.jp true +smith.is true +snailing.org true +snakehosting.dk true +snazel.co.uk true +sneezry.com true +sny.no true +soccergif.com true +soci.ml true +sockeye.cc true +soia.ca true +solihullcarnival.co.uk true +solihulllionsclub.org.uk true +sorz.org true +souki.cz true +soulfulglamour.uk true +soulogic.com true +sour.is true +sourceway.de true +southside-crew.com true +souvik.me true +spartantheatre.org true +spawn.cz true +speedcounter.net true +spencerbaer.com true +spideroak.com true +spongepowered.org true +spreadsheets.google.com true +spreed.me true +sprueche-zum-valentinstag.de true +sprueche-zur-geburt.info true +sprueche-zur-hochzeit.de true +sprueche-zur-konfirmation.de true +spyroszarzonis.com true +squareup.com false +srevilak.net true +sro.center true +ssl.google-analytics.com true +sslmate.com true +stablelib.com true +stage.wepay.com false +standardssuck.org true +starapple.nl true +static.wepay.com false +staticanime.net false +stationary-traveller.eu true +stereo.lu true +stereochro.me true +stesti.cz true +stevegrav.es true +steventress.com true +stewartremodelingadvantage.com true +sticklerjs.org true +stirling.co true +stocktrade.de false +storedsafe.com true +stormhub.org true +strasweb.fr false +stretchmyan.us true +stripe.com true +strongest-privacy.com true +stuartbaxter.co false +studienportal.eu true +studydrive.net true +stulda.cz true +subeesu.com true +subrosa.io true +sufix.cz true +suite73.org true +sunjaydhama.com true +suos.io true +supplies24.at true +supplies24.es true +support.mayfirst.org false +surkatty.org true +survivalmonkey.com true +svager.cz true +swehack.org false +sychov.pro true +sylaps.com true +sysctl.se true +sysdb.io true +syss.de true +t23m-navi.jp false +tadigitalstore.com true +tageau.com true +taken.pl true +talideon.com true +talk.google.com true +talkgadget.google.com true +tallr.se true +tallshoe.com true +tas2580.net true +taskotron.fedoraproject.org true +taskotron.stg.fedoraproject.org true +tatort-fanpage.de true +tauchkater.de true +tbspace.de true +tcgrepublic.com true +tdelmas.ovh true +tdrs.info true +teachforcanada.ca true +teamnorthgermany.de true +teamupturn.com true +techhipster.net true +techhub.ml true +techllage.com true +techloaner.com true +technotonic.com.au false +tegelsensanitaironline.nl true +tekshrek.com true +tempus-aquilae.de true +tent.io true +terraelectronica.ru true +terraweb.net true +terrax.info true +terrax.net true +terrty.net true +testsuite.org true +texte-zur-taufe.de true +thca.ca true +theamp.com true +thebimhub.com true +thecoffeehouse.xyz true +thecustomizewindows.com true +theescapistswiki.com true +thefrozenfire.com true +thehiddenbay.net true +themoep.at true +thepaymentscompany.com true +thepiratebay.al true +therapynotes.com true +thetomharling.com true +theunitedstates.io true +theweilai.com true +thomastimepieces.com.au true +thouni.de true +thumbtack.com true +thusoy.com true +thyngster.com false +tickopa.co.uk true +tid.jp true +timmy.ws true +timotrans.de true +timotrans.eu true +timtaubert.de true +tinfoilsecurity.com false +tinkertry.com false +tinte24.de true +tintenfix.net true +tipps-fuer-den-haushalt.de true +tittelbach.at true +titties.ml true +tls.li true +tmtopup.com true +tno.io true +tobias-kluge.de true +todesschaf.org true +todoist.com true +tollsjekk.no true +tom.horse true +tomfisher.eu true +tomharling.co.uk true +tomharling.uk true +tomrichards.net true +tomvote.com true +toner24.at true +toner24.co.uk true +toner24.es true +toner24.fr true +toner24.it true +toner24.nl true +toner24.pl true +tonerdepot.de true +tonerjet.at true +tonerjet.co.uk true +tonerklick.de true +tonerkurier.de true +tonermaus.de true +tonermonster.de true +tonex.de true +tonex.nl true +tonytan.cn true +tonywebster.com true +topbargains.com.au true +topodin.com true +topshelfguild.com true +toptexture.com true +tor2web.org true +tormentedradio.com true +torproject.org false +torquato.de false +toshnix.com true +totem-eshop.cz true +touch.facebook.com false +touch.mail.ru true +tox.im true +tpbproxy.co true +traas.org true +tracktivity.com.au true +translate.fedoraproject.org true +translate.googleapis.com true +translate.stg.fedoraproject.org true +trashnothing.com true +trauertexte.info true +tresorit.com true +tribaldos.com true +tribut.de true +ts3.consulting true +tuamoronline.com true +tucuxi.org true +tuitle.com true +tunebitfm.de true +tuxplace.nl true +twentymilliseconds.com true +twisto.cz true +twitter.com false +twitteroauth.com true +twofactorauth.org true +twolinepassbrewing.com true +typingrevolution.com true +uae-company-service.com true +ub3rk1tten.com false +ubanquity.com true +ubertt.org true +ucfirst.nl true +ukdefencejournal.org.uk true +ukhas.net true +ukrainians.ch true +ulabox.com true +unison.com true +unitedadmins.com true +unknownphenomena.net true +unravel.ie true +unterfrankenclan.de true +uonstaffhub.com true +uow.ninja true +upitnik.rs true +upload.facebook.com false +uptrends.com true +uptrends.de true +usaa.com false +uscntalk.com true +uspsoig.gov true +utilityapi.com true +utleieplassen.no true +vaddder.com true +vasanth.org true +vbh2o.com true +vechkasov.ru true +venicerealdeal.com true +vhost.co.id true +viasinc.com false +vijos.org true +visionless.me false +vitrado.de true +vmoagents.com false +vocaloid.my true +voicesuk.co.uk true +vomitb.in true +vortexhobbies.com true +votocek.cz true +votockova.cz true +vox.vg true +vpnzoom.com true +vrobert.fr false +vrtak-cz.net true +vserver-preis-vergleich.de true +vyplnto.cz true +vzk.io false +w-spotlight.appspot.com true +wallet.google.com true +walnutgaming.co.uk true +walnutgaming.com true +warrencreative.com false +watsonhall.uk true +wbg-vs.de true +wearvr.com true +webandmore.de false +webandwords.com.au true +webassadors.com false +webcollect.org.uk true +webeau.com true +webfilings-eu-mirror.appspot.com true +webfilings-eu.appspot.com true +webfilings-mirror-hrd.appspot.com true +webfilings.appspot.com true +weblogzwolle.nl true +webmail.gigahost.dk false +webmail.onlime.ch false +webmail.schokokeks.org false +webmaniabr.com true +webmarketingfestival.it true +webogram.org true +webrebels.org true +websenat.de true +webswitch.io true +webtalis.nl true +webtiles.co.uk true +webtrh.cz true +weggeweest.nl true +welches-kinderfahrrad.de true +welpy.com false +wepay.com false +wepay.in.th true +wesecom.com true +wesleyharris.ca true +wettertoertchen.com true +wevahoo.com true +wf-bigsky-master.appspot.com true +wf-demo-eu.appspot.com true +wf-demo-hrd.appspot.com true +wf-dogfood-hrd.appspot.com true +wf-pentest.appspot.com true +wf-staging-hr.appspot.com true +wf-training-hrd.appspot.com true +wf-training-master.appspot.com true +wf-trial-hrd.appspot.com true +whatwg.org true +whd-guide.de true +when-release.ru true +when.fm true +wherephoto.com true +whitestagforge.com true +whocalld.com true +whonix.org true +widememory.com false +wieninternational.at true +wifirst.net true +wiki.python.org true +wildbee.org true +wilf1rst.com true +williamsapiens.com true +williamsonshore.com true +willnorris.com true +wills.co.tt true +winhistory-forum.net true +wisv.ch true +wit.ai true +wondershift.biz true +wootton95.com true +worldcubeassociation.org true +wownmedia.com true +wpletter.de true +writeapp.me false +wtfismyip.com true +wubthecaptain.eu true +wunderlist.com true +wundi.net true +wurzelzwerg.net true +wvr-law.de true +www.aclu.org false +www.airbnb.com true +www.apollo-auto.com true +www.banking.co.at false +www.braintreepayments.com false +www.capitainetrain.com false +www.cyveillance.com true +www.dropbox.com true +www.dropcam.com false +www.entropia.de false +www.eternalgoth.co.uk true +www.etsy.com true +www.evernote.com false +www.facebook.com false +www.gamesdepartment.co.uk false +www.getcloak.com false +www.gmail.com false +www.googlemail.com false +www.gov.uk false +www.grc.com false +www.healthcare.gov false +www.heliosnet.com true +www.honeybadger.io false +www.intercom.io false +www.irccloud.com false +www.lastpass.com false +www.linode.com false +www.lookout.com false +www.makeyourlaws.org true +www.mydigipass.com false +www.mylookout.com false +www.noisebridge.net false +www.opsmate.com true +www.paypal.com false +www.python.org true +www.roddis.net true +www.schokokeks.org true +www.simbolo.co.uk false +www.simple.com false +www.therapynotes.com true +www.tinfoilsecurity.com false +www.torproject.org false +www.twitter.com false +www.usaa.com false +www.viasinc.com true +www.wepay.com false +www.zenpayroll.com false +wzrd.in true +wzyboy.org true +x.io true +xbrlsuccess.appspot.com true +xcoop.me true +xenesisziarovky.sk true +xf-liam.com true +xho.me true +xiaolvmu.me true +xn--maraa-rta.org true +xpd.se true +xps2pdf.co.uk true +xtrim.ru true +xuntier.ch true +y-o-w.com true +yafuoku.ru true +yahvehyireh.com true +yamaken.jp true +yanovich.net true +yaporn.tv false +yello.website true +yenniferallulli.com true +yenniferallulli.de true +yenniferallulli.es true +yenniferallulli.moda true +yenniferallulli.nl true +yetii.net true +yksityisyydensuoja.fi true +yokeepo.com true +yorcom.nl true +youdowell.com true +yoursecondphone.co true +ypart.eu true +yunzhu.li true +yunzhu.org true +z.ai true +zalan.do true +zapier.com true +zbasenem.pl true +zenpayroll.com false +zentraler-kreditausschuss.de true +zentralwolke.de true +zeplin.io false +zeropush.com true +zhang-hao.com true +zhovner.com true +zifb.in true +zixiao.wang true +zlatosnadno.cz true +zlavomat.sk true +zotero.org true +zravypapir.cz true diff --git a/src/html.cc b/src/html.cc index fe861ce7..75d1820f 100644 --- a/src/html.cc +++ b/src/html.cc @@ -26,6 +26,7 @@ #include "msg.h" #include "binaryconst.h" #include "colors.h" +#include "html_charrefs.h" #include "utf8.hh" #include "misc.h" @@ -356,17 +357,32 @@ bool a_Html_tag_set_valign_attr(DilloHtml *html, const char *tag, int tagsize) /* - * Create and add a new Textblock to the current Textblock + * Create and add a new Textblock to the current Textblock. Typically + * only one of addBreaks and addBreakOpt is true. */ -static void Html_add_textblock(DilloHtml *html, int space) +static void Html_add_textblock(DilloHtml *html, bool addBreaks, int breakSpace, + bool addBreakOpt) { Textblock *textblock = new Textblock (prefs.limit_text_width); - HT2TB(html)->addParbreak (space, html->wordStyle ()); - HT2TB(html)->addWidget (textblock, html->style ()); - HT2TB(html)->addParbreak (space, html->wordStyle ()); + if (addBreaks) + HT2TB(html)->addParbreak (breakSpace, html->wordStyle ()); + + HT2TB(html)->addWidget (textblock, html->style ()); /* Works also for floats + etc. */ + if (addBreakOpt) + HT2TB(html)->addBreakOption (html->style (), false); + + if (addBreaks) + HT2TB(html)->addParbreak (breakSpace, html->wordStyle ()); S_TOP(html)->textblock = html->dw = textblock; - S_TOP(html)->hand_over_break = true; + if (addBreaks) + S_TOP(html)->hand_over_break = true; +} + +static bool Html_will_textblock_be_out_of_flow(DilloHtml *html) +{ + return HT2TB(html)->isStyleOutOfFlow (html->style ()); } /* @@ -788,113 +804,16 @@ void a_Html_stash_init(DilloHtml *html) dStr_truncate(html->Stash, 0); } -/* Entities list from the HTML 4.01 DTD */ -typedef struct { - const char *entity; - int isocode; -} Ent_t; - -#define NumEnt 252 -static const Ent_t Entities[NumEnt] = { - {"AElig",0306}, {"Aacute",0301}, {"Acirc",0302}, {"Agrave",0300}, - {"Alpha",01621},{"Aring",0305}, {"Atilde",0303}, {"Auml",0304}, - {"Beta",01622}, {"Ccedil",0307}, {"Chi",01647}, {"Dagger",020041}, - {"Delta",01624},{"ETH",0320}, {"Eacute",0311}, {"Ecirc",0312}, - {"Egrave",0310},{"Epsilon",01625},{"Eta",01627}, {"Euml",0313}, - {"Gamma",01623},{"Iacute",0315}, {"Icirc",0316}, {"Igrave",0314}, - {"Iota",01631}, {"Iuml",0317}, {"Kappa",01632}, {"Lambda",01633}, - {"Mu",01634}, {"Ntilde",0321}, {"Nu",01635}, {"OElig",0522}, - {"Oacute",0323},{"Ocirc",0324}, {"Ograve",0322}, {"Omega",01651}, - {"Omicron",01637},{"Oslash",0330},{"Otilde",0325},{"Ouml",0326}, - {"Phi",01646}, {"Pi",01640}, {"Prime",020063},{"Psi",01650}, - {"Rho",01641}, {"Scaron",0540}, {"Sigma",01643}, {"THORN",0336}, - {"Tau",01644}, {"Theta",01630}, {"Uacute",0332}, {"Ucirc",0333}, - {"Ugrave",0331},{"Upsilon",01645},{"Uuml",0334}, {"Xi",01636}, - {"Yacute",0335},{"Yuml",0570}, {"Zeta",01626}, {"aacute",0341}, - {"acirc",0342}, {"acute",0264}, {"aelig",0346}, {"agrave",0340}, - {"alefsym",020465},{"alpha",01661},{"amp",38}, {"and",021047}, - {"ang",021040}, {"aring",0345}, {"asymp",021110},{"atilde",0343}, - {"auml",0344}, {"bdquo",020036},{"beta",01662}, {"brvbar",0246}, - {"bull",020042},{"cap",021051}, {"ccedil",0347}, {"cedil",0270}, - {"cent",0242}, {"chi",01707}, {"circ",01306}, {"clubs",023143}, - {"cong",021105},{"copy",0251}, {"crarr",020665},{"cup",021052}, - {"curren",0244},{"dArr",020723}, {"dagger",020040},{"darr",020623}, - {"deg",0260}, {"delta",01664}, {"diams",023146},{"divide",0367}, - {"eacute",0351},{"ecirc",0352}, {"egrave",0350}, {"empty",021005}, - {"emsp",020003},{"ensp",020002}, {"epsilon",01665},{"equiv",021141}, - {"eta",01667}, {"eth",0360}, {"euml",0353}, {"euro",020254}, - {"exist",021003},{"fnof",0622}, {"forall",021000},{"frac12",0275}, - {"frac14",0274},{"frac34",0276}, {"frasl",020104},{"gamma",01663}, - {"ge",021145}, {"gt",62}, {"hArr",020724}, {"harr",020624}, - {"hearts",023145},{"hellip",020046},{"iacute",0355},{"icirc",0356}, - {"iexcl",0241}, {"igrave",0354}, {"image",020421},{"infin",021036}, - {"int",021053}, {"iota",01671}, {"iquest",0277}, {"isin",021010}, - {"iuml",0357}, {"kappa",01672}, {"lArr",020720}, {"lambda",01673}, - {"lang",021451},{"laquo",0253}, {"larr",020620}, {"lceil",021410}, - {"ldquo",020034},{"le",021144}, {"lfloor",021412},{"lowast",021027}, - {"loz",022712}, {"lrm",020016}, {"lsaquo",020071},{"lsquo",020030}, - {"lt",60}, {"macr",0257}, {"mdash",020024},{"micro",0265}, - {"middot",0267},{"minus",021022},{"mu",01674}, {"nabla",021007}, - {"nbsp",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}, - {"oplus",021225},{"or",021050}, {"ordf",0252}, {"ordm",0272}, - {"oslash",0370},{"otilde",0365}, {"otimes",021227},{"ouml",0366}, - {"para",0266}, {"part",021002}, {"permil",020060},{"perp",021245}, - {"phi",01706}, {"pi",01700}, {"piv",01726}, {"plusmn",0261}, - {"pound",0243}, {"prime",020062},{"prod",021017}, {"prop",021035}, - {"psi",01710}, {"quot",34}, {"rArr",020722}, {"radic",021032}, - {"rang",021452},{"raquo",0273}, {"rarr",020622}, {"rceil",021411}, - {"rdquo",020035},{"real",020434},{"reg",0256}, {"rfloor",021413}, - {"rho",01701}, {"rlm",020017}, {"rsaquo",020072},{"rsquo",020031}, - {"sbquo",020032},{"scaron",0541},{"sdot",021305}, {"sect",0247}, - {"shy",0255}, {"sigma",01703}, {"sigmaf",01702},{"sim",021074}, - {"spades",023140},{"sub",021202},{"sube",021206}, {"sum",021021}, - {"sup",021203}, {"sup1",0271}, {"sup2",0262}, {"sup3",0263}, - {"supe",021207},{"szlig",0337}, {"tau",01704}, {"there4",021064}, - {"theta",01670},{"thetasym",01721},{"thinsp",020011},{"thorn",0376}, - {"tilde",01334},{"times",0327}, {"trade",020442},{"uArr",020721}, - {"uacute",0372},{"uarr",020621}, {"ucirc",0373}, {"ugrave",0371}, - {"uml",0250}, {"upsih",01722}, {"upsilon",01705},{"uuml",0374}, - {"weierp",020430},{"xi",01676}, {"yacute",0375}, {"yen",0245}, - {"yuml",0377}, {"zeta",01666}, {"zwj",020015}, {"zwnj",020014} -}; - - -/* - * Comparison function for binary search - */ -static int Html_entity_comp(const void *a, const void *b) -{ - return strcmp(((Ent_t *)a)->entity, ((Ent_t *)b)->entity); -} - -/* - * Binary search of 'key' in entity list - */ -static int Html_entity_search(char *key) -{ - Ent_t *res, EntKey; - - EntKey.entity = key; - res = (Ent_t*) bsearch(&EntKey, Entities, NumEnt, - sizeof(Ent_t), Html_entity_comp); - if (res) - return (res - Entities); - return -1; -} - /* * This is M$ non-standard "smart quotes" (w1252). Now even deprecated by them! * * SGML for HTML4.01 defines c >= 128 and c <= 159 as UNUSED. - * TODO: Probably I should remove this hack, and add a HTML warning. --Jcid + * TODO: Probably I should remove this hack. --Jcid */ -static int Html_ms_stupid_quotes_2ucs(int isocode) +static int Html_ms_stupid_quotes_2ucs(int codepoint) { int ret; - switch (isocode) { + switch (codepoint) { case 145: case 146: ret = '\''; break; case 147: @@ -902,130 +821,241 @@ static int Html_ms_stupid_quotes_2ucs(int isocode) case 149: ret = 176; break; case 150: case 151: ret = '-'; break; - default: ret = isocode; break; + default: ret = codepoint; break; } return ret; } /* - * Given an entity, return the UCS character code. - * Returns a negative value (error code) if not a valid entity. - * - * The first character *token is assumed to be == '&' - * - * For valid entities, *entsize is set to the length of the parsed entity. + * Parse a numeric character reference (e.g., "/" or "/"). + * The "&#" has already been consumed. */ -static int Html_parse_entity(DilloHtml *html, const char *token, - int toksize, int *entsize) +static const char *Html_parse_numeric_charref(DilloHtml *html, char *tok, + bool_t is_attr, int *entsize) { - int isocode, i; - char *tok, *s, c; + static char buf[5]; + char *s = tok; + int n, codepoint = -1; - token++; - tok = s = toksize ? dStrndup(token, (uint_t)toksize) : dStrdup(token); - - isocode = -1; - - if (*s == '#') { - /* numeric character reference */ - errno = 0; - if (*++s == 'x' || *s == 'X') { - if (isxdigit(*++s)) { - /* strtol with base 16 accepts leading "0x" - we don't */ - if (*s == '0' && s[1] == 'x') { - s++; - isocode = 0; - } else { - isocode = strtol(s, &s, 16); - } + errno = 0; + + if (*s == 'x' || *s == 'X') { + if (isxdigit(*++s)) { + /* strtol with base 16 accepts leading "0x" - we don't */ + if (*s == '0' && s[1] == 'x') { + s++; + codepoint = 0; + } else { + codepoint = strtol(s, &s, 16); } - } else if (isdigit(*s)) { - isocode = strtol(s, &s, 10); } + } else if (isdigit(*s)) { + codepoint = strtol(s, &s, 10); + } + if (errno) + codepoint = -1; - if (!isocode || errno || isocode > 0xffff) { - /* this catches null bytes, errors and codes >= 0xFFFF */ - BUG_MSG("Numeric character reference \"%s\" out of range.", tok); - isocode = -2; + if (*s == ';') + s++; + else { + if (prefs.show_extra_warnings && (html->DocType == DT_XHTML || + (html->DocType == DT_HTML && html->DocTypeVersion <= 4.01f))) { + char c = *s; + *s = '\0'; + BUG_MSG("Character reference '&#%s' lacks ';'.", tok); + *s = c; } - - if (isocode != -1) { - if (*s == ';') - s++; - else if (prefs.show_extra_warnings) - BUG_MSG("Numeric character reference without trailing ';'."); + /* Don't require ';' for old HTML, except that our current heuristic + * is to require it in attributes to avoid cases like "©=1" found + * in URLs. + */ + if (is_attr || html->DocType == DT_XHTML || + (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)) { + return NULL; } - } else if (isalpha(*s)) { - /* character entity reference */ - while (*++s && (isalnum(*s) || strchr(":_.-", *s))) ; - c = *s; - *s = 0; + } + if ((codepoint < 0x20 && codepoint != '\t' && codepoint != '\n' && + codepoint != '\f') || + (codepoint >= 0x7f && codepoint <= 0x9f) || + (codepoint >= 0xd800 && codepoint <= 0xdfff) || codepoint > 0x10ffff || + ((codepoint & 0xfffe) == 0xfffe) || + (!(html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) && + codepoint > 0xffff)) { + /* this catches null bytes, errors, codes out of range, disallowed + * control chars, permanently undefined chars, and surrogates. + */ + char c = *s; + *s = '\0'; + BUG_MSG("Numeric character reference '&#%s' is not valid.", tok); + *s = c; - if ((i = Html_entity_search(tok)) >= 0) { - isocode = Entities[i].isocode; + codepoint = (codepoint >= 145 && codepoint <= 151) ? + Html_ms_stupid_quotes_2ucs(codepoint) : -1; + } + if (codepoint != -1) { + if (codepoint >= 128) { + n = a_Utf8_encode(codepoint, buf); } else { - if (html->DocType == DT_XHTML && !strcmp(tok, "apos")) { - isocode = 0x27; - } else { - if ((html->DocType == DT_HTML && html->DocTypeVersion == 4.01f) || - html->DocType == DT_XHTML) - BUG_MSG("Undefined character entity '%s'.", tok); - isocode = -3; - } + n = 1; + buf[0] = (char) codepoint; + } + assert(n < 5); + buf[n] = '\0'; + *entsize = s-tok+2; + return buf; + } else { + return NULL; + } +} + +/* + * Comparison function for binary search + */ +static int Html_charref_comp(const void *a, const void *b) +{ + return strcmp(((Charref_t *)a)->ref, ((Charref_t *)b)->ref); +} + +/* + * Binary search of 'key' in charref list + */ +static Charref_t *Html_charref_search(char *key) +{ + Charref_t RefKey; + + RefKey.ref = key; + return (Charref_t*) bsearch(&RefKey, Charrefs, NumRef, + sizeof(Charref_t), Html_charref_comp); +} + +/* + * Parse a named character reference (e.g., "&" or "…"). + * The "&" has already been consumed. + */ +static const char *Html_parse_named_charref(DilloHtml *html, char *tok, + bool_t is_attr, int *entsize) +{ + Charref_t *p; + char c; + char *s = tok; + const char *ret = NULL; + + while (*++s && (isalnum(*s) || strchr(":_.-", *s))) ; + c = *s; + *s = '\0'; + if (c != ';') { + if (prefs.show_extra_warnings && (html->DocType == DT_XHTML || + (html->DocType == DT_HTML && html->DocTypeVersion <= 4.01f))) + BUG_MSG("Character reference '&%s' lacks ';'.", tok); + + /* Don't require ';' for old HTML, except that our current heuristic + * is to require it in attributes to avoid cases like "©=1" found + * in URLs. + */ + if (is_attr || html->DocType == DT_XHTML || + (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)) { + return ret; } - if (c == ';') - s++; - else if (prefs.show_extra_warnings) - BUG_MSG("Character entity reference without trailing ';'."); } + if ((p = Html_charref_search(tok))) { + ret = (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) ? + p->html5_str : p->html4_str; + } + + if (!ret && html->DocType == DT_XHTML && !strcmp(tok, "apos")) + ret = "'"; + + *s = c; + if (c == ';') + s++; + + if (!ret) { + c = *s; + *s = '\0'; + BUG_MSG("Undefined character reference '&%s'.", tok); + *s = c; + } *entsize = s-tok+1; - dFree(tok); + return ret; +} - if (isocode >= 145 && isocode <= 151) { - /* TODO: remove this hack. */ - isocode = Html_ms_stupid_quotes_2ucs(isocode); - } else if (isocode == -1 && prefs.show_extra_warnings) +/* + * Given an entity, return the corresponding string. + * Returns NULL if not a valid entity. + * + * The first character *token is assumed to be == '&' + * + * For valid entities, *entsize is set to the length of the parsed entity. + */ +static const char *Html_parse_entity(DilloHtml *html, const char *token, + int toksize, int *entsize, bool_t is_attr) +{ + const char *ret = NULL; + char *tok; + + if (toksize > 50) { + /* In pathological cases, attributes can be megabytes long and filled + * with character references. As of HTML5, the longest defined character + * reference is about 32 bytes long. + */ + toksize = 50; + } + + token++; + tok = dStrndup(token, (uint_t)toksize); + + if (*tok == '#') { + ret = Html_parse_numeric_charref(html, tok+1, is_attr, entsize); + } else if (isalpha(*tok)) { + ret = Html_parse_named_charref(html, tok, is_attr, entsize); + } else if (prefs.show_extra_warnings && + (!(html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f))) { + // HTML5 doesn't mind literal '&'s. BUG_MSG("Literal '&'."); + } + dFree(tok); - return isocode; + return ret; } /* - * Convert all the entities in a token to utf8 encoding. Takes - * a token and its length, and returns a newly allocated string. + * Parse all the entities in a token. Takes the token and its length, and + * returns a newly allocated string. */ char *a_Html_parse_entities(DilloHtml *html, const char *token, int toksize) { const char *esc_set = "&"; - char *new_str, buf[4]; - int i, j, k, n, s, isocode, entsize; - - new_str = dStrndup(token, toksize); - s = strcspn(new_str, esc_set); - if (new_str[s] == 0) - return new_str; - - for (i = j = s; i < toksize; i++) { - if (token[i] == '&' && - (isocode = Html_parse_entity(html, token+i, - toksize-i, &entsize)) >= 0) { - if (isocode >= 128) { - /* multibyte encoding */ - n = a_Utf8_encode(isocode, buf); - for (k = 0; k < n; ++k) - new_str[j++] = buf[k]; + int i, s, entsize; + char *str; + + s = strcspn(token, esc_set); + if (s >= toksize) { + /* no ampersands */ + str = dStrndup(token, toksize); + } else { + Dstr *ds = dStr_sized_new(toksize); + + dStr_append_l(ds, token, s); + + for (i = s; i < toksize; i++) { + const char *entstr; + const bool_t is_attr = FALSE; + + if (token[i] == '&' && + (entstr = Html_parse_entity(html, token+i, toksize-i, &entsize, + is_attr))) { + dStr_append(ds, entstr); + i += entsize-1; } else { - new_str[j++] = (char) isocode; + dStr_append_c(ds, token[i]); } - i += entsize-1; - } else { - new_str[j++] = token[i]; } + str = ds->str; + dStr_free(ds, 0); } - new_str[j] = '\0'; - return new_str; + return str; } /* @@ -1553,7 +1583,7 @@ static int * 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 + * http://lists.dillo.org/pipermail/dillo-dev/2004-October/002300.html * * This is not a full DOCTYPE parser, just enough for what Dillo uses. */ @@ -2017,7 +2047,7 @@ static void Html_tag_content_frameset (DilloHtml *html, { HT2TB(html)->addParbreak (9, html->wordStyle ()); HT2TB(html)->addText("--FRAME--", html->wordStyle ()); - Html_add_textblock(html, 5); + Html_add_textblock(html, true, 5, false); } /* @@ -2094,8 +2124,8 @@ void a_Html_common_image_attrs(DilloHtml *html, const char *tag, int tagsize) { char *width_ptr, *height_ptr; const char *attrbuf; - CssLength l_w = CSS_CREATE_LENGTH(0.0, CSS_LENGTH_TYPE_AUTO); - CssLength l_h = CSS_CREATE_LENGTH(0.0, CSS_LENGTH_TYPE_AUTO); + 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 w = 0, h = 0; if (prefs.show_tooltip && @@ -2128,7 +2158,7 @@ void a_Html_common_image_attrs(DilloHtml *html, const char *tag, int tagsize) */ if (w < 0 || h < 0 || w > IMAGE_MAX_AREA || h > IMAGE_MAX_AREA || - (h > 0 && w > IMAGE_MAX_AREA / h)) { + (h > 0 && w > IMAGE_MAX_AREA / h)) { dFree(width_ptr); dFree(height_ptr); width_ptr = height_ptr = NULL; @@ -2173,14 +2203,16 @@ DilloImage *a_Html_image_new(DilloHtml *html, const char *tag, int tagsize) return NULL; alt_ptr = a_Html_get_attr_wdef(html, tag, tagsize, "alt", NULL); - if ((!alt_ptr || !*alt_ptr) && !prefs.load_images) { + if (!alt_ptr || !*alt_ptr) { dFree(alt_ptr); - alt_ptr = dStrdup("[IMG]"); // Place holder for img_off mode + alt_ptr = dStrdup("[IMG]"); } dw::Image *dw = new dw::Image(alt_ptr); image = a_Image_new(html->dw->getLayout(), (void*)(dw::core::ImgRenderer*)dw, 0); + + a_Image_ref(image); if (HT2TB(html)->getBgColor()) image->bg_color = HT2TB(html)->getBgColor()->getColor(); @@ -2197,10 +2229,10 @@ DilloImage *a_Html_image_new(DilloHtml *html, const char *tag, int tagsize) if (load_now && Html_load_image(html->bw, url, html->page_url, image)) { // hi->image is NULL if dillo tries to load the image immediately hi->image = NULL; + a_Image_unref(image); } else { // otherwise a reference is kept in html->images hi->image = image; - a_Image_ref(image); } dFree(alt_ptr); @@ -2315,6 +2347,7 @@ static void Html_tag_content_img(DilloHtml *html, const char *tag, int tagsize) // multiple inheritance. dw::Image *dwi = (dw::Image*)(dw::core::ImgRenderer*)Image->img_rndr; HT2TB(html)->addWidget(dwi, html->style()); + HT2TB(html)->addBreakOption (html->style (), false); /* Image maps */ if (a_Html_get_attr(html, tag, tagsize, "ismap")) { @@ -2448,7 +2481,6 @@ static void type = UNKNOWN; } if (type == RECTANGLE || type == CIRCLE || type == POLYGON) { - /* TODO: add support for coords in % */ if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "coords"))) { coords = Html_read_coords(html, attrbuf); @@ -2482,8 +2514,6 @@ static void if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "href"))) { url = a_Html_url_new(html, attrbuf, NULL, 0); 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); } @@ -2797,7 +2827,7 @@ static void Html_tag_close_a(DilloHtml *html) static void Html_tag_open_blockquote(DilloHtml *html, const char *tag, int tagsize) { - Html_add_textblock(html, 9); + Html_add_textblock(html, true, 9, false); } /* @@ -3061,7 +3091,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) { - Html_add_textblock(html, 9); + Html_add_textblock(html, true, 9, false); } /* @@ -3262,27 +3292,26 @@ void a_Html_load_stylesheet(DilloHtml *html, DilloUrl *url) dReturn_if (url == NULL || ! prefs.load_stylesheets); _MSG("Html_load_stylesheet: "); - if (a_Capi_get_buf(url, &data, &len)) { + if ((a_Capi_get_flags_with_redirection(url) & CAPI_Completed) && + 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 */ - char *content_type; - - *endq = '\0'; - content_type = dStrconcat("text/css; charset=", data+10, NULL); - *endq = '"'; - a_Capi_unref_buf(url); - a_Capi_set_content_type(url, content_type, "meta"); - dFree(content_type); - a_Capi_get_buf(url, &data, &len); - } + if (strncmp("@charset \"", data, 10) == 0) { + char *endq = strchr(data+10, '"'); + + if (endq && (endq - data <= 51)) { + /* IANA limits charset names to 40 characters */ + char *content_type; + + *endq = '\0'; + content_type = dStrconcat("text/css; charset=", data+10, NULL); + *endq = '"'; + a_Capi_unref_buf(url); + 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); } + html->styleEngine->parse(html, url, data, len, CSS_ORIGIN_AUTHOR); a_Capi_unref_buf(url); } else { /* Fill a Web structure for the cache query */ @@ -3364,8 +3393,13 @@ static void Html_tag_open_base(DilloHtml *html, const char *tag, int tagsize) if (html->InFlags & IN_HEAD) { if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "href"))) { - BaseUrl = a_Html_url_new(html, attrbuf, "", 1); - if (URL_SCHEME_(BaseUrl)) { + bool_t html5 = html->DocType == DT_HTML && + html->DocTypeVersion >= 5.0f; + + BaseUrl = html5 ? a_Html_url_new(html, attrbuf, NULL, 0) : + a_Html_url_new(html, attrbuf, "", 1); + + if (html5 || URL_SCHEME_(BaseUrl)) { /* Pass the URL_SpamSafe flag to the new base url */ a_Url_set_flags( BaseUrl, URL_FLAGS(html->base_url) & URL_SpamSafe); @@ -3474,7 +3508,7 @@ const TagInfo Tags[] = { {"a", B8(011101),'R',2, Html_tag_open_a, NULL, Html_tag_close_a}, {"abbr", B8(010101),'R',2, Html_tag_open_abbr, NULL, NULL}, /* acronym 010101 -- obsolete in HTML5 */ - {"address", B8(010110),'R',2,Html_tag_open_default, NULL, Html_tag_close_par}, + {"address", B8(011110),'R',2,Html_tag_open_default, NULL, Html_tag_close_par}, {"area", B8(010001),'F',0, Html_tag_open_default, Html_tag_content_area, NULL}, {"article", B8(011110),'R',2, Html_tag_open_sectioning, NULL, NULL}, @@ -3674,10 +3708,10 @@ static int Html_needs_optional_close(int old_idx, int cur_idx) } else if (old_idx == i_TR) { /* TR closes TR */ return (cur_idx == i_TR); - } else if (old_idx == i_DD) { + } else if (old_idx == i_DD) { /* DD is closed by DD and DT */ return (cur_idx == i_DD || cur_idx == i_DT); - } else if (old_idx == i_OPTION) { + } else if (old_idx == i_OPTION) { return 1; // OPTION always needs close } @@ -3879,8 +3913,13 @@ static void Html_check_html5_obsolete(DilloHtml *html, int ni) static void Html_display_block(DilloHtml *html) { - //HT2TB(html)->addParbreak (5, html->styleEngine->wordStyle ()); - Html_add_textblock(html, 0); + Html_add_textblock(html, !Html_will_textblock_be_out_of_flow (html), 0, + false /* Perhaps true for widgets oof? */); +} + +static void Html_display_inline_block(DilloHtml *html) +{ + Html_add_textblock(html, false, 0, true); } static void Html_display_listitem(DilloHtml *html) @@ -3985,6 +4024,9 @@ static void Html_process_tag(DilloHtml *html, char *tag, int tagsize) case DISPLAY_BLOCK: Html_display_block(html); break; + case DISPLAY_INLINE_BLOCK: + Html_display_inline_block(html); + break; case DISPLAY_LIST_ITEM: Html_display_listitem(html); break; @@ -3992,7 +4034,6 @@ static void Html_process_tag(DilloHtml *html, char *tag, int tagsize) S_TOP(html)->display_none = true; break; case DISPLAY_INLINE: - case DISPLAY_INLINE_BLOCK: // TODO: implement inline-block default: break; } @@ -4060,7 +4101,7 @@ static const char *Html_get_attr2(DilloHtml *html, const char *attrname, int tag_parsing_flags) { - int i, isocode, entsize, Found = 0, delimiter = 0, attr_pos = 0; + int i, entsize, Found = 0, delimiter = 0, attr_pos = 0; Dstr *Buf = html->attr_data; DilloHtmlTagParsingState state = SEEK_ATTR_START; @@ -4119,16 +4160,12 @@ static const char *Html_get_attr2(DilloHtml *html, state = FINISHED; } else if (tag[i] == '&' && (tag_parsing_flags & HTML_ParseEntities)) { - if ((isocode = Html_parse_entity(html, tag+i, - tagsize-i, &entsize)) >= 0) { - if (isocode >= 128) { - char buf[4]; - int k, n = a_Utf8_encode(isocode, buf); - for (k = 0; k < n; ++k) - dStr_append_c(Buf, buf[k]); - } else { - dStr_append_c(Buf, (char) isocode); - } + const char *entstr; + const bool_t is_attr = TRUE; + + if ((entstr = Html_parse_entity(html, tag+i, tagsize-i, &entsize, + is_attr))) { + dStr_append(Buf, entstr); i += entsize-1; } else { dStr_append_c(Buf, tag[i]); diff --git a/src/html_charrefs.h b/src/html_charrefs.h new file mode 100644 index 00000000..38f3849f --- /dev/null +++ b/src/html_charrefs.h @@ -0,0 +1,2138 @@ +#ifndef HTML_CHARREFS_H +#define HTML_CHARREFS_H + +typedef struct { + const char *ref; + const char *html5_str; + const char *html4_str; +} Charref_t; + +#define NumRef 2125 +static const Charref_t Charrefs[NumRef] = { +{"AElig", "Æ", "Æ"}, +{"AMP", "&", NULL}, +{"Aacute", "Á", "Á"}, +{"Abreve", "Ă", NULL}, +{"Acirc", "Â", "Â"}, +{"Acy", "А", NULL}, +{"Afr", "𝔄", NULL}, +{"Agrave", "À", "À"}, +{"Alpha", "Α", "Α"}, +{"Amacr", "Ā", NULL}, +{"And", "⩓", NULL}, +{"Aogon", "Ą", NULL}, +{"Aopf", "𝔸", NULL}, +{"ApplyFunction", "", NULL}, +{"Aring", "Å", "Å"}, +{"Ascr", "𝒜", NULL}, +{"Assign", "≔", NULL}, +{"Atilde", "Ã", "Ã"}, +{"Auml", "Ä", "Ä"}, +{"Backslash", "∖", NULL}, +{"Barv", "⫧", NULL}, +{"Barwed", "⌆", NULL}, +{"Bcy", "Б", NULL}, +{"Because", "∵", NULL}, +{"Bernoullis", "ℬ", NULL}, +{"Beta", "Β", "Β"}, +{"Bfr", "𝔅", NULL}, +{"Bopf", "𝔹", NULL}, +{"Breve", "˘", NULL}, +{"Bscr", "ℬ", NULL}, +{"Bumpeq", "≎", NULL}, +{"CHcy", "Ч", NULL}, +{"COPY", "©", NULL}, +{"Cacute", "Ć", NULL}, +{"Cap", "⋒", NULL}, +{"CapitalDifferentialD", "ⅅ", NULL}, +{"Cayleys", "ℭ", NULL}, +{"Ccaron", "Č", NULL}, +{"Ccedil", "Ç", "Ç"}, +{"Ccirc", "Ĉ", NULL}, +{"Cconint", "∰", NULL}, +{"Cdot", "Ċ", NULL}, +{"Cedilla", "¸", NULL}, +{"CenterDot", "·", NULL}, +{"Cfr", "ℭ", NULL}, +{"Chi", "Χ", "Χ"}, +{"CircleDot", "⊙", NULL}, +{"CircleMinus", "⊖", NULL}, +{"CirclePlus", "⊕", NULL}, +{"CircleTimes", "⊗", NULL}, +{"ClockwiseContourIntegral", "∲", NULL}, +{"CloseCurlyDoubleQuote", "”", NULL}, +{"CloseCurlyQuote", "’", NULL}, +{"Colon", "∷", NULL}, +{"Colone", "⩴", NULL}, +{"Congruent", "≡", NULL}, +{"Conint", "∯", NULL}, +{"ContourIntegral", "∮", NULL}, +{"Copf", "ℂ", NULL}, +{"Coproduct", "∐", NULL}, +{"CounterClockwiseContourIntegral", "∳", NULL}, +{"Cross", "⨯", NULL}, +{"Cscr", "𝒞", NULL}, +{"Cup", "⋓", NULL}, +{"CupCap", "≍", NULL}, +{"DD", "ⅅ", NULL}, +{"DDotrahd", "⤑", NULL}, +{"DJcy", "Ђ", NULL}, +{"DScy", "Ѕ", NULL}, +{"DZcy", "Џ", NULL}, +{"Dagger", "‡", "‡"}, +{"Darr", "↡", NULL}, +{"Dashv", "⫤", NULL}, +{"Dcaron", "Ď", NULL}, +{"Dcy", "Д", NULL}, +{"Del", "∇", NULL}, +{"Delta", "Δ", "Δ"}, +{"Dfr", "𝔇", NULL}, +{"DiacriticalAcute", "´", NULL}, +{"DiacriticalDot", "˙", NULL}, +{"DiacriticalDoubleAcute", "˝", NULL}, +{"DiacriticalGrave", "`", NULL}, +{"DiacriticalTilde", "˜", NULL}, +{"Diamond", "⋄", NULL}, +{"DifferentialD", "ⅆ", NULL}, +{"Dopf", "𝔻", NULL}, +{"Dot", "¨", NULL}, +{"DotDot", "⃜", NULL}, +{"DotEqual", "≐", NULL}, +{"DoubleContourIntegral", "∯", NULL}, +{"DoubleDot", "¨", NULL}, +{"DoubleDownArrow", "⇓", NULL}, +{"DoubleLeftArrow", "⇐", NULL}, +{"DoubleLeftRightArrow", "⇔", NULL}, +{"DoubleLeftTee", "⫤", NULL}, +{"DoubleLongLeftArrow", "⟸", NULL}, +{"DoubleLongLeftRightArrow", "⟺", NULL}, +{"DoubleLongRightArrow", "⟹", NULL}, +{"DoubleRightArrow", "⇒", NULL}, +{"DoubleRightTee", "⊨", NULL}, +{"DoubleUpArrow", "⇑", NULL}, +{"DoubleUpDownArrow", "⇕", NULL}, +{"DoubleVerticalBar", "∥", NULL}, +{"DownArrow", "↓", NULL}, +{"DownArrowBar", "⤓", NULL}, +{"DownArrowUpArrow", "⇵", NULL}, +{"DownBreve", "̑", NULL}, +{"DownLeftRightVector", "⥐", NULL}, +{"DownLeftTeeVector", "⥞", NULL}, +{"DownLeftVector", "↽", NULL}, +{"DownLeftVectorBar", "⥖", NULL}, +{"DownRightTeeVector", "⥟", NULL}, +{"DownRightVector", "⇁", NULL}, +{"DownRightVectorBar", "⥗", NULL}, +{"DownTee", "⊤", NULL}, +{"DownTeeArrow", "↧", NULL}, +{"Downarrow", "⇓", NULL}, +{"Dscr", "𝒟", NULL}, +{"Dstrok", "Đ", NULL}, +{"ENG", "Ŋ", NULL}, +{"ETH", "Ð", "Ð"}, +{"Eacute", "É", "É"}, +{"Ecaron", "Ě", NULL}, +{"Ecirc", "Ê", "Ê"}, +{"Ecy", "Э", NULL}, +{"Edot", "Ė", NULL}, +{"Efr", "𝔈", NULL}, +{"Egrave", "È", "È"}, +{"Element", "∈", NULL}, +{"Emacr", "Ē", NULL}, +{"EmptySmallSquare", "◻", NULL}, +{"EmptyVerySmallSquare", "▫", NULL}, +{"Eogon", "Ę", NULL}, +{"Eopf", "𝔼", NULL}, +{"Epsilon", "Ε", "Ε"}, +{"Equal", "⩵", NULL}, +{"EqualTilde", "≂", NULL}, +{"Equilibrium", "⇌", NULL}, +{"Escr", "ℰ", NULL}, +{"Esim", "⩳", NULL}, +{"Eta", "Η", "Η"}, +{"Euml", "Ë", "Ë"}, +{"Exists", "∃", NULL}, +{"ExponentialE", "ⅇ", NULL}, +{"Fcy", "Ф", NULL}, +{"Ffr", "𝔉", NULL}, +{"FilledSmallSquare", "◼", NULL}, +{"FilledVerySmallSquare", "▪", NULL}, +{"Fopf", "𝔽", NULL}, +{"ForAll", "∀", NULL}, +{"Fouriertrf", "ℱ", NULL}, +{"Fscr", "ℱ", NULL}, +{"GJcy", "Ѓ", NULL}, +{"GT", ">", NULL}, +{"Gamma", "Γ", "Γ"}, +{"Gammad", "Ϝ", NULL}, +{"Gbreve", "Ğ", NULL}, +{"Gcedil", "Ģ", NULL}, +{"Gcirc", "Ĝ", NULL}, +{"Gcy", "Г", NULL}, +{"Gdot", "Ġ", NULL}, +{"Gfr", "𝔊", NULL}, +{"Gg", "⋙", NULL}, +{"Gopf", "𝔾", NULL}, +{"GreaterEqual", "≥", NULL}, +{"GreaterEqualLess", "⋛", NULL}, +{"GreaterFullEqual", "≧", NULL}, +{"GreaterGreater", "⪢", NULL}, +{"GreaterLess", "≷", NULL}, +{"GreaterSlantEqual", "⩾", NULL}, +{"GreaterTilde", "≳", NULL}, +{"Gscr", "𝒢", NULL}, +{"Gt", "≫", NULL}, +{"HARDcy", "Ъ", NULL}, +{"Hacek", "ˇ", NULL}, +{"Hat", "^", NULL}, +{"Hcirc", "Ĥ", NULL}, +{"Hfr", "ℌ", NULL}, +{"HilbertSpace", "ℋ", NULL}, +{"Hopf", "ℍ", NULL}, +{"HorizontalLine", "─", NULL}, +{"Hscr", "ℋ", NULL}, +{"Hstrok", "Ħ", NULL}, +{"HumpDownHump", "≎", NULL}, +{"HumpEqual", "≏", NULL}, +{"IEcy", "Е", NULL}, +{"IJlig", "IJ", NULL}, +{"IOcy", "Ё", NULL}, +{"Iacute", "Í", "Í"}, +{"Icirc", "Î", "Î"}, +{"Icy", "И", NULL}, +{"Idot", "İ", NULL}, +{"Ifr", "ℑ", NULL}, +{"Igrave", "Ì", "Ì"}, +{"Im", "ℑ", NULL}, +{"Imacr", "Ī", NULL}, +{"ImaginaryI", "ⅈ", NULL}, +{"Implies", "⇒", NULL}, +{"Int", "∬", NULL}, +{"Integral", "∫", NULL}, +{"Intersection", "⋂", NULL}, +{"InvisibleComma", "", NULL}, +{"InvisibleTimes", "", NULL}, +{"Iogon", "Į", NULL}, +{"Iopf", "𝕀", NULL}, +{"Iota", "Ι", "Ι"}, +{"Iscr", "ℐ", NULL}, +{"Itilde", "Ĩ", NULL}, +{"Iukcy", "І", NULL}, +{"Iuml", "Ï", "Ï"}, +{"Jcirc", "Ĵ", NULL}, +{"Jcy", "Й", NULL}, +{"Jfr", "𝔍", NULL}, +{"Jopf", "𝕁", NULL}, +{"Jscr", "𝒥", NULL}, +{"Jsercy", "Ј", NULL}, +{"Jukcy", "Є", NULL}, +{"KHcy", "Х", NULL}, +{"KJcy", "Ќ", NULL}, +{"Kappa", "Κ", "Κ"}, +{"Kcedil", "Ķ", NULL}, +{"Kcy", "К", NULL}, +{"Kfr", "𝔎", NULL}, +{"Kopf", "𝕂", NULL}, +{"Kscr", "𝒦", NULL}, +{"LJcy", "Љ", NULL}, +{"LT", "<", NULL}, +{"Lacute", "Ĺ", NULL}, +{"Lambda", "Λ", "Λ"}, +{"Lang", "⟪", NULL}, +{"Laplacetrf", "ℒ", NULL}, +{"Larr", "↞", NULL}, +{"Lcaron", "Ľ", NULL}, +{"Lcedil", "Ļ", NULL}, +{"Lcy", "Л", NULL}, +{"LeftAngleBracket", "⟨", NULL}, +{"LeftArrow", "←", NULL}, +{"LeftArrowBar", "⇤", NULL}, +{"LeftArrowRightArrow", "⇆", NULL}, +{"LeftCeiling", "⌈", NULL}, +{"LeftDoubleBracket", "⟦", NULL}, +{"LeftDownTeeVector", "⥡", NULL}, +{"LeftDownVector", "⇃", NULL}, +{"LeftDownVectorBar", "⥙", NULL}, +{"LeftFloor", "⌊", NULL}, +{"LeftRightArrow", "↔", NULL}, +{"LeftRightVector", "⥎", NULL}, +{"LeftTee", "⊣", NULL}, +{"LeftTeeArrow", "↤", NULL}, +{"LeftTeeVector", "⥚", NULL}, +{"LeftTriangle", "⊲", NULL}, +{"LeftTriangleBar", "⧏", NULL}, +{"LeftTriangleEqual", "⊴", NULL}, +{"LeftUpDownVector", "⥑", NULL}, +{"LeftUpTeeVector", "⥠", NULL}, +{"LeftUpVector", "↿", NULL}, +{"LeftUpVectorBar", "⥘", NULL}, +{"LeftVector", "↼", NULL}, +{"LeftVectorBar", "⥒", NULL}, +{"Leftarrow", "⇐", NULL}, +{"Leftrightarrow", "⇔", NULL}, +{"LessEqualGreater", "⋚", NULL}, +{"LessFullEqual", "≦", NULL}, +{"LessGreater", "≶", NULL}, +{"LessLess", "⪡", NULL}, +{"LessSlantEqual", "⩽", NULL}, +{"LessTilde", "≲", NULL}, +{"Lfr", "𝔏", NULL}, +{"Ll", "⋘", NULL}, +{"Lleftarrow", "⇚", NULL}, +{"Lmidot", "Ŀ", NULL}, +{"LongLeftArrow", "⟵", NULL}, +{"LongLeftRightArrow", "⟷", NULL}, +{"LongRightArrow", "⟶", NULL}, +{"Longleftarrow", "⟸", NULL}, +{"Longleftrightarrow", "⟺", NULL}, +{"Longrightarrow", "⟹", NULL}, +{"Lopf", "𝕃", NULL}, +{"LowerLeftArrow", "↙", NULL}, +{"LowerRightArrow", "↘", NULL}, +{"Lscr", "ℒ", NULL}, +{"Lsh", "↰", NULL}, +{"Lstrok", "Ł", NULL}, +{"Lt", "≪", NULL}, +{"Map", "⤅", NULL}, +{"Mcy", "М", NULL}, +{"MediumSpace", " ", NULL}, +{"Mellintrf", "ℳ", NULL}, +{"Mfr", "𝔐", NULL}, +{"MinusPlus", "∓", NULL}, +{"Mopf", "𝕄", NULL}, +{"Mscr", "ℳ", NULL}, +{"Mu", "Μ", "Μ"}, +{"NJcy", "Њ", NULL}, +{"Nacute", "Ń", NULL}, +{"Ncaron", "Ň", NULL}, +{"Ncedil", "Ņ", NULL}, +{"Ncy", "Н", NULL}, +{"NegativeMediumSpace", "", NULL}, +{"NegativeThickSpace", "", NULL}, +{"NegativeThinSpace", "", NULL}, +{"NegativeVeryThinSpace", "", NULL}, +{"NestedGreaterGreater", "≫", NULL}, +{"NestedLessLess", "≪", NULL}, +{"NewLine", "\n", NULL}, +{"Nfr", "𝔑", NULL}, +{"NoBreak", "", NULL}, +{"NonBreakingSpace", " ", NULL}, +{"Nopf", "ℕ", NULL}, +{"Not", "⫬", NULL}, +{"NotCongruent", "≢", NULL}, +{"NotCupCap", "≭", NULL}, +{"NotDoubleVerticalBar", "∦", NULL}, +{"NotElement", "∉", NULL}, +{"NotEqual", "≠", NULL}, +{"NotEqualTilde", "≂̸", NULL}, +{"NotExists", "∄", NULL}, +{"NotGreater", "≯", NULL}, +{"NotGreaterEqual", "≱", NULL}, +{"NotGreaterFullEqual", "≧̸", NULL}, +{"NotGreaterGreater", "≫̸", NULL}, +{"NotGreaterLess", "≹", NULL}, +{"NotGreaterSlantEqual", "⩾̸", NULL}, +{"NotGreaterTilde", "≵", NULL}, +{"NotHumpDownHump", "≎̸", NULL}, +{"NotHumpEqual", "≏̸", NULL}, +{"NotLeftTriangle", "⋪", NULL}, +{"NotLeftTriangleBar", "⧏̸", NULL}, +{"NotLeftTriangleEqual", "⋬", NULL}, +{"NotLess", "≮", NULL}, +{"NotLessEqual", "≰", NULL}, +{"NotLessGreater", "≸", NULL}, +{"NotLessLess", "≪̸", NULL}, +{"NotLessSlantEqual", "⩽̸", NULL}, +{"NotLessTilde", "≴", NULL}, +{"NotNestedGreaterGreater", "⪢̸", NULL}, +{"NotNestedLessLess", "⪡̸", NULL}, +{"NotPrecedes", "⊀", NULL}, +{"NotPrecedesEqual", "⪯̸", NULL}, +{"NotPrecedesSlantEqual", "⋠", NULL}, +{"NotReverseElement", "∌", NULL}, +{"NotRightTriangle", "⋫", NULL}, +{"NotRightTriangleBar", "⧐̸", NULL}, +{"NotRightTriangleEqual", "⋭", NULL}, +{"NotSquareSubset", "⊏̸", NULL}, +{"NotSquareSubsetEqual", "⋢", NULL}, +{"NotSquareSuperset", "⊐̸", NULL}, +{"NotSquareSupersetEqual", "⋣", NULL}, +{"NotSubset", "⊂⃒", NULL}, +{"NotSubsetEqual", "⊈", NULL}, +{"NotSucceeds", "⊁", NULL}, +{"NotSucceedsEqual", "⪰̸", NULL}, +{"NotSucceedsSlantEqual", "⋡", NULL}, +{"NotSucceedsTilde", "≿̸", NULL}, +{"NotSuperset", "⊃⃒", NULL}, +{"NotSupersetEqual", "⊉", NULL}, +{"NotTilde", "≁", NULL}, +{"NotTildeEqual", "≄", NULL}, +{"NotTildeFullEqual", "≇", NULL}, +{"NotTildeTilde", "≉", NULL}, +{"NotVerticalBar", "∤", NULL}, +{"Nscr", "𝒩", NULL}, +{"Ntilde", "Ñ", "Ñ"}, +{"Nu", "Ν", "Ν"}, +{"OElig", "Œ", "Œ"}, +{"Oacute", "Ó", "Ó"}, +{"Ocirc", "Ô", "Ô"}, +{"Ocy", "О", NULL}, +{"Odblac", "Ő", NULL}, +{"Ofr", "𝔒", NULL}, +{"Ograve", "Ò", "Ò"}, +{"Omacr", "Ō", NULL}, +{"Omega", "Ω", "Ω"}, +{"Omicron", "Ο", "Ο"}, +{"Oopf", "𝕆", NULL}, +{"OpenCurlyDoubleQuote", "“", NULL}, +{"OpenCurlyQuote", "‘", NULL}, +{"Or", "⩔", NULL}, +{"Oscr", "𝒪", NULL}, +{"Oslash", "Ø", "Ø"}, +{"Otilde", "Õ", "Õ"}, +{"Otimes", "⨷", NULL}, +{"Ouml", "Ö", "Ö"}, +{"OverBar", "‾", NULL}, +{"OverBrace", "⏞", NULL}, +{"OverBracket", "⎴", NULL}, +{"OverParenthesis", "⏜", NULL}, +{"PartialD", "∂", NULL}, +{"Pcy", "П", NULL}, +{"Pfr", "𝔓", NULL}, +{"Phi", "Φ", "Φ"}, +{"Pi", "Π", "Π"}, +{"PlusMinus", "±", NULL}, +{"Poincareplane", "ℌ", NULL}, +{"Popf", "ℙ", NULL}, +{"Pr", "⪻", NULL}, +{"Precedes", "≺", NULL}, +{"PrecedesEqual", "⪯", NULL}, +{"PrecedesSlantEqual", "≼", NULL}, +{"PrecedesTilde", "≾", NULL}, +{"Prime", "″", "″"}, +{"Product", "∏", NULL}, +{"Proportion", "∷", NULL}, +{"Proportional", "∝", NULL}, +{"Pscr", "𝒫", NULL}, +{"Psi", "Ψ", "Ψ"}, +{"QUOT", "\"", NULL}, +{"Qfr", "𝔔", NULL}, +{"Qopf", "ℚ", NULL}, +{"Qscr", "𝒬", NULL}, +{"RBarr", "⤐", NULL}, +{"REG", "®", NULL}, +{"Racute", "Ŕ", NULL}, +{"Rang", "⟫", NULL}, +{"Rarr", "↠", NULL}, +{"Rarrtl", "⤖", NULL}, +{"Rcaron", "Ř", NULL}, +{"Rcedil", "Ŗ", NULL}, +{"Rcy", "Р", NULL}, +{"Re", "ℜ", NULL}, +{"ReverseElement", "∋", NULL}, +{"ReverseEquilibrium", "⇋", NULL}, +{"ReverseUpEquilibrium", "⥯", NULL}, +{"Rfr", "ℜ", NULL}, +{"Rho", "Ρ", "Ρ"}, +{"RightAngleBracket", "⟩", NULL}, +{"RightArrow", "→", NULL}, +{"RightArrowBar", "⇥", NULL}, +{"RightArrowLeftArrow", "⇄", NULL}, +{"RightCeiling", "⌉", NULL}, +{"RightDoubleBracket", "⟧", NULL}, +{"RightDownTeeVector", "⥝", NULL}, +{"RightDownVector", "⇂", NULL}, +{"RightDownVectorBar", "⥕", NULL}, +{"RightFloor", "⌋", NULL}, +{"RightTee", "⊢", NULL}, +{"RightTeeArrow", "↦", NULL}, +{"RightTeeVector", "⥛", NULL}, +{"RightTriangle", "⊳", NULL}, +{"RightTriangleBar", "⧐", NULL}, +{"RightTriangleEqual", "⊵", NULL}, +{"RightUpDownVector", "⥏", NULL}, +{"RightUpTeeVector", "⥜", NULL}, +{"RightUpVector", "↾", NULL}, +{"RightUpVectorBar", "⥔", NULL}, +{"RightVector", "⇀", NULL}, +{"RightVectorBar", "⥓", NULL}, +{"Rightarrow", "⇒", NULL}, +{"Ropf", "ℝ", NULL}, +{"RoundImplies", "⥰", NULL}, +{"Rrightarrow", "⇛", NULL}, +{"Rscr", "ℛ", NULL}, +{"Rsh", "↱", NULL}, +{"RuleDelayed", "⧴", NULL}, +{"SHCHcy", "Щ", NULL}, +{"SHcy", "Ш", NULL}, +{"SOFTcy", "Ь", NULL}, +{"Sacute", "Ś", NULL}, +{"Sc", "⪼", NULL}, +{"Scaron", "Š", "Š"}, +{"Scedil", "Ş", NULL}, +{"Scirc", "Ŝ", NULL}, +{"Scy", "С", NULL}, +{"Sfr", "𝔖", NULL}, +{"ShortDownArrow", "↓", NULL}, +{"ShortLeftArrow", "←", NULL}, +{"ShortRightArrow", "→", NULL}, +{"ShortUpArrow", "↑", NULL}, +{"Sigma", "Σ", "Σ"}, +{"SmallCircle", "∘", NULL}, +{"Sopf", "𝕊", NULL}, +{"Sqrt", "√", NULL}, +{"Square", "□", NULL}, +{"SquareIntersection", "⊓", NULL}, +{"SquareSubset", "⊏", NULL}, +{"SquareSubsetEqual", "⊑", NULL}, +{"SquareSuperset", "⊐", NULL}, +{"SquareSupersetEqual", "⊒", NULL}, +{"SquareUnion", "⊔", NULL}, +{"Sscr", "𝒮", NULL}, +{"Star", "⋆", NULL}, +{"Sub", "⋐", NULL}, +{"Subset", "⋐", NULL}, +{"SubsetEqual", "⊆", NULL}, +{"Succeeds", "≻", NULL}, +{"SucceedsEqual", "⪰", NULL}, +{"SucceedsSlantEqual", "≽", NULL}, +{"SucceedsTilde", "≿", NULL}, +{"SuchThat", "∋", NULL}, +{"Sum", "∑", NULL}, +{"Sup", "⋑", NULL}, +{"Superset", "⊃", NULL}, +{"SupersetEqual", "⊇", NULL}, +{"Supset", "⋑", NULL}, +{"THORN", "Þ", "Þ"}, +{"TRADE", "™", NULL}, +{"TSHcy", "Ћ", NULL}, +{"TScy", "Ц", NULL}, +{"Tab", "\t", NULL}, +{"Tau", "Τ", "Τ"}, +{"Tcaron", "Ť", NULL}, +{"Tcedil", "Ţ", NULL}, +{"Tcy", "Т", NULL}, +{"Tfr", "𝔗", NULL}, +{"Therefore", "∴", NULL}, +{"Theta", "Θ", "Θ"}, +{"ThickSpace", " ", NULL}, +{"ThinSpace", " ", NULL}, +{"Tilde", "∼", NULL}, +{"TildeEqual", "≃", NULL}, +{"TildeFullEqual", "≅", NULL}, +{"TildeTilde", "≈", NULL}, +{"Topf", "𝕋", NULL}, +{"TripleDot", "⃛", NULL}, +{"Tscr", "𝒯", NULL}, +{"Tstrok", "Ŧ", NULL}, +{"Uacute", "Ú", "Ú"}, +{"Uarr", "↟", NULL}, +{"Uarrocir", "⥉", NULL}, +{"Ubrcy", "Ў", NULL}, +{"Ubreve", "Ŭ", NULL}, +{"Ucirc", "Û", "Û"}, +{"Ucy", "У", NULL}, +{"Udblac", "Ű", NULL}, +{"Ufr", "𝔘", NULL}, +{"Ugrave", "Ù", "Ù"}, +{"Umacr", "Ū", NULL}, +{"UnderBar", "_", NULL}, +{"UnderBrace", "⏟", NULL}, +{"UnderBracket", "⎵", NULL}, +{"UnderParenthesis", "⏝", NULL}, +{"Union", "⋃", NULL}, +{"UnionPlus", "⊎", NULL}, +{"Uogon", "Ų", NULL}, +{"Uopf", "𝕌", NULL}, +{"UpArrow", "↑", NULL}, +{"UpArrowBar", "⤒", NULL}, +{"UpArrowDownArrow", "⇅", NULL}, +{"UpDownArrow", "↕", NULL}, +{"UpEquilibrium", "⥮", NULL}, +{"UpTee", "⊥", NULL}, +{"UpTeeArrow", "↥", NULL}, +{"Uparrow", "⇑", NULL}, +{"Updownarrow", "⇕", NULL}, +{"UpperLeftArrow", "↖", NULL}, +{"UpperRightArrow", "↗", NULL}, +{"Upsi", "ϒ", NULL}, +{"Upsilon", "Υ", "Υ"}, +{"Uring", "Ů", NULL}, +{"Uscr", "𝒰", NULL}, +{"Utilde", "Ũ", NULL}, +{"Uuml", "Ü", "Ü"}, +{"VDash", "⊫", NULL}, +{"Vbar", "⫫", NULL}, +{"Vcy", "В", NULL}, +{"Vdash", "⊩", NULL}, +{"Vdashl", "⫦", NULL}, +{"Vee", "⋁", NULL}, +{"Verbar", "‖", NULL}, +{"Vert", "‖", NULL}, +{"VerticalBar", "∣", NULL}, +{"VerticalLine", "|", NULL}, +{"VerticalSeparator", "❘", NULL}, +{"VerticalTilde", "≀", NULL}, +{"VeryThinSpace", " ", NULL}, +{"Vfr", "𝔙", NULL}, +{"Vopf", "𝕍", NULL}, +{"Vscr", "𝒱", NULL}, +{"Vvdash", "⊪", NULL}, +{"Wcirc", "Ŵ", NULL}, +{"Wedge", "⋀", NULL}, +{"Wfr", "𝔚", NULL}, +{"Wopf", "𝕎", NULL}, +{"Wscr", "𝒲", NULL}, +{"Xfr", "𝔛", NULL}, +{"Xi", "Ξ", "Ξ"}, +{"Xopf", "𝕏", NULL}, +{"Xscr", "𝒳", NULL}, +{"YAcy", "Я", NULL}, +{"YIcy", "Ї", NULL}, +{"YUcy", "Ю", NULL}, +{"Yacute", "Ý", "Ý"}, +{"Ycirc", "Ŷ", NULL}, +{"Ycy", "Ы", NULL}, +{"Yfr", "𝔜", NULL}, +{"Yopf", "𝕐", NULL}, +{"Yscr", "𝒴", NULL}, +{"Yuml", "Ÿ", "Ÿ"}, +{"ZHcy", "Ж", NULL}, +{"Zacute", "Ź", NULL}, +{"Zcaron", "Ž", NULL}, +{"Zcy", "З", NULL}, +{"Zdot", "Ż", NULL}, +{"ZeroWidthSpace", "", NULL}, +{"Zeta", "Ζ", "Ζ"}, +{"Zfr", "ℨ", NULL}, +{"Zopf", "ℤ", NULL}, +{"Zscr", "𝒵", NULL}, +{"aacute", "á", "á"}, +{"abreve", "ă", NULL}, +{"ac", "∾", NULL}, +{"acE", "∾̳", NULL}, +{"acd", "∿", NULL}, +{"acirc", "â", "â"}, +{"acute", "´", "´"}, +{"acy", "а", NULL}, +{"aelig", "æ", "æ"}, +{"af", "", NULL}, +{"afr", "𝔞", NULL}, +{"agrave", "à", "à"}, +{"alefsym", "ℵ", "ℵ"}, +{"aleph", "ℵ", NULL}, +{"alpha", "α", "α"}, +{"amacr", "ā", NULL}, +{"amalg", "⨿", NULL}, +{"amp", "&", "&"}, +{"and", "∧", "∧"}, +{"andand", "⩕", NULL}, +{"andd", "⩜", NULL}, +{"andslope", "⩘", NULL}, +{"andv", "⩚", NULL}, +{"ang", "∠", "∠"}, +{"ange", "⦤", NULL}, +{"angle", "∠", NULL}, +{"angmsd", "∡", NULL}, +{"angmsdaa", "⦨", NULL}, +{"angmsdab", "⦩", NULL}, +{"angmsdac", "⦪", NULL}, +{"angmsdad", "⦫", NULL}, +{"angmsdae", "⦬", NULL}, +{"angmsdaf", "⦭", NULL}, +{"angmsdag", "⦮", NULL}, +{"angmsdah", "⦯", NULL}, +{"angrt", "∟", NULL}, +{"angrtvb", "⊾", NULL}, +{"angrtvbd", "⦝", NULL}, +{"angsph", "∢", NULL}, +{"angst", "Å", NULL}, +{"angzarr", "⍼", NULL}, +{"aogon", "ą", NULL}, +{"aopf", "𝕒", NULL}, +{"ap", "≈", NULL}, +{"apE", "⩰", NULL}, +{"apacir", "⩯", NULL}, +{"ape", "≊", NULL}, +{"apid", "≋", NULL}, +{"apos", "'", NULL}, +{"approx", "≈", NULL}, +{"approxeq", "≊", NULL}, +{"aring", "å", "å"}, +{"ascr", "𝒶", NULL}, +{"ast", "*", NULL}, +{"asymp", "≈", "≈"}, +{"asympeq", "≍", NULL}, +{"atilde", "ã", "ã"}, +{"auml", "ä", "ä"}, +{"awconint", "∳", NULL}, +{"awint", "⨑", NULL}, +{"bNot", "⫭", NULL}, +{"backcong", "≌", NULL}, +{"backepsilon", "϶", NULL}, +{"backprime", "‵", NULL}, +{"backsim", "∽", NULL}, +{"backsimeq", "⋍", NULL}, +{"barvee", "⊽", NULL}, +{"barwed", "⌅", NULL}, +{"barwedge", "⌅", NULL}, +{"bbrk", "⎵", NULL}, +{"bbrktbrk", "⎶", NULL}, +{"bcong", "≌", NULL}, +{"bcy", "б", NULL}, +{"bdquo", "„", "„"}, +{"becaus", "∵", NULL}, +{"because", "∵", NULL}, +{"bemptyv", "⦰", NULL}, +{"bepsi", "϶", NULL}, +{"bernou", "ℬ", NULL}, +{"beta", "β", "β"}, +{"beth", "ℶ", NULL}, +{"between", "≬", NULL}, +{"bfr", "𝔟", NULL}, +{"bigcap", "⋂", NULL}, +{"bigcirc", "◯", NULL}, +{"bigcup", "⋃", NULL}, +{"bigodot", "⨀", NULL}, +{"bigoplus", "⨁", NULL}, +{"bigotimes", "⨂", NULL}, +{"bigsqcup", "⨆", NULL}, +{"bigstar", "★", NULL}, +{"bigtriangledown", "▽", NULL}, +{"bigtriangleup", "△", NULL}, +{"biguplus", "⨄", NULL}, +{"bigvee", "⋁", NULL}, +{"bigwedge", "⋀", NULL}, +{"bkarow", "⤍", NULL}, +{"blacklozenge", "⧫", NULL}, +{"blacksquare", "▪", NULL}, +{"blacktriangle", "▴", NULL}, +{"blacktriangledown", "▾", NULL}, +{"blacktriangleleft", "◂", NULL}, +{"blacktriangleright", "▸", NULL}, +{"blank", "␣", NULL}, +{"blk12", "▒", NULL}, +{"blk14", "░", NULL}, +{"blk34", "▓", NULL}, +{"block", "█", NULL}, +{"bne", "=⃥", NULL}, +{"bnequiv", "≡⃥", NULL}, +{"bnot", "⌐", NULL}, +{"bopf", "𝕓", NULL}, +{"bot", "⊥", NULL}, +{"bottom", "⊥", NULL}, +{"bowtie", "⋈", NULL}, +{"boxDL", "╗", NULL}, +{"boxDR", "╔", NULL}, +{"boxDl", "╖", NULL}, +{"boxDr", "╓", NULL}, +{"boxH", "═", NULL}, +{"boxHD", "╦", NULL}, +{"boxHU", "╩", NULL}, +{"boxHd", "╤", NULL}, +{"boxHu", "╧", NULL}, +{"boxUL", "╝", NULL}, +{"boxUR", "╚", NULL}, +{"boxUl", "╜", NULL}, +{"boxUr", "╙", NULL}, +{"boxV", "║", NULL}, +{"boxVH", "╬", NULL}, +{"boxVL", "╣", NULL}, +{"boxVR", "╠", NULL}, +{"boxVh", "╫", NULL}, +{"boxVl", "╢", NULL}, +{"boxVr", "╟", NULL}, +{"boxbox", "⧉", NULL}, +{"boxdL", "╕", NULL}, +{"boxdR", "╒", NULL}, +{"boxdl", "┐", NULL}, +{"boxdr", "┌", NULL}, +{"boxh", "─", NULL}, +{"boxhD", "╥", NULL}, +{"boxhU", "╨", NULL}, +{"boxhd", "┬", NULL}, +{"boxhu", "┴", NULL}, +{"boxminus", "⊟", NULL}, +{"boxplus", "⊞", NULL}, +{"boxtimes", "⊠", NULL}, +{"boxuL", "╛", NULL}, +{"boxuR", "╘", NULL}, +{"boxul", "┘", NULL}, +{"boxur", "└", NULL}, +{"boxv", "│", NULL}, +{"boxvH", "╪", NULL}, +{"boxvL", "╡", NULL}, +{"boxvR", "╞", NULL}, +{"boxvh", "┼", NULL}, +{"boxvl", "┤", NULL}, +{"boxvr", "├", NULL}, +{"bprime", "‵", NULL}, +{"breve", "˘", NULL}, +{"brvbar", "¦", "¦"}, +{"bscr", "𝒷", NULL}, +{"bsemi", "⁏", NULL}, +{"bsim", "∽", NULL}, +{"bsime", "⋍", NULL}, +{"bsol", "\\", NULL}, +{"bsolb", "⧅", NULL}, +{"bsolhsub", "⟈", NULL}, +{"bull", "•", "•"}, +{"bullet", "•", NULL}, +{"bump", "≎", NULL}, +{"bumpE", "⪮", NULL}, +{"bumpe", "≏", NULL}, +{"bumpeq", "≏", NULL}, +{"cacute", "ć", NULL}, +{"cap", "∩", "∩"}, +{"capand", "⩄", NULL}, +{"capbrcup", "⩉", NULL}, +{"capcap", "⩋", NULL}, +{"capcup", "⩇", NULL}, +{"capdot", "⩀", NULL}, +{"caps", "∩︀", NULL}, +{"caret", "⁁", NULL}, +{"caron", "ˇ", NULL}, +{"ccaps", "⩍", NULL}, +{"ccaron", "č", NULL}, +{"ccedil", "ç", "ç"}, +{"ccirc", "ĉ", NULL}, +{"ccups", "⩌", NULL}, +{"ccupssm", "⩐", NULL}, +{"cdot", "ċ", NULL}, +{"cedil", "¸", "¸"}, +{"cemptyv", "⦲", NULL}, +{"cent", "¢", "¢"}, +{"centerdot", "·", NULL}, +{"cfr", "𝔠", NULL}, +{"chcy", "ч", NULL}, +{"check", "✓", NULL}, +{"checkmark", "✓", NULL}, +{"chi", "χ", "χ"}, +{"cir", "○", NULL}, +{"cirE", "⧃", NULL}, +{"circ", "ˆ", "ˆ"}, +{"circeq", "≗", NULL}, +{"circlearrowleft", "↺", NULL}, +{"circlearrowright", "↻", NULL}, +{"circledR", "®", NULL}, +{"circledS", "Ⓢ", NULL}, +{"circledast", "⊛", NULL}, +{"circledcirc", "⊚", NULL}, +{"circleddash", "⊝", NULL}, +{"cire", "≗", NULL}, +{"cirfnint", "⨐", NULL}, +{"cirmid", "⫯", NULL}, +{"cirscir", "⧂", NULL}, +{"clubs", "♣", "♣"}, +{"clubsuit", "♣", NULL}, +{"colon", ":", NULL}, +{"colone", "≔", NULL}, +{"coloneq", "≔", NULL}, +{"comma", ",", NULL}, +{"commat", "@", NULL}, +{"comp", "∁", NULL}, +{"compfn", "∘", NULL}, +{"complement", "∁", NULL}, +{"complexes", "ℂ", NULL}, +{"cong", "≅", "≅"}, +{"congdot", "⩭", NULL}, +{"conint", "∮", NULL}, +{"copf", "𝕔", NULL}, +{"coprod", "∐", NULL}, +{"copy", "©", "©"}, +{"copysr", "℗", NULL}, +{"crarr", "↵", "↵"}, +{"cross", "✗", NULL}, +{"cscr", "𝒸", NULL}, +{"csub", "⫏", NULL}, +{"csube", "⫑", NULL}, +{"csup", "⫐", NULL}, +{"csupe", "⫒", NULL}, +{"ctdot", "⋯", NULL}, +{"cudarrl", "⤸", NULL}, +{"cudarrr", "⤵", NULL}, +{"cuepr", "⋞", NULL}, +{"cuesc", "⋟", NULL}, +{"cularr", "↶", NULL}, +{"cularrp", "⤽", NULL}, +{"cup", "∪", "∪"}, +{"cupbrcap", "⩈", NULL}, +{"cupcap", "⩆", NULL}, +{"cupcup", "⩊", NULL}, +{"cupdot", "⊍", NULL}, +{"cupor", "⩅", NULL}, +{"cups", "∪︀", NULL}, +{"curarr", "↷", NULL}, +{"curarrm", "⤼", NULL}, +{"curlyeqprec", "⋞", NULL}, +{"curlyeqsucc", "⋟", NULL}, +{"curlyvee", "⋎", NULL}, +{"curlywedge", "⋏", NULL}, +{"curren", "¤", "¤"}, +{"curvearrowleft", "↶", NULL}, +{"curvearrowright", "↷", NULL}, +{"cuvee", "⋎", NULL}, +{"cuwed", "⋏", NULL}, +{"cwconint", "∲", NULL}, +{"cwint", "∱", NULL}, +{"cylcty", "⌭", NULL}, +{"dArr", "⇓", "⇓"}, +{"dHar", "⥥", NULL}, +{"dagger", "†", "†"}, +{"daleth", "ℸ", NULL}, +{"darr", "↓", "↓"}, +{"dash", "‐", NULL}, +{"dashv", "⊣", NULL}, +{"dbkarow", "⤏", NULL}, +{"dblac", "˝", NULL}, +{"dcaron", "ď", NULL}, +{"dcy", "д", NULL}, +{"dd", "ⅆ", NULL}, +{"ddagger", "‡", NULL}, +{"ddarr", "⇊", NULL}, +{"ddotseq", "⩷", NULL}, +{"deg", "°", "°"}, +{"delta", "δ", "δ"}, +{"demptyv", "⦱", NULL}, +{"dfisht", "⥿", NULL}, +{"dfr", "𝔡", NULL}, +{"dharl", "⇃", NULL}, +{"dharr", "⇂", NULL}, +{"diam", "⋄", NULL}, +{"diamond", "⋄", NULL}, +{"diamondsuit", "♦", NULL}, +{"diams", "♦", "♦"}, +{"die", "¨", NULL}, +{"digamma", "ϝ", NULL}, +{"disin", "⋲", NULL}, +{"div", "÷", NULL}, +{"divide", "÷", "÷"}, +{"divideontimes", "⋇", NULL}, +{"divonx", "⋇", NULL}, +{"djcy", "ђ", NULL}, +{"dlcorn", "⌞", NULL}, +{"dlcrop", "⌍", NULL}, +{"dollar", "$", NULL}, +{"dopf", "𝕕", NULL}, +{"dot", "˙", NULL}, +{"doteq", "≐", NULL}, +{"doteqdot", "≑", NULL}, +{"dotminus", "∸", NULL}, +{"dotplus", "∔", NULL}, +{"dotsquare", "⊡", NULL}, +{"doublebarwedge", "⌆", NULL}, +{"downarrow", "↓", NULL}, +{"downdownarrows", "⇊", NULL}, +{"downharpoonleft", "⇃", NULL}, +{"downharpoonright", "⇂", NULL}, +{"drbkarow", "⤐", NULL}, +{"drcorn", "⌟", NULL}, +{"drcrop", "⌌", NULL}, +{"dscr", "𝒹", NULL}, +{"dscy", "ѕ", NULL}, +{"dsol", "⧶", NULL}, +{"dstrok", "đ", NULL}, +{"dtdot", "⋱", NULL}, +{"dtri", "▿", NULL}, +{"dtrif", "▾", NULL}, +{"duarr", "⇵", NULL}, +{"duhar", "⥯", NULL}, +{"dwangle", "⦦", NULL}, +{"dzcy", "џ", NULL}, +{"dzigrarr", "⟿", NULL}, +{"eDDot", "⩷", NULL}, +{"eDot", "≑", NULL}, +{"eacute", "é", "é"}, +{"easter", "⩮", NULL}, +{"ecaron", "ě", NULL}, +{"ecir", "≖", NULL}, +{"ecirc", "ê", "ê"}, +{"ecolon", "≕", NULL}, +{"ecy", "э", NULL}, +{"edot", "ė", NULL}, +{"ee", "ⅇ", NULL}, +{"efDot", "≒", NULL}, +{"efr", "𝔢", NULL}, +{"eg", "⪚", NULL}, +{"egrave", "è", "è"}, +{"egs", "⪖", NULL}, +{"egsdot", "⪘", NULL}, +{"el", "⪙", NULL}, +{"elinters", "⏧", NULL}, +{"ell", "ℓ", NULL}, +{"els", "⪕", NULL}, +{"elsdot", "⪗", NULL}, +{"emacr", "ē", NULL}, +{"empty", "∅", "∅"}, +{"emptyset", "∅", NULL}, +{"emptyv", "∅", NULL}, +{"emsp", " ", " "}, +{"emsp13", " ", NULL}, +{"emsp14", " ", NULL}, +{"eng", "ŋ", NULL}, +{"ensp", " ", " "}, +{"eogon", "ę", NULL}, +{"eopf", "𝕖", NULL}, +{"epar", "⋕", NULL}, +{"eparsl", "⧣", NULL}, +{"eplus", "⩱", NULL}, +{"epsi", "ε", NULL}, +{"epsilon", "ε", "ε"}, +{"epsiv", "ϵ", NULL}, +{"eqcirc", "≖", NULL}, +{"eqcolon", "≕", NULL}, +{"eqsim", "≂", NULL}, +{"eqslantgtr", "⪖", NULL}, +{"eqslantless", "⪕", NULL}, +{"equals", "=", NULL}, +{"equest", "≟", NULL}, +{"equiv", "≡", "≡"}, +{"equivDD", "⩸", NULL}, +{"eqvparsl", "⧥", NULL}, +{"erDot", "≓", NULL}, +{"erarr", "⥱", NULL}, +{"escr", "ℯ", NULL}, +{"esdot", "≐", NULL}, +{"esim", "≂", NULL}, +{"eta", "η", "η"}, +{"eth", "ð", "ð"}, +{"euml", "ë", "ë"}, +{"euro", "€", "€"}, +{"excl", "!", NULL}, +{"exist", "∃", "∃"}, +{"expectation", "ℰ", NULL}, +{"exponentiale", "ⅇ", NULL}, +{"fallingdotseq", "≒", NULL}, +{"fcy", "ф", NULL}, +{"female", "♀", NULL}, +{"ffilig", "ffi", NULL}, +{"fflig", "ff", NULL}, +{"ffllig", "ffl", NULL}, +{"ffr", "𝔣", NULL}, +{"filig", "fi", NULL}, +{"fjlig", "fj", NULL}, +{"flat", "♭", NULL}, +{"fllig", "fl", NULL}, +{"fltns", "▱", NULL}, +{"fnof", "ƒ", "ƒ"}, +{"fopf", "𝕗", NULL}, +{"forall", "∀", "∀"}, +{"fork", "⋔", NULL}, +{"forkv", "⫙", NULL}, +{"fpartint", "⨍", NULL}, +{"frac12", "½", "½"}, +{"frac13", "⅓", NULL}, +{"frac14", "¼", "¼"}, +{"frac15", "⅕", NULL}, +{"frac16", "⅙", NULL}, +{"frac18", "⅛", NULL}, +{"frac23", "⅔", NULL}, +{"frac25", "⅖", NULL}, +{"frac34", "¾", "¾"}, +{"frac35", "⅗", NULL}, +{"frac38", "⅜", NULL}, +{"frac45", "⅘", NULL}, +{"frac56", "⅚", NULL}, +{"frac58", "⅝", NULL}, +{"frac78", "⅞", NULL}, +{"frasl", "⁄", "⁄"}, +{"frown", "⌢", NULL}, +{"fscr", "𝒻", NULL}, +{"gE", "≧", NULL}, +{"gEl", "⪌", NULL}, +{"gacute", "ǵ", NULL}, +{"gamma", "γ", "γ"}, +{"gammad", "ϝ", NULL}, +{"gap", "⪆", NULL}, +{"gbreve", "ğ", NULL}, +{"gcirc", "ĝ", NULL}, +{"gcy", "г", NULL}, +{"gdot", "ġ", NULL}, +{"ge", "≥", "≥"}, +{"gel", "⋛", NULL}, +{"geq", "≥", NULL}, +{"geqq", "≧", NULL}, +{"geqslant", "⩾", NULL}, +{"ges", "⩾", NULL}, +{"gescc", "⪩", NULL}, +{"gesdot", "⪀", NULL}, +{"gesdoto", "⪂", NULL}, +{"gesdotol", "⪄", NULL}, +{"gesl", "⋛︀", NULL}, +{"gesles", "⪔", NULL}, +{"gfr", "𝔤", NULL}, +{"gg", "≫", NULL}, +{"ggg", "⋙", NULL}, +{"gimel", "ℷ", NULL}, +{"gjcy", "ѓ", NULL}, +{"gl", "≷", NULL}, +{"glE", "⪒", NULL}, +{"gla", "⪥", NULL}, +{"glj", "⪤", NULL}, +{"gnE", "≩", NULL}, +{"gnap", "⪊", NULL}, +{"gnapprox", "⪊", NULL}, +{"gne", "⪈", NULL}, +{"gneq", "⪈", NULL}, +{"gneqq", "≩", NULL}, +{"gnsim", "⋧", NULL}, +{"gopf", "𝕘", NULL}, +{"grave", "`", NULL}, +{"gscr", "ℊ", NULL}, +{"gsim", "≳", NULL}, +{"gsime", "⪎", NULL}, +{"gsiml", "⪐", NULL}, +{"gt", ">", ">"}, +{"gtcc", "⪧", NULL}, +{"gtcir", "⩺", NULL}, +{"gtdot", "⋗", NULL}, +{"gtlPar", "⦕", NULL}, +{"gtquest", "⩼", NULL}, +{"gtrapprox", "⪆", NULL}, +{"gtrarr", "⥸", NULL}, +{"gtrdot", "⋗", NULL}, +{"gtreqless", "⋛", NULL}, +{"gtreqqless", "⪌", NULL}, +{"gtrless", "≷", NULL}, +{"gtrsim", "≳", NULL}, +{"gvertneqq", "≩︀", NULL}, +{"gvnE", "≩︀", NULL}, +{"hArr", "⇔", "⇔"}, +{"hairsp", " ", NULL}, +{"half", "½", NULL}, +{"hamilt", "ℋ", NULL}, +{"hardcy", "ъ", NULL}, +{"harr", "↔", "↔"}, +{"harrcir", "⥈", NULL}, +{"harrw", "↭", NULL}, +{"hbar", "ℏ", NULL}, +{"hcirc", "ĥ", NULL}, +{"hearts", "♥", "♥"}, +{"heartsuit", "♥", NULL}, +{"hellip", "…", "…"}, +{"hercon", "⊹", NULL}, +{"hfr", "𝔥", NULL}, +{"hksearow", "⤥", NULL}, +{"hkswarow", "⤦", NULL}, +{"hoarr", "⇿", NULL}, +{"homtht", "∻", NULL}, +{"hookleftarrow", "↩", NULL}, +{"hookrightarrow", "↪", NULL}, +{"hopf", "𝕙", NULL}, +{"horbar", "―", NULL}, +{"hscr", "𝒽", NULL}, +{"hslash", "ℏ", NULL}, +{"hstrok", "ħ", NULL}, +{"hybull", "⁃", NULL}, +{"hyphen", "‐", NULL}, +{"iacute", "í", "í"}, +{"ic", "", NULL}, +{"icirc", "î", "î"}, +{"icy", "и", NULL}, +{"iecy", "е", NULL}, +{"iexcl", "¡", "¡"}, +{"iff", "⇔", NULL}, +{"ifr", "𝔦", NULL}, +{"igrave", "ì", "ì"}, +{"ii", "ⅈ", NULL}, +{"iiiint", "⨌", NULL}, +{"iiint", "∭", NULL}, +{"iinfin", "⧜", NULL}, +{"iiota", "℩", NULL}, +{"ijlig", "ij", NULL}, +{"imacr", "ī", NULL}, +{"image", "ℑ", "ℑ"}, +{"imagline", "ℐ", NULL}, +{"imagpart", "ℑ", NULL}, +{"imath", "ı", NULL}, +{"imof", "⊷", NULL}, +{"imped", "Ƶ", NULL}, +{"in", "∈", NULL}, +{"incare", "℅", NULL}, +{"infin", "∞", "∞"}, +{"infintie", "⧝", NULL}, +{"inodot", "ı", NULL}, +{"int", "∫", "∫"}, +{"intcal", "⊺", NULL}, +{"integers", "ℤ", NULL}, +{"intercal", "⊺", NULL}, +{"intlarhk", "⨗", NULL}, +{"intprod", "⨼", NULL}, +{"iocy", "ё", NULL}, +{"iogon", "į", NULL}, +{"iopf", "𝕚", NULL}, +{"iota", "ι", "ι"}, +{"iprod", "⨼", NULL}, +{"iquest", "¿", "¿"}, +{"iscr", "𝒾", NULL}, +{"isin", "∈", "∈"}, +{"isinE", "⋹", NULL}, +{"isindot", "⋵", NULL}, +{"isins", "⋴", NULL}, +{"isinsv", "⋳", NULL}, +{"isinv", "∈", NULL}, +{"it", "", NULL}, +{"itilde", "ĩ", NULL}, +{"iukcy", "і", NULL}, +{"iuml", "ï", "ï"}, +{"jcirc", "ĵ", NULL}, +{"jcy", "й", NULL}, +{"jfr", "𝔧", NULL}, +{"jmath", "ȷ", NULL}, +{"jopf", "𝕛", NULL}, +{"jscr", "𝒿", NULL}, +{"jsercy", "ј", NULL}, +{"jukcy", "є", NULL}, +{"kappa", "κ", "κ"}, +{"kappav", "ϰ", NULL}, +{"kcedil", "ķ", NULL}, +{"kcy", "к", NULL}, +{"kfr", "𝔨", NULL}, +{"kgreen", "ĸ", NULL}, +{"khcy", "х", NULL}, +{"kjcy", "ќ", NULL}, +{"kopf", "𝕜", NULL}, +{"kscr", "𝓀", NULL}, +{"lAarr", "⇚", NULL}, +{"lArr", "⇐", "⇐"}, +{"lAtail", "⤛", NULL}, +{"lBarr", "⤎", NULL}, +{"lE", "≦", NULL}, +{"lEg", "⪋", NULL}, +{"lHar", "⥢", NULL}, +{"lacute", "ĺ", NULL}, +{"laemptyv", "⦴", NULL}, +{"lagran", "ℒ", NULL}, +{"lambda", "λ", "λ"}, +{"lang", "⟨", "〈"}, +{"langd", "⦑", NULL}, +{"langle", "⟨", NULL}, +{"lap", "⪅", NULL}, +{"laquo", "«", "«"}, +{"larr", "←", "←"}, +{"larrb", "⇤", NULL}, +{"larrbfs", "⤟", NULL}, +{"larrfs", "⤝", NULL}, +{"larrhk", "↩", NULL}, +{"larrlp", "↫", NULL}, +{"larrpl", "⤹", NULL}, +{"larrsim", "⥳", NULL}, +{"larrtl", "↢", NULL}, +{"lat", "⪫", NULL}, +{"latail", "⤙", NULL}, +{"late", "⪭", NULL}, +{"lates", "⪭︀", NULL}, +{"lbarr", "⤌", NULL}, +{"lbbrk", "❲", NULL}, +{"lbrace", "{", NULL}, +{"lbrack", "[", NULL}, +{"lbrke", "⦋", NULL}, +{"lbrksld", "⦏", NULL}, +{"lbrkslu", "⦍", NULL}, +{"lcaron", "ľ", NULL}, +{"lcedil", "ļ", NULL}, +{"lceil", "⌈", "⌈"}, +{"lcub", "{", NULL}, +{"lcy", "л", NULL}, +{"ldca", "⤶", NULL}, +{"ldquo", "“", "“"}, +{"ldquor", "„", NULL}, +{"ldrdhar", "⥧", NULL}, +{"ldrushar", "⥋", NULL}, +{"ldsh", "↲", NULL}, +{"le", "≤", "≤"}, +{"leftarrow", "←", NULL}, +{"leftarrowtail", "↢", NULL}, +{"leftharpoondown", "↽", NULL}, +{"leftharpoonup", "↼", NULL}, +{"leftleftarrows", "⇇", NULL}, +{"leftrightarrow", "↔", NULL}, +{"leftrightarrows", "⇆", NULL}, +{"leftrightharpoons", "⇋", NULL}, +{"leftrightsquigarrow", "↭", NULL}, +{"leftthreetimes", "⋋", NULL}, +{"leg", "⋚", NULL}, +{"leq", "≤", NULL}, +{"leqq", "≦", NULL}, +{"leqslant", "⩽", NULL}, +{"les", "⩽", NULL}, +{"lescc", "⪨", NULL}, +{"lesdot", "⩿", NULL}, +{"lesdoto", "⪁", NULL}, +{"lesdotor", "⪃", NULL}, +{"lesg", "⋚︀", NULL}, +{"lesges", "⪓", NULL}, +{"lessapprox", "⪅", NULL}, +{"lessdot", "⋖", NULL}, +{"lesseqgtr", "⋚", NULL}, +{"lesseqqgtr", "⪋", NULL}, +{"lessgtr", "≶", NULL}, +{"lesssim", "≲", NULL}, +{"lfisht", "⥼", NULL}, +{"lfloor", "⌊", "⌊"}, +{"lfr", "𝔩", NULL}, +{"lg", "≶", NULL}, +{"lgE", "⪑", NULL}, +{"lhard", "↽", NULL}, +{"lharu", "↼", NULL}, +{"lharul", "⥪", NULL}, +{"lhblk", "▄", NULL}, +{"ljcy", "љ", NULL}, +{"ll", "≪", NULL}, +{"llarr", "⇇", NULL}, +{"llcorner", "⌞", NULL}, +{"llhard", "⥫", NULL}, +{"lltri", "◺", NULL}, +{"lmidot", "ŀ", NULL}, +{"lmoust", "⎰", NULL}, +{"lmoustache", "⎰", NULL}, +{"lnE", "≨", NULL}, +{"lnap", "⪉", NULL}, +{"lnapprox", "⪉", NULL}, +{"lne", "⪇", NULL}, +{"lneq", "⪇", NULL}, +{"lneqq", "≨", NULL}, +{"lnsim", "⋦", NULL}, +{"loang", "⟬", NULL}, +{"loarr", "⇽", NULL}, +{"lobrk", "⟦", NULL}, +{"longleftarrow", "⟵", NULL}, +{"longleftrightarrow", "⟷", NULL}, +{"longmapsto", "⟼", NULL}, +{"longrightarrow", "⟶", NULL}, +{"looparrowleft", "↫", NULL}, +{"looparrowright", "↬", NULL}, +{"lopar", "⦅", NULL}, +{"lopf", "𝕝", NULL}, +{"loplus", "⨭", NULL}, +{"lotimes", "⨴", NULL}, +{"lowast", "∗", "∗"}, +{"lowbar", "_", NULL}, +{"loz", "◊", "◊"}, +{"lozenge", "◊", NULL}, +{"lozf", "⧫", NULL}, +{"lpar", "(", NULL}, +{"lparlt", "⦓", NULL}, +{"lrarr", "⇆", NULL}, +{"lrcorner", "⌟", NULL}, +{"lrhar", "⇋", NULL}, +{"lrhard", "⥭", NULL}, +{"lrm", "", ""}, +{"lrtri", "⊿", NULL}, +{"lsaquo", "‹", "‹"}, +{"lscr", "𝓁", NULL}, +{"lsh", "↰", NULL}, +{"lsim", "≲", NULL}, +{"lsime", "⪍", NULL}, +{"lsimg", "⪏", NULL}, +{"lsqb", "[", NULL}, +{"lsquo", "‘", "‘"}, +{"lsquor", "‚", NULL}, +{"lstrok", "ł", NULL}, +{"lt", "<", "<"}, +{"ltcc", "⪦", NULL}, +{"ltcir", "⩹", NULL}, +{"ltdot", "⋖", NULL}, +{"lthree", "⋋", NULL}, +{"ltimes", "⋉", NULL}, +{"ltlarr", "⥶", NULL}, +{"ltquest", "⩻", NULL}, +{"ltrPar", "⦖", NULL}, +{"ltri", "◃", NULL}, +{"ltrie", "⊴", NULL}, +{"ltrif", "◂", NULL}, +{"lurdshar", "⥊", NULL}, +{"luruhar", "⥦", NULL}, +{"lvertneqq", "≨︀", NULL}, +{"lvnE", "≨︀", NULL}, +{"mDDot", "∺", NULL}, +{"macr", "¯", "¯"}, +{"male", "♂", NULL}, +{"malt", "✠", NULL}, +{"maltese", "✠", NULL}, +{"map", "↦", NULL}, +{"mapsto", "↦", NULL}, +{"mapstodown", "↧", NULL}, +{"mapstoleft", "↤", NULL}, +{"mapstoup", "↥", NULL}, +{"marker", "▮", NULL}, +{"mcomma", "⨩", NULL}, +{"mcy", "м", NULL}, +{"mdash", "—", "—"}, +{"measuredangle", "∡", NULL}, +{"mfr", "𝔪", NULL}, +{"mho", "℧", NULL}, +{"micro", "µ", "µ"}, +{"mid", "∣", NULL}, +{"midast", "*", NULL}, +{"midcir", "⫰", NULL}, +{"middot", "·", "·"}, +{"minus", "−", "−"}, +{"minusb", "⊟", NULL}, +{"minusd", "∸", NULL}, +{"minusdu", "⨪", NULL}, +{"mlcp", "⫛", NULL}, +{"mldr", "…", NULL}, +{"mnplus", "∓", NULL}, +{"models", "⊧", NULL}, +{"mopf", "𝕞", NULL}, +{"mp", "∓", NULL}, +{"mscr", "𝓂", NULL}, +{"mstpos", "∾", NULL}, +{"mu", "μ", "μ"}, +{"multimap", "⊸", NULL}, +{"mumap", "⊸", NULL}, +{"nGg", "⋙̸", NULL}, +{"nGt", "≫⃒", NULL}, +{"nGtv", "≫̸", NULL}, +{"nLeftarrow", "⇍", NULL}, +{"nLeftrightarrow", "⇎", NULL}, +{"nLl", "⋘̸", NULL}, +{"nLt", "≪⃒", NULL}, +{"nLtv", "≪̸", NULL}, +{"nRightarrow", "⇏", NULL}, +{"nVDash", "⊯", NULL}, +{"nVdash", "⊮", NULL}, +{"nabla", "∇", "∇"}, +{"nacute", "ń", NULL}, +{"nang", "∠⃒", NULL}, +{"nap", "≉", NULL}, +{"napE", "⩰̸", NULL}, +{"napid", "≋̸", NULL}, +{"napos", "ʼn", NULL}, +{"napprox", "≉", NULL}, +{"natur", "♮", NULL}, +{"natural", "♮", NULL}, +{"naturals", "ℕ", NULL}, +{"nbsp", " ", " "}, +{"nbump", "≎̸", NULL}, +{"nbumpe", "≏̸", NULL}, +{"ncap", "⩃", NULL}, +{"ncaron", "ň", NULL}, +{"ncedil", "ņ", NULL}, +{"ncong", "≇", NULL}, +{"ncongdot", "⩭̸", NULL}, +{"ncup", "⩂", NULL}, +{"ncy", "н", NULL}, +{"ndash", "–", "–"}, +{"ne", "≠", "≠"}, +{"neArr", "⇗", NULL}, +{"nearhk", "⤤", NULL}, +{"nearr", "↗", NULL}, +{"nearrow", "↗", NULL}, +{"nedot", "≐̸", NULL}, +{"nequiv", "≢", NULL}, +{"nesear", "⤨", NULL}, +{"nesim", "≂̸", NULL}, +{"nexist", "∄", NULL}, +{"nexists", "∄", NULL}, +{"nfr", "𝔫", NULL}, +{"ngE", "≧̸", NULL}, +{"nge", "≱", NULL}, +{"ngeq", "≱", NULL}, +{"ngeqq", "≧̸", NULL}, +{"ngeqslant", "⩾̸", NULL}, +{"nges", "⩾̸", NULL}, +{"ngsim", "≵", NULL}, +{"ngt", "≯", NULL}, +{"ngtr", "≯", NULL}, +{"nhArr", "⇎", NULL}, +{"nharr", "↮", NULL}, +{"nhpar", "⫲", NULL}, +{"ni", "∋", "∋"}, +{"nis", "⋼", NULL}, +{"nisd", "⋺", NULL}, +{"niv", "∋", NULL}, +{"njcy", "њ", NULL}, +{"nlArr", "⇍", NULL}, +{"nlE", "≦̸", NULL}, +{"nlarr", "↚", NULL}, +{"nldr", "‥", NULL}, +{"nle", "≰", NULL}, +{"nleftarrow", "↚", NULL}, +{"nleftrightarrow", "↮", NULL}, +{"nleq", "≰", NULL}, +{"nleqq", "≦̸", NULL}, +{"nleqslant", "⩽̸", NULL}, +{"nles", "⩽̸", NULL}, +{"nless", "≮", NULL}, +{"nlsim", "≴", NULL}, +{"nlt", "≮", NULL}, +{"nltri", "⋪", NULL}, +{"nltrie", "⋬", NULL}, +{"nmid", "∤", NULL}, +{"nopf", "𝕟", NULL}, +{"not", "¬", "¬"}, +{"notin", "∉", "∉"}, +{"notinE", "⋹̸", NULL}, +{"notindot", "⋵̸", NULL}, +{"notinva", "∉", NULL}, +{"notinvb", "⋷", NULL}, +{"notinvc", "⋶", NULL}, +{"notni", "∌", NULL}, +{"notniva", "∌", NULL}, +{"notnivb", "⋾", NULL}, +{"notnivc", "⋽", NULL}, +{"npar", "∦", NULL}, +{"nparallel", "∦", NULL}, +{"nparsl", "⫽⃥", NULL}, +{"npart", "∂̸", NULL}, +{"npolint", "⨔", NULL}, +{"npr", "⊀", NULL}, +{"nprcue", "⋠", NULL}, +{"npre", "⪯̸", NULL}, +{"nprec", "⊀", NULL}, +{"npreceq", "⪯̸", NULL}, +{"nrArr", "⇏", NULL}, +{"nrarr", "↛", NULL}, +{"nrarrc", "⤳̸", NULL}, +{"nrarrw", "↝̸", NULL}, +{"nrightarrow", "↛", NULL}, +{"nrtri", "⋫", NULL}, +{"nrtrie", "⋭", NULL}, +{"nsc", "⊁", NULL}, +{"nsccue", "⋡", NULL}, +{"nsce", "⪰̸", NULL}, +{"nscr", "𝓃", NULL}, +{"nshortmid", "∤", NULL}, +{"nshortparallel", "∦", NULL}, +{"nsim", "≁", NULL}, +{"nsime", "≄", NULL}, +{"nsimeq", "≄", NULL}, +{"nsmid", "∤", NULL}, +{"nspar", "∦", NULL}, +{"nsqsube", "⋢", NULL}, +{"nsqsupe", "⋣", NULL}, +{"nsub", "⊄", "⊄"}, +{"nsubE", "⫅̸", NULL}, +{"nsube", "⊈", NULL}, +{"nsubset", "⊂⃒", NULL}, +{"nsubseteq", "⊈", NULL}, +{"nsubseteqq", "⫅̸", NULL}, +{"nsucc", "⊁", NULL}, +{"nsucceq", "⪰̸", NULL}, +{"nsup", "⊅", NULL}, +{"nsupE", "⫆̸", NULL}, +{"nsupe", "⊉", NULL}, +{"nsupset", "⊃⃒", NULL}, +{"nsupseteq", "⊉", NULL}, +{"nsupseteqq", "⫆̸", NULL}, +{"ntgl", "≹", NULL}, +{"ntilde", "ñ", "ñ"}, +{"ntlg", "≸", NULL}, +{"ntriangleleft", "⋪", NULL}, +{"ntrianglelefteq", "⋬", NULL}, +{"ntriangleright", "⋫", NULL}, +{"ntrianglerighteq", "⋭", NULL}, +{"nu", "ν", "ν"}, +{"num", "#", NULL}, +{"numero", "№", NULL}, +{"numsp", " ", NULL}, +{"nvDash", "⊭", NULL}, +{"nvHarr", "⤄", NULL}, +{"nvap", "≍⃒", NULL}, +{"nvdash", "⊬", NULL}, +{"nvge", "≥⃒", NULL}, +{"nvgt", ">⃒", NULL}, +{"nvinfin", "⧞", NULL}, +{"nvlArr", "⤂", NULL}, +{"nvle", "≤⃒", NULL}, +{"nvlt", "<⃒", NULL}, +{"nvltrie", "⊴⃒", NULL}, +{"nvrArr", "⤃", NULL}, +{"nvrtrie", "⊵⃒", NULL}, +{"nvsim", "∼⃒", NULL}, +{"nwArr", "⇖", NULL}, +{"nwarhk", "⤣", NULL}, +{"nwarr", "↖", NULL}, +{"nwarrow", "↖", NULL}, +{"nwnear", "⤧", NULL}, +{"oS", "Ⓢ", NULL}, +{"oacute", "ó", "ó"}, +{"oast", "⊛", NULL}, +{"ocir", "⊚", NULL}, +{"ocirc", "ô", "ô"}, +{"ocy", "о", NULL}, +{"odash", "⊝", NULL}, +{"odblac", "ő", NULL}, +{"odiv", "⨸", NULL}, +{"odot", "⊙", NULL}, +{"odsold", "⦼", NULL}, +{"oelig", "œ", "œ"}, +{"ofcir", "⦿", NULL}, +{"ofr", "𝔬", NULL}, +{"ogon", "˛", NULL}, +{"ograve", "ò", "ò"}, +{"ogt", "⧁", NULL}, +{"ohbar", "⦵", NULL}, +{"ohm", "Ω", NULL}, +{"oint", "∮", NULL}, +{"olarr", "↺", NULL}, +{"olcir", "⦾", NULL}, +{"olcross", "⦻", NULL}, +{"oline", "‾", "‾"}, +{"olt", "⧀", NULL}, +{"omacr", "ō", NULL}, +{"omega", "ω", "ω"}, +{"omicron", "ο", "ο"}, +{"omid", "⦶", NULL}, +{"ominus", "⊖", NULL}, +{"oopf", "𝕠", NULL}, +{"opar", "⦷", NULL}, +{"operp", "⦹", NULL}, +{"oplus", "⊕", "⊕"}, +{"or", "∨", "∨"}, +{"orarr", "↻", NULL}, +{"ord", "⩝", NULL}, +{"order", "ℴ", NULL}, +{"orderof", "ℴ", NULL}, +{"ordf", "ª", "ª"}, +{"ordm", "º", "º"}, +{"origof", "⊶", NULL}, +{"oror", "⩖", NULL}, +{"orslope", "⩗", NULL}, +{"orv", "⩛", NULL}, +{"oscr", "ℴ", NULL}, +{"oslash", "ø", "ø"}, +{"osol", "⊘", NULL}, +{"otilde", "õ", "õ"}, +{"otimes", "⊗", "⊗"}, +{"otimesas", "⨶", NULL}, +{"ouml", "ö", "ö"}, +{"ovbar", "⌽", NULL}, +{"par", "∥", NULL}, +{"para", "¶", "¶"}, +{"parallel", "∥", NULL}, +{"parsim", "⫳", NULL}, +{"parsl", "⫽", NULL}, +{"part", "∂", "∂"}, +{"pcy", "п", NULL}, +{"percnt", "%", NULL}, +{"period", ".", NULL}, +{"permil", "‰", "‰"}, +{"perp", "⊥", "⊥"}, +{"pertenk", "‱", NULL}, +{"pfr", "𝔭", NULL}, +{"phi", "φ", "φ"}, +{"phiv", "ϕ", NULL}, +{"phmmat", "ℳ", NULL}, +{"phone", "☎", NULL}, +{"pi", "π", "π"}, +{"pitchfork", "⋔", NULL}, +{"piv", "ϖ", "ϖ"}, +{"planck", "ℏ", NULL}, +{"planckh", "ℎ", NULL}, +{"plankv", "ℏ", NULL}, +{"plus", "+", NULL}, +{"plusacir", "⨣", NULL}, +{"plusb", "⊞", NULL}, +{"pluscir", "⨢", NULL}, +{"plusdo", "∔", NULL}, +{"plusdu", "⨥", NULL}, +{"pluse", "⩲", NULL}, +{"plusmn", "±", "±"}, +{"plussim", "⨦", NULL}, +{"plustwo", "⨧", NULL}, +{"pm", "±", NULL}, +{"pointint", "⨕", NULL}, +{"popf", "𝕡", NULL}, +{"pound", "£", "£"}, +{"pr", "≺", NULL}, +{"prE", "⪳", NULL}, +{"prap", "⪷", NULL}, +{"prcue", "≼", NULL}, +{"pre", "⪯", NULL}, +{"prec", "≺", NULL}, +{"precapprox", "⪷", NULL}, +{"preccurlyeq", "≼", NULL}, +{"preceq", "⪯", NULL}, +{"precnapprox", "⪹", NULL}, +{"precneqq", "⪵", NULL}, +{"precnsim", "⋨", NULL}, +{"precsim", "≾", NULL}, +{"prime", "′", "′"}, +{"primes", "ℙ", NULL}, +{"prnE", "⪵", NULL}, +{"prnap", "⪹", NULL}, +{"prnsim", "⋨", NULL}, +{"prod", "∏", "∏"}, +{"profalar", "⌮", NULL}, +{"profline", "⌒", NULL}, +{"profsurf", "⌓", NULL}, +{"prop", "∝", "∝"}, +{"propto", "∝", NULL}, +{"prsim", "≾", NULL}, +{"prurel", "⊰", NULL}, +{"pscr", "𝓅", NULL}, +{"psi", "ψ", "ψ"}, +{"puncsp", " ", NULL}, +{"qfr", "𝔮", NULL}, +{"qint", "⨌", NULL}, +{"qopf", "𝕢", NULL}, +{"qprime", "⁗", NULL}, +{"qscr", "𝓆", NULL}, +{"quaternions", "ℍ", NULL}, +{"quatint", "⨖", NULL}, +{"quest", "?", NULL}, +{"questeq", "≟", NULL}, +{"quot", "\"", "\""}, +{"rAarr", "⇛", NULL}, +{"rArr", "⇒", "⇒"}, +{"rAtail", "⤜", NULL}, +{"rBarr", "⤏", NULL}, +{"rHar", "⥤", NULL}, +{"race", "∽̱", NULL}, +{"racute", "ŕ", NULL}, +{"radic", "√", "√"}, +{"raemptyv", "⦳", NULL}, +{"rang", "⟩", "〉"}, +{"rangd", "⦒", NULL}, +{"range", "⦥", NULL}, +{"rangle", "⟩", NULL}, +{"raquo", "»", "»"}, +{"rarr", "→", "→"}, +{"rarrap", "⥵", NULL}, +{"rarrb", "⇥", NULL}, +{"rarrbfs", "⤠", NULL}, +{"rarrc", "⤳", NULL}, +{"rarrfs", "⤞", NULL}, +{"rarrhk", "↪", NULL}, +{"rarrlp", "↬", NULL}, +{"rarrpl", "⥅", NULL}, +{"rarrsim", "⥴", NULL}, +{"rarrtl", "↣", NULL}, +{"rarrw", "↝", NULL}, +{"ratail", "⤚", NULL}, +{"ratio", "∶", NULL}, +{"rationals", "ℚ", NULL}, +{"rbarr", "⤍", NULL}, +{"rbbrk", "❳", NULL}, +{"rbrace", "}", NULL}, +{"rbrack", "]", NULL}, +{"rbrke", "⦌", NULL}, +{"rbrksld", "⦎", NULL}, +{"rbrkslu", "⦐", NULL}, +{"rcaron", "ř", NULL}, +{"rcedil", "ŗ", NULL}, +{"rceil", "⌉", "⌉"}, +{"rcub", "}", NULL}, +{"rcy", "р", NULL}, +{"rdca", "⤷", NULL}, +{"rdldhar", "⥩", NULL}, +{"rdquo", "”", "”"}, +{"rdquor", "”", NULL}, +{"rdsh", "↳", NULL}, +{"real", "ℜ", "ℜ"}, +{"realine", "ℛ", NULL}, +{"realpart", "ℜ", NULL}, +{"reals", "ℝ", NULL}, +{"rect", "▭", NULL}, +{"reg", "®", "®"}, +{"rfisht", "⥽", NULL}, +{"rfloor", "⌋", "⌋"}, +{"rfr", "𝔯", NULL}, +{"rhard", "⇁", NULL}, +{"rharu", "⇀", NULL}, +{"rharul", "⥬", NULL}, +{"rho", "ρ", "ρ"}, +{"rhov", "ϱ", NULL}, +{"rightarrow", "→", NULL}, +{"rightarrowtail", "↣", NULL}, +{"rightharpoondown", "⇁", NULL}, +{"rightharpoonup", "⇀", NULL}, +{"rightleftarrows", "⇄", NULL}, +{"rightleftharpoons", "⇌", NULL}, +{"rightrightarrows", "⇉", NULL}, +{"rightsquigarrow", "↝", NULL}, +{"rightthreetimes", "⋌", NULL}, +{"ring", "˚", NULL}, +{"risingdotseq", "≓", NULL}, +{"rlarr", "⇄", NULL}, +{"rlhar", "⇌", NULL}, +{"rlm", "", ""}, +{"rmoust", "⎱", NULL}, +{"rmoustache", "⎱", NULL}, +{"rnmid", "⫮", NULL}, +{"roang", "⟭", NULL}, +{"roarr", "⇾", NULL}, +{"robrk", "⟧", NULL}, +{"ropar", "⦆", NULL}, +{"ropf", "𝕣", NULL}, +{"roplus", "⨮", NULL}, +{"rotimes", "⨵", NULL}, +{"rpar", ")", NULL}, +{"rpargt", "⦔", NULL}, +{"rppolint", "⨒", NULL}, +{"rrarr", "⇉", NULL}, +{"rsaquo", "›", "›"}, +{"rscr", "𝓇", NULL}, +{"rsh", "↱", NULL}, +{"rsqb", "]", NULL}, +{"rsquo", "’", "’"}, +{"rsquor", "’", NULL}, +{"rthree", "⋌", NULL}, +{"rtimes", "⋊", NULL}, +{"rtri", "▹", NULL}, +{"rtrie", "⊵", NULL}, +{"rtrif", "▸", NULL}, +{"rtriltri", "⧎", NULL}, +{"ruluhar", "⥨", NULL}, +{"rx", "℞", NULL}, +{"sacute", "ś", NULL}, +{"sbquo", "‚", "‚"}, +{"sc", "≻", NULL}, +{"scE", "⪴", NULL}, +{"scap", "⪸", NULL}, +{"scaron", "š", "š"}, +{"sccue", "≽", NULL}, +{"sce", "⪰", NULL}, +{"scedil", "ş", NULL}, +{"scirc", "ŝ", NULL}, +{"scnE", "⪶", NULL}, +{"scnap", "⪺", NULL}, +{"scnsim", "⋩", NULL}, +{"scpolint", "⨓", NULL}, +{"scsim", "≿", NULL}, +{"scy", "с", NULL}, +{"sdot", "⋅", "⋅"}, +{"sdotb", "⊡", NULL}, +{"sdote", "⩦", NULL}, +{"seArr", "⇘", NULL}, +{"searhk", "⤥", NULL}, +{"searr", "↘", NULL}, +{"searrow", "↘", NULL}, +{"sect", "§", "§"}, +{"semi", ";", NULL}, +{"seswar", "⤩", NULL}, +{"setminus", "∖", NULL}, +{"setmn", "∖", NULL}, +{"sext", "✶", NULL}, +{"sfr", "𝔰", NULL}, +{"sfrown", "⌢", NULL}, +{"sharp", "♯", NULL}, +{"shchcy", "щ", NULL}, +{"shcy", "ш", NULL}, +{"shortmid", "∣", NULL}, +{"shortparallel", "∥", NULL}, +{"shy", "", ""}, +{"sigma", "σ", "σ"}, +{"sigmaf", "ς", "ς"}, +{"sigmav", "ς", NULL}, +{"sim", "∼", "∼"}, +{"simdot", "⩪", NULL}, +{"sime", "≃", NULL}, +{"simeq", "≃", NULL}, +{"simg", "⪞", NULL}, +{"simgE", "⪠", NULL}, +{"siml", "⪝", NULL}, +{"simlE", "⪟", NULL}, +{"simne", "≆", NULL}, +{"simplus", "⨤", NULL}, +{"simrarr", "⥲", NULL}, +{"slarr", "←", NULL}, +{"smallsetminus", "∖", NULL}, +{"smashp", "⨳", NULL}, +{"smeparsl", "⧤", NULL}, +{"smid", "∣", NULL}, +{"smile", "⌣", NULL}, +{"smt", "⪪", NULL}, +{"smte", "⪬", NULL}, +{"smtes", "⪬︀", NULL}, +{"softcy", "ь", NULL}, +{"sol", "/", NULL}, +{"solb", "⧄", NULL}, +{"solbar", "⌿", NULL}, +{"sopf", "𝕤", NULL}, +{"spades", "♠", "♠"}, +{"spadesuit", "♠", NULL}, +{"spar", "∥", NULL}, +{"sqcap", "⊓", NULL}, +{"sqcaps", "⊓︀", NULL}, +{"sqcup", "⊔", NULL}, +{"sqcups", "⊔︀", NULL}, +{"sqsub", "⊏", NULL}, +{"sqsube", "⊑", NULL}, +{"sqsubset", "⊏", NULL}, +{"sqsubseteq", "⊑", NULL}, +{"sqsup", "⊐", NULL}, +{"sqsupe", "⊒", NULL}, +{"sqsupset", "⊐", NULL}, +{"sqsupseteq", "⊒", NULL}, +{"squ", "□", NULL}, +{"square", "□", NULL}, +{"squarf", "▪", NULL}, +{"squf", "▪", NULL}, +{"srarr", "→", NULL}, +{"sscr", "𝓈", NULL}, +{"ssetmn", "∖", NULL}, +{"ssmile", "⌣", NULL}, +{"sstarf", "⋆", NULL}, +{"star", "☆", NULL}, +{"starf", "★", NULL}, +{"straightepsilon", "ϵ", NULL}, +{"straightphi", "ϕ", NULL}, +{"strns", "¯", NULL}, +{"sub", "⊂", "⊂"}, +{"subE", "⫅", NULL}, +{"subdot", "⪽", NULL}, +{"sube", "⊆", "⊆"}, +{"subedot", "⫃", NULL}, +{"submult", "⫁", NULL}, +{"subnE", "⫋", NULL}, +{"subne", "⊊", NULL}, +{"subplus", "⪿", NULL}, +{"subrarr", "⥹", NULL}, +{"subset", "⊂", NULL}, +{"subseteq", "⊆", NULL}, +{"subseteqq", "⫅", NULL}, +{"subsetneq", "⊊", NULL}, +{"subsetneqq", "⫋", NULL}, +{"subsim", "⫇", NULL}, +{"subsub", "⫕", NULL}, +{"subsup", "⫓", NULL}, +{"succ", "≻", NULL}, +{"succapprox", "⪸", NULL}, +{"succcurlyeq", "≽", NULL}, +{"succeq", "⪰", NULL}, +{"succnapprox", "⪺", NULL}, +{"succneqq", "⪶", NULL}, +{"succnsim", "⋩", NULL}, +{"succsim", "≿", NULL}, +{"sum", "∑", "∑"}, +{"sung", "♪", NULL}, +{"sup", "⊃", "⊃"}, +{"sup1", "¹", "¹"}, +{"sup2", "²", "²"}, +{"sup3", "³", "³"}, +{"supE", "⫆", NULL}, +{"supdot", "⪾", NULL}, +{"supdsub", "⫘", NULL}, +{"supe", "⊇", "⊇"}, +{"supedot", "⫄", NULL}, +{"suphsol", "⟉", NULL}, +{"suphsub", "⫗", NULL}, +{"suplarr", "⥻", NULL}, +{"supmult", "⫂", NULL}, +{"supnE", "⫌", NULL}, +{"supne", "⊋", NULL}, +{"supplus", "⫀", NULL}, +{"supset", "⊃", NULL}, +{"supseteq", "⊇", NULL}, +{"supseteqq", "⫆", NULL}, +{"supsetneq", "⊋", NULL}, +{"supsetneqq", "⫌", NULL}, +{"supsim", "⫈", NULL}, +{"supsub", "⫔", NULL}, +{"supsup", "⫖", NULL}, +{"swArr", "⇙", NULL}, +{"swarhk", "⤦", NULL}, +{"swarr", "↙", NULL}, +{"swarrow", "↙", NULL}, +{"swnwar", "⤪", NULL}, +{"szlig", "ß", "ß"}, +{"target", "⌖", NULL}, +{"tau", "τ", "τ"}, +{"tbrk", "⎴", NULL}, +{"tcaron", "ť", NULL}, +{"tcedil", "ţ", NULL}, +{"tcy", "т", NULL}, +{"tdot", "⃛", NULL}, +{"telrec", "⌕", NULL}, +{"tfr", "𝔱", NULL}, +{"there4", "∴", "∴"}, +{"therefore", "∴", NULL}, +{"theta", "θ", "θ"}, +{"thetasym", "ϑ", "ϑ"}, +{"thetav", "ϑ", NULL}, +{"thickapprox", "≈", NULL}, +{"thicksim", "∼", NULL}, +{"thinsp", " ", " "}, +{"thkap", "≈", NULL}, +{"thksim", "∼", NULL}, +{"thorn", "þ", "þ"}, +{"tilde", "˜", "˜"}, +{"times", "×", "×"}, +{"timesb", "⊠", NULL}, +{"timesbar", "⨱", NULL}, +{"timesd", "⨰", NULL}, +{"tint", "∭", NULL}, +{"toea", "⤨", NULL}, +{"top", "⊤", NULL}, +{"topbot", "⌶", NULL}, +{"topcir", "⫱", NULL}, +{"topf", "𝕥", NULL}, +{"topfork", "⫚", NULL}, +{"tosa", "⤩", NULL}, +{"tprime", "‴", NULL}, +{"trade", "™", "™"}, +{"triangle", "▵", NULL}, +{"triangledown", "▿", NULL}, +{"triangleleft", "◃", NULL}, +{"trianglelefteq", "⊴", NULL}, +{"triangleq", "≜", NULL}, +{"triangleright", "▹", NULL}, +{"trianglerighteq", "⊵", NULL}, +{"tridot", "◬", NULL}, +{"trie", "≜", NULL}, +{"triminus", "⨺", NULL}, +{"triplus", "⨹", NULL}, +{"trisb", "⧍", NULL}, +{"tritime", "⨻", NULL}, +{"trpezium", "⏢", NULL}, +{"tscr", "𝓉", NULL}, +{"tscy", "ц", NULL}, +{"tshcy", "ћ", NULL}, +{"tstrok", "ŧ", NULL}, +{"twixt", "≬", NULL}, +{"twoheadleftarrow", "↞", NULL}, +{"twoheadrightarrow", "↠", NULL}, +{"uArr", "⇑", "⇑"}, +{"uHar", "⥣", NULL}, +{"uacute", "ú", "ú"}, +{"uarr", "↑", "↑"}, +{"ubrcy", "ў", NULL}, +{"ubreve", "ŭ", NULL}, +{"ucirc", "û", "û"}, +{"ucy", "у", NULL}, +{"udarr", "⇅", NULL}, +{"udblac", "ű", NULL}, +{"udhar", "⥮", NULL}, +{"ufisht", "⥾", NULL}, +{"ufr", "𝔲", NULL}, +{"ugrave", "ù", "ù"}, +{"uharl", "↿", NULL}, +{"uharr", "↾", NULL}, +{"uhblk", "▀", NULL}, +{"ulcorn", "⌜", NULL}, +{"ulcorner", "⌜", NULL}, +{"ulcrop", "⌏", NULL}, +{"ultri", "◸", NULL}, +{"umacr", "ū", NULL}, +{"uml", "¨", "¨"}, +{"uogon", "ų", NULL}, +{"uopf", "𝕦", NULL}, +{"uparrow", "↑", NULL}, +{"updownarrow", "↕", NULL}, +{"upharpoonleft", "↿", NULL}, +{"upharpoonright", "↾", NULL}, +{"uplus", "⊎", NULL}, +{"upsi", "υ", NULL}, +{"upsih", "ϒ", "ϒ"}, +{"upsilon", "υ", "υ"}, +{"upuparrows", "⇈", NULL}, +{"urcorn", "⌝", NULL}, +{"urcorner", "⌝", NULL}, +{"urcrop", "⌎", NULL}, +{"uring", "ů", NULL}, +{"urtri", "◹", NULL}, +{"uscr", "𝓊", NULL}, +{"utdot", "⋰", NULL}, +{"utilde", "ũ", NULL}, +{"utri", "▵", NULL}, +{"utrif", "▴", NULL}, +{"uuarr", "⇈", NULL}, +{"uuml", "ü", "ü"}, +{"uwangle", "⦧", NULL}, +{"vArr", "⇕", NULL}, +{"vBar", "⫨", NULL}, +{"vBarv", "⫩", NULL}, +{"vDash", "⊨", NULL}, +{"vangrt", "⦜", NULL}, +{"varepsilon", "ϵ", NULL}, +{"varkappa", "ϰ", NULL}, +{"varnothing", "∅", NULL}, +{"varphi", "ϕ", NULL}, +{"varpi", "ϖ", NULL}, +{"varpropto", "∝", NULL}, +{"varr", "↕", NULL}, +{"varrho", "ϱ", NULL}, +{"varsigma", "ς", NULL}, +{"varsubsetneq", "⊊︀", NULL}, +{"varsubsetneqq", "⫋︀", NULL}, +{"varsupsetneq", "⊋︀", NULL}, +{"varsupsetneqq", "⫌︀", NULL}, +{"vartheta", "ϑ", NULL}, +{"vartriangleleft", "⊲", NULL}, +{"vartriangleright", "⊳", NULL}, +{"vcy", "в", NULL}, +{"vdash", "⊢", NULL}, +{"vee", "∨", NULL}, +{"veebar", "⊻", NULL}, +{"veeeq", "≚", NULL}, +{"vellip", "⋮", NULL}, +{"verbar", "|", NULL}, +{"vert", "|", NULL}, +{"vfr", "𝔳", NULL}, +{"vltri", "⊲", NULL}, +{"vnsub", "⊂⃒", NULL}, +{"vnsup", "⊃⃒", NULL}, +{"vopf", "𝕧", NULL}, +{"vprop", "∝", NULL}, +{"vrtri", "⊳", NULL}, +{"vscr", "𝓋", NULL}, +{"vsubnE", "⫋︀", NULL}, +{"vsubne", "⊊︀", NULL}, +{"vsupnE", "⫌︀", NULL}, +{"vsupne", "⊋︀", NULL}, +{"vzigzag", "⦚", NULL}, +{"wcirc", "ŵ", NULL}, +{"wedbar", "⩟", NULL}, +{"wedge", "∧", NULL}, +{"wedgeq", "≙", NULL}, +{"weierp", "℘", "℘"}, +{"wfr", "𝔴", NULL}, +{"wopf", "𝕨", NULL}, +{"wp", "℘", NULL}, +{"wr", "≀", NULL}, +{"wreath", "≀", NULL}, +{"wscr", "𝓌", NULL}, +{"xcap", "⋂", NULL}, +{"xcirc", "◯", NULL}, +{"xcup", "⋃", NULL}, +{"xdtri", "▽", NULL}, +{"xfr", "𝔵", NULL}, +{"xhArr", "⟺", NULL}, +{"xharr", "⟷", NULL}, +{"xi", "ξ", "ξ"}, +{"xlArr", "⟸", NULL}, +{"xlarr", "⟵", NULL}, +{"xmap", "⟼", NULL}, +{"xnis", "⋻", NULL}, +{"xodot", "⨀", NULL}, +{"xopf", "𝕩", NULL}, +{"xoplus", "⨁", NULL}, +{"xotime", "⨂", NULL}, +{"xrArr", "⟹", NULL}, +{"xrarr", "⟶", NULL}, +{"xscr", "𝓍", NULL}, +{"xsqcup", "⨆", NULL}, +{"xuplus", "⨄", NULL}, +{"xutri", "△", NULL}, +{"xvee", "⋁", NULL}, +{"xwedge", "⋀", NULL}, +{"yacute", "ý", "ý"}, +{"yacy", "я", NULL}, +{"ycirc", "ŷ", NULL}, +{"ycy", "ы", NULL}, +{"yen", "¥", "¥"}, +{"yfr", "𝔶", NULL}, +{"yicy", "ї", NULL}, +{"yopf", "𝕪", NULL}, +{"yscr", "𝓎", NULL}, +{"yucy", "ю", NULL}, +{"yuml", "ÿ", "ÿ"}, +{"zacute", "ź", NULL}, +{"zcaron", "ž", NULL}, +{"zcy", "з", NULL}, +{"zdot", "ż", NULL}, +{"zeetrf", "ℨ", NULL}, +{"zeta", "ζ", "ζ"}, +{"zfr", "𝔷", NULL}, +{"zhcy", "ж", NULL}, +{"zigrarr", "⇝", NULL}, +{"zopf", "𝕫", NULL}, +{"zscr", "𝓏", NULL}, +{"zwj", "", ""}, +{"zwnj", "", ""}, +}; +#endif /* HTML_CHARREFS_H */ diff --git a/src/klist.c b/src/klist.c index 813269a3..e5e695e2 100644 --- a/src/klist.c +++ b/src/klist.c @@ -74,7 +74,7 @@ int a_Klist_insert(Klist_t **Klist, void *Data) a_Klist_get_data((*Klist), (*Klist)->Counter)); Node = dNew(KlistNode_t, 1); - Node->Key = (*Klist)->Counter; + Node->Key = (*Klist)->Counter; Node->Data = Data; dList_insert_sorted((*Klist)->List, Node, Klist_node_by_node_cmp); return (*Klist)->Counter; diff --git a/src/menu.cc b/src/menu.cc index b93106e1..e86c3a06 100644 --- a/src/menu.cc +++ b/src/menu.cc @@ -232,17 +232,31 @@ static void Menu_stylesheet_cb(Fl_Widget*, void *vUrl) } } +static void Menu_bugmeter_validate(const char *validator_url) +{ + if (popup_url && + dStrAsciiCasecmp(URL_SCHEME(popup_url), "dpi")) { + const char *popup_str = URL_STR(popup_url), + *ptr = strrchr(popup_str, '#'); + char *no_fragment = ptr ? dStrndup(popup_str, ptr - popup_str) + : dStrdup(popup_str); + char *encoded = a_Url_encode_hex_str(no_fragment); + Dstr *dstr = dStr_sized_new(128); + + dStr_sprintf(dstr, validator_url, encoded); + a_UIcmd_open_urlstr(popup_bw, dstr->str); + dStr_free(dstr, 1); + dFree(encoded); + dFree(no_fragment); + } +} + /* * Validate URL with the W3C */ static void Menu_bugmeter_validate_w3c_cb(Fl_Widget*, void*) { - Dstr *dstr = dStr_sized_new(128); - - dStr_sprintf(dstr, "http://validator.w3.org/check?uri=%s", - URL_STR(popup_url)); - a_UIcmd_open_urlstr(popup_bw, dstr->str); - dStr_free(dstr, 1); + Menu_bugmeter_validate("http://validator.w3.org/check?uri=%s"); } /* @@ -250,13 +264,8 @@ static void Menu_bugmeter_validate_w3c_cb(Fl_Widget*, void*) */ static void Menu_bugmeter_validate_wdg_cb(Fl_Widget*, void*) { - Dstr *dstr = dStr_sized_new(128); - - dStr_sprintf(dstr, - "http://www.htmlhelp.org/cgi-bin/validate.cgi?url=%s&warnings=yes", - URL_STR(popup_url)); - a_UIcmd_open_urlstr(popup_bw, dstr->str); - dStr_free(dstr, 1); + Menu_bugmeter_validate( + "http://www.htmlhelp.org/cgi-bin/validate.cgi?url=%s&warnings=yes"); } /* @@ -353,6 +353,7 @@ void a_Nav_push(BrowserWindow *bw, const DilloUrl *url, a_Nav_cancel_expect(bw); a_Bw_expect(bw, url); Nav_open_url(bw, url, requester, 0); + a_UIcmd_set_location_text(bw, URL_STR(url)); } /* diff --git a/src/paths.hh b/src/paths.hh index 8f52cd86..ecc02f8b 100644 --- a/src/paths.hh +++ b/src/paths.hh @@ -15,6 +15,7 @@ #define PATHS_RC_PREFS "dillorc" #define PATHS_RC_KEYS "keysrc" #define PATHS_RC_DOMAIN "domainrc" +#define PATHS_HSTS_PRELOAD "hsts_preload" class Paths { public: @@ -103,8 +103,8 @@ void Png_error_handling(png_structp png_ptr, png_const_charp msg) { DilloPng *png; - MSG("Png_error_handling: %s\n", msg); png = png_get_error_ptr(png_ptr); + MSG("Png_error_handling: %s: %s\n", URL_STR(png->url), msg); png->error = 1; png->state = IS_finished; diff --git a/src/prefs.c b/src/prefs.c index fbd17f33..4ee65ba3 100644 --- a/src/prefs.c +++ b/src/prefs.c @@ -63,10 +63,14 @@ void a_Prefs_init(void) prefs.http_language = NULL; prefs.http_proxy = NULL; prefs.http_max_conns = 6; + prefs.http_persistent_conns = FALSE; prefs.http_proxyuser = NULL; prefs.http_referer = dStrdup(PREFS_HTTP_REFERER); + prefs.http_strict_transport_security = TRUE; prefs.http_user_agent = dStrdup(PREFS_HTTP_USER_AGENT); prefs.limit_text_width = FALSE; + prefs.adjust_min_width = TRUE; + prefs.adjust_table_min_width = TRUE; prefs.load_images=TRUE; prefs.load_background_images=FALSE; prefs.load_stylesheets=TRUE; diff --git a/src/prefs.h b/src/prefs.h index bb97651e..d22ef656 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -66,6 +66,8 @@ typedef struct { int panel_size; bool_t small_icons; bool_t limit_text_width; + bool_t adjust_min_width; + bool_t adjust_table_min_width; bool_t w3c_plus_heuristics; bool_t focus_new_tab; double font_factor; @@ -91,6 +93,8 @@ typedef struct { bool_t load_background_images; bool_t load_stylesheets; bool_t parse_embedded_css; + bool_t http_persistent_conns; + bool_t http_strict_transport_security; int32_t buffered_drawing; char *font_serif; char *font_sans_serif; diff --git a/src/prefsparser.cc b/src/prefsparser.cc index bf891491..a57a1642 100644 --- a/src/prefsparser.cc +++ b/src/prefsparser.cc @@ -167,11 +167,16 @@ void PrefsParser::parse(FILE *fp) { "home", &prefs.home, PREFS_URL, 0 }, { "http_language", &prefs.http_language, PREFS_STRING, 0 }, { "http_max_conns", &prefs.http_max_conns, PREFS_INT32, 0 }, + { "http_persistent_conns", &prefs.http_persistent_conns, PREFS_BOOL, 0 }, { "http_proxy", &prefs.http_proxy, PREFS_URL, 0 }, { "http_proxyuser", &prefs.http_proxyuser, PREFS_STRING, 0 }, { "http_referer", &prefs.http_referer, PREFS_STRING, 0 }, + { "http_strict_transport_security",&prefs.http_strict_transport_security, + PREFS_BOOL, 0 }, { "http_user_agent", &prefs.http_user_agent, PREFS_STRING, 0 }, { "limit_text_width", &prefs.limit_text_width, PREFS_BOOL, 0 }, + { "adjust_min_width", &prefs.adjust_min_width, PREFS_BOOL, 0 }, + { "adjust_table_min_width", &prefs.adjust_table_min_width, PREFS_BOOL, 0 }, { "load_images", &prefs.load_images, PREFS_BOOL, 0 }, { "load_background_images", &prefs.load_background_images, PREFS_BOOL, 0 }, { "load_stylesheets", &prefs.load_stylesheets, PREFS_BOOL, 0 }, diff --git a/src/styleengine.cc b/src/styleengine.cc index 91a2b2c5..c005f881 100644 --- a/src/styleengine.cc +++ b/src/styleengine.cc @@ -71,6 +71,7 @@ StyleEngine::StyleEngine (dw::core::Layout *layout, this->pageUrl = pageUrl ? a_Url_dup(pageUrl) : NULL; this->baseUrl = baseUrl ? a_Url_dup(baseUrl) : NULL; importDepth = 0; + dpmm = layout->dpiX () / 25.4; /* assume dpiX == dpiY */ stackPush (); Node *n = stack->getLastRef (); @@ -154,7 +155,7 @@ void StyleEngine::startElement (const char *tagname, BrowserWindow *bw) { } void StyleEngine::setId (const char *id) { - DoctreeNode *dn = doctree->top (); + DoctreeNode *dn = doctree->top (); assert (dn->id == NULL); dn->id = dStrdup (id); } @@ -588,6 +589,12 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, computeValue (&attrs->hBorderSpacing, p->value.intVal,attrs->font); computeValue (&attrs->vBorderSpacing, p->value.intVal,attrs->font); break; + case CSS_PROPERTY_BOTTOM: + computeLength (&attrs->bottom, p->value.intVal, attrs->font); + break; + case CSS_PROPERTY_CLEAR: + attrs->clear = (ClearType) p->value.intVal; + break; case CSS_PROPERTY_COLOR: attrs->color = Color::create (layout, p->value.intVal); break; @@ -599,6 +606,12 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, if (attrs->display == DISPLAY_NONE) stack->getRef (i)->displayNone = true; break; + case CSS_PROPERTY_FLOAT: + attrs->vloat = (FloatType) p->value.intVal; + break; + case CSS_PROPERTY_LEFT: + computeLength (&attrs->left, p->value.intVal, attrs->font); + 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; @@ -638,6 +651,9 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, if (attrs->margin.top < 0) // \todo fix negative margins in dw/* attrs->margin.top = 0; break; + case CSS_PROPERTY_OVERFLOW: + attrs->overflow = (Overflow) p->value.intVal; + break; case CSS_PROPERTY_PADDING_TOP: computeValue (&attrs->padding.top, p->value.intVal, attrs->font); break; @@ -650,6 +666,12 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, case CSS_PROPERTY_PADDING_RIGHT: computeValue (&attrs->padding.right, p->value.intVal, attrs->font); break; + case CSS_PROPERTY_POSITION: + attrs->position = (Position) p->value.intVal; + break; + case CSS_PROPERTY_RIGHT: + computeLength (&attrs->right, p->value.intVal, attrs->font); + break; case CSS_PROPERTY_TEXT_ALIGN: attrs->textAlign = (TextAlignType) p->value.intVal; break; @@ -662,6 +684,9 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, case CSS_PROPERTY_TEXT_TRANSFORM: attrs->textTransform = (TextTransform) p->value.intVal; break; + case CSS_PROPERTY_TOP: + computeLength (&attrs->top, p->value.intVal, attrs->font); + break; case CSS_PROPERTY_VERTICAL_ALIGN: attrs->valign = (VAlignType) p->value.intVal; break; @@ -689,6 +714,18 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, else if (attrs->wordSpacing < -1000) attrs->wordSpacing = -1000; break; + case CSS_PROPERTY_MIN_WIDTH: + computeLength (&attrs->minWidth, p->value.intVal, attrs->font); + break; + case CSS_PROPERTY_MAX_WIDTH: + computeLength (&attrs->maxWidth, p->value.intVal, attrs->font); + break; + case CSS_PROPERTY_MIN_HEIGHT: + computeLength (&attrs->minHeight, p->value.intVal, attrs->font); + break; + case CSS_PROPERTY_MAX_HEIGHT: + computeLength (&attrs->maxHeight, p->value.intVal, attrs->font); + break; case PROPERTY_X_LINK: attrs->x_link = p->value.intVal; break; @@ -743,11 +780,6 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props, * \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); @@ -1000,7 +1032,7 @@ void StyleEngine::init () { "code, tt, pre, samp, kbd {font-family: monospace}" /* WORKAROUND: Reset font properties in tables as some * pages rely on it (e.g. gmail). - * http://developer.mozilla.org/En/Fixing_Table_Inheritance_in_Quirks_Mode + * http://developer.mozilla.org/en-US/Fixing_Table_Inheritance_in_Quirks_Mode * has a detailed description of the issue. */ "table, caption {font-size: medium; font-weight: normal}"; diff --git a/src/styleengine.hh b/src/styleengine.hh index a07d1863..db3e3b85 100644 --- a/src/styleengine.hh +++ b/src/styleengine.hh @@ -36,6 +36,7 @@ class StyleEngine { CssContext *cssContext; Doctree *doctree; int importDepth; + float dpmm; DilloUrl *pageUrl, *baseUrl; void stackPush (); diff --git a/src/table.cc b/src/table.cc index 7f38cee4..188becbc 100644 --- a/src/table.cc +++ b/src/table.cc @@ -15,6 +15,7 @@ #include "dw/style.hh" #include "dw/textblock.hh" #include "dw/table.hh" +#include "dw/simpletablecell.hh" #include "prefs.h" #include "msg.h" @@ -445,11 +446,11 @@ static void Html_tag_content_table_cell(DilloHtml *html, rowspan = MAX(1, strtol (attrbuf, NULL, 10)); if (html->style ()->textAlign == TEXT_ALIGN_STRING) - col_tb = new dw::TableCell ( + col_tb = new AlignedTableCell ( ((dw::Table*)S_TOP(html)->table)->getCellRef (), prefs.limit_text_width); else - col_tb = new Textblock (prefs.limit_text_width); + col_tb = new SimpleTableCell (prefs.limit_text_width); if (html->style()->borderCollapse == BORDER_MODEL_COLLAPSE){ Html_set_collapsing_border_model(html, col_tb); diff --git a/src/tipwin.cc b/src/tipwin.cc index 01d9a2f4..7cfa0844 100644 --- a/src/tipwin.cc +++ b/src/tipwin.cc @@ -41,7 +41,7 @@ TipWin::TipWin() : Fl_Menu_Window(1, 1) // will autosize { bgcolor = fl_color_cube(FL_NUM_RED - 1, FL_NUM_GREEN - 1, FL_NUM_BLUE - 2); recent = 0; - strcpy(tip, ""); + tip[0] = '\0'; cur_widget = NULL; set_override(); // no border end(); diff --git a/src/uicmd.cc b/src/uicmd.cc index 5225be75..9541a7df 100644 --- a/src/uicmd.cc +++ b/src/uicmd.cc @@ -933,7 +933,7 @@ static int UIcmd_save_file_check(const char *name) int ch; ds = dStr_sized_new(128); dStr_sprintf(ds, - "The file:\n %s (%d Bytes)\nalready exists. What do we do?", + "The file: %s (%d Bytes) already exists. What do we do?", name, (int)ss.st_size); ch = a_Dialog_choice("Dillo Save: File exists!", ds->str, "Abort", "Continue", "Rename", NULL); @@ -46,6 +46,7 @@ #include <ctype.h> #include "url.h" +#include "hsts.h" #include "msg.h" static const char *HEX = "0123456789ABCDEF"; @@ -118,6 +119,12 @@ const char *a_Url_hostname(const DilloUrl *u) } } + if (!url->port) { + if (!dStrAsciiCasecmp(url->scheme, "http")) + url->port = URL_HTTP_PORT; + else if (!dStrAsciiCasecmp(url->scheme, "https")) + url->port = URL_HTTPS_PORT; + } return url->hostname; } @@ -134,10 +141,17 @@ static DilloUrl *Url_object_new(const char *uri_str) url = dNew0(DilloUrl, 1); + /* url->buffer is given a little extra room in case HSTS needs to transform + * a URL string ending in ":80" to ":443". + */ + int len = strlen(uri_str)+2; + s = dNew(char, len); + memcpy(s, uri_str, len-1); + s = dStrstrip(s); + /* remove leading & trailing space from buffer */ - url->buffer = dStrstrip(dStrdup(uri_str)); + url->buffer = s; - s = (char *) url->buffer; p = strpbrk(s, ":/?#"); if (p && p[0] == ':' && p > s) { /* scheme */ *p = 0; @@ -198,7 +212,6 @@ void a_Url_free(DilloUrl *url) dFree((char *)url->hostname); dFree((char *)url->buffer); dStr_free(url->data, 1); - dFree((char *)url->alt); dFree(url); } } @@ -207,7 +220,6 @@ void a_Url_free(DilloUrl *url) * Resolve the URL as RFC3986 suggests. */ static Dstr *Url_resolve_relative(const char *RelStr, - DilloUrl *BaseUrlPar, const char *BaseStr) { char *p, *s, *e; @@ -218,9 +230,7 @@ static Dstr *Url_resolve_relative(const char *RelStr, /* parse relative URL */ RelUrl = Url_object_new(RelStr); - if (BaseUrlPar) { - BaseUrl = BaseUrlPar; - } else if (RelUrl->scheme == NULL) { + if (RelUrl->scheme == NULL) { /* only required when there's no <scheme> in RelStr */ BaseUrl = Url_object_new(BaseStr); } @@ -330,8 +340,7 @@ static Dstr *Url_resolve_relative(const char *RelStr, done: dStr_free(Path, TRUE); a_Url_free(RelUrl); - if (BaseUrl != BaseUrlPar) - a_Url_free(BaseUrl); + a_Url_free(BaseUrl); return SolvedUrl; } @@ -350,7 +359,6 @@ done: * port = 8080 * flags = URL_Get * data = Dstr * ("") - * alt = NULL * ismap_url_len = 0 * } * @@ -379,10 +387,10 @@ DilloUrl* a_Url_new(const char *url_str, const char *base_url) for (i = 0; url_str[i]; ++i) if (url_str[i] > 0x1F && url_str[i] < 0x7F && url_str[i] != ' ') *p++ = url_str[i]; - else { - *p++ = '%'; - *p++ = HEX[(url_str[i] >> 4) & 15]; - *p++ = HEX[url_str[i] & 15]; + else { + *p++ = '%'; + *p++ = HEX[(url_str[i] >> 4) & 15]; + *p++ = HEX[url_str[i] & 15]; } *p = 0; urlstr = str1; @@ -400,7 +408,7 @@ DilloUrl* a_Url_new(const char *url_str, const char *base_url) } /* Resolve the URL */ - SolvedUrl = Url_resolve_relative(urlstr, NULL, base_url); + SolvedUrl = Url_resolve_relative(urlstr, base_url); _MSG("SolvedUrl = %s\n", SolvedUrl->str); /* Fill url data */ @@ -412,6 +420,33 @@ DilloUrl* a_Url_new(const char *url_str, const char *base_url) dFree(str1); dFree(str2); + + /* + * A site's HTTP Strict Transport Security policy may direct us to transform + * URLs like "http://en.wikipedia.org:80" to "https://en.wikipedia.org:443". + */ + if (prefs.http_strict_transport_security && + url->scheme && !dStrAsciiCasecmp(url->scheme, "http") && + a_Hsts_require_https(a_Url_hostname(url))) { + const char *const scheme = "https"; + + MSG("url: HSTS transformation for %s.\n", url->url_string->str); + url->scheme = scheme; + if (url->port == URL_HTTP_PORT) + url->port = URL_HTTPS_PORT; + + if (url->authority) { + int len = strlen(url->authority); + + if (len >= 3 && !strcmp(url->authority + len-3, ":80")) { + strcpy((char *)url->authority + len-2, "443"); + } + } + + dStr_free(url->url_string, TRUE); + url->url_string = NULL; + } + return url; } @@ -429,7 +464,6 @@ DilloUrl* a_Url_dup(const DilloUrl *ori) url->url_string = dStr_new(URL_STR(ori)); url->port = ori->port; url->flags = ori->flags; - url->alt = dStrdup(ori->alt); url->ismap_url_len = ori->ismap_url_len; url->illegal_chars = ori->illegal_chars; url->illegal_chars_spc = ori->illegal_chars_spc; @@ -489,17 +523,6 @@ void a_Url_set_data(DilloUrl *u, Dstr **data) } /* - * Set DilloUrl alt (alternate text to the URL. Used by image maps) - */ -void a_Url_set_alt(DilloUrl *u, const char *alt) -{ - if (u) { - dFree((char *)u->alt); - u->alt = dStrdup(alt); - } -} - -/* * Set DilloUrl ismap coordinates * (this is optimized for not hogging the CPU) */ @@ -509,8 +532,7 @@ void a_Url_set_ismap_coords(DilloUrl *u, char *coord_str) if (!u->ismap_url_len) { /* Save base-url length (without coords) */ - u->ismap_url_len = URL_STR_(u) ? u->url_string->len : 0; - a_Url_set_flags(u, URL_FLAGS(u) | URL_Ismap); + u->ismap_url_len = URL_STR_(u) ? u->url_string->len : 0; } if (u->url_string) { dStr_truncate(u->url_string, u->ismap_url_len); @@ -638,7 +660,7 @@ char *a_Url_string_strip_delimiters(const char *str) /* * Is the provided hostname an IP address? */ -static bool_t Url_host_is_ip(const char *host) +bool_t a_Url_host_is_ip(const char *host) { uint_t len; @@ -724,7 +746,7 @@ static const char *Url_host_find_public_suffix(const char *host) const char *s; uint_t dots; - if (!host || !*host || Url_host_is_ip(host)) + if (!host || !*host || a_Url_host_is_ip(host)) return host; s = host; @@ -13,14 +13,8 @@ #include "../dlib/dlib.h" -#define DILLO_URL_HTTP_PORT 80 -#define DILLO_URL_HTTPS_PORT 443 -#define DILLO_URL_FTP_PORT 21 -#define DILLO_URL_MAILTO_PORT 25 -#define DILLO_URL_NEWS_PORT 119 -#define DILLO_URL_TELNET_PORT 23 -#define DILLO_URL_GOPHER_PORT 70 - +#define URL_HTTP_PORT 80 +#define URL_HTTPS_PORT 443 /* * Values for DilloUrl->flags. @@ -28,12 +22,8 @@ */ #define URL_Get (1 << 0) #define URL_Post (1 << 1) -#define URL_ISindex (1 << 2) -#define URL_Ismap (1 << 3) -#define URL_RealmAccess (1 << 4) #define URL_E2EQuery (1 << 5) -#define URL_ReloadImages (1 << 6) #define URL_ReloadPage (1 << 7) #define URL_ReloadFromCache (1 << 8) @@ -53,7 +43,6 @@ #define URL_QUERY_(u) (u)->query #define URL_FRAGMENT_(u) (u)->fragment #define URL_HOST_(u) a_Url_hostname(u) -#define URL_ALT_(u) (u)->alt #define URL_STR_(u) a_Url_str(u) /* this returns a Dstr* */ #define URL_DATA_(u) (u)->data @@ -75,9 +64,8 @@ #define URL_QUERY(u) NPTR2STR(URL_QUERY_(u)) #define URL_FRAGMENT(u) NPTR2STR(URL_FRAGMENT_(u)) #define URL_HOST(u) NPTR2STR(URL_HOST_(u)) -#define URL_DATA(u) URL_DATA_(u) -#define URL_ALT(u) NPTR2STR(URL_ALT_(u)) #define URL_STR(u) NPTR2STR(URL_STR_(u)) +#define URL_DATA(u) URL_DATA_(u) #define URL_PORT(u) URL_PORT_(u) #define URL_FLAGS(u) URL_FLAGS_(u) #define URL_ILLEGAL_CHARS(u) URL_ILLEGAL_CHARS_(u) @@ -100,7 +88,6 @@ typedef struct { int port; int flags; Dstr *data; /* POST */ - const char *alt; /* "alt" text (used by image maps) */ int ismap_url_len; /* Used by server side image maps */ int illegal_chars; /* number of illegal chars */ int illegal_chars_spc; /* number of illegal space chars */ @@ -115,11 +102,11 @@ DilloUrl* a_Url_dup(const DilloUrl *u); int a_Url_cmp(const DilloUrl *A, const DilloUrl *B); void a_Url_set_flags(DilloUrl *u, int flags); void a_Url_set_data(DilloUrl *u, Dstr **data); -void a_Url_set_alt(DilloUrl *u, const char *alt); 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); +bool_t a_Url_host_is_ip(const char *host); bool_t a_Url_same_organization(const DilloUrl *u1, const DilloUrl *u2); #ifdef __cplusplus } @@ -128,7 +128,7 @@ DilloWeb* a_Web_new(BrowserWindow *bw, const DilloUrl *url, web->flags = 0; web->Image = NULL; web->filename = NULL; - web->stream = NULL; + web->stream = NULL; web->SavedBytes = 0; web->bgColor = 0x000000; /* Dummy value will be overwritten * in a_Web_dispatch_by_type. */ |