nuttx-apps/netutils/libcurl4nx/curl4nx_easy_perform.c
Xiang Xiao d660492289 Run codespell -w against all files
and fix the wrong correction
2020-02-22 14:41:36 -06:00

643 lines
23 KiB
C

/****************************************************************************
* apps/netutils/libcurl4nx/curl4nx_easy_perform.c
* Implementation of the HTTP client, cURL like interface.
*
* Copyright (C) 2019 Gregory Nutt. All rights reserved.
* Author: Sebastien Lorquet <sebastien@lorquet.fr>
*
* 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/compiler.h>
#include <debug.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <strings.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <nuttx/version.h>
#include "netutils/netlib.h"
#include "netutils/curl4nx.h"
#include "curl4nx_private.h"
#if defined(CONFIG_NETUTILS_CODECS)
# if defined(CONFIG_CODECS_URLCODE)
# define WGET_USE_URLENCODE 1
# 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
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define CURL4NX_STATE_STATUSLINE 0
#define CURL4NX_STATE_STATUSCODE 1
#define CURL4NX_STATE_STATUSREASON 2
#define CURL4NX_STATE_HEADERS 3
#define CURL4NX_STATE_DATA_NORMAL 4
#define CURL4NX_STATE_DATA_CHUNKED 5
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: curl4nx_easy_perform()
*
* Description:
* Resolve the hostname to an IP address. There are 3 cases:
* - direct IP: inet_aton is used
* - using internal curl4nx resolution
* - DNS resolution, if available
****************************************************************************/
/****************************************************************************
* Name: curl4nx_resolve
* Description:
* Translate a host name to an IP address (V4 only for the moment)
* - either the host is a string with an IP
* - either the known hosts were defined by CURL4NXOPT_
****************************************************************************/
static int curl4nx_resolve(FAR struct curl4nx_s *handle, FAR char *hostname,
FAR struct in_addr *ip)
{
int ret = inet_aton(hostname, ip);
if (ret != 0)
{
return CURL4NXE_OK; /* IP address is valid */
}
curl4nx_warn("Not a valid IP address, trying hostname resolution\n");
return CURL4NXE_COULDNT_RESOLVE_HOST;
}
/****************************************************************************
* Name: curl4nx_is_header
* Description:
* Return TRUE if buf contains header, then update off to point at the
* beginning of header value
****************************************************************************/
static int curl4nx_is_header(FAR char *buf, int len,
FAR const char *header, FAR int *off)
{
if (strncasecmp(buf, header, strlen(header)))
{
return false;
}
*off = strlen(header);
while ((*off) < len && buf[*off] == ' ')
{
(*off)++;
}
return true;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: curl4nx_easy_perform()
****************************************************************************/
int curl4nx_easy_perform(FAR struct curl4nx_s *handle)
{
struct sockaddr_in server;
FILE * stream; /* IOSTREAM used to printf in the socket TODO avoid */
int rxoff; /* Current offset within RX buffer */
char tmpbuf[16]; /* Buffer to hold small strings */
int tmplen; /* Number of bytes used in tmpbuffer */
int state; /* Current state of the parser */
int ret; /* Return value from internal calls */
char * headerbuf;
int headerlen;
char * end;
int cret = CURL4NXE_OK; /* Public return value */
bool redirected = false; /* Boolean to manage HTTP redirections */
bool chunked = false;
unsigned long long done = 0;
int redircount = 0;
curl4nx_info("started\n");
stream = NULL;
headerbuf = malloc(CONFIG_LIBCURL4NX_MAXHEADERLINE);
if (!headerbuf)
{
cret = CURL4NXE_OUT_OF_MEMORY;
goto abort;
}
if (handle->host[0] == 0 || handle->port == 0)
{
/* URL has not been set */
cret = CURL4NXE_URL_MALFORMAT;
goto freebuf;
}
/* TODO: check that host and port have changed or are the same, so we can
* recycle the socket for the next request.
*/
do
{
handle->sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (handle->sockfd < 0)
{
/* socket failed. It will set the errno appropriately */
curl4nx_err("ERROR: socket failed: %d\n", errno);
cret = CURL4NXE_COULDNT_CONNECT;
goto freebuf;
}
/* TODO: timeouts */
server.sin_family = AF_INET;
server.sin_port = htons(handle->port);
cret = curl4nx_resolve(handle, handle->host, &server.sin_addr);
if (cret != CURL4NXE_OK)
{
/* Could not resolve host (or malformed IP address) */
curl4nx_err("ERROR: Failed to resolve hostname\n");
cret = CURL4NXE_COULDNT_RESOLVE_HOST;
goto freebuf;
}
/* 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(handle->sockfd,
(struct sockaddr *)&server, sizeof(struct sockaddr_in));
if (ret < 0)
{
curl4nx_err("ERROR: connect failed: %d\n", errno);
cret = CURL4NXE_COULDNT_CONNECT;
goto close;
}
curl4nx_info("Connected...\n");
stream = fdopen(handle->sockfd, "wb");
/* Send request */
fprintf(stream, "%s %s HTTP/%d.%d\r\n",
handle->method,
handle->path,
handle->version >> 4,
handle->version & 0x0f);
/* Send headers */
fprintf(stream, "Host: %s\r\n", handle->host);
/* For the moment we do not support compression */
fprintf(stream, "Content-encoding: identity\r\n");
/* Send more headers */
/* End of headers */
fprintf(stream, "\r\n");
/* TODO send data */
fflush(stream);
curl4nx_info("Request sent\n");
/* Now wait for the result */
redirected = false; /* for now */
state = CURL4NX_STATE_STATUSLINE;
tmplen = 0;
while (1)
{
ret = recv(handle->sockfd, handle->rxbuf, handle->rxbufsize, 0);
if (ret < 0)
{
curl4nx_err("RECV failed, errno=%d\n", errno);
cret = CURL4NXE_RECV_ERROR;
goto close;
}
else if (ret == 0)
{
curl4nx_err("Connection lost\n");
cret = CURL4NXE_GOT_NOTHING;
goto close;
}
curl4nx_info("Received %d bytes\n", ret);
rxoff = 0;
if (state == CURL4NX_STATE_STATUSLINE)
{
/* Accumulate HTTP/x.y until space */
while ((tmplen < sizeof(tmpbuf)) && (rxoff < ret))
{
if (handle->rxbuf[rxoff] == ' ')
{
/* found space after http version */
tmpbuf[tmplen] = 0; /* Finish pending string */
rxoff++;
curl4nx_info("received version: [%s]\n", tmpbuf);
tmplen = 0; /* reset buffer to look at code */
state = CURL4NX_STATE_STATUSCODE;
break;
}
/* Not a space: accumulate chars */
tmpbuf[tmplen] = handle->rxbuf[rxoff];
tmplen++;
rxoff++;
}
/* Check for overflow, version code should not fill the tmpbuf */
if (tmplen == sizeof(tmpbuf))
{
/* extremely long http version -> invalid response */
curl4nx_err("Buffer overflow while reading version\n");
cret = CURL4NXE_RECV_ERROR;
goto close;
}
/* No overflow and no space found: wait for next buffer */
}
/* NO ELSE HERE, state may have changed and require new management
* for the same rx buffer.
*/
if (state == CURL4NX_STATE_STATUSCODE)
{
/* Accumulate response code until space */
while ((tmplen < sizeof(tmpbuf)) && (rxoff < ret))
{
if (handle->rxbuf[rxoff] == ' ')
{
/* Found space after http version */
tmpbuf[tmplen] = 0; /* Finish pending string */
rxoff++;
curl4nx_info("received code: [%s]\n", tmpbuf);
handle->status = strtol(tmpbuf, &end, 10);
if (*end != 0)
{
curl4nx_err("Bad status code [%s]\n", tmpbuf);
cret = CURL4NXE_RECV_ERROR;
goto close;
}
tmplen = 0; /* reset buffer to look at code */
state = CURL4NX_STATE_STATUSREASON;
break;
}
/* Not a space: accumulate chars */
tmpbuf[tmplen] = handle->rxbuf[rxoff];
tmplen++;
rxoff++;
}
/* Check for overflow, version code should not fill the tmpbuf */
if (tmplen == sizeof(tmpbuf))
{
/* Extremely long http code -> invalid response */
curl4nx_err("Buffer overflow while reading code\n");
cret = CURL4NXE_RECV_ERROR;
goto close;
}
/* No overflow and no space found: wait for next buffer */
}
if (state == CURL4NX_STATE_STATUSREASON)
{
/* Accumulate response code until CRLF */
while (rxoff < ret)
{
if (handle->rxbuf[rxoff] == 0x0d ||
handle->rxbuf[rxoff] == 0x0a)
{
/* Accumulate all contiguous CR and LF in any order */
tmpbuf[tmplen] = handle->rxbuf[rxoff];
tmplen++;
if (tmplen == 2)
{
if (tmpbuf[0] == 0x0d && tmpbuf[1] == 0x0a)
{
headerlen = 0;
handle->content_length = 0;
state = CURL4NX_STATE_HEADERS;
break;
}
else
{
tmplen = 0; /* Reset search for CRLF */
}
}
}
else
{
/* This char is not interesting: reset storage */
tmplen = 0;
/* curl4nx_info("-> %c\n", handle->rxbuf[rxoff]); */
}
rxoff++;
}
}
if (state == CURL4NX_STATE_HEADERS)
{
while (rxoff < ret)
{
if (handle->rxbuf[rxoff] == 0x0d ||
handle->rxbuf[rxoff] == 0x0a)
{
/* Accumulate all contiguous CR and LF in any order */
tmpbuf[tmplen] = handle->rxbuf[rxoff];
tmplen++;
if (tmplen == 2)
{
if (tmpbuf[0] == 0x0d && tmpbuf[1] == 0x0a)
{
if (headerlen == 0) /* Found an empty header */
{
curl4nx_info("<End of headers>\n");
rxoff++; /* Skip this char */
state = chunked ?
CURL4NX_STATE_DATA_CHUNKED :
CURL4NX_STATE_DATA_NORMAL;
break;
}
else
{
int off;
curl4nx_iofunc_f func;
headerbuf[headerlen] = 0;
func = handle->headerfunc;
if (func == NULL)
{
func = handle->writefunc;
}
func(headerbuf, headerlen, 1,
handle->headerdata);
/* Find the content-length */
if (curl4nx_is_header(headerbuf, headerlen,
"content-length:",
&off))
{
handle->content_length =
strtoull(headerbuf + off, &end, 10);
if (*end != 0)
{
curl4nx_err("Stray chars after "
"content length!\n");
cret = CURL4NXE_RECV_ERROR;
goto close;
}
curl4nx_info("Found content length: "
"%llu\n",
handle->content_length);
}
/* Find the transfer encoding */
if (curl4nx_is_header(headerbuf, headerlen,
"transfer-encoding:",
&off))
{
chunked = !strncasecmp(headerbuf + off,
"chunked", 7);
if (chunked)
{
curl4nx_info("Transfer using "
"chunked format\n");
}
}
/* Find the location */
if (curl4nx_is_header(headerbuf, headerlen,
"location:",
&off))
{
/* Parse the new URL if we get a
* redirection code.
*/
if (handle->status >= 300 &&
handle->status < 400)
{
if (handle->flags &
CURL4NX_FLAGS_FOLLOWLOCATION)
{
if ((handle->max_redirs > 0) &&
(redircount >=
handle->max_redirs))
{
curl4nx_info(
"Too many redirections\n");
cret = CURL4NXE_TOO_MANY_REDIRECTS;
goto close;
}
cret =
curl4nx_easy_setopt(handle,
CURL4NXOPT_URL,
headerbuf + off);
if (cret != CURL4NXE_OK)
{
goto close;
}
redirected = true;
redircount += 1;
curl4nx_info("REDIRECTION (%d) -> %s\n",
redircount,
headerbuf + off);
}
}
}
/* Prepare for next header */
headerlen = 0;
tmplen = 0; /* Reset search for CRLF */
}
}
else
{
tmplen = 0; /* Reset search for CRLF */
}
}
}
else
{
tmplen = 0; /* Reset CRLF detection */
/* Plus one for final zero */
if (headerlen < (CONFIG_LIBCURL4NX_MAXHEADERLINE - 1))
{
headerbuf[headerlen] = handle->rxbuf[rxoff];
headerlen++;
}
else
{
curl4nx_warn("prevented header overload\n");
}
}
rxoff++;
}
}
if (state == CURL4NX_STATE_DATA_NORMAL)
{
if ((handle->flags & CURL4NX_FLAGS_FAILONERROR) &&
handle->status >= 400)
{
cret = CURL4NXE_HTTP_RETURNED_ERROR;
goto close;
}
curl4nx_info("now in normal data state, rxoff=%d rxlen=%d\n",
rxoff, ret);
done += (ret - rxoff);
handle->writefunc(handle->rxbuf + rxoff, ret - rxoff, 1,
handle->writedata);
if (handle->content_length != 0)
{
curl4nx_info("Done %llu of %llu\n",
done, handle->content_length);
if (handle->progressfunc)
{
handle->progressfunc(handle->progressdata,
handle->content_length,
done, 0, 0);
}
if (handle->content_length == done)
{
/* Transfer is complete */
goto close;
}
}
}
if (state == CURL4NX_STATE_DATA_CHUNKED)
{
if ((handle->flags & CURL4NX_FLAGS_FAILONERROR) &&
handle->status >= 400)
{
cret = CURL4NXE_HTTP_RETURNED_ERROR;
goto close;
}
curl4nx_info("now in chunked data state, rxoff=%d rxlen=%d\n",
rxoff, ret);
curl4nx_err("Not supported yet.\n");
goto close;
}
}
/* Done with this connection - this will also close the socket */
close:
curl4nx_info("Closing\n");
if (stream)
{
fclose(stream);
}
}
while (redirected);
freebuf:
free(headerbuf);
abort:
curl4nx_info("done\n");
return cret;
}