Kenji Arai / mbed-os_TYBLE16

Dependents:   TYBLE16_simple_data_logger TYBLE16_MP3_Air

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers lwip_http_client.c Source File

lwip_http_client.c

Go to the documentation of this file.
00001 /**
00002  * @file
00003  * HTTP client
00004  */
00005 
00006 /*
00007  * Copyright (c) 2018 Simon Goldschmidt <goldsimon@gmx.de>
00008  * All rights reserved.
00009  *
00010  * Redistribution and use in source and binary forms, with or without modification,
00011  * are permitted provided that the following conditions are met:
00012  *
00013  * 1. Redistributions of source code must retain the above copyright notice,
00014  *    this list of conditions and the following disclaimer.
00015  * 2. Redistributions in binary form must reproduce the above copyright notice,
00016  *    this list of conditions and the following disclaimer in the documentation
00017  *    and/or other materials provided with the distribution.
00018  * 3. The name of the author may not be used to endorse or promote products
00019  *    derived from this software without specific prior written permission.
00020  *
00021  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
00022  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
00023  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
00024  * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
00025  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
00026  * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00027  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00028  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
00029  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
00030  * OF SUCH DAMAGE.
00031  *
00032  * This file is part of the lwIP TCP/IP stack.
00033  *
00034  * Author: Simon Goldschmidt <goldsimon@gmx.de>
00035  */
00036 
00037 /**
00038  * @defgroup httpc HTTP client
00039  * @ingroup apps
00040  * @todo:
00041  * - persistent connections
00042  * - select outgoing http version
00043  * - optionally follow redirect
00044  * - check request uri for invalid characters? (e.g. encode spaces)
00045  * - IPv6 support
00046  */
00047 
00048 #include "lwip/apps/http_client.h"
00049 
00050 #include "lwip/altcp_tcp.h"
00051 #include "lwip/dns.h"
00052 #include "lwip/debug.h"
00053 #include "lwip/mem.h"
00054 #include "lwip/altcp_tls.h"
00055 #include "lwip/init.h"
00056 
00057 #include <stdio.h>
00058 #include <stdlib.h>
00059 #include <string.h>
00060 
00061 #if LWIP_TCP && LWIP_CALLBACK_API
00062 
00063 /**
00064  * HTTPC_DEBUG: Enable debugging for HTTP client.
00065  */
00066 #ifndef HTTPC_DEBUG
00067 #define HTTPC_DEBUG                 LWIP_DBG_OFF
00068 #endif
00069 
00070 /** Set this to 1 to keep server name and uri in request state */
00071 #ifndef HTTPC_DEBUG_REQUEST
00072 #define HTTPC_DEBUG_REQUEST         0
00073 #endif
00074 
00075 /** This string is passed in the HTTP header as "User-Agent: " */
00076 #ifndef HTTPC_CLIENT_AGENT
00077 #define HTTPC_CLIENT_AGENT "lwIP/" LWIP_VERSION_STRING " (http://savannah.nongnu.org/projects/lwip)"
00078 #endif
00079 
00080 /* the various debug levels for this file */
00081 #define HTTPC_DEBUG_TRACE        (HTTPC_DEBUG | LWIP_DBG_TRACE)
00082 #define HTTPC_DEBUG_STATE        (HTTPC_DEBUG | LWIP_DBG_STATE)
00083 #define HTTPC_DEBUG_WARN         (HTTPC_DEBUG | LWIP_DBG_LEVEL_WARNING)
00084 #define HTTPC_DEBUG_WARN_STATE   (HTTPC_DEBUG | LWIP_DBG_LEVEL_WARNING | LWIP_DBG_STATE)
00085 #define HTTPC_DEBUG_SERIOUS      (HTTPC_DEBUG | LWIP_DBG_LEVEL_SERIOUS)
00086 
00087 #define HTTPC_POLL_INTERVAL     1
00088 #define HTTPC_POLL_TIMEOUT      30 /* 15 seconds */
00089 
00090 #define HTTPC_CONTENT_LEN_INVALID 0xFFFFFFFF
00091 
00092 /* GET request basic */
00093 #define HTTPC_REQ_11 "GET %s HTTP/1.1\r\n" /* URI */\
00094     "User-Agent: %s\r\n" /* User-Agent */ \
00095     "Accept: */*\r\n" \
00096     "Connection: Close\r\n" /* we don't support persistent connections, yet */ \
00097     "\r\n"
00098 #define HTTPC_REQ_11_FORMAT(uri) HTTPC_REQ_11, uri, HTTPC_CLIENT_AGENT
00099 
00100 /* GET request with host */
00101 #define HTTPC_REQ_11_HOST "GET %s HTTP/1.1\r\n" /* URI */\
00102     "User-Agent: %s\r\n" /* User-Agent */ \
00103     "Accept: */*\r\n" \
00104     "Host: %s\r\n" /* server name */ \
00105     "Connection: Close\r\n" /* we don't support persistent connections, yet */ \
00106     "\r\n"
00107 #define HTTPC_REQ_11_HOST_FORMAT(uri, srv_name) HTTPC_REQ_11_HOST, uri, HTTPC_CLIENT_AGENT, srv_name
00108 
00109 /* GET request with proxy */
00110 #define HTTPC_REQ_11_PROXY "GET http://%s%s HTTP/1.1\r\n" /* HOST, URI */\
00111     "User-Agent: %s\r\n" /* User-Agent */ \
00112     "Accept: */*\r\n" \
00113     "Host: %s\r\n" /* server name */ \
00114     "Connection: Close\r\n" /* we don't support persistent connections, yet */ \
00115     "\r\n"
00116 #define HTTPC_REQ_11_PROXY_FORMAT(host, uri, srv_name) HTTPC_REQ_11_PROXY, host, uri, HTTPC_CLIENT_AGENT, srv_name
00117 
00118 /* GET request with proxy (non-default server port) */
00119 #define HTTPC_REQ_11_PROXY_PORT "GET http://%s:%d%s HTTP/1.1\r\n" /* HOST, host-port, URI */\
00120     "User-Agent: %s\r\n" /* User-Agent */ \
00121     "Accept: */*\r\n" \
00122     "Host: %s\r\n" /* server name */ \
00123     "Connection: Close\r\n" /* we don't support persistent connections, yet */ \
00124     "\r\n"
00125 #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
00126 
00127 typedef enum ehttpc_parse_state {
00128   HTTPC_PARSE_WAIT_FIRST_LINE = 0,
00129   HTTPC_PARSE_WAIT_HEADERS,
00130   HTTPC_PARSE_RX_DATA
00131 } httpc_parse_state_t;
00132 
00133 typedef struct _httpc_state
00134 {
00135   struct altcp_pcb* pcb;
00136   ip_addr_t remote_addr;
00137   u16_t remote_port;
00138   int timeout_ticks;
00139   struct pbuf *request;
00140   struct pbuf *rx_hdrs;
00141   u16_t rx_http_version;
00142   u16_t rx_status;
00143   altcp_recv_fn recv_fn;
00144   const httpc_connection_t *conn_settings;
00145   void* callback_arg;
00146   u32_t rx_content_len;
00147   u32_t hdr_content_len;
00148   httpc_parse_state_t parse_state;
00149 #if HTTPC_DEBUG_REQUEST
00150   char* server_name;
00151   char* uri;
00152 #endif
00153 } httpc_state_t;
00154 
00155 /** Free http client state and deallocate all resources within */
00156 static err_t
00157 httpc_free_state(httpc_state_t* req)
00158 {
00159   struct altcp_pcb* tpcb;
00160 
00161   if (req->request != NULL) {
00162     pbuf_free(req->request);
00163     req->request = NULL;
00164   }
00165   if (req->rx_hdrs != NULL) {
00166     pbuf_free(req->rx_hdrs);
00167     req->rx_hdrs = NULL;
00168   }
00169 
00170   tpcb = req->pcb;
00171   mem_free(req);
00172   req = NULL;
00173 
00174   if (tpcb != NULL) {
00175     err_t r;
00176     altcp_arg (tpcb, NULL);
00177     altcp_recv (tpcb, NULL);
00178     altcp_err (tpcb, NULL);
00179     altcp_poll (tpcb, NULL, 0);
00180     altcp_sent (tpcb, NULL);
00181     r = altcp_close (tpcb);
00182     if (r != ERR_OK) {
00183       altcp_abort (tpcb);
00184       return ERR_ABRT;
00185     }
00186   }
00187   return ERR_OK;
00188 }
00189 
00190 /** Close the connection: call finished callback and free the state */
00191 static err_t
00192 httpc_close(httpc_state_t* req, httpc_result_t result, u32_t server_response, err_t err)
00193 {
00194   if (req != NULL) {
00195     if (req->conn_settings != NULL) {
00196       if (req->conn_settings->result_fn != NULL) {
00197         req->conn_settings->result_fn(req->callback_arg, result, req->rx_content_len, server_response, err);
00198       }
00199     }
00200     return httpc_free_state(req);
00201   }
00202   return ERR_OK;
00203 }
00204 
00205 /** Parse http header response line 1 */
00206 static err_t
00207 http_parse_response_status(struct pbuf *p, u16_t *http_version, u16_t *http_status, u16_t *http_status_str_offset)
00208 {
00209   u16_t end1 = pbuf_memfind(p, "\r\n", 2, 0);
00210   if (end1 != 0xFFFF) {
00211     /* get parts of first line */
00212     u16_t space1, space2;
00213     space1 = pbuf_memfind(p, " ", 1, 0);
00214     if (space1 != 0xFFFF) {
00215       if ((pbuf_memcmp(p, 0, "HTTP/", 5) == 0)  && (pbuf_get_at(p, 6) == '.')) {
00216         char status_num[10];
00217         size_t status_num_len;
00218         /* parse http version */
00219         u16_t version = pbuf_get_at(p, 5) - '0';
00220         version <<= 8;
00221         version |= pbuf_get_at(p, 7) - '0';
00222         *http_version = version;
00223 
00224         /* parse http status number */
00225         space2 = pbuf_memfind(p, " ", 1, space1 + 1);
00226         if (space2 != 0xFFFF) {
00227           *http_status_str_offset = space2 + 1;
00228           status_num_len = space2 - space1 - 1;
00229         } else {
00230           status_num_len = end1 - space1 - 1;
00231         }
00232         memset(status_num, 0, sizeof(status_num));
00233         if (pbuf_copy_partial(p, status_num, (u16_t)status_num_len, space1 + 1) == status_num_len) {
00234           int status = atoi(status_num);
00235           if ((status > 0) && (status <= 0xFFFF)) {
00236             *http_status = (u16_t)status;
00237             return ERR_OK;
00238           }
00239         }
00240       }
00241     }
00242   }
00243   return ERR_VAL;
00244 }
00245 
00246 /** Wait for all headers to be received, return its length and content-length (if available) */
00247 static err_t
00248 http_wait_headers(struct pbuf *p, u32_t *content_length, u16_t *total_header_len)
00249 {
00250   u16_t end1 = pbuf_memfind(p, "\r\n\r\n", 4, 0);
00251   if (end1 < (0xFFFF - 2)) {
00252     /* all headers received */
00253     /* check if we have a content length (@todo: case insensitive?) */
00254     u16_t content_len_hdr;
00255     *content_length = HTTPC_CONTENT_LEN_INVALID;
00256     *total_header_len = end1 + 4;
00257 
00258     content_len_hdr = pbuf_memfind(p, "Content-Length: ", 16, 0);
00259     if (content_len_hdr != 0xFFFF) {
00260       u16_t content_len_line_end = pbuf_memfind(p, "\r\n", 2, content_len_hdr);
00261       if (content_len_line_end != 0xFFFF) {
00262         char content_len_num[16];
00263         u16_t content_len_num_len = (u16_t)(content_len_line_end - content_len_hdr - 16);
00264         memset(content_len_num, 0, sizeof(content_len_num));
00265         if (pbuf_copy_partial(p, content_len_num, content_len_num_len, content_len_hdr + 16) == content_len_num_len) {
00266           int len = atoi(content_len_num);
00267           if ((len >= 0) && ((u32_t)len < HTTPC_CONTENT_LEN_INVALID)) {
00268             *content_length = (u32_t)len;
00269           }
00270         }
00271       }
00272     }
00273     return ERR_OK;
00274   }
00275   return ERR_VAL;
00276 }
00277 
00278 /** http client tcp recv callback */
00279 static err_t
00280 httpc_tcp_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t r)
00281 {
00282   httpc_state_t* req = (httpc_state_t*)arg;
00283   LWIP_UNUSED_ARG(r);
00284 
00285   if (p == NULL) {
00286     httpc_result_t result;
00287     if (req->parse_state != HTTPC_PARSE_RX_DATA) {
00288       /* did not get RX data yet */
00289       result = HTTPC_RESULT_ERR_CLOSED;
00290     } else if ((req->hdr_content_len != HTTPC_CONTENT_LEN_INVALID) &&
00291       (req->hdr_content_len != req->rx_content_len)) {
00292       /* header has been received with content length but not all data received */
00293       result = HTTPC_RESULT_ERR_CONTENT_LEN;
00294     } else {
00295       /* receiving data and either all data received or no content length header */
00296       result = HTTPC_RESULT_OK;
00297     }
00298     return httpc_close(req, result, req->rx_status, ERR_OK);
00299   }
00300   if (req->parse_state != HTTPC_PARSE_RX_DATA) {
00301     if (req->rx_hdrs == NULL) {
00302       req->rx_hdrs = p;
00303     } else {
00304       pbuf_cat(req->rx_hdrs, p);
00305     }
00306     if (req->parse_state == HTTPC_PARSE_WAIT_FIRST_LINE) {
00307       u16_t status_str_off;
00308       err_t err = http_parse_response_status(req->rx_hdrs, &req->rx_http_version, &req->rx_status, &status_str_off);
00309       if (err == ERR_OK) {
00310         /* don't care status string */
00311         req->parse_state = HTTPC_PARSE_WAIT_HEADERS;
00312       }
00313     }
00314     if (req->parse_state == HTTPC_PARSE_WAIT_HEADERS) {
00315       u16_t total_header_len;
00316       err_t err = http_wait_headers(req->rx_hdrs, &req->hdr_content_len, &total_header_len);
00317       if (err == ERR_OK) {
00318         struct pbuf *q;
00319         /* full header received, send window update for header bytes and call into client callback */
00320         altcp_recved (pcb, total_header_len);
00321         if (req->conn_settings) {
00322           if (req->conn_settings->headers_done_fn) {
00323             err = req->conn_settings->headers_done_fn(req, req->callback_arg, req->rx_hdrs, total_header_len, req->hdr_content_len);
00324             if (err != ERR_OK) {
00325               return httpc_close(req, HTTPC_RESULT_LOCAL_ABORT, req->rx_status, err);
00326             }
00327           }
00328         }
00329         /* hide header bytes in pbuf */
00330         q = pbuf_free_header(req->rx_hdrs, total_header_len);
00331         p = q;
00332         req->rx_hdrs = NULL;
00333         /* go on with data */
00334         req->parse_state = HTTPC_PARSE_RX_DATA;
00335       }
00336     }
00337   }
00338   if ((p != NULL) && (req->parse_state == HTTPC_PARSE_RX_DATA)) {
00339     req->rx_content_len += p->tot_len;
00340     if (req->recv_fn != NULL) {
00341       /* directly return here: the connection migth already be aborted from the callback! */
00342       return req->recv_fn(req->callback_arg, pcb, p, r);
00343     } else {
00344       altcp_recved (pcb, p->tot_len);
00345       pbuf_free(p);
00346     }
00347   }
00348   return ERR_OK;
00349 }
00350 
00351 /** http client tcp err callback */
00352 static void
00353 httpc_tcp_err(void *arg, err_t err)
00354 {
00355   httpc_state_t* req = (httpc_state_t*)arg;
00356   if (req != NULL) {
00357     /* pcb has already been deallocated */
00358     req->pcb = NULL;
00359     httpc_close(req, HTTPC_RESULT_ERR_CLOSED, 0, err);
00360   }
00361 }
00362 
00363 /** http client tcp poll callback */
00364 static err_t
00365 httpc_tcp_poll(void *arg, struct altcp_pcb *pcb)
00366 {
00367   /* implement timeout */
00368   httpc_state_t* req = (httpc_state_t*)arg;
00369   LWIP_UNUSED_ARG(pcb);
00370   if (req != NULL) {
00371     if (req->timeout_ticks) {
00372       req->timeout_ticks--;
00373     }
00374     if (!req->timeout_ticks) {
00375       return httpc_close(req, HTTPC_RESULT_ERR_TIMEOUT, 0, ERR_OK);
00376     }
00377   }
00378   return ERR_OK;
00379 }
00380 
00381 /** http client tcp sent callback */
00382 static err_t
00383 httpc_tcp_sent(void *arg, struct altcp_pcb *pcb, u16_t len)
00384 {
00385   /* nothing to do here for now */
00386   LWIP_UNUSED_ARG(arg);
00387   LWIP_UNUSED_ARG(pcb);
00388   LWIP_UNUSED_ARG(len);
00389   return ERR_OK;
00390 }
00391 
00392 /** http client tcp connected callback */
00393 static err_t
00394 httpc_tcp_connected(void *arg, struct altcp_pcb *pcb, err_t err)
00395 {
00396   err_t r;
00397   httpc_state_t* req = (httpc_state_t*)arg;
00398   LWIP_UNUSED_ARG(pcb);
00399   LWIP_UNUSED_ARG(err);
00400 
00401   /* send request; last char is zero termination */
00402   r = altcp_write (req->pcb, req->request->payload, req->request->len - 1, TCP_WRITE_FLAG_COPY);
00403   if (r != ERR_OK) {
00404      /* could not write the single small request -> fail, don't retry */
00405      return httpc_close(req, HTTPC_RESULT_ERR_MEM, 0, r);
00406   }
00407   /* everything written, we can free the request */
00408   pbuf_free(req->request);
00409   req->request = NULL;
00410 
00411   altcp_output (req->pcb);
00412   return ERR_OK;
00413 }
00414 
00415 /** Start the http request when the server IP addr is known */
00416 static err_t
00417 httpc_get_internal_addr(httpc_state_t* req, const ip_addr_t *ipaddr)
00418 {
00419   err_t err;
00420   LWIP_ASSERT("req != NULL", req != NULL);
00421 
00422   if (&req->remote_addr != ipaddr) {
00423     /* fill in remote addr if called externally */
00424     req->remote_addr = *ipaddr;
00425   }
00426 
00427   err = altcp_connect (req->pcb, &req->remote_addr, req->remote_port, httpc_tcp_connected);
00428   if (err == ERR_OK) {
00429     return ERR_OK;
00430   }
00431   LWIP_DEBUGF(HTTPC_DEBUG_WARN_STATE, ("tcp_connect failed: %d\n", (int)err));
00432   return err;
00433 }
00434 
00435 #if LWIP_DNS
00436 /** DNS callback
00437  * If ipaddr is non-NULL, resolving succeeded and the request can be sent, otherwise it failed.
00438  */
00439 static void
00440 httpc_dns_found(const char* hostname, const ip_addr_t *ipaddr, void *arg)
00441 {
00442   httpc_state_t* req = (httpc_state_t*)arg;
00443   err_t err;
00444   httpc_result_t result;
00445 
00446   LWIP_UNUSED_ARG(hostname);
00447 
00448   if (ipaddr != NULL) {
00449     err = httpc_get_internal_addr(req, ipaddr);
00450     if (err == ERR_OK) {
00451       return;
00452     }
00453     result = HTTPC_RESULT_ERR_CONNECT;
00454   } else {
00455     LWIP_DEBUGF(HTTPC_DEBUG_WARN_STATE, ("httpc_dns_found: failed to resolve hostname: %s\n",
00456       hostname));
00457     result = HTTPC_RESULT_ERR_HOSTNAME;
00458     err = ERR_ARG;
00459   }
00460   httpc_close(req, result, 0, err);
00461 }
00462 #endif /* LWIP_DNS */
00463 
00464 /** Start the http request after converting 'server_name' to ip address (DNS or address string) */
00465 static err_t
00466 httpc_get_internal_dns(httpc_state_t* req, const char* server_name)
00467 {
00468   err_t err;
00469   LWIP_ASSERT("req != NULL", req != NULL);
00470 
00471 #if LWIP_DNS
00472   err = dns_gethostbyname(server_name, &req->remote_addr, httpc_dns_found, req);
00473 #else
00474   err = ipaddr_aton(server_name, &req->remote_addr) ? ERR_OK : ERR_ARG;
00475 #endif
00476 
00477   if (err == ERR_OK) {
00478     /* cached or IP-string */
00479     err = httpc_get_internal_addr(req, &req->remote_addr);
00480   } else if (err == ERR_INPROGRESS) {
00481     return ERR_OK;
00482   }
00483   return err;
00484 }
00485 
00486 static int
00487 httpc_create_request_string(const httpc_connection_t *settings, const char* server_name, int server_port, const char* uri,
00488                             int use_host, char *buffer, size_t buffer_size)
00489 {
00490   if (settings->use_proxy) {
00491     LWIP_ASSERT("server_name != NULL", server_name != NULL);
00492     if (server_port != HTTP_DEFAULT_PORT) {
00493       return snprintf(buffer, buffer_size, HTTPC_REQ_11_PROXY_PORT_FORMAT(server_name, server_port, uri, server_name));
00494     } else {
00495       return snprintf(buffer, buffer_size, HTTPC_REQ_11_PROXY_FORMAT(server_name, uri, server_name));
00496     }
00497   } else if (use_host) {
00498     LWIP_ASSERT("server_name != NULL", server_name != NULL);
00499     return snprintf(buffer, buffer_size, HTTPC_REQ_11_HOST_FORMAT(uri, server_name));
00500   } else {
00501     return snprintf(buffer, buffer_size, HTTPC_REQ_11_FORMAT(uri));
00502   }
00503 }
00504 
00505 /** Initialize the connection struct */
00506 static err_t
00507 httpc_init_connection_common(httpc_state_t **connection, const httpc_connection_t *settings, const char* server_name,
00508                       u16_t server_port, const char* uri, altcp_recv_fn recv_fn, void* callback_arg, int use_host)
00509 {
00510   size_t alloc_len;
00511   mem_size_t mem_alloc_len;
00512   int req_len, req_len2;
00513   httpc_state_t *req;
00514 #if HTTPC_DEBUG_REQUEST
00515   size_t server_name_len, uri_len;
00516 #endif
00517 
00518   LWIP_ASSERT("uri != NULL", uri != NULL);
00519 
00520   /* get request len */
00521   req_len = httpc_create_request_string(settings, server_name, server_port, uri, use_host, NULL, 0);
00522   if ((req_len < 0) || (req_len > 0xFFFF)) {
00523     return ERR_VAL;
00524   }
00525   /* alloc state and request in one block */
00526   alloc_len = sizeof(httpc_state_t);
00527 #if HTTPC_DEBUG_REQUEST
00528   server_name_len = server_name ? strlen(server_name) : 0;
00529   uri_len = strlen(uri);
00530   alloc_len += server_name_len + 1 + uri_len + 1;
00531 #endif
00532   mem_alloc_len = (mem_size_t)alloc_len;
00533   if ((mem_alloc_len < alloc_len) || (req_len + 1 > 0xFFFF)) {
00534     return ERR_VAL;
00535   }
00536 
00537   req = (httpc_state_t*)mem_malloc((mem_size_t)alloc_len);
00538   if(req == NULL) {
00539     return ERR_MEM;
00540   }
00541   memset(req, 0, sizeof(httpc_state_t));
00542   req->timeout_ticks = HTTPC_POLL_TIMEOUT;
00543   req->request = pbuf_alloc(PBUF_RAW, (u16_t)(req_len + 1), PBUF_RAM);
00544   if (req->request == NULL) {
00545     httpc_free_state(req);
00546     return ERR_MEM;
00547   }
00548   if (req->request->next != NULL) {
00549     /* need a pbuf in one piece */
00550     httpc_free_state(req);
00551     return ERR_MEM;
00552   }
00553   req->hdr_content_len = HTTPC_CONTENT_LEN_INVALID;
00554 #if HTTPC_DEBUG_REQUEST
00555   req->server_name = (char*)(req + 1);
00556   if (server_name) {
00557     memcpy(req->server_name, server_name, server_name_len + 1);
00558   }
00559   req->uri = req->server_name + server_name_len + 1;
00560   memcpy(req->uri, uri, uri_len + 1);
00561 #endif
00562   req->pcb = altcp_new(settings->altcp_allocator);
00563   if(req->pcb == NULL) {
00564     httpc_free_state(req);
00565     return ERR_MEM;
00566   }
00567   req->remote_port = settings->use_proxy ? settings->proxy_port : server_port;
00568   altcp_arg (req->pcb, req);
00569   altcp_recv (req->pcb, httpc_tcp_recv);
00570   altcp_err (req->pcb, httpc_tcp_err);
00571   altcp_poll (req->pcb, httpc_tcp_poll, HTTPC_POLL_INTERVAL);
00572   altcp_sent (req->pcb, httpc_tcp_sent);
00573 
00574   /* set up request buffer */
00575   req_len2 = httpc_create_request_string(settings, server_name, server_port, uri, use_host,
00576     (char *)req->request->payload, req_len + 1);
00577   if (req_len2 != req_len) {
00578     httpc_free_state(req);
00579     return ERR_VAL;
00580   }
00581 
00582   req->recv_fn = recv_fn;
00583   req->conn_settings = settings;
00584   req->callback_arg = callback_arg;
00585 
00586   *connection = req;
00587   return ERR_OK;
00588 }
00589 
00590 /**
00591  * Initialize the connection struct
00592  */
00593 static err_t
00594 httpc_init_connection(httpc_state_t **connection, const httpc_connection_t *settings, const char* server_name,
00595                       u16_t server_port, const char* uri, altcp_recv_fn recv_fn, void* callback_arg)
00596 {
00597   return httpc_init_connection_common(connection, settings, server_name, server_port, uri, recv_fn, callback_arg, 1);
00598 }
00599 
00600 
00601 /**
00602  * Initialize the connection struct (from IP address)
00603  */
00604 static err_t
00605 httpc_init_connection_addr(httpc_state_t **connection, const httpc_connection_t *settings,
00606                            const ip_addr_t* server_addr, u16_t server_port, const char* uri,
00607                            altcp_recv_fn recv_fn, void* callback_arg)
00608 {
00609   char *server_addr_str = ipaddr_ntoa(server_addr);
00610   if (server_addr_str == NULL) {
00611     return ERR_VAL;
00612   }
00613   return httpc_init_connection_common(connection, settings, server_addr_str, server_port, uri,
00614     recv_fn, callback_arg, 1);
00615 }
00616 
00617 /**
00618  * @ingroup httpc 
00619  * HTTP client API: get a file by passing server IP address
00620  *
00621  * @param server_addr IP address of the server to connect
00622  * @param port tcp port of the server
00623  * @param uri uri to get from the server, remember leading "/"!
00624  * @param settings connection settings (callbacks, proxy, etc.)
00625  * @param recv_fn the http body (not the headers) are passed to this callback
00626  * @param callback_arg argument passed to all the callbacks
00627  * @param connection retreives the connection handle (to match in callbacks)
00628  * @return ERR_OK if starting the request succeeds (callback_fn will be called later)
00629  *         or an error code
00630  */
00631 err_t
00632 httpc_get_file(const ip_addr_t* server_addr, u16_t port, const char* uri, const httpc_connection_t *settings,
00633                altcp_recv_fn recv_fn, void* callback_arg, httpc_state_t **connection)
00634 {
00635   err_t err;
00636   httpc_state_t* req;
00637 
00638   LWIP_ERROR("invalid parameters", (server_addr != NULL) && (uri != NULL) && (recv_fn != NULL), return ERR_ARG;);
00639 
00640   err = httpc_init_connection_addr(&req, settings, server_addr, port,
00641     uri, recv_fn, callback_arg);
00642   if (err != ERR_OK) {
00643     return err;
00644   }
00645 
00646   if (settings->use_proxy) {
00647     err = httpc_get_internal_addr(req, &settings->proxy_addr);
00648   } else {
00649     err = httpc_get_internal_addr(req, server_addr);
00650   }
00651   if(err != ERR_OK) {
00652     httpc_free_state(req);
00653     return err;
00654   }
00655 
00656   if (connection != NULL) {
00657     *connection = req;
00658   }
00659   return ERR_OK;
00660 }
00661 
00662 /**
00663  * @ingroup httpc 
00664  * HTTP client API: get a file by passing server name as string (DNS name or IP address string)
00665  *
00666  * @param server_name server name as string (DNS name or IP address string)
00667  * @param port tcp port of the server
00668  * @param uri uri to get from the server, remember leading "/"!
00669  * @param settings connection settings (callbacks, proxy, etc.)
00670  * @param recv_fn the http body (not the headers) are passed to this callback
00671  * @param callback_arg argument passed to all the callbacks
00672  * @param connection retreives the connection handle (to match in callbacks)
00673  * @return ERR_OK if starting the request succeeds (callback_fn will be called later)
00674  *         or an error code
00675  */
00676 err_t
00677 httpc_get_file_dns(const char* server_name, u16_t port, const char* uri, const httpc_connection_t *settings,
00678                    altcp_recv_fn recv_fn, void* callback_arg, httpc_state_t **connection)
00679 {
00680   err_t err;
00681   httpc_state_t* req;
00682 
00683   LWIP_ERROR("invalid parameters", (server_name != NULL) && (uri != NULL) && (recv_fn != NULL), return ERR_ARG;);
00684 
00685   err = httpc_init_connection(&req, settings, server_name, port, uri, recv_fn, callback_arg);
00686   if (err != ERR_OK) {
00687     return err;
00688   }
00689 
00690   if (settings->use_proxy) {
00691     err = httpc_get_internal_addr(req, &settings->proxy_addr);
00692   } else {
00693     err = httpc_get_internal_dns(req, server_name);
00694   }
00695   if(err != ERR_OK) {
00696     httpc_free_state(req);
00697     return err;
00698   }
00699 
00700   if (connection != NULL) {
00701     *connection = req;
00702   }
00703   return ERR_OK;
00704 }
00705 
00706 #if LWIP_HTTPC_HAVE_FILE_IO
00707 /* Implementation to disk via fopen/fwrite/fclose follows */
00708 
00709 typedef struct _httpc_filestate
00710 {
00711   const char* local_file_name;
00712   FILE *file;
00713   httpc_connection_t settings;
00714   const httpc_connection_t *client_settings;
00715   void *callback_arg;
00716 } httpc_filestate_t;
00717 
00718 static void httpc_fs_result(void *arg, httpc_result_t httpc_result, u32_t rx_content_len,
00719   u32_t srv_res, err_t err);
00720 
00721 /** Initalize http client state for download to file system */
00722 static err_t
00723 httpc_fs_init(httpc_filestate_t **filestate_out, const char* local_file_name,
00724               const httpc_connection_t *settings, void* callback_arg)
00725 {
00726   httpc_filestate_t *filestate;
00727   size_t file_len, alloc_len;
00728   FILE *f;
00729 
00730   file_len = strlen(local_file_name);
00731   alloc_len = sizeof(httpc_filestate_t) + file_len + 1;
00732 
00733   filestate = (httpc_filestate_t *)mem_malloc((mem_size_t)alloc_len);
00734   if (filestate == NULL) {
00735     return ERR_MEM;
00736   }
00737   memset(filestate, 0, sizeof(httpc_filestate_t));
00738   filestate->local_file_name = (const char *)(filestate + 1);
00739   memcpy((char *)(filestate + 1), local_file_name, file_len + 1);
00740   filestate->file = NULL;
00741   filestate->client_settings = settings;
00742   filestate->callback_arg = callback_arg;
00743   /* copy client settings but override result callback */
00744   memcpy(&filestate->settings, settings, sizeof(httpc_connection_t));
00745   filestate->settings.result_fn = httpc_fs_result;
00746 
00747   f = fopen(local_file_name, "wb");
00748   if(f == NULL) {
00749     /* could not open file */
00750     mem_free(filestate);
00751     return ERR_VAL;
00752   }
00753   filestate->file = f;
00754   *filestate_out = filestate;
00755   return ERR_OK;
00756 }
00757 
00758 /** Free http client state for download to file system */
00759 static void
00760 httpc_fs_free(httpc_filestate_t *filestate)
00761 {
00762   if (filestate != NULL) {
00763     if (filestate->file != NULL) {
00764       fclose(filestate->file);
00765       filestate->file = NULL;
00766     }
00767     mem_free(filestate);
00768   }
00769 }
00770 
00771 /** Connection closed (success or error) */
00772 static void
00773 httpc_fs_result(void *arg, httpc_result_t httpc_result, u32_t rx_content_len,
00774                 u32_t srv_res, err_t err)
00775 {
00776   httpc_filestate_t *filestate = (httpc_filestate_t *)arg;
00777   if (filestate != NULL) {
00778     if (filestate->client_settings->result_fn != NULL) {
00779       filestate->client_settings->result_fn(filestate->callback_arg, httpc_result, rx_content_len,
00780         srv_res, err);
00781     }
00782     httpc_fs_free(filestate);
00783   }
00784 }
00785 
00786 /** tcp recv callback */
00787 static err_t
00788 httpc_fs_tcp_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err)
00789 {
00790   httpc_filestate_t *filestate = (httpc_filestate_t*)arg;
00791   struct pbuf* q;
00792   LWIP_UNUSED_ARG(err);
00793 
00794   LWIP_ASSERT("p != NULL", p != NULL);
00795 
00796   for (q = p; q != NULL; q = q->next) {
00797     fwrite(q->payload, 1, q->len, filestate->file);
00798   }
00799   altcp_recved (pcb, p->tot_len);
00800   pbuf_free(p);
00801   return ERR_OK;
00802 }
00803 
00804 /**
00805  * @ingroup httpc 
00806  * HTTP client API: get a file to disk by passing server IP address
00807  *
00808  * @param server_addr IP address of the server to connect
00809  * @param port tcp port of the server
00810  * @param uri uri to get from the server, remember leading "/"!
00811  * @param settings connection settings (callbacks, proxy, etc.)
00812  * @param callback_arg argument passed to all the callbacks
00813  * @param connection retreives the connection handle (to match in callbacks)
00814  * @return ERR_OK if starting the request succeeds (callback_fn will be called later)
00815  *         or an error code
00816  */
00817 err_t
00818 httpc_get_file_to_disk(const ip_addr_t* server_addr, u16_t port, const char* uri, const httpc_connection_t *settings,
00819                        void* callback_arg, const char* local_file_name, httpc_state_t **connection)
00820 {
00821   err_t err;
00822   httpc_state_t* req;
00823   httpc_filestate_t *filestate;
00824 
00825   LWIP_ERROR("invalid parameters", (server_addr != NULL) && (uri != NULL) && (local_file_name != NULL), return ERR_ARG;);
00826 
00827   err = httpc_fs_init(&filestate, local_file_name, settings, callback_arg);
00828   if (err != ERR_OK) {
00829     return err;
00830   }
00831 
00832   err = httpc_init_connection_addr(&req, &filestate->settings, server_addr, port,
00833     uri, httpc_fs_tcp_recv, filestate);
00834   if (err != ERR_OK) {
00835     httpc_fs_free(filestate);
00836     return err;
00837   }
00838 
00839   if (settings->use_proxy) {
00840     err = httpc_get_internal_addr(req, &settings->proxy_addr);
00841   } else {
00842     err = httpc_get_internal_addr(req, server_addr);
00843   }
00844   if(err != ERR_OK) {
00845     httpc_fs_free(filestate);
00846     httpc_free_state(req);
00847     return err;
00848   }
00849 
00850   if (connection != NULL) {
00851     *connection = req;
00852   }
00853   return ERR_OK;
00854 }
00855 
00856 /**
00857  * @ingroup httpc 
00858  * HTTP client API: get a file to disk by passing server name as string (DNS name or IP address string)
00859  *
00860  * @param server_name server name as string (DNS name or IP address string)
00861  * @param port tcp port of the server
00862  * @param uri uri to get from the server, remember leading "/"!
00863  * @param settings connection settings (callbacks, proxy, etc.)
00864  * @param callback_arg argument passed to all the callbacks
00865  * @param connection retreives the connection handle (to match in callbacks)
00866  * @return ERR_OK if starting the request succeeds (callback_fn will be called later)
00867  *         or an error code
00868  */
00869 err_t
00870 httpc_get_file_dns_to_disk(const char* server_name, u16_t port, const char* uri, const httpc_connection_t *settings,
00871                            void* callback_arg, const char* local_file_name, httpc_state_t **connection)
00872 {
00873   err_t err;
00874   httpc_state_t* req;
00875   httpc_filestate_t *filestate;
00876 
00877   LWIP_ERROR("invalid parameters", (server_name != NULL) && (uri != NULL) && (local_file_name != NULL), return ERR_ARG;);
00878 
00879   err = httpc_fs_init(&filestate, local_file_name, settings, callback_arg);
00880   if (err != ERR_OK) {
00881     return err;
00882   }
00883 
00884   err = httpc_init_connection(&req, &filestate->settings, server_name, port,
00885     uri, httpc_fs_tcp_recv, filestate);
00886   if (err != ERR_OK) {
00887     httpc_fs_free(filestate);
00888     return err;
00889   }
00890 
00891   if (settings->use_proxy) {
00892     err = httpc_get_internal_addr(req, &settings->proxy_addr);
00893   } else {
00894     err = httpc_get_internal_dns(req, server_name);
00895   }
00896   if(err != ERR_OK) {
00897     httpc_fs_free(filestate);
00898     httpc_free_state(req);
00899     return err;
00900   }
00901 
00902   if (connection != NULL) {
00903     *connection = req;
00904   }
00905   return ERR_OK;
00906 }
00907 #endif /* LWIP_HTTPC_HAVE_FILE_IO */
00908 
00909 #endif /* LWIP_TCP && LWIP_CALLBACK_API */