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