1 /** 2 * @file 3 * HTTP client 4 */ 5 6 /* 7 * Copyright (c) 2018 Simon Goldschmidt <[email protected]> 8 * All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without modification, 11 * are permitted provided that the following conditions are met: 12 * 13 * 1. Redistributions of source code must retain the above copyright notice, 14 * this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright notice, 16 * this list of conditions and the following disclaimer in the documentation 17 * and/or other materials provided with the distribution. 18 * 3. The name of the author may not be used to endorse or promote products 19 * derived from this software without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 22 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 23 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 24 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 26 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 29 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 30 * OF SUCH DAMAGE. 31 * 32 * This file is part of the lwIP TCP/IP stack. 33 * 34 * Author: Simon Goldschmidt <[email protected]> 35 */ 36 37 /** 38 * @defgroup httpc HTTP client 39 * @ingroup apps 40 * @todo: 41 * - persistent connections 42 * - select outgoing http version 43 * - optionally follow redirect 44 * - check request uri for invalid characters? (e.g. encode spaces) 45 * - IPv6 support 46 */ 47 48 #include "lwip/apps/http_client.h" 49 50 #include "lwip/altcp_tcp.h" 51 #include "lwip/dns.h" 52 #include "lwip/debug.h" 53 #include "lwip/mem.h" 54 #include "lwip/altcp_tls.h" 55 #include "lwip/init.h" 56 57 #include <stdio.h> 58 #include <string.h> 59 60 #if LWIP_TCP && LWIP_CALLBACK_API 61 62 /** 63 * HTTPC_DEBUG: Enable debugging for HTTP client. 64 */ 65 #ifndef HTTPC_DEBUG 66 #define HTTPC_DEBUG LWIP_DBG_OFF 67 #endif 68 69 /** Set this to 1 to keep server name and uri in request state */ 70 #ifndef HTTPC_DEBUG_REQUEST 71 #define HTTPC_DEBUG_REQUEST 0 72 #endif 73 74 /** This string is passed in the HTTP header as "User-Agent: " */ 75 #ifndef HTTPC_CLIENT_AGENT 76 #define HTTPC_CLIENT_AGENT "lwIP/" LWIP_VERSION_STRING " (http://savannah.nongnu.org/projects/lwip)" 77 #endif 78 79 /* the various debug levels for this file */ 80 #define HTTPC_DEBUG_TRACE (HTTPC_DEBUG | LWIP_DBG_TRACE) 81 #define HTTPC_DEBUG_STATE (HTTPC_DEBUG | LWIP_DBG_STATE) 82 #define HTTPC_DEBUG_WARN (HTTPC_DEBUG | LWIP_DBG_LEVEL_WARNING) 83 #define HTTPC_DEBUG_WARN_STATE (HTTPC_DEBUG | LWIP_DBG_LEVEL_WARNING | LWIP_DBG_STATE) 84 #define HTTPC_DEBUG_SERIOUS (HTTPC_DEBUG | LWIP_DBG_LEVEL_SERIOUS) 85 86 #define HTTPC_POLL_INTERVAL 1 87 #define HTTPC_POLL_TIMEOUT 30 /* 15 seconds */ 88 89 #define HTTPC_CONTENT_LEN_INVALID 0xFFFFFFFF 90 91 /* GET request basic */ 92 #define HTTPC_REQ_11 "GET %s HTTP/1.1\r\n" /* URI */\ 93 "User-Agent: %s\r\n" /* User-Agent */ \ 94 "Accept: */*\r\n" \ 95 "Connection: Close\r\n" /* we don't support persistent connections, yet */ \ 96 "\r\n" 97 #define HTTPC_REQ_11_FORMAT(uri) HTTPC_REQ_11, uri, HTTPC_CLIENT_AGENT 98 99 /* GET request with host */ 100 #define HTTPC_REQ_11_HOST "GET %s HTTP/1.1\r\n" /* URI */\ 101 "User-Agent: %s\r\n" /* User-Agent */ \ 102 "Accept: */*\r\n" \ 103 "Host: %s\r\n" /* server name */ \ 104 "Connection: Close\r\n" /* we don't support persistent connections, yet */ \ 105 "\r\n" 106 #define HTTPC_REQ_11_HOST_FORMAT(uri, srv_name) HTTPC_REQ_11_HOST, uri, HTTPC_CLIENT_AGENT, srv_name 107 108 /* GET request with proxy */ 109 #define HTTPC_REQ_11_PROXY "GET http://%s%s HTTP/1.1\r\n" /* HOST, URI */\ 110 "User-Agent: %s\r\n" /* User-Agent */ \ 111 "Accept: */*\r\n" \ 112 "Host: %s\r\n" /* server name */ \ 113 "Connection: Close\r\n" /* we don't support persistent connections, yet */ \ 114 "\r\n" 115 #define HTTPC_REQ_11_PROXY_FORMAT(host, uri, srv_name) HTTPC_REQ_11_PROXY, host, uri, HTTPC_CLIENT_AGENT, srv_name 116 117 /* GET request with proxy (non-default server port) */ 118 #define HTTPC_REQ_11_PROXY_PORT "GET http://%s:%d%s HTTP/1.1\r\n" /* HOST, host-port, URI */\ 119 "User-Agent: %s\r\n" /* User-Agent */ \ 120 "Accept: */*\r\n" \ 121 "Host: %s\r\n" /* server name */ \ 122 "Connection: Close\r\n" /* we don't support persistent connections, yet */ \ 123 "\r\n" 124 #define HTTPC_REQ_11_PROXY_PORT_FORMAT(host, host_port, uri, srv_name) HTTPC_REQ_11_PROXY_PORT, host, host_port, uri, HTTPC_CLIENT_AGENT, srv_name 125 126 typedef enum ehttpc_parse_state { 127 HTTPC_PARSE_WAIT_FIRST_LINE = 0, 128 HTTPC_PARSE_WAIT_HEADERS, 129 HTTPC_PARSE_RX_DATA 130 } httpc_parse_state_t; 131 132 typedef struct _httpc_state 133 { 134 struct altcp_pcb* pcb; 135 ip_addr_t remote_addr; 136 u16_t remote_port; 137 int timeout_ticks; 138 struct pbuf *request; 139 struct pbuf *rx_hdrs; 140 u16_t rx_http_version; 141 u16_t rx_status; 142 altcp_recv_fn recv_fn; 143 const httpc_connection_t *conn_settings; 144 void* callback_arg; 145 u32_t rx_content_len; 146 u32_t hdr_content_len; 147 httpc_parse_state_t parse_state; 148 #if HTTPC_DEBUG_REQUEST 149 char* server_name; 150 char* uri; 151 #endif 152 } httpc_state_t; 153 154 /** Free http client state and deallocate all resources within */ 155 static err_t 156 httpc_free_state(httpc_state_t* req) 157 { 158 struct altcp_pcb* tpcb; 159 160 if (req->request != NULL) { 161 pbuf_free(req->request); 162 req->request = NULL; 163 } 164 if (req->rx_hdrs != NULL) { 165 pbuf_free(req->rx_hdrs); 166 req->rx_hdrs = NULL; 167 } 168 169 tpcb = req->pcb; 170 mem_free(req); 171 req = NULL; 172 173 if (tpcb != NULL) { 174 err_t r; 175 altcp_arg(tpcb, NULL); 176 altcp_recv(tpcb, NULL); 177 altcp_err(tpcb, NULL); 178 altcp_poll(tpcb, NULL, 0); 179 altcp_sent(tpcb, NULL); 180 r = altcp_close(tpcb); 181 if (r != ERR_OK) { 182 altcp_abort(tpcb); 183 return ERR_ABRT; 184 } 185 } 186 return ERR_OK; 187 } 188 189 /** Close the connection: call finished callback and free the state */ 190 static err_t 191 httpc_close(httpc_state_t* req, httpc_result_t result, u32_t server_response, err_t err) 192 { 193 if (req != NULL) { 194 if (req->conn_settings != NULL) { 195 if (req->conn_settings->result_fn != NULL) { 196 req->conn_settings->result_fn(req->callback_arg, result, req->rx_content_len, server_response, err); 197 } 198 } 199 return httpc_free_state(req); 200 } 201 return ERR_OK; 202 } 203 204 /** Parse http header response line 1 */ 205 static err_t 206 http_parse_response_status(struct pbuf *p, u16_t *http_version, u16_t *http_status, u16_t *http_status_str_offset) 207 { 208 u16_t end1 = pbuf_memfind(p, "\r\n", 2, 0); 209 if (end1 != 0xFFFF) { 210 /* get parts of first line */ 211 u16_t space1, space2; 212 space1 = pbuf_memfind(p, " ", 1, 0); 213 if (space1 != 0xFFFF) { 214 if ((pbuf_memcmp(p, 0, "HTTP/", 5) == 0) && (pbuf_get_at(p, 6) == '.')) { 215 char status_num[10]; 216 size_t status_num_len; 217 /* parse http version */ 218 u16_t version = pbuf_get_at(p, 5) - '0'; 219 version <<= 8; 220 version |= pbuf_get_at(p, 7) - '0'; 221 *http_version = version; 222 223 /* parse http status number */ 224 space2 = pbuf_memfind(p, " ", 1, space1 + 1); 225 if (space2 != 0xFFFF) { 226 *http_status_str_offset = space2 + 1; 227 status_num_len = space2 - space1 - 1; 228 } else { 229 status_num_len = end1 - space1 - 1; 230 } 231 memset(status_num, 0, sizeof(status_num)); 232 if (pbuf_copy_partial(p, status_num, (u16_t)status_num_len, space1 + 1) == status_num_len) { 233 int status = atoi(status_num); 234 if ((status > 0) && (status <= 0xFFFF)) { 235 *http_status = (u16_t)status; 236 return ERR_OK; 237 } 238 } 239 } 240 } 241 } 242 return ERR_VAL; 243 } 244 245 /** Wait for all headers to be received, return its length and content-length (if available) */ 246 static err_t 247 http_wait_headers(struct pbuf *p, u32_t *content_length, u16_t *total_header_len) 248 { 249 u16_t end1 = pbuf_memfind(p, "\r\n\r\n", 4, 0); 250 if (end1 < (0xFFFF - 2)) { 251 /* all headers received */ 252 /* check if we have a content length (@todo: case insensitive?) */ 253 u16_t content_len_hdr; 254 *content_length = HTTPC_CONTENT_LEN_INVALID; 255 *total_header_len = end1 + 4; 256 257 content_len_hdr = pbuf_memfind(p, "Content-Length: ", 16, 0); 258 if (content_len_hdr != 0xFFFF) { 259 u16_t content_len_line_end = pbuf_memfind(p, "\r\n", 2, content_len_hdr); 260 if (content_len_line_end != 0xFFFF) { 261 char content_len_num[16]; 262 u16_t content_len_num_len = (u16_t)(content_len_line_end - content_len_hdr - 16); 263 memset(content_len_num, 0, sizeof(content_len_num)); 264 if (pbuf_copy_partial(p, content_len_num, content_len_num_len, content_len_hdr + 16) == content_len_num_len) { 265 int len = atoi(content_len_num); 266 if ((len >= 0) && ((u32_t)len < HTTPC_CONTENT_LEN_INVALID)) { 267 *content_length = (u32_t)len; 268 } 269 } 270 } 271 } 272 return ERR_OK; 273 } 274 return ERR_VAL; 275 } 276 277 /** http client tcp recv callback */ 278 static err_t 279 httpc_tcp_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t r) 280 { 281 httpc_state_t* req = (httpc_state_t*)arg; 282 LWIP_UNUSED_ARG(r); 283 284 if (p == NULL) { 285 httpc_result_t result; 286 if (req->parse_state != HTTPC_PARSE_RX_DATA) { 287 /* did not get RX data yet */ 288 result = HTTPC_RESULT_ERR_CLOSED; 289 } else if ((req->hdr_content_len != HTTPC_CONTENT_LEN_INVALID) && 290 (req->hdr_content_len != req->rx_content_len)) { 291 /* header has been received with content length but not all data received */ 292 result = HTTPC_RESULT_ERR_CONTENT_LEN; 293 } else { 294 /* receiving data and either all data received or no content length header */ 295 result = HTTPC_RESULT_OK; 296 } 297 return httpc_close(req, result, req->rx_status, ERR_OK); 298 } 299 if (req->parse_state != HTTPC_PARSE_RX_DATA) { 300 if (req->rx_hdrs == NULL) { 301 req->rx_hdrs = p; 302 } else { 303 pbuf_cat(req->rx_hdrs, p); 304 } 305 if (req->parse_state == HTTPC_PARSE_WAIT_FIRST_LINE) { 306 u16_t status_str_off; 307 err_t err = http_parse_response_status(req->rx_hdrs, &req->rx_http_version, &req->rx_status, &status_str_off); 308 if (err == ERR_OK) { 309 /* don't care status string */ 310 req->parse_state = HTTPC_PARSE_WAIT_HEADERS; 311 } 312 } 313 if (req->parse_state == HTTPC_PARSE_WAIT_HEADERS) { 314 u16_t total_header_len; 315 err_t err = http_wait_headers(req->rx_hdrs, &req->hdr_content_len, &total_header_len); 316 if (err == ERR_OK) { 317 struct pbuf *q; 318 /* full header received, send window update for header bytes and call into client callback */ 319 altcp_recved(pcb, total_header_len); 320 if (req->conn_settings) { 321 if (req->conn_settings->headers_done_fn) { 322 err = req->conn_settings->headers_done_fn(req, req->callback_arg, req->rx_hdrs, total_header_len, req->hdr_content_len); 323 if (err != ERR_OK) { 324 return httpc_close(req, HTTPC_RESULT_LOCAL_ABORT, req->rx_status, err); 325 } 326 } 327 } 328 /* hide header bytes in pbuf */ 329 q = pbuf_free_header(req->rx_hdrs, total_header_len); 330 p = q; 331 req->rx_hdrs = NULL; 332 /* go on with data */ 333 req->parse_state = HTTPC_PARSE_RX_DATA; 334 } 335 } 336 } 337 if ((p != NULL) && (req->parse_state == HTTPC_PARSE_RX_DATA)) { 338 req->rx_content_len += p->tot_len; 339 if (req->recv_fn != NULL) { 340 /* directly return here: the connection migth already be aborted from the callback! */ 341 return req->recv_fn(req->callback_arg, pcb, p, r); 342 } else { 343 altcp_recved(pcb, p->tot_len); 344 pbuf_free(p); 345 } 346 } 347 return ERR_OK; 348 } 349 350 /** http client tcp err callback */ 351 static void 352 httpc_tcp_err(void *arg, err_t err) 353 { 354 httpc_state_t* req = (httpc_state_t*)arg; 355 if (req != NULL) { 356 /* pcb has already been deallocated */ 357 req->pcb = NULL; 358 httpc_close(req, HTTPC_RESULT_ERR_CLOSED, 0, err); 359 } 360 } 361 362 /** http client tcp poll callback */ 363 static err_t 364 httpc_tcp_poll(void *arg, struct altcp_pcb *pcb) 365 { 366 /* implement timeout */ 367 httpc_state_t* req = (httpc_state_t*)arg; 368 LWIP_UNUSED_ARG(pcb); 369 if (req != NULL) { 370 if (req->timeout_ticks) { 371 req->timeout_ticks--; 372 } 373 if (!req->timeout_ticks) { 374 return httpc_close(req, HTTPC_RESULT_ERR_TIMEOUT, 0, ERR_OK); 375 } 376 } 377 return ERR_OK; 378 } 379 380 /** http client tcp sent callback */ 381 static err_t 382 httpc_tcp_sent(void *arg, struct altcp_pcb *pcb, u16_t len) 383 { 384 /* nothing to do here for now */ 385 LWIP_UNUSED_ARG(arg); 386 LWIP_UNUSED_ARG(pcb); 387 LWIP_UNUSED_ARG(len); 388 return ERR_OK; 389 } 390 391 /** http client tcp connected callback */ 392 static err_t 393 httpc_tcp_connected(void *arg, struct altcp_pcb *pcb, err_t err) 394 { 395 err_t r; 396 httpc_state_t* req = (httpc_state_t*)arg; 397 LWIP_UNUSED_ARG(pcb); 398 LWIP_UNUSED_ARG(err); 399 400 /* send request; last char is zero termination */ 401 r = altcp_write(req->pcb, req->request->payload, req->request->len - 1, TCP_WRITE_FLAG_COPY); 402 if (r != ERR_OK) { 403 /* could not write the single small request -> fail, don't retry */ 404 return httpc_close(req, HTTPC_RESULT_ERR_MEM, 0, r); 405 } 406 /* everything written, we can free the request */ 407 pbuf_free(req->request); 408 req->request = NULL; 409 410 altcp_output(req->pcb); 411 return ERR_OK; 412 } 413 414 /** Start the http request when the server IP addr is known */ 415 static err_t 416 httpc_get_internal_addr(httpc_state_t* req, const ip_addr_t *ipaddr) 417 { 418 err_t err; 419 LWIP_ASSERT("req != NULL", req != NULL); 420 421 if (&req->remote_addr != ipaddr) { 422 /* fill in remote addr if called externally */ 423 req->remote_addr = *ipaddr; 424 } 425 426 err = altcp_connect(req->pcb, &req->remote_addr, req->remote_port, httpc_tcp_connected); 427 if (err == ERR_OK) { 428 return ERR_OK; 429 } 430 LWIP_DEBUGF(HTTPC_DEBUG_WARN_STATE, ("tcp_connect failed: %d\n", (int)err)); 431 return err; 432 } 433 434 #if LWIP_DNS 435 /** DNS callback 436 * If ipaddr is non-NULL, resolving succeeded and the request can be sent, otherwise it failed. 437 */ 438 static void 439 httpc_dns_found(const char* hostname, const ip_addr_t *ipaddr, void *arg) 440 { 441 httpc_state_t* req = (httpc_state_t*)arg; 442 err_t err; 443 httpc_result_t result; 444 445 LWIP_UNUSED_ARG(hostname); 446 447 if (ipaddr != NULL) { 448 err = httpc_get_internal_addr(req, ipaddr); 449 if (err == ERR_OK) { 450 return; 451 } 452 result = HTTPC_RESULT_ERR_CONNECT; 453 } else { 454 LWIP_DEBUGF(HTTPC_DEBUG_WARN_STATE, ("httpc_dns_found: failed to resolve hostname: %s\n", 455 hostname)); 456 result = HTTPC_RESULT_ERR_HOSTNAME; 457 err = ERR_ARG; 458 } 459 httpc_close(req, result, 0, err); 460 } 461 #endif /* LWIP_DNS */ 462 463 /** Start the http request after converting 'server_name' to ip address (DNS or address string) */ 464 static err_t 465 httpc_get_internal_dns(httpc_state_t* req, const char* server_name) 466 { 467 err_t err; 468 LWIP_ASSERT("req != NULL", req != NULL); 469 470 #if LWIP_DNS 471 err = dns_gethostbyname(server_name, &req->remote_addr, httpc_dns_found, req); 472 #else 473 err = ipaddr_aton(server_name, &req->remote_addr) ? ERR_OK : ERR_ARG; 474 #endif 475 476 if (err == ERR_OK) { 477 /* cached or IP-string */ 478 err = httpc_get_internal_addr(req, &req->remote_addr); 479 } else if (err == ERR_INPROGRESS) { 480 return ERR_OK; 481 } 482 return err; 483 } 484 485 static int 486 httpc_create_request_string(const httpc_connection_t *settings, const char* server_name, int server_port, const char* uri, 487 int use_host, char *buffer, size_t buffer_size) 488 { 489 if (settings->use_proxy) { 490 LWIP_ASSERT("server_name != NULL", server_name != NULL); 491 if (server_port != HTTP_DEFAULT_PORT) { 492 return snprintf(buffer, buffer_size, HTTPC_REQ_11_PROXY_PORT_FORMAT(server_name, server_port, uri, server_name)); 493 } else { 494 return snprintf(buffer, buffer_size, HTTPC_REQ_11_PROXY_FORMAT(server_name, uri, server_name)); 495 } 496 } else if (use_host) { 497 LWIP_ASSERT("server_name != NULL", server_name != NULL); 498 return snprintf(buffer, buffer_size, HTTPC_REQ_11_HOST_FORMAT(uri, server_name)); 499 } else { 500 return snprintf(buffer, buffer_size, HTTPC_REQ_11_FORMAT(uri)); 501 } 502 } 503 504 /** Initialize the connection struct */ 505 static err_t 506 httpc_init_connection_common(httpc_state_t **connection, const httpc_connection_t *settings, const char* server_name, 507 u16_t server_port, const char* uri, altcp_recv_fn recv_fn, void* callback_arg, int use_host) 508 { 509 size_t alloc_len; 510 mem_size_t mem_alloc_len; 511 int req_len, req_len2; 512 httpc_state_t *req; 513 #if HTTPC_DEBUG_REQUEST 514 size_t server_name_len, uri_len; 515 #endif 516 517 LWIP_ASSERT("uri != NULL", uri != NULL); 518 519 /* get request len */ 520 req_len = httpc_create_request_string(settings, server_name, server_port, uri, use_host, NULL, 0); 521 if ((req_len < 0) || (req_len > 0xFFFF)) { 522 return ERR_VAL; 523 } 524 /* alloc state and request in one block */ 525 alloc_len = sizeof(httpc_state_t); 526 #if HTTPC_DEBUG_REQUEST 527 server_name_len = server_name ? strlen(server_name) : 0; 528 uri_len = strlen(uri); 529 alloc_len += server_name_len + 1 + uri_len + 1; 530 #endif 531 mem_alloc_len = (mem_size_t)alloc_len; 532 if ((mem_alloc_len < alloc_len) || (req_len + 1 > 0xFFFF)) { 533 return ERR_VAL; 534 } 535 536 req = (httpc_state_t*)mem_malloc((mem_size_t)alloc_len); 537 if(req == NULL) { 538 return ERR_MEM; 539 } 540 memset(req, 0, sizeof(httpc_state_t)); 541 req->timeout_ticks = HTTPC_POLL_TIMEOUT; 542 req->request = pbuf_alloc(PBUF_RAW, (u16_t)(req_len + 1), PBUF_RAM); 543 if (req->request == NULL) { 544 httpc_free_state(req); 545 return ERR_MEM; 546 } 547 if (req->request->next != NULL) { 548 /* need a pbuf in one piece */ 549 httpc_free_state(req); 550 return ERR_MEM; 551 } 552 req->hdr_content_len = HTTPC_CONTENT_LEN_INVALID; 553 #if HTTPC_DEBUG_REQUEST 554 req->server_name = (char*)(req + 1); 555 if (server_name) { 556 memcpy(req->server_name, server_name, server_name_len + 1); 557 } 558 req->uri = req->server_name + server_name_len + 1; 559 memcpy(req->uri, uri, uri_len + 1); 560 #endif 561 req->pcb = altcp_new(settings->altcp_allocator); 562 if(req->pcb == NULL) { 563 httpc_free_state(req); 564 return ERR_MEM; 565 } 566 req->remote_port = settings->use_proxy ? settings->proxy_port : server_port; 567 altcp_arg(req->pcb, req); 568 altcp_recv(req->pcb, httpc_tcp_recv); 569 altcp_err(req->pcb, httpc_tcp_err); 570 altcp_poll(req->pcb, httpc_tcp_poll, HTTPC_POLL_INTERVAL); 571 altcp_sent(req->pcb, httpc_tcp_sent); 572 573 /* set up request buffer */ 574 req_len2 = httpc_create_request_string(settings, server_name, server_port, uri, use_host, 575 (char *)req->request->payload, req_len + 1); 576 if (req_len2 != req_len) { 577 httpc_free_state(req); 578 return ERR_VAL; 579 } 580 581 req->recv_fn = recv_fn; 582 req->conn_settings = settings; 583 req->callback_arg = callback_arg; 584 585 *connection = req; 586 return ERR_OK; 587 } 588 589 /** 590 * Initialize the connection struct 591 */ 592 static err_t 593 httpc_init_connection(httpc_state_t **connection, const httpc_connection_t *settings, const char* server_name, 594 u16_t server_port, const char* uri, altcp_recv_fn recv_fn, void* callback_arg) 595 { 596 return httpc_init_connection_common(connection, settings, server_name, server_port, uri, recv_fn, callback_arg, 1); 597 } 598 599 600 /** 601 * Initialize the connection struct (from IP address) 602 */ 603 static err_t 604 httpc_init_connection_addr(httpc_state_t **connection, const httpc_connection_t *settings, 605 const ip_addr_t* server_addr, u16_t server_port, const char* uri, 606 altcp_recv_fn recv_fn, void* callback_arg) 607 { 608 char *server_addr_str = ipaddr_ntoa(server_addr); 609 if (server_addr_str == NULL) { 610 return ERR_VAL; 611 } 612 return httpc_init_connection_common(connection, settings, server_addr_str, server_port, uri, 613 recv_fn, callback_arg, 1); 614 } 615 616 /** 617 * @ingroup httpc 618 * HTTP client API: get a file by passing server IP address 619 * 620 * @param server_addr IP address of the server to connect 621 * @param port tcp port of the server 622 * @param uri uri to get from the server, remember leading "/"! 623 * @param settings connection settings (callbacks, proxy, etc.) 624 * @param recv_fn the http body (not the headers) are passed to this callback 625 * @param callback_arg argument passed to all the callbacks 626 * @param connection retreives the connection handle (to match in callbacks) 627 * @return ERR_OK if starting the request succeeds (callback_fn will be called later) 628 * or an error code 629 */ 630 err_t 631 httpc_get_file(const ip_addr_t* server_addr, u16_t port, const char* uri, const httpc_connection_t *settings, 632 altcp_recv_fn recv_fn, void* callback_arg, httpc_state_t **connection) 633 { 634 err_t err; 635 httpc_state_t* req; 636 637 LWIP_ERROR("invalid parameters", (server_addr != NULL) && (uri != NULL) && (recv_fn != NULL), return ERR_ARG;); 638 639 err = httpc_init_connection_addr(&req, settings, server_addr, port, 640 uri, recv_fn, callback_arg); 641 if (err != ERR_OK) { 642 return err; 643 } 644 645 if (settings->use_proxy) { 646 err = httpc_get_internal_addr(req, &settings->proxy_addr); 647 } else { 648 err = httpc_get_internal_addr(req, server_addr); 649 } 650 if(err != ERR_OK) { 651 httpc_free_state(req); 652 return err; 653 } 654 655 if (connection != NULL) { 656 *connection = req; 657 } 658 return ERR_OK; 659 } 660 661 /** 662 * @ingroup httpc 663 * HTTP client API: get a file by passing server name as string (DNS name or IP address string) 664 * 665 * @param server_name server name as string (DNS name or IP address string) 666 * @param port tcp port of the server 667 * @param uri uri to get from the server, remember leading "/"! 668 * @param settings connection settings (callbacks, proxy, etc.) 669 * @param recv_fn the http body (not the headers) are passed to this callback 670 * @param callback_arg argument passed to all the callbacks 671 * @param connection retreives the connection handle (to match in callbacks) 672 * @return ERR_OK if starting the request succeeds (callback_fn will be called later) 673 * or an error code 674 */ 675 err_t 676 httpc_get_file_dns(const char* server_name, u16_t port, const char* uri, const httpc_connection_t *settings, 677 altcp_recv_fn recv_fn, void* callback_arg, httpc_state_t **connection) 678 { 679 err_t err; 680 httpc_state_t* req; 681 682 LWIP_ERROR("invalid parameters", (server_name != NULL) && (uri != NULL) && (recv_fn != NULL), return ERR_ARG;); 683 684 err = httpc_init_connection(&req, settings, server_name, port, uri, recv_fn, callback_arg); 685 if (err != ERR_OK) { 686 return err; 687 } 688 689 if (settings->use_proxy) { 690 err = httpc_get_internal_addr(req, &settings->proxy_addr); 691 } else { 692 err = httpc_get_internal_dns(req, server_name); 693 } 694 if(err != ERR_OK) { 695 httpc_free_state(req); 696 return err; 697 } 698 699 if (connection != NULL) { 700 *connection = req; 701 } 702 return ERR_OK; 703 } 704 705 #if LWIP_HTTPC_HAVE_FILE_IO 706 /* Implementation to disk via fopen/fwrite/fclose follows */ 707 708 typedef struct _httpc_filestate 709 { 710 const char* local_file_name; 711 FILE *file; 712 httpc_connection_t settings; 713 const httpc_connection_t *client_settings; 714 void *callback_arg; 715 } httpc_filestate_t; 716 717 static void httpc_fs_result(void *arg, httpc_result_t httpc_result, u32_t rx_content_len, 718 u32_t srv_res, err_t err); 719 720 /** Initalize http client state for download to file system */ 721 static err_t 722 httpc_fs_init(httpc_filestate_t **filestate_out, const char* local_file_name, 723 const httpc_connection_t *settings, void* callback_arg) 724 { 725 httpc_filestate_t *filestate; 726 size_t file_len, alloc_len; 727 FILE *f; 728 729 file_len = strlen(local_file_name); 730 alloc_len = sizeof(httpc_filestate_t) + file_len + 1; 731 732 filestate = (httpc_filestate_t *)mem_malloc((mem_size_t)alloc_len); 733 if (filestate == NULL) { 734 return ERR_MEM; 735 } 736 memset(filestate, 0, sizeof(httpc_filestate_t)); 737 filestate->local_file_name = (const char *)(filestate + 1); 738 memcpy((char *)(filestate + 1), local_file_name, file_len + 1); 739 filestate->file = NULL; 740 filestate->client_settings = settings; 741 filestate->callback_arg = callback_arg; 742 /* copy client settings but override result callback */ 743 memcpy(&filestate->settings, settings, sizeof(httpc_connection_t)); 744 filestate->settings.result_fn = httpc_fs_result; 745 746 f = fopen(local_file_name, "wb"); 747 if(f == NULL) { 748 /* could not open file */ 749 mem_free(filestate); 750 return ERR_VAL; 751 } 752 filestate->file = f; 753 *filestate_out = filestate; 754 return ERR_OK; 755 } 756 757 /** Free http client state for download to file system */ 758 static void 759 httpc_fs_free(httpc_filestate_t *filestate) 760 { 761 if (filestate != NULL) { 762 if (filestate->file != NULL) { 763 fclose(filestate->file); 764 filestate->file = NULL; 765 } 766 mem_free(filestate); 767 } 768 } 769 770 /** Connection closed (success or error) */ 771 static void 772 httpc_fs_result(void *arg, httpc_result_t httpc_result, u32_t rx_content_len, 773 u32_t srv_res, err_t err) 774 { 775 httpc_filestate_t *filestate = (httpc_filestate_t *)arg; 776 if (filestate != NULL) { 777 if (filestate->client_settings->result_fn != NULL) { 778 filestate->client_settings->result_fn(filestate->callback_arg, httpc_result, rx_content_len, 779 srv_res, err); 780 } 781 httpc_fs_free(filestate); 782 } 783 } 784 785 /** tcp recv callback */ 786 static err_t 787 httpc_fs_tcp_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err) 788 { 789 httpc_filestate_t *filestate = (httpc_filestate_t*)arg; 790 struct pbuf* q; 791 LWIP_UNUSED_ARG(err); 792 793 LWIP_ASSERT("p != NULL", p != NULL); 794 795 for (q = p; q != NULL; q = q->next) { 796 fwrite(q->payload, 1, q->len, filestate->file); 797 } 798 altcp_recved(pcb, p->tot_len); 799 pbuf_free(p); 800 return ERR_OK; 801 } 802 803 /** 804 * @ingroup httpc 805 * HTTP client API: get a file to disk by passing server IP address 806 * 807 * @param server_addr IP address of the server to connect 808 * @param port tcp port of the server 809 * @param uri uri to get from the server, remember leading "/"! 810 * @param settings connection settings (callbacks, proxy, etc.) 811 * @param callback_arg argument passed to all the callbacks 812 * @param connection retreives the connection handle (to match in callbacks) 813 * @return ERR_OK if starting the request succeeds (callback_fn will be called later) 814 * or an error code 815 */ 816 err_t 817 httpc_get_file_to_disk(const ip_addr_t* server_addr, u16_t port, const char* uri, const httpc_connection_t *settings, 818 void* callback_arg, const char* local_file_name, httpc_state_t **connection) 819 { 820 err_t err; 821 httpc_state_t* req; 822 httpc_filestate_t *filestate; 823 824 LWIP_ERROR("invalid parameters", (server_addr != NULL) && (uri != NULL) && (local_file_name != NULL), return ERR_ARG;); 825 826 err = httpc_fs_init(&filestate, local_file_name, settings, callback_arg); 827 if (err != ERR_OK) { 828 return err; 829 } 830 831 err = httpc_init_connection_addr(&req, &filestate->settings, server_addr, port, 832 uri, httpc_fs_tcp_recv, filestate); 833 if (err != ERR_OK) { 834 httpc_fs_free(filestate); 835 return err; 836 } 837 838 if (settings->use_proxy) { 839 err = httpc_get_internal_addr(req, &settings->proxy_addr); 840 } else { 841 err = httpc_get_internal_addr(req, server_addr); 842 } 843 if(err != ERR_OK) { 844 httpc_fs_free(filestate); 845 httpc_free_state(req); 846 return err; 847 } 848 849 if (connection != NULL) { 850 *connection = req; 851 } 852 return ERR_OK; 853 } 854 855 /** 856 * @ingroup httpc 857 * HTTP client API: get a file to disk by passing server name as string (DNS name or IP address string) 858 * 859 * @param server_name server name as string (DNS name or IP address string) 860 * @param port tcp port of the server 861 * @param uri uri to get from the server, remember leading "/"! 862 * @param settings connection settings (callbacks, proxy, etc.) 863 * @param callback_arg argument passed to all the callbacks 864 * @param connection retreives the connection handle (to match in callbacks) 865 * @return ERR_OK if starting the request succeeds (callback_fn will be called later) 866 * or an error code 867 */ 868 err_t 869 httpc_get_file_dns_to_disk(const char* server_name, u16_t port, const char* uri, const httpc_connection_t *settings, 870 void* callback_arg, const char* local_file_name, httpc_state_t **connection) 871 { 872 err_t err; 873 httpc_state_t* req; 874 httpc_filestate_t *filestate; 875 876 LWIP_ERROR("invalid parameters", (server_name != NULL) && (uri != NULL) && (local_file_name != NULL), return ERR_ARG;); 877 878 err = httpc_fs_init(&filestate, local_file_name, settings, callback_arg); 879 if (err != ERR_OK) { 880 return err; 881 } 882 883 err = httpc_init_connection(&req, &filestate->settings, server_name, port, 884 uri, httpc_fs_tcp_recv, filestate); 885 if (err != ERR_OK) { 886 httpc_fs_free(filestate); 887 return err; 888 } 889 890 if (settings->use_proxy) { 891 err = httpc_get_internal_addr(req, &settings->proxy_addr); 892 } else { 893 err = httpc_get_internal_dns(req, server_name); 894 } 895 if(err != ERR_OK) { 896 httpc_fs_free(filestate); 897 httpc_free_state(req); 898 return err; 899 } 900 901 if (connection != NULL) { 902 *connection = req; 903 } 904 return ERR_OK; 905 } 906 #endif /* LWIP_HTTPC_HAVE_FILE_IO */ 907 908 #endif /* LWIP_TCP && LWIP_CALLBACK_API */ 909