]>
Commit | Line | Data |
---|---|---|
cfbae482 YCLP |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * WGET/HTTP support driver based on U-BOOT's nfs.c | |
4 | * Copyright Duncan Hare <dh@synoia.com> 2017 | |
5 | */ | |
6 | ||
04592adb | 7 | #include <asm/global_data.h> |
cfbae482 | 8 | #include <command.h> |
fe1489bc | 9 | #include <display_options.h> |
cfbae482 | 10 | #include <env.h> |
0ebbed66 | 11 | #include <efi_loader.h> |
cfbae482 | 12 | #include <image.h> |
04592adb | 13 | #include <lmb.h> |
cfbae482 YCLP |
14 | #include <mapmem.h> |
15 | #include <net.h> | |
16 | #include <net/tcp.h> | |
17 | #include <net/wget.h> | |
8cf18da1 | 18 | #include <stdlib.h> |
cfbae482 | 19 | |
04592adb MK |
20 | DECLARE_GLOBAL_DATA_PTR; |
21 | ||
4caacb2f MV |
22 | /* The default, change with environment variable 'httpdstp' */ |
23 | #define SERVER_PORT 80 | |
24 | ||
bf962de9 | 25 | #define HASHES_PER_LINE 65 |
cfbae482 | 26 | |
bf962de9 | 27 | #define HTTP_MAX_HDR_LEN 2048 |
cfbae482 | 28 | |
bf962de9 MK |
29 | #define HTTP_STATUS_BAD 0 |
30 | #define HTTP_STATUS_OK 200 | |
cfbae482 | 31 | |
bf962de9 MK |
32 | static const char http_proto[] = "HTTP/1.0"; |
33 | static const char http_eom[] = "\r\n\r\n"; | |
34 | static const char content_len[] = "Content-Length:"; | |
35 | static const char linefeed[] = "\r\n"; | |
36 | static struct in_addr web_server_ip; | |
37 | static unsigned int server_port; | |
38 | static unsigned long content_length; | |
39 | static u32 http_hdr_size, max_rx_pos; | |
40 | static int wget_tsize_num_hash; | |
cfbae482 YCLP |
41 | |
42 | static char *image_url; | |
cfbae482 YCLP |
43 | static enum net_loop_state wget_loop_state; |
44 | ||
cfbae482 YCLP |
45 | /** |
46 | * store_block() - store block in memory | |
47 | * @src: source of data | |
48 | * @offset: offset | |
49 | * @len: length | |
50 | */ | |
51 | static inline int store_block(uchar *src, unsigned int offset, unsigned int len) | |
52 | { | |
04592adb | 53 | ulong store_addr = image_load_addr + offset; |
cfbae482 YCLP |
54 | uchar *ptr; |
55 | ||
32a6c5ea AC |
56 | // Avoid overflow |
57 | if (wget_info->buffer_size && wget_info->buffer_size < offset + len) | |
58 | return -1; | |
2dd076a9 | 59 | if (CONFIG_IS_ENABLED(LMB) && wget_info->set_bootdev) { |
04592adb | 60 | if (store_addr < image_load_addr || |
51ebd514 | 61 | lmb_read_check(store_addr, len)) { |
9349fc2e JF |
62 | if (!wget_info->silent) { |
63 | printf("\nwget error: "); | |
64 | printf("trying to overwrite reserved memory\n"); | |
65 | } | |
04592adb MK |
66 | return -1; |
67 | } | |
68 | } | |
69 | ||
70 | ptr = map_sysmem(store_addr, len); | |
cfbae482 YCLP |
71 | memcpy(ptr, src, len); |
72 | unmap_sysmem(ptr); | |
73 | ||
cfbae482 YCLP |
74 | return 0; |
75 | } | |
76 | ||
bf962de9 | 77 | static void show_block_marker(u32 packets) |
cfbae482 | 78 | { |
bf962de9 | 79 | int cnt; |
cfbae482 | 80 | |
9349fc2e JF |
81 | if (wget_info->silent) |
82 | return; | |
83 | ||
bf962de9 MK |
84 | if (content_length != -1) { |
85 | if (net_boot_file_size > content_length) | |
86 | content_length = net_boot_file_size; | |
cfbae482 | 87 | |
bf962de9 MK |
88 | cnt = net_boot_file_size * 50 / content_length; |
89 | while (wget_tsize_num_hash < cnt) { | |
90 | putc('#'); | |
91 | wget_tsize_num_hash++; | |
92 | } | |
93 | } else { | |
94 | if ((packets % 10) == 0) | |
95 | putc('#'); | |
96 | else if (((packets + 1) % (10 * HASHES_PER_LINE)) == 0) | |
97 | puts("\n"); | |
cfbae482 YCLP |
98 | } |
99 | } | |
100 | ||
bf962de9 | 101 | static void tcp_stream_on_closed(struct tcp_stream *tcp) |
cfbae482 | 102 | { |
bf962de9 MK |
103 | if (tcp->status != TCP_ERR_OK) |
104 | wget_loop_state = NETLOOP_FAIL; | |
105 | ||
106 | net_set_state(wget_loop_state); | |
107 | if (wget_loop_state != NETLOOP_SUCCESS) { | |
108 | net_boot_file_size = 0; | |
9349fc2e JF |
109 | if (!wget_info->silent) |
110 | printf("\nwget: Transfer Fail, TCP status - %d\n", | |
111 | tcp->status); | |
bf962de9 MK |
112 | return; |
113 | } | |
cfbae482 | 114 | |
9349fc2e JF |
115 | if (!wget_info->silent) |
116 | printf("\nPackets received %d, Transfer Successful\n", | |
117 | tcp->rx_packets); | |
bf962de9 MK |
118 | wget_info->file_size = net_boot_file_size; |
119 | if (wget_info->method == WGET_HTTP_METHOD_GET && wget_info->set_bootdev) { | |
120 | efi_set_bootdev("Http", NULL, image_url, | |
121 | map_sysmem(image_load_addr, 0), | |
122 | net_boot_file_size); | |
123 | env_set_hex("filesize", net_boot_file_size); | |
124 | } | |
cfbae482 YCLP |
125 | } |
126 | ||
bf962de9 | 127 | static void tcp_stream_on_rcv_nxt_update(struct tcp_stream *tcp, u32 rx_bytes) |
cfbae482 | 128 | { |
bf962de9 MK |
129 | char *pos, *tail; |
130 | uchar saved, *ptr; | |
131 | int reply_len; | |
cfbae482 | 132 | |
bf962de9 MK |
133 | if (http_hdr_size) { |
134 | net_boot_file_size = rx_bytes - http_hdr_size; | |
135 | show_block_marker(tcp->rx_packets); | |
136 | return; | |
cfbae482 | 137 | } |
cfbae482 | 138 | |
bf962de9 | 139 | ptr = map_sysmem(image_load_addr, rx_bytes + 1); |
cfbae482 | 140 | |
bf962de9 MK |
141 | saved = ptr[rx_bytes]; |
142 | ptr[rx_bytes] = '\0'; | |
143 | pos = strstr((char *)ptr, http_eom); | |
144 | ptr[rx_bytes] = saved; | |
145 | ||
146 | if (!pos) { | |
147 | if (rx_bytes < HTTP_MAX_HDR_LEN && | |
148 | tcp->state == TCP_ESTABLISHED) | |
149 | goto end; | |
150 | ||
9349fc2e JF |
151 | if (!wget_info->silent) |
152 | printf("ERROR: misssed HTTP header\n"); | |
bf962de9 MK |
153 | tcp_stream_close(tcp); |
154 | goto end; | |
737c2dca | 155 | } |
2dd076a9 | 156 | |
bf962de9 MK |
157 | http_hdr_size = pos - (char *)ptr + strlen(http_eom); |
158 | *pos = '\0'; | |
159 | ||
160 | if (wget_info->headers && http_hdr_size < MAX_HTTP_HEADERS_SIZE) | |
161 | strcpy(wget_info->headers, ptr); | |
162 | ||
163 | /* check for HTTP proto */ | |
164 | if (strncasecmp((char *)ptr, "HTTP/", 5)) { | |
165 | debug_cond(DEBUG_WGET, "wget: Connected Bad Xfer " | |
166 | "(no HTTP Status Line found)\n"); | |
167 | tcp_stream_close(tcp); | |
168 | goto end; | |
2dd076a9 AC |
169 | } |
170 | ||
bf962de9 MK |
171 | /* get HTTP reply len */ |
172 | pos = strstr((char *)ptr, linefeed); | |
173 | if (pos) | |
174 | reply_len = pos - (char *)ptr; | |
175 | else | |
176 | reply_len = http_hdr_size - strlen(http_eom); | |
177 | ||
178 | pos = strchr((char *)ptr, ' '); | |
179 | if (!pos || pos - (char *)ptr > reply_len) { | |
180 | debug_cond(DEBUG_WGET, "wget: Connected Bad Xfer " | |
181 | "(no HTTP Status Code found)\n"); | |
182 | tcp_stream_close(tcp); | |
183 | goto end; | |
2dd076a9 AC |
184 | } |
185 | ||
bf962de9 MK |
186 | wget_info->status_code = (u32)simple_strtoul(pos + 1, &tail, 10); |
187 | if (tail == pos + 1 || *tail != ' ') { | |
188 | debug_cond(DEBUG_WGET, "wget: Connected Bad Xfer " | |
189 | "(bad HTTP Status Code)\n"); | |
190 | tcp_stream_close(tcp); | |
191 | goto end; | |
192 | } | |
2dd076a9 | 193 | |
bf962de9 MK |
194 | debug_cond(DEBUG_WGET, |
195 | "wget: HTTP Status Code %d\n", wget_info->status_code); | |
196 | ||
197 | if (wget_info->status_code != HTTP_STATUS_OK) { | |
198 | debug_cond(DEBUG_WGET, "wget: Connected Bad Xfer\n"); | |
199 | tcp_stream_close(tcp); | |
200 | goto end; | |
201 | } | |
2dd076a9 | 202 | |
bf962de9 MK |
203 | debug_cond(DEBUG_WGET, "wget: Connctd pkt %p hlen %x\n", |
204 | ptr, http_hdr_size); | |
2dd076a9 | 205 | |
bf962de9 MK |
206 | content_length = -1; |
207 | pos = strstr((char *)ptr, content_len); | |
2dd076a9 | 208 | if (pos) { |
bf962de9 | 209 | pos += strlen(content_len) + 1; |
2dd076a9 AC |
210 | while (*pos == ' ') |
211 | pos++; | |
bf962de9 MK |
212 | content_length = simple_strtoul(pos, &tail, 10); |
213 | if (*tail != '\r' && *tail != '\n' && *tail != '\0') | |
214 | content_length = -1; | |
215 | } | |
216 | ||
217 | if (content_length >= 0) { | |
2dd076a9 AC |
218 | debug_cond(DEBUG_WGET, |
219 | "wget: Connected Len %lu\n", | |
220 | content_length); | |
221 | wget_info->hdr_cont_len = content_length; | |
32a6c5ea AC |
222 | if (wget_info->buffer_size && wget_info->buffer_size < wget_info->hdr_cont_len){ |
223 | tcp_stream_reset(tcp); | |
224 | goto end; | |
225 | } | |
226 | ||
2dd076a9 | 227 | } |
2dd076a9 | 228 | |
bf962de9 MK |
229 | net_boot_file_size = rx_bytes - http_hdr_size; |
230 | memmove(ptr, ptr + http_hdr_size, max_rx_pos + 1 - http_hdr_size); | |
231 | wget_loop_state = NETLOOP_SUCCESS; | |
cfbae482 | 232 | |
bf962de9 MK |
233 | end: |
234 | unmap_sysmem(ptr); | |
235 | } | |
a8bd5ec0 | 236 | |
bf962de9 MK |
237 | static int tcp_stream_rx(struct tcp_stream *tcp, u32 rx_offs, void *buf, int len) |
238 | { | |
239 | if ((max_rx_pos == (u32)(-1)) || (max_rx_pos < rx_offs + len - 1)) | |
240 | max_rx_pos = rx_offs + len - 1; | |
a8bd5ec0 | 241 | |
32a6c5ea AC |
242 | // Avoid overflow |
243 | if (store_block(buf, rx_offs - http_hdr_size, len) < 0) | |
244 | return -1; | |
2dd076a9 | 245 | |
bf962de9 | 246 | return len; |
cfbae482 YCLP |
247 | } |
248 | ||
bf962de9 | 249 | static int tcp_stream_tx(struct tcp_stream *tcp, u32 tx_offs, void *buf, int maxlen) |
cfbae482 | 250 | { |
bf962de9 MK |
251 | int ret; |
252 | const char *method; | |
cfbae482 | 253 | |
bf962de9 MK |
254 | if (tx_offs) |
255 | return 0; | |
cfbae482 | 256 | |
bf962de9 MK |
257 | switch (wget_info->method) { |
258 | case WGET_HTTP_METHOD_HEAD: | |
259 | method = "HEAD"; | |
cfbae482 | 260 | break; |
bf962de9 MK |
261 | case WGET_HTTP_METHOD_GET: |
262 | default: | |
263 | method = "GET"; | |
cfbae482 | 264 | break; |
bf962de9 | 265 | } |
cfbae482 | 266 | |
bf962de9 MK |
267 | ret = snprintf(buf, maxlen, "%s %s %s\r\n\r\n", |
268 | method, image_url, http_proto); | |
cab7867c | 269 | |
bf962de9 MK |
270 | return ret; |
271 | } | |
cfbae482 | 272 | |
bf962de9 MK |
273 | static int tcp_stream_on_create(struct tcp_stream *tcp) |
274 | { | |
275 | if (tcp->rhost.s_addr != web_server_ip.s_addr || | |
276 | tcp->rport != server_port) | |
277 | return 0; | |
278 | ||
279 | tcp->max_retry_count = WGET_RETRY_COUNT; | |
280 | tcp->initial_timeout = WGET_TIMEOUT; | |
281 | tcp->on_closed = tcp_stream_on_closed; | |
282 | tcp->on_rcv_nxt_update = tcp_stream_on_rcv_nxt_update; | |
283 | tcp->rx = tcp_stream_rx; | |
284 | tcp->tx = tcp_stream_tx; | |
285 | ||
286 | return 1; | |
cfbae482 YCLP |
287 | } |
288 | ||
cfbae482 YCLP |
289 | #define BLOCKSIZE 512 |
290 | ||
291 | void wget_start(void) | |
292 | { | |
bf962de9 | 293 | struct tcp_stream *tcp; |
2b177498 | 294 | |
2dd076a9 AC |
295 | if (!wget_info) |
296 | wget_info = &default_wget_info; | |
297 | ||
cfbae482 YCLP |
298 | image_url = strchr(net_boot_file_name, ':'); |
299 | if (image_url > 0) { | |
300 | web_server_ip = string_to_ip(net_boot_file_name); | |
301 | ++image_url; | |
302 | net_server_ip = web_server_ip; | |
303 | } else { | |
304 | web_server_ip = net_server_ip; | |
305 | image_url = net_boot_file_name; | |
306 | } | |
307 | ||
308 | debug_cond(DEBUG_WGET, | |
309 | "wget: Transfer HTTP Server %pI4; our IP %pI4\n", | |
310 | &web_server_ip, &net_ip); | |
311 | ||
312 | /* Check if we need to send across this subnet */ | |
313 | if (net_gateway.s_addr && net_netmask.s_addr) { | |
314 | struct in_addr our_net; | |
315 | struct in_addr server_net; | |
316 | ||
317 | our_net.s_addr = net_ip.s_addr & net_netmask.s_addr; | |
318 | server_net.s_addr = net_server_ip.s_addr & net_netmask.s_addr; | |
319 | if (our_net.s_addr != server_net.s_addr) | |
320 | debug_cond(DEBUG_WGET, | |
321 | "wget: sending through gateway %pI4", | |
322 | &net_gateway); | |
323 | } | |
324 | debug_cond(DEBUG_WGET, "URL '%s'\n", image_url); | |
325 | ||
326 | if (net_boot_file_expected_size_in_blocks) { | |
327 | debug_cond(DEBUG_WGET, "wget: Size is 0x%x Bytes = ", | |
328 | net_boot_file_expected_size_in_blocks * BLOCKSIZE); | |
329 | print_size(net_boot_file_expected_size_in_blocks * BLOCKSIZE, | |
330 | ""); | |
331 | } | |
332 | debug_cond(DEBUG_WGET, | |
333 | "\nwget:Load address: 0x%lx\nLoading: *\b", image_load_addr); | |
334 | ||
cfbae482 YCLP |
335 | /* |
336 | * Zero out server ether to force arp resolution in case | |
337 | * the server ip for the previous u-boot command, for example dns | |
338 | * is not the same as the web server ip. | |
339 | */ | |
340 | ||
341 | memset(net_server_ethaddr, 0, 6); | |
342 | ||
bf962de9 MK |
343 | max_rx_pos = (u32)(-1); |
344 | net_boot_file_size = 0; | |
345 | http_hdr_size = 0; | |
346 | wget_tsize_num_hash = 0; | |
347 | wget_loop_state = NETLOOP_FAIL; | |
348 | ||
349 | wget_info->status_code = HTTP_STATUS_BAD; | |
350 | wget_info->file_size = 0; | |
351 | wget_info->hdr_cont_len = 0; | |
352 | if (wget_info->headers) | |
353 | wget_info->headers[0] = 0; | |
354 | ||
2b177498 | 355 | server_port = env_get_ulong("httpdstp", 10, SERVER_PORT) & 0xffff; |
bf962de9 | 356 | tcp_stream_set_on_create_handler(tcp_stream_on_create); |
2b177498 MK |
357 | tcp = tcp_stream_connect(web_server_ip, server_port); |
358 | if (!tcp) { | |
9349fc2e JF |
359 | if (!wget_info->silent) |
360 | printf("No free tcp streams\n"); | |
2b177498 MK |
361 | net_set_state(NETLOOP_FAIL); |
362 | return; | |
363 | } | |
bf962de9 | 364 | tcp_stream_put(tcp); |
cfbae482 | 365 | } |
8cf18da1 | 366 | |
9bab7d2a | 367 | int wget_do_request(ulong dst_addr, char *uri) |
8cf18da1 MK |
368 | { |
369 | int ret; | |
370 | char *s, *host_name, *file_name, *str_copy; | |
371 | ||
372 | /* | |
373 | * Download file using wget. | |
374 | * | |
375 | * U-Boot wget takes the target uri in this format. | |
376 | * "<http server ip>:<file path>" e.g.) 192.168.1.1:/sample/test.iso | |
377 | * Need to resolve the http server ip address before starting wget. | |
378 | */ | |
379 | str_copy = strdup(uri); | |
380 | if (!str_copy) | |
381 | return -ENOMEM; | |
382 | ||
383 | s = str_copy + strlen("http://"); | |
384 | host_name = strsep(&s, "/"); | |
385 | if (!s) { | |
8cf18da1 MK |
386 | ret = -EINVAL; |
387 | goto out; | |
388 | } | |
389 | file_name = s; | |
390 | ||
9bab7d2a AC |
391 | host_name = strsep(&host_name, ":"); |
392 | ||
393 | if (string_to_ip(host_name).s_addr) { | |
394 | s = host_name; | |
395 | } else { | |
396 | #if IS_ENABLED(CONFIG_CMD_DNS) | |
397 | net_dns_resolve = host_name; | |
398 | net_dns_env_var = "httpserverip"; | |
399 | if (net_loop(DNS) < 0) { | |
400 | ret = -EINVAL; | |
401 | goto out; | |
402 | } | |
403 | s = env_get("httpserverip"); | |
404 | if (!s) { | |
405 | ret = -EINVAL; | |
406 | goto out; | |
407 | } | |
408 | #else | |
8cf18da1 MK |
409 | ret = -EINVAL; |
410 | goto out; | |
9bab7d2a | 411 | #endif |
8cf18da1 MK |
412 | } |
413 | ||
414 | strlcpy(net_boot_file_name, s, sizeof(net_boot_file_name)); | |
415 | strlcat(net_boot_file_name, ":/", sizeof(net_boot_file_name)); /* append '/' which is removed by strsep() */ | |
416 | strlcat(net_boot_file_name, file_name, sizeof(net_boot_file_name)); | |
417 | image_load_addr = dst_addr; | |
418 | ret = net_loop(WGET); | |
419 | ||
420 | out: | |
421 | free(str_copy); | |
422 | ||
de28a2a5 | 423 | return ret < 0 ? ret : 0; |
8cf18da1 | 424 | } |
f01c961e MK |
425 | |
426 | /** | |
427 | * wget_validate_uri() - validate the uri for wget | |
428 | * | |
429 | * @uri: uri string | |
430 | * | |
431 | * This function follows the current U-Boot wget implementation. | |
432 | * scheme: only "http:" is supported | |
433 | * authority: | |
434 | * - user information: not supported | |
435 | * - host: supported | |
436 | * - port: not supported(always use the default port) | |
437 | * | |
438 | * Uri is expected to be correctly percent encoded. | |
439 | * This is the minimum check, control codes(0x1-0x19, 0x7F, except '\0') | |
440 | * and space character(0x20) are not allowed. | |
441 | * | |
442 | * TODO: stricter uri conformance check | |
443 | * | |
444 | * Return: true on success, false on failure | |
445 | */ | |
446 | bool wget_validate_uri(char *uri) | |
447 | { | |
448 | char c; | |
449 | bool ret = true; | |
450 | char *str_copy, *s, *authority; | |
451 | ||
452 | for (c = 0x1; c < 0x21; c++) { | |
453 | if (strchr(uri, c)) { | |
454 | log_err("invalid character is used\n"); | |
455 | return false; | |
456 | } | |
457 | } | |
458 | if (strchr(uri, 0x7f)) { | |
459 | log_err("invalid character is used\n"); | |
460 | return false; | |
461 | } | |
462 | ||
463 | if (strncmp(uri, "http://", 7)) { | |
464 | log_err("only http:// is supported\n"); | |
465 | return false; | |
466 | } | |
467 | str_copy = strdup(uri); | |
468 | if (!str_copy) | |
469 | return false; | |
470 | ||
471 | s = str_copy + strlen("http://"); | |
472 | authority = strsep(&s, "/"); | |
473 | if (!s) { | |
474 | log_err("invalid uri, no file path\n"); | |
475 | ret = false; | |
476 | goto out; | |
477 | } | |
478 | s = strchr(authority, '@'); | |
479 | if (s) { | |
480 | log_err("user information is not supported\n"); | |
481 | ret = false; | |
482 | goto out; | |
483 | } | |
484 | s = strchr(authority, ':'); | |
485 | if (s) { | |
486 | log_err("user defined port is not supported\n"); | |
487 | ret = false; | |
488 | goto out; | |
489 | } | |
490 | ||
491 | out: | |
492 | free(str_copy); | |
493 | ||
494 | return ret; | |
495 | } |