medvednikov

/

vxx2Public
0 commits0 issues4 pull requests0 contributorsDiscussionsProjectsCI

feat(net, veb): add OpenSSL-based HTTPS server support #7

Openguweigangwants to mergepr/27480intomaster· last Jun 17
17 files changed+887-37
vlib/net/mbedtls/mbedtls_ssl_listener_test.vnew file+16-0

@@ -0,0 +1,16 @@

1+module main

2+

3+import net.mbedtls

4+import os

5+

6+fn test_mbedtls_ssl_listener_preserves_ipv6_literal_by_default() ! {

7+ $if macos || linux {

8+ cert_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.crt')

9+ key_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.key')

10+ mut listener := mbedtls.new_ssl_listener('[::1]:0', mbedtls.SSLConnectConfig{

11+ cert: cert_path

12+ cert_key: key_path

13+ })!

14+ listener.shutdown()!

15+ }

16+}

vlib/net/mbedtls/ssl_connection.c.v+30-19

@@ -165,8 +165,9 @@ pub mut:

165165

166166// SSLListener listens on a TCP port and accepts connection secured with TLS

167167pub struct SSLListener {

168- saddr string

169- config SSLConnectConfig

168+ saddr string

169+ config SSLConnectConfig

170+ options SSLListenerOptions

170171mut:

171172 server_fd C.mbedtls_net_context

172173 ssl C.mbedtls_ssl_context

@@ -183,11 +184,19 @@ mut:

183184 // duration time.Duration

184185}

185186

186-// create a new SSLListener binding to `saddr`

187-pub fn new_ssl_listener(saddr string, config SSLConnectConfig) !&SSLListener {

187+// SSLListenerOptions configures the TCP listener used by an SSLListener.

188+@[params]

189+pub struct SSLListenerOptions {

190+pub:

191+ family net.AddrFamily = .unspec

192+}

193+

194+// new_ssl_listener creates a new SSLListener binding to `saddr`.

195+pub fn new_ssl_listener(saddr string, config SSLConnectConfig, options SSLListenerOptions) !&SSLListener {

188196 mut listener := &SSLListener{

189- saddr: saddr

190- config: config

197+ saddr: saddr

198+ config: config

199+ options: options

191200 }

192201 listener.init()!

193202 listener.opened = true

@@ -216,13 +225,23 @@ pub fn (mut l SSLListener) shutdown() ! {

216225 }

217226}

218227

228+fn ssl_listener_family(saddr string, family net.AddrFamily) net.AddrFamily {

229+ if family != .unspec {

230+ return family

231+ }

232+ address, _ := net.split_address(saddr) or { return .ip }

233+ if address == '::' || address.contains(':') {

234+ return .ip6

235+ }

236+ return .ip

237+}

238+

219239// internal function to init and bind the listener

220240fn (mut l SSLListener) init() ! {

221241 $if trace_ssl ? {

222242 eprintln(@METHOD)

223243 }

224244

225- lhost, lport := net.split_address(l.saddr)!

226245 if l.config.cert == '' || l.config.cert_key == '' {

227246 return error('net.mbedtls SSLListener.init, no certificate or key provided')

228247 }

@@ -259,18 +278,10 @@ fn (mut l SSLListener) init() ! {

259278 C.mbedtls_ssl_conf_authmode(&l.conf, C.MBEDTLS_SSL_VERIFY_REQUIRED)

260279 }

261280

262- mut bind_ip := unsafe { nil }

263- if lhost != '' {

264- bind_ip = voidptr(lhost.str)

265- }

266- bind_port := lport.str()

267-

268- ret = C.mbedtls_net_bind(&l.server_fd, bind_ip, voidptr(bind_port.str), C.MBEDTLS_NET_PROTO_TCP)

269-

270- if ret != 0 {

271- return error_with_code("net.mbedtls SSLListener.init, mbedtls_net_bind can't bind to ${l.saddr} error ret: ${ret}",

272- ret)

273- }

281+ tcp_listener := net.listen_tcp(ssl_listener_family(l.saddr, l.options.family), l.saddr, net.ListenOptions{

282+ backlog: C.MBEDTLS_NET_LISTEN_BACKLOG

283+ }) or { return error('net.mbedtls SSLListener.init, listen_tcp failed for ${l.saddr}: ${err}') }

284+ l.server_fd.fd = tcp_listener.sock.handle

274285

275286 ret = C.mbedtls_ssl_config_defaults(&l.conf, C.MBEDTLS_SSL_IS_SERVER,

276287 C.MBEDTLS_SSL_TRANSPORT_STREAM, C.MBEDTLS_SSL_PRESET_DEFAULT)

vlib/net/openssl/openssl.c.v+22-0

@@ -103,14 +103,26 @@ fn C.SSL_CTX_set_options(ctx &C.SSL_CTX, options i32)

103103

104104fn C.SSL_CTX_set_verify_depth(s &C.SSL_CTX, depth i32)

105105

106+fn C.SSL_CTX_set_verify(ctx &C.SSL_CTX, mode i32, callback voidptr)

107+

106108fn C.SSL_CTX_load_verify_locations(ctx &C.SSL_CTX, const_file &char, ca_path &char) i32

107109

108110fn C.SSL_CTX_free(ctx &C.SSL_CTX)

109111

110112fn C.SSL_CTX_use_certificate_file(ctx &C.SSL_CTX, const_file &char, file_type i32) i32

111113

114+fn C.SSL_CTX_use_certificate_chain_file(ctx &C.SSL_CTX, const_file &char) i32

115+

112116fn C.SSL_CTX_use_PrivateKey_file(ctx &C.SSL_CTX, const_file &char, file_type i32) i32

113117

118+fn C.v_net_openssl_SSL_CTX_use_certificate_chain_memory(ctx &C.SSL_CTX, data &u8, len usize) i32

119+

120+fn C.v_net_openssl_SSL_CTX_extra_chain_certs_count(ctx &C.SSL_CTX) int

121+

122+fn C.v_net_openssl_SSL_CTX_use_PrivateKey_memory(ctx &C.SSL_CTX, data &u8, len usize) i32

123+

124+fn C.v_net_openssl_SSL_CTX_load_verify_memory(ctx &C.SSL_CTX, data &u8, len usize) i32

125+

114126fn C.SSL_new(&C.SSL_CTX) &C.SSL

115127

116128fn C.SSL_set_fd(ssl &C.SSL, fd i32) i32

@@ -147,6 +159,8 @@ fn C.v_net_openssl_set_alpn_protos(ssl &C.SSL, protos &u8, protos_len u32) i32

147159

148160fn C.v_net_openssl_get0_alpn_selected(ssl &C.SSL, data voidptr, len &u32)

149161

162+fn C.v_net_openssl_SSL_CTX_set_alpn_select_protos(ctx &C.SSL_CTX, protos &u8, protos_len u32) i32

163+

150164fn C.SSL_shutdown(&C.SSL) i32

151165

152166fn C.SSL_free(&C.SSL)

@@ -159,8 +173,16 @@ fn C.SSLv23_client_method() &C.SSL_METHOD

159173

160174fn C.TLS_method() voidptr

161175

176+fn C.v_net_openssl_TLS_server_method() &C.SSL_METHOD

177+

162178fn C.TLSv1_2_method() voidptr

163179

180+fn C.SSL_CTX_check_private_key(ctx &C.SSL_CTX) i32

181+

182+fn C.SSL_accept(ssl &C.SSL) i32

183+

184+fn C.ERR_print_errors_fp(fp voidptr)

185+

164186fn C.v_net_openssl_init_ssl() i32

165187

166188fn init() {

vlib/net/openssl/openssl_compat.h+167-0

@@ -1,3 +1,9 @@

1+#include <limits.h>

2+#include <stdlib.h>

3+#include <string.h>

4+#include <openssl/pem.h>

5+#include <openssl/x509_vfy.h>

6+

17// Match the init API to the OpenSSL headers that are actually available.

28#if defined(LIBRESSL_VERSION_NUMBER) || !defined(OPENSSL_VERSION_NUMBER) \

39 || OPENSSL_VERSION_NUMBER < 0x10100000L

@@ -11,6 +17,91 @@ static int v_net_openssl_init_ssl(void) {

1117}

1218#endif

1319

20+static int v_net_openssl_SSL_CTX_use_certificate_chain_memory(SSL_CTX *ctx,

21+ const unsigned char *data, size_t len) {

22+ if (len > INT_MAX) {

23+ return 0;

24+ }

25+ BIO *bio = BIO_new_mem_buf((const void *)data, (int)len);

26+ if (bio == NULL) {

27+ return 0;

28+ }

29+ X509 *cert = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);

30+ if (cert == NULL) {

31+ BIO_free(bio);

32+ return 0;

33+ }

34+ int result = SSL_CTX_use_certificate(ctx, cert);

35+ X509_free(cert);

36+ if (result != 1) {

37+ BIO_free(bio);

38+ return 0;

39+ }

40+ while ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) {

41+ // SSL_CTX_add_extra_chain_cert takes ownership of cert on success.

42+ if (SSL_CTX_add_extra_chain_cert(ctx, cert) != 1) {

43+ X509_free(cert);

44+ BIO_free(bio);

45+ return 0;

46+ }

47+ }

48+ // Reaching the end of a PEM stream leaves PEM_R_NO_START_LINE queued.

49+ ERR_clear_error();

50+ BIO_free(bio);

51+ return 1;

52+}

53+

54+static int v_net_openssl_SSL_CTX_extra_chain_certs_count(SSL_CTX *ctx) {

55+ STACK_OF(X509) *chain = NULL;

56+ SSL_CTX_get_extra_chain_certs(ctx, &chain);

57+ return chain == NULL ? 0 : sk_X509_num(chain);

58+}

59+

60+static int v_net_openssl_SSL_CTX_use_PrivateKey_memory(SSL_CTX *ctx,

61+ const unsigned char *data, size_t len) {

62+ if (len > INT_MAX) {

63+ return 0;

64+ }

65+ BIO *bio = BIO_new_mem_buf((const void *)data, (int)len);

66+ if (bio == NULL) {

67+ return 0;

68+ }

69+ EVP_PKEY *key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);

70+ BIO_free(bio);

71+ if (key == NULL) {

72+ return 0;

73+ }

74+ int result = SSL_CTX_use_PrivateKey(ctx, key);

75+ EVP_PKEY_free(key);

76+ return result;

77+}

