mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-09-25 04:09:31 +03:00
693 lines
16 KiB
C
693 lines
16 KiB
C
/*
|
|
This file is part of GNU libmicrohttpd
|
|
Copyright (C) 2024 Christian Grothoff
|
|
|
|
GNU libmicrohttpd is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
GNU libmicrohttpd is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*/
|
|
|
|
/**
|
|
* @file libtest_convenience.c
|
|
* @brief convenience functions implementing clients making requests for libtest users
|
|
* @author Christian Grothoff
|
|
*/
|
|
#include <pthread.h>
|
|
#include <stdbool.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include "libtest.h"
|
|
#include <curl/curl.h>
|
|
|
|
|
|
#ifndef CURL_VERSION_BITS
|
|
# define CURL_VERSION_BITS(x,y,z) ((x) << 16 | (y) << 8 | (z))
|
|
#endif
|
|
#ifndef CURL_AT_LEAST_VERSION
|
|
# define CURL_AT_LEAST_VERSION(x,y,z) \
|
|
(LIBCURL_VERSION_NUM >= CURL_VERSION_BITS (x, y, z))
|
|
#endif
|
|
|
|
#if CURL_AT_LEAST_VERSION (7,83,0)
|
|
# define HAVE_LIBCRUL_NEW_HDR_API 1
|
|
#endif
|
|
|
|
|
|
/**
|
|
* Closure for the write_cb().
|
|
*/
|
|
struct WriteBuffer
|
|
{
|
|
/**
|
|
* Where to store the response.
|
|
*/
|
|
char *buf;
|
|
|
|
/**
|
|
* Number of bytes in @e buf.
|
|
*/
|
|
size_t len;
|
|
|
|
/**
|
|
* Current write offset in @e buf.
|
|
*/
|
|
size_t pos;
|
|
|
|
/**
|
|
* Set to non-zero on errors (buffer full).
|
|
*/
|
|
int err;
|
|
};
|
|
|
|
|
|
/**
|
|
* Callback for CURLOPT_WRITEFUNCTION processing
|
|
* data downloaded from the HTTP server.
|
|
*
|
|
* @param ptr data uploaded
|
|
* @param size size of a member
|
|
* @param nmemb number of members
|
|
* @param stream must be a `struct WriteBuffer`
|
|
* @return bytes processed (size*nmemb) or error
|
|
*/
|
|
static size_t
|
|
write_cb (void *ptr,
|
|
size_t size,
|
|
size_t nmemb,
|
|
void *stream)
|
|
{
|
|
struct WriteBuffer *wb = stream;
|
|
size_t prod = size * nmemb;
|
|
|
|
if ( (prod / size != nmemb) ||
|
|
(wb->pos + prod < wb->pos) ||
|
|
(wb->pos + prod > wb->len) )
|
|
{
|
|
wb->err = 1;
|
|
return CURLE_WRITE_ERROR;
|
|
}
|
|
memcpy (wb->buf + wb->pos,
|
|
ptr,
|
|
prod);
|
|
wb->pos += prod;
|
|
return prod;
|
|
}
|
|
|
|
|
|
/**
|
|
* Declare variables needed to check a download.
|
|
*
|
|
* @param text text data we expect to receive
|
|
*/
|
|
#define DECLARE_WB(text) \
|
|
size_t wb_tlen = strlen (text); \
|
|
char wb_buf[wb_tlen]; \
|
|
struct WriteBuffer wb = { \
|
|
.buf = wb_buf, \
|
|
.len = wb_tlen \
|
|
}
|
|
|
|
|
|
/**
|
|
* Set CURL options to the write_cb() and wb buffer
|
|
* to check a download.
|
|
*
|
|
* @param c CURL handle
|
|
*/
|
|
#define SETUP_WB(c) do { \
|
|
if (CURLE_OK != \
|
|
curl_easy_setopt (c, \
|
|
CURLOPT_WRITEFUNCTION, \
|
|
&write_cb)) \
|
|
{ \
|
|
curl_easy_cleanup (c); \
|
|
return "Failed to set write callback for curl request"; \
|
|
} \
|
|
if (CURLE_OK != \
|
|
curl_easy_setopt (c, \
|
|
CURLOPT_WRITEDATA, \
|
|
&wb)) \
|
|
{ \
|
|
curl_easy_cleanup (c); \
|
|
return "Failed to set write buffer for curl request"; \
|
|
} \
|
|
} while (0)
|
|
|
|
/**
|
|
* Check that we received the expected text.
|
|
*
|
|
* @param text text we expect to have downloaded
|
|
*/
|
|
#define CHECK_WB(text) do { \
|
|
if ( (wb_tlen != wb.pos) || \
|
|
(0 != wb.err) || \
|
|
(0 != memcmp (text, \
|
|
wb_buf, \
|
|
wb_tlen)) ) \
|
|
return "Downloaded data does not match expectations"; \
|
|
} while (0)
|
|
|
|
|
|
/**
|
|
* Perform the curl request @a c and cleanup and
|
|
* return an error if the request failed.
|
|
*
|
|
* @param c request to perform
|
|
*/
|
|
#define PERFORM_REQUEST(c) do { \
|
|
CURLcode res; \
|
|
res = curl_easy_perform (c); \
|
|
if (CURLE_OK != res) \
|
|
{ \
|
|
curl_easy_cleanup (c); \
|
|
return "Failed to fetch URL"; \
|
|
} \
|
|
} while (0)
|
|
|
|
/**
|
|
* Check that the curl request @a c completed
|
|
* with the @a want status code.
|
|
* Return an error if the status does not match.
|
|
*
|
|
* @param c request to check
|
|
* @param want desired HTTP status code
|
|
*/
|
|
#define CHECK_STATUS(c,want) do { \
|
|
if (! check_status (c, want)) \
|
|
{ \
|
|
curl_easy_cleanup (c); \
|
|
return "Unexpected HTTP status"; \
|
|
} \
|
|
} while (0)
|
|
|
|
/**
|
|
* Chec that the HTTP status of @a c matches @a expected_status
|
|
*
|
|
* @param a completed CURL request
|
|
* @param expected_status the expected HTTP response code
|
|
* @return true if the status matches
|
|
*/
|
|
static bool
|
|
check_status (CURL *c,
|
|
unsigned int expected_status)
|
|
{
|
|
long status;
|
|
|
|
if (CURLE_OK !=
|
|
curl_easy_getinfo (c,
|
|
CURLINFO_RESPONSE_CODE,
|
|
&status))
|
|
{
|
|
fprintf (stderr,
|
|
"Failed to get HTTP status");
|
|
return false;
|
|
}
|
|
if (((unsigned int) status) != expected_status)
|
|
{
|
|
fprintf (stderr,
|
|
"Expected HTTP status %u, got %ld\n",
|
|
expected_status,
|
|
status);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the @a base_url for the @a c handle.
|
|
*
|
|
* @param[in,out] c curl handle to manipulate
|
|
* @param base_url base URL to set
|
|
* @return true on success, false on failure (@a c will be cleaned up in this case)
|
|
*/
|
|
static bool
|
|
set_url (CURL *c,
|
|
const char *base_url)
|
|
{
|
|
if (CURLE_OK !=
|
|
curl_easy_setopt (c,
|
|
CURLOPT_URL,
|
|
base_url))
|
|
{
|
|
curl_easy_cleanup (c);
|
|
return false;
|
|
}
|
|
if (CURLE_OK !=
|
|
curl_easy_setopt (c,
|
|
CURLOPT_VERBOSE,
|
|
1))
|
|
{
|
|
curl_easy_cleanup (c);
|
|
return false;
|
|
}
|
|
if (0 == strncasecmp (base_url,
|
|
"https://",
|
|
strlen ("https://")))
|
|
{
|
|
/* disable certificate checking */
|
|
if ( (CURLE_OK !=
|
|
curl_easy_setopt (c,
|
|
CURLOPT_SSL_VERIFYPEER,
|
|
0L)) ||
|
|
(CURLE_OK !=
|
|
curl_easy_setopt (c,
|
|
CURLOPT_SSL_VERIFYHOST,
|
|
0L)) )
|
|
{
|
|
curl_easy_cleanup (c);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
const char *
|
|
MHDT_client_get_root (
|
|
const void *cls,
|
|
const struct MHDT_PhaseContext *pc)
|
|
{
|
|
const char *text = cls;
|
|
CURL *c;
|
|
DECLARE_WB (text);
|
|
|
|
c = curl_easy_init ();
|
|
if (NULL == c)
|
|
return "Failed to initialize Curl handle";
|
|
if (! set_url (c,
|
|
pc->base_url))
|
|
return "Failed to set URL for curl request";
|
|
SETUP_WB (c);
|
|
PERFORM_REQUEST (c);
|
|
CHECK_STATUS (c, MHD_HTTP_STATUS_OK);
|
|
curl_easy_cleanup (c);
|
|
CHECK_WB (text);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
const char *
|
|
MHDT_client_get_with_query (
|
|
const void *cls,
|
|
const struct MHDT_PhaseContext *pc)
|
|
{
|
|
const char *args = cls;
|
|
size_t alen = strlen (args);
|
|
CURL *c;
|
|
size_t blen = strlen (pc->base_url);
|
|
char u[alen + blen + 1];
|
|
|
|
memcpy (u,
|
|
pc->base_url,
|
|
blen);
|
|
memcpy (u + blen,
|
|
args,
|
|
alen);
|
|
u[alen + blen] = '\0';
|
|
c = curl_easy_init ();
|
|
if (NULL == c)
|
|
return "Failed to initialize Curl handle";
|
|
if (! set_url (c,
|
|
u))
|
|
return "Failed to set URL for curl request";
|
|
PERFORM_REQUEST (c);
|
|
CHECK_STATUS (c,
|
|
MHD_HTTP_STATUS_NO_CONTENT);
|
|
curl_easy_cleanup (c);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
const char *
|
|
MHDT_client_set_header (
|
|
const void *cls,
|
|
const struct MHDT_PhaseContext *pc)
|
|
{
|
|
const char *hdr = cls;
|
|
CURL *c;
|
|
CURLcode res;
|
|
struct curl_slist *slist;
|
|
|
|
c = curl_easy_init ();
|
|
if (NULL == c)
|
|
return "Failed to initialize Curl handle";
|
|
if (! set_url (c,
|
|
pc->base_url))
|
|
return "Failed to set URL for curl request";
|
|
slist = curl_slist_append (NULL,
|
|
hdr);
|
|
if (CURLE_OK !=
|
|
curl_easy_setopt (c,
|
|
CURLOPT_HTTPHEADER,
|
|
slist))
|
|
{
|
|
curl_easy_cleanup (c);
|
|
curl_slist_free_all (slist);
|
|
return "Failed to set custom header for curl request";
|
|
}
|
|
res = curl_easy_perform (c);
|
|
curl_slist_free_all (slist);
|
|
if (CURLE_OK != res)
|
|
{
|
|
curl_easy_cleanup (c);
|
|
return "Failed to fetch URL";
|
|
}
|
|
CHECK_STATUS (c,
|
|
MHD_HTTP_STATUS_NO_CONTENT);
|
|
curl_easy_cleanup (c);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
const char *
|
|
MHDT_client_expect_header (const void *cls,
|
|
const struct MHDT_PhaseContext *pc)
|
|
{
|
|
#ifdef HAVE_LIBCRUL_NEW_HDR_API
|
|
const char *hdr = cls;
|
|
size_t hlen = strlen (hdr) + 1;
|
|
char key[hlen];
|
|
const char *colon = strchr (hdr, ':');
|
|
const char *value;
|
|
CURL *c;
|
|
bool found = false;
|
|
|
|
if (NULL == colon)
|
|
return "Invalid expected header passed";
|
|
memcpy (key,
|
|
hdr,
|
|
hlen);
|
|
key[colon - hdr] = '\0';
|
|
value = &key[colon - hdr + 1];
|
|
c = curl_easy_init ();
|
|
if (NULL == c)
|
|
return "Failed to initialize Curl handle";
|
|
if (! set_url (c,
|
|
pc->base_url))
|
|
return "Failed to set URL for curl request";
|
|
PERFORM_REQUEST (c);
|
|
CHECK_STATUS (c,
|
|
MHD_HTTP_STATUS_NO_CONTENT);
|
|
for (size_t index = 0; ! found; index++)
|
|
{
|
|
CURLHcode rval;
|
|
struct curl_header *hout;
|
|
|
|
rval = curl_easy_header (c,
|
|
key,
|
|
index,
|
|
CURLH_HEADER,
|
|
-1 /* last request */,
|
|
&hout);
|
|
if (CURLHE_BADINDEX == rval)
|
|
break;
|
|
found = (0 == strcmp (value,
|
|
hout->value));
|
|
}
|
|
if (! found)
|
|
{
|
|
curl_easy_cleanup (c);
|
|
return "Expected HTTP response header not found";
|
|
}
|
|
curl_easy_cleanup (c);
|
|
return NULL;
|
|
#else /* ! HAVE_LIBCRUL_NEW_HDR_API */
|
|
(void) cls; (void) pc;
|
|
return NULL;
|
|
#endif /* ! HAVE_LIBCRUL_NEW_HDR_API */
|
|
}
|
|
|
|
|
|
/**
|
|
* Closure for the read_cb().
|
|
*/
|
|
struct ReadBuffer
|
|
{
|
|
/**
|
|
* Origin of data to upload.
|
|
*/
|
|
const char *buf;
|
|
|
|
/**
|
|
* Number of bytes in @e buf.
|
|
*/
|
|
size_t len;
|
|
|
|
/**
|
|
* Current read offset in @e buf.
|
|
*/
|
|
size_t pos;
|
|
|
|
/**
|
|
* Number of chunks to user when sending.
|
|
*/
|
|
unsigned int chunks;
|
|
|
|
};
|
|
|
|
|
|
/**
|
|
* Callback for CURLOPT_READFUNCTION for uploading
|
|
* data to the HTTP server.
|
|
*
|
|
* @param ptr data uploaded
|
|
* @param size size of a member
|
|
* @param nmemb number of members
|
|
* @param stream must be a `struct ReadBuffer`
|
|
* @return bytes processed (size*nmemb) or error
|
|
*/
|
|
static size_t
|
|
read_cb (void *ptr,
|
|
size_t size,
|
|
size_t nmemb,
|
|
void *stream)
|
|
{
|
|
struct ReadBuffer *rb = stream;
|
|
size_t limit = size * nmemb;
|
|
|
|
if (limit / size != nmemb)
|
|
return CURLE_WRITE_ERROR;
|
|
if (limit > rb->len - rb->pos)
|
|
limit = rb->len - rb->pos;
|
|
if ( (rb->chunks > 1) &&
|
|
(limit > 1) )
|
|
{
|
|
limit /= rb->chunks;
|
|
rb->chunks--;
|
|
}
|
|
memcpy (ptr,
|
|
rb->buf + rb->pos,
|
|
limit);
|
|
rb->pos += limit;
|
|
return limit;
|
|
}
|
|
|
|
|
|
const char *
|
|
MHDT_client_put_data (
|
|
const void *cls,
|
|
const struct MHDT_PhaseContext *pc)
|
|
{
|
|
const char *text = cls;
|
|
struct ReadBuffer rb = {
|
|
.buf = text,
|
|
.len = strlen (text)
|
|
};
|
|
CURL *c;
|
|
|
|
c = curl_easy_init ();
|
|
if (NULL == c)
|
|
return "Failed to initialize Curl handle";
|
|
if (! set_url (c,
|
|
pc->base_url))
|
|
return "Failed to set URL for curl request";
|
|
if (CURLE_OK !=
|
|
curl_easy_setopt (c,
|
|
CURLOPT_UPLOAD,
|
|
1L))
|
|
{
|
|
curl_easy_cleanup (c);
|
|
return "Failed to set PUT method for curl request";
|
|
}
|
|
if (CURLE_OK !=
|
|
curl_easy_setopt (c,
|
|
CURLOPT_READFUNCTION,
|
|
&read_cb))
|
|
{
|
|
curl_easy_cleanup (c);
|
|
return "Failed to set READFUNCTION for curl request";
|
|
}
|
|
if (CURLE_OK !=
|
|
curl_easy_setopt (c,
|
|
CURLOPT_READDATA,
|
|
&rb))
|
|
{
|
|
curl_easy_cleanup (c);
|
|
return "Failed to set READFUNCTION for curl request";
|
|
}
|
|
if (CURLE_OK !=
|
|
curl_easy_setopt (c,
|
|
CURLOPT_INFILESIZE_LARGE,
|
|
(curl_off_t) rb.len))
|
|
{
|
|
curl_easy_cleanup (c);
|
|
return "Failed to set INFILESIZE_LARGE for curl request";
|
|
}
|
|
PERFORM_REQUEST (c);
|
|
CHECK_STATUS (c,
|
|
MHD_HTTP_STATUS_NO_CONTENT);
|
|
curl_easy_cleanup (c);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
const char *
|
|
MHDT_client_chunk_data (
|
|
const void *cls,
|
|
const struct MHDT_PhaseContext *pc)
|
|
{
|
|
const char *text = cls;
|
|
struct ReadBuffer rb = {
|
|
.buf = text,
|
|
.len = strlen (text),
|
|
.chunks = 2
|
|
};
|
|
CURL *c;
|
|
|
|
c = curl_easy_init ();
|
|
if (NULL == c)
|
|
return "Failed to initialize Curl handle";
|
|
if (! set_url (c,
|
|
pc->base_url))
|
|
return "Failed to set URL for curl request";
|
|
if (CURLE_OK !=
|
|
curl_easy_setopt (c,
|
|
CURLOPT_UPLOAD,
|
|
1L))
|
|
{
|
|
curl_easy_cleanup (c);
|
|
return "Failed to set PUT method for curl request";
|
|
}
|
|
if (CURLE_OK !=
|
|
curl_easy_setopt (c,
|
|
CURLOPT_READFUNCTION,
|
|
&read_cb))
|
|
{
|
|
curl_easy_cleanup (c);
|
|
return "Failed to set READFUNCTION for curl request";
|
|
}
|
|
if (CURLE_OK !=
|
|
curl_easy_setopt (c,
|
|
CURLOPT_READDATA,
|
|
&rb))
|
|
{
|
|
curl_easy_cleanup (c);
|
|
return "Failed to set READFUNCTION for curl request";
|
|
}
|
|
PERFORM_REQUEST (c);
|
|
CHECK_STATUS (c,
|
|
MHD_HTTP_STATUS_NO_CONTENT);
|
|
curl_easy_cleanup (c);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
const char *
|
|
MHDT_client_do_post (
|
|
const void *cls,
|
|
const struct MHDT_PhaseContext *pc)
|
|
{
|
|
const struct MHDT_PostInstructions *pi = cls;
|
|
CURL *c;
|
|
struct curl_slist *request_hdr = NULL;
|
|
|
|
/* reset wants in case we re-use the array */
|
|
if (NULL != pi->wants)
|
|
{
|
|
for (unsigned int i = 0; NULL != pi->wants[i].key; i++)
|
|
{
|
|
pi->wants[i].value_off = 0;
|
|
pi->wants[i].satisfied = false;
|
|
}
|
|
}
|
|
c = curl_easy_init ();
|
|
if (NULL == c)
|
|
return "Failed to initialize Curl handle";
|
|
if (! set_url (c,
|
|
pc->base_url))
|
|
return "Failed to set URL for curl request";
|
|
if (CURLE_OK !=
|
|
curl_easy_setopt (c,
|
|
CURLOPT_POST,
|
|
1L))
|
|
{
|
|
curl_easy_cleanup (c);
|
|
return "Failed to set POST method for curl request";
|
|
}
|
|
if (CURLE_OK !=
|
|
curl_easy_setopt (c,
|
|
CURLOPT_POSTFIELDS,
|
|
pi->postdata))
|
|
{
|
|
curl_easy_cleanup (c);
|
|
return "Failed to set POSTFIELDS for curl request";
|
|
}
|
|
if (0 != pi->postdata_size)
|
|
{
|
|
if (CURLE_OK !=
|
|
curl_easy_setopt (c,
|
|
CURLOPT_POSTFIELDSIZE_LARGE,
|
|
(curl_off_t) pi->postdata_size))
|
|
{
|
|
curl_easy_cleanup (c);
|
|
return "Failed to set POSTFIELDS for curl request";
|
|
}
|
|
}
|
|
if (NULL != pi->postheader)
|
|
{
|
|
request_hdr = curl_slist_append (request_hdr,
|
|
pi->postheader);
|
|
}
|
|
if (CURLE_OK !=
|
|
curl_easy_setopt (c,
|
|
CURLOPT_HTTPHEADER,
|
|
request_hdr))
|
|
{
|
|
curl_easy_cleanup (c);
|
|
curl_slist_free_all (request_hdr);
|
|
return "Failed to set HTTPHEADER for curl request";
|
|
}
|
|
PERFORM_REQUEST (c);
|
|
CHECK_STATUS (c,
|
|
MHD_HTTP_STATUS_NO_CONTENT);
|
|
curl_easy_cleanup (c);
|
|
curl_slist_free_all (request_hdr);
|
|
if (NULL != pi->wants)
|
|
{
|
|
for (unsigned int i = 0; NULL != pi->wants[i].key; i++)
|
|
{
|
|
if (! pi->wants[i].satisfied)
|
|
{
|
|
fprintf (stderr,
|
|
"Server did not correctly detect key '%s'\n",
|
|
pi->wants[i].key);
|
|
return "key-value data not matched by server";
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|