Path: blob/main/system/lib/fetch/emscripten_fetch.c
4154 views
// Copyright 2016 The Emscripten Authors. All rights reserved.1// Emscripten is available under two separate licenses, the MIT license and the2// University of Illinois/NCSA Open Source License. Both these licenses can be3// found in the LICENSE file.45#include <bits/errno.h>6#include <math.h>7#include <memory.h>8#include <stdlib.h>9#include <string.h>10#include <threads.h>11#include <stdbool.h>1213#include <emscripten/emscripten.h>14#include <emscripten/fetch.h>15#include <emscripten/html5.h>16#include <emscripten/threading.h>17#include <emscripten/console.h>1819#include "emscripten_internal.h"2021// From https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState22#define STATE_UNSENT 0 // Client has been created. open() not called yet.23#define STATE_OPENED 1 // open() has been called.24#define STATE_HEADERS_RECEIVED 2 // send() has been called, and headers and status are available.25#define STATE_LOADING 3 // Downloading; responseText holds partial data.26#define STATE_DONE 4 // The operation is complete.2728#define STATE_MAX STATE_DONE2930// Uncomment the following and clear the cache with emcc --clear-cache to rebuild this file to31// enable internal debugging. #define FETCH_DEBUG3233static void fetch_free(emscripten_fetch_t* fetch);3435typedef struct emscripten_fetch_queue {36emscripten_fetch_t** queuedOperations;37int numQueuedItems;38int queueSize;39} emscripten_fetch_queue;4041emscripten_fetch_queue* _emscripten_get_fetch_queue() {42static thread_local emscripten_fetch_queue g_queue;4344if (!g_queue.queuedOperations) {45g_queue.queueSize = 64;46g_queue.numQueuedItems = 0;47g_queue.queuedOperations = malloc(sizeof(emscripten_fetch_t*) * g_queue.queueSize);48}49return &g_queue;50}5152void emscripten_proxy_fetch(emscripten_fetch_t* fetch) {53// TODO: mutex lock54emscripten_fetch_queue* queue = _emscripten_get_fetch_queue();55// TODO handle case when queue->numQueuedItems >= queue->queueSize56queue->queuedOperations[queue->numQueuedItems++] = fetch;57#ifdef FETCH_DEBUG58emscripten_dbgf("Queued fetch to fetch-worker to process. There are "59"now %d operations in the queue.", queue->numQueuedItems);60#endif61// TODO: mutex unlock62}6364void emscripten_fetch_attr_init(emscripten_fetch_attr_t* fetch_attr) {65memset(fetch_attr, 0, sizeof(emscripten_fetch_attr_t));66}6768emscripten_fetch_t* emscripten_fetch(emscripten_fetch_attr_t* fetch_attr, const char* url) {69if (!fetch_attr || !url) {70return NULL;71}7273const bool synchronous = (fetch_attr->attributes & EMSCRIPTEN_FETCH_SYNCHRONOUS) != 0;74const bool readFromIndexedDB =75(fetch_attr->attributes & (EMSCRIPTEN_FETCH_APPEND | EMSCRIPTEN_FETCH_NO_DOWNLOAD)) != 0 ||76((fetch_attr->attributes & EMSCRIPTEN_FETCH_REPLACE) == 0);77const bool writeToIndexedDB = (fetch_attr->attributes & EMSCRIPTEN_FETCH_PERSIST_FILE) != 0 ||78!strncmp(fetch_attr->requestMethod, "EM_IDB_", strlen("EM_IDB_"));79const bool performXhr = (fetch_attr->attributes & EMSCRIPTEN_FETCH_NO_DOWNLOAD) == 0;80if (emscripten_is_main_browser_thread() && synchronous && (performXhr || readFromIndexedDB || writeToIndexedDB)) {81#ifdef FETCH_DEBUG82emscripten_errf("emscripten_fetch('%s') failed! Synchronous blocking XHRs and IndexedDB operations are not supported on the main browser thread. Try dropping the EMSCRIPTEN_FETCH_SYNCHRONOUS flag, or run with the linker flag --proxy-to-worker to decouple main C runtime thread from the main browser thread.", url);83#endif84return NULL;85}8687emscripten_fetch_t* fetch = (emscripten_fetch_t*)calloc(1, sizeof(emscripten_fetch_t));88if (!fetch) {89return NULL;90}91fetch->userData = fetch_attr->userData;92fetch->__attributes.timeoutMSecs = fetch_attr->timeoutMSecs;93fetch->__attributes.attributes = fetch_attr->attributes;94fetch->__attributes.withCredentials = fetch_attr->withCredentials;95fetch->__attributes.requestData = fetch_attr->requestData;96fetch->__attributes.requestDataSize = fetch_attr->requestDataSize;97strcpy(fetch->__attributes.requestMethod, fetch_attr->requestMethod);98fetch->__attributes.onerror = fetch_attr->onerror;99fetch->__attributes.onsuccess = fetch_attr->onsuccess;100fetch->__attributes.onprogress = fetch_attr->onprogress;101fetch->__attributes.onreadystatechange = fetch_attr->onreadystatechange;102#define STRDUP_OR_ABORT(s, str_to_dup) \103if (str_to_dup) { \104s = strdup(str_to_dup); \105if (!s) { \106fetch_free(fetch); \107return 0; \108} \109}110STRDUP_OR_ABORT(fetch->url, url);111STRDUP_OR_ABORT(fetch->__attributes.destinationPath, fetch_attr->destinationPath);112STRDUP_OR_ABORT(fetch->__attributes.userName, fetch_attr->userName);113STRDUP_OR_ABORT(fetch->__attributes.password, fetch_attr->password);114STRDUP_OR_ABORT(fetch->__attributes.overriddenMimeType, fetch_attr->overriddenMimeType);115#undef STRDUP_OR_ABORT116117if (fetch_attr->requestHeaders) {118size_t headersCount = 0;119while (fetch_attr->requestHeaders[headersCount]) {120++headersCount;121}122const char** headers = (const char**)malloc((headersCount + 1) * sizeof(const char*));123if (!headers) {124fetch_free(fetch);125return NULL;126}127memset((void*)headers, 0, (headersCount + 1) * sizeof(const char*));128129for (size_t i = 0; i < headersCount; ++i) {130headers[i] = strdup(fetch_attr->requestHeaders[i]);131if (!headers[i]) {132for (size_t j = 0; j < i; ++j) {133free((void*)headers[j]);134}135free((void*)headers);136fetch_free(fetch);137return NULL;138}139}140headers[headersCount] = 0;141fetch->__attributes.requestHeaders = headers;142fetch->responseUrl = NULL; // responseUrl is not set until STATE_HEADERS_RECEIVED143}144145emscripten_start_fetch(fetch);146return fetch;147}148149EMSCRIPTEN_RESULT emscripten_fetch_wait(emscripten_fetch_t* fetch, double timeoutMsecs) {150return EMSCRIPTEN_RESULT_FAILED;151}152153EMSCRIPTEN_RESULT emscripten_fetch_close(emscripten_fetch_t* fetch) {154if (!fetch) {155return EMSCRIPTEN_RESULT_SUCCESS; // Closing null pointer is ok, same as with free().156}157158// This function frees the fetch pointer so that it is invalid to access it anymore.159// Use a few key fields as an integrity check that we are being passed a good pointer to a valid160// fetch structure, which has not been yet closed. (double close is an error)161if (fetch->id == 0 || fetch->readyState > STATE_MAX) {162return EMSCRIPTEN_RESULT_INVALID_PARAM;163}164165// This fetch is aborted. Call the error handler if the fetch was still in progress and was166// canceled in flight.167if (fetch->readyState != STATE_DONE && fetch->__attributes.onerror) {168fetch->status = (unsigned short)-1;169strcpy(fetch->statusText, "aborted with emscripten_fetch_close()");170fetch->__attributes.onerror(fetch);171}172173fetch_free(fetch);174return EMSCRIPTEN_RESULT_SUCCESS;175}176177size_t emscripten_fetch_get_response_headers_length(emscripten_fetch_t *fetch) {178if (!fetch || fetch->readyState < STATE_HEADERS_RECEIVED) {179return 0;180}181182return (size_t)_emscripten_fetch_get_response_headers_length(fetch->id);183}184185size_t emscripten_fetch_get_response_headers(emscripten_fetch_t *fetch, char *dst, size_t dstSizeBytes) {186if (!fetch || fetch->readyState < STATE_HEADERS_RECEIVED) {187return 0;188}189190return (size_t)_emscripten_fetch_get_response_headers(fetch->id, dst, dstSizeBytes);191}192193char **emscripten_fetch_unpack_response_headers(const char *headersString) {194// Get size of output array and allocate.195size_t numHeaders = 0;196for (const char *pos = strchr(headersString, '\n'); pos; pos = strchr(pos + 1, '\n')) {197numHeaders++;198}199char **unpackedHeaders = (char**)malloc(sizeof(char*) * ((numHeaders * 2) + 1));200if (!unpackedHeaders) {201return NULL;202}203unpackedHeaders[numHeaders * 2] = NULL;204205// Allocate each header.206const char *rowStart = headersString;207const char *rowEnd = strchr(rowStart, '\n');208for (size_t headerNum = 0; rowEnd; headerNum += 2) {209const char *split = strchr(rowStart, ':');210size_t headerSize = (size_t)split - (size_t)rowStart;211char* header = (char*)malloc(headerSize + 1);212unpackedHeaders[headerNum] = header;213if (!header) {214emscripten_fetch_free_unpacked_response_headers(unpackedHeaders);215return NULL;216}217strncpy(header, rowStart, headerSize);218header[headerSize] = '\0';219220size_t valueSize = (size_t)rowEnd - (size_t)split;221char* value = (char*)malloc(valueSize + 1);222unpackedHeaders[headerNum+1] = value;223if (!value) {224emscripten_fetch_free_unpacked_response_headers(unpackedHeaders);225return NULL;226}227strncpy(value, split + 1, valueSize);228value[valueSize] = '\0';229230rowStart = rowEnd + 1;231rowEnd = strchr(rowStart, '\n');232}233234return unpackedHeaders;235}236237void emscripten_fetch_free_unpacked_response_headers(char **unpackedHeaders) {238if (unpackedHeaders) {239for (size_t i = 0; unpackedHeaders[i]; ++i) {240free((void*)unpackedHeaders[i]);241}242free((void*)unpackedHeaders);243}244}245246static void fetch_free(emscripten_fetch_t* fetch) {247emscripten_fetch_free(fetch->id);248fetch->id = 0;249free((void*)fetch->data);250free((void*)fetch->url);251free((void*)fetch->__attributes.destinationPath);252free((void*)fetch->__attributes.userName);253free((void*)fetch->__attributes.password);254if (fetch->__attributes.requestHeaders) {255for (size_t i = 0; fetch->__attributes.requestHeaders[i]; ++i) {256free((void*)fetch->__attributes.requestHeaders[i]);257}258free((void*)fetch->__attributes.requestHeaders);259}260free((void*)fetch->__attributes.overriddenMimeType);261free((void*)fetch->responseUrl);262free(fetch);263}264265266