78+

79+static int v_net_openssl_SSL_CTX_load_verify_memory(SSL_CTX *ctx,

80+ const unsigned char *data, size_t len) {

81+ if (len > INT_MAX) {

82+ return 0;

83+ }

84+ BIO *bio = BIO_new_mem_buf((const void *)data, (int)len);

85+ if (bio == NULL) {

86+ return 0;

87+ }

88+ X509_STORE *store = SSL_CTX_get_cert_store(ctx);

89+ X509 *cert = NULL;

90+ int loaded = 0;

91+ while ((cert = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL)) != NULL) {

92+ if (X509_STORE_add_cert(store, cert) != 1) {

93+ X509_free(cert);

94+ BIO_free(bio);

95+ return 0;

96+ }

97+ X509_free(cert);

98+ loaded++;

99+ }

100+ ERR_clear_error();

101+ BIO_free(bio);

102+ return loaded > 0 ? 1 : 0;

103+}

104+

14105// SSL_get1_peer_certificate is only available in OpenSSL 3.x.

15106#if defined(LIBRESSL_VERSION_NUMBER) || !defined(OPENSSL_VERSION_NUMBER) \

16107 || OPENSSL_VERSION_NUMBER < 0x30000000L

@@ -40,7 +131,70 @@ static void v_net_openssl_get0_alpn_selected(SSL *ssl, const unsigned char **dat

40131 *data = NULL;

41132 *len = 0;

42133}

134+static int v_net_openssl_SSL_CTX_set_alpn_select_protos(SSL_CTX *ctx,

135+ const unsigned char *protos, unsigned int protos_len) {

136+ (void)ctx;

137+ (void)protos;

138+ (void)protos_len;

139+ return -1;

140+}

43141#else

