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