This commit is contained in:
2026-03-08 22:49:39 +01:00
parent 5d29d6f3b6
commit c752553d2e
14 changed files with 255 additions and 52 deletions

View File

@@ -14,10 +14,18 @@ Please press this <button id="btn">Button</button> for something to happen.
Some input text: <input type="Text" id="inp1" value="Default input value" /> Some input text: <input type="Text" id="inp1" value="Default input value" />
</p> </p>
<div id="test" class="yellow"> <div id="test" class="yellow">
<p> Hi there!</p> <p> Hi there! <a href="/">Click here to load this page again</a></p>
<p> To <a href="page2.html">page 2</a></p>
</div> </div>
<h1 id="t" class="blue">Ja</h1> <h1 id="t" class="blue">Ja</h1>
Ja Ja
<h1> Nou...</h1> <h1> Nou...</h1>
<div class="image">
<img src="test.jpg" />
</div>
<script>
console.log(document.cookie);
document.cookie = "test-koekje=koekje_in_seconden:" + Date.now();
</script>
</body> </body>
</html> </html>

17
example/page2.html Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Example of racket webview</title>
<link href="/styles.css" rel="stylesheet" />
</head>
<body>
<h1 id="h1">This is Page 2</h1>
<div id="test" class="yellow">
<p> Hi there! <a href="/">Click here to load this main page again</a></p>
</div>
<div class="image">
<img src="test.jpg" />
</div>
</body>
</html>

View File

@@ -20,4 +20,12 @@ h1.blue {
color: red; color: red;
} }
.image {
width: 70%;
}
.image img {
width: 100%;
}

BIN
example/test.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

View File