142+typedef struct {

143+ unsigned char *protos;

144+ unsigned int protos_len;

145+} v_net_openssl_alpn_select_state;

146+

147+static void v_net_openssl_free_alpn_select_state(void *parent, void *ptr,

148+ CRYPTO_EX_DATA *ad, int idx, long argl, void *argp) {

149+ (void)parent;

150+ (void)ad;

151+ (void)idx;

152+ (void)argl;

153+ (void)argp;

154+ v_net_openssl_alpn_select_state *selection =

155+ (v_net_openssl_alpn_select_state *)ptr;

156+ if (selection != NULL) {

157+ free(selection->protos);

158+ free(selection);

159+ }

160+}

161+

162+static int v_net_openssl_alpn_select_cb(SSL *ssl, const unsigned char **out,

163+ unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) {

164+ (void)ssl;

165+ v_net_openssl_alpn_select_state *state = (v_net_openssl_alpn_select_state *)arg;

166+ if (SSL_select_next_proto((unsigned char **)out, outlen, state->protos,

167+ state->protos_len, in, inlen) == OPENSSL_NPN_NEGOTIATED) {

168+ return SSL_TLSEXT_ERR_OK;

169+ }

170+ return SSL_TLSEXT_ERR_NOACK;

171+}

172+

173+static int v_net_openssl_SSL_CTX_set_alpn_select_protos(SSL_CTX *ctx,

174+ const unsigned char *protos, unsigned int protos_len) {

175+ v_net_openssl_alpn_select_state *selection =

176+ (v_net_openssl_alpn_select_state *)malloc(sizeof(*selection));

177+ if (selection == NULL) {

178+ return -1;

179+ }

180+ selection->protos = (unsigned char *)malloc(protos_len);

181+ if (selection->protos == NULL) {

182+ free(selection);

183+ return -1;

184+ }

185+ memcpy(selection->protos, protos, protos_len);

186+ selection->protos_len = protos_len;

187+ int state_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,

188+ v_net_openssl_free_alpn_select_state);

189+ if (state_index < 0 || SSL_CTX_set_ex_data(ctx, state_index, selection) != 1) {

190+ free(selection->protos);

191+ free(selection);

192+ return -1;

193+ }

194+ SSL_CTX_set_alpn_select_cb(ctx, v_net_openssl_alpn_select_cb, selection);

195+ return 0;

196+}

197+

44198static int v_net_openssl_set_alpn_protos(SSL *ssl, const unsigned char *protos, unsigned int protos_len) {

45199 return SSL_set_alpn_protos(ssl, protos, protos_len);

46200}

