281 lines
7.1 KiB
C
281 lines
7.1 KiB
C
/*
|
|
Copyright (c) 2017, Jan-Philipp Litza <janphilipp@litza.de>
|
|
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.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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.
|
|
*/
|
|
|
|
|
|
#include "uclient.h"
|
|
|
|
#include <libubox/blobmsg.h>
|
|
#include <libubox/uloop.h>
|
|
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
|
|
|
|
#define TIMEOUT_MSEC 300000
|
|
|
|
static const char *const user_agent = "OLSRDHelper.so (using libuclient)";
|
|
|
|
|
|
const char *uclient_get_errmsg(int code) {
|
|
static char http_code_errmsg[16];
|
|
if (code & UCLIENT_ERROR_STATUS_CODE) {
|
|
snprintf(http_code_errmsg, 16, "HTTP error %d",
|
|
code & (~UCLIENT_ERROR_STATUS_CODE));
|
|
return http_code_errmsg;
|
|
}
|
|
switch(code) {
|
|
case UCLIENT_ERROR_CONNECT:
|
|
return "Connection failed";
|
|
case UCLIENT_ERROR_TIMEDOUT:
|
|
return "Connection timed out";
|
|
case UCLIENT_ERROR_REDIRECT_FAILED:
|
|
return "Failed to redirect";
|
|
case UCLIENT_ERROR_TOO_MANY_REDIRECTS:
|
|
return "Too many redirects";
|
|
case UCLIENT_ERROR_CONNECTION_RESET_PREMATURELY:
|
|
return "Connection reset prematurely";
|
|
case UCLIENT_ERROR_SIZE_MISMATCH:
|
|
return "Incorrect file size";
|
|
case UCLIENT_ERROR_NOT_JSON:
|
|
return "Response not json";
|
|
default:
|
|
return "Unknown error";
|
|
}
|
|
}
|
|
|
|
|
|
static void request_done(struct uclient *cl, int err_code) {
|
|
uclient_data(cl)->err_code = err_code;
|
|
uclient_disconnect(cl);
|
|
uloop_end();
|
|
}
|
|
|
|
|
|
static void header_done_cb(struct uclient *cl) {
|
|
const struct blobmsg_policy policy = {
|
|
.name = "content-length",
|
|
.type = BLOBMSG_TYPE_STRING,
|
|
};
|
|
struct blob_attr *tb_len;
|
|
|
|
if (uclient_data(cl)->retries < 10) {
|
|
int ret = uclient_http_redirect(cl);
|
|
if (ret < 0) {
|
|
request_done(cl, UCLIENT_ERROR_REDIRECT_FAILED);
|
|
return;
|
|
}
|
|
if (ret > 0) {
|
|
uclient_data(cl)->retries++;
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (cl->status_code) {
|
|
case 200:
|
|
break;
|
|
case 301:
|
|
case 302:
|
|
case 307:
|
|
request_done(cl, UCLIENT_ERROR_TOO_MANY_REDIRECTS);
|
|
return;
|
|
default:
|
|
request_done(cl, UCLIENT_ERROR_STATUS_CODE | cl->status_code);
|
|
return;
|
|
}
|
|
|
|
blobmsg_parse(&policy, 1, &tb_len, blob_data(cl->meta), blob_len(cl->meta));
|
|
if (tb_len) {
|
|
char *endptr;
|
|
|
|
errno = 0;
|
|
unsigned long long val = strtoull(blobmsg_get_string(tb_len), &endptr, 10);
|
|
if (!errno && !*endptr && val <= SSIZE_MAX) {
|
|
if (uclient_data(cl)->length >= 0 && uclient_data(cl)->length != (ssize_t)val) {
|
|
request_done(cl, UCLIENT_ERROR_SIZE_MISMATCH);
|
|
return;
|
|
}
|
|
|
|
uclient_data(cl)->length = val;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void eof_cb(struct uclient *cl) {
|
|
request_done(cl, cl->data_eof ? 0 : UCLIENT_ERROR_CONNECTION_RESET_PREMATURELY);
|
|
if (cl->data_eof) {
|
|
uclient_data(cl)->eof(cl);
|
|
}
|
|
}
|
|
|
|
|
|
ssize_t uclient_read_account(struct uclient *cl, char *buf, int len) {
|
|
struct uclient_data *d = uclient_data(cl);
|
|
int r = uclient_read(cl, buf, len);
|
|
|
|
if (r >= 0) {
|
|
d->downloaded += r;
|
|
|
|
if (d->length >= 0 && d->downloaded > d->length) {
|
|
request_done(cl, UCLIENT_ERROR_SIZE_MISMATCH);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
// src https://github.com/curl/curl/blob/2610142139d14265ed9acf9ed83cdf73d6bb4d05/lib/escape.c
|
|
|
|
/* Portable character check (remember EBCDIC). Do not use isalnum() because
|
|
its behavior is altered by the current locale.
|
|
See https://datatracker.ietf.org/doc/html/rfc3986#section-2.3
|
|
*/
|
|
bool Curl_isunreserved(unsigned char in)
|
|
{
|
|
switch(in) {
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
case 'a': case 'b': case 'c': case 'd': case 'e':
|
|
case 'f': case 'g': case 'h': case 'i': case 'j':
|
|
case 'k': case 'l': case 'm': case 'n': case 'o':
|
|
case 'p': case 'q': case 'r': case 's': case 't':
|
|
case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
|
|
case 'A': case 'B': case 'C': case 'D': case 'E':
|
|
case 'F': case 'G': case 'H': case 'I': case 'J':
|
|
case 'K': case 'L': case 'M': case 'N': case 'O':
|
|
case 'P': case 'Q': case 'R': case 'S': case 'T':
|
|
case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
|
|
case '-': case '.': case '_': case '~':
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
char *curl_easy_escape(const char *string, int inlength)
|
|
{
|
|
size_t length;
|
|
|
|
if (inlength < 0)
|
|
return NULL;
|
|
|
|
length = (inlength ? (size_t)inlength : strlen(string));
|
|
if (!length)
|
|
return strdup("");
|
|
|
|
char * out = malloc((length * 3) + 1);
|
|
|
|
if (!out)
|
|
return NULL;
|
|
|
|
size_t offset = 0;
|
|
|
|
// this isn't pretending like we're complying to any spec other than urlencode, thx
|
|
int slashes = 0;
|
|
|
|
while (length--) {
|
|
unsigned char in = *string; /* we need to treat the characters unsigned */
|
|
|
|
if (slashes == 3) {
|
|
if (Curl_isunreserved(in)) {
|
|
/* append this */
|
|
out[offset] = in;
|
|
offset++;
|
|
} else {
|
|
/* encode it */
|
|
if (snprintf(out + offset, 4, "%%%02X", in) != 3) {
|
|
free(out);
|
|
return NULL;
|
|
}
|
|
|
|
offset += 3;
|
|
}
|
|
} else {
|
|
out[offset] = in;
|
|
offset++;
|
|
|
|
if (in == '/') {
|
|
slashes++;
|
|
}
|
|
}
|
|
|
|
string++;
|
|
}
|
|
|
|
out[offset] = '\0';
|
|
|
|
return out;
|
|
}
|
|
|
|
int get_url(const char *user_url, void (*read_cb)(struct uclient *cl), void (*eof2_cb)(struct uclient *cl), void *cb_data, ssize_t len) {
|
|
char *url = curl_easy_escape(user_url, 0);
|
|
if (!url)
|
|
return UCLIENT_ERROR_CONNECT;
|
|
|
|
struct uclient_data d = { .custom = cb_data, .length = len, .eof = eof2_cb };
|
|
struct uclient_cb cb = {
|
|
.header_done = header_done_cb,
|
|
.data_read = read_cb,
|
|
.data_eof = eof_cb,
|
|
.error = request_done,
|
|
};
|
|
|
|
struct uclient *cl = uclient_new(url, NULL, &cb);
|
|
if (!cl)
|
|
goto err;
|
|
|
|
cl->priv = &d;
|
|
if (uclient_set_timeout(cl, TIMEOUT_MSEC))
|
|
goto err;
|
|
if (uclient_connect(cl))
|
|
goto err;
|
|
if (uclient_http_set_request_type(cl, "GET"))
|
|
goto err;
|
|
if (uclient_http_reset_headers(cl))
|
|
goto err;
|
|
if (uclient_http_set_header(cl, "User-Agent", user_agent))
|
|
goto err;
|
|
if (uclient_request(cl))
|
|
goto err;
|
|
uloop_run();
|
|
uclient_free(cl);
|
|
free(url);
|
|
|
|
if (!d.err_code && d.length >= 0 && d.downloaded != d.length)
|
|
return UCLIENT_ERROR_SIZE_MISMATCH;
|
|
|
|
return d.err_code;
|
|
|
|
err:
|
|
if (cl)
|
|
uclient_free(cl);
|
|
|
|
free(url);
|
|
|
|
return UCLIENT_ERROR_CONNECT;
|
|
}
|