blob: 26086edd19253c98f2ab36467b89f313ca06c347 [file] [log] [blame]
Jerome Forissier3c656c92024-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>
Ilias Apalodimas5907c812024-11-10 10:28:40 +020010#include "lwip/altcp_tls.h"
Jerome Forissier3c656c92024-10-16 12:04:09 +020011#include <lwip/timeouts.h>
Ilias Apalodimas5907c812024-11-10 10:28:40 +020012#include <rng.h>
Jerome Forissier3c656c92024-10-16 12:04:09 +020013#include <mapmem.h>
14#include <net.h>
15#include <time.h>
Ilias Apalodimas5907c812024-11-10 10:28:40 +020016#include <dm/uclass.h>
Jerome Forissier3c656c92024-10-16 12:04:09 +020017
18#define SERVER_NAME_SIZE 200
19#define HTTP_PORT_DEFAULT 80
Ilias Apalodimas5907c812024-11-10 10:28:40 +020020#define HTTPS_PORT_DEFAULT 443
Jerome Forissier3c656c92024-10-16 12:04:09 +020021#define PROGRESS_PRINT_STEP_BYTES (100 * 1024)
22
23enum done_state {
Jerome Forissier356011f2024-11-07 12:27:57 +010024 NOT_DONE = 0,
25 SUCCESS = 1,
26 FAILURE = 2
Jerome Forissier3c656c92024-10-16 12:04:09 +020027};
28
29struct wget_ctx {
30 char *path;
31 ulong daddr;
32 ulong saved_daddr;
33 ulong size;
34 ulong prevsize;
35 ulong start_time;
36 enum done_state done;
37};
38
Ilias Apalodimas5907c812024-11-10 10:28:40 +020039bool wget_validate_uri(char *uri);
40
41int mbedtls_hardware_poll(void *data, unsigned char *output, size_t len,
42 size_t *olen)
43{
44 struct udevice *dev;
45 u64 rng = 0;
46 int ret;
47
48 *olen = 0;
49
50 ret = uclass_get_device(UCLASS_RNG, 0, &dev);
51 if (ret) {
52 log_err("Failed to get an rng: %d\n", ret);
53 return ret;
54 }
55 ret = dm_rng_read(dev, &rng, sizeof(rng));
56 if (ret)
57 return ret;
58
59 memcpy(output, &rng, len);
60 *olen = sizeof(rng);
61
62 return 0;
63}
64
65static int parse_url(char *url, char *host, u16 *port, char **path,
66 bool *is_https)
Jerome Forissier3c656c92024-10-16 12:04:09 +020067{
68 char *p, *pp;
69 long lport;
Ilias Apalodimas5907c812024-11-10 10:28:40 +020070 size_t prefix_len = 0;
Jerome Forissier3c656c92024-10-16 12:04:09 +020071
Ilias Apalodimas5907c812024-11-10 10:28:40 +020072 if (!wget_validate_uri(url)) {
73 log_err("Invalid URL. Use http(s)://\n");
Jerome Forissier3c656c92024-10-16 12:04:09 +020074 return -EINVAL;
75 }
76
Ilias Apalodimas5907c812024-11-10 10:28:40 +020077 *is_https = false;
78 *port = HTTP_PORT_DEFAULT;
79 prefix_len = strlen("http://");
80 p = strstr(url, "http://");
81 if (!p) {
82 p = strstr(url, "https://");
83 prefix_len = strlen("https://");
84 *port = HTTPS_PORT_DEFAULT;
85 *is_https = true;
86 }
87
88 p += prefix_len;
Jerome Forissier3c656c92024-10-16 12:04:09 +020089
90 /* Parse hostname */
91 pp = strchr(p, ':');
92 if (!pp)
93 pp = strchr(p, '/');
94 if (!pp)
95 return -EINVAL;
96
97 if (p + SERVER_NAME_SIZE <= pp)
98 return -EINVAL;
99
100 memcpy(host, p, pp - p);
101 host[pp - p] = '\0';
102
103 if (*pp == ':') {
104 /* Parse port number */
105 p = pp + 1;
106 lport = simple_strtol(p, &pp, 10);
107 if (pp && *pp != '/')
108 return -EINVAL;
109 if (lport > 65535)
110 return -EINVAL;
111 *port = (u16)lport;
Jerome Forissier3c656c92024-10-16 12:04:09 +0200112 }
Ilias Apalodimas5907c812024-11-10 10:28:40 +0200113
Jerome Forissier3c656c92024-10-16 12:04:09 +0200114 if (*pp != '/')
115 return -EINVAL;
116 *path = pp;
117
118 return 0;
119}
120
121/*
122 * Legacy syntax support
123 * Convert [<server_name_or_ip>:]filename into a URL if needed
124 */
125static int parse_legacy_arg(char *arg, char *nurl, size_t rem)
126{
127 char *p = nurl;
128 size_t n;
129 char *col = strchr(arg, ':');
130 char *env;
131 char *server;
132 char *path;
133
134 if (strstr(arg, "http") == arg) {
135 n = snprintf(nurl, rem, "%s", arg);
136 if (n < 0 || n > rem)
137 return -1;
138 return 0;
139 }
140
141 n = snprintf(p, rem, "%s", "http://");
142 if (n < 0 || n > rem)
143 return -1;
144 p += n;
145 rem -= n;
146
147 if (col) {
148 n = col - arg;
149 server = arg;
150 path = col + 1;
151 } else {
152 env = env_get("httpserverip");
153 if (!env)
154 env = env_get("serverip");
155 if (!env) {
156 log_err("error: httpserver/serverip has to be set\n");
157 return -1;
158 }
159 n = strlen(env);
160 server = env;
161 path = arg;
162 }
163
164 if (rem < n)
165 return -1;
Jerome Forissier356011f2024-11-07 12:27:57 +0100166 strlcpy(p, server, n);
Jerome Forissier3c656c92024-10-16 12:04:09 +0200167 p += n;
168 rem -= n;
169 if (rem < 1)
170 return -1;
171 *p = '/';
172 p++;
173 rem--;
174 n = strlen(path);
175 if (rem < n)
176 return -1;
Jerome Forissier356011f2024-11-07 12:27:57 +0100177 strlcpy(p, path, n);
Jerome Forissier3c656c92024-10-16 12:04:09 +0200178 p += n;
179 rem -= n;
180 if (rem < 1)
181 return -1;
182 *p = '\0';
183
184 return 0;
185}
186
187static err_t httpc_recv_cb(void *arg, struct altcp_pcb *pcb, struct pbuf *pbuf,
188 err_t err)
189{
190 struct wget_ctx *ctx = arg;
191 struct pbuf *buf;
192
193 if (!pbuf)
194 return ERR_BUF;
195
196 if (!ctx->start_time)
197 ctx->start_time = get_timer(0);
198
199 for (buf = pbuf; buf; buf = buf->next) {
200 memcpy((void *)ctx->daddr, buf->payload, buf->len);
201 ctx->daddr += buf->len;
202 ctx->size += buf->len;
203 if (ctx->size - ctx->prevsize > PROGRESS_PRINT_STEP_BYTES) {
204 printf("#");
205 ctx->prevsize = ctx->size;
206 }
207 }
208
209 altcp_recved(pcb, pbuf->tot_len);
210 pbuf_free(pbuf);
211 return ERR_OK;
212}
213
214static void httpc_result_cb(void *arg, httpc_result_t httpc_result,
215 u32_t rx_content_len, u32_t srv_res, err_t err)
216{
217 struct wget_ctx *ctx = arg;
218 ulong elapsed;
219
220 if (httpc_result != HTTPC_RESULT_OK) {
221 log_err("\nHTTP client error %d\n", httpc_result);
222 ctx->done = FAILURE;
223 return;
224 }
225 if (srv_res != 200) {
226 log_err("\nHTTP server error %d\n", srv_res);
227 ctx->done = FAILURE;
228 return;
229 }
230
231 elapsed = get_timer(ctx->start_time);
232 if (!elapsed)
233 elapsed = 1;
234 if (rx_content_len > PROGRESS_PRINT_STEP_BYTES)
235 printf("\n");
236 printf("%u bytes transferred in %lu ms (", rx_content_len, elapsed);
237 print_size(rx_content_len / elapsed * 1000, "/s)\n");
238 printf("Bytes transferred = %lu (%lx hex)\n", ctx->size, ctx->size);
239 efi_set_bootdev("Net", "", ctx->path, map_sysmem(ctx->saved_daddr, 0),
240 rx_content_len);
241 if (env_set_hex("filesize", rx_content_len) ||
242 env_set_hex("fileaddr", ctx->saved_daddr)) {
243 log_err("Could not set filesize or fileaddr\n");
244 ctx->done = FAILURE;
245 return;
246 }
247
248 ctx->done = SUCCESS;
249}
250
251static int wget_loop(struct udevice *udev, ulong dst_addr, char *uri)
252{
253 char server_name[SERVER_NAME_SIZE];
Ilias Apalodimas5907c812024-11-10 10:28:40 +0200254#if defined CONFIG_WGET_HTTPS
255 altcp_allocator_t tls_allocator;
256#endif
Jerome Forissier3c656c92024-10-16 12:04:09 +0200257 httpc_connection_t conn;
258 httpc_state_t *state;
259 struct netif *netif;
260 struct wget_ctx ctx;
261 char *path;
262 u16 port;
Ilias Apalodimas5907c812024-11-10 10:28:40 +0200263 bool is_https;
Jerome Forissier3c656c92024-10-16 12:04:09 +0200264
265 ctx.daddr = dst_addr;
266 ctx.saved_daddr = dst_addr;
267 ctx.done = NOT_DONE;
268 ctx.size = 0;
269 ctx.prevsize = 0;
270 ctx.start_time = 0;
271
Ilias Apalodimas5907c812024-11-10 10:28:40 +0200272 if (parse_url(uri, server_name, &port, &path, &is_https))
Jerome Forissier3c656c92024-10-16 12:04:09 +0200273 return CMD_RET_USAGE;
274
275 netif = net_lwip_new_netif(udev);
276 if (!netif)
277 return -1;
278
279 memset(&conn, 0, sizeof(conn));
Ilias Apalodimas5907c812024-11-10 10:28:40 +0200280#if defined CONFIG_WGET_HTTPS
281 if (is_https) {
282 tls_allocator.alloc = &altcp_tls_alloc;
283 tls_allocator.arg =
284 altcp_tls_create_config_client(NULL, 0, server_name);
285
286 if (!tls_allocator.arg) {
287 log_err("error: Cannot create a TLS connection\n");
288 net_lwip_remove_netif(netif);
289 return -1;
290 }
291
292 conn.altcp_allocator = &tls_allocator;
293 }
294#endif
295
Jerome Forissier3c656c92024-10-16 12:04:09 +0200296 conn.result_fn = httpc_result_cb;
297 ctx.path = path;
298 if (httpc_get_file_dns(server_name, port, path, &conn, httpc_recv_cb,
299 &ctx, &state)) {
300 net_lwip_remove_netif(netif);
301 return CMD_RET_FAILURE;
302 }
303
304 while (!ctx.done) {
305 net_lwip_rx(udev, netif);
306 sys_check_timeouts();
307 if (ctrlc())
308 break;
309 }
310
311 net_lwip_remove_netif(netif);
312
313 if (ctx.done == SUCCESS)
314 return 0;
315
316 return -1;
317}
318
319int wget_with_dns(ulong dst_addr, char *uri)
320{
321 eth_set_current();
322
323 return wget_loop(eth_get_dev(), dst_addr, uri);
324}
325
326int do_wget(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[])
327{
328 char *end;
329 char *url;
330 ulong dst_addr;
331 char nurl[1024];
332
333 if (argc < 2 || argc > 3)
334 return CMD_RET_USAGE;
335
336 dst_addr = hextoul(argv[1], &end);
Jerome Forissier356011f2024-11-07 12:27:57 +0100337 if (end == (argv[1] + strlen(argv[1]))) {
Jerome Forissier3c656c92024-10-16 12:04:09 +0200338 if (argc < 3)
339 return CMD_RET_USAGE;
340 url = argv[2];
341 } else {
342 dst_addr = image_load_addr;
343 url = argv[1];
344 }
345
346 if (parse_legacy_arg(url, nurl, sizeof(nurl)))
Jerome Forissier356011f2024-11-07 12:27:57 +0100347 return CMD_RET_FAILURE;
Jerome Forissier3c656c92024-10-16 12:04:09 +0200348
349 if (wget_with_dns(dst_addr, nurl))
350 return CMD_RET_FAILURE;
351
352 return CMD_RET_SUCCESS;
353}
354
355/**
356 * wget_validate_uri() - validate the uri for wget
357 *
358 * @uri: uri string
359 *
360 * This function follows the current U-Boot wget implementation.
361 * scheme: only "http:" is supported
362 * authority:
363 * - user information: not supported
364 * - host: supported
365 * - port: not supported(always use the default port)
366 *
367 * Uri is expected to be correctly percent encoded.
368 * This is the minimum check, control codes(0x1-0x19, 0x7F, except '\0')
369 * and space character(0x20) are not allowed.
370 *
371 * TODO: stricter uri conformance check
372 *
373 * Return: true on success, false on failure
374 */
375bool wget_validate_uri(char *uri)
376{
377 char c;
378 bool ret = true;
379 char *str_copy, *s, *authority;
Ilias Apalodimas5907c812024-11-10 10:28:40 +0200380 size_t prefix_len = 0;
Jerome Forissier3c656c92024-10-16 12:04:09 +0200381
382 for (c = 0x1; c < 0x21; c++) {
383 if (strchr(uri, c)) {
384 log_err("invalid character is used\n");
385 return false;
386 }
387 }
Ilias Apalodimas5907c812024-11-10 10:28:40 +0200388
Jerome Forissier3c656c92024-10-16 12:04:09 +0200389 if (strchr(uri, 0x7f)) {
390 log_err("invalid character is used\n");
391 return false;
392 }
393
Ilias Apalodimas5907c812024-11-10 10:28:40 +0200394 if (!strncmp(uri, "http://", strlen("http://"))) {
395 prefix_len = strlen("http://");
396 } else if (!strncmp(uri, "https://", strlen("https://"))) {
397 prefix_len = strlen("https://");
398 } else {
399 log_err("only http(s):// is supported\n");
Jerome Forissier3c656c92024-10-16 12:04:09 +0200400 return false;
401 }
Ilias Apalodimas5907c812024-11-10 10:28:40 +0200402
Jerome Forissier3c656c92024-10-16 12:04:09 +0200403 str_copy = strdup(uri);
404 if (!str_copy)
405 return false;
406
407 s = str_copy + strlen("http://");
408 authority = strsep(&s, "/");
409 if (!s) {
410 log_err("invalid uri, no file path\n");
411 ret = false;
412 goto out;
413 }
414 s = strchr(authority, '@');
415 if (s) {
416 log_err("user information is not supported\n");
417 ret = false;
418 goto out;
419 }
420
421out:
422 free(str_copy);
423
424 return ret;
425}