1 // SPDX-License-Identifier: GPL-2.0
3 * WGET/HTTP support driver based on U-BOOT's nfs.c
4 * Copyright Duncan Hare <dh@synoia.com> 2017
7 #include <asm/global_data.h>
9 #include <display_options.h>
11 #include <efi_loader.h>
20 DECLARE_GLOBAL_DATA_PTR
;
22 /* The default, change with environment variable 'httpdstp' */
23 #define SERVER_PORT 80
25 #define HASHES_PER_LINE 65
27 #define HTTP_MAX_HDR_LEN 2048
29 #define HTTP_STATUS_BAD 0
30 #define HTTP_STATUS_OK 200
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
;
42 static char *image_url
;
43 static enum net_loop_state wget_loop_state
;
46 * store_block() - store block in memory
47 * @src: source of data
51 static inline int store_block(uchar
*src
, unsigned int offset
, unsigned int len
)
53 ulong store_addr
= image_load_addr
+ offset
;
57 if (wget_info
->buffer_size
&& wget_info
->buffer_size
< offset
+ len
)
59 if (CONFIG_IS_ENABLED(LMB
) && wget_info
->set_bootdev
) {
60 if (store_addr
< image_load_addr
||
61 lmb_read_check(store_addr
, len
)) {
62 if (!wget_info
->silent
) {
63 printf("\nwget error: ");
64 printf("trying to overwrite reserved memory\n");
70 ptr
= map_sysmem(store_addr
, len
);
71 memcpy(ptr
, src
, len
);
77 static void show_block_marker(u32 packets
)
81 if (wget_info
->silent
)
84 if (content_length
!= -1) {
85 if (net_boot_file_size
> content_length
)
86 content_length
= net_boot_file_size
;
88 cnt
= net_boot_file_size
* 50 / content_length
;
89 while (wget_tsize_num_hash
< cnt
) {
91 wget_tsize_num_hash
++;
94 if ((packets
% 10) == 0)
96 else if (((packets
+ 1) % (10 * HASHES_PER_LINE
)) == 0)
101 static void tcp_stream_on_closed(struct tcp_stream
*tcp
)
103 if (tcp
->status
!= TCP_ERR_OK
)
104 wget_loop_state
= NETLOOP_FAIL
;
106 net_set_state(wget_loop_state
);
107 if (wget_loop_state
!= NETLOOP_SUCCESS
) {
108 net_boot_file_size
= 0;
109 if (!wget_info
->silent
)
110 printf("\nwget: Transfer Fail, TCP status - %d\n",
115 if (!wget_info
->silent
)
116 printf("\nPackets received %d, Transfer Successful\n",
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),
123 env_set_hex("filesize", net_boot_file_size
);
127 static void tcp_stream_on_rcv_nxt_update(struct tcp_stream
*tcp
, u32 rx_bytes
)
134 net_boot_file_size
= rx_bytes
- http_hdr_size
;
135 show_block_marker(tcp
->rx_packets
);
139 ptr
= map_sysmem(image_load_addr
, rx_bytes
+ 1);
141 saved
= ptr
[rx_bytes
];
142 ptr
[rx_bytes
] = '\0';
143 pos
= strstr((char *)ptr
, http_eom
);
144 ptr
[rx_bytes
] = saved
;
147 if (rx_bytes
< HTTP_MAX_HDR_LEN
&&
148 tcp
->state
== TCP_ESTABLISHED
)
151 if (!wget_info
->silent
)
152 printf("ERROR: misssed HTTP header\n");
153 tcp_stream_close(tcp
);
157 http_hdr_size
= pos
- (char *)ptr
+ strlen(http_eom
);
160 if (wget_info
->headers
&& http_hdr_size
< MAX_HTTP_HEADERS_SIZE
)
161 strcpy(wget_info
->headers
, ptr
);
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
);
171 /* get HTTP reply len */
172 pos
= strstr((char *)ptr
, linefeed
);
174 reply_len
= pos
- (char *)ptr
;
176 reply_len
= http_hdr_size
- strlen(http_eom
);
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
);
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
);
194 debug_cond(DEBUG_WGET
,
195 "wget: HTTP Status Code %d\n", wget_info
->status_code
);
197 if (wget_info
->status_code
!= HTTP_STATUS_OK
) {
198 debug_cond(DEBUG_WGET
, "wget: Connected Bad Xfer\n");
199 tcp_stream_close(tcp
);
203 debug_cond(DEBUG_WGET
, "wget: Connctd pkt %p hlen %x\n",
207 pos
= strstr((char *)ptr
, content_len
);
209 pos
+= strlen(content_len
) + 1;
212 content_length
= simple_strtoul(pos
, &tail
, 10);
213 if (*tail
!= '\r' && *tail
!= '\n' && *tail
!= '\0')
217 if (content_length
>= 0) {
218 debug_cond(DEBUG_WGET
,
219 "wget: Connected Len %lu\n",
221 wget_info
->hdr_cont_len
= content_length
;
222 if (wget_info
->buffer_size
&& wget_info
->buffer_size
< wget_info
->hdr_cont_len
){
223 tcp_stream_reset(tcp
);
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
;
237 static int tcp_stream_rx(struct tcp_stream
*tcp
, u32 rx_offs
, void *buf
, int len
)
239 if ((max_rx_pos
== (u32
)(-1)) || (max_rx_pos
< rx_offs
+ len
- 1))
240 max_rx_pos
= rx_offs
+ len
- 1;
243 if (store_block(buf
, rx_offs
- http_hdr_size
, len
) < 0)
249 static int tcp_stream_tx(struct tcp_stream
*tcp
, u32 tx_offs
, void *buf
, int maxlen
)
257 switch (wget_info
->method
) {
258 case WGET_HTTP_METHOD_HEAD
:
261 case WGET_HTTP_METHOD_GET
:
267 ret
= snprintf(buf
, maxlen
, "%s %s %s\r\n\r\n",
268 method
, image_url
, http_proto
);
273 static int tcp_stream_on_create(struct tcp_stream
*tcp
)
275 if (tcp
->rhost
.s_addr
!= web_server_ip
.s_addr
||
276 tcp
->rport
!= server_port
)
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
;
289 #define BLOCKSIZE 512
291 void wget_start(void)
293 struct tcp_stream
*tcp
;
296 wget_info
= &default_wget_info
;
298 image_url
= strchr(net_boot_file_name
, ':');
300 web_server_ip
= string_to_ip(net_boot_file_name
);
302 net_server_ip
= web_server_ip
;
304 web_server_ip
= net_server_ip
;
305 image_url
= net_boot_file_name
;
308 debug_cond(DEBUG_WGET
,
309 "wget: Transfer HTTP Server %pI4; our IP %pI4\n",
310 &web_server_ip
, &net_ip
);
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
;
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",
324 debug_cond(DEBUG_WGET
, "URL '%s'\n", image_url
);
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
,
332 debug_cond(DEBUG_WGET
,
333 "\nwget:Load address: 0x%lx\nLoading: *\b", image_load_addr
);
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.
341 memset(net_server_ethaddr
, 0, 6);
343 max_rx_pos
= (u32
)(-1);
344 net_boot_file_size
= 0;
346 wget_tsize_num_hash
= 0;
347 wget_loop_state
= NETLOOP_FAIL
;
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;
355 server_port
= env_get_ulong("httpdstp", 10, SERVER_PORT
) & 0xffff;
356 tcp_stream_set_on_create_handler(tcp_stream_on_create
);
357 tcp
= tcp_stream_connect(web_server_ip
, server_port
);
359 if (!wget_info
->silent
)
360 printf("No free tcp streams\n");
361 net_set_state(NETLOOP_FAIL
);
367 int wget_do_request(ulong dst_addr
, char *uri
)
370 char *s
, *host_name
, *file_name
, *str_copy
;
373 * Download file using wget.
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.
379 str_copy
= strdup(uri
);
383 s
= str_copy
+ strlen("http://");
384 host_name
= strsep(&s
, "/");
391 host_name
= strsep(&host_name
, ":");
393 if (string_to_ip(host_name
).s_addr
) {
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) {
403 s
= env_get("httpserverip");
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
);
423 return ret
< 0 ? ret
: 0;
427 * wget_validate_uri() - validate the uri for wget
431 * This function follows the current U-Boot wget implementation.
432 * scheme: only "http:" is supported
434 * - user information: not supported
436 * - port: not supported(always use the default port)
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.
442 * TODO: stricter uri conformance check
444 * Return: true on success, false on failure
446 bool wget_validate_uri(char *uri
)
450 char *str_copy
, *s
, *authority
;
452 for (c
= 0x1; c
< 0x21; c
++) {
453 if (strchr(uri
, c
)) {
454 log_err("invalid character is used\n");
458 if (strchr(uri
, 0x7f)) {
459 log_err("invalid character is used\n");
463 if (strncmp(uri
, "http://", 7)) {
464 log_err("only http:// is supported\n");
467 str_copy
= strdup(uri
);
471 s
= str_copy
+ strlen("http://");
472 authority
= strsep(&s
, "/");
474 log_err("invalid uri, no file path\n");
478 s
= strchr(authority
, '@');
480 log_err("user information is not supported\n");
484 s
= strchr(authority
, ':');
486 log_err("user defined port is not supported\n");