@@ -58,3 +212,16 @@ static void v_net_openssl_get0_alpn_selected(SSL *ssl, const unsigned char **dat

58212#ifndef SSL_ERROR_WANT_ASYNC_JOB

59213#define SSL_ERROR_WANT_ASYNC_JOB 10

60214#endif

215+

216+// TLS_server_method() is only available in OpenSSL 1.1.0 and newer.

217+// On older OpenSSL/LibreSSL, fall back to SSLv23_server_method().

218+#if defined(LIBRESSL_VERSION_NUMBER) || !defined(OPENSSL_VERSION_NUMBER) \

219+ || OPENSSL_VERSION_NUMBER < 0x10100000L

220+static const SSL_METHOD *v_net_openssl_TLS_server_method(void) {

221+ return SSLv23_server_method();

222+}

223+#else

224+static const SSL_METHOD *v_net_openssl_TLS_server_method(void) {

225+ return TLS_server_method();

226+}

227+#endif

vlib/net/openssl/openssl_sslconn_shutdown_does_not_panic_test.vnew file+60-0

@@ -0,0 +1,60 @@

1+// vtest build: present_openssl? && !(windows && tinyc)

2+import time

3+import context

4+import net.openssl

5+

6+fn server() ! {

7+ cfg := openssl.SSLConnectConfig{

8+ cert: '-----BEGIN CERTIFICATE-----\nMIIEOTCCAyECFG64Q2g46jZb3kRbDOJWX/BwjSp6MA0GCSqGSIb3DQEBCwUAMEUx\nCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl\ncm5ldCBXaWRnaXRzIFB0eSBMdGQwIBcNMjMwODAyMTcyOTQyWhgPMjA1MDEyMTcx\nNzI5NDJaMGsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYD\nVQQHDAtMb3MgQW5nZWxlczEdMBsGA1UECgwUQ2F0YWx5c3QgRGV2ZWxvcG1lbnQx\nEjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC\nggIBALqAI4fqUi+QBVWcsXglouLdOML5+w0+1hSR1KdO0Q5XPdQAs/yYWJ+KUkDw\nG++rfy9DUPq7FNRBVurXQkcAtn6gXdllGUSjwUiDo/N4mMOyS/2sufBuaeww7jVi\nrppH+zwP1tUnjRd6khl6bi1Ian9VSzr3Iy9CkXIg1GU4CPXkOydLeoQfepXxWoK1\nOUNwT3VKC/stAfY3j/NIIeiJYkyuRGFCkxn/BUjN+AsXiTugRcYKEFHdIPkOuCXp\nYbhf+lLsczpxCs3rdZG9b/N6mEDCzXTmeHkmsjdPTf+1k5DZZvKzVBBrgdxCgBb7\n5RwjF5v9WmnIc33wWgfJC6FaUzj9NYxYUbPHD+jTz0rJB/jj4u/xJlM/e5NRmXdW\n70pOMKXtWjRSolLOFIPKLY1qs3KMTAZxKKWPDDF7WlMJxMRt7nnnks5yw43Nog4C\njDLk1ZgETnPpLgo3jbmJdIv+OHKTJrBlVvDq7VTyixCoS5G8KoOmyQJhaXG6NwE2\niVhH5JIKgzgCfetfDsnjxqJ/qtrFXPa8FF2TsomD0NK/GZmIcs+9OeVB75Jn5uhF\nfLHScpiTbuu5w3P/LI/MqihLRB6RRNnRzPH8fIg5bYC9b770ta/8GcFRuYE8t+UR\nGtqXJoIKixbDlqV54kal8FQzYzhETf9+NM6Kb/lKEfG/pslvAgMBAAEwDQYJKoZI\nhvcNAQELBQADggEBALI3uNiNO0QE1brA3QYFK+d9ZroB72NrJ0UNkzYHDg2Fc6xg\n4aVVfaxY08+TmKc0JlMOW+pUxeCW/+UBSngdQiR9EE9xm0k0XIrAsy9RXxRvEtPu\nM1VI2h7ayp1Y2BrnQinevTSgtqLRyS1VbOFRl1FiyVvinw2I0KsDdAMNevAPXcOa\nQ8pUgUq6f56DkhocQaj+hxD/uV8HryNxuoSXnPhvfTN3z4YRGzsaWevJ9EYJliOM\n+XugcqfFJ+W7/QCEcAHCL+Bw6OydG5NFORr3p57PXjjcL/uKmxPBrWg2Bz6uT4uR\nMhj0zttiFHLAt9jGfyk6W57UNUja1e1ggftJJhs=\n-----END CERTIFICATE-----\n'

9+ cert_key: '-----BEGIN RSA PRIVATE KEY-----\n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\n-----END RSA PRIVATE KEY-----\n'

10+ validate: false

11+ in_memory_verification: true

12+ }

13+ mut srv := openssl.new_ssl_listener('127.0.0.1:64445', cfg) or {

14+ eprintln('Listen: ${err}')

15+ return err

16+ }

17+ eprintln('[+] Listening')

18+ mut cli := srv.accept() or {

19+ eprintln('Accept: ${err}')

20+ return err

21+ }

22+ eprintln('[+] Accepted connection')

23+ cli.shutdown()!

24+}

25+

26+// 用 _test.v 内的 test_ 函数

27+fn test_shutdown_does_not_panic() {

28+ _ := spawn server()

29+ time.sleep(1 * time.second)

30+ mut client := openssl.new_ssl_conn(validate: false)!

31+ client.dial('127.0.0.1', 64445) or {

32+ eprintln('Connect: ${err}')

33+ return

34+ }

35+ eprintln('[+] Connected')

36+ time.sleep(1 * time.second)

37+ mut background := context.background()

38+ mut ctx, cancel := context.with_timeout(mut background, 2 * time.second)

39+ spawn fn () {

40+ mut i := 0

41+ for {

42+ i++

43+ print('\r${i}...')

44+ flush_stdout()

45+ time.sleep(1 * time.second)

46+ }

47+ }()

48+ mut done := ctx.done()

49+ for {

50+ select {

51+ _ := <-done {

52+ break

53+ }

54+ }

55+ }

56+

57+ eprintln('\nTimeout without panic - OK')

58+

59+ assert true

60+}

vlib/net/openssl/ssl_connection.c.v+11-1

@@ -70,7 +70,17 @@ fn ssl_remaining_timeout(deadline time.Time) time.Duration {

7070 if deadline.unix() == 0 {

7171 return net.infinite_timeout

7272 }

73- return deadline - time.now()

73+ remaining := deadline - time.now()

74+ if remaining <= 0 {

75+ // The finite deadline has already expired. Return a minimal positive

76+ // duration so select()/wait_for() perform an immediate, zero-length wait

77+ // and report net.err_timed_out, instead of misreading a non-positive

78+ // remaining time as net.infinite_timeout (== "wait forever"). Without

79+ // this, a peer that stalls mid-handshake/read could park the caller

80+ // indefinitely despite a finite timeout being configured.

81+ return time.nanosecond

82+ }

83+ return remaining

7484}

7585

7686// close closes the ssl connection and does cleanup

vlib/net/openssl/ssl_listener.c.vnew file+200-0

@@ -0,0 +1,200 @@

1+module openssl

2+

3+import net

4+

5+// SSLListener is the SSL listener implementation for OpenSSL.

6+pub struct SSLListener {

7+ saddr string

8+ config SSLConnectConfig

9+ options SSLListenerOptions

10+mut:

11+ tcp_listener &net.TcpListener = unsafe { nil }

12+ sslctx &C.SSL_CTX = unsafe { nil }

13+}

14+

15+// SSLListenerOptions configures the TCP listener used by an SSLListener.

16+@[params]

17+pub struct SSLListenerOptions {

18+pub:

19+ family net.AddrFamily = .ip

20+}

21+

22+// new_ssl_listener creates a new SSLListener binding to `saddr` with `config`.

23+pub fn new_ssl_listener(saddr string, config SSLConnectConfig, options SSLListenerOptions) !&SSLListener {

24+ mut listener := &SSLListener{

25+ saddr: saddr

26+ config: config

27+ options: options

28+ }

29+ listener.init()!

30+ return listener

31+}

32+

33+fn (mut l SSLListener) init() ! {

34+ if l.config.cert == '' || l.config.cert_key == '' {

35+ return error('net.openssl SSLListener.init, no certificate or key provided')

36+ }

37+ if l.config.validate && l.config.verify == '' {

38+ return error('net.openssl SSLListener.init, no root CA provided')

39+ }

40+

41+ l.tcp_listener = net.listen_tcp(l.options.family, l.saddr, net.ListenOptions{})!

42+

43+ l.sslctx = unsafe { C.SSL_CTX_new(C.v_net_openssl_TLS_server_method()) }

44+ if l.sslctx == 0 {

45+ l.tcp_listener.close() or {}

46+ return error('net.openssl SSLListener.init, could not get ssl context')

47+ }

48+

49+ if l.config.in_memory_verification {

50+ mut res := C.v_net_openssl_SSL_CTX_use_certificate_chain_memory(l.sslctx,

51+ l.config.cert.str, usize(l.config.cert.len))

52+ if res != 1 {

53+ l.shutdown() or {}

54+ return error('net.openssl SSLListener.init, could not load certificate from memory')

55+ }

56+ res = C.v_net_openssl_SSL_CTX_use_PrivateKey_memory(l.sslctx, l.config.cert_key.str,

57+ usize(l.config.cert_key.len))

58+ if res != 1 {

59+ l.shutdown() or {}

60+ return error('net.openssl SSLListener.init, could not load private key from memory')

61+ }

62+ if l.config.validate {

63+ res = C.v_net_openssl_SSL_CTX_load_verify_memory(l.sslctx, l.config.verify.str,

64+ usize(l.config.verify.len))

65+ if res != 1 {

66+ l.shutdown() or {}

67+ return error('net.openssl SSLListener.init, could not load root CA from memory')

68+ }

69+ }

70+ } else {

71+ mut res := C.SSL_CTX_use_certificate_chain_file(voidptr(l.sslctx), &char(l.config.cert.str))

72+ if res != 1 {

73+ C.ERR_print_errors_fp(C.stderr)

74+ l.shutdown() or {}

75+ return error('net.openssl SSLListener.init, SSL_CTX_use_certificate_chain_file failed')

76+ }

77+ res = C.SSL_CTX_use_PrivateKey_file(voidptr(l.sslctx), &char(l.config.cert_key.str),

78+ C.SSL_FILETYPE_PEM)

79+ if res != 1 {

80+ l.shutdown() or {}

81+ return error('net.openssl SSLListener.init, SSL_CTX_use_PrivateKey_file failed')

82+ }

83+ }

84+

85+ mut res := C.SSL_CTX_check_private_key(voidptr(l.sslctx))

86+ if res != 1 {

87+ l.shutdown() or {}

88+ return error('net.openssl SSLListener.init, SSL_CTX_check_private_key failed')

89+ }

90+

91+ if l.config.validate {

92+ if !l.config.in_memory_verification {

93+ res = C.SSL_CTX_load_verify_locations(voidptr(l.sslctx), &char(l.config.verify.str),

94+ unsafe { nil })

95+ if res != 1 {

96+ l.shutdown() or {}

97+ return error('net.openssl SSLListener.init, SSL_CTX_load_verify_locations failed')

98+ }

99+ }

100+ C.SSL_CTX_set_verify(voidptr(l.sslctx),

101+ C.SSL_VERIFY_PEER | C.SSL_VERIFY_FAIL_IF_NO_PEER_CERT, unsafe { nil })

102+ }

103+

104+ if l.config.alpn_protocols.len > 0 {

105+ mut wire := []u8{cap: 64}

106+ for proto in l.config.alpn_protocols {

107+ if proto.len == 0 || proto.len > 255 {

108+ l.shutdown() or {}

109+ return error('net.openssl SSLListener.init, invalid ALPN protocol "${proto}"')

110+ }

111+ wire << u8(proto.len)

112+ wire << proto.bytes()

113+ }

114+ if C.v_net_openssl_SSL_CTX_set_alpn_select_protos(l.sslctx, wire.data, u32(wire.len)) != 0 {

115+ l.shutdown() or {}

116+ return error('net.openssl SSLListener.init, failed to configure ALPN protocols (requires OpenSSL >= 1.0.2)')

117+ }

118+ }

119+}

120+

121+// accept accepts a new TCP connection and performs the SSL handshake.

122+pub fn (mut l SSLListener) accept() !&SSLConn {

123+ mut conn := l.accept_without_handshake()!

124+ conn.accept_handshake() or {

125+ conn.shutdown() or {}

126+ return err

127+ }

128+ return conn

129+}

130+

131+// accept_without_handshake accepts a new TCP connection but does not perform the handshake yet.

132+pub fn (mut l SSLListener) accept_without_handshake() !&SSLConn {

133+ mut tcp_conn := l.tcp_listener.accept()!

134+

135+ ssl := unsafe { &C.SSL(C.SSL_new(l.sslctx)) }

136+ if ssl == 0 {

137+ tcp_conn.close() or {}

138+ return error('net.openssl SSLListener.accept, could not create SSL instance')

139+ }

140+

141+ if C.SSL_set_fd(voidptr(ssl), tcp_conn.sock.handle) != 1 {

142+ C.SSL_free(voidptr(ssl))

143+ tcp_conn.close() or {}

144+ return error('net.openssl SSLListener.accept, could not assign ssl to socket')

145+ }

146+ // OpenSSL only returns SSL_ERROR_WANT_READ/WRITE while the socket is non-blocking.

147+ // The SSLConn retry loops use those results to enforce the configured deadline.

148+ net.set_blocking(tcp_conn.sock.handle, false) or {

149+ C.SSL_free(voidptr(ssl))

150+ tcp_conn.close() or {}

151+ return error('net.openssl SSLListener.accept, could not make socket non-blocking: ${err}')

152+ }

153+

154+ mut conn := &SSLConn{

155+ config: l.config

156+ sslctx: unsafe { nil } // 设为 nil 避免连接关闭时意外释放共享的 sslctx

157+ ssl: ssl

158+ handle: tcp_conn.sock.handle

159+ duration: tcp_conn.read_timeout()

160+ owns_socket: true

161+ }

162+

163+ return conn

164+}

165+

166+// accept_handshake performs the SSL handshake on the connection.

167+pub fn (mut conn SSLConn) accept_handshake() ! {

168+ // 执行 SSL 握手过程

169+ deadline := ssl_timeout_deadline(conn.duration)

170+ for {

171+ res := C.SSL_accept(voidptr(conn.ssl))

172+ if res == 1 {

173+ break

174+ }

175+

176+ err_res := ssl_error(res, conn.ssl)!

177+ if err_res == .ssl_error_want_read {

178+ conn.wait_for_read(ssl_remaining_timeout(deadline))!

179+ continue

180+ }

181+ if err_res == .ssl_error_want_write {

182+ conn.wait_for_write(ssl_remaining_timeout(deadline))!

183+ continue

184+ }

185+

186+ // 握手失败

187+ return error('net.openssl SSLListener.accept, SSL handshake failed: ${err_res}')

188+ }

189+}

190+

191+// shutdown shuts down the SSL listener and releases the context.

192+pub fn (mut l SSLListener) shutdown() ! {

193+ if l.sslctx != 0 {

194+ C.SSL_CTX_free(l.sslctx)

195+ l.sslctx = unsafe { nil }

196+ }

197+ if l.tcp_listener != unsafe { nil } {

198+ l.tcp_listener.close()!

199+ }

200+}

vlib/net/openssl/ssl_listener_test.c.vnew file+146-0

@@ -0,0 +1,146 @@

1+// vtest build: present_openssl? && !(windows && tinyc)

2+module openssl

3+

4+import net

5+import os

6+import time

7+

8+fn new_test_ssl_listener(address string, family net.AddrFamily) !&SSLListener {

9+ cert_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.crt')

10+ key_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.key')

11+ return new_ssl_listener(address, SSLConnectConfig{

12+ cert: cert_path

13+ cert_key: key_path

14+ },

15+ family: family

16+ )

17+}

18+

19+fn load_test_certificate_pem() !string {

20+ cert_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.crt')

21+ return os.read_file(cert_path)

22+}

23+

24+fn load_test_private_key_pem() !string {

25+ key_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.key')

26+ return os.read_file(key_path)

27+}

28+

29+fn accept_one_ssl_connection(mut listener SSLListener) {

30+ mut conn := listener.accept() or { return }

31+ conn.duration = 500 * time.millisecond

32+ mut buffer := []u8{len: 1}

33+ _ := conn.read(mut buffer) or { 0 }

34+ conn.shutdown() or {}

35+}

36+

37+fn close_tcp_connection_after(mut conn net.TcpConn, delay time.Duration) {

38+ time.sleep(delay)

39+ conn.close() or {}

40+}

41+

42+fn test_ssl_listener_honors_ipv6_family() ! {

43+ $if macos || linux {

44+ mut listener := new_test_ssl_listener('[::1]:0', .ip6)!

45+ defer {

46+ listener.shutdown() or {}

47+ }

48+ assert listener.tcp_listener.addr()!.family() == .ip6

49+ }

50+}

51+

52+fn test_ssl_listener_handshake_honors_timeout() ! {

53+ mut listener := new_test_ssl_listener('127.0.0.1:0', .ip)!

54+ defer {

55+ listener.shutdown() or {}

56+ }

57+ address := listener.tcp_listener.addr()!.str()

58+ mut tcp_client := net.dial_tcp(address)!

59+ close_thread := spawn close_tcp_connection_after(mut tcp_client, time.second)

60+

61+ mut ssl_conn := listener.accept_without_handshake()!

62+ defer {

63+ ssl_conn.shutdown() or {}

64+ }

65+ ssl_conn.duration = 100 * time.millisecond

66+ stopwatch := time.new_stopwatch()

67+ ssl_conn.accept_handshake() or {

68+ assert stopwatch.elapsed() < 500 * time.millisecond

69+ close_thread.wait()

70+ return

71+ }

72+ close_thread.wait()

73+ assert false, 'TLS handshake unexpectedly succeeded without receiving client TLS data'

74+}

75+

76+fn test_ssl_remaining_timeout_clamps_expired_finite_deadline() {

77+ // A finite deadline that has already expired must report an immediate

78+ // timeout (a minimal positive duration), not net.infinite_timeout, so that

79+ // select()/wait_for() return net.err_timed_out instead of blocking forever

80+ // after a peer stalls mid-handshake/read past the configured timeout.

81+ expired := time.now().add(-time.second)

82+ remaining := ssl_remaining_timeout(expired)

83+ assert remaining > 0

84+ assert remaining != net.infinite_timeout

85+ // An unset deadline still means "wait forever".

86+ assert ssl_remaining_timeout(time.unix(0)) == net.infinite_timeout

87+ // A live finite deadline returns its actual remaining time.

88+ live := ssl_remaining_timeout(time.now().add(5 * time.second))

89+ assert live > 4 * time.second

90+ assert live <= 5 * time.second

91+}

92+

93+fn test_ssl_listener_loads_in_memory_credentials_without_temp_files() ! {

94+ start := time.now().unix()

95+ mut listener := new_ssl_listener('127.0.0.1:0', SSLConnectConfig{

96+ cert: load_test_certificate_pem()!

97+ cert_key: load_test_private_key_pem()!

98+ in_memory_verification: true

99+ })!

100+ listener.shutdown()!

101+ finish := time.now().unix()

102+ for timestamp in start .. finish + 1 {

103+ for name in ['v_srv_cert', 'v_srv_cert_key', 'v_srv_ca'] {

104+ assert !os.exists(os.join_path(os.temp_dir(), name + timestamp.str()))

105+ }

106+ }

107+}

108+

109+fn test_ssl_listener_file_credentials_load_full_certificate_chain() ! {

110+ cert_path := os.join_path(os.temp_dir(),

111+ 'v_openssl_listener_fullchain_${time.now().unix_nano()}.pem')

112+ os.write_file(cert_path, load_test_certificate_pem()! + load_test_certificate_pem()!)!

113+ defer {

114+ os.rm(cert_path) or {}

115+ }

116+ mut listener := new_ssl_listener('127.0.0.1:0', SSLConnectConfig{

117+ cert: cert_path

118+ cert_key: os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.key')

119+ })!

120+ defer {

121+ listener.shutdown() or {}

122+ }

123+ assert C.v_net_openssl_SSL_CTX_extra_chain_certs_count(listener.sslctx) == 1

124+}

125+

126+fn test_ssl_listener_negotiates_alpn() ! {

127+ mut listener := new_ssl_listener('127.0.0.1:0', SSLConnectConfig{

128+ cert: load_test_certificate_pem()!

129+ cert_key: load_test_private_key_pem()!

130+ in_memory_verification: true

131+ alpn_protocols: ['h2', 'http/1.1']

132+ })!

133+ defer {

134+ listener.shutdown() or {}

135+ }

136+ port := listener.tcp_listener.addr()!.port()!

137+ server_thread := spawn accept_one_ssl_connection(mut listener)

138+ mut client := new_ssl_conn(SSLConnectConfig{

139+ alpn_protocols: ['http/1.1']

140+ })!

141+ client.dial('127.0.0.1', port)!

142+ assert client.negotiated_alpn() == 'http/1.1'

143+ client.duration = 500 * time.millisecond

144+ client.shutdown() or {}

145+ server_thread.wait()

146+}

vlib/net/ssl/ssl_d_use_openssl.v+7-0

@@ -3,6 +3,8 @@ module ssl

33import net.openssl

44

55pub type SSLConn = openssl.SSLConn

6+pub type SSLListener = openssl.SSLListener

7+pub type SSLListenerOptions = openssl.SSLListenerOptions

68

79@[params]

810pub struct SSLConnectConfig {

@@ -13,3 +15,8 @@ pub struct SSLConnectConfig {

1315pub fn new_ssl_conn(config SSLConnectConfig) !&SSLConn {

1416 return openssl.new_ssl_conn(config.SSLConnectConfig) or { return err }

1517}

18+

19+// new_ssl_listener returns a new SSLListener with the given config.

20+pub fn new_ssl_listener(saddr string, config SSLConnectConfig, options SSLListenerOptions) !&SSLListener {

21+ return openssl.new_ssl_listener(saddr, config.SSLConnectConfig, options) or { return err }

22+}

vlib/net/ssl/ssl_listener_options_test.vnew file+18-0

@@ -0,0 +1,18 @@

1+module main

2+

3+import net.ssl

4+import os

5+

6+fn test_net_ssl_listener_preserves_ipv6_options() ! {

7+ $if macos || linux {

8+ cert_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.crt')

9+ key_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.key')

10+ mut listener := ssl.new_ssl_listener('[::1]:0', ssl.SSLConnectConfig{

11+ cert: cert_path

12+ cert_key: key_path

13+ }, ssl.SSLListenerOptions{

14+ family: .ip6

15+ })!

16+ listener.shutdown()!

17+ }

18+}

vlib/net/ssl/ssl_notd_use_openssl.v+7-0

@@ -3,6 +3,8 @@ module ssl

33import net.mbedtls

44

55pub type SSLConn = mbedtls.SSLConn

6+pub type SSLListener = mbedtls.SSLListener

7+pub type SSLListenerOptions = mbedtls.SSLListenerOptions

68

79@[params]

810pub struct SSLConnectConfig {

@@ -13,3 +15,8 @@ pub struct SSLConnectConfig {

1315pub fn new_ssl_conn(config SSLConnectConfig) !&SSLConn {

1416 return mbedtls.new_ssl_conn(config.SSLConnectConfig) or { return err }

1517}

18+

19+// new_ssl_listener returns a new SSLListener with the given config.

20+pub fn new_ssl_listener(saddr string, config SSLConnectConfig, options SSLListenerOptions) !&SSLListener {

21+ return mbedtls.new_ssl_listener(saddr, config.SSLConnectConfig, options) or { return err }

22+}

vlib/net/ssl/ssl_openssl_listener_test.vnew file+20-0

@@ -0,0 +1,20 @@

1+// vtest build: present_openssl? && !(windows && tinyc)

2+// vtest vflags: -d use_openssl

3+module main

4+

5+import net.ssl

6+import os

7+

8+fn test_net_ssl_openssl_listener_preserves_ipv6_options() ! {

9+ $if macos || linux {

10+ cert_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.crt')

11+ key_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.key')

12+ mut listener := ssl.new_ssl_listener('[::1]:0', ssl.SSLConnectConfig{

13+ cert: cert_path

14+ cert_key: key_path

15+ }, ssl.SSLListenerOptions{

16+ family: .ip6

17+ })!

18+ listener.shutdown()!

19+ }

20+}

vlib/veb/ssl_d_use_openssl.v+117-14

@@ -1,19 +1,12 @@

11module veb

22

3+import io

34import net

5+import net.http

6+import net.openssl

7+import os

48import time

59

6-@[params]

7-pub struct SSLConnectConfig {

8-pub:

9- verify string

10- cert string

11- cert_key string

12- validate bool

13- in_memory_verification bool

14- read_timeout time.Duration

15-}

16-

1710@[params]

1811pub struct RunParams {

1912pub:

@@ -26,9 +19,119 @@ pub:

2619 timeout_in_seconds int = 30

2720 max_request_buffer_size int = 8192

2821 benchmark_page_generation bool // for the "page rendered in X ms"

29- ssl_config SSLConnectConfig

22+ ssl_config openssl.SSLConnectConfig

23+}

24+

25+fn run_at_with_ssl[A, X](mut global_app A, params RunParams) ! {

26+ routes := generate_routes[A, X](global_app)!

27+ controllers_sorted := check_duplicate_routes_in_controllers[A](global_app, routes)!

28+ if params.show_startup_message {

29+ println('[veb] Running app on https://${startup_host(params)}:${params.port}/')

30+ }

31+ flush_stdout()

32+ mut ssl_listener := openssl.new_ssl_listener(listen_addr(params), params.ssl_config,

33+ family: params.family

34+ )!

35+ defer {

36+ ssl_listener.shutdown() or {}

37+ }

38+ ssl_params := &SslRequestParams{

39+ global_app: unsafe { voidptr(&global_app) }

40+ controllers_sorted: controllers_sorted

41+ routes: &routes

42+ benchmark_page_generation: params.benchmark_page_generation

43+ max_request_buffer_size: if params.max_request_buffer_size > 0 {

44+ params.max_request_buffer_size

45+ } else {

46+ max_read

47+ }

48+ }

49+ $if A is BeforeAcceptApp {

50+ global_app.before_accept_loop()

51+ }

52+ for {

53+ mut ssl_conn := ssl_listener.accept_without_handshake() or {

54+ eprintln('[veb] accept() failed, reason: ${err}; skipping')

55+ continue

56+ }

57+ ssl_conn.duration = params.timeout_in_seconds * time.second

58+ spawn handle_ssl_connection[A, X](mut ssl_conn, ssl_params)

59+ }

60+}

61+

62+fn handle_ssl_connection[A, X](mut ssl_conn openssl.SSLConn, params &SslRequestParams) {

63+ defer {

64+ ssl_conn.shutdown() or {}

65+ }

66+ ssl_conn.accept_handshake() or { return }

67+ mut reader := io.new_buffered_reader(

68+ reader: ssl_conn

69+ cap: params.max_request_buffer_size

70+ )

71+ defer {

72+ unsafe {

73+ reader.free()

74+ }

75+ }

76+ for {

77+ req := read_request_from_buffered_reader(mut reader) or {

78+ if err !is io.Eof {

79+ write_ssl_response(mut ssl_conn, http_400) or {}

80+ }

81+ return

82+ }

83+ completed_context := handle_ssl_request[A, X](req, params) or {

84+ write_ssl_response(mut ssl_conn, http_400) or {}

85+ return

86+ }

87+ if completed_context.takeover_mode != .none {

88+ eprintln('[veb] HTTPS connections do not support takeover connections yet; closing the connection after this response.')

89+ }

90+ write_ssl_context_response(mut ssl_conn, completed_context) or {

91+ eprintln('[veb] error sending HTTPS response: ${err}')

92+ return

93+ }

94+ if completed_context.takeover_mode != .none

95+ || should_close_connection(completed_context.req, completed_context.res, completed_context.client_wants_to_close) {

96+ return

97+ }

98+ }

99+}

100+

101+fn write_ssl_context_response(mut ssl_conn openssl.SSLConn, completed_context &Context) ! {

102+ if !completed_context.done && completed_context.return_type == .normal {

103+ return error('context did not send a response')

104+ }

105+ match completed_context.return_type {

106+ .normal {

107+ write_ssl_response(mut ssl_conn, completed_context.res)!

108+ }

109+ .file {

110+ write_ssl_response(mut ssl_conn, completed_context.res)!

111+ if completed_context.return_file == '' {

112+ return error('missing file response path')

113+ }

114+ mut file := os.open(completed_context.return_file)!

115+ defer {

116+ file.close()

117+ }

118+ mut buf := []u8{len: max_read}

119+ for {

120+ n := file.read(mut buf) or {

121+ if err is io.Eof {

122+ break

123+ }

124+ return err

125+ }

126+ if n <= 0 {

127+ break

128+ }

129+ ssl_conn.write(buf[..n])!

130+ }

131+ }

132+ }

30133}

31134

32-fn run_at_with_ssl[A, X](mut _global_app A, _params RunParams) ! {

33- return error('veb HTTPS server requires net.mbedtls; -d use_openssl only avoids mbedtls for HTTP apps')

135+fn write_ssl_response(mut ssl_conn openssl.SSLConn, resp http.Response) ! {

136+ ssl_conn.write(resp.bytes())!

34137}

vlib/veb/ssl_notd_use_openssl.v+3-1

@@ -29,7 +29,9 @@ fn run_at_with_ssl[A, X](mut global_app A, params RunParams) ! {

2929 println('[veb] Running app on https://${startup_host(params)}:${params.port}/')

3030 }

3131 flush_stdout()

32- mut ssl_listener := mbedtls.new_ssl_listener(listen_addr(params), params.ssl_config)!

32+ mut ssl_listener := mbedtls.new_ssl_listener(listen_addr(params), params.ssl_config,

33+ family: params.family

34+ )!

3335 defer {

3436 ssl_listener.shutdown() or {}

3537 }

renamedvlib/veb/tests/ssl_test.v →vlib/veb/tests/ssl_mbedtls_test.v+8-2

@@ -1,4 +1,4 @@

1-// vtest build: !sanitized_job?

1+// vtest build: !sanitized_job? && !use_openssl?

22import net.http

33import net.mbedtls

44import os

@@ -11,9 +11,14 @@ pub struct Context {

1111}

1212

1313pub struct App {

14+ veb.Middleware[Context]

1415 started chan bool

1516}

1617

18+fn passthrough_middleware(mut _ctx Context) bool {

19+ return true

20+}

21+

1722pub fn (mut app App) before_accept_loop() {

1823 app.started <- true

1924}

@@ -22,10 +27,11 @@ pub fn (app &App) index(mut ctx Context) veb.Result {

2227 return ctx.text('secure')

2328}

2429

25-fn test_veb_serves_https_requests() {

30+fn test_veb_serves_https_requests() ! {

2631 cert_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.crt')

2732 key_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.key')

2833 mut app := &App{}

34+ app.use(handler: passthrough_middleware)

2935 spawn veb.run_at[App, Context](mut app,

3036 host: '127.0.0.1'

3137 port: https_port

vlib/veb/tests/ssl_openssl_test.vnew file+53-0

@@ -0,0 +1,53 @@

1+// vtest build: present_openssl? && !sanitized_job? && !(windows && tinyc)

2+// vtest vflags: -d use_openssl

3+import net.http

4+import net.openssl

5+import os

6+import veb

7+

8+const https_port = 13014

9+

10+pub struct Context {

11+ veb.Context

12+}

13+

14+pub struct App {

15+ veb.Middleware[Context]

16+ started chan bool

17+}

18+

19+fn passthrough_middleware(mut _ctx Context) bool {

20+ return true

21+}

22+

23+pub fn (mut app App) before_accept_loop() {

24+ app.started <- true

25+}

26+

27+pub fn (app &App) index(mut ctx Context) veb.Result {

28+ return ctx.text('secure')

29+}

30+

31+fn test_veb_serves_https_requests() ! {

32+ cert_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.crt')

33+ key_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.key')

34+ mut app := &App{}

35+ app.use(handler: passthrough_middleware)

36+ spawn veb.run_at[App, Context](mut app,

37+ host: '127.0.0.1'

38+ port: https_port

39+ family: .ip

40+ timeout_in_seconds: 2

41+ ssl_config: openssl.SSLConnectConfig{

42+ cert: cert_path

43+ cert_key: key_path

44+ }

45+ )

46+ _ := <-app.started

47+ res := http.fetch(

48+ url: 'https://127.0.0.1:${https_port}/'

49+ validate: false

50+ )!

51+ assert res.status_code == 200

52+ assert res.body == 'secure'

53+}

vlib/veb/veb.v+2-0

@@ -349,6 +349,8 @@ fn handle_route[A, X](mut app A, mut user_context X, url urllib.URL, host string

349349 if !user_context.Context.done {

350350 validate_middleware[X](mut user_context, get_handlers_for_method(route.after_middlewares,

351351 user_context.Context.req.method))

352+ // Preserve an after-middleware response, or restore the handler response state.

353+ user_context.Context.done = user_context.Context.done || was_done

352354 }

353355 }

354356 }