Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/system/lib/fetch/emscripten_fetch.c
4154 views
1
// Copyright 2016 The Emscripten Authors. All rights reserved.
2
// Emscripten is available under two separate licenses, the MIT license and the
3
// University of Illinois/NCSA Open Source License. Both these licenses can be
4
// found in the LICENSE file.
5
6
#include <bits/errno.h>
7
#include <math.h>
8
#include <memory.h>
9
#include <stdlib.h>
10
#include <string.h>
11
#include <threads.h>
12
#include <stdbool.h>
13
14
#include <emscripten/emscripten.h>
15
#include <emscripten/fetch.h>
16
#include <emscripten/html5.h>
17
#include <emscripten/threading.h>
18
#include <emscripten/console.h>
19
20
#include "emscripten_internal.h"
21
22
// From https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState
23
#define STATE_UNSENT 0 // Client has been created. open() not called yet.
24
#define STATE_OPENED 1 // open() has been called.
25
#define STATE_HEADERS_RECEIVED 2 // send() has been called, and headers and status are available.
26
#define STATE_LOADING 3 // Downloading; responseText holds partial data.
27
#define STATE_DONE 4 // The operation is complete.
28
29
#define STATE_MAX STATE_DONE
30
31
// Uncomment the following and clear the cache with emcc --clear-cache to rebuild this file to
32
// enable internal debugging. #define FETCH_DEBUG
33
34
static void fetch_free(emscripten_fetch_t* fetch);
35
36
typedef struct emscripten_fetch_queue {
37
emscripten_fetch_t** queuedOperations;
38
int numQueuedItems;
39
int queueSize;
40
} emscripten_fetch_queue;
41
42
emscripten_fetch_queue* _emscripten_get_fetch_queue() {
43
static thread_local emscripten_fetch_queue g_queue;
44
45
if (!g_queue.queuedOperations) {
46
g_queue.queueSize = 64;
47
g_queue.numQueuedItems = 0;
48
g_queue.queuedOperations = malloc(sizeof(emscripten_fetch_t*) * g_queue.queueSize);
49
}
50
return &g_queue;
51
}
52
53
void emscripten_proxy_fetch(emscripten_fetch_t* fetch) {
54
// TODO: mutex lock
55
emscripten_fetch_queue* queue = _emscripten_get_fetch_queue();
56
// TODO handle case when queue->numQueuedItems >= queue->queueSize
57
queue->queuedOperations[queue->numQueuedItems++] = fetch;
58
#ifdef FETCH_DEBUG
59
emscripten_dbgf("Queued fetch to fetch-worker to process. There are "
60
"now %d operations in the queue.", queue->numQueuedItems);
61
#endif
62
// TODO: mutex unlock
63
}
64
65
void emscripten_fetch_attr_init(emscripten_fetch_attr_t* fetch_attr) {
66
memset(fetch_attr, 0, sizeof(emscripten_fetch_attr_t));
67
}
68
69
emscripten_fetch_t* emscripten_fetch(emscripten_fetch_attr_t* fetch_attr, const char* url) {
70
if (!fetch_attr || !url) {
71
return NULL;
72
}
73
74
const bool synchronous = (fetch_attr->attributes & EMSCRIPTEN_FETCH_SYNCHRONOUS) != 0;
75
const bool readFromIndexedDB =
76
(fetch_attr->attributes & (EMSCRIPTEN_FETCH_APPEND | EMSCRIPTEN_FETCH_NO_DOWNLOAD)) != 0 ||
77
((fetch_attr->attributes & EMSCRIPTEN_FETCH_REPLACE) == 0);
78
const bool writeToIndexedDB = (fetch_attr->attributes & EMSCRIPTEN_FETCH_PERSIST_FILE) != 0 ||
79
!strncmp(fetch_attr->requestMethod, "EM_IDB_", strlen("EM_IDB_"));
80
const bool performXhr = (fetch_attr->attributes & EMSCRIPTEN_FETCH_NO_DOWNLOAD) == 0;
81
if (emscripten_is_main_browser_thread() && synchronous && (performXhr || readFromIndexedDB || writeToIndexedDB)) {
82
#ifdef FETCH_DEBUG
83
emscripten_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);
84
#endif
85
return NULL;
86
}
87
88
emscripten_fetch_t* fetch = (emscripten_fetch_t*)calloc(1, sizeof(emscripten_fetch_t));
89
if (!fetch) {
90
return NULL;
91
}
92
fetch->userData = fetch_attr->userData;
93
fetch->__attributes.timeoutMSecs = fetch_attr->timeoutMSecs;
94
fetch->__attributes.attributes = fetch_attr->attributes;
95
fetch->__attributes.withCredentials = fetch_attr->withCredentials;
96
fetch->__attributes.requestData = fetch_attr->requestData;
97
fetch->__attributes.requestDataSize = fetch_attr->requestDataSize;
98
strcpy(fetch->__attributes.requestMethod, fetch_attr->requestMethod);
99
fetch->__attributes.onerror = fetch_attr->onerror;
100
fetch->__attributes.onsuccess = fetch_attr->onsuccess;
101
fetch->__attributes.onprogress = fetch_attr->onprogress;
102
fetch->__attributes.onreadystatechange = fetch_attr->onreadystatechange;
103
#define STRDUP_OR_ABORT(s, str_to_dup) \
104
if (str_to_dup) { \
105
s = strdup(str_to_dup); \
106
if (!s) { \
107
fetch_free(fetch); \
108
return 0; \
109
} \
110
}
111
STRDUP_OR_ABORT(fetch->url, url);
112
STRDUP_OR_ABORT(fetch->__attributes.destinationPath, fetch_attr->destinationPath);
113
STRDUP_OR_ABORT(fetch->__attributes.userName, fetch_attr->userName);
114
STRDUP_OR_ABORT(fetch->__attributes.password, fetch_attr->password);
115
STRDUP_OR_ABORT(fetch->__attributes.overriddenMimeType, fetch_attr->overriddenMimeType);
116
#undef STRDUP_OR_ABORT
117
118
if (fetch_attr->requestHeaders) {
119
size_t headersCount = 0;
120
while (fetch_attr->requestHeaders[headersCount]) {
121
++headersCount;
122
}
123
const char** headers = (const char**)malloc((headersCount + 1) * sizeof(const char*));
124
if (!headers) {
125
fetch_free(fetch);
126
return NULL;
127
}
128
memset((void*)headers, 0, (headersCount + 1) * sizeof(const char*));
129
130
for (size_t i = 0; i < headersCount; ++i) {
131
headers[i] = strdup(fetch_attr->requestHeaders[i]);
132
if (!headers[i]) {
133
for (size_t j = 0; j < i; ++j) {
134
free((void*)headers[j]);
135
}
136
free((void*)headers);
137
fetch_free(fetch);
138
return NULL;
139
}
140
}
141
headers[headersCount] = 0;
142
fetch->__attributes.requestHeaders = headers;
143
fetch->responseUrl = NULL; // responseUrl is not set until STATE_HEADERS_RECEIVED
144
}
145
146
emscripten_start_fetch(fetch);
147
return fetch;
148
}
149
150
EMSCRIPTEN_RESULT emscripten_fetch_wait(emscripten_fetch_t* fetch, double timeoutMsecs) {
151
return EMSCRIPTEN_RESULT_FAILED;
152
}
153
154
EMSCRIPTEN_RESULT emscripten_fetch_close(emscripten_fetch_t* fetch) {
155
if (!fetch) {
156
return EMSCRIPTEN_RESULT_SUCCESS; // Closing null pointer is ok, same as with free().
157
}
158
159
// This function frees the fetch pointer so that it is invalid to access it anymore.
160
// Use a few key fields as an integrity check that we are being passed a good pointer to a valid
161
// fetch structure, which has not been yet closed. (double close is an error)
162
if (fetch->id == 0 || fetch->readyState > STATE_MAX) {
163
return EMSCRIPTEN_RESULT_INVALID_PARAM;
164
}
165
166
// This fetch is aborted. Call the error handler if the fetch was still in progress and was
167
// canceled in flight.
168
if (fetch->readyState != STATE_DONE && fetch->__attributes.onerror) {
169
fetch->status = (unsigned short)-1;
170
strcpy(fetch->statusText, "aborted with emscripten_fetch_close()");
171
fetch->__attributes.onerror(fetch);
172
}
173
174
fetch_free(fetch);
175
return EMSCRIPTEN_RESULT_SUCCESS;
176
}
177
178
size_t emscripten_fetch_get_response_headers_length(emscripten_fetch_t *fetch) {
179
if (!fetch || fetch->readyState < STATE_HEADERS_RECEIVED) {
180
return 0;
181
}
182
183
return (size_t)_emscripten_fetch_get_response_headers_length(fetch->id);
184
}
185
186
size_t emscripten_fetch_get_response_headers(emscripten_fetch_t *fetch, char *dst, size_t dstSizeBytes) {
187
if (!fetch || fetch->readyState < STATE_HEADERS_RECEIVED) {
188
return 0;
189
}
190
191
return (size_t)_emscripten_fetch_get_response_headers(fetch->id, dst, dstSizeBytes);
192
}
193
194
char **emscripten_fetch_unpack_response_headers(const char *headersString) {
195
// Get size of output array and allocate.
196
size_t numHeaders = 0;
197
for (const char *pos = strchr(headersString, '\n'); pos; pos = strchr(pos + 1, '\n')) {
198
numHeaders++;
199
}
200
char **unpackedHeaders = (char**)malloc(sizeof(char*) * ((numHeaders * 2) + 1));
201
if (!unpackedHeaders) {
202
return NULL;
203
}
204
unpackedHeaders[numHeaders * 2] = NULL;
205
206
// Allocate each header.
207
const char *rowStart = headersString;
208
const char *rowEnd = strchr(rowStart, '\n');
209
for (size_t headerNum = 0; rowEnd; headerNum += 2) {
210
const char *split = strchr(rowStart, ':');
211
size_t headerSize = (size_t)split - (size_t)rowStart;
212
char* header = (char*)malloc(headerSize + 1);
213
unpackedHeaders[headerNum] = header;
214
if (!header) {
215
emscripten_fetch_free_unpacked_response_headers(unpackedHeaders);
216
return NULL;
217
}
218
strncpy(header, rowStart, headerSize);
219
header[headerSize] = '\0';
220
221
size_t valueSize = (size_t)rowEnd - (size_t)split;
222
char* value = (char*)malloc(valueSize + 1);
223
unpackedHeaders[headerNum+1] = value;
224
if (!value) {
225
emscripten_fetch_free_unpacked_response_headers(unpackedHeaders);
226
return NULL;
227
}
228
strncpy(value, split + 1, valueSize);
229
value[valueSize] = '\0';
230
231
rowStart = rowEnd + 1;
232
rowEnd = strchr(rowStart, '\n');
233
}
234
235
return unpackedHeaders;
236
}
237
238
void emscripten_fetch_free_unpacked_response_headers(char **unpackedHeaders) {
239
if (unpackedHeaders) {
240
for (size_t i = 0; unpackedHeaders[i]; ++i) {
241
free((void*)unpackedHeaders[i]);
242
}
243
free((void*)unpackedHeaders);
244
}
245
}
246
247
static void fetch_free(emscripten_fetch_t* fetch) {
248
emscripten_fetch_free(fetch->id);
249
fetch->id = 0;
250
free((void*)fetch->data);
251
free((void*)fetch->url);
252
free((void*)fetch->__attributes.destinationPath);
253
free((void*)fetch->__attributes.userName);
254
free((void*)fetch->__attributes.password);
255
if (fetch->__attributes.requestHeaders) {
256
for (size_t i = 0; fetch->__attributes.requestHeaders[i]; ++i) {
257
free((void*)fetch->__attributes.requestHeaders[i]);
258
}
259
free((void*)fetch->__attributes.requestHeaders);
260
}
261
free((void*)fetch->__attributes.overriddenMimeType);
262
free((void*)fetch->responseUrl);
263
free(fetch);
264
}
265
266