/**************************************************************************** * netutils/webclient/webclient.c * Implementation of the HTTP client. * * Copyright (C) 2007, 2009, 2011-2012, 2014, 2020 Gregory Nutt. * All rights reserved. * Author: Gregory Nutt * * Based on uIP which also has a BSD style license: * * Author: Adam Dunkels * Copyright (c) 2002, Adam Dunkels. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /* This example shows a HTTP client that is able to download web pages * and files from web servers. It requires a number of callback * functions to be implemented by the module that utilizes the code: * webclient_datahandler(). */ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "netutils/netlib.h" #include "netutils/webclient.h" #if defined(CONFIG_NETUTILS_CODECS) # if defined(CONFIG_CODECS_URLCODE) # include "netutils/urldecode.h" # endif # if defined(CONFIG_CODECS_BASE64) # include "netutils/base64.h" # endif #else # undef CONFIG_CODECS_URLCODE # undef CONFIG_CODECS_BASE64 #endif #ifndef CONFIG_NSH_WGET_USERAGENT # if CONFIG_VERSION_MAJOR != 0 || CONFIG_VERSION_MINOR != 0 # define CONFIG_NSH_WGET_USERAGENT \ "NuttX/" CONFIG_VERSION_STRING " (; http://www.nuttx.org/)" # else # define CONFIG_NSH_WGET_USERAGENT \ "NuttX/6.xx.x (; http://www.nuttx.org/)" # endif #endif /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #ifndef CONFIG_WEBCLIENT_TIMEOUT # define CONFIG_WEBCLIENT_TIMEOUT 10 #endif #ifndef CONFIG_WEBCLIENT_MAX_REDIRECT /* The default value 50 is taken from curl's --max-redirs option. */ # define CONFIG_WEBCLIENT_MAX_REDIRECT 50 #endif #define WEBCLIENT_STATE_STATUSLINE 0 #define WEBCLIENT_STATE_HEADERS 1 #define WEBCLIENT_STATE_DATA 2 #define WEBCLIENT_STATE_CLOSE 3 #define HTTPSTATUS_NONE 0 #define HTTPSTATUS_OK 1 #define HTTPSTATUS_MOVED 2 #define HTTPSTATUS_ERROR 3 #define ISO_NL 0x0a #define ISO_CR 0x0d #define ISO_SPACE 0x20 #define WGET_MODE_GET 0 #define WGET_MODE_POST 1 /**************************************************************************** * Private Types ****************************************************************************/ struct wget_s { /* Internal status */ uint8_t state; uint8_t httpstatus; uint16_t port; /* The port number to use in the connection */ /* These describe the just-received buffer of data */ FAR char *buffer; /* user-provided buffer */ int buflen; /* Length of the user provided buffer */ int offset; /* Offset to the beginning of interesting data */ int datend; /* Offset+1 to the last valid byte of data in the buffer */ /* Buffer HTTP header data and parse line at a time */ char line[CONFIG_WEBCLIENT_MAXHTTPLINE]; int ndx; #ifdef CONFIG_WEBCLIENT_GETMIMETYPE char mimetype[CONFIG_WEBCLIENT_MAXMIMESIZE]; #endif char scheme[sizeof("https") + 1]; char hostname[CONFIG_WEBCLIENT_MAXHOSTNAME]; char filename[CONFIG_WEBCLIENT_MAXFILENAME]; }; struct conn { bool tls; int sockfd; struct webclient_tls_connection *tls_conn; }; /**************************************************************************** * Private Data ****************************************************************************/ static const char g_http10[] = "HTTP/1.0"; static const char g_http11[] = "HTTP/1.1"; #ifdef CONFIG_WEBCLIENT_GETMIMETYPE static const char g_httpcontenttype[] = "content-type: "; #endif static const char g_httphost[] = "host: "; static const char g_httplocation[] = "location: "; static const char g_httpuseragentfields[] = "Connection: close\r\n" "User-Agent: " CONFIG_NSH_WGET_USERAGENT "\r\n\r\n"; static const char g_httpcrnl[] = "\r\n"; static const char g_httpform[] = "Content-Type: " "application/x-www-form-urlencoded"; static const char g_httpcontsize[] = "Content-Length: "; #if 0 static const char g_httpconn[] = "Connection: Keep-Alive"; static const char g_httpcache[] = "Cache-Control: no-cache"; #endif /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: conn_send ****************************************************************************/ static ssize_t conn_send(struct webclient_context *ctx, struct conn *conn, const void *buffer, size_t len) { if (conn->tls) { return ctx->tls_ops->send(ctx->tls_ctx, conn->tls_conn, buffer, len); } else { ssize_t ret = send(conn->sockfd, buffer, len, 0); if (ret == -1) { return -errno; } return ret; } } /**************************************************************************** * Name: conn_send_all ****************************************************************************/ static int conn_send_all(struct webclient_context *ctx, struct conn *conn, const void *buffer, size_t len) { size_t left = len; ssize_t ret; while (left > 0) { ret = conn_send(ctx, conn, buffer, left); if (ret < 0) { goto errout; } buffer += ret; left -= ret; } return len; errout: return ret; } /**************************************************************************** * Name: conn_recv ****************************************************************************/ static ssize_t conn_recv(struct webclient_context *ctx, struct conn *conn, void *buffer, size_t len) { if (conn->tls) { return ctx->tls_ops->recv(ctx->tls_ctx, conn->tls_conn, buffer, len); } else { ssize_t ret = recv(conn->sockfd, buffer, len, 0); if (ret == -1) { return -errno; } return ret; } } /**************************************************************************** * Name: conn_close ****************************************************************************/ static void conn_close(struct webclient_context *ctx, struct conn *conn) { if (conn->tls) { ctx->tls_ops->close(ctx->tls_ctx, conn->tls_conn); } else { close(conn->sockfd); } } /**************************************************************************** * Name: webclient_static_body_func ****************************************************************************/ static int webclient_static_body_func(FAR void *buffer, FAR size_t *sizep, FAR const void * FAR *datap, size_t reqsize, FAR void *ctx) { *datap = ctx; *sizep = reqsize; return 0; } /**************************************************************************** * Name: append ****************************************************************************/ static char *append(char *dest, char *ep, const char *src) { int len; if (dest == NULL) { return NULL; } len = strlen(src); if (ep - dest < len + 1) { return NULL; } memcpy(dest, src, len); dest[len] = '\0'; return dest + len; } /**************************************************************************** * Name: wget_strcpy ****************************************************************************/ #ifdef WGET_USE_URLENCODE static char *wget_strcpy(char *dest, const char *src) { int len = strlen(src); memcpy(dest, src, len); dest[len] = '\0'; return dest + len; } #endif /**************************************************************************** * Name: wget_urlencode_strcpy ****************************************************************************/ #ifdef WGET_USE_URLENCODE static char *wget_urlencode_strcpy(char *dest, const char *src) { int len = strlen(src); int d_len; d_len = urlencode_len(src, len); urlencode(src, len, dest, &d_len); return dest + d_len; } #endif /**************************************************************************** * Name: wget_parsestatus ****************************************************************************/ static inline int wget_parsestatus(struct webclient_context *ctx, struct wget_s *ws) { int offset; int ndx; char *dest; offset = ws->offset; ndx = ws->ndx; while (offset < ws->datend) { ws->line[ndx] = ws->buffer[offset]; if (ws->line[ndx] == ISO_NL) { ws->line[ndx] = '\0'; if ((strncmp(ws->line, g_http10, strlen(g_http10)) == 0) || (strncmp(ws->line, g_http11, strlen(g_http11)) == 0)) { unsigned long http_status; char *ep; dest = &(ws->line[9]); ws->httpstatus = HTTPSTATUS_NONE; errno = 0; http_status = strtoul(dest, &ep, 10); if (ep == dest) { return -EINVAL; } if (*ep != ' ') { return -EINVAL; } if (errno != 0) { return -errno; } ctx->http_status = http_status; if (ctx->http_reason != NULL) { strncpy(ctx->http_reason, ep + 1, ctx->http_reason_len - 1); ctx->http_reason[ctx->http_reason_len - 1] = 0; } /* Check for 2xx (Successful) */ if ((http_status / 100) == 2) { ws->httpstatus = HTTPSTATUS_OK; } /* Check for 3xx (Redirection) * Location: header line will contain the new location. */ else if ((http_status / 100) == 3) { ws->httpstatus = HTTPSTATUS_MOVED; } } else { return - ECONNABORTED; } /* We're done parsing the status line, so start parsing * the HTTP headers. */ ws->state = WEBCLIENT_STATE_HEADERS; break; } else { offset++; ndx++; } } ws->offset = offset; ws->ndx = ndx; return OK; } /**************************************************************************** * Name: parseurl ****************************************************************************/ static int parseurl(FAR const char *url, FAR struct wget_s *ws) { struct url_s url_s; int ret; memset(&url_s, 0, sizeof(url_s)); url_s.scheme = ws->scheme; url_s.schemelen = sizeof(ws->scheme); url_s.host = ws->hostname; url_s.hostlen = sizeof(ws->hostname); url_s.path = ws->filename; url_s.pathlen = sizeof(ws->filename); ret = netlib_parseurl(url, &url_s); if (ret < 0) { return ret; } if (url_s.port == 0) { if (!strcmp(ws->scheme, "https")) { ws->port = 443; } else { ws->port = 80; } } else { ws->port = url_s.port; } return 0; } /**************************************************************************** * Name: wget_parseheaders ****************************************************************************/ static inline int wget_parseheaders(struct wget_s *ws) { int offset; int ndx; int ret = OK; offset = ws->offset; ndx = ws->ndx; while (offset < ws->datend) { ws->line[ndx] = ws->buffer[offset]; if (ws->line[ndx] == ISO_NL) { /* We have an entire HTTP header line in s.line, so * we parse it. */ if (ndx > 0) /* Should always be true */ { if (ws->line[0] == ISO_CR) { /* This was the last header line (i.e., and empty "\r\n"), * so we are done with the headers and proceed with the * actual data. */ ws->state = WEBCLIENT_STATE_DATA; goto exit; } /* Truncate the trailing \r\n */ ws->line[ndx - 1] = '\0'; /* Check for specific HTTP header fields. */ #ifdef CONFIG_WEBCLIENT_GETMIMETYPE if (strncasecmp(ws->line, g_httpcontenttype, strlen(g_httpcontenttype)) == 0) { /* Found Content-type field. */ char *dest = strchr(ws->line, ';'); if (dest != NULL) { *dest = 0; } strncpy(ws->mimetype, ws->line + strlen(g_httpcontenttype), sizeof(ws->mimetype)); } else #endif if (strncasecmp(ws->line, g_httplocation, strlen(g_httplocation)) == 0) { /* Parse the new host and filename from the URL. */ ninfo("Redirect to location: '%s'\n", ws->line + strlen(g_httplocation)); ret = parseurl(ws->line + strlen(g_httplocation), ws); ninfo("New hostname='%s' filename='%s'\n", ws->hostname, ws->filename); } } /* We're done parsing this line, so we reset the index to the start * of the next line. */ ndx = 0; } else { ndx++; } offset++; } exit: ws->offset = ++offset; ws->ndx = ndx; return ret; } /**************************************************************************** * Name: wget_gethostip * * Description: * Call getaddrinfo() to get the IPv4 address associated with a hostname. * * Input Parameters * hostname - The host name to use in the nslookup. * * Output Parameters * dest - The location to return the IPv4 address. * * Returned Value: * Zero (OK) on success; ERROR on failure. * ****************************************************************************/ static int wget_gethostip(FAR char *hostname, FAR struct in_addr *dest) { #ifdef CONFIG_LIBC_NETDB FAR struct addrinfo hint; FAR struct addrinfo *info; FAR struct sockaddr_in *addr; memset(&hint, 0, sizeof(hint)); hint.ai_family = AF_INET; if (getaddrinfo(hostname, NULL, &hint, &info) != OK) { return ERROR; } addr = (FAR struct sockaddr_in *)info->ai_addr; memcpy(dest, &addr->sin_addr, sizeof(struct in_addr)); freeaddrinfo(info); return OK; #else /* No host name support */ /* Convert strings to numeric IPv4 address */ int ret = inet_pton(AF_INET, hostname, dest); /* The inet_pton() function returns 1 if the conversion succeeds. It will * return 0 if the input is not a valid IPv4 dotted-decimal string or -1 * with errno set to EAFNOSUPPORT if the address family argument is * unsupported. */ return (ret > 0) ? OK : ERROR; #endif } /**************************************************************************** * Name: webclient_perform * * Returned Value: * 0: if the operation completed successfully; * Negative errno: On a failure * ****************************************************************************/ int webclient_perform(FAR struct webclient_context *ctx) { struct sockaddr_in server; struct wget_s *ws; struct timeval tv; bool redirected; unsigned int nredirect = 0; char *dest; char *ep; struct conn conn; FAR const struct webclient_tls_ops *tls_ops = ctx->tls_ops; FAR const char *method = ctx->method; FAR void *tls_ctx = ctx->tls_ctx; unsigned int i; int len; int ret; /* Initialize the state structure */ ws = calloc(1, sizeof(struct wget_s)); if (!ws) { return -errno; } ws->buffer = ctx->buffer; ws->buflen = ctx->buflen; /* Parse the hostname (with optional port number) and filename * from the URL. */ ret = parseurl(ctx->url, ws); if (ret != 0) { nwarn("WARNING: Malformed URL: %s\n", ctx->url); free(ws); return ret; } ninfo("hostname='%s' filename='%s'\n", ws->hostname, ws->filename); /* The following sequence may repeat indefinitely if we are redirected */ do { if (!strcmp(ws->scheme, "https") && tls_ops != NULL) { conn.tls = true; } else if (!strcmp(ws->scheme, "http")) { conn.tls = false; } else { nerr("ERROR: unsupported scheme: %s\n", ws->scheme); free(ws); return -ENOTSUP; } /* Re-initialize portions of the state structure that could have * been left from the previous time through the loop and should not * persist with the new connection. */ ws->httpstatus = HTTPSTATUS_NONE; ws->offset = 0; ws->datend = 0; ws->ndx = 0; if (conn.tls) { char port_str[sizeof("65535")]; snprintf(port_str, sizeof(port_str), "%u", ws->port); ret = tls_ops->connect(tls_ctx, ws->hostname, port_str, CONFIG_WEBCLIENT_TIMEOUT, &conn.tls_conn); } else { /* Create a socket */ conn.sockfd = socket(AF_INET, SOCK_STREAM, 0); if (conn.sockfd < 0) { /* socket failed. It will set the errno appropriately */ nerr("ERROR: socket failed: %d\n", errno); free(ws); return -errno; } /* Set send and receive timeout values */ tv.tv_sec = CONFIG_WEBCLIENT_TIMEOUT; tv.tv_usec = 0; setsockopt(conn.sockfd, SOL_SOCKET, SO_RCVTIMEO, (FAR const void *)&tv, sizeof(struct timeval)); setsockopt(conn.sockfd, SOL_SOCKET, SO_SNDTIMEO, (FAR const void *)&tv, sizeof(struct timeval)); /* Get the server address from the host name */ server.sin_family = AF_INET; server.sin_port = htons(ws->port); ret = wget_gethostip(ws->hostname, &server.sin_addr); if (ret < 0) { /* Could not resolve host (or malformed IP address) */ nwarn("WARNING: Failed to resolve hostname\n"); ret = -EHOSTUNREACH; goto errout_with_errno; } /* Connect to server. First we have to set some fields in the * 'server' address structure. The system will assign me an * arbitrary local port that is not in use. */ ret = connect(conn.sockfd, (struct sockaddr *)&server, sizeof(struct sockaddr_in)); if (ret == -1) { ret = -errno; } } if (ret < 0) { nerr("ERROR: connect failed: %d\n", errno); free(ws); return ret; } /* Send the request */ dest = ws->buffer; ep = ws->buffer + ws->buflen; dest = append(dest, ep, method); dest = append(dest, ep, " "); #ifndef WGET_USE_URLENCODE dest = append(dest, ep, ws->filename); #else /* TODO: should we use wget_urlencode_strcpy? */ dest = append(dest, ep, ws->filename); #endif dest = append(dest, ep, " "); dest = append(dest, ep, g_http10); dest = append(dest, ep, g_httpcrnl); dest = append(dest, ep, g_httphost); dest = append(dest, ep, ws->hostname); dest = append(dest, ep, g_httpcrnl); for (i = 0; i < ctx->nheaders; i++) { dest = append(dest, ep, ctx->headers[i]); dest = append(dest, ep, g_httpcrnl); } if (ctx->bodylen) { char post_size[sizeof("18446744073709551615")]; dest = append(dest, ep, g_httpcontsize); sprintf(post_size, "%zu", ctx->bodylen); dest = append(dest, ep, post_size); dest = append(dest, ep, g_httpcrnl); } dest = append(dest, ep, g_httpuseragentfields); if (dest == NULL) { ret = -E2BIG; goto errout_with_errno; } len = dest - ws->buffer; ret = conn_send_all(ctx, &conn, ws->buffer, len); if (ret >= 0) { size_t sent = 0; while (sent < ctx->bodylen) { FAR const void *input_buffer; size_t input_buffer_size = ws->buflen; size_t todo = ctx->bodylen - sent; if (input_buffer_size > todo) { input_buffer_size = todo; } input_buffer = ws->buffer; ret = ctx->body_callback(ws->buffer, &input_buffer_size, &input_buffer, todo, ctx->body_callback_arg); if (ret < 0) { nerr("ERROR: body_callback failed: %d\n", -ret); goto errout_with_errno; } ret = conn_send_all(ctx, &conn, input_buffer, input_buffer_size); if (ret < 0) { break; } sent += input_buffer_size; } } if (ret < 0) { nerr("ERROR: send failed: %d\n", -ret); goto errout_with_errno; } /* Now loop to get the file sent in response to the GET. This * loop continues until either we read the end of file (nbytes == 0) * or until we detect that we have been redirected. */ ws->state = WEBCLIENT_STATE_STATUSLINE; redirected = false; for (; ; ) { ws->datend = conn_recv(ctx, &conn, ws->buffer, ws->buflen); if (ws->datend < 0) { ret = ws->datend; nerr("ERROR: recv failed: %d\n", -ret); goto errout_with_errno; } else if (ws->datend == 0) { ninfo("Connection lost\n"); break; } /* Handle initial parsing of the status line */ ws->offset = 0; if (ws->state == WEBCLIENT_STATE_STATUSLINE) { ret = wget_parsestatus(ctx, ws); if (ret < 0) { goto errout_with_errno; } } /* Parse the HTTP data */ if (ws->state == WEBCLIENT_STATE_HEADERS) { ret = wget_parseheaders(ws); if (ret < 0) { goto errout_with_errno; } } /* Dispose of the data payload */ if (ws->state == WEBCLIENT_STATE_DATA) { if (ws->httpstatus != HTTPSTATUS_MOVED) { /* Let the client decide what to do with the * received file. */ if (ctx->sink_callback) { ret = ctx->sink_callback(&ws->buffer, ws->offset, ws->datend, &ws->buflen, ctx->sink_callback_arg); if (ret != 0) { goto errout_with_errno; } } else { ctx->callback(&ws->buffer, ws->offset, ws->datend, &ws->buflen, ctx->sink_callback_arg); } } else { redirected = true; nredirect++; if (nredirect > CONFIG_WEBCLIENT_MAX_REDIRECT) { nerr("ERROR: too many redirects (%u)\n", nredirect); goto errout; } break; } } } conn_close(ctx, &conn); } while (redirected); free(ws); return OK; errout: ret = -errno; errout_with_errno: conn_close(ctx, &conn); free(ws); return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: web_post_str ****************************************************************************/ #ifdef WGET_USE_URLENCODE char *web_post_str(FAR char *buffer, int *size, FAR char *name, FAR char *value) { char *dst = buffer; buffer = wget_strcpy(buffer, name); buffer = wget_strcpy(buffer, "="); buffer = wget_urlencode_strcpy(buffer, value); *size = buffer - dst; return dst; } #endif /**************************************************************************** * Name: web_post_strlen ****************************************************************************/ #ifdef WGET_USE_URLENCODE int web_post_strlen(FAR char *name, FAR char *value) { return strlen(name) + urlencode_len(value, strlen(value)) + 1; } #endif /**************************************************************************** * Name: web_posts_str ****************************************************************************/ #ifdef WGET_USE_URLENCODE char *web_posts_str(FAR char *buffer, int *size, FAR char **name, FAR char **value, int len) { char *dst = buffer; int wlen; int i; for (i = 0; i < len; i++) { if (i > 0) { buffer = wget_strcpy(buffer, "&"); } wlen = *size; buffer = web_post_str(buffer, &wlen, name[i], value[i]); buffer += wlen; } *size = buffer - dst; return dst; } #endif /**************************************************************************** * Name: web_posts_strlen ****************************************************************************/ #ifdef WGET_USE_URLENCODE int web_posts_strlen(FAR char **name, FAR char **value, int len) { int wlen = 0; int i; for (i = 0; i < len; i++) { wlen += web_post_strlen(name[i], value[i]); } return wlen + len - 1; } #endif /**************************************************************************** * Name: wget * * Description: * Obtain the requested file from an HTTP server using the GET method. * * Input Parameters * url - A pointer to a string containing either the full URL to * the file to get (e.g., http://www.nutt.org/index.html, or * http://192.168.23.1:80/index.html). * buffer - A user provided buffer to receive the file data (also * used for the outgoing GET request * buflen - The size of the user provided buffer * callback - As data is obtained from the host, this function is * to dispose of each block of file data as it is received. * arg - User argument passed to callback. * * Returned Value: * 0: if the GET operation completed successfully; * -1: On a failure with errno set appropriately * ****************************************************************************/ int wget(FAR const char *url, FAR char *buffer, int buflen, wget_callback_t callback, FAR void *arg) { struct webclient_context ctx; int ret; webclient_set_defaults(&ctx); ctx.method = "GET"; ctx.url = url; ctx.buffer = buffer; ctx.buflen = buflen; ctx.callback = callback; ctx.sink_callback_arg = arg; ret = webclient_perform(&ctx); if (ret < 0) { errno = -ret; return ERROR; } return OK; } /**************************************************************************** * Name: wget_post ****************************************************************************/ int wget_post(FAR const char *url, FAR const char *posts, FAR char *buffer, int buflen, wget_callback_t callback, FAR void *arg) { static const char *headers = g_httpform; struct webclient_context ctx; int ret; webclient_set_defaults(&ctx); ctx.method = "POST"; ctx.url = url; ctx.buffer = buffer; ctx.buflen = buflen; ctx.callback = callback; ctx.sink_callback_arg = arg; ctx.headers = &headers; ctx.nheaders = 1; webclient_set_static_body(&ctx, posts, strlen(posts)); ret = webclient_perform(&ctx); if (ret < 0) { errno = -ret; return ERROR; } return OK; } /**************************************************************************** * Name: webclient_set_defaults ****************************************************************************/ void webclient_set_defaults(FAR struct webclient_context *ctx) { memset(ctx, 0, sizeof(*ctx)); ctx->method = "GET"; } /**************************************************************************** * Name: webclient_set_static_body ****************************************************************************/ void webclient_set_static_body(FAR struct webclient_context *ctx, FAR const void *body, size_t bodylen) { ctx->body_callback = webclient_static_body_func; ctx->body_callback_arg = (void *)body; /* discard const */ ctx->bodylen = bodylen; }