| /* |
| * 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 */ |