summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFelix Fietkau2026-01-31 13:45:38 +0000
committerFelix Fietkau2026-01-31 13:54:29 +0000
commit80c9bd29c2331f34e91ec47fd7b486f58613a224 (patch)
treedaeff73040ef96dc8612c5d6ed3d0f4907f63205
parent9c2ad269c42bca917fc563c97c732d8c47fd1bfc (diff)
downloaduclient-80c9bd29c2331f34e91ec47fd7b486f58613a224.tar.gz
uclient-http: fix hang on HTTP to HTTPS redirect
Defer header processing to a uloop timeout to avoid modifying stream state while inside its callback chain. When switching protocols during a redirect, the old stream's callback code could corrupt the new SSL stream's state due to the union memory sharing between ustream_fd and ustream_ssl. Add HTTP_STATE_PROCESS_HEADERS intermediate state to prevent body data from being processed before the deferred header_done callback runs. Also detect protocol changes (HTTP to HTTPS) in uclient_http_connect() to force a proper disconnect and reconnect. Fixes: https://github.com/openwrt/uclient/issues/13 Signed-off-by: Felix Fietkau <nbd@nbd.name>
-rw-r--r--uclient-http.c40
1 files changed, 31 insertions, 9 deletions
diff --git a/uclient-http.c b/uclient-http.c
index 5327614..fce62bb 100644
--- a/uclient-http.c
+++ b/uclient-http.c
@@ -53,6 +53,7 @@ enum http_state {
HTTP_STATE_HEADERS_SENT,
HTTP_STATE_REQUEST_DONE,
HTTP_STATE_RECV_HEADERS,
+ HTTP_STATE_PROCESS_HEADERS,
HTTP_STATE_RECV_DATA,
HTTP_STATE_ERROR,
};
@@ -78,6 +79,7 @@ struct uclient_http {
};
struct uloop_timeout disconnect_t;
+ struct uloop_timeout process_headers_t;
unsigned int seq;
int fd;
@@ -116,6 +118,7 @@ static const char * const uclient_http_prefix[] = {
};
static int uclient_http_connect(struct uclient *cl);
+static void __uclient_notify_read(struct uclient_http *uh);
static int uclient_do_connect(struct uclient_http *uh, const char *port)
{
@@ -146,6 +149,7 @@ static int uclient_do_connect(struct uclient_http *uh, const char *port)
static void uclient_http_disconnect(struct uclient_http *uh)
{
uloop_timeout_cancel(&uh->disconnect_t);
+ uloop_timeout_cancel(&uh->process_headers_t);
if (!uh->us)
return;
@@ -637,15 +641,12 @@ uclient_http_send_headers(struct uclient_http *uh)
return 0;
}
-static void uclient_http_headers_complete(struct uclient_http *uh)
+static void uclient_http_process_headers_cb(struct uloop_timeout *timeout)
{
+ struct uclient_http *uh = container_of(timeout, struct uclient_http, process_headers_t);
enum auth_type auth_type = uh->auth_type;
unsigned int seq = uh->seq;
- uh->state = HTTP_STATE_RECV_DATA;
- uh->uc.meta = uh->meta.head;
- uclient_http_process_headers(uh);
-
if (auth_type == AUTH_TYPE_UNKNOWN && uh->uc.status_code == 401 &&
(uh->req_type == REQ_HEAD || uh->req_type == REQ_GET)) {
uclient_http_connect(&uh->uc);
@@ -654,6 +655,8 @@ static void uclient_http_headers_complete(struct uclient_http *uh)
return;
}
+ uh->state = HTTP_STATE_RECV_DATA;
+
if (uh->uc.cb->header_done)
uh->uc.cb->header_done(&uh->uc);
@@ -664,7 +667,20 @@ static void uclient_http_headers_complete(struct uclient_http *uh)
uh->content_length == 0) {
uh->eof = true;
uclient_notify_eof(uh);
+ return;
}
+
+ __uclient_notify_read(uh);
+}
+
+static void uclient_http_headers_complete(struct uclient_http *uh)
+{
+ uh->state = HTTP_STATE_PROCESS_HEADERS;
+ uh->uc.meta = uh->meta.head;
+ uclient_http_process_headers(uh);
+
+ uh->process_headers_t.cb = uclient_http_process_headers_cb;
+ uloop_timeout_set(&uh->process_headers_t, 1);
}
static void uclient_parse_http_line(struct uclient_http *uh, char *data)
@@ -730,6 +746,9 @@ static void __uclient_notify_read(struct uclient_http *uh)
if (uh->state < HTTP_STATE_REQUEST_DONE || uh->state == HTTP_STATE_ERROR)
return;
+ if (uh->state == HTTP_STATE_PROCESS_HEADERS)
+ return;
+
data = ustream_get_read_buf(uh->us, &len);
if (!data || !len)
return;
@@ -773,7 +792,7 @@ static void __uclient_notify_read(struct uclient_http *uh)
return;
data = ustream_get_read_buf(uh->us, &len);
- } while (data && uh->state < HTTP_STATE_RECV_DATA);
+ } while (data && uh->state == HTTP_STATE_RECV_HEADERS);
if (!len)
return;
@@ -906,6 +925,7 @@ static int uclient_setup_https(struct uclient_http *uh)
int ret;
memset(&uh->ussl, 0, sizeof(uh->ussl));
+ uh->ufd.fd.fd = -1;
uh->ssl = true;
uh->us = us;
@@ -933,9 +953,10 @@ static int uclient_setup_https(struct uclient_http *uh)
static int uclient_http_connect(struct uclient *cl)
{
struct uclient_http *uh = container_of(cl, struct uclient_http, uc);
+ bool ssl = cl->url->prefix == PREFIX_HTTPS;
int ret;
- if (!cl->eof || uh->disconnect || uh->connection_close)
+ if (!cl->eof || uh->disconnect || uh->connection_close || uh->ssl != ssl)
uclient_http_disconnect(uh);
uclient_http_init_request(uh);
@@ -943,7 +964,7 @@ static int uclient_http_connect(struct uclient *cl)
if (uh->us)
return 0;
- uh->ssl = cl->url->prefix == PREFIX_HTTPS;
+ uh->ssl = ssl;
if (uh->ssl)
ret = uclient_setup_https(uh);
@@ -1199,7 +1220,8 @@ int uclient_http_redirect(struct uclient *cl)
if (uclient_http_connect(cl))
return -1;
- uclient_http_request_done(cl);
+ if (uclient_request(cl))
+ return -1;
return true;
}