summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/IO/IO.c21
-rw-r--r--src/IO/Makefile.am7
-rw-r--r--src/IO/Url.h5
-rw-r--r--src/IO/about.c4
-rw-r--r--src/IO/http.c439
-rw-r--r--src/IO/ssl.c1105
-rw-r--r--src/IO/ssl.h47
-rw-r--r--src/Makefile.am2
-rw-r--r--src/cache.c108
-rw-r--r--src/capi.c80
-rw-r--r--src/colors.c7
-rw-r--r--src/dillo.cc3
-rw-r--r--src/html.cc31
-rw-r--r--src/keys.cc1
-rw-r--r--src/keysrc2
-rw-r--r--src/prefsparser.cc202
-rw-r--r--src/prefsparser.hh1
-rw-r--r--src/styleengine.cc8
-rw-r--r--src/url.c10
-rw-r--r--src/url.h4
20 files changed, 1719 insertions, 368 deletions
diff --git a/src/IO/IO.c b/src/IO/IO.c
index 0addf486..e5c5fc79 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 "ssl.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_Ssl_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_Ssl_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_Ssl_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_Ssl_write(conn, io->Buf->str, io->Buf->len)
+ : write(io->FD, io->Buf->str, io->Buf->len);
if (St < 0) {
/* Error */
if (errno == EINTR) {
@@ -372,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);
@@ -381,7 +386,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 1B\n");
break;
}
} else { /* 1 FWD */
@@ -395,7 +400,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 1F\n");
break;
}
}
@@ -424,7 +429,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 2B\n");
break;
}
} else { /* 2 FWD */
@@ -443,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..ff600521 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 \
+ ssl.h \
+ ssl.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 0cc3b427..1fe6485b 100644
--- a/src/IO/about.c
+++ b/src/IO/about.c
@@ -236,6 +236,8 @@ const char *const AboutSplash=
" <tr>\n"
" <td>\n"
"<ul>\n"
+" <li> Read the <a href='http://www.dillo.org/dillo3-help.html'>help</a>,\n"
+" it's short.\n"
" <li> There's a\n"
" <a href='http://www.dillo.org/dillorc'>dillorc</a>\n"
" (readable config) file inside the tarball. It is well-commented\n"
@@ -248,6 +250,8 @@ const char *const AboutSplash=
" <li> Cookies are disabled by default for privacy. To log into certain\n"
" sites, you may need to <a href='http://www.dillo.org/Cookies.txt'>enable\n"
" cookies selectively</a>.\n"
+" <li> To stop third-party ads and tracking, you can use a\n"
+" ~/.dillo/<a href='http://www.dillo.org/domainrc'>domainrc</a>/ file.\n"
" <li> Frames, Java and Javascript are not supported.\n"
" <li> This release is mainly intended for <strong>developers</strong>\n"
" and <strong>advanced users</strong>.\n"
diff --git a/src/IO/http.c b/src/IO/http.c
index 49b3a3ac..5f97c0fd 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 "ssl.h"
#include "Url.h"
#include "../msg.h"
#include "../klist.h"
@@ -48,22 +49,22 @@ D_STMT_START { \
#define _MSG_BW(web, root, ...)
-static const int HTTP_PORT = 80;
-
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_SSL = 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 connect_port;
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 */
+ Dstr *https_proxy_reply;
} SocketData_t;
/* Data structures and functions to queue sockets that need to be
@@ -81,11 +82,11 @@ typedef struct {
} FdMapEntry_t;
static void Http_socket_enqueue(HostConnection_t *hc, SocketData_t* sock);
-static SocketData_t* Http_socket_dequeue(HostConnection_t *hc);
static HostConnection_t *Http_host_connection_get(const char *host);
static void Http_host_connection_remove(HostConnection_t *hc);
-static int Http_connect_socket(ChainLink *Info);
-static void Http_send_query(ChainLink *Info, SocketData_t *S);
+static void Http_connect_socket(ChainLink *Info, HostConnection_t *hc);
+static char *Http_get_connect_str(const DilloUrl *url);
+static void Http_send_query(SocketData_t *S);
static void Http_socket_free(int SKey);
/*
@@ -170,7 +171,21 @@ 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) ? 0 : 1;
+ 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);
}
/*
@@ -183,42 +198,78 @@ static void Http_fd_map_remove_entry(int fd)
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;
+
+ if (success) {
+ a_Chain_bfcb(OpSend, info, &sd->SockFD, "FD");
+ Http_send_query(sd);
+ } else {
+ 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(HostConnection_t *hc, SocketData_t *sd)
+{
+ dList_remove(hc->queue, sd);
+ sd->flags &= ~HTTP_SOCKET_QUEUED;
+ hc->active_conns++;
+ sd->connected_to = hc->host;
+}
+
static void Http_connect_queued_sockets(HostConnection_t *hc)
{
SocketData_t *sd;
- while (hc->active_conns < prefs.http_max_conns &&
- (sd = Http_socket_dequeue(hc))) {
+ int i;
- sd->flags &= ~HTTP_SOCKET_QUEUED;
+ for (i = 0;
+ i < dList_length(hc->queue) && hc->active_conns < prefs.http_max_conns;
+ i++) {
+ sd = dList_nth_data(hc->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 {
- FdMapEntry_t *e = dNew0(FdMapEntry_t, 1);
+ if (!(sd->flags & HTTP_SOCKET_TO_BE_FREED)) {
+ int connect_ready = SSL_CONNECT_READY;
+
+ if (sd->flags & HTTP_SOCKET_SSL)
+ connect_ready = a_Ssl_connect_ready(sd->url);
- e->fd = sd->SockFD;
- e->skey = VOIDP2INT(sd->Info->LocalKey);
- dList_append(fd_map, e);
+ if (connect_ready == SSL_CONNECT_NEVER || !a_Web_valid(sd->web)) {
+ int SKey = VOIDP2INT(sd->Info->LocalKey);
- hc->active_conns++;
- a_Chain_bcb(OpSend, sd->Info, &sd->SockFD, "FD");
- a_Chain_fcb(OpSend, sd->Info, &sd->SockFD, "FD");
- Http_send_query(sd->Info, sd);
- sd->connected_to = hc->host;
+ Http_socket_free(SKey);
+ } else if (connect_ready == SSL_CONNECT_READY) {
+ i--;
+ Http_socket_activate(hc, sd);
+ Http_connect_socket(sd->Info, hc);
}
}
+ if (sd->flags & HTTP_SOCKET_TO_BE_FREED) {
+ dList_remove(hc->queue, sd);
+ dFree(sd);
+ i--;
+ }
}
+
+ _MSG("Queue %s len %d\n", hc->host, dList_length(hc->queue));
}
/*
@@ -231,18 +282,25 @@ 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_Ssl_reset_server_state(S->url);
if (S->connected_to) {
+ a_Ssl_close_by_fd(S->SockFD);
+
HostConnection_t *hc = Http_host_connection_get(S->connected_to);
hc->active_conns--;
Http_connect_queued_sockets(hc);
if (hc->active_conns == 0)
Http_host_connection_remove(hc);
}
+ a_Url_free(S->url);
dFree(S);
}
}
@@ -300,23 +358,22 @@ 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 &&
- !dStrAsciiCasecmp(URL_SCHEME(url), "http")) ? "keep-alive" : "close";
+ (prefs.http_persistent_conns == TRUE) ? "keep-alive" : "close";
if (use_proxy) {
dStr_sprintfa(request_uri, "%s%s",
@@ -335,7 +392,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) {
@@ -400,14 +457,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.
@@ -415,26 +471,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 SSL handshake.
+ */
+static void Http_connect_ssl(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_Ssl_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, HostConnection_t *hc)
{
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));
@@ -442,10 +521,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));
@@ -459,10 +539,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(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:%d\n", inet_ntoa(sin->sin_addr),
+ S->connect_port);
break;
}
#ifdef ENABLE_IPV6
@@ -472,36 +553,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(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:%d\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_SSL) {
+ Http_connect_ssl(Info);
} else {
- 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;
@@ -509,14 +588,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;
@@ -525,21 +603,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.
*/
-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);
@@ -571,14 +649,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.
@@ -586,34 +656,32 @@ 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;
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);
+ 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;
+ hc = Http_host_connection_get(host);
+ Http_socket_enqueue(hc, 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", host);
+ }
+ }
+ if (clean_up) {
Http_socket_free(SKey);
-
- } 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, 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);
}
}
}
@@ -629,6 +697,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 */
@@ -637,18 +706,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_SSL;
/* 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 */
@@ -659,43 +730,60 @@ 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 HostConnection_t.
+ * This is not built to accept arbitrary sockets.
+ */
+static bool_t Http_socket_reuse_compatible(SocketData_t *old,
+ SocketData_t *new)
+{
+ if (a_Web_valid(new->web) &&
+ old->connect_port == new->connect_port &&
+ ((old->flags & HTTP_SOCKET_SSL) == (new->flags & HTTP_SOCKET_SSL)) &&
+ ((old->flags & HTTP_SOCKET_SSL) == 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);
- HostConnection_t *hc = Http_host_connection_get(old_sd->connected_to);
- int i, n = dList_length(hc->queue);
- for (i = 0; i < n; i++) {
- new_sd = dList_nth_data(hc->queue, i);
-
- if (a_Web_valid(new_sd->web) && old_sd->port == new_sd->port) {
- new_sd->SockFD = old_sd->SockFD;
- Http_fd_map_remove_entry(old_sd->SockFD);
- a_Klist_remove(ValidSocks, SKey);
- dFree(old_sd);
-
- dList_remove(hc->queue, new_sd);
- new_sd->flags &= ~HTTP_SOCKET_QUEUED;
- FdMapEntry_t *e = dNew0(FdMapEntry_t, 1);
- e->fd = new_sd->SockFD;
- e->skey = VOIDP2INT(new_sd->Info->LocalKey);
- dList_append(fd_map, e);
-
- a_Chain_bcb(OpSend, new_sd->Info, &new_sd->SockFD, "FD");
- a_Chain_fcb(OpSend, new_sd->Info, &new_sd->SockFD, "FD");
- Http_send_query(new_sd->Info, new_sd);
- new_sd->connected_to = hc->host;
- return;
+ if (old_sd) {
+ HostConnection_t *hc = Http_host_connection_get(old_sd->connected_to);
+ int i, n = dList_length(hc->queue);
+
+ for (i = 0; i < n; i++) {
+ new_sd = dList_nth_data(hc->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;
+ hc->active_conns--;
+ Http_socket_free(SKey);
+
+ MSG("Reusing fd %d for %s\n", new_sd->SockFD,URL_STR(new_sd->url));
+ Http_socket_activate(hc, 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);
}
- dClose(old_sd->SockFD);
- Http_fd_map_remove_entry(old_sd->SockFD);
- a_Klist_remove(ValidSocks, SKey);
- hc->active_conns--;
- dFree(old_sd);
}
/*
@@ -705,8 +793,8 @@ 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) );
@@ -730,43 +818,81 @@ void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info,
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 */
- SocketData_t *sd;
/* 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->web->url));
- a_Chain_fcb(OpAbort, Info, NULL, "Both");
+ 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:
- /* Data1 = dbuf */
- a_Chain_fcb(OpSend, Info, Data1, "send_page_2eof");
+ 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_Ssl_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:
- a_Chain_fcb(OpEnd, Info, NULL, NULL);
- Http_socket_free(SKey);
+ 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\n");
+ MSG_WARN("Unused CCC 2F Op %d\n", Op);
break;
}
} else { /* 2 BCK */
@@ -791,12 +917,13 @@ void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info,
}
break;
case OpAbort:
+ Http_socket_free(SKey);
a_Chain_bcb(OpAbort, Info, NULL, NULL);
dFree(Info);
break;
- default:
- MSG_WARN("Unused CCC\n");
- break;
+ default:
+ MSG_WARN("Unused CCC 2B Op %d\n", Op);
+ break;
}
}
}
@@ -808,6 +935,8 @@ void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info,
*/
static void Http_socket_enqueue(HostConnection_t *hc, SocketData_t* sock)
{
+ sock->flags |= HTTP_SOCKET_QUEUED;
+
if ((sock->web->flags & WEB_Image) == 0) {
int i, n = dList_length(hc->queue);
@@ -823,14 +952,6 @@ static void Http_socket_enqueue(HostConnection_t *hc, SocketData_t* sock)
dList_append(hc->queue, sock);
}
-static SocketData_t* Http_socket_dequeue(HostConnection_t *hc)
-{
- SocketData_t *sd = dList_nth_data(hc->queue, 0);
-
- dList_remove(hc->queue, sd);
- return sd;
-}
-
static HostConnection_t *Http_host_connection_get(const char *host)
{
int i;
@@ -867,8 +988,10 @@ static void Http_host_connection_remove_all()
while (dList_length(host_connections) > 0) {
hc = (HostConnection_t*) dList_nth_data(host_connections, 0);
- while ((sd = Http_socket_dequeue(hc)))
+ while ((sd = dList_nth_data(hc->queue, 0))) {
+ dList_remove(hc->queue, sd);
dFree(sd);
+ }
Http_host_connection_remove(hc);
}
dList_free(host_connections);
diff --git a/src/IO/ssl.c b/src/IO/ssl.c
new file mode 100644
index 00000000..3c21960c
--- /dev/null
+++ b/src/IO/ssl.c
@@ -0,0 +1,1105 @@
+/*
+ * File: ssl.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
+ * project's "OpenSSL" library, and distribute the linked executables, without
+ * including the source code for OpenSSL 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".
+ */
+
+/* https://www.ssllabs.com/ssltest/viewMyClient.html */
+
+/*
+ * 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_Ssl_init()
+{
+ MSG("SSL: 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 "ssl.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_GOOD 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 SSL connection information
+ */
+typedef struct {
+ int fd;
+ DilloUrl *url;
+ SSL *ssl;
+ bool_t connecting;
+} Conn_t;
+
+/* List of active SSL connections */
+static Klist_t *conn_list = NULL;
+
+/*
+ * If ssl_context is still NULL, this corresponds to SSL being disabled.
+ */
+static SSL_CTX *ssl_context;
+static Dlist *servers;
+static Dlist *fd_map;
+
+static void Ssl_connect_cb(int fd, void *vssl);
+
+/*
+ * Compare by FD.
+ */
+static int Ssl_fd_map_cmp(const void *v1, const void *v2)
+{
+ int fd = VOIDP2INT(v2);
+ const FdMapEntry_t *e = v1;
+
+ return (fd != e->fd);
+}
+
+static void Ssl_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), Ssl_fd_map_cmp)) {
+ MSG_ERR("SSL 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 Ssl_fd_map_remove_entry(int fd)
+{
+ void *data = dList_find_custom(fd_map, INT2VOIDP(fd), Ssl_fd_map_cmp);
+
+//MSG("REMOVE ENTRY %d\n", fd);
+ if (data) {
+ dList_remove_fast(fd_map, data);
+ dFree(data);
+ } else {
+ MSG("SSL FD ENTRY NOT FOUND FOR %d\n", fd);
+ }
+}
+
+/*
+ * Return SSL connection information for a given file
+ * descriptor, or NULL if no SSL connection was found.
+ */
+void *a_Ssl_connection(int fd)
+{
+ Conn_t *conn;
+
+ if (fd_map) {
+ FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd),
+ Ssl_fd_map_cmp);
+
+ if (fme && (conn = a_Klist_get_data(conn_list, fme->connkey)))
+ return conn;
+ }
+ return NULL;
+}
+
+/*
+ * Add a new SSL connection information node.
+ */
+static int Ssl_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);
+
+ Ssl_fd_map_add_entry(fd, key);
+
+ return key;
+}
+
+/*
+ * Let's monitor for ssl alerts.
+ */
+static void Ssl_info_cb(const SSL *ssl, int where, int ret)
+{
+ if (where & SSL_CB_ALERT) {
+ MSG("SSL ALERT on %s: %s\n", (where & SSL_CB_READ) ? "read" : "write",
+ SSL_alert_desc_string_long(ret));
+ }
+}
+
+/*
+ * 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 Ssl_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 *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 *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_Ssl_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, Ssl_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);
+
+ Ssl_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 Ssl_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;
+}
+
+/*
+ * Test whether a URL corresponds to a server.
+ */
+static int Ssl_servers_cmp(const void *v1, const void *v2)
+{
+ Server_t *s = (Server_t *)v1;
+ const DilloUrl *url = (const DilloUrl *)v2;
+ const char *host = URL_HOST(url);
+ int port = URL_PORT(url);
+
+ return (dStrAsciiCasecmp(s->hostname, host) || (port != s->port));
+}
+
+/*
+ * 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: 1 means yes, 0 means not yet, -1 means never.
+ * TODO: Something clearer or different.
+ */
+int a_Ssl_connect_ready(const DilloUrl *url)
+{
+ Server_t *s;
+ int i, len;
+ const char *host = URL_HOST(url);
+ const int port = URL_PORT(url);
+ int ret = SSL_CONNECT_READY;
+
+ dReturn_val_if_fail(ssl_context, SSL_CONNECT_NEVER);
+
+ len = dList_length(servers);
+
+ for (i = 0; i < len; i++) {
+ s = dList_nth_data(servers, i);
+
+ if (!dStrAsciiCasecmp(s->hostname, host) && (port == s->port)) {
+ if (s->cert_status == CERT_STATUS_RECEIVING)
+ ret = SSL_CONNECT_NOT_YET;
+ else if (s->cert_status == CERT_STATUS_BAD)
+ ret = SSL_CONNECT_NEVER;
+
+ if (s->cert_status == CERT_STATUS_NONE)
+ s->cert_status = CERT_STATUS_RECEIVING;
+ return ret;
+ }
+ }
+ s = dNew(Server_t, 1);
+
+ s->port = port;
+ s->hostname = dStrdup(host);
+ s->cert_status = CERT_STATUS_RECEIVING;
+ dList_append(servers, s);
+ return ret;
+}
+
+/*
+ * Did we find problems with the certificate, and did the user proceed to
+ * reject the connection?
+ */
+static int Ssl_user_said_no(const DilloUrl *url)
+{
+ Server_t *s = dList_find_custom(servers, url, Ssl_servers_cmp);
+
+ if (!s)
+ return FALSE;
+
+ return s->cert_status == CERT_STATUS_BAD;
+}
+
+/*
+ * Did we find problems with the certificate, and did the user proceed to
+ * accept the connection anyway?
+ */
+static int Ssl_user_said_yes(const DilloUrl *url)
+{
+ Server_t *s = dList_find_custom(servers, url, Ssl_servers_cmp);
+
+ if (!s)
+ return FALSE;
+
+ return s->cert_status == CERT_STATUS_USER_ACCEPTED;
+}
+
+/******************** 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';
+}
+
+static bool_t Ssl_check_cert_hostname(X509 *cert, const DilloUrl *url, int *choice)
+{
+ dReturn_val_if_fail(cert && url, -1);
+
+ char *msg;
+ const char *host = URL_HOST(url);
+ 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 */
+
+ /* 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;
+ }
+ }
+ 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;
+ }
+ 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;
+ msg = dStrconcat("no certificate subject alternative name matches"
+ " requested host name \n", host, NULL);
+ *choice = a_Dialog_choice("Dillo SSL",
+ msg, "Continue", "Cancel", NULL);
+ dFree(msg);
+
+ switch (*choice){
+ case 1:
+ success = TRUE;
+ break;
+ case 2:
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ 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 SSL",
+ 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 SSL",
+ 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 */
+
+/*
+ * 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 Ssl_examine_certificate(SSL *ssl, const DilloUrl *url)
+{
+ X509 *remote_cert;
+ long st;
+ char buf[4096], *cn, *msg;
+ int choice = -1, ret = -1;
+ char *title = dStrconcat("Dillo SSL: ", URL_HOST(url), NULL);
+ Server_t *srv = dList_find_custom(servers, url, Ssl_servers_cmp);
+
+ 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.\n"
+ "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 (Ssl_check_cert_hostname(remote_cert, url, &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.\nFor 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 */
+ Ssl_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\n"
+ "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\n"
+ "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\n"
+ "Certificates usually have a range of dates over which\n"
+ "they are to be considered valid, and the certificate\n"
+ "presented has a starting validity after today's date\n"
+ "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:
+ choice = a_Dialog_choice(title,
+ "The remote certificate has expired. The certificate\n"
+ "wasn't designed to last this long. You should avoid \n"
+ "this site.",
+ "Continue", "Cancel", NULL);
+ if (choice == 1) {
+ ret = 0;
+ }
+ 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.\n"
+ "Some of the certificate data was improperly formatted\n"
+ "making it impossible to determine if the certificate\n"
+ "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\n"
+ "incorrectly (possibly due to configuration problems\n"
+ "with the remote system. The connection should not\n"
+ "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\n"
+ "does not match other information presented\n"
+ "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:
+ choice = a_Dialog_choice(title,
+ "Self signed certificate in certificate chain. The certificate "
+ "chain could be built up using the untrusted certificates but the "
+ "root could not be found locally.",
+ "Continue", "Cancel", NULL);
+ if (choice == 1) {
+ ret = 0;
+ }
+ break;
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+ choice = a_Dialog_choice(title,
+ "Unable to get local issuer certificate. The issuer certificate "
+ "of an untrusted certificate cannot be found.",
+ "Continue", "Cancel", NULL);
+ if (choice == 1) {
+ ret = 0;
+ }
+ 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_GOOD;
+ 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_Ssl_reset_server_state(const DilloUrl *url)
+{
+ if (servers) {
+ Server_t *s = dList_find_custom(servers, url, Ssl_servers_cmp);
+
+ if (s && s->cert_status == CERT_STATUS_RECEIVING)
+ s->cert_status = CERT_STATUS_NONE;
+ }
+}
+
+/*
+ * Close an open SSL connection.
+ */
+static void Ssl_close_by_key(int connkey)
+{
+ Conn_t *c;
+
+ if ((c = a_Klist_get_data(conn_list, connkey))) {
+ a_Ssl_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);
+ Ssl_fd_map_remove_entry(c->fd);
+ a_Klist_remove(conn_list, connkey);
+ dFree(c);
+ }
+}
+
+/*
+ * Connect, set a callback if it's still not completed. If completed, check
+ * the certificate and report back to http.
+ */
+static void Ssl_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("Ssl_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 ssl -- want %s\n", fd,
+ err1_ret == SSL_ERROR_WANT_READ ? "read" : "write");
+ a_IOwatch_remove_fd(fd, -1);
+ a_IOwatch_add_fd(fd, want, Ssl_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("SSL 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("SSL 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 {
+ if (Ssl_user_said_yes(conn->url) ||
+ (Ssl_examine_certificate(conn->ssl, 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) {
+ Ssl_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 Ssl_connect_cb(int fd, void *vconnkey)
+{
+ Ssl_connect(fd, VOIDP2INT(vconnkey));
+}
+
+/*
+ * Perform the SSL handshake on an open socket.
+ */
+void a_Ssl_handshake(int fd, const DilloUrl *url)
+{
+ SSL *ssl;
+ bool_t success = TRUE;
+ int connkey = -1;
+
+ if (!ssl_context)
+ success = FALSE;
+
+ if (success && Ssl_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 SSL 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 = Ssl_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_Ssl_reset_server_state(url);
+ a_Http_connect_done(fd, success);
+ } else {
+ Ssl_connect(fd, connkey);
+ }
+}
+
+/*
+ * Read data from an open SSL connection.
+ */
+int a_Ssl_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 SSL connection.
+ */
+int a_Ssl_write(void *conn, void *buf, size_t len)
+{
+ Conn_t *c = (Conn_t*)conn;
+ return SSL_write(c->ssl, buf, len);
+}
+
+void a_Ssl_close_by_fd(int fd)
+{
+ FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd),
+ Ssl_fd_map_cmp);
+
+ if (fme) {
+ Ssl_close_by_key(fme->connkey);
+ }
+}
+
+static void Ssl_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 Ssl_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_Ssl_freeall(void)
+{
+ if (ssl_context)
+ SSL_CTX_free(ssl_context);
+ Ssl_fd_map_remove_all();
+ Ssl_servers_freeall();
+}
+
+#endif /* ENABLE_SSL */
diff --git a/src/IO/ssl.h b/src/IO/ssl.h
new file mode 100644
index 00000000..f55479b2
--- /dev/null
+++ b/src/IO/ssl.h
@@ -0,0 +1,47 @@
+#ifndef __SSL_H__
+#define __SSL_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "../url.h"
+
+#define SSL_CONNECT_NEVER -1
+#define SSL_CONNECT_NOT_YET 0
+#define SSL_CONNECT_READY 1
+
+void a_Ssl_init();
+
+
+#ifdef ENABLE_SSL
+int a_Ssl_connect_ready(const DilloUrl *url);
+void a_Ssl_reset_server_state(const DilloUrl *url);
+
+/* Use to initiate a SSL connection. */
+void a_Ssl_handshake(int fd, const DilloUrl *url);
+
+void *a_Ssl_connection(int fd);
+
+void a_Ssl_freeall();
+
+void a_Ssl_close_by_fd(int fd);
+int a_Ssl_read(void *conn, void *buf, size_t len);
+int a_Ssl_write(void *conn, void *buf, size_t len);
+#else
+
+#define a_Ssl_connect_ready(url) SSL_CONNECT_NEVER
+#define a_Ssl_reset_server_state(url) ;
+#define a_Ssl_handshake(fd, url) ;
+#define a_Ssl_connection(fd) NULL
+#define a_Ssl_freeall() ;
+#define a_Ssl_close_by_fd(fd) ;
+#define a_Ssl_read(conn, buf, len) 0
+#define a_Ssl_write(conn, buf, len) 0
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __SSL_H__ */
+
diff --git a/src/Makefile.am b/src/Makefile.am
index 597a743b..57a68148 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -21,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 \
diff --git a/src/cache.c b/src/cache.c
index 189e18d5..2cc8c0aa 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -675,30 +675,23 @@ static void Cache_parse_header(CacheEntry_t *entry)
if (header[9] == '3' && header[10] == '0' &&
(location_str = Cache_parse_field(header, "Location"))) {
/* 30x: URL redirection */
- DilloUrl *location_url = a_Url_new(location_str,URL_STR_(entry->Url));
-
- if (!a_Domain_permit(entry->Url, location_url)) {
- /* don't redirect; just show body like usual (if any) */
+ entry->Location = a_Url_new(location_str, URL_STR_(entry->Url));
+
+ if (!a_Domain_permit(entry->Url, entry->Location) ||
+ (URL_FLAGS(entry->Location) & (URL_Post + URL_Get) &&
+ dStrAsciiCasecmp(URL_SCHEME(entry->Location), "dpi") == 0 &&
+ dStrAsciiCasecmp(URL_SCHEME(entry->Url), "dpi") != 0)) {
+ /* Domain test, and forbid dpi GET and POST from non dpi-generated
+ * urls.
+ */
MSG("Redirection not followed from %s to %s\n",
- URL_HOST(entry->Url), URL_STR(location_url));
- a_Url_free(location_url);
+ URL_HOST(entry->Url), URL_STR(entry->Location));
} else {
entry->Flags |= CA_Redirect;
if (header[11] == '1')
entry->Flags |= CA_ForceRedirect; /* 301 Moved Permanently */
else if (header[11] == '2')
entry->Flags |= CA_TempRedirect; /* 302 Temporary Redirect */
-
- if (URL_FLAGS(location_url) & (URL_Post + URL_Get) &&
- dStrAsciiCasecmp(URL_SCHEME(location_url), "dpi") == 0 &&
- dStrAsciiCasecmp(URL_SCHEME(entry->Url), "dpi") != 0) {
- /* Forbid dpi GET and POST from non dpi-generated urls */
- MSG("Redirection Denied! '%s' -> '%s'\n",
- URL_STR(entry->Url), URL_STR(location_url));
- a_Url_free(location_url);
- } else {
- entry->Location = location_url;
- }
}
dFree(location_str);
} else if (strncmp(header + 9, "401", 3) == 0) {
@@ -1142,6 +1135,27 @@ static void Cache_savelink_cb(void *vdata)
}
/*
+ * Let the client know that we're not following a redirection.
+ */
+static void Cache_provide_redirection_blocked_page(CacheEntry_t *entry,
+ CacheClient_t *client)
+{
+ DilloWeb *clientWeb = client->Web;
+
+ a_Web_dispatch_by_type("text/html", clientWeb, &client->Callback,
+ &client->CbData);
+ client->Buf = dStrconcat("<!doctype html><html><body>"
+ "Dillo blocked a redirection attempt from <a href=\"",
+ URL_STR(entry->Url), "\">", URL_STR(entry->Url),
+ "</a> to <a href=\"", URL_STR(entry->Location), "\">",
+ URL_STR(entry->Location), "</a> based on your domainrc "
+ "settings.</body></html>", NULL);
+ client->BufSize = strlen(client->Buf);
+ (client->Callback)(CA_Send, client);
+ dFree(client->Buf);
+}
+
+/*
* Update cache clients for a single cache-entry
* Tasks:
* - Set the client function (if not already set)
@@ -1226,33 +1240,40 @@ static CacheEntry_t *Cache_process_queue(CacheEntry_t *entry)
/* Set the client function */
if (!Client->Callback) {
Client->Callback = Cache_null_client;
- if (TypeMismatch) {
- AbortEntry = TRUE;
+
+ if (entry->Location && !(entry->Flags & CA_Redirect)) {
+ /* Not following redirection, so don't display page body. */
} else {
- const char *curr_type = Cache_current_content_type(entry);
- st = a_Web_dispatch_by_type(curr_type, ClientWeb,
- &Client->Callback, &Client->CbData);
- if (st == -1) {
- /* MIME type is not viewable */
- if (ClientWeb->flags & WEB_RootUrl) {
- MSG("Content-Type '%s' not viewable.\n", curr_type);
- /* prepare a download offer... */
- AbortEntry = OfferDownload = TRUE;
- } else {
- /* TODO: Resource Type not handled.
- * Not aborted to avoid multiple connections on the same
- * resource. A better idea is to abort the connection and
- * to keep a failed-resource flag in the cache entry. */
+ if (TypeMismatch) {
+ AbortEntry = TRUE;
+ } else {
+ const char *curr_type = Cache_current_content_type(entry);
+ st = a_Web_dispatch_by_type(curr_type, ClientWeb,
+ &Client->Callback,
+ &Client->CbData);
+ if (st == -1) {
+ /* MIME type is not viewable */
+ if (ClientWeb->flags & WEB_RootUrl) {
+ MSG("Content-Type '%s' not viewable.\n", curr_type);
+ /* prepare a download offer... */
+ AbortEntry = OfferDownload = TRUE;
+ } else {
+ /* TODO: Resource Type not handled.
+ * Not aborted to avoid multiple connections on the
+ * same resource. A better idea is to abort the
+ * connection and to keep a failed-resource flag in
+ * the cache entry. */
+ }
}
}
- }
- if (AbortEntry) {
- if (ClientWeb->flags & WEB_RootUrl)
- a_Nav_cancel_expect_if_eq(Client_bw, Client->Url);
- a_Bw_remove_client(Client_bw, Client->Key);
- Cache_client_dequeue(Client);
- --i; /* Keep the index value in the next iteration */
- continue;
+ if (AbortEntry) {
+ if (ClientWeb->flags & WEB_RootUrl)
+ a_Nav_cancel_expect_if_eq(Client_bw, Client->Url);
+ a_Bw_remove_client(Client_bw, Client->Key);
+ Cache_client_dequeue(Client);
+ --i; /* Keep the index value in the next iteration */
+ continue;
+ }
}
}
@@ -1276,6 +1297,11 @@ static CacheEntry_t *Cache_process_queue(CacheEntry_t *entry)
if (entry->Flags & CA_GotData) {
/* Copy flags to a local var */
int flags = ClientWeb->flags;
+
+ if (ClientWeb->flags & WEB_RootUrl && entry->Location &&
+ !(entry->Flags & CA_Redirect)) {
+ Cache_provide_redirection_blocked_page(entry, Client);
+ }
/* We finished sending data, let the client know */
(Client->Callback)(CA_Close, Client);
if (ClientWeb->flags & WEB_RootUrl)
diff --git a/src/capi.c b/src/capi.c
index 8013d3c9..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);
@@ -401,7 +372,8 @@ int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData)
if ((web->stream = fopen(web->filename, "w"))) {
use_cache = 1;
} else {
- MSG_WARN("Cannot open \"%s\" for writing.\n", web->filename);
+ MSG_WARN("Cannot open \"%s\" for writing: %s.\n",
+ web->filename, dStrerror(errno));
}
}
} else if (a_Cache_download_enabled(web->url)) {
@@ -442,8 +414,19 @@ int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData)
}
dFree(server);
- } else if (!dStrAsciiCasecmp(scheme, "http")) {
+ } 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);
/* create a new connection and start the CCC operations */
@@ -631,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 {
@@ -658,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 */
@@ -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;
}
}
@@ -736,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 */
@@ -786,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 237e63a1..3e194339 100644
--- a/src/colors.c
+++ b/src/colors.c
@@ -62,6 +62,7 @@ static const struct key {
{ "darkgoldenrod", 0xb8860b},
{ "darkgray", 0xa9a9a9},
{ "darkgreen", 0x006400},
+ { "darkgrey", 0xa9a9a9},
{ "darkkhaki", 0xbdb76b},
{ "darkmagenta", 0x8b008b},
{ "darkolivegreen", 0x556b2f},
@@ -72,11 +73,13 @@ static const struct key {
{ "darkseagreen", 0x8fbc8f},
{ "darkslateblue", 0x483d8b},
{ "darkslategray", 0x2f4f4f},
+ { "darkslategrey", 0x2f4f4f},
{ "darkturquoise", 0x00ced1},
{ "darkviolet", 0x9400d3},
{ "deeppink", 0xff1493},
{ "deepskyblue", 0x00bfff},
{ "dimgray", 0x696969},
+ { "dimgrey", 0x696969},
{ "dodgerblue", 0x1e90ff},
{ "firebrick", 0xb22222},
{ "floralwhite", 0xfffaf0},
@@ -93,6 +96,7 @@ static const struct key {
{ "green", 0x008000},
#ifdef EXTENDED_COLOR
{ "greenyellow", 0xadff2f},
+ { "grey", 0x808080},
{ "honeydew", 0xf0fff0},
{ "hotpink", 0xff69b4},
{ "indianred", 0xcd5c5c},
@@ -107,6 +111,7 @@ static const struct key {
{ "lightcoral", 0xf08080},
{ "lightcyan", 0xe0ffff},
{ "lightgoldenrodyellow", 0xfafad2},
+ { "lightgray", 0xd3d3d3},
{ "lightgreen", 0x90ee90},
{ "lightgrey", 0xd3d3d3},
{ "lightpink", 0xffb6c1},
@@ -114,6 +119,7 @@ static const struct key {
{ "lightseagreen", 0x20b2aa},
{ "lightskyblue", 0x87cefa},
{ "lightslategray", 0x778899},
+ { "lightslategrey", 0x778899},
{ "lightsteelblue", 0xb0c4de},
{ "lightyellow", 0xffffe0},
#endif
@@ -178,6 +184,7 @@ static const struct key {
{ "skyblue", 0x87ceeb},
{ "slateblue", 0x6a5acd},
{ "slategray", 0x708090},
+ { "slategrey", 0x708090},
{ "snow", 0xfffafa},
{ "springgreen", 0x00ff7f},
{ "steelblue", 0x4682b4},
diff --git a/src/dillo.cc b/src/dillo.cc
index 1efd3b2c..847c9d63 100644
--- a/src/dillo.cc
+++ b/src/dillo.cc
@@ -45,6 +45,7 @@
#include "dns.h"
#include "web.hh"
+#include "IO/ssl.h"
#include "IO/Url.h"
#include "IO/mime.h"
#include "capi.h"
@@ -476,6 +477,7 @@ int main(int argc, char **argv)
a_Dns_init();
a_Web_init();
a_Http_init();
+ a_Ssl_init();
a_Mime_init();
a_Capi_init();
a_Dicache_init();
@@ -597,6 +599,7 @@ int main(int argc, char **argv)
a_Cache_freeall();
a_Dicache_freeall();
a_Http_freeall();
+ a_Ssl_freeall();
a_Dns_freeall();
a_History_freeall();
a_Prefs_freeall();
diff --git a/src/html.cc b/src/html.cc
index 82e28733..3e4f27a8 100644
--- a/src/html.cc
+++ b/src/html.cc
@@ -995,6 +995,14 @@ static const char *Html_parse_entity(DilloHtml *html, const char *token,
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);
@@ -1575,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.
*/
@@ -1647,7 +1655,11 @@ static void Html_parse_doctype(DilloHtml *html, const char *tag, int tagsize)
html->DocTypeVersion = 2.0f;
}
} else if (!dStrAsciiCasecmp(ntag, "<!DOCTYPE html>") ||
- !dStrAsciiCasecmp(ntag, "<!DOCTYPE html >")) {
+ !dStrAsciiCasecmp(ntag, "<!DOCTYPE html >") ||
+ !dStrAsciiCasecmp(ntag,
+ "<!DOCTYPE html SYSTEM \"about:legacy-compat\">") ||
+ !dStrAsciiCasecmp(ntag,
+ "<!DOCTYPE html SYSTEM 'about:legacy-compat'>")) {
html->DocType = DT_HTML;
html->DocTypeVersion = 5.0f;
}
@@ -2469,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);
@@ -3384,8 +3395,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);
@@ -3494,7 +3510,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},
@@ -3772,7 +3788,8 @@ static void Html_test_section(DilloHtml *html, int new_idx, int IsCloseTag)
int tag_idx;
if (!(html->InFlags & IN_HTML) && html->DocType == DT_NONE)
- BUG_MSG("The required DOCTYPE declaration is missing.");
+ BUG_MSG("The required DOCTYPE declaration is missing. "
+ "Handling as HTML4.");
if (!(html->InFlags & IN_HTML)) {
tag = "<html>";
diff --git a/src/keys.cc b/src/keys.cc
index 27b275a3..44f6acd9 100644
--- a/src/keys.cc
+++ b/src/keys.cc
@@ -59,6 +59,7 @@ static const Mapping_t keyNames[] = {
{ "Home", FL_Home },
{ "Insert", FL_Insert },
{ "Left", FL_Left },
+ { "Menu", FL_Menu },
{ "PageDown", FL_Page_Down },
{ "PageUp", FL_Page_Up },
{ "Print", FL_Print },
diff --git a/src/keysrc b/src/keysrc
index 11913e78..64d527d6 100644
--- a/src/keysrc
+++ b/src/keysrc
@@ -9,7 +9,7 @@
# (OS X: Use "Meta" for Command)
#
# Key names recognized: "Backspace", "Delete", "Down", "End", "Esc",
-# "F1" through "F12", "Home", "Insert", "Left", "PageDown", "PageUp",
+# "F1" through "F12", "Home", "Insert", "Left", "Menu", "PageDown", "PageUp",
# "Print", "Return", "Right", "Space", "Tab", "Up".
#
# Multimedia keys: "Back", "Favorites", "Forward", "HomePage", "Mail",
diff --git a/src/prefsparser.cc b/src/prefsparser.cc
index d54d017b..d01dcac5 100644
--- a/src/prefsparser.cc
+++ b/src/prefsparser.cc
@@ -39,108 +39,21 @@ typedef struct {
const char *name;
void *pref;
PrefType_t type;
+ int count;
} SymNode_t;
/*
* Parse a name/value pair and set preferences accordingly.
*/
-int PrefsParser::parseOption(char *name, char *value)
+static int parseOption(char *name, char *value,
+ SymNode_t *symbols, int n_symbols)
{
- const SymNode_t *node;
- uint_t i;
+ SymNode_t *node;
+ int i;
int st;
- /* Symbol array, sorted alphabetically */
- const SymNode_t symbols[] = {
- { "allow_white_bg", &prefs.allow_white_bg, PREFS_BOOL },
- { "white_bg_replacement", &prefs.white_bg_replacement, PREFS_COLOR },
- { "bg_color", &prefs.bg_color, PREFS_COLOR },
- { "buffered_drawing", &prefs.buffered_drawing, PREFS_INT32 },
- { "contrast_visited_color", &prefs.contrast_visited_color, PREFS_BOOL },
- { "enterpress_forces_submit", &prefs.enterpress_forces_submit,
- PREFS_BOOL },
- { "focus_new_tab", &prefs.focus_new_tab, PREFS_BOOL },
- { "font_cursive", &prefs.font_cursive, PREFS_STRING },
- { "font_factor", &prefs.font_factor, PREFS_DOUBLE },
- { "font_fantasy", &prefs.font_fantasy, PREFS_STRING },
- { "font_max_size", &prefs.font_max_size, PREFS_INT32 },
- { "font_min_size", &prefs.font_min_size, PREFS_INT32 },
- { "font_monospace", &prefs.font_monospace, PREFS_STRING },
- { "font_sans_serif", &prefs.font_sans_serif, PREFS_STRING },
- { "font_serif", &prefs.font_serif, PREFS_STRING },
- { "fullwindow_start", &prefs.fullwindow_start, PREFS_BOOL },
- { "geometry", NULL, PREFS_GEOMETRY },
- { "home", &prefs.home, PREFS_URL },
- { "http_language", &prefs.http_language, PREFS_STRING },
- { "http_max_conns", &prefs.http_max_conns, PREFS_INT32 },
- { "http_persistent_conns", &prefs.http_persistent_conns, PREFS_BOOL },
- { "http_proxy", &prefs.http_proxy, PREFS_URL },
- { "http_proxyuser", &prefs.http_proxyuser, PREFS_STRING },
- { "http_referer", &prefs.http_referer, PREFS_STRING },
- { "http_user_agent", &prefs.http_user_agent, PREFS_STRING },
- { "limit_text_width", &prefs.limit_text_width, PREFS_BOOL },
- { "adjust_min_width", &prefs.adjust_min_width, PREFS_BOOL },
- { "adjust_table_min_width", &prefs.adjust_table_min_width, PREFS_BOOL },
- { "load_images", &prefs.load_images, PREFS_BOOL },
- { "load_background_images", &prefs.load_background_images, PREFS_BOOL },
- { "load_stylesheets", &prefs.load_stylesheets, PREFS_BOOL },
- { "middle_click_drags_page", &prefs.middle_click_drags_page,
- PREFS_BOOL },
- { "middle_click_opens_new_tab", &prefs.middle_click_opens_new_tab,
- PREFS_BOOL },
- { "right_click_closes_tab", &prefs.right_click_closes_tab, PREFS_BOOL },
- { "no_proxy", &prefs.no_proxy, PREFS_STRING },
- { "panel_size", &prefs.panel_size, PREFS_PANEL_SIZE },
- { "parse_embedded_css", &prefs.parse_embedded_css, PREFS_BOOL },
- { "save_dir", &prefs.save_dir, PREFS_STRING },
- { "search_url", &prefs.search_urls, PREFS_STRINGS },
- { "show_back", &prefs.show_back, PREFS_BOOL },
- { "show_bookmarks", &prefs.show_bookmarks, PREFS_BOOL },
- { "show_clear_url", &prefs.show_clear_url, PREFS_BOOL },
- { "show_extra_warnings", &prefs.show_extra_warnings, PREFS_BOOL },
- { "show_filemenu", &prefs.show_filemenu, PREFS_BOOL },
- { "show_forw", &prefs.show_forw, PREFS_BOOL },
- { "show_help", &prefs.show_help, PREFS_BOOL },
- { "show_home", &prefs.show_home, PREFS_BOOL },
- { "show_msg", &prefs.show_msg, PREFS_BOOL },
- { "show_progress_box", &prefs.show_progress_box, PREFS_BOOL },
- { "show_quit_dialog", &prefs.show_quit_dialog, PREFS_BOOL },
- { "show_reload", &prefs.show_reload, PREFS_BOOL },
- { "show_save", &prefs.show_save, PREFS_BOOL },
- { "show_url", &prefs.show_url, PREFS_BOOL },
- { "show_search", &prefs.show_search, PREFS_BOOL },
- { "show_stop", &prefs.show_stop, PREFS_BOOL },
- { "show_tools", &prefs.show_tools, PREFS_BOOL },
- { "show_tooltip", &prefs.show_tooltip, PREFS_BOOL },
- { "show_ui_tooltip", &prefs.show_ui_tooltip, PREFS_BOOL },
- { "small_icons", &prefs.small_icons, PREFS_BOOL },
- { "start_page", &prefs.start_page, PREFS_URL },
- { "theme", &prefs.theme, PREFS_STRING },
- { "ui_button_highlight_color", &prefs.ui_button_highlight_color,
- PREFS_COLOR },
- { "ui_fg_color", &prefs.ui_fg_color, PREFS_COLOR },
- { "ui_main_bg_color", &prefs.ui_main_bg_color, PREFS_COLOR },
- { "ui_selection_color", &prefs.ui_selection_color, PREFS_COLOR },
- { "ui_tab_active_bg_color", &prefs.ui_tab_active_bg_color, PREFS_COLOR },
- { "ui_tab_bg_color", &prefs.ui_tab_bg_color, PREFS_COLOR },
- { "ui_tab_active_fg_color", &prefs.ui_tab_active_fg_color, PREFS_COLOR },
- { "ui_tab_fg_color", &prefs.ui_tab_fg_color, PREFS_COLOR },
- { "ui_text_bg_color", &prefs.ui_text_bg_color, PREFS_COLOR },
- { "w3c_plus_heuristics", &prefs.w3c_plus_heuristics, PREFS_BOOL },
- { "penalty_hyphen", &prefs.penalty_hyphen, PREFS_FRACTION_100 },
- { "penalty_hyphen_2", &prefs.penalty_hyphen_2, PREFS_FRACTION_100 },
- { "penalty_em_dash_left", &prefs.penalty_em_dash_left,
- PREFS_FRACTION_100 },
- { "penalty_em_dash_right", &prefs.penalty_em_dash_right,
- PREFS_FRACTION_100 },
- { "penalty_em_dash_right_2", &prefs.penalty_em_dash_right_2,
- PREFS_FRACTION_100 },
- { "stretchability_factor", &prefs.stretchability_factor,
- PREFS_FRACTION_100 }
- };
-
node = NULL;
- for (i = 0; i < sizeof(symbols) / sizeof(symbols[0]); i++) {
+ for (i = 0; i < n_symbols; i++) {
if (!strcmp(symbols[i].name, name)) {
node = & (symbols[i]);
break;
@@ -169,12 +82,13 @@ int PrefsParser::parseOption(char *name, char *value)
case PREFS_STRINGS:
{
Dlist *lp = *(Dlist **)node->pref;
- if (dList_length(lp) == 2 && !dList_nth_data(lp, 1)) {
+ if (node->count == 0) {
/* override the default */
- void *data = dList_nth_data(lp, 0);
- dList_remove(lp, data);
- dList_remove(lp, NULL);
- dFree(data);
+ for (i = 0; i < dList_length(lp); i++) {
+ void *data = dList_nth_data(lp, i);
+ dList_remove(lp, data);
+ dFree(data);
+ }
}
dList_append(lp, dStrdup(value));
break;
@@ -217,6 +131,8 @@ int PrefsParser::parseOption(char *name, char *value)
MSG_WARN("prefs: {%s} IS recognized but not handled!\n", name);
break; /* Not reached */
}
+ node->count++;
+
return 0;
}
@@ -228,6 +144,94 @@ void PrefsParser::parse(FILE *fp)
char *line, *name, *value, *oldLocale;
int st;
+ /* Symbol array, sorted alphabetically */
+ SymNode_t symbols[] = {
+ { "allow_white_bg", &prefs.allow_white_bg, PREFS_BOOL, 0 },
+ { "white_bg_replacement", &prefs.white_bg_replacement, PREFS_COLOR, 0 },
+ { "bg_color", &prefs.bg_color, PREFS_COLOR, 0 },
+ { "buffered_drawing", &prefs.buffered_drawing, PREFS_INT32, 0 },
+ { "contrast_visited_color", &prefs.contrast_visited_color, PREFS_BOOL, 0 },
+ { "enterpress_forces_submit", &prefs.enterpress_forces_submit,
+ PREFS_BOOL, 0 },
+ { "focus_new_tab", &prefs.focus_new_tab, PREFS_BOOL, 0 },
+ { "font_cursive", &prefs.font_cursive, PREFS_STRING, 0 },
+ { "font_factor", &prefs.font_factor, PREFS_DOUBLE, 0 },
+ { "font_fantasy", &prefs.font_fantasy, PREFS_STRING, 0 },
+ { "font_max_size", &prefs.font_max_size, PREFS_INT32, 0 },
+ { "font_min_size", &prefs.font_min_size, PREFS_INT32, 0 },
+ { "font_monospace", &prefs.font_monospace, PREFS_STRING, 0 },
+ { "font_sans_serif", &prefs.font_sans_serif, PREFS_STRING, 0 },
+ { "font_serif", &prefs.font_serif, PREFS_STRING, 0 },
+ { "fullwindow_start", &prefs.fullwindow_start, PREFS_BOOL, 0 },
+ { "geometry", NULL, PREFS_GEOMETRY, 0 },
+ { "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_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 },
+ { "middle_click_drags_page", &prefs.middle_click_drags_page,
+ PREFS_BOOL, 0 },
+ { "middle_click_opens_new_tab", &prefs.middle_click_opens_new_tab,
+ PREFS_BOOL, 0 },
+ { "right_click_closes_tab", &prefs.right_click_closes_tab, PREFS_BOOL, 0 },
+ { "no_proxy", &prefs.no_proxy, PREFS_STRING, 0 },
+ { "panel_size", &prefs.panel_size, PREFS_PANEL_SIZE, 0 },
+ { "parse_embedded_css", &prefs.parse_embedded_css, PREFS_BOOL, 0 },
+ { "save_dir", &prefs.save_dir, PREFS_STRING, 0 },
+ { "search_url", &prefs.search_urls, PREFS_STRINGS, 0 },
+ { "show_back", &prefs.show_back, PREFS_BOOL, 0 },
+ { "show_bookmarks", &prefs.show_bookmarks, PREFS_BOOL, 0 },
+ { "show_clear_url", &prefs.show_clear_url, PREFS_BOOL, 0 },
+ { "show_extra_warnings", &prefs.show_extra_warnings, PREFS_BOOL, 0 },
+ { "show_filemenu", &prefs.show_filemenu, PREFS_BOOL, 0 },
+ { "show_forw", &prefs.show_forw, PREFS_BOOL, 0 },
+ { "show_help", &prefs.show_help, PREFS_BOOL, 0 },
+ { "show_home", &prefs.show_home, PREFS_BOOL, 0 },
+ { "show_msg", &prefs.show_msg, PREFS_BOOL, 0 },
+ { "show_progress_box", &prefs.show_progress_box, PREFS_BOOL, 0 },
+ { "show_quit_dialog", &prefs.show_quit_dialog, PREFS_BOOL, 0 },
+ { "show_reload", &prefs.show_reload, PREFS_BOOL, 0 },
+ { "show_save", &prefs.show_save, PREFS_BOOL, 0 },
+ { "show_url", &prefs.show_url, PREFS_BOOL, 0 },
+ { "show_search", &prefs.show_search, PREFS_BOOL, 0 },
+ { "show_stop", &prefs.show_stop, PREFS_BOOL, 0 },
+ { "show_tools", &prefs.show_tools, PREFS_BOOL, 0 },
+ { "show_tooltip", &prefs.show_tooltip, PREFS_BOOL, 0 },
+ { "show_ui_tooltip", &prefs.show_ui_tooltip, PREFS_BOOL, 0 },
+ { "small_icons", &prefs.small_icons, PREFS_BOOL, 0 },
+ { "start_page", &prefs.start_page, PREFS_URL, 0 },
+ { "theme", &prefs.theme, PREFS_STRING, 0 },
+ { "ui_button_highlight_color", &prefs.ui_button_highlight_color,
+ PREFS_COLOR, 0 },
+ { "ui_fg_color", &prefs.ui_fg_color, PREFS_COLOR, 0 },
+ { "ui_main_bg_color", &prefs.ui_main_bg_color, PREFS_COLOR, 0 },
+ { "ui_selection_color", &prefs.ui_selection_color, PREFS_COLOR, 0 },
+ { "ui_tab_active_bg_color", &prefs.ui_tab_active_bg_color, PREFS_COLOR, 0 },
+ { "ui_tab_bg_color", &prefs.ui_tab_bg_color, PREFS_COLOR, 0 },
+ { "ui_tab_active_fg_color", &prefs.ui_tab_active_fg_color, PREFS_COLOR, 0 },
+ { "ui_tab_fg_color", &prefs.ui_tab_fg_color, PREFS_COLOR, 0 },
+ { "ui_text_bg_color", &prefs.ui_text_bg_color, PREFS_COLOR, 0 },
+ { "w3c_plus_heuristics", &prefs.w3c_plus_heuristics, PREFS_BOOL, 0 },
+ { "penalty_hyphen", &prefs.penalty_hyphen, PREFS_FRACTION_100, 0 },
+ { "penalty_hyphen_2", &prefs.penalty_hyphen_2, PREFS_FRACTION_100, 0 },
+ { "penalty_em_dash_left", &prefs.penalty_em_dash_left,
+ PREFS_FRACTION_100, 0 },
+ { "penalty_em_dash_right", &prefs.penalty_em_dash_right,
+ PREFS_FRACTION_100, 0 },
+ { "penalty_em_dash_right_2", &prefs.penalty_em_dash_right_2,
+ PREFS_FRACTION_100, 0 },
+ { "stretchability_factor", &prefs.stretchability_factor,
+ PREFS_FRACTION_100, 0 }
+ };
// changing the LC_NUMERIC locale (temporarily) to C
// avoids parsing problems with float numbers
oldLocale = dStrdup(setlocale(LC_NUMERIC, NULL));
@@ -239,7 +243,7 @@ void PrefsParser::parse(FILE *fp)
if (st == 0) {
_MSG("prefsparser: name=%s, value=%s\n", name, value);
- parseOption(name, value);
+ parseOption(name, value, symbols, sizeof(symbols) / sizeof(symbols[0]));
} else if (st < 0) {
MSG_ERR("prefsparser: Syntax error in dillorc:"
" name=\"%s\" value=\"%s\"\n", name, value);
diff --git a/src/prefsparser.hh b/src/prefsparser.hh
index d10c43c4..91f6f7ee 100644
--- a/src/prefsparser.hh
+++ b/src/prefsparser.hh
@@ -15,7 +15,6 @@
#ifdef __cplusplus
class PrefsParser {
public:
- static int parseOption(char *name, char *value);
static int parseLine(char *line, char *name, char *value);
static void parse(FILE *fp);
};
diff --git a/src/styleengine.cc b/src/styleengine.cc
index 4192a9ef..97ca417e 100644
--- a/src/styleengine.cc
+++ b/src/styleengine.cc
@@ -430,12 +430,10 @@ void StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props,
fontAttrs.size = roundInt(24.2 * prefs.font_factor);
break;
case CSS_FONT_SIZE_SMALLER:
- fontAttrs.size = roundInt(fontAttrs.size * 0.83 *
- prefs.font_factor);
+ fontAttrs.size = roundInt(fontAttrs.size * 0.83);
break;
case CSS_FONT_SIZE_LARGER:
- fontAttrs.size = roundInt(fontAttrs.size * 1.2 *
- prefs.font_factor);
+ fontAttrs.size = roundInt(fontAttrs.size * 1.2);
break;
default:
assert(false); // invalid font-size enum
@@ -1040,7 +1038,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/url.c b/src/url.c
index c1a8396d..aa211fb7 100644
--- a/src/url.c
+++ b/src/url.c
@@ -118,6 +118,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;
}
@@ -638,7 +644,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 +730,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 ef532f76..6920f769 100644
--- a/src/url.h
+++ b/src/url.h
@@ -13,6 +13,9 @@
#include "../dlib/dlib.h"
+#define URL_HTTP_PORT 80
+#define URL_HTTPS_PORT 443
+
/*
* Values for DilloUrl->flags.
* Specifies which which action to perform with an URL.
@@ -111,6 +114,7 @@ void a_Url_set_ismap_coords(DilloUrl *u, char *coord_str);
char *a_Url_decode_hex_str(const char *str);
char *a_Url_encode_hex_str(const char *str);
char *a_Url_string_strip_delimiters(const char *str);
+bool_t a_Url_host_is_ip(const char *host);
bool_t a_Url_same_organization(const DilloUrl *u1, const DilloUrl *u2);
#ifdef __cplusplus
}