From: Igor Velkov <[email protected]>

wget copies every received block to memory, so a download cannot be
larger than the free RAM. Writing a disk image to a block device while
it arrives needs the blocks one by one instead.

Add a sink callback to struct wget_http_info. When a client sets it,
store_block() passes each block to the sink and copies nothing, and
wget does not set fileaddr. altcp_recved() follows the sink, so the
TCP window reopens only after the sink has taken the data.

lwIP hands the body to the receive callback whatever the HTTP status.
With a sink, wget checks the status in the headers callback and refuses
the body of any reply other than 200, then reports the server error.

Signed-off-by: Igor Velkov <[email protected]>
---
 include/net-common.h |  6 +++++
 net/lwip/wget.c      | 76 ++++++++++++++++++++++++++++++++++++++++++++--------
 2 files changed, 71 insertions(+), 11 deletions(-)

diff --git a/include/net-common.h b/include/net-common.h
index f1f670dbd8b..75640d4ba41 100644
--- a/include/net-common.h
+++ b/include/net-common.h
@@ -623,6 +623,10 @@ enum wget_http_method {
  * @hdr_cont_len:      content length according to headers. Filled by wget
  * @headers:           buffer for headers. Filled by wget.
  * @silent:            do not print anything to the console. Filled by client.
+ * @sink:              if set, receives the payload piece by piece, and wget
+ *                     writes nothing to the destination address; a negative
+ *                     return aborts the transfer. lwIP only. Filled by client.
+ * @sink_priv:         first argument of @sink. Filled by client.
  */
 struct wget_http_info {
        enum wget_http_method method;
@@ -634,6 +638,8 @@ struct wget_http_info {
        u32 hdr_cont_len;
        char *headers;
        bool silent;
+       int (*sink)(void *priv, const void *buf, size_t len);
+       void *sink_priv;
 };
 
 extern struct wget_http_info default_wget_info;
diff --git a/net/lwip/wget.c b/net/lwip/wget.c
index 615ea035a28..5d275d28a81 100644
--- a/net/lwip/wget.c
+++ b/net/lwip/wget.c
@@ -41,6 +41,8 @@ struct wget_ctx {
        ulong hash_count;
        enum done_state done;
        bool aborted;
+       /* status that made headers_done refuse the body, for the message */
+       u32 bad_status;
 };
 
 static void wget_lwip_fill_info(struct pbuf *hdr, u16_t hdr_len, u32_t 
hdr_cont_len)
@@ -141,25 +143,19 @@ static int parse_url(char *url, char *host, u16 *port, 
char **path,
 }
 
 /**
- * store_block() - copy received data
+ * copy_block() - copy received data to memory
  *
- * This function is called by the receive callback to copy a block of data
- * into its final location (ctx->daddr). Before doing so, it checks if the copy
- * is allowed.
+ * This function copies a block of data into its final location (ctx->daddr).
+ * Before doing so, it checks if the copy is allowed.
  *
  * @ctx: the context for the current transfer
  * @src: the data received from the TCP stack
  * @len: the length of the data
  */
-static int store_block(struct wget_ctx *ctx, void *src, u16_t len)
+static int copy_block(struct wget_ctx *ctx, void *src, u16_t len)
 {
        ulong store_addr = ctx->daddr;
        uchar *ptr;
-       ulong pos;
-
-       /* Avoid overflow */
-       if (wget_info->buffer_size && wget_info->buffer_size < ctx->size + len)
-               return -1;
 
        if (CONFIG_IS_ENABLED(LMB) && wget_info->set_bootdev) {
                if (store_addr + len < store_addr ||
@@ -176,6 +172,35 @@ static int store_block(struct wget_ctx *ctx, void *src, 
u16_t len)
        memcpy(ptr, src, len);
        unmap_sysmem(ptr);
 
+       return 0;
+}
+
+/**
+ * store_block() - store received data
+ *
+ * The receive callback calls this function to pass a block of data to the
+ * client's sink or, without one, to copy it to memory.
+ *
+ * @ctx: the context for the current transfer
+ * @src: the data received from the TCP stack
+ * @len: the length of the data
+ */
+static int store_block(struct wget_ctx *ctx, void *src, u16_t len)
+{
+       ulong pos;
+       int ret;
+
+       /* Avoid overflow */
+       if (wget_info->buffer_size && wget_info->buffer_size < ctx->size + len)
+               return -1;
+
+       if (wget_info->sink)
+               ret = wget_info->sink(wget_info->sink_priv, src, len);
+       else
+               ret = copy_block(ctx, src, len);
+       if (ret < 0)
+               return -1;
+
        ctx->daddr += len;
        ctx->size += len;
 
@@ -235,6 +260,11 @@ static void httpc_result_cb(void *arg, httpc_result_t 
httpc_result,
                ctx->done = FAILURE;
                return;
        }
+       if (ctx->bad_status) {
+               log_err("\nHTTP server error %d\n", ctx->bad_status);
+               ctx->done = FAILURE;
+               return;
+       }
 
        if (httpc_result != HTTPC_RESULT_OK) {
                log_err("\nHTTP client error %d\n", httpc_result);
@@ -270,7 +300,8 @@ static void httpc_result_cb(void *arg, httpc_result_t 
httpc_result,
                                rx_content_len);
        wget_lwip_set_file_size(rx_content_len);
        if (env_set_hex("filesize", rx_content_len) ||
-           env_set_hex("fileaddr", ctx->saved_daddr)) {
+           (!wget_info->sink &&
+            env_set_hex("fileaddr", ctx->saved_daddr))) {
                log_err("Could not set filesize or fileaddr\n");
                ctx->done = FAILURE;
                return;
@@ -279,13 +310,35 @@ static void httpc_result_cb(void *arg, httpc_result_t 
httpc_result,
        ctx->done = SUCCESS;
 }
 
+/* Status code from the "HTTP/1.x NNN ..." line, or 0 if there is none */
+static u32 http_status(struct pbuf *hdr)
+{
+       char line[16];
+       u16_t n;
+
+       n = pbuf_copy_partial(hdr, line, sizeof(line) - 1, 0);
+       line[n] = '\0';
+       if (n < 12 || strncmp(line, "HTTP/", 5))
+               return 0;
+
+       return simple_strtoul(line + 9, NULL, 10);
+}
+
 static err_t httpc_headers_done_cb(httpc_state_t *connection, void *arg, 
struct pbuf *hdr,
                                   u16_t hdr_len, u32_t content_len)
 {
        struct wget_ctx *ctx = arg;
+       u32 status;
 
        wget_lwip_fill_info(hdr, hdr_len, content_len);
 
+       /* the body of an error page must not reach a sink */
+       status = http_status(hdr);
+       if (wget_info->sink && status != 200) {
+               ctx->bad_status = status;
+               return ERR_VAL;
+       }
+
        /* lwIP passes U32_MAX when the reply has no usable Content-Length */
        if (wget_info->check_buffer_size && content_len != U32_MAX &&
            (ulong)content_len > wget_info->buffer_size)
@@ -423,6 +476,7 @@ int wget_do_request(ulong dst_addr, char *uri)
        ctx.content_len = 0;
        ctx.hash_count = 0;
        ctx.aborted = false;
+       ctx.bad_status = 0;
 
        ret = parse_url(uri, ctx.server_name, &ctx.port, &ctx.path, &is_https);
        if (ret)

-- 
2.43.0


Reply via email to