@@ -18,6 +18,7 @@
rkt-webview-create rkt-webview-create
rkt-webview-close rkt-webview-close
rkt-webview-set-ou-token
rkt-webview-set-url! rkt-webview-set-url!
rkt-webview-set-html! rkt-webview-set-html!
rkt-webview-run-js rkt-webview-run-js
@@ -196,6 +197,10 @@
(define-rktwebview rkt_webview_close (define-rktwebview rkt_webview_close
(_fun _int -> _void)) (_fun _int -> _void))
;void rkt_webview_set_ou_token(rktwebview_t wv, const char *token)
(define-rktwebview rkt_webview_set_ou_token
(_fun _int _string/utf-8 -> _void))
;RKTWEBVIEW_QT_EXPORT result_t rkt_webview_set_url(int wv, const char *url); ;RKTWEBVIEW_QT_EXPORT result_t rkt_webview_set_url(int wv, const char *url);
(define-rktwebview rkt_webview_set_url (define-rktwebview rkt_webview_set_url
(_fun _int _string/utf-8 -> _rkt_result_t)) (_fun _int _string/utf-8 -> _rkt_result_t))
@@ -362,6 +367,10 @@
(enqueue! (rkt-wv-evt-queue handle) 'quit) (enqueue! (rkt-wv-evt-queue handle) 'quit)
#t) #t)
(define (rkt-webview-set-ou-token handle token)
(rkt_webview_set_ou_token (rkt-wv-win handle) token)
#t)
(define (rkt-webview-set-url! wv url) (define (rkt-webview-set-url! wv url)
(rkt_webview_set_url (rkt-wv-win wv) url)) (rkt_webview_set_url (rkt-wv-win wv) url))

View File

@@ -3,9 +3,12 @@
(require "racket-webview-qt.rkt" (require "racket-webview-qt.rkt"
"utils.rkt" "utils.rkt"
"mimetypes.rkt" "mimetypes.rkt"
web-server/servlet racket/async-channel
web-server/servlet-env
web-server/http web-server/http
web-server/servlet-dispatch
web-server/web-server
web-server/servlet-env
(prefix-in c: net/cookies)
net/url net/url
racket/runtime-path racket/runtime-path
racket/file racket/file
@@ -19,6 +22,8 @@
(prefix-in g: gregor) (prefix-in g: gregor)
(prefix-in g: gregor/time) (prefix-in g: gregor/time)
gregor-utils gregor-utils
lru-cache
racket-self-signed-cert
) )
(provide webview-create (provide webview-create
@@ -95,9 +100,6 @@
;; Web server ;; Web server
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define current-servlet-port 8083)
(define current-window-nr 1)
(define-runtime-path js-path "../js") (define-runtime-path js-path "../js")
(define (default-boilerplate-js) (define (default-boilerplate-js)
@@ -106,16 +108,20 @@
(define-struct wv (define-struct wv
([handle #:mutable] ([handle #:mutable]
port [port #:mutable]
[window-nr #:mutable] [window-nr #:mutable]
[file-getter #:mutable] [file-getter #:mutable]
[boilerplate-js #:mutable] [boilerplate-js #:mutable]
[webserver-thread #:mutable]) [webserver-thread #:mutable]
[request-count #:mutable]
[sec-token-cache #:mutable]
[cert-ou-token #:mutable]
)
#:transparent) #:transparent)
(define (process-html wv-handle path out) (define (process-html wv-handle path out)
(let ((html (file->string path)) (let ((html (file->string path))
(boilerplate-js (wv-boilerplate-js wv-handle))) (boilerplate-js ((wv-boilerplate-js wv-handle))))
(set! html (string-replace html "<head>" (set! html (string-replace html "<head>"
(string-append "<head>" "\n" (string-append "<head>" "\n"
"<script>" "\n" "<script>" "\n"
@@ -127,17 +133,70 @@
(let ((content (file->bytes path))) (let ((content (file->bytes path)))
(display content out))) (display content out)))
(define (make-security-token)
(letrec ((f (λ (n)
(if (= 0 n)
""
(string-append
(string (integer->char (+ 97 (random 26))))
(f (- n 1)))))))
(string->symbol (f 20))))
(define (get-security-token req)
(let* ((headers (request-headers/raw req))
(cookie-header (findf (λ (h)
(eq? (string->symbol
(format "~a" (header-field h)))
'Cookie))
headers)))
(if (eq? cookie-header #f)
#f
(let ((cookies (c:cookie-header->alist (header-value cookie-header))))
(displayln (format "Cookies: ~a" cookies))
(let ((sec-token (findf (λ (c)
(eq? (string->symbol
(format "~a" (car c)))
'rkt-webview-token))
cookies)))
(if (eq? sec-token #f)
#f
(string->symbol (format "~a" (cdr sec-token)))))))))
(define (make-sec-header sec-cache)
(let ((tok (make-security-token)))
(lru-add! sec-cache tok)
(displayln (format "new sec-token: ~a" tok))
(make-header #"Set-Cookie"
(string->bytes/utf-8
(format "rkt-webview-token=~a" tok)))
)
)
(define (web-serve wv-handle req) (define (web-serve wv-handle req)
(let* ((path (url->string (request-uri req))) (let* ((path (url->string (request-uri req)))
(file-getter (wv-file-getter wv-handle))) (file-getter (wv-file-getter wv-handle))
(token (get-security-token req))
(sec-cache (wv-sec-token-cache wv-handle))
(cache-empty? (lru-empty? sec-cache))
(token-in-cache? (not (or (eq? token #f)
(eq? (lru-has? sec-cache token) #f))))
)
(if (and (eq? token-in-cache? #f) (not cache-empty?))
(response/output
#:code 401
(λ (out)
#t))
(let* ((file-to-serve (build-path (file-getter path))) (let* ((file-to-serve (build-path (file-getter path)))
(ext-bytes (path-get-extension file-to-serve)) (ext-bytes (path-get-extension file-to-serve))
(ext (if (eq? ext-bytes #f) #f (ext (if (eq? ext-bytes #f) #f
(string->symbol (string-downcase (substring (bytes->string/utf-8 ext-bytes) 1))))) (string->symbol
(string-downcase
(substring (bytes->string/utf-8 ext-bytes) 1)))))
) )
(if (file-exists? file-to-serve) (if (file-exists? file-to-serve)
(response/output (response/output
#:mime-type (string->bytes/utf-8 (mimetype-for-ext ext)) #:mime-type (string->bytes/utf-8 (mimetype-for-ext ext))
#:headers (list (make-sec-header sec-cache))
(λ (out) (λ (out)
(if (or (eq? ext 'html) (eq? ext 'htm)) (if (or (eq? ext 'html) (eq? ext 'htm))
(process-html wv-handle file-to-serve out) (process-html wv-handle file-to-serve out)
@@ -145,25 +204,49 @@
)) ))
(response/output (response/output
#:code 404 #:code 404
#:headers (list (make-sec-header sec-cache))
(λ (out) (λ (out)
(displayln (format "~a not found" path) out))) (displayln (format "~a not found" path) out)))
) )
) )
) )
) )
)
(define (start-web-server h) (define (start-web-server h channel cert)
(if (eq? cert #f)
(thread (λ () (thread (λ ()
(serve/servlet (serve
(λ (req) (web-serve h req)) #:dispatch (dispatch/servlet
(λ (req) (web-serve h req)))
#:listen-ip "127.0.0.1" #:listen-ip "127.0.0.1"
#:port (wv-port h) #:port 0
#:command-line? #t #:confirmation-channel channel
#:servlet-path "" )
#:stateless? #t )
;#:launch-browser #f )
#:servlet-regexp #rx"") (let* ((f1 "c:/tmp/my.crt")
))) (f2 "c:/tmp/my.key")
(fh1 (open-output-file f1 #:exists 'replace))
(fh2 (open-output-file f2 #:exists 'replace)))
(display (certificate cert) fh1)
(display (private-key cert) fh2)
(close-output-port fh1)
(close-output-port fh2)
(thread (λ ()
(serve
#:dispatch (dispatch/servlet
(λ (req) (web-serve h req)))
#:dispatch-server-connect@ (make-ssl-connect@ f1 f2)
#:listen-ip "127.0.0.1"
#:port 0
#:confirmation-channel channel
)
)
)
)
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -271,25 +354,33 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (webview-create file-getter event-callback (define (webview-create file-getter event-callback
#:boilerplate-js [bj (default-boilerplate-js)] #:boilerplate-js [bj (λ () (default-boilerplate-js))]
#:parent [p #f]) #:parent [p #f])
(let* ((h (make-wv #f current-servlet-port -1 file-getter bj #f)) (let* ((h (make-wv #f 0 -1 file-getter bj #f 0 (make-lru 250 #:cmp eq?)
(server (let ((s (start-web-server h))) (symbol->string (make-security-token))))
(cert (generate-self-signed-cert 2048 365 '("127.0.0.1" "localhost")
"NL" "Dijkema"
#:ou (wv-cert-ou-token h)))
(channel (make-async-channel))
(server (let ((s (start-web-server h channel cert)))
(sleep 1) ;;; TODO: Check if web server is up. (sleep 1) ;;; TODO: Check if web server is up.
s)) s))
(port-nr (let ((pn (async-channel-get channel)))
(set-wv-port! h pn)
pn))
(event-processor (λ (wv evt) (event-processor (λ (wv evt)
(event-callback h (util-parse-event evt)))) (event-callback h (util-parse-event evt))))
(ph (if (wv? p) (wv-handle p) #f)) (ph (if (wv? p) (wv-handle p) #f))
(wv (rkt-webview-create ph event-processor)) (wv (rkt-webview-create ph event-processor))
(base-req (format "http://127.0.0.1:~a" (base-req (format "https://127.0.0.1:~a" (wv-port h)))
(wv-port h)))
) )
(set-wv-handle! h wv) (set-wv-handle! h wv)
(set-wv-window-nr! h (rkt-wv-win wv)) (set-wv-window-nr! h (rkt-wv-win wv))
(set-wv-webserver-thread! h server) (set-wv-webserver-thread! h server)
(rkt-webview-set-ou-token (wv-handle h) (wv-cert-ou-token h))
(rkt-webview-set-url! (wv-handle h) base-req) (rkt-webview-set-url! (wv-handle h) base-req)
(set! current-servlet-port (+ current-servlet-port 1)) h)
h)) )
(define/contract (webview-devtools wv) (define/contract (webview-devtools wv)
(-> wv? symbol?) (-> wv? symbol?)
@@ -680,7 +771,8 @@
(define (test) (define (test)
(let* ((cb (λ (handle evt) (let* ((cb (λ (handle evt)
(displayln evt))) (displayln evt)))
(h (webview-create file-getter cb))) (h (webview-create file-getter cb))
)
(webview-set-title! h "This is a test window") (webview-set-title! h "This is a test window")
(webview-resize h 800 600) (webview-resize h 800 600)
(webview-move h 350 220) (webview-move h 350 220)

View File

@@ -25,6 +25,7 @@
#define COMMAND_CHOOSE_DIR 19 #define COMMAND_CHOOSE_DIR 19
#define COMMAND_FILE_OPEN 20 #define COMMAND_FILE_OPEN 20
#define COMMAND_FILE_SAVE 21 #define COMMAND_FILE_SAVE 21
#define COMMAND_SET_OU_TOKEN 22
class Command class Command
{ {

View File

@@ -202,3 +202,9 @@ rkt_js_result_t *rkt_webview_file_save(rktwebview_t w, const char *title, const
rkt_webview_init(); rkt_webview_init();
return handler->rktFileSave(w, title, base_dir, permitted_exts); return handler->rktFileSave(w, title, base_dir, permitted_exts);
} }
void rkt_webview_set_ou_token(rktwebview_t wv, const char *token)
{
rkt_webview_init();
handler->rktSetOUToken(wv, token);
}

View File

@@ -57,6 +57,7 @@ RKTWEBVIEW_QT_EXPORT int rkt_webview_create(rktwebview_t parent, event_cb_t js_e
RKTWEBVIEW_QT_EXPORT void rkt_webview_close(rktwebview_t wv); RKTWEBVIEW_QT_EXPORT void rkt_webview_close(rktwebview_t wv);
RKTWEBVIEW_QT_EXPORT bool rkt_webview_valid(rktwebview_t wv); RKTWEBVIEW_QT_EXPORT bool rkt_webview_valid(rktwebview_t wv);
RKTWEBVIEW_QT_EXPORT result_t rkt_webview_set_title(rktwebview_t wv, const char *title); RKTWEBVIEW_QT_EXPORT result_t rkt_webview_set_title(rktwebview_t wv, const char *title);
RKTWEBVIEW_QT_EXPORT void rkt_webview_set_ou_token(rktwebview_t wv, const char *token);
RKTWEBVIEW_QT_EXPORT result_t rkt_webview_set_url(rktwebview_t wv, const char *url); RKTWEBVIEW_QT_EXPORT result_t rkt_webview_set_url(rktwebview_t wv, const char *url);

View File

@@ -65,6 +65,20 @@ void Rktwebview_qt::processCommand(Command *cmd)
cmd->done = true; cmd->done = true;
} }
break; break;
case COMMAND_SET_OU_TOKEN: {
doEvents();
int wv = cmd->args[0].toInt();
QString ou_token = cmd->args[1].toString();
if (_views.contains(wv)) {
WebviewWindow *w= _views[wv];
w->setOUToken(ou_token);
cmd->result = true;
} else {
cmd->result = false;
}
cmd->done = true;
}
break;
case COMMAND_SET_URL: { case COMMAND_SET_URL: {
doEvents(); doEvents();
int wv = cmd->args[0].toInt(); int wv = cmd->args[0].toInt();
@@ -371,6 +385,18 @@ void Rktwebview_qt::rktWebViewClose(int wv)
while(!c.done) { doEvents(); } while(!c.done) { doEvents(); }
} }
void Rktwebview_qt::rktSetOUToken(rktwebview_t wv, const char *ou_token)
{
Command c(COMMAND_SET_OU_TOKEN);
c.args.push_back(wv);
QString ou_tok(ou_token);
c.args.push_back(ou_tok);
postCommand(&c);
while(!c.done) { doEvents(); }
}
result_t Rktwebview_qt::rktSetUrl(rktwebview_t wv, const char *url) result_t Rktwebview_qt::rktSetUrl(rktwebview_t wv, const char *url)
{ {
Command c(COMMAND_SET_URL); Command c(COMMAND_SET_URL);

View File

@@ -66,6 +66,7 @@ public:
public: public:
int rktWebViewCreate(int parent, event_cb_t js_evt_cb); int rktWebViewCreate(int parent, event_cb_t js_evt_cb);
void rktWebViewClose(int wv); void rktWebViewClose(int wv);
void rktSetOUToken(rktwebview_t wv, const char *ou_token);
void rktQuit(); void rktQuit();
result_t rktOpenDevtools(rktwebview_t wv); result_t rktOpenDevtools(rktwebview_t wv);
@@ -109,7 +110,8 @@ public:
public: public:
Rktwebview_qt(Rktwebview_qt **handler); Rktwebview_qt(Rktwebview_qt **handler);
};
};
#endif // RKTWEBVIEW_QT_H #endif // RKTWEBVIEW_QT_H

View File

@@ -37,6 +37,25 @@ WebviewWindow::WebviewWindow(WebviewWindow *parent)
} }
void WebviewWindow::handleCertificate(const QWebEngineCertificateError &certificateError)
{
QList<QSslCertificate> chain = certificateError.certificateChain();
int i;
for(i = 0; i < chain.size(); i++) {
const QSslCertificate &cert = chain[i];
if (cert.isSelfSigned()) {
QStringList l = cert.issuerInfo(QSslCertificate::OrganizationalUnitName);
if (!l.empty()) {
QString ou = l[0];
if (ou != "" && ou == _ou_token) {
QWebEngineCertificateError &e = const_cast<QWebEngineCertificateError &>(certificateError);
e.acceptCertificate();
}
}
}
}
}
void WebviewWindow::processJsEvents() void WebviewWindow::processJsEvents()
{ {
QWebEnginePage *p = _view->page(); QWebEnginePage *p = _view->page();
@@ -101,6 +120,11 @@ int WebviewWindow::resizeCount()
return _resized; return _resized;
} }
void WebviewWindow::setOUToken(const QString &token)
{
_ou_token = token;
}
void WebviewWindow::addView(WebViewQt *v, Rktwebview_qt *c) void WebviewWindow::addView(WebViewQt *v, Rktwebview_qt *c)
{ {
_container = c; _container = c;
@@ -143,6 +167,7 @@ void WebviewWindow::addView(WebViewQt *v, Rktwebview_qt *c)
connect(page, &QWebEnginePage::loadStarted, this, [this]() { connect(page, &QWebEnginePage::loadStarted, this, [this]() {
_container->onPageLoad(_view->id()); _container->onPageLoad(_view->id());
}); });
connect(page, &QWebEnginePage::certificateError, this, &WebviewWindow::handleCertificate);
} }
WebViewQt *WebviewWindow::view() WebViewQt *WebviewWindow::view()

View File

@@ -3,6 +3,7 @@
#include <QMainWindow> #include <QMainWindow>
#include <QTimer> #include <QTimer>
#include <QWebEngineCertificateError>
class WebViewQt; class WebViewQt;
class Rktwebview_qt; class Rktwebview_qt;
@@ -30,6 +31,11 @@ private:
int _moved; int _moved;
int _resized; int _resized;
QString _ou_token;
private slots:
void handleCertificate(const QWebEngineCertificateError &certificateError);
public slots: public slots:
void processJsEvents(); void processJsEvents();
@@ -42,6 +48,8 @@ public:
int moveCount(); int moveCount();
int resizeCount(); int resizeCount();
void setOUToken(const QString &token);
public: public:
void addView(WebViewQt *v, Rktwebview_qt *c); void addView(WebViewQt *v, Rktwebview_qt *c);
WebViewQt *view(); WebViewQt *view();