diff --git a/include/netutils/webclient.h b/include/netutils/webclient.h index 8a15ac535..33a92cb8d 100644 --- a/include/netutils/webclient.h +++ b/include/netutils/webclient.h @@ -234,6 +234,7 @@ typedef CODE int (*webclient_body_callback_t)( struct webclient_tls_connection; struct webclient_poll_info; +struct webclient_conn_s; struct webclient_tls_ops { @@ -252,6 +253,25 @@ struct webclient_tls_ops CODE int (*get_poll_info)(FAR void *ctx, FAR struct webclient_tls_connection *conn, FAR struct webclient_poll_info *info); + + /* init_connection: Initialize TLS over an existing connection + * + * This method is used for https proxy, which is essentially + * tunnelling over http. + * + * hostname parameter is supposed to be used for server certificate + * validation. + * + * This method can be NULL. + * In that case, webclient_perform fails with -ENOTSUP + * when it turns out that tunnelling is necessary. + */ + + CODE int (*init_connection)(FAR void *ctx, + FAR struct webclient_conn_s *conn, + FAR const char *hostname, + unsigned int timeout_second, + FAR struct webclient_tls_connection **connp); }; /* Note on webclient_client lifetime diff --git a/netutils/webclient/webclient.c b/netutils/webclient/webclient.c index 6ffcd1671..b121d27b5 100644 --- a/netutils/webclient/webclient.c +++ b/netutils/webclient/webclient.c @@ -222,6 +222,8 @@ struct wget_s size_t state_len; FAR const void *data_buffer; size_t data_len; + + FAR struct webclient_context *tunnel; }; /**************************************************************************** @@ -264,6 +266,7 @@ static const char g_httpcache[] = "Cache-Control: no-cache"; static void free_ws(FAR struct wget_s *ws) { free(ws->conn); + free(ws->tunnel); free(ws); } @@ -1277,10 +1280,50 @@ int webclient_perform(FAR struct webclient_context *ctx) if (ctx->proxy != NULL) { - nerr("ERROR: TLS over proxy is not implemented\n"); - free_ws(ws); - _SET_STATE(ctx, WEBCLIENT_CONTEXT_STATE_DONE); - return -ENOTSUP; + FAR struct webclient_context *tunnel; + + DEBUGASSERT(ws->tunnel == NULL); + + if (tls_ops->init_connection == NULL) + { + nerr("ERROR: TLS over proxy is not implemented\n"); + ret = -ENOTSUP; + goto errout_with_errno; + } + + /* Create a temporary context to establish a tunnel. */ + + ws->tunnel = tunnel = calloc(1, sizeof(*ws->tunnel)); + if (tunnel == NULL) + { + ret = -ENOMEM; + goto errout_with_errno; + } + + webclient_set_defaults(tunnel); + tunnel->method = "CONNECT"; + tunnel->flags |= WEBCLIENT_FLAG_TUNNEL; + tunnel->tunnel_target_host = ws->target.hostname; + tunnel->tunnel_target_port = ws->target.port; + tunnel->proxy = ctx->proxy; + + /* Inherit some configurations. + * + * Revisit: should there be independent configurations? + */ + + tunnel->protocol_version = ctx->protocol_version; + if ((ctx->flags & WEBCLIENT_FLAG_NON_BLOCKING) != 0) + { + tunnel->flags |= WEBCLIENT_FLAG_NON_BLOCKING; + } + + /* Abuse the buffer of the original request. + * It's safe with the current usage. + */ + + tunnel->buffer = ctx->buffer; + tunnel->buflen = ctx->buflen; } } else @@ -1340,7 +1383,64 @@ int webclient_perform(FAR struct webclient_context *ctx) if (ws->state == WEBCLIENT_STATE_CONNECT) { - if (conn->tls) + if (ws->tunnel != NULL) + { + ret = webclient_perform(ws->tunnel); + if (ret == 0) + { + FAR struct webclient_conn_s *tunnel_conn; + + ret = webclient_get_tunnel(ws->tunnel, &tunnel_conn); + if (ret == 0) + { + DEBUGASSERT(tunnel_conn != NULL); + DEBUGASSERT(!tunnel_conn->tls); + free(ws->tunnel); + ws->tunnel = NULL; + + if (conn->tls) + { + /* Revisit: tunnel_conn here should have + * timeout configured already. + * Configuring it again here is redundant. + */ + + ret = tls_ops->init_connection(tls_ctx, + tunnel_conn, + ws->target.hostname, + ctx->timeout_sec, + &conn->tls_conn); + if (ret == 0) + { + /* Note: tunnel_conn has been consumed by + * tls_ops->init_connection + */ + + ws->need_conn_close = true; + } + else + { + /* Note: restarting tls_ops->init_connection + * is not implemented + */ + + DEBUGASSERT(ret != -EAGAIN && + ret != -EINPROGRESS && + ret != -EALREADY); + conn_close(ctx, tunnel_conn); + free(tunnel_conn); + } + } + else + { + conn->sockfd = tunnel_conn->sockfd; + ws->need_conn_close = true; + free(tunnel_conn); + } + } + } + } + else if (conn->tls) { char port_str[sizeof("65535")]; @@ -2268,6 +2368,11 @@ int webclient_get_poll_info(FAR struct webclient_context *ctx, return -EINVAL; } + if (ws->tunnel != NULL) + { + return webclient_get_poll_info(ws->tunnel, info); + } + conn = ws->conn; if (conn->tls) {