blob: c72d640dbbf561ae146599c9f96af371026c812e [file] [log] [blame]
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <unistd.h>
#include <curl/curl.h>
#include "upload.h"
#include "kvextract.h"
#include "utils.h"
#define DEVICE_KEY_PATH "/tmp/ssl/private/device.key"
#define DEVICE_CERT_PATH "/tmp/ssl/certs/device.pem"
#define FORM_DATA_SPLITTER_PREFIX "foo-splitter-"
#define CURL_CLEANUP_AND_RETURN \
if (curl_handle) curl_easy_cleanup(curl_handle); \
curl_global_cleanup(); \
return -1
// Helper macro for setting a curl option and on error logging an error and
// returning -1. Thanks @drheld
#define SET_CURL_OPT(curl, option, param) \
do { \
CURLcode res = curl_easy_setopt(curl, option, param); \
if (res != CURLE_OK) { \
fprintf(stderr, "failed to set curl option %s: %s (%d)\n", \
#option, curl_easy_strerror(res), res); \
if (curl_headers) curl_slist_free_all(curl_headers); \
} \
} while (0)
// If we ever change this to C++ pull in the arraysize macro instead.
#define ARRAYSIZE(x) (sizeof(x)/sizeof(x[0]))
struct getdatamem {
char memory[4096]; // it'll be storing a URL, so this is big enough
size_t used_size;
};
struct postdatamem {
char content_type[128];
char *blob;
char data_prefix[128];
char data_postfix[64];
long prefix_offset;
long prefix_length;
long postfix_offset;
long postfix_length;
long blob_offset;
long blob_length;
};
static size_t my_write_callback(void *contents, size_t size, size_t nmemb,
void* userp) {
size_t realsize = size * nmemb;
struct getdatamem* memp = (struct getdatamem*) userp;
if (realsize + memp->used_size > sizeof(memp->memory)) {
fprintf(stderr, "curl sent back more data than fits in our buffer "
" outsize=%zd usedsize=%zd\n", sizeof(memp->memory), realsize +
memp->used_size);
return 0;
}
memcpy(&(memp->memory[memp->used_size]), contents, realsize);
memp->used_size += realsize;
memp->memory[memp->used_size] = '\0';
return realsize;
}
static size_t my_read_callback(char* buffer, size_t size, size_t nitems,
void *instream) {
size_t realsize = size * nitems;
size_t xfer_size;
struct postdatamem *postdata = (struct postdatamem*) instream;
if (postdata->prefix_offset < postdata->prefix_length) {
xfer_size = postdata->prefix_length - postdata->prefix_offset;
if (xfer_size > realsize)
xfer_size = realsize;
memcpy(buffer, &(postdata->data_prefix[postdata->prefix_offset]),
xfer_size);
postdata->prefix_offset += xfer_size;
realsize -= xfer_size;
buffer = buffer + xfer_size;
}
if (realsize > 0 && postdata->blob_offset < postdata->blob_length) {
xfer_size = postdata->blob_length - postdata->blob_offset;
if (xfer_size > realsize)
xfer_size = realsize;
memcpy(buffer, &(postdata->blob[postdata->blob_offset]), xfer_size);
postdata->blob_offset += xfer_size;
realsize -= xfer_size;
buffer = buffer + xfer_size;
}
if (realsize > 0 && postdata->postfix_offset < postdata->postfix_length) {
xfer_size = postdata->postfix_length - postdata->postfix_offset;
if (xfer_size > realsize)
xfer_size = realsize;
memcpy(buffer, &(postdata->data_postfix[postdata->postfix_offset]),
xfer_size);
postdata->postfix_offset += xfer_size;
realsize -= xfer_size;
}
return (size * nitems) - realsize;
}
static int do_request_via_ipv(CURL *curl_handle, const char *server_url,
struct getdatamem *getdata, struct postdatamem *postdata,
int ipv, long *http_code) {
struct curl_slist* curl_headers = NULL;
struct curl_slist* new_headers = NULL;
curl_easy_reset(curl_handle);
SET_CURL_OPT(curl_handle, CURLOPT_URL, server_url);
SET_CURL_OPT(curl_handle, CURLOPT_USERAGENT, "upload-logs");
SET_CURL_OPT(curl_handle, CURLOPT_IPRESOLVE, ipv);
SET_CURL_OPT(curl_handle, CURLOPT_FOLLOWLOCATION, 0L);
SET_CURL_OPT(curl_handle, CURLOPT_SSL_VERIFYPEER, 1L);
SET_CURL_OPT(curl_handle, CURLOPT_SSL_VERIFYHOST, 2L);
SET_CURL_OPT(curl_handle, CURLOPT_TIMEOUT, 60L);
if (path_exists(DEVICE_KEY_PATH))
SET_CURL_OPT(curl_handle, CURLOPT_SSLKEY, DEVICE_KEY_PATH);
if (path_exists(DEVICE_CERT_PATH))
SET_CURL_OPT(curl_handle, CURLOPT_SSLCERT, DEVICE_CERT_PATH);
// Log upload server doesn't like Expect: 100-continue
// so remove that.
new_headers = curl_slist_append(curl_headers, "Expect:");
if (!new_headers) {
fprintf(stderr, "failed setting up curl headers\n");
return -1;
} else
curl_headers = new_headers;
if (getdata) {
SET_CURL_OPT(curl_handle, CURLOPT_HTTPGET, 1L);
SET_CURL_OPT(curl_handle, CURLOPT_WRITEFUNCTION, my_write_callback);
SET_CURL_OPT(curl_handle, CURLOPT_WRITEDATA, (void *)getdata);
} else {
SET_CURL_OPT(curl_handle, CURLOPT_POST, 1L);
SET_CURL_OPT(curl_handle, CURLOPT_READFUNCTION, my_read_callback);
SET_CURL_OPT(curl_handle, CURLOPT_READDATA, (void *)postdata);
SET_CURL_OPT(curl_handle, CURLOPT_POSTFIELDSIZE,
postdata->prefix_length + postdata->blob_length +
postdata->postfix_length);
new_headers = curl_slist_append(curl_headers, postdata->content_type);
if (!new_headers) {
fprintf(stderr, "failed setting up curl headers\n");
curl_slist_free_all(curl_headers);
return -1;
} else
curl_headers = new_headers;
// If this is a retry we need to reset these values.
postdata->prefix_offset = 0;
postdata->blob_offset = 0;
postdata->postfix_offset = 0;
}
SET_CURL_OPT(curl_handle, CURLOPT_HTTPHEADER, curl_headers);
CURLcode curl_res = curl_easy_perform(curl_handle);
curl_slist_free_all(curl_headers);
curl_headers = NULL;
*http_code = 500;
curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, http_code);
if (curl_res != CURLE_OK) {
*http_code = 599;
fprintf(stderr, "request failed: %s (%d)\n", curl_easy_strerror(curl_res),
curl_res);
return -1;
} else {
if (*http_code == 200) {
if (getdata) {
// Success for the GET case.
return 0;
} else {
// This should not happen for POST, so it's failure.
fprintf(stderr, "received a 200 response for a POST request\n");
return -1;
}
} else if (*http_code == 302 && postdata) {
// 302 is success for the post case.
return 0;
} else {
// Failure due to some other HTTP error.
return -1;
}
}
}
int upload_file(const char *server_url, const char* target_name, char* data,
ssize_t len, struct kvpair* kvpairs_save) {
char url[2048];
CURL *curl_handle = NULL;
int timeval;
struct getdatamem getdata;
int i, rv = -1;
int resolvers[2] = { CURL_IPRESOLVE_V6, CURL_IPRESOLVE_V4 };
const char *resolvers_str[2] = { "IPv6", "IPv4" };
memset(url, 0, sizeof(url));
memset(&getdata, 0, sizeof(getdata));
struct postdatamem postdata;
memset(&postdata, 0, sizeof(postdata));
// Strip any leading slashes from the target name
while (target_name[0] == '/') {
target_name++;
}
// Setup curl
if (curl_global_init(CURL_GLOBAL_ALL)) {
fprintf(stderr, "failed with curl_global_init\n");
return -1;
}
for (i=0; i < (int)ARRAYSIZE(resolvers); ++i) {
long http_code;
struct kvpair* kvpairs = kvpairs_save;
curl_handle = curl_easy_init();
if (!curl_handle) {
curl_global_cleanup();
fprintf(stderr, "failed initializing curl\n");
return -1;
}
// Construct the full URL
snprintf(url, sizeof(url), "%s/upload/%s%s", server_url, target_name,
(kvpairs != NULL) ? "?" : "");
while (kvpairs != NULL) {
// Now add the URL parameters
size_t lim;
char *url_end;
char* encoded_key = curl_easy_escape(curl_handle, kvpairs->key, 0);
if (!encoded_key) {
fprintf(stderr, "failure in curl_easy_escape\n");
CURL_CLEANUP_AND_RETURN;
}
char *encoded_value = curl_easy_escape(curl_handle, kvpairs->value, 0);
if (!encoded_value) {
fprintf(stderr, "failure in curl_easy_escape\n");
CURL_CLEANUP_AND_RETURN;
}
url_end = url + strlen(url);
lim = sizeof(url) - strlen(url);
snprintf(url_end, lim, "%s=%s%s", encoded_key, encoded_value,
(kvpairs->next_pair != NULL) ? "&" : "");
curl_free(encoded_key);
curl_free(encoded_value);
kvpairs = kvpairs->next_pair;
}
http_code = 0;
rv = do_request_via_ipv(curl_handle, url, &getdata, NULL, resolvers[i],
&http_code);
if (rv) {
fprintf(stderr, "Curl failed in GET %s\n", resolvers_str[i]);
curl_easy_cleanup(curl_handle);
curl_handle = NULL;
continue;
}
timeval = (int) time(NULL);
snprintf(postdata.content_type, sizeof(postdata.content_type),
"Content-Type: multipart/form-data; boundary=%s-%d",
FORM_DATA_SPLITTER_PREFIX, timeval);
snprintf(postdata.data_prefix, sizeof(postdata.data_prefix),
"--%s-%d\r\n"
"Content-Disposition: form-data; name=\"file\"; "
"filename=\"%s\"\r\n\r\n",
FORM_DATA_SPLITTER_PREFIX, timeval, target_name);
postdata.prefix_length = strlen(postdata.data_prefix);
snprintf(postdata.data_postfix, sizeof(postdata.data_postfix),
"\r\n--%s-%d--\r\n\r\n", FORM_DATA_SPLITTER_PREFIX, timeval);
postdata.postfix_length = strlen(postdata.data_postfix);
postdata.blob = data;
postdata.blob_length = len;
http_code = 0;
rv = do_request_via_ipv(curl_handle, getdata.memory, NULL, &postdata,
resolvers[i], &http_code);
if (rv) {
curl_easy_cleanup(curl_handle);
fprintf(stderr, "curl failed with %s http error: %ld\n",
resolvers_str[i], http_code);
curl_handle = NULL;
continue;
}
// If we get here, that means the GET and POST both succeeded.
break;
}
if (curl_handle)
curl_easy_cleanup(curl_handle);
curl_global_cleanup();
return rv;
}