Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f56b55708 | |||
| 35aa88199d | |||
| 949af5c092 | |||
| 44caad5ae9 | |||
| fa6ec7695a | |||
| 081209bd2f | |||
| 90b97f7d34 | |||
| fde9c289cc | |||
| 7f0b9d2873 | |||
| 5816661ebf | |||
| 946552f79c | |||
| e812e3cbbe | |||
| 23cecce39d | |||
| d8ee592823 | |||
| 221d2d0e6e | |||
| 3efccc88aa | |||
| 195d55732d | |||
| 91396e71e0 | |||
| 29561c4b71 | |||
| 9446f5992d | |||
| ecafa799d3 | |||
| 3692bbba77 | |||
| b1ed3de161 | |||
| b6fae3ef78 | |||
| 4bb341913f | |||
| 3fd9e266a1 | |||
| d194289740 | |||
| baf570ab65 | |||
| daef7b3ea4 | |||
| de406f1151 | |||
| fb9d35db77 | |||
| a6570f2659 | |||
| a79b61968f | |||
| 9312d6d5ca | |||
| c7c8de32f9 | |||
| b592bd1073 | |||
| 8044107bc7 |
25
test/echo-server.scm
Normal file
25
test/echo-server.scm
Normal file
@@ -0,0 +1,25 @@
|
||||
(import chicken scheme posix)
|
||||
(use spiffy websockets)
|
||||
|
||||
(ping-interval 0)
|
||||
(drop-incoming-pings #f)
|
||||
(propagate-common-errors #f)
|
||||
(max-message-size 20971520)
|
||||
(max-frame-size 20971520)
|
||||
|
||||
(handle-not-found
|
||||
(lambda (path)
|
||||
(with-websocket
|
||||
(lambda ()
|
||||
(let loop ()
|
||||
(receive (data type) (receive-message)
|
||||
(unless (eq? type 'connection-close)
|
||||
(send-message data type)
|
||||
(loop))))))))
|
||||
|
||||
(debug-log (current-output-port))
|
||||
(root-path ".")
|
||||
(server-port 8080)
|
||||
(start-server)
|
||||
|
||||
|
||||
12
test/hello.scm
Normal file
12
test/hello.scm
Normal file
@@ -0,0 +1,12 @@
|
||||
(import chicken scheme)
|
||||
(use spiffy websockets)
|
||||
|
||||
(handle-not-found
|
||||
(lambda (path)
|
||||
(when (string= path "/web-socket")
|
||||
(with-websocket
|
||||
(lambda ()
|
||||
(send-message (string-append "you said: " (receive-message))))))))
|
||||
|
||||
(root-path ".")
|
||||
(start-server port: 8080)
|
||||
13
test/index.html
Normal file
13
test/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<html>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
var ws = new WebSocket("ws://localhost:8080/web-socket");
|
||||
ws.onmessage = function(evt) {
|
||||
alert(evt.data);
|
||||
};
|
||||
ws.onopen = function() {
|
||||
ws.send('Hello!');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
10
test/ws-test.spec
Normal file
10
test/ws-test.spec
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"servers": [
|
||||
{"agent": "AutobahnServer",
|
||||
"url": "ws://localhost:8080/web-socket",
|
||||
"options": {"version": 13}}
|
||||
],
|
||||
"cases": ["1.*", "2.*", "3.*", "4.*", "5.*"],
|
||||
"exclude-cases": [],
|
||||
"exclude-agent-cases": {}
|
||||
}
|
||||
74
utf8-grammar.scm
Normal file
74
utf8-grammar.scm
Normal file
@@ -0,0 +1,74 @@
|
||||
(define (ucs-range->char-set/inclusive lower upper)
|
||||
(ucs-range->char-set lower (add1 upper)))
|
||||
|
||||
(define utf8-tail
|
||||
(in (ucs-range->char-set/inclusive #x80 #xBF)))
|
||||
|
||||
(define utf8-1
|
||||
(satisfies (lambda (c) (or (< (char->integer c) 128)
|
||||
(and (> (char->integer c) 128)
|
||||
(< (char->integer c) 191))))))
|
||||
|
||||
(define utf8-2
|
||||
(sequence
|
||||
(in (ucs-range->char-set/inclusive #xC2 #xDF))
|
||||
utf8-tail))
|
||||
|
||||
(define utf8-3
|
||||
(any-of
|
||||
(sequence
|
||||
(is #\xE0)
|
||||
(in (ucs-range->char-set/inclusive #xA0 #xBF))
|
||||
utf8-tail)
|
||||
(sequence
|
||||
(in (ucs-range->char-set/inclusive #xE1 #xEC))
|
||||
(repeated utf8-tail 2))
|
||||
(sequence
|
||||
(is #\xED)
|
||||
(in (ucs-range->char-set/inclusive #x80 #x9F))
|
||||
utf8-tail)
|
||||
(sequence
|
||||
(in (ucs-range->char-set/inclusive #xEE #xEF))
|
||||
(repeated utf8-tail 2))))
|
||||
|
||||
(define utf8-4
|
||||
(any-of
|
||||
(sequence
|
||||
(is #\xF0)
|
||||
(in (ucs-range->char-set/inclusive #x90 #xBF))
|
||||
(repeated utf8-tail 2))
|
||||
(sequence
|
||||
(in (ucs-range->char-set/inclusive #xF1 #xF3))
|
||||
(repeated utf8-tail 3))
|
||||
(sequence
|
||||
(is #\xF4)
|
||||
(in (ucs-range->char-set/inclusive #x80 #x8F))
|
||||
(repeated utf8-tail 2))))
|
||||
|
||||
(define utf8-char
|
||||
(any-of
|
||||
utf8-1
|
||||
utf8-2
|
||||
utf8-3
|
||||
utf8-4))
|
||||
|
||||
(define utf8-string
|
||||
(followed-by (zero-or-more utf8-char) end-of-input))
|
||||
|
||||
;; (parse utf8-string (->parser-input "Hello-µ@ßöäüàá-UTF-8!!"))
|
||||
;; (parse utf8-char (->parser-input #\a))
|
||||
|
||||
;; (define (valid-utf8? s)
|
||||
;; (let ((len (string-length s)))
|
||||
;; (let loop ((i 0))
|
||||
;; (if (= i len)
|
||||
;; #t
|
||||
;; (let ((r (parse utf8-char (->parser-input (->string (string-ref s i))))))
|
||||
;; (if r
|
||||
;; (loop (+ i (length r)))
|
||||
;; (string-ref s i)))))))
|
||||
;; (valid-utf8? "Hello-µ@ßöäüàá-UTF-8!!")
|
||||
;; (valid-utf8? "Hello")
|
||||
;; (parse utf8-char (->parser-input (->string #\H)))
|
||||
|
||||
;; #\xC0
|
||||
@@ -1,64 +0,0 @@
|
||||
#include "utf8validator.h"
|
||||
|
||||
static const uint8_t UTF8VALIDATOR_DFA[] =
|
||||
{
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
|
||||
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
|
||||
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
|
||||
|
||||
0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
|
||||
0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
|
||||
0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
|
||||
1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
|
||||
1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
|
||||
1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1 // s7..s8
|
||||
};
|
||||
|
||||
#define UTF8_ACCEPT 0
|
||||
#define UTF8_REJECT 1
|
||||
|
||||
void utf8vld_reset (utf8_validator_t* validator) {
|
||||
validator->state = UTF8_ACCEPT;
|
||||
validator->current_index = 0;
|
||||
validator->total_index = 0;
|
||||
validator->is_valid = 1;
|
||||
validator->ends_on_codepoint = 1;
|
||||
}
|
||||
|
||||
void utf8vld_validate (utf8_validator_t* validator, const uint8_t* data, size_t offset, size_t length) {
|
||||
|
||||
int state = validator->state;
|
||||
|
||||
for (size_t i = offset; i < length + offset; ++i) {
|
||||
|
||||
state = UTF8VALIDATOR_DFA[256 + (state << 4) + UTF8VALIDATOR_DFA[data[i]]];
|
||||
|
||||
if (state == UTF8_REJECT)
|
||||
{
|
||||
validator->state = state;
|
||||
validator->current_index = i - offset;
|
||||
validator->total_index += i - offset;
|
||||
validator->is_valid = 0;
|
||||
validator->ends_on_codepoint = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
validator->state = state;
|
||||
validator->current_index = length;
|
||||
validator->total_index += length;
|
||||
validator->is_valid = 1;
|
||||
validator->ends_on_codepoint = validator->state == UTF8_ACCEPT;
|
||||
}
|
||||
|
||||
int utf8_valid(const uint8_t* data, size_t len) {
|
||||
utf8_validator_t validator;
|
||||
utf8vld_reset(&validator);
|
||||
utf8vld_validate(&validator, data, 0, len);
|
||||
return validator.is_valid;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
#ifndef UTF8_VALIDATOR_H
|
||||
#define UTF8_VALIDATOR_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
size_t current_index;
|
||||
size_t total_index;
|
||||
int state;
|
||||
int is_valid;
|
||||
int ends_on_codepoint;
|
||||
} utf8_validator_t;
|
||||
|
||||
extern void utf8vld_reset (utf8_validator_t* validator);
|
||||
|
||||
extern void utf8vld_validate (utf8_validator_t* validator, const uint8_t* data, size_t offset, size_t length);
|
||||
|
||||
extern int utf8_valid(const uint8_t* data, size_t len);
|
||||
|
||||
#endif // UTF8_VALIDATOR_H
|
||||
@@ -4,7 +4,7 @@
|
||||
(synopsis "websockets provides a websocket API.")
|
||||
(license "BSD")
|
||||
(category web)
|
||||
(depends srfi-1 srfi-4 spiffy intarweb uri-common base64 simple-sha1 srfi-18
|
||||
srfi-13 miscmacros mailbox)
|
||||
;(test-depends http-client test server-test regex)
|
||||
(files "websockets.setup" "websockets.meta" "websockets.release-info" "LICENSE"))
|
||||
(author "Thomas Hintz")
|
||||
(depends (spiffy 5.3.1) intarweb uri-common base64 simple-sha1 mailbox comparse)
|
||||
(files "websockets.setup" "websockets.meta" "websockets.release-info" "LICENSE"
|
||||
"utf8-grammar.scm"))
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
;; -*- scheme -*-
|
||||
(repo git "git@bitbucket.org:thomashintz/{egg-name}.git")
|
||||
;(uri targz "https://github.com/mario-goulart/{egg-name}/tarball/{egg-release}")
|
||||
(uri targz "https://bitbucket.org/thomashintz/{egg-name}/get/{egg-release}.tar.gz")
|
||||
|
||||
(release "0.0.1")
|
||||
(release "0.1.0")
|
||||
(release "0.1.1")
|
||||
(release "0.1.2")
|
||||
(release "0.1.3")
|
||||
(release "0.1.4")
|
||||
(release "0.1.5")
|
||||
(release "0.1.6")
|
||||
|
||||
329
websockets.scm
329
websockets.scm
@@ -8,50 +8,50 @@
|
||||
|
||||
; high level API
|
||||
with-websocket with-concurrent-websocket
|
||||
send-message receive-message
|
||||
send-message receive-message current-websocket
|
||||
|
||||
; low level API
|
||||
send-frame read-frame read-frame-payload
|
||||
receive-fragments valid-utf8?
|
||||
control-frame? upgrade-to-websocket
|
||||
current-websocket unmask close-websocket
|
||||
process-fragments
|
||||
;; send-frame read-frame read-frame-payload
|
||||
;; receive-fragments valid-utf8?
|
||||
;; control-frame? upgrade-to-websocket
|
||||
;; current-websocket unmask close-websocket
|
||||
;; process-fragments
|
||||
|
||||
; fragment
|
||||
make-fragment fragment? fragment-payload fragment-length
|
||||
fragment-masked? fragment-masking-key fragment-last?
|
||||
fragment-optype
|
||||
;; ; fragment
|
||||
;; make-fragment fragment? fragment-payload fragment-length
|
||||
;; fragment-masked? fragment-masking-key fragment-last?
|
||||
;; fragment-optype
|
||||
)
|
||||
|
||||
(import chicken scheme data-structures extras ports posix foreign)
|
||||
(use srfi-1 srfi-4 spiffy intarweb uri-common base64 simple-sha1 srfi-18
|
||||
srfi-13 miscmacros mailbox)
|
||||
|
||||
; TODO make sure all C operations check args to prevent overflows
|
||||
(foreign-declare "#include \"utf8validator.c\"")
|
||||
(import chicken scheme data-structures extras ports posix foreign
|
||||
srfi-13 srfi-14 srfi-18)
|
||||
(use srfi-1 srfi-4 spiffy intarweb uri-common base64 simple-sha1
|
||||
mailbox comparse)
|
||||
|
||||
(define-inline (neq? obj1 obj2) (not (eq? obj1 obj2)))
|
||||
|
||||
(define current-websocket (make-parameter #f))
|
||||
(define ping-interval (make-parameter 15))
|
||||
(define close-timeout (make-parameter 5))
|
||||
(define connection-timeout (make-parameter 58))
|
||||
(define connection-timeout (make-parameter 58)) ; a little grace period from 60s
|
||||
(define accept-connection (make-parameter (lambda (origin) #t)))
|
||||
(define drop-incoming-pings (make-parameter #t))
|
||||
(define propagate-common-errors (make-parameter #f))
|
||||
(define access-denied ; TODO test
|
||||
(make-parameter (lambda () (send-status 'forbidden "<h1>Access denied</h1>"))))
|
||||
|
||||
(define max-frame-size (make-parameter 65536)) ; 64KiB
|
||||
(define max-message-size (make-parameter 1048576)) ; 1MiB
|
||||
(define max-frame-size (make-parameter 1048576)) ; 1MiB
|
||||
(define max-message-size
|
||||
(make-parameter 1048576 ; 1MiB
|
||||
(lambda (v)
|
||||
(if (> v 1073741823) ; max int size for unmask/utf8 check
|
||||
(signal (make-property-condition 'out-of-range))
|
||||
v))))
|
||||
|
||||
(define (make-websocket-exception . conditions)
|
||||
(apply make-composite-condition (append `(,(make-property-condition 'websocket))
|
||||
conditions)))
|
||||
|
||||
(define (make-invalid-header-exception type k v)
|
||||
(make-composite-condition (make-websocket-exception
|
||||
(make-property-condition type k v))
|
||||
(make-property-condition 'invalid-header)))
|
||||
|
||||
(define (make-protocol-violation-exception msg)
|
||||
(make-composite-condition (make-property-condition 'websocket)
|
||||
(make-property-condition 'protocol-error 'msg msg)))
|
||||
@@ -74,7 +74,8 @@
|
||||
('connection-close 8)
|
||||
('ping 9)
|
||||
('pong 10)
|
||||
(else (error "bad optype")))) ; TODO
|
||||
(else (signal (make-websocket-exception
|
||||
(make-property-condition 'invalid-optype))))))
|
||||
|
||||
(define (control-frame? optype)
|
||||
(or (eq? optype 'ping) (eq? optype 'pong) (eq? optype 'connection-close)))
|
||||
@@ -102,22 +103,11 @@
|
||||
fragment?
|
||||
(payload fragment-payload)
|
||||
(length fragment-length)
|
||||
(masked fragment-masked?)
|
||||
(masked fragment-masked? set-fragment-masked!)
|
||||
(masking-key fragment-masking-key)
|
||||
(fin fragment-last?)
|
||||
(optype fragment-optype))
|
||||
|
||||
(define (string->bytes str)
|
||||
(let* ((lst (map char->integer (string->list str)))
|
||||
(bv (make-u8vector (length lst))))
|
||||
(let loop ((lst lst)
|
||||
(pos 0))
|
||||
(if (null? lst) bv
|
||||
(begin
|
||||
(u8vector-set! bv pos (car lst))
|
||||
(loop (cdr lst) (+ pos 1)))))))
|
||||
|
||||
|
||||
(define (hex-string->string hexstr)
|
||||
;; convert a string like "a745ff12" to a string
|
||||
(let ((result (make-string (/ (string-length hexstr) 2))))
|
||||
@@ -176,10 +166,12 @@
|
||||
outbound-port)
|
||||
|
||||
(write-string data len outbound-port)
|
||||
(flush-output outbound-port)
|
||||
#t))
|
||||
|
||||
(define (send-message optype #!optional (data "") (ws (current-websocket)))
|
||||
(define (send-message data #!optional (optype 'text) (ws (current-websocket)))
|
||||
;; TODO break up large data into multiple frames?
|
||||
(optype->opcode optype) ; triggers error if invalid
|
||||
(dynamic-wind
|
||||
(lambda () (mutex-lock! (websocket-send-mutex ws)))
|
||||
(lambda () (send-frame ws optype data #t))
|
||||
@@ -191,84 +183,44 @@
|
||||
(u8vector-set! tmaskkey 1 (vector-ref frame-masking-key 1))
|
||||
(u8vector-set! tmaskkey 2 (vector-ref frame-masking-key 2))
|
||||
(u8vector-set! tmaskkey 3 (vector-ref frame-masking-key 3))
|
||||
(define-external wsmaskkey blob (u8vector->blob/shared tmaskkey))
|
||||
|
||||
(define-external wslen int len)
|
||||
|
||||
(define-external wsv scheme-pointer payload)
|
||||
((foreign-lambda* void ()
|
||||
((foreign-lambda* void ((blob wsmaskkey) (size_t wslen) (scheme-pointer wsv))
|
||||
"
|
||||
const unsigned char* maskkey2 = wsmaskkey;
|
||||
const unsigned int kd = *(unsigned int*)maskkey2;
|
||||
const unsigned char* __restrict kb = maskkey2;
|
||||
|
||||
|
||||
for (int i = wslen >> 2; i != 0; --i)
|
||||
size_t i;
|
||||
for (i = wslen >> 2; i != 0; --i)
|
||||
{
|
||||
*((unsigned int*)wsv) ^= kd;
|
||||
wsv += 4;
|
||||
}
|
||||
|
||||
const int rem = wslen & 3;
|
||||
for (int i = 0; i < rem; ++i)
|
||||
const size_t rem = wslen & 3;
|
||||
for (i = 0; i < rem; ++i)
|
||||
{
|
||||
*((unsigned int*)wsv++) ^= kb[i];
|
||||
}
|
||||
"
|
||||
))
|
||||
) (u8vector->blob/shared tmaskkey) len payload)
|
||||
payload)
|
||||
|
||||
(define (unmask fragment)
|
||||
(if (fragment-masked? fragment)
|
||||
(websocket-unmask-frame-payload
|
||||
(fragment-payload fragment)
|
||||
(fragment-length fragment)
|
||||
(fragment-masking-key fragment))
|
||||
(let ((r (websocket-unmask-frame-payload
|
||||
(fragment-payload fragment)
|
||||
(fragment-length fragment)
|
||||
(fragment-masking-key fragment))))
|
||||
(set-fragment-masked! fragment #f)
|
||||
r)
|
||||
(fragment-payload fragment)))
|
||||
|
||||
(define (read-frame-payload inbound-port frame-payload-length)
|
||||
(let ((masked-data (make-string frame-payload-length)))
|
||||
(read-string! frame-payload-length masked-data inbound-port)
|
||||
masked-data)
|
||||
;; (let* ((masked-data (make-string frame-payload-length)))
|
||||
;; (read-string! frame-payload-length masked-data inbound-port)
|
||||
|
||||
;; (define tmaskkey (make-u8vector 4 #f #t #t))
|
||||
;; (u8vector-set! tmaskkey 0 (vector-ref frame-masking-key 0))
|
||||
;; (u8vector-set! tmaskkey 1 (vector-ref frame-masking-key 1))
|
||||
;; (u8vector-set! tmaskkey 2 (vector-ref frame-masking-key 2))
|
||||
;; (u8vector-set! tmaskkey 3 (vector-ref frame-masking-key 3))
|
||||
;; (define-external wsmaskkey blob (u8vector->blob/shared tmaskkey))
|
||||
|
||||
;; (define-external wslen int frame-payload-length)
|
||||
|
||||
;; (define-external wsv scheme-pointer masked-data)
|
||||
|
||||
;; (if frame-masked
|
||||
;; (begin
|
||||
;; ((foreign-lambda* void ()
|
||||
;; "
|
||||
;; const unsigned char* maskkey2 = wsmaskkey;
|
||||
;; const unsigned int kd = *(unsigned int*)maskkey2;
|
||||
;; const unsigned char* __restrict kb = maskkey2;
|
||||
|
||||
|
||||
;; for (int i = wslen >> 2; i != 0; --i)
|
||||
;; {
|
||||
;; *((unsigned int*)wsv) ^= kd;
|
||||
;; wsv += 4;
|
||||
;; }
|
||||
|
||||
;; const int rem = wslen & 3;
|
||||
;; for (int i = 0; i < rem; ++i)
|
||||
;; {
|
||||
;; *((unsigned int*)wsv++) ^= kb[i];
|
||||
;; }
|
||||
;; "
|
||||
;; ))
|
||||
;; masked-data)
|
||||
;; masked-data))
|
||||
)
|
||||
masked-data))
|
||||
|
||||
(define (read-frame total-size ws)
|
||||
(let* ((inbound-port (websocket-inbound-port ws))
|
||||
@@ -322,67 +274,40 @@
|
||||
(read-frame-payload inbound-port frame-payload-length)
|
||||
frame-payload-length frame-masked
|
||||
frame-masking-key frame-fin frame-optype))
|
||||
((eq? frame-optype 'connection-close)
|
||||
((eq? frame-optype 'connection-close) ; TODO, same as above?
|
||||
(make-fragment
|
||||
(read-frame-payload inbound-port frame-payload-length)
|
||||
frame-payload-length frame-masked frame-masking-key
|
||||
frame-fin frame-optype))
|
||||
(else
|
||||
(thread-signal! (websocket-user-thread ws)
|
||||
(make-websocket-exception
|
||||
(make-property-condition 'unhandled-opcode
|
||||
'optype frame-optype)))
|
||||
(signal (make-websocket-exception
|
||||
(make-property-condition 'unhandled-opcode
|
||||
(make-property-condition 'unhandled-optype
|
||||
'optype frame-optype)))))))))))
|
||||
|
||||
(define (valid-utf8-2? s)
|
||||
(define-external str c-string s)
|
||||
(define-external len int (string-length s))
|
||||
(zero?
|
||||
((foreign-lambda* int ()
|
||||
"
|
||||
static const uint8_t utf8d[] = {
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
|
||||
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
|
||||
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
|
||||
0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
|
||||
0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
|
||||
0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
|
||||
1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
|
||||
1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
|
||||
1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
|
||||
};
|
||||
|
||||
uint32_t si;
|
||||
uint32_t *state;
|
||||
si = 0;
|
||||
state = &si;
|
||||
uint32_t type;
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
// type = utf8d[(uint8_t)str[i]];
|
||||
type = utf8d[*((uint8_t*)str)];
|
||||
*state = utf8d[256 + (*state) * 16 + type];
|
||||
|
||||
if (*state != 0) // reject
|
||||
break;
|
||||
}
|
||||
|
||||
C_return(*state);
|
||||
"
|
||||
))
|
||||
))
|
||||
(include "utf8-grammar.scm")
|
||||
|
||||
(define (valid-utf8? s)
|
||||
(let ((len (string-length s)))
|
||||
((foreign-lambda int "utf8_valid" scheme-pointer int)
|
||||
s len)))
|
||||
(or (let ((len (string-length s)))
|
||||
; Try to validate as an ascii string first. Its essentially
|
||||
; free, doesn't generate garbage and is many, many times
|
||||
; faster than the general purpose validator.
|
||||
(= 1
|
||||
((foreign-lambda* int ((size_t ws_utlen) (scheme-pointer ws_uts))
|
||||
"
|
||||
if (ws_utlen > UINT_MAX) { return -1; }
|
||||
|
||||
int i;
|
||||
for (i = ws_utlen; i != 0; --i)
|
||||
{
|
||||
if (*((unsigned char*)ws_uts++) > 127)
|
||||
{
|
||||
C_return(0);
|
||||
}
|
||||
}
|
||||
|
||||
C_return(1);
|
||||
") len s)))
|
||||
(parse utf8-string (->parser-input s))))
|
||||
|
||||
(define (close-code->integer s)
|
||||
(if (string-null? s)
|
||||
@@ -452,7 +377,7 @@ static const uint8_t utf8d[] = {
|
||||
; immediate response
|
||||
((and (eq? optype 'ping) last-frame (<= len 125))
|
||||
(unless (drop-incoming-pings)
|
||||
(send-message 'pong (unmask fragment)))
|
||||
(send-message (unmask fragment) 'pong))
|
||||
(loop fragments first type total-size))
|
||||
|
||||
; protocol violation checks
|
||||
@@ -480,8 +405,13 @@ static const uint8_t utf8d[] = {
|
||||
(define (process-fragments fragments optype #!optional (ws (current-websocket)))
|
||||
(let ((message-body (string-concatenate/shared
|
||||
(reverse (map unmask fragments)))))
|
||||
(when (and (eq? optype 'text)
|
||||
(not (valid-utf8? message-body)))
|
||||
(when (and (or (eq? optype 'text) (eq? optype 'connection-close))
|
||||
(not (valid-utf8?
|
||||
(if (eq? optype 'text)
|
||||
message-body
|
||||
(if (> (string-length message-body) 2)
|
||||
(substring message-body 2)
|
||||
"")))))
|
||||
(set-websocket-state! ws 'error)
|
||||
(signal (make-websocket-exception
|
||||
(make-property-condition
|
||||
@@ -497,6 +427,7 @@ static const uint8_t utf8d[] = {
|
||||
(values #!eof optype)
|
||||
(process-fragments fragments optype)))))
|
||||
|
||||
; TODO does #!optional and #!key work together?
|
||||
(define (close-websocket #!optional (ws (current-websocket))
|
||||
#!key (close-reason 'normal) (data (make-u8vector 0)))
|
||||
(define invalid-close-reason #f)
|
||||
@@ -525,11 +456,15 @@ static const uint8_t utf8d[] = {
|
||||
#t)
|
||||
(let loop ()
|
||||
(receive (data type) (receive-message ws)
|
||||
(unless (eq? type 'connection-close) (loop)))))
|
||||
(if (eq? type 'connection-close)
|
||||
(unless (valid-utf8? data)
|
||||
(set! close-reason 'invalid-data))
|
||||
(loop)))))
|
||||
(begin
|
||||
(send-frame ws 'connection-close
|
||||
(u8vector 3 (close-reason->close-code close-reason))
|
||||
#t)))))))
|
||||
#t))))
|
||||
"close timeout thread")))
|
||||
(thread-start! close-thread)
|
||||
(if (> (close-timeout) 0)
|
||||
(unless (thread-join! close-thread (close-timeout) #f)
|
||||
@@ -538,8 +473,7 @@ static const uint8_t utf8d[] = {
|
||||
;; (make-websocket-exception
|
||||
;; (make-property-condition 'close-timeout)))
|
||||
)
|
||||
(thread-join! close-thread))
|
||||
(log-to (error-log) "closed")))
|
||||
(thread-join! close-thread))))
|
||||
|
||||
|
||||
(define (sha1-sum in-bv)
|
||||
@@ -547,7 +481,6 @@ static const uint8_t utf8d[] = {
|
||||
|
||||
(define (websocket-compute-handshake client-key)
|
||||
(let* ((key-and-magic
|
||||
; TODO generate new, randome, secure key every time
|
||||
(string-append client-key "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
|
||||
(key-and-magic-sha1 (sha1-sum key-and-magic)))
|
||||
(base64-encode key-and-magic-sha1)))
|
||||
@@ -583,21 +516,22 @@ static const uint8_t utf8d[] = {
|
||||
(lambda ()
|
||||
(let loop ()
|
||||
(thread-sleep! (ping-interval))
|
||||
(send-message 'ping "" ws)
|
||||
(loop))))))
|
||||
(when (eq? (websocket-state ws) 'open)
|
||||
(send-message "" 'ping ws)
|
||||
(loop))))
|
||||
"ping thread")))
|
||||
|
||||
; make sure the request meets the spec for websockets
|
||||
(cond ((not (and (eq? (header-value 'connection headers #f) 'upgrade)
|
||||
(cond ((not (and (member 'upgrade (header-values 'connection headers))
|
||||
(string-ci= (car (header-value 'upgrade headers '(""))) "websocket")))
|
||||
(signal (make-invalid-header-exception 'upgrade 'value
|
||||
(header-value 'upgrade headers #f))))
|
||||
(signal (make-websocket-exception
|
||||
(make-property-condition 'missing-upgrade-header))))
|
||||
((not (string= (header-value 'sec-websocket-version headers "") "13"))
|
||||
(signal (make-invalid-header-exception
|
||||
'websocket-version 'version
|
||||
(header-value 'sec-websocket-version headers #f))))
|
||||
(with-headers ; TODO test
|
||||
`((sec-websocket-version "13"))
|
||||
(lambda () (send-status 'upgrade-required))))
|
||||
((not ((accept-connection) (header-value 'origin headers "")))
|
||||
(signal (make-invalid-header-exception 'origin 'value
|
||||
(header-value 'origin headers #f)))))
|
||||
((access-denied))))
|
||||
|
||||
(with-headers
|
||||
`((upgrade ("WebSocket" . #f))
|
||||
@@ -616,14 +550,16 @@ static const uint8_t utf8d[] = {
|
||||
; Add one to attempt to alleviate checking the timestamp
|
||||
; right before when the timeout should happen.
|
||||
(thread-sleep! (+ 1 (connection-timeout)))
|
||||
(if (< (- (time->seconds (current-time))
|
||||
(time->seconds (websocket-last-message-timestamp ws)))
|
||||
(connection-timeout))
|
||||
(loop)
|
||||
(begin (thread-signal! (websocket-user-thread ws)
|
||||
(make-websocket-exception
|
||||
(make-property-condition 'connection-timeout)))
|
||||
(close-websocket ws close-reason: 1001))))))))
|
||||
(when (eq? (websocket-state ws) 'open)
|
||||
(if (< (- (time->seconds (current-time))
|
||||
(time->seconds (websocket-last-message-timestamp ws)))
|
||||
(connection-timeout))
|
||||
(loop)
|
||||
(begin (thread-signal!
|
||||
(websocket-user-thread ws)
|
||||
(make-websocket-exception
|
||||
(make-property-condition 'connection-timeout)))
|
||||
(close-websocket ws close-reason: 'going-away)))))))))
|
||||
|
||||
(when (> (ping-interval) 0)
|
||||
(thread-start! ping-thread))
|
||||
@@ -631,6 +567,15 @@ static const uint8_t utf8d[] = {
|
||||
ws))
|
||||
|
||||
(define (with-websocket proc #!optional (concurrent #f))
|
||||
(define (handle-error close-reason exn)
|
||||
(set-websocket-state! (current-websocket) 'closing)
|
||||
(close-websocket (current-websocket) close-reason: close-reason)
|
||||
(unless (port-closed? (request-port (current-request)))
|
||||
(close-input-port (request-port (current-request))))
|
||||
(unless (port-closed? (response-port (current-response)))
|
||||
(close-output-port (response-port (current-response))))
|
||||
(when (propagate-common-errors)
|
||||
(signal exn)))
|
||||
(parameterize
|
||||
((current-websocket (websocket-accept concurrent)))
|
||||
(condition-case
|
||||
@@ -638,49 +583,11 @@ static const uint8_t utf8d[] = {
|
||||
(close-websocket)
|
||||
(close-input-port (request-port (current-request)))
|
||||
(close-output-port (response-port (current-response))))
|
||||
(exn (websocket protocol-error)
|
||||
(set-websocket-state! (current-websocket) 'closing)
|
||||
(close-websocket (current-websocket) close-reason: 'protocol-error)
|
||||
(unless (port-closed? (request-port (current-request)))
|
||||
(close-input-port (request-port (current-request))))
|
||||
(unless (port-closed? (response-port (current-response)))
|
||||
(close-output-port (response-port (current-response))))
|
||||
(when (propagate-common-errors)
|
||||
(signal exn)))
|
||||
(exn (websocket invalid-data)
|
||||
(set-websocket-state! (current-websocket) 'closing)
|
||||
(close-websocket (current-websocket) close-reason: 'invalid-data)
|
||||
(unless (port-closed? (request-port (current-request)))
|
||||
(close-input-port (request-port (current-request))))
|
||||
(unless (port-closed? (response-port (current-response)))
|
||||
(close-output-port (response-port (current-response))))
|
||||
(when (propagate-common-errors)
|
||||
(signal exn)))
|
||||
(exn (websocket connection-timeout)
|
||||
(set-websocket-state! (current-websocket) 'closing)
|
||||
(close-websocket (current-websocket) close-reason: 'going-away)
|
||||
(unless (port-closed? (request-port (current-request)))
|
||||
(close-input-port (request-port (current-request))))
|
||||
(unless (port-closed? (response-port (current-response)))
|
||||
(close-output-port (response-port (current-response))))
|
||||
(when (propagate-common-errors)
|
||||
(signal exn)))
|
||||
(exn (websocket message-too-large)
|
||||
(set-websocket-state! (current-websocket) 'closing)
|
||||
(close-websocket (current-websocket) close-reason: 'message-too-large)
|
||||
(unless (port-closed? (request-port (current-request)))
|
||||
(close-input-port (request-port (current-request))))
|
||||
(unless (port-closed? (response-port (current-response)))
|
||||
(close-output-port (response-port (current-response))))
|
||||
(when (propagate-common-errors)
|
||||
(signal exn)))
|
||||
(exn ()
|
||||
(close-websocket (current-websocket) close-reason: 1011)
|
||||
(unless (port-closed? (request-port (current-request)))
|
||||
(close-input-port (request-port (current-request))))
|
||||
(unless (port-closed? (response-port (current-response)))
|
||||
(close-output-port (response-port (current-response))))
|
||||
(signal (make-websocket-exception (make-property-condition 'unexpected-error)))))))
|
||||
(exn (websocket protocol-error) (handle-error 'protocol-error exn))
|
||||
(exn (websocket invalid-data) (handle-error 'invalid-data exn))
|
||||
(exn (websocket connection-timeout) (handle-error 'going-away exn))
|
||||
(exn (websocket message-too-large) (handle-error 'message-too-large exn))
|
||||
(exn () (handle-error 'unexpected-error exn)))))
|
||||
|
||||
(define (with-concurrent-websocket proc)
|
||||
(let ((parent-thread (current-thread)))
|
||||
|
||||
@@ -6,4 +6,4 @@
|
||||
|
||||
(install-extension 'websockets
|
||||
'("websockets.so" "websockets.import.so")
|
||||
`((version "0.0.1")))
|
||||
`((version "0.1.6")))
|
||||
|
||||
Reference in New Issue
Block a user