From f44c9936950ed01057ecef822c9c871173d23ece Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Mon, 11 Apr 2022 09:11:00 -0400 Subject: [PATCH 01/24] experimenting with input proxy --- minigui.d | 52 ++++++++++++++++++++++++++++++++++++++-- minigui_addons/webview.d | 14 ++++++----- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/minigui.d b/minigui.d index 73c125c..5ad7e9d 100644 --- a/minigui.d +++ b/minigui.d @@ -8082,12 +8082,59 @@ class Window : Widget { } win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.normal, WindowFlags.dontAutoShow | WindowFlags.managesChildWindowFocus); + /+ + // for input proxy + auto display = XDisplayConnection.get; + auto inputProxy = XCreateSimpleWindow(display, win.window, -1, -1, 1, 1, 0, 0, 0); + XSelectInput(display, inputProxy, EventMask.KeyPressMask | EventMask.KeyReleaseMask); + XMapWindow(display, inputProxy); + import std.stdio; writefln("input proxy: 0x%0x", inputProxy); + this.inputProxy = new SimpleWindow(inputProxy); + + XEvent lastEvent; + this.inputProxy.handleNativeEvent = (XEvent ev) { + lastEvent = ev; + return 1; + }; + this.inputProxy.setEventHandlers( + (MouseEvent e) { + dispatchMouseEvent(e); + }, + (KeyEvent e) { + //import std.stdio; + //writefln("%x %s", cast(uint) e.key, e.key); + if(dispatchKeyEvent(e)) { + // FIXME: i should trap error + if(auto nw = cast(NestedChildWindowWidget) focusedWidget) { + auto thing = nw.focusableWindow(); + if(thing && thing.window) { + import std.stdio; writeln("sending event ", lastEvent.xkey); + XSendEvent(XDisplayConnection.get, thing.window, false, 0, &lastEvent); + } + } + } + }, + (dchar e) { + if(e == 13) e = 10; // hack? + if(e == 127) return; // linux sends this, windows doesn't. we don't want it. + dispatchCharEvent(e); + }, + ); + // done + +/ + + + win.setRequestedInputFocus = &this.setRequestedInputFocus; this(win); } + SimpleWindow inputProxy; + private SimpleWindow setRequestedInputFocus() { + // return inputProxy; + if(auto fw = cast(NestedChildWindowWidget) focusedWidget) { // sdpyPrintDebugString("heaven"); return fw.focusableWindow; @@ -8126,13 +8173,14 @@ class Window : Widget { event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false; event.dispatch(); - return true; + return !event.defaultPrevented; } bool dispatchCharEvent(dchar ch) { if(focusedWidget) { auto event = new CharEvent(focusedWidget, ch); event.dispatch(); + return !event.defaultPrevented; } return true; } @@ -8249,7 +8297,7 @@ class Window : Widget { } } - return true; + return true; // FIXME: the event default prevented? } /++ diff --git a/minigui_addons/webview.d b/minigui_addons/webview.d index 71129d9..194743e 100644 --- a/minigui_addons/webview.d +++ b/minigui_addons/webview.d @@ -548,7 +548,7 @@ version(cef) { runInGuiThread({ ret = 1; - scope WebViewWidget delegate(Widget, BrowserSettings) o = (parent, passed_settings) { + scope WebViewWidget delegate(Widget, BrowserSettings) accept = (parent, passed_settings) { ret = 0; if(parent !is null) { auto widget = new WebViewWidget_CEF(this.client, parent); @@ -560,7 +560,7 @@ version(cef) { } return null; }; - this.client.openNewWindow(OpenNewWindowParams(target_url.toGC, o)); + this.client.openNewWindow(OpenNewWindowParams(target_url.toGC, accept)); return; }); @@ -836,10 +836,12 @@ version(cef) { // sdpyPrintDebugString("take"); } override int on_set_focus(RC!(cef_browser_t) browser, cef_focus_source_t source) nothrow { - //browser.runOnWebView((ev) { - //sdpyPrintDebugString("setting"); - //ev.parentWindow.focusedWidget = ev; - //}); + /+ + browser.runOnWebView((ev) { + sdpyPrintDebugString("setting"); + ev.parentWindow.focusedWidget = ev; + }); + +/ return 1; // otherwise, cancel because this bullshit tends to steal focus from other applications and i never, ever, ever want that to happen. // seems to happen because of race condition in it getting a focus event and then stealing the focus from the parent From 912047ccdda7e7775f64431a631519e6024d2494 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Mon, 11 Apr 2022 09:13:58 -0400 Subject: [PATCH 02/24] enable transparency in imagebox --- minigui.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/minigui.d b/minigui.d index 5ad7e9d..b6e5b68 100644 --- a/minigui.d +++ b/minigui.d @@ -11333,7 +11333,7 @@ class ImageBox : Widget { if(this.parentWindow && this.parentWindow.win) { if(sprite) sprite.dispose(); - sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_)); + sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_, true)); } redraw(); } @@ -11367,7 +11367,7 @@ class ImageBox : Widget { private void updateSprite() { if(sprite is null && this.parentWindow && this.parentWindow.win) { - sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_)); + sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_, true)); } } From b68c33bb094d2ad552bc56753ab3e0067fd90a2b Mon Sep 17 00:00:00 2001 From: WebFreak001 Date: Wed, 13 Apr 2022 18:18:20 +0200 Subject: [PATCH 03/24] add support for no_proxy / proxy ignore hosts List of domains, ips or ip ranges to not use a proxy with. By default uses the no_proxy or NO_PROXY environment variable. --- http2.d | 289 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 274 insertions(+), 15 deletions(-) diff --git a/http2.d b/http2.d index 4833243..a23d8c0 100644 --- a/http2.d +++ b/http2.d @@ -566,7 +566,7 @@ private class UnixAddress : Address { struct Uri { alias toString this; // blargh idk a url really is a string, but should it be implicit? - // scheme//userinfo@host:port/path?query#fragment + // scheme://userinfo@host:port/path?query#fragment string scheme; /// e.g. "http" in "http://example.com/" string userinfo; /// the username (and possibly a password) in the uri @@ -581,6 +581,12 @@ struct Uri { reparse(uri); } + /// Returns `port` if set, otherwise if scheme is https 443, otherwise always 80 + int effectivePort() const @property nothrow pure @safe @nogc { + return port != 0 ? port + : scheme == "https" ? 443 : 80; + } + private string unixSocketPath = null; /// Indicates it should be accessed through a unix socket instead of regular tcp. Returns new version without modifying this object. Uri viaUnixSocket(string path) const { @@ -991,10 +997,8 @@ class HttpRequest { requestParameters.method = method; requestParameters.unixSocketPath = where.unixSocketPath; requestParameters.host = parts.host; - requestParameters.port = cast(ushort) parts.port; + requestParameters.port = cast(ushort) parts.effectivePort; requestParameters.ssl = parts.scheme == "https"; - if(parts.port == 0) - requestParameters.port = requestParameters.ssl ? 443 : 80; requestParameters.uri = parts.path.length ? parts.path : "/"; if(parts.query.length) { requestParameters.uri ~= "?"; @@ -2403,17 +2407,7 @@ class HttpClient { or set [HttpRequest.retainCookies|request.retainCookies] to `true` on the returned object. But see important implementation shortcomings on [retainCookies]. +/ HttpRequest request(Uri uri, HttpVerb method = HttpVerb.GET, ubyte[] bodyData = null, string contentType = null) { - string proxyToUse; - switch(uri.scheme) { - case "http": - proxyToUse = httpProxy; - break; - case "https": - proxyToUse = httpsProxy; - break; - default: - proxyToUse = null; - } + string proxyToUse = getProxyFor(uri); auto request = new HttpRequest(this, uri, method, cache, defaultTimeout, proxyToUse); @@ -2466,6 +2460,8 @@ class HttpClient { The environment variables are used, if present, on all operating systems. History: + no_proxy support added April 13, 2022 + Added April 12, 2021 (included in dub v9.5) Bugs: @@ -2477,10 +2473,240 @@ class HttpClient { import std.process; httpProxy = environment.get("http_proxy", environment.get("HTTP_PROXY", null)); httpsProxy = environment.get("https_proxy", environment.get("HTTPS_PROXY", null)); + auto noProxy = environment.get("no_proxy", environment.get("NO_PROXY", null)); + if (noProxy.length) { + proxyIgnore = noProxy.split(","); + foreach (ref rule; proxyIgnore) + rule = rule.strip; + } // FIXME: on Windows, I should use the Internet Explorer proxy settings } + /++ + Checks if the given uri should be proxied according to the httpProxy, httpsProxy, proxyIgnore + variables and returns either httpProxy, httpsProxy or null. + + If neither `httpProxy` or `httpsProxy` are set this always returns `null`. Same if `proxyIgnore` + contains `*`. + + DNS is not resolved for proxyIgnore IPs, only IPs match IPs and hosts match hosts. + +/ + string getProxyFor(Uri uri) { + string proxyToUse; + switch(uri.scheme) { + case "http": + proxyToUse = httpProxy; + break; + case "https": + proxyToUse = httpsProxy; + break; + default: + proxyToUse = null; + } + + if (proxyToUse.length) { + foreach (ignore; proxyIgnore) { + if (matchProxyIgnore(ignore, uri)) { + return null; + } + } + } + + return proxyToUse; + } + + /// Returns -1 on error, otherwise the IP as uint. Parsing is very strict. + private static long tryParseIPv4(scope const(char)[] s) nothrow { + import std.algorithm : findSplit, all; + import std.ascii : isDigit; + + static int parseNum(scope const(char)[] num) nothrow { + if (num.length < 1 || num.length > 3 || !num.representation.all!isDigit) + return -1; + try { + auto ret = num.to!int; + return ret > 255 ? -1 : ret; + } catch (Exception) { + assert(false); + } + } + + if (s.length < "0.0.0.0".length || s.length > "255.255.255.255".length) + return -1; + auto firstPair = s.findSplit("."); + auto secondPair = firstPair[2].findSplit("."); + auto thirdPair = secondPair[2].findSplit("."); + auto a = parseNum(firstPair[0]); + auto b = parseNum(secondPair[0]); + auto c = parseNum(thirdPair[0]); + auto d = parseNum(thirdPair[2]); + if (a < 0 || b < 0 || c < 0 || d < 0) + return -1; + return (cast(uint)a << 24) | (b << 16) | (c << 8) | (d); + } + + unittest { + assert(tryParseIPv4("0.0.0.0") == 0); + assert(tryParseIPv4("127.0.0.1") == 0x7f000001); + assert(tryParseIPv4("162.217.114.56") == 0xa2d97238); + assert(tryParseIPv4("256.0.0.1") == -1); + assert(tryParseIPv4("0.0.0.-2") == -1); + assert(tryParseIPv4("0.0.0.a") == -1); + assert(tryParseIPv4("0.0.0") == -1); + assert(tryParseIPv4("0.0.0.0.0") == -1); + } + + /++ + Returns true if the given no_proxy rule matches the uri. + + Invalid IP ranges are silently ignored and return false. + + See $(LREF proxyIgnore). + +/ + static bool matchProxyIgnore(scope const(char)[] rule, scope const Uri uri) nothrow { + import std.algorithm; + import std.ascii : isDigit; + import std.uni : sicmp; + + string uriHost = uri.host; + if (uriHost.length && uriHost[$ - 1] == '.') + uriHost = uriHost[0 .. $ - 1]; + + if (rule == "*") + return true; + while (rule.length && rule[0] == '.') rule = rule[1 .. $]; + + static int parsePort(scope const(char)[] portStr) nothrow { + if (portStr.length < 1 || portStr.length > 5 || !portStr.representation.all!isDigit) + return -1; + try { + return portStr.to!int; + } catch (Exception) { + assert(false, "to!int should succeed"); + } + } + + if (sicmp(rule, uriHost) == 0 + || (uriHost.length > rule.length + && sicmp(rule, uriHost[$ - rule.length .. $]) == 0 + && uriHost[$ - rule.length - 1] == '.')) + return true; + + if (rule.startsWith("[")) { // IPv6 + // below code is basically nothrow lastIndexOfAny("]:") + ptrdiff_t lastColon = cast(ptrdiff_t) rule.length - 1; + while (lastColon >= 0) { + if (rule[lastColon] == ']' || rule[lastColon] == ':') + break; + lastColon--; + } + if (lastColon == -1) + return false; // malformed + + if (rule[lastColon] == ':') { // match with port + auto port = parsePort(rule[lastColon + 1 .. $]); + if (port != -1) { + if (uri.effectivePort != port.to!int) + return false; + return uriHost == rule[0 .. lastColon]; + } + } + // exact match of host already done above + } else { + auto slash = rule.lastIndexOfNothrow('/'); + if (slash == -1) { // no IP range + auto colon = rule.lastIndexOfNothrow(':'); + auto host = colon == -1 ? rule : rule[0 .. colon]; + auto port = colon != -1 ? parsePort(rule[colon + 1 .. $]) : -1; + auto ip = tryParseIPv4(host); + if (ip == -1) { // not an IPv4, test for host with port + return port != -1 + && uri.effectivePort == port + && uriHost == host; + } else { + // perform IPv4 equals + auto other = tryParseIPv4(uriHost); + if (other == -1) + return false; // rule == IPv4, uri != IPv4 + if (port != -1) + return uri.effectivePort == port + && uriHost == host; + else + return uriHost == host; + } + } else { + auto maskStr = rule[slash + 1 .. $]; + auto ip = tryParseIPv4(rule[0 .. slash]); + if (ip == -1) + return false; + if (maskStr.length && maskStr.length < 3 && maskStr.representation.all!isDigit) { + // IPv4 range match + int mask; + try { + mask = maskStr.to!int; + } catch (Exception) { + assert(false); + } + + auto other = tryParseIPv4(uriHost); + if (other == -1) + return false; // rule == IPv4, uri != IPv4 + + if (mask == 0) // matches all + return true; + if (mask > 32) // matches none + return false; + + auto shift = 32 - mask; + return cast(uint)other >> shift + == cast(uint)ip >> shift; + } + } + } + return false; + } + + unittest { + assert(matchProxyIgnore("0.0.0.0/0", Uri("http://127.0.0.1:80/a"))); + assert(matchProxyIgnore("0.0.0.0/0", Uri("http://127.0.0.1/a"))); + assert(!matchProxyIgnore("0.0.0.0/0", Uri("https://dlang.org/a"))); + assert(matchProxyIgnore("*", Uri("https://dlang.org/a"))); + assert(matchProxyIgnore("127.0.0.0/8", Uri("http://127.0.0.1:80/a"))); + assert(matchProxyIgnore("127.0.0.0/8", Uri("http://127.0.0.1/a"))); + assert(matchProxyIgnore("127.0.0.1", Uri("http://127.0.0.1:1234/a"))); + assert(!matchProxyIgnore("127.0.0.1:80", Uri("http://127.0.0.1:1234/a"))); + assert(!matchProxyIgnore("127.0.0.1/8", Uri("http://localhost/a"))); // no DNS resolution / guessing + assert(!matchProxyIgnore("0.0.0.0/1", Uri("http://localhost/a")) + && !matchProxyIgnore("128.0.0.0/1", Uri("http://localhost/a"))); // no DNS resolution / guessing 2 + foreach (m; 1 .. 32) { + assert(matchProxyIgnore(text("127.0.0.1/", m), Uri("http://127.0.0.1/a"))); + assert(!matchProxyIgnore(text("127.0.0.1/", m), Uri("http://128.0.0.1/a"))); + bool expectedMatch = m <= 24; + assert(expectedMatch == matchProxyIgnore(text("127.0.1.0/", m), Uri("http://127.0.1.128/a")), m.to!string); + } + assert(matchProxyIgnore("localhost", Uri("http://localhost/a"))); + assert(matchProxyIgnore("localhost", Uri("http://foo.localhost/a"))); + assert(matchProxyIgnore("localhost", Uri("http://foo.localhost./a"))); + assert(matchProxyIgnore(".localhost", Uri("http://localhost/a"))); + assert(matchProxyIgnore(".localhost", Uri("http://foo.localhost/a"))); + assert(matchProxyIgnore(".localhost", Uri("http://foo.localhost./a"))); + assert(!matchProxyIgnore("foo.localhost", Uri("http://localhost/a"))); + assert(matchProxyIgnore("foo.localhost", Uri("http://foo.localhost/a"))); + assert(matchProxyIgnore("foo.localhost", Uri("http://foo.localhost./a"))); + assert(!matchProxyIgnore("bar.localhost", Uri("http://localhost/a"))); + assert(!matchProxyIgnore("bar.localhost", Uri("http://foo.localhost/a"))); + assert(!matchProxyIgnore("bar.localhost", Uri("http://foo.localhost./a"))); + assert(!matchProxyIgnore("bar.localhost", Uri("http://bbar.localhost./a"))); + assert(matchProxyIgnore("[::1]", Uri("http://[::1]/a"))); + assert(!matchProxyIgnore("[::1]", Uri("http://[::2]/a"))); + assert(matchProxyIgnore("[::1]:80", Uri("http://[::1]/a"))); + assert(!matchProxyIgnore("[::1]:443", Uri("http://[::1]/a"))); + assert(!matchProxyIgnore("[::1]:80", Uri("https://[::1]/a"))); + assert(matchProxyIgnore("[::1]:443", Uri("https://[::1]/a"))); + assert(matchProxyIgnore("google.com", Uri("https://GOOGLE.COM/a"))); + } + /++ Proxies to use for requests. The [HttpClient] constructor will set these to the system values, then you can reset it to `null` if you want to override and not use the proxy after all, or you @@ -2496,6 +2722,28 @@ class HttpClient { string httpProxy; /// ditto string httpsProxy; + /++ + List of hosts or ips, optionally including a port, where not to proxy. + + Each entry may be one of the following formats: + - `127.0.0.1` (IPv4, any port) + - `127.0.0.1:1234` (IPv4, specific port) + - `127.0.0.1/8` (IPv4 range / CIDR block, any port) + - `[::1]` (IPv6, any port) + - `[::1]:1234` (IPv6, specific port) + - `*` (all hosts and ports, basically don't proxy at all anymore) + - `.domain.name`, `domain.name` (don't proxy the specified domain, + leading dots are stripped and subdomains are also not proxied) + - `.domain.name:1234`, `domain.name:1234` (same as above, with specific port) + + No DNS resolution or regex is done in this list. + + See https://about.gitlab.com/blog/2021/01/27/we-need-to-talk-no-proxy/ + + History: + Added April 13, 2022 + +/ + string[] proxyIgnore; /// See [retainCookies] for important caveats. void setCookie(string name, string value, string domain = null) { @@ -2572,6 +2820,17 @@ class HttpClient { private CookieHeader[][string] cookies; } +private ptrdiff_t lastIndexOfNothrow(T)(scope T[] arr, T value) nothrow +{ + ptrdiff_t ret = cast(ptrdiff_t)arr.length - 1; + while (ret >= 0) { + if (arr[ret] == value) + return ret; + ret--; + } + return ret; +} + interface ICache { /++ The client is about to make the given `request`. It will ALWAYS pass it to the cache object first so you can decide if you want to and can provide a response. You should probably check the appropriate headers to see if you should even attempt to look up on the cache (HttpClient does NOT do this to give maximum flexibility to the cache implementor). From ffdb0df0724227370baa9f1bd2c85caf8f24a705 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Wed, 13 Apr 2022 16:06:21 -0400 Subject: [PATCH 04/24] better openssl error messages --- http2.d | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/http2.d b/http2.d index a23d8c0..579c03f 100644 --- a/http2.d +++ b/http2.d @@ -3039,6 +3039,47 @@ version(use_openssl) { return ossllib.TLS_client_method(); } + static immutable string[] sslErrorCodes = [ + "OK (code 0)", + "Unspecified SSL/TLS error (code 1)", + "Unable to get TLS issuer certificate (code 2)", + "Unable to get TLS CRL (code 3)", + "Unable to decrypt TLS certificate signature (code 4)", + "Unable to decrypt TLS CRL signature (code 5)", + "Unable to decode TLS issuer public key (code 6)", + "TLS certificate signature failure (code 7)", + "TLS CRL signature failure (code 8)", + "TLS certificate not yet valid (code 9)", + "TLS certificate expired (code 10)", + "TLS CRL not yet valid (code 11)", + "TLS CRL expired (code 12)", + "TLS error in certificate not before field (code 13)", + "TLS error in certificate not after field (code 14)", + "TLS error in CRL last update field (code 15)", + "TLS error in CRL next update field (code 16)", + "TLS system out of memory (code 17)", + "TLS certificate is self-signed (code 18)", + "Self-signed certificate in TLS chain (code 19)", + "Unable to get TLS issuer certificate locally (code 20)", + "Unable to verify TLS leaf signature (code 21)", + "TLS certificate chain too long (code 22)", + "TLS certificate was revoked (code 23)", + "TLS CA is invalid (code 24)", + "TLS error: path length exceeded (code 25)", + "TLS error: invalid purpose (code 26)", + "TLS error: certificate untrusted (code 27)", + "TLS error: certificate rejected (code 28)", + ]; + + string getOpenSslErrorCode(int error) { + if(error == 62) + return "TLS certificate hostname mismatch"; + + if(error < 0 || error >= sslErrorCodes.length) + return "SSL/TLS error code " ~ to!string(error); + return sslErrorCodes[error]; + } + struct SSL {} struct SSL_CTX {} struct SSL_METHOD {} @@ -3509,7 +3550,7 @@ version(use_openssl) { auto err = SSL_get_verify_result(ssl); //printf("wtf\n"); //scanf("%d\n", i); - throw new Exception("ssl connect failed " ~ str ~ " // " ~ to!string(err)); + throw new Exception("Secure connect failed " ~ getOpenSslErrorCode(err)); } } From 00424e9cfc62173f9a66a12e4b6419364eaa816b Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Wed, 13 Apr 2022 16:23:37 -0400 Subject: [PATCH 05/24] tls hostname check too --- http2.d | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/http2.d b/http2.d index 579c03f..10982dc 100644 --- a/http2.d +++ b/http2.d @@ -3071,9 +3071,9 @@ version(use_openssl) { "TLS error: certificate rejected (code 28)", ]; - string getOpenSslErrorCode(int error) { + string getOpenSslErrorCode(long error) { if(error == 62) - return "TLS certificate hostname mismatch"; + return "TLS certificate host name mismatch"; if(error < 0 || error >= sslErrorCodes.length) return "SSL/TLS error code " ~ to!string(error); @@ -3122,6 +3122,8 @@ version(use_openssl) { X509_STORE* function(SSL_CTX*) SSL_CTX_get_cert_store; c_long function(const SSL* ssl) SSL_get_verify_result; + X509_VERIFY_PARAM* function(const SSL*) SSL_get0_param; + /+ SSL_CTX_load_verify_locations SSL_CTX_set_client_CA_list @@ -3140,6 +3142,7 @@ version(use_openssl) { struct X509; struct X509_STORE; struct EVP_PKEY; + struct X509_VERIFY_PARAM; import core.stdc.config; @@ -3165,6 +3168,8 @@ version(use_openssl) { X509* function(FILE *fp, X509 **x) d2i_X509_fp; X509* function(X509** a, const(ubyte*)* pp, c_long length) d2i_X509; + + int function(X509_VERIFY_PARAM* a, const char* b, size_t l) X509_VERIFY_PARAM_set1_host; } } @@ -3183,6 +3188,12 @@ version(use_openssl) { else throw new Exception("SSL_get_verify_result not loaded"); } + X509_VERIFY_PARAM* SSL_get0_param(const SSL* ssl) { + if(ossllib.SSL_get0_param) + return ossllib.SSL_get0_param(ssl); + else throw new Exception("SSL_get0_param not loaded"); + } + X509_STORE* SSL_CTX_get_cert_store(SSL_CTX* a) { if(ossllib.SSL_CTX_get_cert_store) return ossllib.SSL_CTX_get_cert_store(a); @@ -3288,7 +3299,11 @@ version(use_openssl) { return ossllib.SSL_ctrl(a, 55 /*SSL_CTRL_SET_TLSEXT_HOSTNAME*/, 0 /*TLSEXT_NAMETYPE_host_name*/, cast(void*) b); else throw new Exception("SSL_set_tlsext_host_name not loaded"); } - + int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM* a, const char* b, size_t l) { + if(eallib.X509_VERIFY_PARAM_set1_host) + return eallib.X509_VERIFY_PARAM_set1_host(a, b, l); + else throw new Exception("X509_VERIFY_PARAM_set1_host not loaded"); + } SSL_METHOD* SSLv3_client_method() { if(ossllib.SSLv3_client_method) return ossllib.SSLv3_client_method(); @@ -3480,8 +3495,11 @@ version(use_openssl) { debug SSL_CTX_keylog_cb_func(ctx, &write_to_file); ssl = SSL_new(ctx); - if(hostname.length) + if(hostname.length) { SSL_set_tlsext_host_name(ssl, toStringz(hostname)); + if(verifyPeer) + X509_VERIFY_PARAM_set1_host(SSL_get0_param(ssl), hostname.ptr, hostname.length); + } if(verifyPeer) SSL_set_verify(ssl, SSL_VERIFY_PEER, null); @@ -3550,7 +3568,7 @@ version(use_openssl) { auto err = SSL_get_verify_result(ssl); //printf("wtf\n"); //scanf("%d\n", i); - throw new Exception("Secure connect failed " ~ getOpenSslErrorCode(err)); + throw new Exception("Secure connect failed: " ~ getOpenSslErrorCode(err)); } } From 2e37fa70c3f565c6bbe04cf5735b2a8a565fd787 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Thu, 14 Apr 2022 08:41:24 -0400 Subject: [PATCH 06/24] simplify adding dynamic load functions --- http2.d | 294 +++++++++++++++++--------------------------------------- 1 file changed, 87 insertions(+), 207 deletions(-) diff --git a/http2.d b/http2.d index 10982dc..45fdf1d 100644 --- a/http2.d +++ b/http2.d @@ -3031,14 +3031,7 @@ void main() { version(use_openssl) { alias SslClientSocket = OpenSslSocket; - // macros in the original C - SSL_METHOD* SSLv23_client_method() { - if(ossllib.SSLv23_client_method) - return ossllib.SSLv23_client_method(); - else - return ossllib.TLS_client_method(); - } - + // CRL = Certificate Revocation List static immutable string[] sslErrorCodes = [ "OK (code 0)", "Unspecified SSL/TLS error (code 1)", @@ -3086,6 +3079,20 @@ version(use_openssl) { enum SSL_VERIFY_NONE = 0; enum SSL_VERIFY_PEER = 1; + // copy it into the buf[0 .. size] and return actual length you read. + // rwflag == 0 when reading, 1 when writing. + extern(C) alias pem_password_cb = int function(char* buffer, int bufferSize, int rwflag, void* userPointer); + extern(C) alias print_errors_cb = int function(const char*, size_t, void*); + extern(C) alias client_cert_cb = int function(SSL *ssl, X509 **x509, EVP_PKEY **pkey); + extern(C) alias keylog_cb = void function(SSL*, char*); + + struct X509; + struct X509_STORE; + struct EVP_PKEY; + struct X509_VERIFY_PARAM; + + import core.stdc.config; + struct ossllib { __gshared static extern(C) { /* these are only on older openssl versions { */ @@ -3129,22 +3136,10 @@ version(use_openssl) { SSL_CTX_set_client_CA_list +/ - // client cert things void function (SSL_CTX *ctx, int function(SSL *ssl, X509 **x509, EVP_PKEY **pkey)) SSL_CTX_set_client_cert_cb; } } - // copy it into the buf[0 .. size] and return actual length you read. - // rwflag == 0 when reading, 1 when writing. - extern(C) - alias pem_password_cb = int function(char* buffer, int bufferSize, int rwflag, void* userPointer); - - struct X509; - struct X509_STORE; - struct EVP_PKEY; - struct X509_VERIFY_PARAM; - - import core.stdc.config; struct eallib { __gshared static extern(C) { @@ -3173,161 +3168,51 @@ version(use_openssl) { } } - extern(C) - alias print_errors_cb = int function(const char*, size_t, void*); + struct OpenSSL { + static: - int SSL_CTX_set_default_verify_paths(SSL_CTX* a) { - if(ossllib.SSL_CTX_set_default_verify_paths) - return ossllib.SSL_CTX_set_default_verify_paths(a); - else throw new Exception("SSL_CTX_set_default_verify_paths not loaded"); - } + template opDispatch(string name) { + auto opDispatch(T...)(T t) { + static if(__traits(hasMember, ossllib, name)) { + auto ptr = __traits(getMember, ossllib, name); + } else static if(__traits(hasMember, eallib, name)) { + auto ptr = __traits(getMember, eallib, name); + } else static assert(0); - c_long SSL_get_verify_result(const SSL* ssl) { - if(ossllib.SSL_get_verify_result) - return ossllib.SSL_get_verify_result(ssl); - else throw new Exception("SSL_get_verify_result not loaded"); - } + if(ptr is null) + throw new Exception(name ~ " not loaded"); + return ptr(t); + } + } - X509_VERIFY_PARAM* SSL_get0_param(const SSL* ssl) { - if(ossllib.SSL_get0_param) - return ossllib.SSL_get0_param(ssl); - else throw new Exception("SSL_get0_param not loaded"); - } + // macros in the original C + SSL_METHOD* SSLv23_client_method() { + if(ossllib.SSLv23_client_method) + return ossllib.SSLv23_client_method(); + else + return ossllib.TLS_client_method(); + } - X509_STORE* SSL_CTX_get_cert_store(SSL_CTX* a) { - if(ossllib.SSL_CTX_get_cert_store) - return ossllib.SSL_CTX_get_cert_store(a); - else throw new Exception("SSL_CTX_get_cert_store not loaded"); - } + void SSL_set_tlsext_host_name(SSL* a, const char* b) { + if(ossllib.SSL_ctrl) + return ossllib.SSL_ctrl(a, 55 /*SSL_CTRL_SET_TLSEXT_HOSTNAME*/, 0 /*TLSEXT_NAMETYPE_host_name*/, cast(void*) b); + else throw new Exception("SSL_set_tlsext_host_name not loaded"); + } - SSL_CTX* SSL_CTX_new(const SSL_METHOD* a) { - if(ossllib.SSL_CTX_new) - return ossllib.SSL_CTX_new(a); - else throw new Exception("SSL_CTX_new not loaded"); - } - SSL* SSL_new(SSL_CTX* a) { - if(ossllib.SSL_new) - return ossllib.SSL_new(a); - else throw new Exception("SSL_new not loaded"); - } - int SSL_set_fd(SSL* a, int b) { - if(ossllib.SSL_set_fd) - return ossllib.SSL_set_fd(a, b); - else throw new Exception("SSL_set_fd not loaded"); - } + // special case + @trusted nothrow @nogc int SSL_shutdown(SSL* a) { + if(ossllib.SSL_shutdown) + return ossllib.SSL_shutdown(a); + assert(0); + } - extern(C) - alias client_cert_cb = int function(SSL *ssl, X509 **x509, EVP_PKEY **pkey); + void SSL_CTX_keylog_cb_func(SSL_CTX* ctx, keylog_cb func) { + // this isn't in openssl 1.0 and is non-essential, so it is allowed to fail. + if(ossllib.SSL_CTX_set_keylog_callback) + ossllib.SSL_CTX_set_keylog_callback(ctx, func); + //else throw new Exception("SSL_CTX_keylog_cb_func not loaded"); + } - void SSL_CTX_set_client_cert_cb(SSL_CTX *ctx, client_cert_cb cb) { - if(ossllib.SSL_CTX_set_client_cert_cb) - return ossllib.SSL_CTX_set_client_cert_cb(ctx, cb); - else throw new Exception("SSL_CTX_set_client_cert_cb not loaded"); - } - - X509* d2i_X509(X509** a, const(ubyte*)* pp, c_long length) { - if(eallib.d2i_X509) - return eallib.d2i_X509(a, pp, length); - else throw new Exception("d2i_X509 not loaded"); - } - - X509* PEM_read_X509(FILE *fp, X509 **x, pem_password_cb *cb, void *u) { - if(eallib.PEM_read_X509) - return eallib.PEM_read_X509(fp, x, cb, u); - else throw new Exception("PEM_read_X509 not loaded"); - } - EVP_PKEY* PEM_read_PrivateKey(FILE *fp, EVP_PKEY **x, pem_password_cb *cb, void *u) { - if(eallib.PEM_read_PrivateKey) - return eallib.PEM_read_PrivateKey(fp, x, cb, u); - else throw new Exception("PEM_read_PrivateKey not loaded"); - } - - EVP_PKEY* d2i_PrivateKey_fp(FILE *fp, EVP_PKEY **a) { - if(eallib.d2i_PrivateKey_fp) - return eallib.d2i_PrivateKey_fp(fp, a); - else throw new Exception("d2i_PrivateKey_fp not loaded"); - } - X509* d2i_X509_fp(FILE *fp, X509 **x) { - if(eallib.d2i_X509_fp) - return eallib.d2i_X509_fp(fp, x); - else throw new Exception("d2i_X509_fp not loaded"); - } - - int SSL_connect(SSL* a) { - if(ossllib.SSL_connect) - return ossllib.SSL_connect(a); - else throw new Exception("SSL_connect not loaded"); - } - int SSL_write(SSL* a, const void* b, int c) { - if(ossllib.SSL_write) - return ossllib.SSL_write(a, b, c); - else throw new Exception("SSL_write not loaded"); - } - int SSL_read(SSL* a, void* b, int c) { - if(ossllib.SSL_read) - return ossllib.SSL_read(a, b, c); - else throw new Exception("SSL_read not loaded"); - } - @trusted nothrow @nogc int SSL_shutdown(SSL* a) { - if(ossllib.SSL_shutdown) - return ossllib.SSL_shutdown(a); - assert(0); - } - void SSL_free(SSL* a) { - if(ossllib.SSL_free) - return ossllib.SSL_free(a); - else throw new Exception("SSL_free not loaded"); - } - void SSL_CTX_free(SSL_CTX* a) { - if(ossllib.SSL_CTX_free) - return ossllib.SSL_CTX_free(a); - else throw new Exception("SSL_CTX_free not loaded"); - } - - int SSL_pending(const SSL* a) { - if(ossllib.SSL_pending) - return ossllib.SSL_pending(a); - else throw new Exception("SSL_pending not loaded"); - } - void SSL_set_verify(SSL* a, int b, void* c) { - if(ossllib.SSL_set_verify) - return ossllib.SSL_set_verify(a, b, c); - else throw new Exception("SSL_set_verify not loaded"); - } - void SSL_set_tlsext_host_name(SSL* a, const char* b) { - if(ossllib.SSL_ctrl) - return ossllib.SSL_ctrl(a, 55 /*SSL_CTRL_SET_TLSEXT_HOSTNAME*/, 0 /*TLSEXT_NAMETYPE_host_name*/, cast(void*) b); - else throw new Exception("SSL_set_tlsext_host_name not loaded"); - } - int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM* a, const char* b, size_t l) { - if(eallib.X509_VERIFY_PARAM_set1_host) - return eallib.X509_VERIFY_PARAM_set1_host(a, b, l); - else throw new Exception("X509_VERIFY_PARAM_set1_host not loaded"); - } - SSL_METHOD* SSLv3_client_method() { - if(ossllib.SSLv3_client_method) - return ossllib.SSLv3_client_method(); - else throw new Exception("SSLv3_client_method not loaded"); - } - SSL_METHOD* TLS_client_method() { - if(ossllib.TLS_client_method) - return ossllib.TLS_client_method(); - else throw new Exception("TLS_client_method not loaded"); - } - void ERR_print_errors_cb(print_errors_cb cb, void* u) { - if(eallib.ERR_print_errors_cb) - return eallib.ERR_print_errors_cb(cb, u); - else throw new Exception("ERR_print_errors_cb not loaded"); - } - void X509_free(X509* x) { - if(eallib.X509_free) - return eallib.X509_free(x); - else throw new Exception("X509_free not loaded"); - } - int X509_STORE_add_cert(X509_STORE* s, X509* x) { - if(eallib.X509_STORE_add_cert) - return eallib.X509_STORE_add_cert(s, x); - else throw new Exception("X509_STORE_add_cert not loaded"); } extern(C) @@ -3339,15 +3224,6 @@ version(use_openssl) { return 0; } - extern(C) - void SSL_CTX_keylog_cb_func(SSL_CTX* ctx, void function(SSL*, char*) func) - { - // this isn't in openssl 1.0 and is non-essential, so it is allowed to fail. - if(ossllib.SSL_CTX_set_keylog_callback) - ossllib.SSL_CTX_set_keylog_callback(ctx, func); - //else throw new Exception("SSL_CTX_keylog_cb_func not loaded"); - } - private __gshared void* ossllib_handle; version(Windows) @@ -3475,7 +3351,7 @@ version(use_openssl) { string logfile = environment.get("SSLKEYLOGFILE"); if (logfile !is null) { - auto f = std.stdio.File("/tmp/keyfile", "a+"); + auto f = std.stdio.File(logfile, "a+"); f.writeln(fromStringz(line)); f.close(); } @@ -3485,31 +3361,31 @@ version(use_openssl) { private SSL* ssl; private SSL_CTX* ctx; private void initSsl(bool verifyPeer, string hostname) { - ctx = SSL_CTX_new(SSLv23_client_method()); + ctx = OpenSSL.SSL_CTX_new(OpenSSL.SSLv23_client_method()); assert(ctx !is null); - SSL_CTX_set_default_verify_paths(ctx); + OpenSSL.SSL_CTX_set_default_verify_paths(ctx); version(Windows) loadCertificatesFromRegistry(ctx); - debug SSL_CTX_keylog_cb_func(ctx, &write_to_file); - ssl = SSL_new(ctx); + debug OpenSSL.SSL_CTX_keylog_cb_func(ctx, &write_to_file); + ssl = OpenSSL.SSL_new(ctx); if(hostname.length) { - SSL_set_tlsext_host_name(ssl, toStringz(hostname)); + OpenSSL.SSL_set_tlsext_host_name(ssl, toStringz(hostname)); if(verifyPeer) - X509_VERIFY_PARAM_set1_host(SSL_get0_param(ssl), hostname.ptr, hostname.length); + OpenSSL.X509_VERIFY_PARAM_set1_host(OpenSSL.SSL_get0_param(ssl), hostname.ptr, hostname.length); } if(verifyPeer) - SSL_set_verify(ssl, SSL_VERIFY_PEER, null); + OpenSSL.SSL_set_verify(ssl, SSL_VERIFY_PEER, null); else - SSL_set_verify(ssl, SSL_VERIFY_NONE, null); + OpenSSL.SSL_set_verify(ssl, SSL_VERIFY_NONE, null); - SSL_set_fd(ssl, cast(int) this.handle); // on win64 it is necessary to truncate, but the value is never large anyway see http://openssl.6102.n7.nabble.com/Sockets-windows-64-bit-td36169.html + OpenSSL.SSL_set_fd(ssl, cast(int) this.handle); // on win64 it is necessary to truncate, but the value is never large anyway see http://openssl.6102.n7.nabble.com/Sockets-windows-64-bit-td36169.html - SSL_CTX_set_client_cert_cb(ctx, &cb); + OpenSSL.SSL_CTX_set_client_cert_cb(ctx, &cb); } extern(C) @@ -3534,12 +3410,12 @@ version(use_openssl) { else goto case der; case pem: - *x509 = PEM_read_X509(fpCert, null, null, null); - *pkey = PEM_read_PrivateKey(fpKey, null, null, null); + *x509 = OpenSSL.PEM_read_X509(fpCert, null, null, null); + *pkey = OpenSSL.PEM_read_PrivateKey(fpKey, null, null, null); break; case der: - *x509 = d2i_X509_fp(fpCert, null); - *pkey = d2i_PrivateKey_fp(fpKey, null); + *x509 = OpenSSL.d2i_X509_fp(fpCert, null); + *pkey = OpenSSL.d2i_PrivateKey_fp(fpKey, null); break; } @@ -3550,7 +3426,7 @@ version(use_openssl) { } bool dataPending() { - return SSL_pending(ssl) > 0; + return OpenSSL.SSL_pending(ssl) > 0; } @trusted @@ -3561,11 +3437,11 @@ version(use_openssl) { @trusted void do_ssl_connect() { - if(SSL_connect(ssl) == -1) { + if(OpenSSL.SSL_connect(ssl) == -1) { string str; - ERR_print_errors_cb(&collectSslErrors, &str); + OpenSSL.ERR_print_errors_cb(&collectSslErrors, &str); int i; - auto err = SSL_get_verify_result(ssl); + auto err = OpenSSL.SSL_get_verify_result(ssl); //printf("wtf\n"); //scanf("%d\n", i); throw new Exception("Secure connect failed: " ~ getOpenSslErrorCode(err)); @@ -3576,10 +3452,10 @@ version(use_openssl) { override ptrdiff_t send(scope const(void)[] buf, SocketFlags flags) { //import std.stdio;writeln(cast(string) buf); debug(arsd_http2_verbose) writeln("ssl writing ", buf.length); - auto retval = SSL_write(ssl, buf.ptr, cast(uint) buf.length); + auto retval = OpenSSL.SSL_write(ssl, buf.ptr, cast(uint) buf.length); if(retval == -1) { string str; - ERR_print_errors_cb(&collectSslErrors, &str); + OpenSSL.ERR_print_errors_cb(&collectSslErrors, &str); int i; //printf("wtf\n"); //scanf("%d\n", i); @@ -3595,11 +3471,11 @@ version(use_openssl) { override ptrdiff_t receive(scope void[] buf, SocketFlags flags) { debug(arsd_http2_verbose) writeln("ssl_read before"); - auto retval = SSL_read(ssl, buf.ptr, cast(int)buf.length); + auto retval = OpenSSL.SSL_read(ssl, buf.ptr, cast(int)buf.length); debug(arsd_http2_verbose) writeln("ssl_read after"); if(retval == -1) { string str; - ERR_print_errors_cb(&collectSslErrors, &str); + OpenSSL.ERR_print_errors_cb(&collectSslErrors, &str); int i; //printf("wtf\n"); //scanf("%d\n", i); @@ -3617,7 +3493,7 @@ version(use_openssl) { } override void close() { - if(ssl) SSL_shutdown(ssl); + if(ssl) OpenSSL.SSL_shutdown(ssl); super.close(); } @@ -3629,8 +3505,8 @@ version(use_openssl) { void freeSsl() { if(ssl is null) return; - SSL_free(ssl); - SSL_CTX_free(ctx); + OpenSSL.SSL_free(ssl); + OpenSSL.SSL_CTX_free(ctx); ssl = null; } @@ -5136,7 +5012,7 @@ version(Windows) { scope(exit) CertCloseStore(store, 0); - X509_STORE* ssl_store = SSL_CTX_get_cert_store(ctx); + X509_STORE* ssl_store = OpenSSL.SSL_CTX_get_cert_store(ctx); PCCERT_CONTEXT c; while((c = CertEnumCertificatesInStore(store, c)) !is null) { FILETIME na = c.pCertInfo.NotAfter; @@ -5157,10 +5033,14 @@ version(Windows) { } const(ubyte)* thing = c.pbCertEncoded; - auto x509 = d2i_X509(null, &thing, c.cbCertEncoded); + auto x509 = OpenSSL.d2i_X509(null, &thing, c.cbCertEncoded); if (x509) { - auto success = X509_STORE_add_cert(ssl_store, x509); - X509_free(x509); + auto success = OpenSSL.X509_STORE_add_cert(ssl_store, x509); + //if(!success) + //writeln("FAILED HERE"); + OpenSSL.X509_free(x509); + } else { + //writeln("FAILED"); } } From 2501e6e575bca9219dd36093aa01ae2f26a64241 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Thu, 14 Apr 2022 12:51:18 -0400 Subject: [PATCH 07/24] use Windows to verify certificates as well as provide them --- http2.d | 84 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/http2.d b/http2.d index 45fdf1d..51686e7 100644 --- a/http2.d +++ b/http2.d @@ -3073,9 +3073,10 @@ version(use_openssl) { return sslErrorCodes[error]; } - struct SSL {} - struct SSL_CTX {} - struct SSL_METHOD {} + struct SSL; + struct SSL_CTX; + struct SSL_METHOD; + struct X509_STORE_CTX; enum SSL_VERIFY_NONE = 0; enum SSL_VERIFY_PEER = 1; @@ -3148,6 +3149,8 @@ version(use_openssl) { void function() OpenSSL_add_all_digests; /* } */ + const(char)* function(int) OpenSSL_version; + void function(ulong, void*) OPENSSL_init_crypto; void function(print_errors_cb, void*) ERR_print_errors_cb; @@ -3163,8 +3166,12 @@ version(use_openssl) { X509* function(FILE *fp, X509 **x) d2i_X509_fp; X509* function(X509** a, const(ubyte*)* pp, c_long length) d2i_X509; + int function(X509* a, ubyte** o) i2d_X509; int function(X509_VERIFY_PARAM* a, const char* b, size_t l) X509_VERIFY_PARAM_set1_host; + + X509* function(X509_STORE_CTX *ctx) X509_STORE_CTX_get_current_cert; + int function(X509_STORE_CTX *ctx) X509_STORE_CTX_get_error; } } @@ -3364,10 +3371,6 @@ version(use_openssl) { ctx = OpenSSL.SSL_CTX_new(OpenSSL.SSLv23_client_method()); assert(ctx !is null); - OpenSSL.SSL_CTX_set_default_verify_paths(ctx); - version(Windows) - loadCertificatesFromRegistry(ctx); - debug OpenSSL.SSL_CTX_keylog_cb_func(ctx, &write_to_file); ssl = OpenSSL.SSL_new(ctx); @@ -3377,9 +3380,15 @@ version(use_openssl) { OpenSSL.X509_VERIFY_PARAM_set1_host(OpenSSL.SSL_get0_param(ssl), hostname.ptr, hostname.length); } - if(verifyPeer) - OpenSSL.SSL_set_verify(ssl, SSL_VERIFY_PEER, null); - else + if(verifyPeer) { + OpenSSL.SSL_CTX_set_default_verify_paths(ctx); + + version(Windows) { + loadCertificatesFromRegistry(ctx); + } + + OpenSSL.SSL_set_verify(ssl, SSL_VERIFY_PEER, &verifyCertificateFromRegistryArsdHttp); + } else OpenSSL.SSL_set_verify(ssl, SSL_VERIFY_NONE, null); OpenSSL.SSL_set_fd(ssl, cast(int) this.handle); // on win64 it is necessary to truncate, but the value is never large anyway see http://openssl.6102.n7.nabble.com/Sockets-windows-64-bit-td36169.html @@ -4997,11 +5006,62 @@ public { } } +private extern(C) +int verifyCertificateFromRegistryArsdHttp(int preverify_ok, X509_STORE_CTX* ctx) { + version(Windows) { + if(preverify_ok) + return 1; + + auto err_cert = OpenSSL.X509_STORE_CTX_get_current_cert(ctx); + auto err = OpenSSL.X509_STORE_CTX_get_error(ctx); + + if(err == 62) + return 0; // hostname mismatch is an error we can trust; that means OpenSSL already found the certificate and rejected it + + auto len = OpenSSL.i2d_X509(err_cert, null); + if(len == -1) + return 0; + ubyte[] buffer = new ubyte[](len); + auto ptr = buffer.ptr; + len = OpenSSL.i2d_X509(err_cert, &ptr); + if(len != buffer.length) + return 0; + + + CERT_CHAIN_PARA thing; + thing.cbSize = thing.sizeof; + auto context = CertCreateCertificateContext(X509_ASN_ENCODING, buffer.ptr, cast(int) buffer.length); + if(context is null) + return 0; + scope(exit) CertFreeCertificateContext(context); + + PCCERT_CHAIN_CONTEXT chain; + if(CertGetCertificateChain(null, context, null, null, &thing, 0, null, &chain)) { + scope(exit) + CertFreeCertificateChain(chain); + + DWORD errorStatus = chain.TrustStatus.dwErrorStatus; + + if(errorStatus == 0) + return 1; // Windows approved it, OK carry on + // otherwise, sustain OpenSSL's original ruling + } + + return 0; + } else { + return preverify_ok; + } +} + + version(Windows) { pragma(lib, "crypt32"); import core.sys.windows.wincrypt; - extern(Windows) + extern(Windows) { PCCERT_CONTEXT CertEnumCertificatesInStore(HCERTSTORE hCertStore, PCCERT_CONTEXT pPrevCertContext); + // BOOL CertGetCertificateChain(HCERTCHAINENGINE hChainEngine, PCCERT_CONTEXT pCertContext, LPFILETIME pTime, HCERTSTORE hAdditionalStore, PCERT_CHAIN_PARA pChainPara, DWORD dwFlags, LPVOID pvReserved, PCCERT_CHAIN_CONTEXT *ppChainContext); + PCCERT_CONTEXT CertCreateCertificateContext(DWORD dwCertEncodingType, const BYTE *pbCertEncoded, DWORD cbCertEncoded); + } void loadCertificatesFromRegistry(SSL_CTX* ctx) { auto store = CertOpenSystemStore(0, "ROOT"); @@ -5045,6 +5105,8 @@ version(Windows) { } CertFreeCertificateContext(c); + + // import core.stdc.stdio; printf("%s\n", OpenSSL.OpenSSL_version(0)); } From 7d81f250fe98906cbfc6896487afff6339c037f7 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 19 Apr 2022 12:07:08 -0400 Subject: [PATCH 08/24] more improvements to input handling and web --- minigui.d | 185 ++++++++++++++++++++++++++++++++++----- minigui_addons/webview.d | 167 +++++++++++++++++++++++++++++++---- simpledisplay.d | 50 ++++++++++- 3 files changed, 358 insertions(+), 44 deletions(-) diff --git a/minigui.d b/minigui.d index b6e5b68..80ae350 100644 --- a/minigui.d +++ b/minigui.d @@ -1925,6 +1925,18 @@ class Widget : ReflectableProperties { return StyleInformation(this); } + int focusableWidgets(scope int delegate(Widget) dg) { + foreach(widget; WidgetStream(this)) { + if(widget.tabStop && !widget.hidden) { + int result = dg(widget); + if (result) + return result; + } + } + return 0; + } + + // FIXME: I kinda want to hide events from implementation widgets // so it just catches them all and stops propagation... // i guess i can do it with a event listener on star. @@ -7084,7 +7096,7 @@ class HorizontalLayout : Layout { } -version(Windows) +version(win32_widgets) private extern(Windows) LRESULT DoubleBufferWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) nothrow { @@ -7944,6 +7956,12 @@ class Window : Widget { Window.newWindowCreated(this); } + version(custom_widgets) + override void defaultEventHandler_click(ClickEvent event) { + if(event.target && event.target.tabStop) + event.target.focus(); + } + private static void delegate(Window) newWindowCreated; version(win32_widgets) @@ -8082,13 +8100,13 @@ class Window : Widget { } win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.normal, WindowFlags.dontAutoShow | WindowFlags.managesChildWindowFocus); - /+ + ///+ // for input proxy auto display = XDisplayConnection.get; auto inputProxy = XCreateSimpleWindow(display, win.window, -1, -1, 1, 1, 0, 0, 0); XSelectInput(display, inputProxy, EventMask.KeyPressMask | EventMask.KeyReleaseMask); XMapWindow(display, inputProxy); - import std.stdio; writefln("input proxy: 0x%0x", inputProxy); + //import std.stdio; writefln("input proxy: 0x%0x", inputProxy); this.inputProxy = new SimpleWindow(inputProxy); XEvent lastEvent; @@ -8108,8 +8126,11 @@ class Window : Widget { if(auto nw = cast(NestedChildWindowWidget) focusedWidget) { auto thing = nw.focusableWindow(); if(thing && thing.window) { - import std.stdio; writeln("sending event ", lastEvent.xkey); - XSendEvent(XDisplayConnection.get, thing.window, false, 0, &lastEvent); + lastEvent.xkey.window = thing.window; + // import std.stdio; writeln("sending event ", lastEvent.xkey); + trapXErrors( { + XSendEvent(XDisplayConnection.get, thing.window, false, 0, &lastEvent); + }); } } } @@ -8121,7 +8142,7 @@ class Window : Widget { }, ); // done - +/ + //+/ @@ -8133,13 +8154,7 @@ class Window : Widget { SimpleWindow inputProxy; private SimpleWindow setRequestedInputFocus() { - // return inputProxy; - - if(auto fw = cast(NestedChildWindowWidget) focusedWidget) { - // sdpyPrintDebugString("heaven"); - return fw.focusableWindow; - } - return win; + return inputProxy; } /// ditto @@ -8173,14 +8188,15 @@ class Window : Widget { event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false; event.dispatch(); - return !event.defaultPrevented; + return !event.propagationStopped; } + // returns true if propagation should continue into nested things.... prolly not a great thing to do. bool dispatchCharEvent(dchar ch) { if(focusedWidget) { auto event = new CharEvent(focusedWidget, ch); event.dispatch(); - return !event.defaultPrevented; + return !event.propagationStopped; } return true; } @@ -8343,18 +8359,29 @@ class Window : Widget { } static Widget getFirstFocusable(Widget start) { - if(start.tabStop && !start.hidden) - return start; + if(start is null) + return null; - if(!start.hidden) - foreach(child; start.children) { - auto f = getFirstFocusable(child); - if(f !is null) - return f; + foreach(widget; &start.focusableWidgets) { + return widget; } + return null; } + static Widget getLastFocusable(Widget start) { + if(start is null) + return null; + + Widget last; + foreach(widget; &start.focusableWidgets) { + last = widget; + } + + return last; + } + + mixin Emits!ClosingEvent; mixin Emits!ClosedEvent; } @@ -10457,9 +10484,10 @@ class Menu : Window { if(!menuParent.parentWindow.win.closed) { if(auto maw = cast(MouseActivatedWidget) menuParent) { maw.setDynamicState(DynamicState.depressed, false); + maw.setDynamicState(DynamicState.hover, false); maw.redraw(); } - menuParent.parentWindow.win.focus(); + // menuParent.parentWindow.win.focus(); } clickListener.disconnect(); } @@ -14225,6 +14253,117 @@ interface ReflectableProperties { } } +private struct Stack(T) { + this(int maxSize) { + internalLength = 0; + arr = initialBuffer[]; + } + + ///. + void push(T t) { + if(internalLength >= arr.length) { + auto oldarr = arr; + if(arr.length < 4096) + arr = new T[arr.length * 2]; + else + arr = new T[arr.length + 4096]; + arr[0 .. oldarr.length] = oldarr[]; + } + + arr[internalLength] = t; + internalLength++; + } + + ///. + T pop() { + assert(internalLength); + internalLength--; + return arr[internalLength]; + } + + ///. + T peek() { + assert(internalLength); + return arr[internalLength - 1]; + } + + ///. + @property bool empty() { + return internalLength ? false : true; + } + + ///. + private T[] arr; + private size_t internalLength; + private T[64] initialBuffer; + // the static array is allocated with this object, so if we have a small stack (which we prolly do; dom trees usually aren't insanely deep), + // using this saves us a bunch of trips to the GC. In my last profiling, I got about a 50x improvement in the push() + // function thanks to this, and push() was actually one of the slowest individual functions in the code! +} + +/// This is the lazy range that walks the tree for you. It tries to go in the lexical order of the source: node, then children from first to last, each recursively. +private struct WidgetStream { + + ///. + @property Widget front() { + return current.widget; + } + + /// Use Widget.tree instead. + this(Widget start) { + current.widget = start; + current.childPosition = -1; + isEmpty = false; + stack = typeof(stack)(0); + } + + /* + Handle it + handle its children + + */ + + ///. + void popFront() { + more: + if(isEmpty) return; + + // FIXME: the profiler says this function is somewhat slow (noticeable because it can be called a lot of times) + + current.childPosition++; + if(current.childPosition >= current.widget.children.length) { + if(stack.empty()) + isEmpty = true; + else { + current = stack.pop(); + goto more; + } + } else { + stack.push(current); + current.widget = current.widget.children[current.childPosition]; + current.childPosition = -1; + } + } + + ///. + @property bool empty() { + return isEmpty; + } + + private: + + struct Current { + Widget widget; + int childPosition; + } + + Current current; + + Stack!(Current) stack; + + bool isEmpty; +} + /+ diff --git a/minigui_addons/webview.d b/minigui_addons/webview.d index 194743e..d1e74cf 100644 --- a/minigui_addons/webview.d +++ b/minigui_addons/webview.d @@ -360,7 +360,7 @@ class WebViewWidget_CEF : WebViewWidgetBase { //semaphore = new Semaphore; assert(CefApp.active); - this(new MiniguiCefClient(openNewWindow), parent); + this(new MiniguiCefClient(openNewWindow), parent, false); cef_window_info_t window_info; window_info.parent_window = containerWindow.nativeWindowHandle; @@ -398,7 +398,7 @@ class WebViewWidget_CEF : WebViewWidgetBase { .destroy(this); // but this is ok to do some memory management cleanup } - private this(MiniguiCefClient client, Widget parent) { + private this(MiniguiCefClient client, Widget parent, bool isDevTools) { super(parent); this.client = client; @@ -407,22 +407,56 @@ class WebViewWidget_CEF : WebViewWidgetBase { mapping[containerWindow.nativeWindowHandle()] = this; - - this.parentWindow.addEventListener((FocusEvent fe) { - if(!browserHandle) return; - //browserHandle.get_host.set_focus(true); - - executeJavascript("if(window.__arsdPreviouslyFocusedNode) window.__arsdPreviouslyFocusedNode.focus(); window.dispatchEvent(new FocusEvent(\"focus\"));"); + this.addEventListener(delegate(KeyDownEvent ke) { + if(ke.key == Key.Tab) + ke.preventDefault(); }); - this.parentWindow.addEventListener((BlurEvent be) { + + this.addEventListener((FocusEvent fe) { if(!browserHandle) return; - executeJavascript("if(document.activeElement) { window.__arsdPreviouslyFocusedNode = document.activeElement; document.activeElement.blur(); } window.dispatchEvent(new FocusEvent(\"blur\"));"); + XFocusChangeEvent ev; + ev.type = arsd.simpledisplay.EventType.FocusIn; + ev.display = XDisplayConnection.get; + ev.window = ozone; + ev.mode = NotifyModes.NotifyNormal; + ev.detail = NotifyDetail.NotifyVirtual; + + trapXErrors( { + XSendEvent(XDisplayConnection.get, ozone, false, 0, cast(XEvent*) &ev); + }); + + // this also works if the message is buggy and it avoids weirdness from raising window etc + //executeJavascript("if(window.__arsdPreviouslyFocusedNode) window.__arsdPreviouslyFocusedNode.focus(); window.dispatchEvent(new FocusEvent(\"focus\"));"); + }); + this.addEventListener((BlurEvent be) { + if(!browserHandle) return; + + XFocusChangeEvent ev; + ev.type = arsd.simpledisplay.EventType.FocusOut; + ev.display = XDisplayConnection.get; + ev.window = ozone; + ev.mode = NotifyModes.NotifyNormal; + ev.detail = NotifyDetail.NotifyNonlinearVirtual; + + trapXErrors( { + XSendEvent(XDisplayConnection.get, ozone, false, 0, cast(XEvent*) &ev); + }); + + //executeJavascript("if(document.activeElement) { window.__arsdPreviouslyFocusedNode = document.activeElement; document.activeElement.blur(); } window.dispatchEvent(new FocusEvent(\"blur\"));"); }); bool closeAttempted = false; + if(isDevTools) this.parentWindow.addEventListener((scope ClosingEvent ce) { + this.parentWindow.hide(); + ce.preventDefault(); + }); + else + this.parentWindow.addEventListener((scope ClosingEvent ce) { + if(devTools) + devTools.close(); if(!closeAttempted && browserHandle) { browserHandle.get_host.close_browser(true); ce.preventDefault(); @@ -450,6 +484,7 @@ class WebViewWidget_CEF : WebViewWidgetBase { } private NativeWindowHandle browserWindow; + private NativeWindowHandle ozone; private RC!cef_browser_t browserHandle; private static WebViewWidget[NativeWindowHandle] mapping; @@ -475,9 +510,37 @@ class WebViewWidget_CEF : WebViewWidgetBase { browserHandle.get_main_frame.execute_java_script(&c, &u, line); } + private Window devTools; override void showDevTools() { if(!browserHandle) return; - browserHandle.get_host.show_dev_tools(null /* window info */, client.passable, null /* settings */, null /* inspect element at coordinates */); + + if(devTools is null) { + auto host = browserHandle.get_host; + + if(host.has_dev_tools()) { + host.close_dev_tools(); + return; + } + + cef_window_info_t windowinfo; + version(linux) { + auto sw = new Window("DevTools"); + //sw.win.beingOpenKeepsAppOpen = false; + devTools = sw; + + auto wv = new WebViewWidget_CEF(client, sw, true); + + sw.show(); + + windowinfo.parent_window = wv.containerWindow.nativeWindowHandle; + } + host.show_dev_tools(&windowinfo, client.passable, null /* settings */, null /* inspect element at coordinates */); + } else { + if(devTools.hidden) + devTools.show(); + else + devTools.hide(); + } } // FYI the cef browser host also allows things like custom spelling dictionaries and getting navigation entries. @@ -534,7 +597,7 @@ version(cef) { cef_dictionary_value_t** extra_info, int* no_javascript_access ) { - + sdpyPrintDebugString("on_before_popup"); if(this.client.openNewWindow is null) return 1; // new windows disabled @@ -551,7 +614,7 @@ version(cef) { scope WebViewWidget delegate(Widget, BrowserSettings) accept = (parent, passed_settings) { ret = 0; if(parent !is null) { - auto widget = new WebViewWidget_CEF(this.client, parent); + auto widget = new WebViewWidget_CEF(this.client, parent, false); (*windowInfo).parent_window = widget.containerWindow.nativeWindowHandle; passed_settings.set(browser_settings); @@ -589,10 +652,13 @@ version(cef) { import arsd.simpledisplay : Window; Window root; Window parent; + Window ozone; uint c = 0; auto display = XDisplayConnection.get; Window* children; XQueryTree(display, handle, &root, &parent, &children, &c); + if(c == 1) + ozone = children[0]; XFree(children); } else static assert(0); @@ -600,8 +666,9 @@ version(cef) { auto wv = *wvp; wv.browserWindow = handle; wv.browserHandle = RC!cef_browser_t(ptr); + wv.ozone = ozone ? ozone : handle; - wv.browserWindowWrapped = new SimpleWindow(wv.browserWindow); + wv.browserWindowWrapped = new SimpleWindow(wv.ozone); /+ XSelectInput(XDisplayConnection.get, handle, EventMask.FocusChangeMask); @@ -831,17 +898,70 @@ version(cef) { } } + class MiniguiRequestHandler : CEF!cef_request_handler_t { + override int on_before_browse(RC!(cef_browser_t), RC!(cef_frame_t), RC!(cef_request_t), int, int) nothrow { + return 0; + } + override int on_open_urlfrom_tab(RC!(cef_browser_t), RC!(cef_frame_t), const(cef_string_utf16_t)*, cef_window_open_disposition_t, int) nothrow { + return 0; + } + override cef_resource_request_handler_t* get_resource_request_handler(RC!(cef_browser_t), RC!(cef_frame_t), RC!(cef_request_t), int, int, const(cef_string_utf16_t)*, int*) nothrow { + return null; + } + override int get_auth_credentials(RC!(cef_browser_t), const(cef_string_utf16_t)*, int, const(cef_string_utf16_t)*, int, const(cef_string_utf16_t)*, const(cef_string_utf16_t)*, RC!(cef_auth_callback_t)) nothrow { + // this is for http basic auth popup..... + return 0; + } + override int on_quota_request(RC!(cef_browser_t), const(cef_string_utf16_t)*, long, RC!(cef_callback_t)) nothrow { + return 0; + } + override int on_certificate_error(RC!(cef_browser_t), cef_errorcode_t, const(cef_string_utf16_t)*, RC!(cef_sslinfo_t), RC!(cef_callback_t)) nothrow { + return 0; + } + override int on_select_client_certificate(RC!(cef_browser_t), int, const(cef_string_utf16_t)*, int, ulong, cef_x509certificate_t**, RC!(cef_select_client_certificate_callback_t)) nothrow { + return 0; + } + override void on_plugin_crashed(RC!(cef_browser_t), const(cef_string_utf16_t)*) nothrow { + + } + override void on_render_view_ready(RC!(cef_browser_t) p) nothrow { + + } + override void on_render_process_terminated(RC!(cef_browser_t), cef_termination_status_t) nothrow { + + } + override void on_document_available_in_main_frame(RC!(cef_browser_t) browser) nothrow { + browser.runOnWebView(delegate(wv) { + wv.executeJavascript("console.log('here');"); + }); + + } + } + class MiniguiFocusHandler : CEF!cef_focus_handler_t { override void on_take_focus(RC!(cef_browser_t) browser, int next) nothrow { - // sdpyPrintDebugString("take"); + browser.runOnWebView(delegate(wv) { + Widget f; + if(next) { + f = Window.getFirstFocusable(wv.parentWindow); + } else { + foreach(w; &wv.parentWindow.focusableWidgets) { + if(w is wv) + break; + f = w; + } + } + if(f) + f.focus(); + }); } override int on_set_focus(RC!(cef_browser_t) browser, cef_focus_source_t source) nothrow { /+ browser.runOnWebView((ev) { - sdpyPrintDebugString("setting"); - ev.parentWindow.focusedWidget = ev; + ev.focus(); // even this can steal focus from other parts of my application! }); +/ + //sdpyPrintDebugString("setting"); return 1; // otherwise, cancel because this bullshit tends to steal focus from other applications and i never, ever, ever want that to happen. // seems to happen because of race condition in it getting a focus event and then stealing the focus from the parent @@ -850,7 +970,12 @@ version(cef) { // it also breaks its own pop up menus and drop down boxes to allow this! wtf } override void on_got_focus(RC!(cef_browser_t) browser) nothrow { - // sdpyPrintDebugString("got"); + browser.runOnWebView((ev) { + // this sometimes steals from the app too but it is relatively acceptable + // steals when i mouse in from the side of the window quickly, but still + // i want the minigui state to match so i'll allow it + ev.focus(); + }); } } @@ -865,6 +990,7 @@ version(cef) { MiniguiDownloadHandler downloadHandler; MiniguiKeyboardHandler keyboardHandler; MiniguiFocusHandler focusHandler; + MiniguiRequestHandler requestHandler; this(void delegate(scope OpenNewWindowParams) openNewWindow) { this.openNewWindow = openNewWindow; lsh = new MiniguiCefLifeSpanHandler(this); @@ -874,6 +1000,7 @@ version(cef) { downloadHandler = new MiniguiDownloadHandler(); keyboardHandler = new MiniguiKeyboardHandler(); focusHandler = new MiniguiFocusHandler(); + requestHandler = new MiniguiRequestHandler(); } override cef_audio_handler_t* get_audio_handler() { @@ -917,10 +1044,12 @@ version(cef) { override cef_render_handler_t* get_render_handler() { // this thing might work for an off-screen thing // like to an image or to a video stream maybe + // + // might be useful to have it render here then send it over too for remote X sharing a process return null; } override cef_request_handler_t* get_request_handler() { - return null; + return requestHandler.returnable; } override int on_process_message_received(RC!cef_browser_t, RC!cef_frame_t, cef_process_id_t, RC!cef_process_message_t) { return 0; // return 1 if you can actually handle the message diff --git a/simpledisplay.d b/simpledisplay.d index 00087a9..699f93b 100644 --- a/simpledisplay.d +++ b/simpledisplay.d @@ -12296,6 +12296,13 @@ version(X11) { if(width == 0 || height == 0) { XSetClipMask(display, gc, None); + if(xrenderPicturePainter) { + + XRectangle[1] rects; + rects[0] = XRectangle(short.min, short.min, short.max, short.max); + XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); + } + version(with_xft) { if(xftFont is null || xftDraw is null) return; @@ -12306,6 +12313,9 @@ version(X11) { rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); + if(xrenderPicturePainter) + XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); + version(with_xft) { if(xftFont is null || xftDraw is null) return; @@ -12572,6 +12582,12 @@ version(X11) { XRenderPictureAttributes attrs; // FIXME: I can prolly reuse this as long as the pixmap itself is valid. xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); + + // need to initialize the clip + XRectangle[1] rects; + rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); + + XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); } XRenderComposite( @@ -13613,6 +13629,35 @@ mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrL } } + /++ + Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. + + Please note that it returns + +/ + XErrorEvent[] trapXErrors(scope void delegate() dg) { + + static XErrorEvent[] errorBuffer; + + static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { + errorBuffer ~= *evt; + return 0; + } + + auto savedErrorHandler = XSetErrorHandler(&handler); + + try { + dg(); + } finally { + XSync(XDisplayConnection.get, 0/*False*/); + XSetErrorHandler(savedErrorHandler); + } + + auto bfr = errorBuffer; + errorBuffer = null; + + return bfr; + } + /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. class XDisplayConnection { private __gshared Display* display; @@ -16986,6 +17031,9 @@ extern(C) { extern(C) alias XIOErrorHandler = int function (Display* display); } +extern(C) nothrow +alias XErrorHandler = int function(Display*, XErrorEvent*); + extern(C) nothrow @nogc { struct Screen{ XExtData *ext_data; /* hook for extension to hang data */ @@ -17190,8 +17238,6 @@ struct Visual byte pad; } - alias XErrorHandler = int function(Display*, XErrorEvent*); - struct XRectangle { short x; short y; From 79b630e5474e42f44a412a3502d9496df359da73 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 19 Apr 2022 12:20:46 -0400 Subject: [PATCH 09/24] version forgot --- http2.d | 2 +- minigui.d | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/http2.d b/http2.d index 51686e7..67a1b27 100644 --- a/http2.d +++ b/http2.d @@ -3070,7 +3070,7 @@ version(use_openssl) { if(error < 0 || error >= sslErrorCodes.length) return "SSL/TLS error code " ~ to!string(error); - return sslErrorCodes[error]; + return sslErrorCodes[cast(size_t) error]; } struct SSL; diff --git a/minigui.d b/minigui.d index 80ae350..7a3e63a 100644 --- a/minigui.d +++ b/minigui.d @@ -8100,6 +8100,7 @@ class Window : Widget { } win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.normal, WindowFlags.dontAutoShow | WindowFlags.managesChildWindowFocus); + static if(UsingSimpledisplayX11) { ///+ // for input proxy auto display = XDisplayConnection.get; @@ -8143,6 +8144,7 @@ class Window : Widget { ); // done //+/ + } From 628e02c9895b5b97f0cadb7e239a9cba98ab8501 Mon Sep 17 00:00:00 2001 From: JR Date: Wed, 20 Apr 2022 15:46:21 +0200 Subject: [PATCH 10/24] Use arrays for potential OpenSSL lib filenames This should fix SSL loading on macOS while allowing for easy additions on so-name bumps on any platform. --- http2.d | 71 +++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/http2.d b/http2.d index 67a1b27..aa4c460 100644 --- a/http2.d +++ b/http2.d @@ -3252,33 +3252,60 @@ version(use_openssl) { return; synchronized(loadSslMutex) { - version(OSX) { - // newest box - ossllib_handle = dlopen("libssl.1.1.dylib", RTLD_NOW); - // other boxes - if(ossllib_handle is null) - ossllib_handle = dlopen("libssl.dylib", RTLD_NOW); - // old ones like my laptop test - if(ossllib_handle is null) - ossllib_handle = dlopen("/usr/local/opt/openssl/lib/libssl.1.0.0.dylib", RTLD_NOW); + version(Posix) { + version(OSX) { + static immutable string[] ossllibs = [ + "libssl.46.dylib", + "libssl.44.dylib", + "libssl.43.dylib", + "libssl.35.dylib", + "libssl.1.1.dylib", + "/usr/local/opt/openssl/lib/libssl.1.0.0.dylib", + "libssl.dylib", + ]; + } else { + static immutable string[] ossllibs = [ + "libssl.so.1.1", + "libssl.so.1.0.2", + "libssl.so.1.0.1", + "libssl.so.1.0.0", + "libssl.so", + ]; + } - } else version(Posix) { - ossllib_handle = dlopen("libssl.so.1.1", RTLD_NOW); - if(ossllib_handle is null) - ossllib_handle = dlopen("libssl.so", RTLD_NOW); + foreach(lib; ossllibs) { + ossllib_handle = dlopen(lib.ptr, RTLD_NOW); + if(ossllib_handle !is null) break; + } } else version(Windows) { - version(X86_64) + version(X86_64) { ossllib_handle = LoadLibraryW("libssl-1_1-x64.dll"w.ptr); - if(ossllib_handle is null) - ossllib_handle = LoadLibraryW("libssl32.dll"w.ptr); - version(X86_64) oeaylib_handle = LoadLibraryW("libcrypto-1_1-x64.dll"w.ptr); - if(oeaylib_handle is null) - oeaylib_handle = LoadLibraryW("libeay32.dll"w.ptr); + } else { + static immutable wstring[] ossllibs = [ + "libssl-1_1.dll"w, + "libssl32.dll"w, + ]; - if(ossllib_handle is null) { - ossllib_handle = LoadLibraryW("ssleay32.dll"w.ptr); - oeaylib_handle = ossllib_handle; + foreach(lib; ossllibs) { + ossllib_handle = LoadLibraryW(lib.ptr); + if(ossllib_handle !is null) break; + } + + static immutable wstring[] eaylibs = [ + "libcrypto-1_1.dll"w, + "libeay32.dll", + ]; + + foreach(lib; eaylibs) { + oeaylib_handle = LoadLibraryW(lib.ptr); + if (oeaylib_handle !is null) break; + } + + if(ossllib_handle is null) { + ossllib_handle = LoadLibraryW("ssleay32.dll"w.ptr); + oeaylib_handle = ossllib_handle; + } } } From 582f4287f8d4b22570f1716b4ce293dc85a56c37 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Wed, 20 Apr 2022 13:34:05 -0400 Subject: [PATCH 11/24] fixup the pr --- http2.d | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/http2.d b/http2.d index aa4c460..ef002de 100644 --- a/http2.d +++ b/http2.d @@ -3260,8 +3260,8 @@ version(use_openssl) { "libssl.43.dylib", "libssl.35.dylib", "libssl.1.1.dylib", - "/usr/local/opt/openssl/lib/libssl.1.0.0.dylib", "libssl.dylib", + "/usr/local/opt/openssl/lib/libssl.1.0.0.dylib", ]; } else { static immutable string[] ossllibs = [ @@ -3281,31 +3281,33 @@ version(use_openssl) { version(X86_64) { ossllib_handle = LoadLibraryW("libssl-1_1-x64.dll"w.ptr); oeaylib_handle = LoadLibraryW("libcrypto-1_1-x64.dll"w.ptr); - } else { - static immutable wstring[] ossllibs = [ - "libssl-1_1.dll"w, - "libssl32.dll"w, - ]; + } - foreach(lib; ossllibs) { - ossllib_handle = LoadLibraryW(lib.ptr); - if(ossllib_handle !is null) break; - } + static immutable wstring[] ossllibs = [ + "libssl-1_1.dll"w, + "libssl32.dll"w, + ]; - static immutable wstring[] eaylibs = [ - "libcrypto-1_1.dll"w, - "libeay32.dll", - ]; + if(ossllib_handle is null) + foreach(lib; ossllibs) { + ossllib_handle = LoadLibraryW(lib.ptr); + if(ossllib_handle !is null) break; + } - foreach(lib; eaylibs) { - oeaylib_handle = LoadLibraryW(lib.ptr); - if (oeaylib_handle !is null) break; - } + static immutable wstring[] eaylibs = [ + "libcrypto-1_1.dll"w, + "libeay32.dll", + ]; - if(ossllib_handle is null) { - ossllib_handle = LoadLibraryW("ssleay32.dll"w.ptr); - oeaylib_handle = ossllib_handle; - } + if(oeaylib_handle is null) + foreach(lib; eaylibs) { + oeaylib_handle = LoadLibraryW(lib.ptr); + if (oeaylib_handle !is null) break; + } + + if(ossllib_handle is null) { + ossllib_handle = LoadLibraryW("ssleay32.dll"w.ptr); + oeaylib_handle = ossllib_handle; } } From 546edf89aff9e2b026eb7474951fb48b33f7eb32 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Fri, 22 Apr 2022 07:51:00 -0400 Subject: [PATCH 12/24] oops should have committed this ages ago --- apng.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apng.d b/apng.d index dacecce..83c2cb2 100644 --- a/apng.d +++ b/apng.d @@ -728,7 +728,7 @@ ApngAnimation readApng(in ubyte[] data, bool strictApng = false, scope ApngAnima obj.frames[frameNumber - 1].compressedDatastream ~= chunk.payload[offset .. $]; break; default: - obj.handleOtherChunk(chunk); + obj.handleOtherChunkWhenLoading(chunk); } } From 089c37bccef835d4ab28f77b2709206ab6446d6c Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 26 Apr 2022 11:56:09 -0400 Subject: [PATCH 13/24] lol TLS by default STRIKES AGAIN --- http2.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/http2.d b/http2.d index ef002de..00fed28 100644 --- a/http2.d +++ b/http2.d @@ -4698,9 +4698,9 @@ class WebSocket { } } - private bool loopExited; + private __gshared bool loopExited; /++ - + Exits the running [WebSocket.eventLoop]. You can call this from a signal handler or another thread. +/ void exitEventLoop() { loopExited = true; From 777b82fd9b098c0966d83be9839291e6837ca3e3 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Sat, 7 May 2022 07:32:56 -0400 Subject: [PATCH 14/24] percent encode nonascii urls --- http2.d | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/http2.d b/http2.d index 00fed28..ca64241 100644 --- a/http2.d +++ b/http2.d @@ -578,7 +578,36 @@ struct Uri { /// Breaks down a uri string to its components this(string uri) { - reparse(uri); + size_t lastGoodIndex; + foreach(char ch; uri) { + if(ch > 127) { + break; + } + lastGoodIndex++; + } + + string replacement = uri[0 .. lastGoodIndex]; + foreach(char ch; uri[lastGoodIndex .. $]) { + if(ch > 127) { + // need to percent-encode any non-ascii in it + char[3] buffer; + buffer[0] = '%'; + + auto first = ch / 16; + auto second = ch % 16; + first += (first >= 10) ? ('A'-10) : '0'; + second += (second >= 10) ? ('A'-10) : '0'; + + buffer[1] = cast(char) first; + buffer[2] = cast(char) second; + + replacement ~= buffer[]; + } else { + replacement ~= ch; + } + } + + reparse(replacement); } /// Returns `port` if set, otherwise if scheme is https 443, otherwise always 80 From ac64e36c36002e1069ea6fbf1cb50bc50f23b2ba Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Sun, 8 May 2022 10:44:31 -0400 Subject: [PATCH 15/24] affected rows thing --- postgres.d | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/postgres.d b/postgres.d index 593894f..d5f0d47 100644 --- a/postgres.d +++ b/postgres.d @@ -184,6 +184,19 @@ class PostgresResult : ResultSet { return row; } + int affectedRows() { + auto g = PQcmdTuples(res); + if(g is null) + return 0; + int num; + while(*g) { + num *= 10; + num += *g - '0'; + g++; + } + return num; + } + void popFront() { position++; if(position < numRows) @@ -308,6 +321,7 @@ extern(C) { int row_number, int column_number); + char* PQcmdTuples(PGresult *res); } From 1cca75a7f16c0ea2bd1424197cf49597030c45e2 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 10 May 2022 14:32:52 -0400 Subject: [PATCH 16/24] initial find thing --- minigui_addons/webview.d | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/minigui_addons/webview.d b/minigui_addons/webview.d index d1e74cf..53b05de 100644 --- a/minigui_addons/webview.d +++ b/minigui_addons/webview.d @@ -490,6 +490,61 @@ class WebViewWidget_CEF : WebViewWidgetBase { private static WebViewWidget[NativeWindowHandle] mapping; private static WebViewWidget[NativeWindowHandle] browserMapping; + private { + int findingIdent; + string findingText; + bool findingCase; + } + + // might not be stable, webview does this fully integrated + void findText(string text, bool forward = true, bool matchCase = false, bool findNext = false) { + if(browserHandle) { + auto host = browserHandle.get_host(); + + static ident = 0; + auto txt = cef_string_t(text); + host.find(++ident, &txt, forward, matchCase, findNext); + + findingIdent = ident; + findingText = text; + findingCase = matchCase; + } + } + + // ditto + void findPrevious() { + if(findingIdent == 0) + return; + if(!browserHandle) + return; + auto host = browserHandle.get_host(); + auto txt = cef_string_t(findingText); + host.find(findingIdent, &txt, 0, findingCase, 1); + } + + // ditto + void findNext() { + if(findingIdent == 0) + return; + if(!browserHandle) + return; + auto host = browserHandle.get_host(); + auto txt = cef_string_t(findingText); + host.find(findingIdent, &txt, 1, findingCase, 1); + } + + // ditto + void stopFind() { + if(findingIdent == 0) + return; + if(!browserHandle) + return; + auto host = browserHandle.get_host(); + host.stop_finding(1); + + findingIdent = 0; + } + override void refresh() { if(browserHandle) browserHandle.reload(); } override void back() { if(browserHandle) browserHandle.go_back(); } override void forward() { if(browserHandle) browserHandle.go_forward(); } From 641d823dddec9d243860089584efdbfbf5ec66a8 Mon Sep 17 00:00:00 2001 From: 0xEAB Date: Sun, 15 May 2022 01:21:57 +0200 Subject: [PATCH 17/24] improve privilege dropping in cgi --- cgi.d | 75 ++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/cgi.d b/cgi.d index 5fc18af..3a562ed 100644 --- a/cgi.d +++ b/cgi.d @@ -476,6 +476,7 @@ import std.base64; static import std.algorithm; import std.datetime; import std.range; +import std.typecons : Nullable; import std.process; @@ -3510,11 +3511,11 @@ struct RequestServer { foundHost = false; } if(foundUid) { - privDropUserId = to!int(arg); + privilegesDropToUid = to!int(arg); foundUid = false; } if(foundGid) { - privDropGroupId = to!int(arg); + privilegesDropToGid = to!int(arg); foundGid = false; } if(arg == "--listening-host" || arg == "-h" || arg == "/listening-host") @@ -3528,7 +3529,30 @@ struct RequestServer { } } - // FIXME: the privDropUserId/group id need to be set in here instead of global + /// + Nullable!uid_t privilegesDropToUid; + /// + Nullable!gid_t privilegesDropToGid; + + private void dropPrivileges() { + version(Posix) { + import core.sys.posix.unistd; + + if (!privilegesDropToGid.isNull && setgid(privilegesDropToGid.get) != 0) + throw new Exception("Dropping privileges via setgid() failed."); + + if (!privilegesDropToUid.isNull && setuid(privilegesDropToUid.get) != 0) + throw new Exception("Dropping privileges via setuid() failed."); + } + else { + // FIXME: Windows? + //pragma(msg, "Dropping privileges is not implemented for this platform"); + } + + // done, set null + privilegesDropToGid.nullify(); + privilegesDropToUid.nullify(); + } /++ Serves a single HTTP request on this thread, with an embedded server, then stops. Designed for cases like embedded oauth responders @@ -3557,7 +3581,7 @@ struct RequestServer { bool tcp; void delegate() cleanup; - auto socket = startListening(listeningHost, listeningPort, tcp, cleanup, 1); + auto socket = startListening(listeningHost, listeningPort, tcp, cleanup, 1, &dropPrivileges); auto connection = socket.accept(); doThreadHttpConnectionGuts!(CustomCgi, fun, true)(connection); @@ -3680,28 +3704,6 @@ else private __gshared bool globalStopFlag = false; -private int privDropUserId; -private int privDropGroupId; - -// Added Jan 11, 2021 -private void dropPrivs() { - version(Posix) { - import core.sys.posix.unistd; - - auto userId = privDropUserId; - auto groupId = privDropGroupId; - - if((userId != 0 || groupId != 0) && getuid() == 0) { - if(groupId) - setgid(groupId); - if(userId) - setuid(userId); - } - - } - // FIXME: Windows? -} - version(embedded_httpd_processes) void serveEmbeddedHttpdProcesses(alias fun, CustomCgi = Cgi)(RequestServer params) { import core.sys.posix.unistd; @@ -3745,7 +3747,7 @@ void serveEmbeddedHttpdProcesses(alias fun, CustomCgi = Cgi)(RequestServer param close(sock); throw new Exception("listen"); } - dropPrivs(); + params.dropPrivileges(); } version(embedded_httpd_processes_accept_after_fork) {} else { @@ -5153,9 +5155,13 @@ import core.atomic; /** To use this thing: + --- void handler(Socket s) { do something... } - auto manager = new ListeningConnectionManager("127.0.0.1", 80, &handler); + auto manager = new ListeningConnectionManager("127.0.0.1", 80, &handler, &delegateThatDropsPrivileges); manager.listen(); + --- + + The 4th parameter is optional. I suggest you use BufferedInputRange(connection) to handle the input. As a packet comes in, you will get control. You can just continue; though to fetch more. @@ -5355,15 +5361,15 @@ class ListeningConnectionManager { private void dg_handler(Socket s) { fhandler(s); } - this(string host, ushort port, void function(Socket) handler) { + this(string host, ushort port, void function(Socket) handler, void delegate() dropPrivs = null) { fhandler = handler; - this(host, port, &dg_handler); + this(host, port, &dg_handler, dropPrivs); } - this(string host, ushort port, void delegate(Socket) handler) { + this(string host, ushort port, void delegate(Socket) handler, void delegate() dropPrivs = null) { this.handler = handler; - listener = startListening(host, port, tcp, cleanup, 128); + listener = startListening(host, port, tcp, cleanup, 128, dropPrivs); version(cgi_use_fiber) version(cgi_use_fork) listener.blocking = false; @@ -5376,7 +5382,7 @@ class ListeningConnectionManager { void delegate(Socket) handler; } -Socket startListening(string host, ushort port, ref bool tcp, ref void delegate() cleanup, int backQueue) { +Socket startListening(string host, ushort port, ref bool tcp, ref void delegate() cleanup, int backQueue, void delegate() dropPrivs) { Socket listener; if(host.startsWith("unix:")) { version(Posix) { @@ -5424,7 +5430,8 @@ Socket startListening(string host, ushort port, ref bool tcp, ref void delegate( listener.listen(backQueue); - dropPrivs(); + if (dropPrivs !is null) // can be null, backwards compatibility + dropPrivs(); return listener; } From f8099360fbc240bab8e4a201449306301e7c134c Mon Sep 17 00:00:00 2001 From: 0xEAB Date: Sun, 15 May 2022 02:33:27 +0200 Subject: [PATCH 18/24] simplify cgi to treat zero as special uid/gid --- cgi.d | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/cgi.d b/cgi.d index 3a562ed..4178a30 100644 --- a/cgi.d +++ b/cgi.d @@ -476,7 +476,6 @@ import std.base64; static import std.algorithm; import std.datetime; import std.range; -import std.typecons : Nullable; import std.process; @@ -3529,19 +3528,21 @@ struct RequestServer { } } - /// - Nullable!uid_t privilegesDropToUid; - /// - Nullable!gid_t privilegesDropToGid; + /// user (uid) to drop privileges to + /// 0 … do nothing + uid_t privilegesDropToUid = 0; + /// group (gid) to drop privileges to + /// 0 … do nothing + gid_t privilegesDropToGid = 0; private void dropPrivileges() { version(Posix) { import core.sys.posix.unistd; - if (!privilegesDropToGid.isNull && setgid(privilegesDropToGid.get) != 0) + if (privilegesDropToGid != 0 && setgid(privilegesDropToGid) != 0) throw new Exception("Dropping privileges via setgid() failed."); - if (!privilegesDropToUid.isNull && setuid(privilegesDropToUid.get) != 0) + if (privilegesDropToUid != 0 && setuid(privilegesDropToUid) != 0) throw new Exception("Dropping privileges via setuid() failed."); } else { @@ -3549,9 +3550,9 @@ struct RequestServer { //pragma(msg, "Dropping privileges is not implemented for this platform"); } - // done, set null - privilegesDropToGid.nullify(); - privilegesDropToUid.nullify(); + // done, set zero + privilegesDropToGid = 0; + privilegesDropToUid = 0; } /++ From 767519c4def1ae2c491204a5f1a0f3904dbd04cb Mon Sep 17 00:00:00 2001 From: 0xEAB Date: Sun, 15 May 2022 02:39:52 +0200 Subject: [PATCH 19/24] convert to uid_t/gid_t --- cgi.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cgi.d b/cgi.d index 4178a30..000b4a4 100644 --- a/cgi.d +++ b/cgi.d @@ -3510,11 +3510,11 @@ struct RequestServer { foundHost = false; } if(foundUid) { - privilegesDropToUid = to!int(arg); + privilegesDropToUid = to!uid_t(arg); foundUid = false; } if(foundGid) { - privilegesDropToGid = to!int(arg); + privilegesDropToGid = to!gid_t(arg); foundGid = false; } if(arg == "--listening-host" || arg == "-h" || arg == "/listening-host") From 5c7cc81c84ee0c77d6c0ea40c3ab8d5fee4c8e8f Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 17 May 2022 15:46:34 -0400 Subject: [PATCH 20/24] thing for irc --- terminal.d | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/terminal.d b/terminal.d index 1d5f215..b49bfb6 100644 --- a/terminal.d +++ b/terminal.d @@ -7468,6 +7468,16 @@ struct ScrollbackBuffer { scrollbackPosition_++; } + /++ + Adds a line by components without affecting scrollback. + + History: + Added May 17, 2022 + +/ + void addLine(LineComponent[] components...) { + lines ~= Line(components.dup); + } + /++ Scrolling controls. From 74f064f0bb03b2e65cc056c87b6cadc4393e0a57 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Wed, 25 May 2022 10:17:57 -0400 Subject: [PATCH 21/24] issue #328 - empty POST should still have content-length --- http2.d | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/http2.d b/http2.d index ca64241..3e56ecf 100644 --- a/http2.d +++ b/http2.d @@ -1141,13 +1141,18 @@ class HttpRequest { } headers ~= "\r\n"; + bool specSaysRequestAlwaysHasBody = + requestParameters.method == HttpVerb.POST || + requestParameters.method == HttpVerb.PUT || + requestParameters.method == HttpVerb.PATCH; + if(requestParameters.userAgent.length) headers ~= "User-Agent: "~requestParameters.userAgent~"\r\n"; if(requestParameters.contentType.length) headers ~= "Content-Type: "~requestParameters.contentType~"\r\n"; if(requestParameters.authorization.length) headers ~= "Authorization: "~requestParameters.authorization~"\r\n"; - if(requestParameters.bodyData.length) + if(requestParameters.bodyData.length || specSaysRequestAlwaysHasBody) headers ~= "Content-Length: "~to!string(requestParameters.bodyData.length)~"\r\n"; if(requestParameters.acceptGzip) headers ~= "Accept-Encoding: gzip\r\n"; From dc68816960bca0b5d913d53edf32199666aedef5 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Mon, 13 Jun 2022 20:38:09 -0400 Subject: [PATCH 22/24] history search control externally --- terminal.d | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/terminal.d b/terminal.d index b49bfb6..ac0f56d 100644 --- a/terminal.d +++ b/terminal.d @@ -4875,7 +4875,7 @@ class LineGetter { it to `write` or `writeln`, you should return `["write", "writeln"]`. If you offer different tab complete in different places, you still - need to return the whole string. For example, a file competition of + need to return the whole string. For example, a file completion of a second argument, when the user writes `terminal.d term` and you want it to complete to an additional `terminal.d`, you should return `["terminal.d terminal.d"]`; in other words, `candidate ~ completion` @@ -6409,6 +6409,25 @@ class LineGetter { maybePositionCursor(); } + bool isSearchingHistory() { + return supplementalGetter !is null; + } + + /++ + Cancels an in-progress history search immediately, discarding the result, returning + to the normal prompt. + + If the user is not currently searching history (see [isSearchingHistory]), this + function does nothing. + +/ + void cancelHistorySearch() { + if(isSearchingHistory()) { + lastDrawLength = maximumDrawWidth - 1; + supplementalGetter = null; + redraw(); + } + } + /++ for integrating into another event loop you can pass individual events to this and From 7b2e819357600232ad28b3259b3accf207631741 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Mon, 13 Jun 2022 20:38:21 -0400 Subject: [PATCH 23/24] stuff --- cgi.d | 4 ++-- joystick.d | 5 ++++- webview.d | 4 ++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cgi.d b/cgi.d index 000b4a4..d9c693a 100644 --- a/cgi.d +++ b/cgi.d @@ -10799,7 +10799,7 @@ auto serveStaticFile(string urlPrefix, string filename = null, string contentTyp // man 2 sendfile assert(urlPrefix[0] == '/'); if(filename is null) - filename = urlPrefix[1 .. $]; + filename = decodeComponent(urlPrefix[1 .. $]); // FIXME is this actually correct? if(contentType is null) { contentType = contentTypeFromFileExtension(filename); } @@ -10881,7 +10881,7 @@ auto serveStaticFileDirectory(string urlPrefix, string directory = null) { assert(directory[$-1] == '/'); static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) { - auto file = cgi.pathInfo[urlPrefix.length .. $]; + auto file = decodeComponent(cgi.pathInfo[urlPrefix.length .. $]); // FIXME: is this actually correct if(file.indexOf("/") != -1 || file.indexOf("\\") != -1) return false; diff --git a/joystick.d b/joystick.d index f73f1bb..9d183cd 100644 --- a/joystick.d +++ b/joystick.d @@ -199,7 +199,9 @@ version(linux) { break; else assert(0); // , to!string(fd) ~ " " ~ to!string(errno)); } - assert(r == event.sizeof); + if(r != event.sizeof) + throw new Exception("Read something weird off the joystick event fd"); + //import std.stdio; writeln(event); ptrdiff_t player = -1; foreach(i, f; joystickFds) @@ -230,6 +232,7 @@ version(linux) { } if(event.type & JS_EVENT_BUTTON) { joystickState[player].buttons[event.number] = event.value ? 255 : 0; + //writeln(player, " ", event.number, " ", event.value, " ", joystickState[player].buttons[event.number]);//, " != ", event.value ? 255 : 0); } } } diff --git a/webview.d b/webview.d index 8ee11f3..36602f2 100644 --- a/webview.d +++ b/webview.d @@ -109,6 +109,10 @@ struct RC(T) { object = null; } + bool opCast(T:bool)() nothrow { + return inner !is null; + } + void opAssign(T obj) { obj.AddRef(); if(object) From 396dead446c0b800f6491de8ad72eac28f37ff13 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Mon, 13 Jun 2022 21:02:16 -0400 Subject: [PATCH 24/24] omg i forgot to commit --- cgi.d | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cgi.d b/cgi.d index d9c693a..89b33d7 100644 --- a/cgi.d +++ b/cgi.d @@ -3528,6 +3528,11 @@ struct RequestServer { } } + version(Windows) { + private alias uid_t = int; + private alias gid_t = int; + } + /// user (uid) to drop privileges to /// 0 … do nothing uid_t privilegesDropToUid = 0;