blob: 53c3b169e01e48dc9ce9fa05b337fffdbaa023b9 [file] [log] [blame]
Jerome Forissier359d4ed2024-10-16 12:04:09 +02001// SPDX-License-Identifier: GPL-2.0+
2/* Copyright (C) 2024 Linaro Ltd. */
3
4#include <command.h>
5#include <console.h>
6#include <display_options.h>
7#include <efi_loader.h>
8#include <image.h>
9#include <lwip/apps/http_client.h>
10#include <lwip/timeouts.h>
11#include <mapmem.h>
12#include <net.h>
13#include <time.h>
14
15#define SERVER_NAME_SIZE 200
16#define HTTP_PORT_DEFAULT 80
17#define PROGRESS_PRINT_STEP_BYTES (100 * 1024)
18
19enum done_state {
20 NOT_DONE = 0,
21 SUCCESS = 1,
22 FAILURE = 2
23};
24
25struct wget_ctx {
Adriano Cordovaa8a8d5c62024-11-11 18:09:00 -030026 char server_name[SERVER_NAME_SIZE];
27 u16 port;
Jerome Forissier359d4ed2024-10-16 12:04:09 +020028 char *path;
29 ulong daddr;
30 ulong saved_daddr;
31 ulong size;
32 ulong prevsize;
33 ulong start_time;
34 enum done_state done;
35};
36
Adriano Cordova25e88412024-11-11 18:09:01 -030037static void wget_lwip_fill_info(struct pbuf *hdr, u16_t hdr_len, u32_t hdr_cont_len)
38{
39 if (wget_info->headers && hdr_len < MAX_HTTP_HEADERS_SIZE)
40 pbuf_copy_partial(hdr, (void *)wget_info->headers, hdr_len, 0);
41 wget_info->hdr_cont_len = (u32)hdr_cont_len;
42}
43
44static void wget_lwip_set_file_size(u32_t rx_content_len)
45{
46 wget_info->file_size = (ulong)rx_content_len;
47}
48
Jerome Forissier359d4ed2024-10-16 12:04:09 +020049static int parse_url(char *url, char *host, u16 *port, char **path)
50{
51 char *p, *pp;
52 long lport;
53
54 p = strstr(url, "http://");
55 if (!p) {
56 log_err("only http:// is supported\n");
57 return -EINVAL;
58 }
59
60 p += strlen("http://");
61
62 /* Parse hostname */
63 pp = strchr(p, ':');
64 if (!pp)
65 pp = strchr(p, '/');
66 if (!pp)
67 return -EINVAL;
68
69 if (p + SERVER_NAME_SIZE <= pp)
70 return -EINVAL;
71
72 memcpy(host, p, pp - p);
73 host[pp - p] = '\0';
74
75 if (*pp == ':') {
76 /* Parse port number */
77 p = pp + 1;
78 lport = simple_strtol(p, &pp, 10);
79 if (pp && *pp != '/')
80 return -EINVAL;
81 if (lport > 65535)
82 return -EINVAL;
83 *port = (u16)lport;
84 } else {
85 *port = HTTP_PORT_DEFAULT;
86 }
87 if (*pp != '/')
88 return -EINVAL;
89 *path = pp;
90
91 return 0;
92}
93
94/*
95 * Legacy syntax support
96 * Convert [<server_name_or_ip>:]filename into a URL if needed
97 */
98static int parse_legacy_arg(char *arg, char *nurl, size_t rem)
99{
100 char *p = nurl;
101 size_t n;
102 char *col = strchr(arg, ':');
103 char *env;
104 char *server;
105 char *path;
106
107 if (strstr(arg, "http") == arg) {
108 n = snprintf(nurl, rem, "%s", arg);
109 if (n < 0 || n > rem)
110 return -1;
111 return 0;
112 }
113
114 n = snprintf(p, rem, "%s", "http://");
115 if (n < 0 || n > rem)
116 return -1;
117 p += n;
118 rem -= n;
119
120 if (col) {
121 n = col - arg;
122 server = arg;
123 path = col + 1;
124 } else {
125 env = env_get("httpserverip");
126 if (!env)
127 env = env_get("serverip");
128 if (!env) {
129 log_err("error: httpserver/serverip has to be set\n");
130 return -1;
131 }
132 n = strlen(env);
133 server = env;
134 path = arg;
135 }
136
137 if (rem < n)
138 return -1;
139 strncpy(p, server, n);
140 p += n;
141 rem -= n;
142 if (rem < 1)
143 return -1;
144 *p = '/';
145 p++;
146 rem--;
147 n = strlen(path);
148 if (rem < n)
149 return -1;
150 strncpy(p, path, n);
151 p += n;
152 rem -= n;
153 if (rem < 1)
154 return -1;
155 *p = '\0';
156
157 return 0;
158}
159
160static err_t httpc_recv_cb(void *arg, struct altcp_pcb *pcb, struct pbuf *pbuf,
161 err_t err)
162{
163 struct wget_ctx *ctx = arg;
164 struct pbuf *buf;
165
166 if (!pbuf)
167 return ERR_BUF;
168
169 if (!ctx->start_time)
170 ctx->start_time = get_timer(0);
171
172 for (buf = pbuf; buf; buf = buf->next) {
173 memcpy((void *)ctx->daddr, buf->payload, buf->len);
174 ctx->daddr += buf->len;
175 ctx->size += buf->len;
176 if (ctx->size - ctx->prevsize > PROGRESS_PRINT_STEP_BYTES) {
177 printf("#");
178 ctx->prevsize = ctx->size;
179 }
180 }
181
182 altcp_recved(pcb, pbuf->tot_len);
183 pbuf_free(pbuf);
184 return ERR_OK;
185}
186
187static void httpc_result_cb(void *arg, httpc_result_t httpc_result,
188 u32_t rx_content_len, u32_t srv_res, err_t err)
189{
190 struct wget_ctx *ctx = arg;
191 ulong elapsed;
192
Adriano Cordova25e88412024-11-11 18:09:01 -0300193 wget_info->status_code = (u32)srv_res;
194
195 if (err == ERR_BUF) {
196 ctx->done = FAILURE;
197 return;
198 }
199
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200200 if (httpc_result != HTTPC_RESULT_OK) {
201 log_err("\nHTTP client error %d\n", httpc_result);
202 ctx->done = FAILURE;
203 return;
204 }
205 if (srv_res != 200) {
206 log_err("\nHTTP server error %d\n", srv_res);
207 ctx->done = FAILURE;
208 return;
209 }
210
211 elapsed = get_timer(ctx->start_time);
212 if (!elapsed)
213 elapsed = 1;
214 if (rx_content_len > PROGRESS_PRINT_STEP_BYTES)
215 printf("\n");
216 printf("%u bytes transferred in %lu ms (", rx_content_len, elapsed);
217 print_size(rx_content_len / elapsed * 1000, "/s)\n");
218 printf("Bytes transferred = %lu (%lx hex)\n", ctx->size, ctx->size);
Adriano Cordova25e88412024-11-11 18:09:01 -0300219 if (wget_info->set_bootdev) {
220 efi_set_bootdev("Net", "", ctx->path, map_sysmem(ctx->saved_daddr, 0),
221 rx_content_len);
222 }
223 wget_lwip_set_file_size(rx_content_len);
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200224 if (env_set_hex("filesize", rx_content_len) ||
225 env_set_hex("fileaddr", ctx->saved_daddr)) {
226 log_err("Could not set filesize or fileaddr\n");
227 ctx->done = FAILURE;
228 return;
229 }
230
231 ctx->done = SUCCESS;
232}
233
Adriano Cordova25e88412024-11-11 18:09:01 -0300234static err_t httpc_headers_done_cb(httpc_state_t *connection, void *arg, struct pbuf *hdr,
235 u16_t hdr_len, u32_t content_len)
236{
237 wget_lwip_fill_info(hdr, hdr_len, content_len);
238
239 if (wget_info->check_buffer_size && (ulong)content_len > wget_info->buffer_size)
240 return ERR_BUF;
241
242 return ERR_OK;
243}
244
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200245static int wget_loop(struct udevice *udev, ulong dst_addr, char *uri)
246{
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200247 httpc_connection_t conn;
248 httpc_state_t *state;
249 struct netif *netif;
250 struct wget_ctx ctx;
251 char *path;
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200252
253 ctx.daddr = dst_addr;
254 ctx.saved_daddr = dst_addr;
255 ctx.done = NOT_DONE;
256 ctx.size = 0;
257 ctx.prevsize = 0;
258 ctx.start_time = 0;
259
Adriano Cordovaa8a8d5c62024-11-11 18:09:00 -0300260 if (parse_url(uri, ctx.server_name, &ctx.port, &path))
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200261 return CMD_RET_USAGE;
262
263 netif = net_lwip_new_netif(udev);
264 if (!netif)
265 return -1;
266
267 memset(&conn, 0, sizeof(conn));
268 conn.result_fn = httpc_result_cb;
Adriano Cordova25e88412024-11-11 18:09:01 -0300269 conn.headers_done_fn = httpc_headers_done_cb;
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200270 ctx.path = path;
Adriano Cordovaa8a8d5c62024-11-11 18:09:00 -0300271 if (httpc_get_file_dns(ctx.server_name, ctx.port, path, &conn, httpc_recv_cb,
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200272 &ctx, &state)) {
273 net_lwip_remove_netif(netif);
274 return CMD_RET_FAILURE;
275 }
276
277 while (!ctx.done) {
278 net_lwip_rx(udev, netif);
279 sys_check_timeouts();
280 if (ctrlc())
281 break;
282 }
283
284 net_lwip_remove_netif(netif);
285
286 if (ctx.done == SUCCESS)
287 return 0;
288
289 return -1;
290}
291
292int wget_with_dns(ulong dst_addr, char *uri)
293{
294 eth_set_current();
295
Adriano Cordova25e88412024-11-11 18:09:01 -0300296 if (!wget_info)
297 wget_info = &default_wget_info;
298
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200299 return wget_loop(eth_get_dev(), dst_addr, uri);
300}
301
302int do_wget(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[])
303{
304 char *end;
305 char *url;
306 ulong dst_addr;
307 char nurl[1024];
308
309 if (argc < 2 || argc > 3)
310 return CMD_RET_USAGE;
311
312 dst_addr = hextoul(argv[1], &end);
313 if (end == (argv[1] + strlen(argv[1]))) {
314 if (argc < 3)
315 return CMD_RET_USAGE;
316 url = argv[2];
317 } else {
318 dst_addr = image_load_addr;
319 url = argv[1];
320 }
321
322 if (parse_legacy_arg(url, nurl, sizeof(nurl)))
323 return CMD_RET_FAILURE;
324
Adriano Cordova25e88412024-11-11 18:09:01 -0300325 wget_info = &default_wget_info;
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200326 if (wget_with_dns(dst_addr, nurl))
327 return CMD_RET_FAILURE;
328
329 return CMD_RET_SUCCESS;
330}
331
332/**
333 * wget_validate_uri() - validate the uri for wget
334 *
335 * @uri: uri string
336 *
337 * This function follows the current U-Boot wget implementation.
338 * scheme: only "http:" is supported
339 * authority:
340 * - user information: not supported
341 * - host: supported
342 * - port: not supported(always use the default port)
343 *
344 * Uri is expected to be correctly percent encoded.
345 * This is the minimum check, control codes(0x1-0x19, 0x7F, except '\0')
346 * and space character(0x20) are not allowed.
347 *
348 * TODO: stricter uri conformance check
349 *
350 * Return: true on success, false on failure
351 */
352bool wget_validate_uri(char *uri)
353{
354 char c;
355 bool ret = true;
356 char *str_copy, *s, *authority;
357
358 for (c = 0x1; c < 0x21; c++) {
359 if (strchr(uri, c)) {
360 log_err("invalid character is used\n");
361 return false;
362 }
363 }
364 if (strchr(uri, 0x7f)) {
365 log_err("invalid character is used\n");
366 return false;
367 }
368
369 if (strncmp(uri, "http://", 7)) {
370 log_err("only http:// is supported\n");
371 return false;
372 }
373 str_copy = strdup(uri);
374 if (!str_copy)
375 return false;
376
377 s = str_copy + strlen("http://");
378 authority = strsep(&s, "/");
379 if (!s) {
380 log_err("invalid uri, no file path\n");
381 ret = false;
382 goto out;
383 }
384 s = strchr(authority, '@');
385 if (s) {
386 log_err("user information is not supported\n");
387 ret = false;
388 goto out;
389 }
390
391out:
392 free(str_copy);
393
394 return ret;
395}