aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/IO/IO.c34
-rw-r--r--src/IO/Makefile.am7
-rw-r--r--src/IO/Url.h5
-rw-r--r--src/IO/about.c2
-rw-r--r--src/IO/http.c670
-rw-r--r--src/IO/tls.c1258
-rw-r--r--src/IO/tls.h49
-rw-r--r--src/Makefile.am9
-rw-r--r--src/cache.c140
-rw-r--r--src/cache.h3
-rw-r--r--src/capi.c225
-rw-r--r--src/colors.c2
-rw-r--r--src/cookies.h3
-rw-r--r--src/css.cc2
-rw-r--r--src/css.hh2
-rw-r--r--src/cssparser.cc68
-rw-r--r--src/decode.c26
-rw-r--r--src/decode.h16
-rw-r--r--src/dialog.cc31
-rw-r--r--src/dillo.cc26
-rw-r--r--src/form.cc4
-rw-r--r--src/gif.c4
-rw-r--r--src/hsts.c364
-rw-r--r--src/hsts.h19
-rwxr-xr-xsrc/hsts_preload2037
-rw-r--r--src/html.cc529
-rw-r--r--src/html_charrefs.h2138
-rw-r--r--src/klist.c2
-rw-r--r--src/menu.cc35
-rw-r--r--src/nav.c1
-rw-r--r--src/paths.hh1
-rw-r--r--src/png.c2
-rw-r--r--src/prefs.c4
-rw-r--r--src/prefs.h4
-rw-r--r--src/prefsparser.cc5
-rw-r--r--src/styleengine.cc46
-rw-r--r--src/styleengine.hh1
-rw-r--r--src/table.cc5
-rw-r--r--src/tipwin.cc2
-rw-r--r--src/uicmd.cc2
-rw-r--r--src/url.c84
-rw-r--r--src/url.h21
-rw-r--r--src/web.cc2
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);
diff --git a/src/capi.c b/src/capi.c
index 85aef974..8c4a1ae2 100644
--- a/src/capi.c
+++ b/src/capi.c
@@ -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
diff --git a/src/css.cc b/src/css.cc
index 5bdf4fdb..5c64c619 100644
--- a/src/css.cc
+++ b/src/css.cc
@@ -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);
}
}
diff --git a/src/css.hh b/src/css.hh
index 22e7e700..c2a28770 100644
--- a/src/css.hh
+++ b/src/css.hh
@@ -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;
diff --git a/src/gif.c b/src/gif.c
index 34424d33..adc9f49d 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -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., "&#47;" or "&#x2F;").
+ * 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 "&copy=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., "&amp;" or "&hellip;").
+ * 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 "&copy=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");
}
/*
diff --git a/src/nav.c b/src/nav.c
index 4ccb28be..3aac475a 100644
--- a/src/nav.c
+++ b/src/nav.c
@@ -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:
diff --git a/src/png.c b/src/png.c
index 093e2600..652e861e 100644
--- a/src/png.c
+++ b/src/png.c
@@ -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);
diff --git a/src/url.c b/src/url.c
index 4eacb7a4..5ffe58fd 100644
--- a/src/url.c
+++ b/src/url.c
@@ -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;
diff --git a/src/url.h b/src/url.h
index bb20d789..93d198f8 100644
--- a/src/url.h
+++ b/src/url.h
@@ -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
}
diff --git a/src/web.cc b/src/web.cc
index b835610c..a175ddb9 100644
--- a/src/web.cc
+++ b/src/web.cc
@@ -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. */