blob: b4857b057735adde1978257481c47419daec2738 [file] [log] [blame] [raw]
/*
* HTTP file download
*
* Copyright (c) 2016-2017 Fabrice Bellard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <assert.h>
#include <stdarg.h>
#include <sys/time.h>
#include <ctype.h>
#include "cutils.h"
#include "list.h"
#include "fs.h"
#include "fs_utils.h"
#include "fs_wget.h"
#if defined(EMSCRIPTEN)
#include <emscripten.h>
#else
#include <curl/multi.h>
#endif
/***********************************************/
/* HTTP get */
#ifdef EMSCRIPTEN
struct XHRState {
void *opaque;
WGetWriteCallback *cb;
};
static int downloading_count;
void fs_wget_init(void)
{
}
extern void fs_wget_update_downloading(int flag);
static void fs_wget_update_downloading_count(int incr)
{
int prev_state, state;
prev_state = (downloading_count > 0);
downloading_count += incr;
state = (downloading_count > 0);
if (prev_state != state)
fs_wget_update_downloading(state);
}
static void fs_wget_onerror(unsigned int handle, void *opaque, int status,
const char *status_text)
{
XHRState *s = opaque;
if (status <= 0)
status = -404; /* HTTP not found error */
else
status = -status;
fs_wget_update_downloading_count(-1);
if (s->cb)
s->cb(s->opaque, status, NULL, 0);
}
static void fs_wget_onload(unsigned int handle,
void *opaque, void *data, unsigned int size)
{
XHRState *s = opaque;
fs_wget_update_downloading_count(-1);
if (s->cb)
s->cb(s->opaque, 0, data, size);
}
extern int emscripten_async_wget3_data(const char* url, const char* requesttype, const char *user, const char *password, const uint8_t *post_data, int post_data_len, void *arg, int free, em_async_wget2_data_onload_func onload, em_async_wget2_data_onerror_func onerror, em_async_wget2_data_onprogress_func onprogress);
XHRState *fs_wget2(const char *url, const char *user, const char *password,
WGetReadCallback *read_cb, uint64_t post_data_len,
void *opaque, WGetWriteCallback *cb, BOOL single_write)
{
XHRState *s;
const char *request;
uint8_t *post_data;
s = mallocz(sizeof(*s));
s->opaque = opaque;
s->cb = cb;
if (post_data_len != 0) {
request = "POST";
post_data = malloc(post_data_len);
read_cb(opaque, post_data, post_data_len);
} else {
request = "GET";
post_data = NULL;
}
fs_wget_update_downloading_count(1);
emscripten_async_wget3_data(url, request, user, password,
post_data, post_data_len, s, 1, fs_wget_onload,
fs_wget_onerror, NULL);
if (post_data_len != 0)
free(post_data);
return s;
}
void fs_wget_free(XHRState *s)
{
s->cb = NULL;
s->opaque = NULL;
}
#else
struct XHRState {
struct list_head link;
CURL *eh;
void *opaque;
WGetWriteCallback *write_cb;
WGetReadCallback *read_cb;
BOOL single_write;
DynBuf dbuf; /* used if single_write */
};
typedef struct {
struct list_head link;
int64_t timeout;
void (*cb)(void *opaque);
void *opaque;
} AsyncCallState;
static CURLM *curl_multi_ctx;
static struct list_head xhr_list; /* list of XHRState.link */
void fs_wget_init(void)
{
if (curl_multi_ctx)
return;
curl_global_init(CURL_GLOBAL_ALL);
curl_multi_ctx = curl_multi_init();
init_list_head(&xhr_list);
}
void fs_wget_end(void)
{
curl_multi_cleanup(curl_multi_ctx);
curl_global_cleanup();
}
static size_t fs_wget_write_cb(char *ptr, size_t size, size_t nmemb,
void *userdata)
{
XHRState *s = userdata;
size *= nmemb;
if (s->single_write) {
dbuf_write(&s->dbuf, s->dbuf.size, (void *)ptr, size);
} else {
s->write_cb(s->opaque, 1, ptr, size);
}
return size;
}
static size_t fs_wget_read_cb(char *ptr, size_t size, size_t nmemb,
void *userdata)
{
XHRState *s = userdata;
size *= nmemb;
return s->read_cb(s->opaque, ptr, size);
}
XHRState *fs_wget2(const char *url, const char *user, const char *password,
WGetReadCallback *read_cb, uint64_t post_data_len,
void *opaque, WGetWriteCallback *write_cb, BOOL single_write)
{
XHRState *s;
s = mallocz(sizeof(*s));
s->eh = curl_easy_init();
s->opaque = opaque;
s->write_cb = write_cb;
s->read_cb = read_cb;
s->single_write = single_write;
dbuf_init(&s->dbuf);
curl_easy_setopt(s->eh, CURLOPT_PRIVATE, s);
curl_easy_setopt(s->eh, CURLOPT_WRITEDATA, s);
curl_easy_setopt(s->eh, CURLOPT_WRITEFUNCTION, fs_wget_write_cb);
curl_easy_setopt(s->eh, CURLOPT_HEADER, 0);
curl_easy_setopt(s->eh, CURLOPT_URL, url);
curl_easy_setopt(s->eh, CURLOPT_VERBOSE, 0L);
curl_easy_setopt(s->eh, CURLOPT_ACCEPT_ENCODING, "");
if (user) {
curl_easy_setopt(s->eh, CURLOPT_USERNAME, user);
curl_easy_setopt(s->eh, CURLOPT_PASSWORD, password);
}
if (post_data_len != 0) {
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers,
"Content-Type: application/octet-stream");
curl_easy_setopt(s->eh, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(s->eh, CURLOPT_POST, 1L);
curl_easy_setopt(s->eh, CURLOPT_POSTFIELDSIZE_LARGE,
(curl_off_t)post_data_len);
curl_easy_setopt(s->eh, CURLOPT_READDATA, s);
curl_easy_setopt(s->eh, CURLOPT_READFUNCTION, fs_wget_read_cb);
}
curl_multi_add_handle(curl_multi_ctx, s->eh);
list_add_tail(&s->link, &xhr_list);
return s;
}
void fs_wget_free(XHRState *s)
{
dbuf_free(&s->dbuf);
curl_easy_cleanup(s->eh);
list_del(&s->link);
free(s);
}
/* timeout is in ms */
void fs_net_set_fdset(int *pfd_max, fd_set *rfds, fd_set *wfds, fd_set *efds,
int *ptimeout)
{
long timeout;
int n, fd_max;
CURLMsg *msg;
if (!curl_multi_ctx)
return;
curl_multi_perform(curl_multi_ctx, &n);
for(;;) {
msg = curl_multi_info_read(curl_multi_ctx, &n);
if (!msg)
break;
if (msg->msg == CURLMSG_DONE) {
XHRState *s;
long http_code;
curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, (char **)&s);
curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE,
&http_code);
/* signal the end of the transfer or error */
if (http_code == 200) {
if (s->single_write) {
s->write_cb(s->opaque, 0, s->dbuf.buf, s->dbuf.size);
} else {
s->write_cb(s->opaque, 0, NULL, 0);
}
} else {
s->write_cb(s->opaque, -http_code, NULL, 0);
}
curl_multi_remove_handle(curl_multi_ctx, s->eh);
curl_easy_cleanup(s->eh);
dbuf_free(&s->dbuf);
list_del(&s->link);
free(s);
}
}
curl_multi_fdset(curl_multi_ctx, rfds, wfds, efds, &fd_max);
*pfd_max = max_int(*pfd_max, fd_max);
curl_multi_timeout(curl_multi_ctx, &timeout);
if (timeout >= 0)
*ptimeout = min_int(*ptimeout, timeout);
}
void fs_net_event_loop(FSNetEventLoopCompletionFunc *cb, void *opaque)
{
fd_set rfds, wfds, efds;
int timeout, fd_max;
struct timeval tv;
if (!curl_multi_ctx)
return;
for(;;) {
fd_max = -1;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&efds);
timeout = 10000;
fs_net_set_fdset(&fd_max, &rfds, &wfds, &efds, &timeout);
if (cb) {
if (cb(opaque))
break;
} else {
if (list_empty(&xhr_list))
break;
}
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
select(fd_max + 1, &rfds, &wfds, &efds, &tv);
}
}
#endif /* !EMSCRIPTEN */
XHRState *fs_wget(const char *url, const char *user, const char *password,
void *opaque, WGetWriteCallback *cb, BOOL single_write)
{
return fs_wget2(url, user, password, NULL, 0, opaque, cb, single_write);
}
/***********************************************/
/* file decryption */
#define ENCRYPTED_FILE_HEADER_SIZE (4 + AES_BLOCK_SIZE)
#define DEC_BUF_SIZE (256 * AES_BLOCK_SIZE)
struct DecryptFileState {
DecryptFileCB *write_cb;
void *opaque;
int dec_state;
int dec_buf_pos;
AES_KEY *aes_state;
uint8_t iv[AES_BLOCK_SIZE];
uint8_t dec_buf[DEC_BUF_SIZE];
};
DecryptFileState *decrypt_file_init(AES_KEY *aes_state,
DecryptFileCB *write_cb,
void *opaque)
{
DecryptFileState *s;
s = mallocz(sizeof(*s));
s->write_cb = write_cb;
s->opaque = opaque;
s->aes_state = aes_state;
return s;
}
int decrypt_file(DecryptFileState *s, const uint8_t *data,
size_t size)
{
int l, len, ret;
while (size != 0) {
switch(s->dec_state) {
case 0:
l = min_int(size, ENCRYPTED_FILE_HEADER_SIZE - s->dec_buf_pos);
memcpy(s->dec_buf + s->dec_buf_pos, data, l);
s->dec_buf_pos += l;
if (s->dec_buf_pos >= ENCRYPTED_FILE_HEADER_SIZE) {
if (memcmp(s->dec_buf, encrypted_file_magic, 4) != 0)
return -1;
memcpy(s->iv, s->dec_buf + 4, AES_BLOCK_SIZE);
s->dec_state = 1;
s->dec_buf_pos = 0;
}
break;
case 1:
l = min_int(size, DEC_BUF_SIZE - s->dec_buf_pos);
memcpy(s->dec_buf + s->dec_buf_pos, data, l);
s->dec_buf_pos += l;
if (s->dec_buf_pos >= DEC_BUF_SIZE) {
/* keep one block in case it is the padding */
len = s->dec_buf_pos - AES_BLOCK_SIZE;
AES_cbc_encrypt(s->dec_buf, s->dec_buf, len,
s->aes_state, s->iv, FALSE);
ret = s->write_cb(s->opaque, s->dec_buf, len);
if (ret < 0)
return ret;
memcpy(s->dec_buf, s->dec_buf + s->dec_buf_pos - AES_BLOCK_SIZE,
AES_BLOCK_SIZE);
s->dec_buf_pos = AES_BLOCK_SIZE;
}
break;
default:
abort();
}
data += l;
size -= l;
}
return 0;
}
/* write last blocks */
int decrypt_file_flush(DecryptFileState *s)
{
int len, pad_len, ret;
if (s->dec_state != 1)
return -1;
len = s->dec_buf_pos;
if (len == 0 ||
(len % AES_BLOCK_SIZE) != 0)
return -1;
AES_cbc_encrypt(s->dec_buf, s->dec_buf, len,
s->aes_state, s->iv, FALSE);
pad_len = s->dec_buf[s->dec_buf_pos - 1];
if (pad_len < 1 || pad_len > AES_BLOCK_SIZE)
return -1;
len -= pad_len;
if (len != 0) {
ret = s->write_cb(s->opaque, s->dec_buf, len);
if (ret < 0)
return ret;
}
return 0;
}
void decrypt_file_end(DecryptFileState *s)
{
free(s);
}
/* XHR file */
typedef struct {
FSDevice *fs;
FSFile *f;
int64_t pos;
FSWGetFileCB *cb;
void *opaque;
FSFile *posted_file;
int64_t read_pos;
DecryptFileState *dec_state;
} FSWGetFileState;
static int fs_wget_file_write_cb(void *opaque, const uint8_t *data,
size_t size)
{
FSWGetFileState *s = opaque;
FSDevice *fs = s->fs;
int ret;
ret = fs->fs_write(fs, s->f, s->pos, data, size);
if (ret < 0)
return ret;
s->pos += ret;
return ret;
}
static void fs_wget_file_on_load(void *opaque, int err, void *data, size_t size)
{
FSWGetFileState *s = opaque;
FSDevice *fs = s->fs;
int ret;
int64_t ret_size;
// printf("err=%d size=%ld\n", err, size);
if (err < 0) {
ret_size = err;
goto done;
} else {
if (s->dec_state) {
ret = decrypt_file(s->dec_state, data, size);
if (ret >= 0 && err == 0) {
/* handle the end of file */
decrypt_file_flush(s->dec_state);
}
} else {
ret = fs_wget_file_write_cb(s, data, size);
}
if (ret < 0) {
ret_size = ret;
goto done;
} else if (err == 0) {
/* end of transfer */
ret_size = s->pos;
done:
s->cb(fs, s->f, ret_size, s->opaque);
if (s->dec_state)
decrypt_file_end(s->dec_state);
free(s);
}
}
}
static size_t fs_wget_file_read_cb(void *opaque, void *data, size_t size)
{
FSWGetFileState *s = opaque;
FSDevice *fs = s->fs;
int ret;
if (!s->posted_file)
return 0;
ret = fs->fs_read(fs, s->posted_file, s->read_pos, data, size);
if (ret < 0)
return 0;
s->read_pos += ret;
return ret;
}
void fs_wget_file2(FSDevice *fs, FSFile *f, const char *url,
const char *user, const char *password,
FSFile *posted_file, uint64_t post_data_len,
FSWGetFileCB *cb, void *opaque,
AES_KEY *aes_state)
{
FSWGetFileState *s;
s = mallocz(sizeof(*s));
s->fs = fs;
s->f = f;
s->pos = 0;
s->cb = cb;
s->opaque = opaque;
s->posted_file = posted_file;
s->read_pos = 0;
if (aes_state) {
s->dec_state = decrypt_file_init(aes_state, fs_wget_file_write_cb, s);
}
fs_wget2(url, user, password, fs_wget_file_read_cb, post_data_len,
s, fs_wget_file_on_load, FALSE);
}
/***********************************************/
/* PBKDF2 */
#ifdef USE_BUILTIN_CRYPTO
#define HMAC_BLOCK_SIZE 64
typedef struct {
SHA256_CTX ctx;
uint8_t K[HMAC_BLOCK_SIZE + SHA256_DIGEST_LENGTH];
} HMAC_SHA256_CTX;
void hmac_sha256_init(HMAC_SHA256_CTX *s, const uint8_t *key, int key_len)
{
int i, l;
if (key_len > HMAC_BLOCK_SIZE) {
SHA256(key, key_len, s->K);
l = SHA256_DIGEST_LENGTH;
} else {
memcpy(s->K, key, key_len);
l = key_len;
}
memset(s->K + l, 0, HMAC_BLOCK_SIZE - l);
for(i = 0; i < HMAC_BLOCK_SIZE; i++)
s->K[i] ^= 0x36;
SHA256_Init(&s->ctx);
SHA256_Update(&s->ctx, s->K, HMAC_BLOCK_SIZE);
}
void hmac_sha256_update(HMAC_SHA256_CTX *s, const uint8_t *buf, int len)
{
SHA256_Update(&s->ctx, buf, len);
}
/* out has a length of SHA256_DIGEST_LENGTH */
void hmac_sha256_final(HMAC_SHA256_CTX *s, uint8_t *out)
{
int i;
SHA256_Final(s->K + HMAC_BLOCK_SIZE, &s->ctx);
for(i = 0; i < HMAC_BLOCK_SIZE; i++)
s->K[i] ^= (0x36 ^ 0x5c);
SHA256(s->K, HMAC_BLOCK_SIZE + SHA256_DIGEST_LENGTH, out);
}
#define SALT_LEN_MAX 32
void pbkdf2_hmac_sha256(const uint8_t *pwd, int pwd_len,
const uint8_t *salt, int salt_len,
int iter, int key_len, uint8_t *out)
{
uint8_t F[SHA256_DIGEST_LENGTH], U[SALT_LEN_MAX + 4];
HMAC_SHA256_CTX ctx;
int it, U_len, j, l;
uint32_t i;
assert(salt_len <= SALT_LEN_MAX);
i = 1;
while (key_len > 0) {
memset(F, 0, SHA256_DIGEST_LENGTH);
memcpy(U, salt, salt_len);
U[salt_len] = i >> 24;
U[salt_len + 1] = i >> 16;
U[salt_len + 2] = i >> 8;
U[salt_len + 3] = i;
U_len = salt_len + 4;
for(it = 0; it < iter; it++) {
hmac_sha256_init(&ctx, pwd, pwd_len);
hmac_sha256_update(&ctx, U, U_len);
hmac_sha256_final(&ctx, U);
for(j = 0; j < SHA256_DIGEST_LENGTH; j++)
F[j] ^= U[j];
U_len = SHA256_DIGEST_LENGTH;
}
l = min_int(key_len, SHA256_DIGEST_LENGTH);
memcpy(out, F, l);
out += l;
key_len -= l;
i++;
}
}
#else
void pbkdf2_hmac_sha256(const uint8_t *pwd, int pwd_len,
const uint8_t *salt, int salt_len,
int iter, int key_len, uint8_t *out)
{
PKCS5_PBKDF2_HMAC((const char *)pwd, pwd_len, salt, salt_len,
iter, EVP_sha256(), key_len, out);
}
#endif /* !USE_BUILTIN_CRYPTO */