From 0f7f27801ef1d802508f022364004ab264490c63 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Sun, 17 Nov 2019 10:47:01 +0000 Subject: [PATCH] http redirect: 303: force method to GET This teaches http client stuff how to handle 303 redirects... these can happen after POST where the server side wants you to come back with a GET to the Location: mentioned. lws client will follow the redirect and force GET, this works for both h1 and h2. Client protocol handler has to act differently if it finds it is connecting for the initial POST or the subsequent GET, it can find out which by checking a new api lws_http_is_redirected_to_get(wsi) which returns nonzero if in GET mode. Minimal example for server form-post has a new --303 switch to enable this behaviour there and the client post example has additions to check lws_http_is_redirected_to_get(). --- include/libwebsockets/lws-http.h | 13 +++++- lib/core-net/close.c | 4 +- lib/core-net/private-lib-core-net.h | 1 + lib/roles/http/client/client-handshake.c | 40 +++++++++++++++-- lib/roles/http/client/client-http.c | 43 ++++++++++++++++--- lib/tls/mbedtls/mbedtls-client.c | 4 +- .../minimal-http-client-post.c | 13 +++++- .../minimal-http-server-form-post.c | 37 +++++++++++----- 8 files changed, 128 insertions(+), 27 deletions(-) diff --git a/include/libwebsockets/lws-http.h b/include/libwebsockets/lws-http.h index fce21d8c1..17df7bac0 100644 --- a/include/libwebsockets/lws-http.h +++ b/include/libwebsockets/lws-http.h @@ -788,8 +788,19 @@ lws_h2_client_stream_long_poll_rxonly(struct lws *wsi); * LWS_WITH_HTTP_STREAM_COMPRESSION set, then a NOP is provided for this api, * allowing user code to build either way and use compression if available. */ -LWS_VISIBLE int +LWS_VISIBLE LWS_EXTERN int lws_http_compression_apply(struct lws *wsi, const char *name, unsigned char **p, unsigned char *end, char decomp); + +/** + * lws_http_is_redirected_to_get() - true if redirected to GET + * + * \param wsi: the wsi to check + * + * Check if the wsi is currently in GET mode, after, eg, doing a POST and + * receiving a 303. + */ +LWS_VISIBLE LWS_EXTERN int +lws_http_is_redirected_to_get(struct lws *wsi); ///@} diff --git a/lib/core-net/close.c b/lib/core-net/close.c index b3d8ad513..cbc6bcac7 100644 --- a/lib/core-net/close.c +++ b/lib/core-net/close.c @@ -79,8 +79,10 @@ __lws_reset_wsi(struct lws *wsi) * or by specified the user. We should only free what we allocated. */ if (wsi->protocol && wsi->protocol->per_session_data_size && - wsi->user_space && !wsi->user_space_externally_allocated) + wsi->user_space && !wsi->user_space_externally_allocated) { lws_free(wsi->user_space); + wsi->user_space = NULL; + } lws_buflist_destroy_all_segments(&wsi->buflist); lws_buflist_destroy_all_segments(&wsi->buflist_out); diff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h index 7bff03b12..f4452c0ed 100644 --- a/lib/core-net/private-lib-core-net.h +++ b/lib/core-net/private-lib-core-net.h @@ -747,6 +747,7 @@ struct lws { unsigned int transaction_from_pipeline_queue:1; unsigned int keepalive_active:1; unsigned int keepalive_rejected:1; + unsigned int redirected_to_get:1; unsigned int client_pipeline:1; unsigned int client_h2_alpn:1; unsigned int client_h2_substream:1; diff --git a/lib/roles/http/client/client-handshake.c b/lib/roles/http/client/client-handshake.c index 8a29e6899..3713e6f7a 100644 --- a/lib/roles/http/client/client-handshake.c +++ b/lib/roles/http/client/client-handshake.c @@ -322,7 +322,8 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads, goto conn_good; } - lwsl_debug("%s: getsockopt says err %d\n", __func__, e); + lwsl_debug("%s: getsockopt fd %d says err %d\n", __func__, + wsi->desc.sockfd, e); } lwsl_debug("%s: getsockopt check: conn fail: errno %d\n", @@ -963,6 +964,17 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, if (!stash) return NULL; + /* + * _WSI_TOKEN_CLIENT_ORIGIN, + * _WSI_TOKEN_CLIENT_SENT_PROTOCOLS, + * _WSI_TOKEN_CLIENT_METHOD, + * _WSI_TOKEN_CLIENT_IFACE, + * _WSI_TOKEN_CLIENT_ALPN + * address + * host + * path + */ + for (n = 0; n < (int)LWS_ARRAY_SIZE(hnames2); n++) if (lws_hdr_total_length(wsi, hnames2[n])) { memcpy(p, lws_hdr_simple_ptr(wsi, hnames2[n]), @@ -981,6 +993,8 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, path = p; if (!port) { + lwsl_info("%s: forcing port 443\n", __func__); + port = 443; ssl = 1; } @@ -1008,7 +1022,10 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, compatible_close(wsi->desc.sockfd); #if defined(LWS_WITH_TLS) - wsi->tls.use_ssl = ssl; + if (!ssl) + wsi->tls.use_ssl &= LCCSCF_USE_SSL; + else + wsi->tls.use_ssl |= LCCSCF_USE_SSL; #else if (ssl) { lwsl_err("%s: not configured for ssl\n", __func__); @@ -1046,6 +1063,17 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_HOST, host)) goto bail; + /* + * _WSI_TOKEN_CLIENT_ORIGIN, + * _WSI_TOKEN_CLIENT_SENT_PROTOCOLS, + * _WSI_TOKEN_CLIENT_METHOD, + * _WSI_TOKEN_CLIENT_IFACE, + * _WSI_TOKEN_CLIENT_ALPN + * address + * host + * path + */ + p = stash; for (n = 0; n < (int)LWS_ARRAY_SIZE(hnames2); n++) { if (lws_hdr_simple_create(wsi, hnames2[n], p)) @@ -1060,6 +1088,11 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, lws_free_set_NULL(stash); +#if defined(LWS_WITH_HTTP2) + if (wsi->client_h2_substream) + wsi->h2.END_STREAM = wsi->h2.END_HEADERS = 0; +#endif + *pwsi = lws_client_connect_2_dnsreq(wsi); return *pwsi; @@ -1258,9 +1291,10 @@ lws_http_client_connect_via_info2(struct lws *wsi) * allocated stash */ for (n = 0; n < (int)LWS_ARRAY_SIZE(hnames); n++) - if (hnames[n] && stash->cis[n]) + if (hnames[n] && stash->cis[n]) { if (lws_hdr_simple_create(wsi, hnames[n], stash->cis[n])) goto bail1; + } #if defined(LWS_WITH_SOCKS5) if (!wsi->vhost->socks_proxy_port) diff --git a/lib/roles/http/client/client-http.c b/lib/roles/http/client/client-http.c index b08d1dc44..4ead44cf6 100644 --- a/lib/roles/http/client/client-http.c +++ b/lib/roles/http/client/client-http.c @@ -702,6 +702,13 @@ lws_http_client_http_response(struct lws *_wsi) #endif #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + +int +lws_http_is_redirected_to_get(struct lws *wsi) +{ + return wsi->redirected_to_get; +} + int lws_client_interpret_server_handshake(struct lws *wsi) { @@ -710,6 +717,7 @@ lws_client_interpret_server_handshake(struct lws *wsi) const char *prot, *ads = NULL, *path, *cce = NULL; struct allocated_headers *ah; struct lws *w = lws_client_wsi_effective(wsi); + struct lws *nwsi = lws_get_network_wsi(wsi); char *p, *q; char new_path[300]; @@ -797,16 +805,37 @@ lws_client_interpret_server_handshake(struct lws *wsi) goto bail3; } + /* + * Some redirect codes imply we have to change the method + * used for the subsequent transaction, commonly POST -> + * 303 -> GET. + */ + + if (n == 303) { + char *mp = lws_hdr_simple_ptr(wsi,_WSI_TOKEN_CLIENT_METHOD); + int ml = lws_hdr_total_length(wsi, _WSI_TOKEN_CLIENT_METHOD); + + if (ml >= 3 && mp) { + lwsl_info("%s: 303 switching to GET\n", __func__); + memcpy(mp, "GET", 4); + wsi->redirected_to_get = 1; + wsi->http.ah->frags[wsi->http.ah->frag_index[ + _WSI_TOKEN_CLIENT_METHOD]].len = 3; + } + } + /* Relative reference absolute path */ - if (p[0] == '/') { + if (p[0] == '/' || !strchr(p, ':')) { #if defined(LWS_WITH_TLS) - ssl = wsi->tls.use_ssl & LCCSCF_USE_SSL; + ssl = nwsi->tls.use_ssl & LCCSCF_USE_SSL; #endif ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); - port = wsi->c_port; - /* +1 as lws_client_reset expects leading / omitted */ - path = p + 1; + port = nwsi->c_port; + path = p; + /* lws_client_reset expects leading / omitted */ + if (*path == '/') + path++; } /* Absolute (Full) URI */ else if (strchr(p, ':')) { @@ -816,14 +845,14 @@ lws_client_interpret_server_handshake(struct lws *wsi) } if (!strcmp(prot, "wss") || !strcmp(prot, "https")) - ssl = 1; + ssl = LCCSCF_USE_SSL; } /* Relative reference relative path */ else { /* This doesn't try to calculate an absolute path, * that will be left to the server */ #if defined(LWS_WITH_TLS) - ssl = wsi->tls.use_ssl & LCCSCF_USE_SSL; + ssl = nwsi->tls.use_ssl & LCCSCF_USE_SSL; #endif ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); diff --git a/lib/tls/mbedtls/mbedtls-client.c b/lib/tls/mbedtls/mbedtls-client.c index d50ee4d97..8a1b63e61 100644 --- a/lib/tls/mbedtls/mbedtls-client.c +++ b/lib/tls/mbedtls/mbedtls-client.c @@ -177,8 +177,8 @@ lws_tls_client_confirm_peer_cert(struct lws *wsi, char *ebuf, int ebuf_len) return 0; } lws_snprintf(ebuf, ebuf_len, - "server's cert didn't look good, X509_V_ERR = %d: %s\n", - n, ERR_error_string(n, sb)); + "server's cert didn't look good, (use_ssl 0x%x) X509_V_ERR = %d: %s\n", + (unsigned int)wsi->tls.use_ssl, n, ERR_error_string(n, sb)); lwsl_info("%s\n", ebuf); lws_tls_err_describe_clear(); diff --git a/minimal-examples/http-client/minimal-http-client-post/minimal-http-client-post.c b/minimal-examples/http-client/minimal-http-client-post/minimal-http-client-post.c index 3f139f1e1..b291efb77 100644 --- a/minimal-examples/http-client/minimal-http-client-post/minimal-http-client-post.c +++ b/minimal-examples/http-client/minimal-http-client-post/minimal-http-client-post.c @@ -96,11 +96,17 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, /* * Tell lws we are going to send the body next... */ - lws_client_http_body_pending(wsi, 1); - lws_callback_on_writable(wsi); + if (!lws_http_is_redirected_to_get(wsi)) { + lwsl_user("%s: doing POST flow\n", __func__); + lws_client_http_body_pending(wsi, 1); + lws_callback_on_writable(wsi); + } else + lwsl_user("%s: doing GET flow\n", __func__); break; case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE: + if (lws_http_is_redirected_to_get(wsi)) + break; lwsl_user("LWS_CALLBACK_CLIENT_HTTP_WRITEABLE\n"); n = LWS_WRITE_HTTP; @@ -236,6 +242,9 @@ int main(int argc, const char **argv) i.path = "/testserver/formtest"; } + if (lws_cmdline_option(argc, argv, "--form1")) + i.path = "/form1"; + i.host = i.address; i.origin = i.address; i.method = "POST"; diff --git a/minimal-examples/http-server/minimal-http-server-form-post/minimal-http-server-form-post.c b/minimal-examples/http-server/minimal-http-server-form-post/minimal-http-server-form-post.c index c7d09439e..4711fc261 100644 --- a/minimal-examples/http-server/minimal-http-server-form-post/minimal-http-server-form-post.c +++ b/minimal-examples/http-server/minimal-http-server-form-post/minimal-http-server-form-post.c @@ -24,7 +24,7 @@ struct pss { struct lws_spa *spa; }; -static int interrupted; +static int interrupted, use303; static const char * const param_names[] = { "text1", @@ -81,6 +81,11 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, return -1; break; + case LWS_CALLBACK_CLOSED_CLIENT_HTTP: + if (pss->spa && lws_spa_destroy(pss->spa)) + return -1; + break; + case LWS_CALLBACK_HTTP_BODY_COMPLETION: /* inform the spa no more payload data coming */ @@ -90,22 +95,27 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, /* we just dump the decoded things to the log */ - for (n = 0; n < (int)LWS_ARRAY_SIZE(param_names); n++) { - if (!lws_spa_get_string(pss->spa, n)) - lwsl_user("%s: undefined\n", param_names[n]); - else - lwsl_user("%s: (len %d) '%s'\n", - param_names[n], - lws_spa_get_length(pss->spa, n), - lws_spa_get_string(pss->spa, n)); - } + if (pss->spa) + for (n = 0; n < (int)LWS_ARRAY_SIZE(param_names); n++) { + if (!lws_spa_get_string(pss->spa, n)) + lwsl_user("%s: undefined\n", param_names[n]); + else + lwsl_user("%s: (len %d) '%s'\n", + param_names[n], + lws_spa_get_length(pss->spa, n), + lws_spa_get_string(pss->spa, n)); + } + + if (pss->spa && lws_spa_destroy(pss->spa)) + return -1; /* * Our response is to redirect to a static page. We could * have generated a dynamic html page here instead. */ - if (lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY, + if (lws_http_redirect(wsi, use303 ? HTTP_STATUS_SEE_OTHER : + HTTP_STATUS_MOVED_PERMANENTLY, (unsigned char *)"after-form1.html", 16, &p, end) < 0) return -1; @@ -192,6 +202,11 @@ int main(int argc, const char **argv) info.ssl_private_key_filepath = "localhost-100y.key"; } + if (lws_cmdline_option(argc, argv, "--303")) { + lwsl_user("%s: using 303 redirect\n", __func__); + use303 = 1; + } + context = lws_create_context(&info); if (!context) { lwsl_err("lws init failed\n");