Line data Source code
1 : /*
2 : * GPAC - Multimedia Framework C SDK
3 : *
4 : * Authors: Jean Le Feuvre
5 : * Copyright (c) Telecom ParisTech 2007-2021
6 : * All rights reserved
7 : *
8 : * This file is part of GPAC / JavaScript XmlHttpRequest bindings
9 : *
10 : * GPAC is free software; you can redistribute it and/or modify
11 : * it under the terms of the GNU Lesser General Public License as published by
12 : * the Free Software Foundation; either version 2, or (at your option)
13 : * any later version.
14 : *
15 : * GPAC is distributed in the hope that it will be useful,
16 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 : * GNU Lesser General Public License for more details.
19 : *
20 : * You should have received a copy of the GNU Lesser General Public
21 : * License along with this library; see the file COPYING. If not, write to
22 : * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 : *
24 : */
25 :
26 : /*
27 : ANY CHANGE TO THE API MUST BE REFLECTED IN THE DOCUMENTATION IN gpac/share/doc/idl/xhr.idl
28 : (no way to define inline JS doc with doxygen)
29 : */
30 :
31 : #include <gpac/setup.h>
32 :
33 : #ifdef GPAC_HAS_QJS
34 :
35 : /*base SVG type*/
36 : #include <gpac/nodes_svg.h>
37 : #include <gpac/nodes_mpeg4.h>
38 : #include <gpac/nodes_x3d.h>
39 : /*dom events*/
40 : #include <gpac/events.h>
41 :
42 : #include <gpac/download.h>
43 : #include <gpac/network.h>
44 : #include <gpac/options.h>
45 : #include <gpac/xml.h>
46 :
47 :
48 : #include <gpac/internal/scenegraph_dev.h>
49 :
50 : #include "../quickjs/quickjs.h"
51 : #include "../scenegraph/qjs_common.h"
52 :
53 : typedef struct __xhr_context XMLHTTPContext;
54 :
55 : /************************************************************
56 : *
57 : * xmlHttpRequest implementation
58 : *
59 : *************************************************************/
60 : typedef enum {
61 : XHR_ONABORT,
62 : XHR_ONERROR,
63 : XHR_ONLOAD,
64 : XHR_ONLOADEND,
65 : XHR_ONLOADSTART,
66 : XHR_ONPROGRESS,
67 : XHR_ONREADYSTATECHANGE,
68 : XHR_ONTIMEOUT,
69 : XHR_READYSTATE,
70 : XHR_RESPONSE,
71 : XHR_RESPONSETYPE,
72 : XHR_RESPONSETEXT,
73 : XHR_RESPONSEXML,
74 : XHR_STATUS,
75 : XHR_STATUSTEXT,
76 : XHR_TIMEOUT,
77 : XHR_UPLOAD,
78 : XHR_WITHCREDENTIALS,
79 : XHR_STATIC_UNSENT,
80 : XHR_STATIC_OPENED,
81 : XHR_STATIC_HEADERS_RECEIVED,
82 : XHR_STATIC_LOADING,
83 : XHR_STATIC_DONE,
84 : XHR_CACHE,
85 : } XHR_JSProperty;
86 :
87 : typedef enum {
88 : XHR_READYSTATE_UNSENT = 0,
89 : XHR_READYSTATE_OPENED = 1,
90 : XHR_READYSTATE_HEADERS_RECEIVED = 2,
91 : XHR_READYSTATE_LOADING = 3,
92 : XHR_READYSTATE_DONE = 4
93 : } XHR_ReadyState;
94 :
95 : typedef enum {
96 : XHR_RESPONSETYPE_NONE,
97 : XHR_RESPONSETYPE_ARRAYBUFFER,
98 : XHR_RESPONSETYPE_BLOB,
99 : XHR_RESPONSETYPE_DOCUMENT,
100 : XHR_RESPONSETYPE_JSON,
101 : XHR_RESPONSETYPE_TEXT,
102 : XHR_RESPONSETYPE_SAX,
103 : XHR_RESPONSETYPE_PUSH,
104 : } XHR_ResponseType;
105 :
106 : typedef enum {
107 : XHR_CACHETYPE_NORMAL,
108 : XHR_CACHETYPE_NONE,
109 : XHR_CACHETYPE_MEMORY,
110 : } XHR_CacheType;
111 :
112 : struct __xhr_context
113 : {
114 : JSContext *c;
115 : JSValue _this;
116 :
117 : /* callback functions */
118 : JSValue onabort;
119 : JSValue onerror;
120 : JSValue onreadystatechange;
121 : JSValue onload;
122 : JSValue onloadstart;
123 : JSValue onloadend;
124 : JSValue onprogress;
125 : JSValue ontimeout;
126 :
127 : XHR_ReadyState readyState;
128 : Bool async;
129 :
130 : /* GPAC extension to control the caching of XHR-downloaded resources */
131 : XHR_CacheType cache;
132 :
133 : /*header/header-val, terminated by NULL*/
134 : char **headers;
135 : u32 cur_header;
136 : char **recv_headers;
137 :
138 : char *method, *url;
139 : GF_DownloadSession *sess;
140 : char *data;
141 : u32 size;
142 : JSValue arraybuffer;
143 : GF_Err ret_code;
144 : u32 html_status;
145 : char *statusText;
146 : char *mime;
147 : u32 timeout;
148 : XHR_ResponseType responseType;
149 : Bool withCredentials;
150 : Bool isFile;
151 :
152 : GF_SAXParser *sax;
153 : GF_List *node_stack;
154 :
155 : GF_DOMEventTarget *event_target;
156 :
157 : /* dom graph in which the XHR is created */
158 : GF_SceneGraph *owning_graph;
159 : #ifndef GPAC_DISABLE_SVG
160 : /* dom graph used to parse XML into */
161 : GF_SceneGraph *document;
162 : #endif
163 : Bool js_dom_loaded;
164 : };
165 :
166 : GF_JSClass xhrClass;
167 :
168 : #if 0 //unused
169 : GF_SceneGraph *xml_http_get_scenegraph(XMLHTTPContext *ctx)
170 : {
171 : return ctx->owning_graph;
172 : }
173 : #endif
174 :
175 11 : static void xml_http_reset_recv_hdr(XMLHTTPContext *ctx)
176 : {
177 : u32 nb_hdr = 0;
178 11 : if (ctx->recv_headers) {
179 18 : while (ctx->recv_headers[nb_hdr]) {
180 12 : gf_free(ctx->recv_headers[nb_hdr]);
181 12 : gf_free(ctx->recv_headers[nb_hdr+1]);
182 12 : nb_hdr+=2;
183 : }
184 6 : gf_free(ctx->recv_headers);
185 6 : ctx->recv_headers = NULL;
186 : }
187 11 : }
188 :
189 12 : static void xml_http_append_recv_header(XMLHTTPContext *ctx, const char *hdr, const char *val)
190 : {
191 : u32 nb_hdr = 0;
192 12 : if (ctx->recv_headers) {
193 6 : while (ctx->recv_headers[nb_hdr]) nb_hdr+=2;
194 : }
195 12 : ctx->recv_headers = (char **)gf_realloc(ctx->recv_headers, sizeof(char*)*(nb_hdr+3));
196 12 : ctx->recv_headers[nb_hdr] = gf_strdup(hdr);
197 12 : ctx->recv_headers[nb_hdr+1] = gf_strdup(val ? val : "");
198 12 : ctx->recv_headers[nb_hdr+2] = NULL;
199 12 : }
200 :
201 6 : static void xml_http_append_send_header(XMLHTTPContext *ctx, char *hdr, char *val)
202 : {
203 6 : if (!hdr) return;
204 :
205 6 : if (ctx->headers) {
206 : u32 nb_hdr = 0;
207 0 : while (ctx->headers && ctx->headers[nb_hdr]) {
208 0 : if (stricmp(ctx->headers[nb_hdr], hdr)) {
209 0 : nb_hdr+=2;
210 0 : continue;
211 : }
212 : /*ignore these ones*/
213 0 : if (!stricmp(hdr, "Accept-Charset")
214 0 : || !stricmp(hdr, "Accept-Encoding")
215 0 : || !stricmp(hdr, "Content-Length")
216 0 : || !stricmp(hdr, "Expect")
217 0 : || !stricmp(hdr, "Date")
218 0 : || !stricmp(hdr, "Host")
219 0 : || !stricmp(hdr, "Keep-Alive")
220 0 : || !stricmp(hdr, "Referer")
221 0 : || !stricmp(hdr, "TE")
222 0 : || !stricmp(hdr, "Trailer")
223 0 : || !stricmp(hdr, "Transfer-Encoding")
224 0 : || !stricmp(hdr, "Upgrade")
225 : ) {
226 : return;
227 : }
228 :
229 : /*replace content for these ones*/
230 0 : if (!stricmp(hdr, "Authorization")
231 0 : || !stricmp(hdr, "Content-Base")
232 0 : || !stricmp(hdr, "Content-Location")
233 0 : || !stricmp(hdr, "Content-MD5")
234 0 : || !stricmp(hdr, "Content-Range")
235 0 : || !stricmp(hdr, "Content-Type")
236 0 : || !stricmp(hdr, "Content-Version")
237 0 : || !stricmp(hdr, "Delta-Base")
238 0 : || !stricmp(hdr, "Depth")
239 0 : || !stricmp(hdr, "Destination")
240 0 : || !stricmp(hdr, "ETag")
241 0 : || !stricmp(hdr, "From")
242 0 : || !stricmp(hdr, "If-Modified-Since")
243 0 : || !stricmp(hdr, "If-Range")
244 0 : || !stricmp(hdr, "If-Unmodified-Since")
245 0 : || !stricmp(hdr, "Max-Forwards")
246 0 : || !stricmp(hdr, "MIME-Version")
247 0 : || !stricmp(hdr, "Overwrite")
248 0 : || !stricmp(hdr, "Proxy-Authorization")
249 0 : || !stricmp(hdr, "SOAPAction")
250 0 : || !stricmp(hdr, "Timeout") ) {
251 0 : gf_free(ctx->headers[nb_hdr+1]);
252 0 : ctx->headers[nb_hdr+1] = gf_strdup(val);
253 0 : return;
254 : }
255 : /*append value*/
256 : else {
257 0 : char *new_val = (char *)gf_malloc(sizeof(char) * (strlen(ctx->headers[nb_hdr+1])+strlen(val)+3));
258 0 : sprintf(new_val, "%s, %s", ctx->headers[nb_hdr+1], val);
259 0 : gf_free(ctx->headers[nb_hdr+1]);
260 0 : ctx->headers[nb_hdr+1] = new_val;
261 0 : return;
262 : }
263 : }
264 : }
265 6 : xml_http_append_recv_header(ctx, hdr, val);
266 : }
267 :
268 15 : static void xml_http_del_data(XMLHTTPContext *ctx)
269 : {
270 30 : if (!JS_IsUndefined(ctx->arraybuffer)) {
271 4 : JS_DetachArrayBuffer(ctx->c, ctx->arraybuffer);
272 4 : JS_FreeValue(ctx->c, ctx->arraybuffer);
273 4 : ctx->arraybuffer = JS_UNDEFINED;
274 : }
275 15 : if (ctx->data) {
276 3 : gf_free(ctx->data);
277 3 : ctx->data = NULL;
278 : }
279 15 : ctx->size = 0;
280 15 : }
281 :
282 11 : static void xml_http_reset_partial(XMLHTTPContext *ctx)
283 : {
284 11 : xml_http_reset_recv_hdr(ctx);
285 11 : xml_http_del_data(ctx);
286 11 : if (ctx->mime) {
287 3 : gf_free(ctx->mime);
288 3 : ctx->mime = NULL;
289 : }
290 11 : if (ctx->statusText) {
291 0 : gf_free(ctx->statusText);
292 0 : ctx->statusText = NULL;
293 : }
294 11 : ctx->cur_header = 0;
295 11 : ctx->html_status = 0;
296 11 : }
297 :
298 8 : static void xml_http_reset(XMLHTTPContext *ctx)
299 : {
300 8 : if (ctx->method) {
301 4 : gf_free(ctx->method);
302 4 : ctx->method = NULL;
303 : }
304 8 : if (ctx->url) {
305 4 : gf_free(ctx->url);
306 4 : ctx->url = NULL;
307 : }
308 :
309 8 : xml_http_reset_partial(ctx);
310 :
311 8 : if (ctx->sess) {
312 : GF_DownloadSession *tmp = ctx->sess;
313 1 : ctx->sess = NULL;
314 1 : gf_dm_sess_abort(tmp);
315 1 : gf_dm_sess_del(tmp);
316 : }
317 :
318 8 : if (ctx->url) {
319 0 : gf_free(ctx->url);
320 0 : ctx->url = NULL;
321 : }
322 8 : if (ctx->sax) {
323 1 : gf_xml_sax_del(ctx->sax);
324 1 : ctx->sax = NULL;
325 : }
326 8 : if (ctx->node_stack) {
327 1 : gf_list_del(ctx->node_stack);
328 1 : ctx->node_stack = NULL;
329 : }
330 : #ifndef GPAC_DISABLE_SVG
331 8 : if (ctx->document) {
332 3 : if (ctx->js_dom_loaded) {
333 2 : dom_js_unload();
334 2 : ctx->js_dom_loaded = GF_FALSE;
335 : }
336 3 : gf_node_unregister(ctx->document->RootNode, NULL);
337 :
338 : /*we're sure the graph is a "nomade" one since we initially put the refcount to 1 ourselves*/
339 3 : ctx->document->reference_count--;
340 3 : if (!ctx->document->reference_count) {
341 2 : gf_sg_js_dom_pre_destroy(JS_GetRuntime(ctx->c), ctx->document, NULL);
342 2 : gf_sg_del(ctx->document);
343 : }
344 : }
345 8 : ctx->document = NULL;
346 : #endif
347 8 : ctx->size = 0;
348 8 : ctx->async = GF_FALSE;
349 8 : ctx->readyState = XHR_READYSTATE_UNSENT;
350 8 : ctx->ret_code = GF_OK;
351 8 : }
352 :
353 415 : static void xml_http_finalize(JSRuntime *rt, JSValue obj)
354 : {
355 415 : XMLHTTPContext *ctx = (XMLHTTPContext *) JS_GetOpaque(obj, xhrClass.class_id);
356 415 : if (!ctx) return;
357 : JS_FreeValueRT(rt, ctx->onabort);
358 : JS_FreeValueRT(rt, ctx->onerror);
359 : JS_FreeValueRT(rt, ctx->onload);
360 : JS_FreeValueRT(rt, ctx->onloadend);
361 : JS_FreeValueRT(rt, ctx->onloadstart);
362 : JS_FreeValueRT(rt, ctx->onprogress);
363 : JS_FreeValueRT(rt, ctx->onreadystatechange);
364 : JS_FreeValueRT(rt, ctx->ontimeout);
365 4 : xml_http_reset(ctx);
366 : #ifndef GPAC_DISABLE_SVG
367 4 : if (ctx->event_target)
368 0 : gf_dom_event_target_del(ctx->event_target);
369 : #endif
370 :
371 4 : gf_free(ctx);
372 : }
373 :
374 12 : static GFINLINE GF_SceneGraph *xml_get_scenegraph(JSContext *c)
375 : {
376 : GF_SceneGraph *scene;
377 12 : JSValue global = JS_GetGlobalObject(c);
378 12 : scene = (GF_SceneGraph *) JS_GetOpaque_Nocheck(global);
379 : JS_FreeValue(c, global);
380 12 : return scene;
381 : }
382 :
383 : #ifndef GPAC_DISABLE_SVG
384 0 : void xhr_get_event_target(JSContext *c, JSValue obj, GF_SceneGraph **sg, GF_DOMEventTarget **target)
385 : {
386 0 : if (c) {
387 : /*XHR interface*/
388 0 : XMLHTTPContext *ctx = JS_GetOpaque(obj, xhrClass.class_id);
389 0 : if (!ctx) return;
390 :
391 0 : *sg = xml_get_scenegraph(c);
392 0 : *target = ctx->event_target;
393 : }
394 : }
395 : #endif
396 :
397 4 : static JSValue xml_http_constructor(JSContext *c, JSValueConst new_target, int argc, JSValueConst *argv)
398 : {
399 : XMLHTTPContext *p;
400 : JSValue obj;
401 :
402 4 : GF_SAFEALLOC(p, XMLHTTPContext);
403 4 : if (!p) {
404 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[WHR] Failed to allocate XHR object\n"));
405 0 : return JS_EXCEPTION;
406 : }
407 4 : obj = JS_NewObjectClass(c, xhrClass.class_id);
408 4 : p->c = c;
409 4 : p->_this = obj;
410 4 : p->owning_graph = xml_get_scenegraph(c);
411 : #ifndef GPAC_DISABLE_SVG
412 4 : if (p->owning_graph)
413 0 : p->event_target = gf_dom_event_target_new(GF_DOM_EVENT_TARGET_XHR, p);
414 : #endif
415 :
416 4 : p->onabort = JS_NULL;
417 4 : p->onerror = JS_NULL;
418 4 : p->onreadystatechange = JS_NULL;
419 4 : p->onload = JS_NULL;
420 4 : p->onloadstart = JS_NULL;
421 4 : p->onloadend = JS_NULL;
422 4 : p->onprogress = JS_NULL;
423 4 : p->ontimeout = JS_NULL;
424 :
425 4 : JS_SetOpaque(obj, p);
426 4 : return obj;
427 : }
428 :
429 13 : static void xml_http_fire_event(XMLHTTPContext *ctx, GF_EventType evtType)
430 : {
431 : #ifndef GPAC_DISABLE_SVG
432 : GF_DOM_Event xhr_evt;
433 13 : if (!ctx->event_target)
434 13 : return;
435 :
436 : memset(&xhr_evt, 0, sizeof(GF_DOM_Event));
437 0 : xhr_evt.type = evtType;
438 0 : xhr_evt.target = ctx->event_target->ptr;
439 0 : xhr_evt.target_type = ctx->event_target->ptr_type;
440 0 : gf_sg_fire_dom_event(ctx->event_target, &xhr_evt, ctx->owning_graph, NULL);
441 : #endif
442 : }
443 :
444 :
445 10 : static void xml_http_state_change(XMLHTTPContext *ctx)
446 : {
447 : #ifndef GPAC_DISABLE_VRML
448 : GF_SceneGraph *scene;
449 : GF_Node *n;
450 : #endif
451 :
452 10 : gf_js_lock(ctx->c, GF_TRUE);
453 20 : if (! JS_IsNull(ctx->onreadystatechange)) {
454 10 : JSValue ret = JS_Call(ctx->c, ctx->onreadystatechange, ctx->_this, 0, NULL);
455 10 : if (JS_IsException(ret))
456 0 : js_dump_error(ctx->c);
457 10 : JS_FreeValue(ctx->c, ret);
458 : }
459 :
460 10 : js_do_loop(ctx->c);
461 10 : gf_js_lock(ctx->c, GF_FALSE);
462 :
463 10 : if (! ctx->owning_graph) return;
464 :
465 : /*Flush BIFS eventOut events*/
466 : #ifndef GPAC_DISABLE_VRML
467 0 : scene = (GF_SceneGraph *)JS_GetContextOpaque(ctx->c);
468 : /*this is a scene, we look for a node (if scene is used, this is DOM-based scripting not VRML*/
469 0 : if (scene->__reserved_null == 0) return;
470 0 : n = (GF_Node *)JS_GetContextOpaque(ctx->c);
471 0 : gf_js_vrml_flush_event_out(n, (GF_ScriptPriv *)n->sgprivate->UserPrivate);
472 : #endif
473 : }
474 :
475 :
476 4 : static JSValue xml_http_open(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
477 : {
478 : const char *val;
479 : GF_JSAPIParam par;
480 : XMLHTTPContext *ctx;
481 : GF_SceneGraph *scene;
482 :
483 4 : ctx = (XMLHTTPContext *) JS_GetOpaque(obj, xhrClass.class_id);
484 4 : if (!ctx) return JS_EXCEPTION;
485 :
486 : /*reset*/
487 4 : if (ctx->readyState) xml_http_reset(ctx);
488 :
489 4 : if (argc<2) return JS_EXCEPTION;
490 : /*method is a string*/
491 8 : if (!JS_CHECK_STRING(argv[0])) return JS_EXCEPTION;
492 : /*url is a string*/
493 8 : if (!JS_CHECK_STRING(argv[1])) return JS_EXCEPTION;
494 :
495 4 : xml_http_reset(ctx);
496 : val = JS_ToCString(c, argv[0]);
497 4 : if (strcmp(val, "GET") && strcmp(val, "POST") && strcmp(val, "HEAD")
498 0 : && strcmp(val, "PUT") && strcmp(val, "DELETE") && strcmp(val, "OPTIONS") ) {
499 0 : JS_FreeCString(c, val);
500 0 : return JS_EXCEPTION;
501 : }
502 :
503 4 : ctx->method = gf_strdup(val);
504 4 : JS_FreeCString(c, val);
505 :
506 : /*concatenate URL*/
507 4 : scene = xml_get_scenegraph(c);
508 : #ifndef GPAC_DISABLE_VRML
509 4 : while (scene && scene->pOwningProto && scene->parent_scene) scene = scene->parent_scene;
510 : #endif
511 :
512 4 : par.uri.nb_params = 0;
513 : val = JS_ToCString(c, argv[1]);
514 4 : par.uri.url = (char *) val;
515 4 : ctx->url = NULL;
516 4 : if (scene && scene->script_action) {
517 0 : scene->script_action(scene->script_action_cbck, GF_JSAPI_OP_RESOLVE_URI, scene->RootNode, &par);
518 0 : ctx->url = par.uri.url;
519 : } else {
520 4 : ctx->url = gf_strdup(val);
521 : }
522 4 : JS_FreeCString(c, val);
523 :
524 : /*async defaults to true*/
525 4 : ctx->async = GF_TRUE;
526 4 : if (argc>2) {
527 : val = NULL;
528 0 : ctx->async = JS_ToBool(c, argv[2]) ? GF_TRUE : GF_FALSE;
529 0 : if (argc>3) {
530 0 : if (!JS_CHECK_STRING(argv[3])) return JS_EXCEPTION;
531 : /*TODO*/
532 0 : if (argc>4) {
533 0 : if (!JS_CHECK_STRING(argv[4])) return JS_EXCEPTION;
534 : val = JS_ToCString(c, argv[4]);
535 : /*TODO*/
536 : } else {
537 : val = JS_ToCString(c, argv[3]);
538 : }
539 : }
540 0 : JS_FreeCString(c, val);
541 : }
542 : /*OPEN success*/
543 4 : ctx->readyState = XHR_READYSTATE_OPENED;
544 4 : xml_http_state_change(ctx);
545 4 : xml_http_fire_event(ctx, GF_EVENT_MEDIA_LOAD_START);
546 4 : if (JS_IsFunction(c, ctx->onloadstart) ) {
547 0 : return JS_Call(ctx->c, ctx->onloadstart, ctx->_this, 0, NULL);
548 : }
549 4 : return JS_TRUE;
550 : }
551 :
552 6 : static JSValue xml_http_set_header(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
553 : {
554 : const char *hdr, *val;
555 6 : XMLHTTPContext *ctx = (XMLHTTPContext *) JS_GetOpaque(obj, xhrClass.class_id);
556 6 : if (!ctx) return JS_EXCEPTION;
557 :
558 6 : if (ctx->readyState!=XHR_READYSTATE_OPENED) return JS_EXCEPTION;
559 6 : if (argc!=2) return JS_EXCEPTION;
560 12 : if (!JS_CHECK_STRING(argv[0])) return JS_EXCEPTION;
561 12 : if (!JS_CHECK_STRING(argv[1])) return JS_EXCEPTION;
562 :
563 : hdr = JS_ToCString(c, argv[0]);
564 : val = JS_ToCString(c, argv[1]);
565 6 : xml_http_append_send_header(ctx, (char *)hdr, (char *)val);
566 6 : JS_FreeCString(c, hdr);
567 6 : JS_FreeCString(c, val);
568 6 : return JS_TRUE;
569 : }
570 :
571 : #ifndef GPAC_DISABLE_SVG
572 :
573 22 : static void xml_http_sax_start(void *sax_cbck, const char *node_name, const char *name_space, const GF_XMLAttribute *attributes, u32 nb_attributes)
574 : {
575 : u32 i;
576 : GF_DOMFullAttribute *prev = NULL;
577 : GF_DOMFullNode *par;
578 : XMLHTTPContext *ctx = (XMLHTTPContext *)sax_cbck;
579 22 : GF_DOMFullNode *node = (GF_DOMFullNode *) gf_node_new(ctx->document, TAG_DOMFullNode);
580 :
581 22 : node->name = gf_strdup(node_name);
582 102 : for (i=0; i<nb_attributes; i++) {
583 : /* special case for 'xml:id' to be parsed as an ID
584 : NOTE: we do not test for the 'id' attribute because without DTD we are not sure that it's an ID */
585 80 : if (!stricmp(attributes[i].name, "xml:id")) {
586 0 : u32 id = gf_sg_get_max_node_id(ctx->document) + 1;
587 0 : gf_node_set_id((GF_Node *)node, id, attributes[i].value);
588 : } else {
589 : GF_DOMFullAttribute *att;
590 80 : GF_SAFEALLOC(att, GF_DOMFullAttribute);
591 80 : if (!att) {
592 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[XHR] Fail to allocate DOM attribute\n"));
593 0 : continue;
594 : }
595 80 : att->tag = TAG_DOM_ATT_any;
596 80 : att->name = gf_strdup(attributes[i].name);
597 80 : att->data_type = (u16) DOM_String_datatype;
598 80 : att->data = gf_svg_create_attribute_value(att->data_type);
599 80 : *((char **)att->data) = gf_strdup(attributes[i].value);
600 80 : if (prev) prev->next = (GF_DOMAttribute*)att;
601 21 : else node->attributes = (GF_DOMAttribute*)att;
602 : prev = att;
603 : }
604 : }
605 22 : par = (GF_DOMFullNode *)gf_list_last(ctx->node_stack);
606 22 : gf_node_register((GF_Node*)node, (GF_Node*)par);
607 22 : if (par) {
608 21 : gf_node_list_add_child(&par->children, (GF_Node*)node);
609 : } else {
610 : assert(!ctx->document->RootNode);
611 1 : ctx->document->RootNode = (GF_Node*)node;
612 : }
613 22 : gf_list_add(ctx->node_stack, node);
614 22 : }
615 :
616 22 : static void xml_http_sax_end(void *sax_cbck, const char *node_name, const char *name_space)
617 : {
618 : XMLHTTPContext *ctx = (XMLHTTPContext *)sax_cbck;
619 22 : GF_DOMFullNode *par = (GF_DOMFullNode *)gf_list_last(ctx->node_stack);
620 22 : if (par) {
621 : /*depth mismatch*/
622 22 : if (strcmp(par->name, node_name)) return;
623 22 : gf_list_rem_last(ctx->node_stack);
624 : }
625 : }
626 30 : static void xml_http_sax_text(void *sax_cbck, const char *content, Bool is_cdata)
627 : {
628 : XMLHTTPContext *ctx = (XMLHTTPContext *)sax_cbck;
629 30 : GF_DOMFullNode *par = (GF_DOMFullNode *)gf_list_last(ctx->node_stack);
630 30 : if (par) {
631 : u32 i, len;
632 : GF_DOMText *txt;
633 : /*basic check, remove all empty text nodes*/
634 30 : len = (u32) strlen(content);
635 55 : for (i=0; i<len; i++) {
636 30 : if (!strchr(" \n\r\t", content[i])) break;
637 : }
638 30 : if (i==len) return;
639 :
640 5 : txt = gf_dom_add_text_node((GF_Node *)par, gf_strdup(content) );
641 5 : txt->type = is_cdata ? GF_DOM_TEXT_CDATA : GF_DOM_TEXT_REGULAR;
642 : }
643 : }
644 : #endif // GPAC_DISABLE_SVG
645 :
646 3 : static void xml_http_terminate(XMLHTTPContext *ctx, GF_Err error)
647 : {
648 : /*if we get here, destroy downloader*/
649 3 : if (ctx->sess) {
650 0 : gf_dm_sess_del(ctx->sess);
651 0 : ctx->sess = NULL;
652 : }
653 :
654 : /*but stay in loaded mode*/
655 3 : ctx->readyState = XHR_READYSTATE_DONE;
656 3 : xml_http_state_change(ctx);
657 3 : xml_http_fire_event(ctx, GF_EVENT_LOAD);
658 3 : xml_http_fire_event(ctx, GF_EVENT_MEDIA_LOAD_DONE);
659 3 : if (JS_IsFunction(ctx->c, ctx->onload)) {
660 3 : JSValue rval = JS_Call(ctx->c, ctx->onload, ctx->_this, 0, NULL);
661 3 : if (JS_IsException(rval)) js_dump_error(ctx->c);
662 3 : JS_FreeValue(ctx->c, rval);
663 : }
664 3 : if (JS_IsFunction(ctx->c, ctx->onloadend)) {
665 0 : JSValue rval = JS_Call(ctx->c, ctx->onloadend, ctx->_this, 0, NULL);
666 0 : if (JS_IsException(rval)) js_dump_error(ctx->c);
667 0 : JS_FreeValue(ctx->c, rval);
668 : }
669 3 : }
670 :
671 13 : static void xml_http_on_data(void *usr_cbk, GF_NETIO_Parameter *parameter)
672 : {
673 : Bool locked = GF_FALSE;
674 : XMLHTTPContext *ctx = (XMLHTTPContext *)usr_cbk;
675 :
676 : /*make sure we can grab JS and the session is not destroyed*/
677 26 : while (ctx->sess) {
678 0 : if (gf_js_try_lock(ctx->c) ) {
679 : locked = GF_TRUE;
680 : break;
681 : }
682 0 : gf_sleep(1);
683 : }
684 : /*if session not set, we've had an abort*/
685 13 : if (!ctx->sess && !ctx->isFile) {
686 0 : if (locked)
687 0 : gf_js_lock(ctx->c, GF_FALSE);
688 : return;
689 : }
690 :
691 13 : if (!ctx->isFile) {
692 : assert( locked );
693 0 : gf_js_lock(ctx->c, GF_FALSE);
694 : locked = GF_FALSE;
695 : }
696 13 : switch (parameter->msg_type) {
697 : case GF_NETIO_SETUP:
698 : /*nothing to do*/
699 : goto exit;
700 : case GF_NETIO_CONNECTED:
701 : /*nothing to do*/
702 : goto exit;
703 3 : case GF_NETIO_WAIT_FOR_REPLY:
704 : /*reset send() state (data, current header) and prepare recv headers*/
705 3 : xml_http_reset_partial(ctx);
706 3 : ctx->readyState = XHR_READYSTATE_HEADERS_RECEIVED;
707 3 : xml_http_state_change(ctx);
708 3 : xml_http_fire_event(ctx, GF_EVENT_MEDIA_PROGRESS);
709 3 : if (JS_IsFunction(ctx->c, ctx->onprogress) ) {
710 0 : JSValue rval = JS_Call(ctx->c, ctx->onprogress, ctx->_this, 0, NULL);
711 0 : if (JS_IsException(rval)) js_dump_error(ctx->c);
712 0 : JS_FreeValue(ctx->c, rval);
713 : }
714 : goto exit;
715 : /*this is signaled sent AFTER headers*/
716 0 : case GF_NETIO_PARSE_REPLY:
717 0 : ctx->html_status = parameter->reply;
718 0 : if (parameter->value) {
719 0 : ctx->statusText = gf_strdup(parameter->value);
720 : }
721 0 : ctx->readyState = XHR_READYSTATE_LOADING;
722 0 : xml_http_state_change(ctx);
723 0 : ctx->readyState = XHR_READYSTATE_HEADERS_RECEIVED;
724 0 : xml_http_state_change(ctx);
725 0 : xml_http_fire_event(ctx, GF_EVENT_MEDIA_PROGRESS);
726 0 : if (JS_IsFunction(ctx->c, ctx->onprogress) ) {
727 0 : JSValue rval = JS_Call(ctx->c, ctx->onprogress, ctx->_this, 0, NULL);
728 0 : if (JS_IsException(rval)) js_dump_error(ctx->c);
729 0 : JS_FreeValue(ctx->c, rval);
730 : }
731 : goto exit;
732 :
733 0 : case GF_NETIO_GET_METHOD:
734 0 : parameter->name = ctx->method;
735 0 : goto exit;
736 0 : case GF_NETIO_GET_HEADER:
737 0 : if (ctx->headers && ctx->headers[2*ctx->cur_header]) {
738 0 : parameter->name = ctx->headers[2*ctx->cur_header];
739 0 : parameter->value = ctx->headers[2*ctx->cur_header+1];
740 0 : ctx->cur_header++;
741 : }
742 : goto exit;
743 0 : case GF_NETIO_GET_CONTENT:
744 0 : if (ctx->data) {
745 0 : parameter->data = ctx->data;
746 0 : parameter->size = ctx->size;
747 : }
748 : goto exit;
749 6 : case GF_NETIO_PARSE_HEADER:
750 6 : xml_http_append_recv_header(ctx, parameter->name, parameter->value);
751 : /*prepare SAX parser*/
752 6 : if (ctx->responseType != XHR_RESPONSETYPE_SAX) goto exit;
753 2 : if (strcmp(parameter->name, "Content-Type")) goto exit;
754 :
755 : #ifndef GPAC_DISABLE_SVG
756 1 : if (!strncmp(parameter->value, "application/xml", 15)
757 0 : || !strncmp(parameter->value, "text/xml", 8)
758 0 : || strstr(parameter->value, "+xml")
759 0 : || strstr(parameter->value, "/xml")
760 : // || !strncmp(parameter->value, "text/plain", 10)
761 : ) {
762 : assert(!ctx->sax);
763 1 : ctx->sax = gf_xml_sax_new(xml_http_sax_start, xml_http_sax_end, xml_http_sax_text, ctx);
764 1 : ctx->node_stack = gf_list_new();
765 1 : ctx->document = gf_sg_new();
766 : /*mark this doc as "nomade", and let it leave until all references to it are destroyed*/
767 1 : ctx->document->reference_count = 1;
768 : }
769 : #endif
770 :
771 : goto exit;
772 1 : case GF_NETIO_DATA_EXCHANGE:
773 1 : if (parameter->data && parameter->size) {
774 1 : if (ctx->sax) {
775 : GF_Err e;
776 1 : if (!ctx->size) e = gf_xml_sax_init(ctx->sax, (unsigned char *) parameter->data);
777 0 : else e = gf_xml_sax_parse(ctx->sax, parameter->data);
778 1 : if (e) {
779 0 : gf_xml_sax_del(ctx->sax);
780 0 : ctx->sax = NULL;
781 : }
782 : goto exit;
783 : }
784 :
785 : /*detach arraybuffer if any*/
786 0 : if (!JS_IsUndefined(ctx->arraybuffer)) {
787 0 : JS_DetachArrayBuffer(ctx->c, ctx->arraybuffer);
788 0 : JS_FreeValue(ctx->c, ctx->arraybuffer);
789 0 : ctx->arraybuffer = JS_UNDEFINED;
790 : }
791 :
792 0 : if (ctx->responseType!=XHR_RESPONSETYPE_PUSH) {
793 0 : ctx->data = (char *)gf_realloc(ctx->data, sizeof(char)*(ctx->size+parameter->size+1));
794 0 : memcpy(ctx->data + ctx->size, parameter->data, sizeof(char)*parameter->size);
795 0 : ctx->size += parameter->size;
796 0 : ctx->data[ctx->size] = 0;
797 : }
798 :
799 0 : if (JS_IsFunction(ctx->c, ctx->onprogress) ) {
800 0 : JSValue prog_evt = JS_NewObject(ctx->c);
801 0 : JSValue buffer_ab=JS_UNDEFINED;
802 0 : u32 bps=0;
803 0 : u64 tot_size=0;
804 0 : gf_dm_sess_get_stats(ctx->sess, NULL, NULL, &tot_size, NULL, &bps, NULL);
805 0 : JS_SetPropertyStr(ctx->c, prog_evt, "lengthComputable", JS_NewBool(ctx->c, tot_size ? 1 : 0));
806 0 : JS_SetPropertyStr(ctx->c, prog_evt, "loaded", JS_NewInt64(ctx->c, ctx->size));
807 0 : JS_SetPropertyStr(ctx->c, prog_evt, "total", JS_NewInt64(ctx->c, tot_size));
808 0 : JS_SetPropertyStr(ctx->c, prog_evt, "bps", JS_NewInt64(ctx->c, bps*8));
809 0 : if (ctx->responseType==XHR_RESPONSETYPE_PUSH) {
810 0 : buffer_ab = JS_NewArrayBuffer(ctx->c, (u8 *) parameter->data, parameter->size, NULL, ctx, 1);
811 0 : JS_SetPropertyStr(ctx->c, prog_evt, "buffer", buffer_ab);
812 : }
813 :
814 0 : JSValue rval = JS_Call(ctx->c, ctx->onprogress, ctx->_this, 1, &prog_evt);
815 0 : if (JS_IsException(rval)) js_dump_error(ctx->c);
816 0 : JS_FreeValue(ctx->c, rval);
817 0 : if (ctx->responseType==XHR_RESPONSETYPE_PUSH) {
818 0 : JS_DetachArrayBuffer(ctx->c, buffer_ab);
819 : }
820 0 : JS_FreeValue(ctx->c, prog_evt);
821 : }
822 : }
823 : goto exit;
824 : case GF_NETIO_DATA_TRANSFERED:
825 : /* No return, go till the end of the function */
826 : break;
827 : case GF_NETIO_DISCONNECTED:
828 : goto exit;
829 0 : case GF_NETIO_STATE_ERROR:
830 0 : ctx->ret_code = parameter->error;
831 : /* No return, go till the end of the function */
832 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[XmlHttpRequest] Download error: %s\n", gf_error_to_string(parameter->error)));
833 : break;
834 0 : case GF_NETIO_REQUEST_SESSION:
835 : case GF_NETIO_CANCEL_STREAM:
836 0 : parameter->error = GF_NOT_SUPPORTED;
837 0 : return;
838 : }
839 3 : if (ctx->async) {
840 3 : xml_http_terminate(ctx, parameter->error);
841 : }
842 :
843 18 : exit:
844 13 : if (locked) {
845 0 : gf_js_lock(ctx->c, GF_FALSE);
846 : }
847 : }
848 :
849 3 : static GF_Err xml_http_process_local(XMLHTTPContext *ctx)
850 : {
851 : /* For XML Http Requests to files, we fake the processing by calling the HTTP callbacks */
852 : GF_NETIO_Parameter par;
853 : u64 fsize;
854 : char contentLengthHeader[256];
855 : FILE *responseFile;
856 :
857 : /*opera-style local host*/
858 3 : if (!strnicmp(ctx->url, "file://localhost", 16)) responseFile = gf_fopen(ctx->url+16, "rb");
859 : /*regular-style local host*/
860 3 : else if (!strnicmp(ctx->url, "file://", 7)) responseFile = gf_fopen(ctx->url+7, "rb");
861 : /* other types: e.g. "C:\" */
862 3 : else responseFile = gf_fopen(ctx->url, "rb");
863 :
864 3 : if (!responseFile) {
865 0 : ctx->html_status = 404;
866 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[XmlHttpRequest] cannot open local file %s\n", ctx->url));
867 0 : xml_http_fire_event(ctx, GF_EVENT_ERROR);
868 0 : if (JS_IsFunction(ctx->c, ctx->onerror) ) {
869 0 : JSValue rval = JS_Call(ctx->c, ctx->onerror, ctx->_this, 0, NULL);
870 0 : if (JS_IsException(rval)) js_dump_error(ctx->c);
871 0 : JS_FreeValue(ctx->c, rval);
872 : }
873 : return GF_BAD_PARAM;
874 : }
875 3 : ctx->isFile = GF_TRUE;
876 :
877 3 : par.msg_type = GF_NETIO_WAIT_FOR_REPLY;
878 3 : xml_http_on_data(ctx, &par);
879 :
880 3 : fsize = gf_fsize(responseFile);
881 :
882 3 : ctx->html_status = 200;
883 :
884 3 : ctx->data = (char *)gf_malloc(sizeof(char)*(size_t)(fsize+1));
885 3 : fsize = gf_fread(ctx->data, (size_t)fsize, responseFile);
886 3 : gf_fclose(responseFile);
887 3 : ctx->data[fsize] = 0;
888 3 : ctx->size = (u32)fsize;
889 :
890 : memset(&par, 0, sizeof(GF_NETIO_Parameter));
891 3 : par.msg_type = GF_NETIO_PARSE_HEADER;
892 3 : par.name = "Content-Type";
893 3 : if (ctx->responseType == XHR_RESPONSETYPE_SAX) {
894 1 : par.value = "application/xml";
895 2 : } else if (ctx->responseType == XHR_RESPONSETYPE_DOCUMENT) {
896 2 : par.value = "application/xml";
897 : } else {
898 0 : par.value = "application/octet-stream";
899 : }
900 3 : xml_http_on_data(ctx, &par);
901 :
902 : memset(&par, 0, sizeof(GF_NETIO_Parameter));
903 3 : par.msg_type = GF_NETIO_PARSE_HEADER;
904 3 : par.name = "Content-Length";
905 3 : sprintf(contentLengthHeader, "%d", ctx->size);
906 3 : par.value = contentLengthHeader;
907 3 : xml_http_on_data(ctx, &par);
908 :
909 :
910 3 : if (ctx->sax) {
911 : memset(&par, 0, sizeof(GF_NETIO_Parameter));
912 1 : par.msg_type = GF_NETIO_DATA_EXCHANGE;
913 1 : par.data = ctx->data;
914 1 : par.size = ctx->size;
915 1 : ctx->size = 0;
916 1 : xml_http_on_data(ctx, &par);
917 1 : ctx->size = par.size;
918 : }
919 :
920 : memset(&par, 0, sizeof(GF_NETIO_Parameter));
921 3 : par.msg_type = GF_NETIO_DATA_TRANSFERED;
922 3 : xml_http_on_data(ctx, &par);
923 :
924 3 : if (!ctx->async) {
925 0 : xml_http_terminate(ctx, GF_OK);
926 : }
927 : return GF_OK;
928 : }
929 :
930 4 : static JSValue xml_http_send(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
931 : {
932 : GF_Err e;
933 : GF_JSAPIParam par;
934 : GF_SceneGraph *scene;
935 : const char *data = NULL;
936 : u32 data_size = 0;
937 4 : XMLHTTPContext *ctx = (XMLHTTPContext *) JS_GetOpaque(obj, xhrClass.class_id);
938 4 : if (!ctx) return JS_EXCEPTION;
939 :
940 4 : if (ctx->readyState!=XHR_READYSTATE_OPENED) return JS_EXCEPTION;
941 4 : if (ctx->sess) return JS_EXCEPTION;
942 :
943 4 : scene = xml_get_scenegraph(c);
944 4 : if (scene) {
945 0 : par.dnld_man = NULL;
946 0 : if (scene->script_action)
947 0 : scene->script_action(scene->script_action_cbck, GF_JSAPI_OP_GET_DOWNLOAD_MANAGER, NULL, &par);
948 : } else {
949 4 : par.dnld_man = jsf_get_download_manager(c);
950 : }
951 4 : if (!par.dnld_man) return JS_EXCEPTION;
952 :
953 4 : if (argc) {
954 0 : if (JS_IsNull(argv[0])) {
955 0 : } else if (JS_IsArrayBuffer(c, argv[0])) {
956 : size_t asize;
957 0 : data = JS_GetArrayBuffer(c, &asize, argv[0] );
958 0 : if (data) data_size = (u32) asize;
959 0 : } else if (JS_IsObject(argv[0])) {
960 : /*NOT SUPPORTED YET, we must serialize the sg*/
961 0 : return JS_EXCEPTION;
962 : } else {
963 0 : if (!JS_CHECK_STRING(argv[0])) return JS_EXCEPTION;
964 : data = JS_ToCString(c, argv[0]);
965 0 : data_size = (u32) strlen(ctx->data);
966 : }
967 : }
968 :
969 : /*reset previous text*/
970 4 : xml_http_del_data(ctx);
971 4 : ctx->data = data ? gf_strdup(data) : NULL;
972 4 : ctx->size = data_size;
973 :
974 4 : JS_FreeCString(c, data);
975 :
976 4 : if (!strncmp(ctx->url, "http://", 7)) {
977 : u32 flags = GF_NETIO_SESSION_NOTIFY_DATA;
978 1 : if (!ctx->async)
979 : flags |= GF_NETIO_SESSION_NOT_THREADED;
980 :
981 1 : if (ctx->cache != XHR_CACHETYPE_NORMAL) {
982 0 : if (ctx->cache == XHR_CACHETYPE_NONE) {
983 0 : flags |= GF_NETIO_SESSION_NOT_CACHED;
984 : }
985 0 : if (ctx->cache == XHR_CACHETYPE_MEMORY) {
986 0 : flags |= GF_NETIO_SESSION_MEMORY_CACHE;
987 : }
988 : }
989 1 : ctx->sess = gf_dm_sess_new(par.dnld_man, ctx->url, flags, xml_http_on_data, ctx, &e);
990 1 : if (!ctx->sess) return JS_EXCEPTION;
991 :
992 : /*start our download (whether the session is threaded or not)*/
993 1 : e = gf_dm_sess_process(ctx->sess);
994 1 : if (e!=GF_OK) {
995 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[XmlHttpRequest] Error processing %s: %s\n", ctx->url, gf_error_to_string(e) ));
996 : }
997 1 : if (!ctx->async) {
998 0 : xml_http_terminate(ctx, e);
999 : }
1000 : } else {
1001 3 : e = xml_http_process_local(ctx);
1002 3 : if (e!=GF_OK) {
1003 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[XmlHttpRequest] Error processing %s: %s\n", ctx->url, gf_error_to_string(e) ));
1004 : }
1005 : }
1006 :
1007 4 : return JS_TRUE;
1008 : }
1009 :
1010 0 : static JSValue xml_http_abort(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
1011 : {
1012 : GF_DownloadSession *sess;
1013 0 : XMLHTTPContext *ctx = JS_GetOpaque(obj, xhrClass.class_id);
1014 0 : if (!ctx) return JS_EXCEPTION;
1015 :
1016 0 : sess = ctx->sess;
1017 0 : ctx->sess = NULL;
1018 0 : if (sess) {
1019 : //abort first, so that on HTTP/2 this results in RST_STREAM
1020 0 : gf_dm_sess_abort(sess);
1021 0 : gf_dm_sess_del(sess);
1022 : }
1023 :
1024 0 : xml_http_fire_event(ctx, GF_EVENT_ABORT);
1025 0 : xml_http_reset(ctx);
1026 0 : if (JS_IsFunction(c, ctx->onabort)) {
1027 0 : return JS_Call(ctx->c, ctx->onabort, ctx->_this, 0, NULL);
1028 : }
1029 0 : return JS_TRUE;
1030 : }
1031 :
1032 2 : static JSValue xml_http_get_all_headers(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
1033 : {
1034 : u32 nb_hdr;
1035 2 : char *szVal = NULL;
1036 : JSValue res;
1037 2 : XMLHTTPContext *ctx = JS_GetOpaque(obj, xhrClass.class_id);
1038 2 : if (!ctx) return JS_EXCEPTION;
1039 :
1040 : /*must be received or loaded*/
1041 2 : if (ctx->readyState<XHR_READYSTATE_LOADING) return JS_EXCEPTION;
1042 : nb_hdr = 0;
1043 2 : if (ctx->recv_headers) {
1044 6 : while (ctx->recv_headers[nb_hdr]) {
1045 4 : if (szVal) gf_dynstrcat(&szVal, "\r\n", NULL);
1046 4 : gf_dynstrcat(&szVal, ctx->recv_headers[nb_hdr], NULL);
1047 4 : gf_dynstrcat(&szVal, ctx->recv_headers[nb_hdr+1], ": ");
1048 4 : nb_hdr+=2;
1049 : }
1050 : }
1051 :
1052 2 : if (!szVal) {
1053 0 : return JS_NULL;
1054 : }
1055 2 : res = JS_NewString(c, szVal);
1056 2 : gf_free(szVal);
1057 2 : return res;
1058 : }
1059 :
1060 2 : static JSValue xml_http_get_header(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
1061 : {
1062 : u32 nb_hdr;
1063 : const char *hdr;
1064 2 : char *szVal = NULL;
1065 2 : XMLHTTPContext *ctx = JS_GetOpaque(obj, xhrClass.class_id);
1066 2 : if (!ctx) return JS_EXCEPTION;
1067 :
1068 4 : if (!JS_CHECK_STRING(argv[0])) return JS_EXCEPTION;
1069 : /*must be received or loaded*/
1070 2 : if (ctx->readyState<XHR_READYSTATE_LOADING) return JS_EXCEPTION;
1071 : hdr = JS_ToCString(c, argv[0]);
1072 :
1073 : nb_hdr = 0;
1074 2 : if (ctx->recv_headers) {
1075 6 : while (ctx->recv_headers[nb_hdr]) {
1076 4 : if (!strcmp(ctx->recv_headers[nb_hdr], hdr)) {
1077 0 : gf_dynstrcat(&szVal, ctx->recv_headers[nb_hdr+1], ", ");
1078 : }
1079 4 : nb_hdr+=2;
1080 : }
1081 : }
1082 2 : JS_FreeCString(c, hdr);
1083 2 : if (!szVal) {
1084 2 : return JS_NULL;
1085 : }
1086 0 : JSValue res = JS_NewString(c, szVal);
1087 0 : gf_free(szVal);
1088 0 : return res;
1089 : }
1090 :
1091 : #ifndef GPAC_DISABLE_SVG
1092 2 : static GF_Err xml_http_load_dom(XMLHTTPContext *ctx)
1093 : {
1094 : GF_Err e;
1095 2 : GF_DOMParser *parser = gf_xml_dom_new();
1096 2 : e = gf_xml_dom_parse_string(parser, ctx->data);
1097 2 : if (!e) {
1098 2 : e = gf_sg_init_from_xml_node(ctx->document, gf_xml_dom_get_root(parser));
1099 : }
1100 2 : gf_xml_dom_del(parser);
1101 2 : return e;
1102 : }
1103 : #endif //GPAC_DISABLE_SVG
1104 :
1105 :
1106 3 : static JSValue xml_http_overrideMimeType(JSContext *c, JSValueConst obj, int argc, JSValueConst *argv)
1107 : {
1108 : const char *mime;
1109 3 : XMLHTTPContext *ctx = JS_GetOpaque(obj, xhrClass.class_id);
1110 3 : if (!ctx || !argc) return JS_EXCEPTION;
1111 :
1112 6 : if (!JS_CHECK_STRING(argv[0])) return JS_EXCEPTION;
1113 : mime = JS_ToCString(c, argv[0]);
1114 3 : if (ctx->mime) gf_free(ctx->mime);
1115 3 : ctx->mime = gf_strdup(mime);
1116 3 : JS_FreeCString(c, mime);
1117 3 : return JS_TRUE;
1118 : }
1119 :
1120 24 : static JSValue xml_http_getProperty(JSContext *c, JSValueConst obj, int magic)
1121 : {
1122 24 : XMLHTTPContext *ctx = JS_GetOpaque(obj, xhrClass.class_id);
1123 24 : if (!ctx) return JS_EXCEPTION;
1124 :
1125 24 : switch (magic) {
1126 0 : case XHR_ONABORT: return JS_DupValue(c, ctx->onabort);
1127 0 : case XHR_ONERROR: return JS_DupValue(c, ctx->onerror);
1128 0 : case XHR_ONLOAD: return JS_DupValue(c, ctx->onload);
1129 0 : case XHR_ONLOADSTART: return JS_DupValue(c, ctx->onloadstart);
1130 0 : case XHR_ONLOADEND: return JS_DupValue(c, ctx->onloadend);
1131 0 : case XHR_ONPROGRESS: return JS_DupValue(c, ctx->onprogress);
1132 0 : case XHR_ONREADYSTATECHANGE: return JS_DupValue(c, ctx->onreadystatechange);
1133 0 : case XHR_ONTIMEOUT: return JS_DupValue(c, ctx->ontimeout);
1134 17 : case XHR_READYSTATE: return JS_NewInt32(c, ctx->readyState);
1135 0 : case XHR_RESPONSETEXT:
1136 0 : if (ctx->readyState<XHR_READYSTATE_LOADING) return JS_NULL;
1137 0 : if (ctx->data) {
1138 0 : return JS_NewString(c, ctx->data);
1139 : } else {
1140 0 : return JS_NULL;
1141 : }
1142 :
1143 0 : case XHR_RESPONSEXML:
1144 0 : if (ctx->readyState<XHR_READYSTATE_LOADING) return JS_NULL;
1145 : #ifndef GPAC_DISABLE_SVG
1146 0 : if (ctx->data) {
1147 0 : if (!ctx->document) {
1148 0 : ctx->document = gf_sg_new();
1149 : /*mark this doc as "nomade", and let it leave until all references to it are destroyed*/
1150 0 : ctx->document->reference_count = 1;
1151 0 : xml_http_load_dom(ctx);
1152 : }
1153 0 : if (!ctx->js_dom_loaded) {
1154 0 : dom_js_load(ctx->document, c);
1155 0 : ctx->js_dom_loaded = GF_TRUE;
1156 : }
1157 0 : return dom_document_construct_external(c, ctx->document);
1158 : } else {
1159 0 : return JS_NULL;
1160 : }
1161 : #else
1162 : return js_throw_err_msg(c, GF_NOT_SUPPORTED, "DOM support not included in buil");
1163 : #endif
1164 :
1165 5 : case XHR_RESPONSE:
1166 5 : if (ctx->readyState<XHR_READYSTATE_LOADING) return JS_NULL;
1167 :
1168 5 : if (ctx->data) {
1169 5 : switch(ctx->responseType)
1170 : {
1171 0 : case XHR_RESPONSETYPE_NONE:
1172 : case XHR_RESPONSETYPE_TEXT:
1173 0 : return JS_NewString(c, ctx->data);
1174 :
1175 0 : case XHR_RESPONSETYPE_ARRAYBUFFER:
1176 0 : if (JS_IsUndefined(ctx->arraybuffer)) {
1177 0 : ctx->arraybuffer = JS_NewArrayBuffer(c, ctx->data, ctx->size, NULL, ctx, 1);
1178 : }
1179 : return JS_DupValue(c, ctx->arraybuffer);
1180 : break;
1181 : case XHR_RESPONSETYPE_DOCUMENT:
1182 : #ifndef GPAC_DISABLE_SVG
1183 : if (ctx->data) {
1184 4 : if (!ctx->document) {
1185 2 : ctx->document = gf_sg_new();
1186 : /*mark this doc as "nomade", and let it leave until all references to it are destroyed*/
1187 2 : ctx->document->reference_count = 1;
1188 2 : xml_http_load_dom(ctx);
1189 : }
1190 4 : if (!ctx->js_dom_loaded) {
1191 2 : dom_js_load(ctx->document, c);
1192 2 : ctx->js_dom_loaded = GF_TRUE;
1193 : }
1194 4 : return dom_document_construct_external(c, ctx->document);
1195 : }
1196 : return JS_NULL;
1197 : #else
1198 : return js_throw_err_msg(c, GF_NOT_SUPPORTED, "DOM support not included in buil");
1199 : #endif
1200 0 : case XHR_RESPONSETYPE_JSON:
1201 0 : return JS_ParseJSON(c, ctx->data, ctx->size, "responseJSON");
1202 0 : case XHR_RESPONSETYPE_PUSH:
1203 0 : return JS_EXCEPTION;
1204 : default:
1205 : /*other types not supported */
1206 : break;
1207 : }
1208 0 : }
1209 1 : return JS_NULL;
1210 :
1211 2 : case XHR_STATUS:
1212 2 : return JS_NewInt32(c, ctx->html_status);
1213 :
1214 0 : case XHR_STATUSTEXT:
1215 0 : if (ctx->statusText) {
1216 0 : return JS_NewString(c, ctx->statusText);
1217 : }
1218 0 : return JS_NULL;
1219 0 : case XHR_TIMEOUT:
1220 0 : return JS_NewInt32(c, ctx->timeout);
1221 0 : case XHR_WITHCREDENTIALS:
1222 0 : return ctx->withCredentials ? JS_TRUE : JS_FALSE;
1223 0 : case XHR_UPLOAD:
1224 : /* TODO */
1225 0 : return JS_EXCEPTION;
1226 0 : case XHR_RESPONSETYPE:
1227 0 : switch (ctx->responseType) {
1228 0 : case XHR_RESPONSETYPE_NONE: return JS_NewString(c, "");
1229 0 : case XHR_RESPONSETYPE_ARRAYBUFFER: return JS_NewString(c, "arraybuffer");
1230 0 : case XHR_RESPONSETYPE_BLOB: return JS_NewString(c, "blob");
1231 0 : case XHR_RESPONSETYPE_DOCUMENT: return JS_NewString(c, "document");
1232 0 : case XHR_RESPONSETYPE_SAX: return JS_NewString(c, "sax");
1233 0 : case XHR_RESPONSETYPE_JSON: return JS_NewString(c, "json");
1234 0 : case XHR_RESPONSETYPE_TEXT: return JS_NewString(c, "text");
1235 0 : case XHR_RESPONSETYPE_PUSH: return JS_NewString(c, "push");
1236 : }
1237 0 : return JS_NULL;
1238 : case XHR_STATIC_UNSENT:
1239 : return JS_NewInt32(c, XHR_READYSTATE_UNSENT);
1240 : case XHR_STATIC_OPENED:
1241 : return JS_NewInt32(c, XHR_READYSTATE_OPENED);
1242 : case XHR_STATIC_HEADERS_RECEIVED:
1243 : return JS_NewInt32(c, XHR_READYSTATE_HEADERS_RECEIVED);
1244 : case XHR_STATIC_LOADING:
1245 : return JS_NewInt32(c, XHR_READYSTATE_LOADING);
1246 : case XHR_STATIC_DONE:
1247 : return JS_NewInt32(c, XHR_READYSTATE_DONE);
1248 0 : case XHR_CACHE:
1249 0 : switch (ctx->cache) {
1250 0 : case XHR_CACHETYPE_NORMAL: return JS_NewString(c, "normal");
1251 0 : case XHR_CACHETYPE_NONE: return JS_NewString(c, "none");
1252 0 : case XHR_CACHETYPE_MEMORY: return JS_NewString(c, "memory");
1253 : }
1254 0 : return JS_NULL;
1255 : default:
1256 : break;
1257 : }
1258 0 : return JS_UNDEFINED;
1259 : }
1260 :
1261 17 : static JSValue xml_http_setProperty(JSContext *c, JSValueConst obj, JSValueConst value, int magic)
1262 : {
1263 17 : XMLHTTPContext *ctx = JS_GetOpaque(obj, xhrClass.class_id);
1264 17 : if (!ctx) return JS_EXCEPTION;
1265 :
1266 : #define SET_CBK(_sym) \
1267 : if (JS_IsFunction(c, value) || JS_IsUndefined(value) || JS_IsNull(value)) {\
1268 : JS_FreeValue(c, ctx->_sym);\
1269 : ctx->_sym = JS_DupValue(c, value);\
1270 : return JS_TRUE;\
1271 : }\
1272 : return JS_EXCEPTION;\
1273 :
1274 17 : switch (magic) {
1275 4 : case XHR_ONERROR:
1276 8 : SET_CBK(onerror)
1277 :
1278 0 : case XHR_ONABORT:
1279 0 : SET_CBK(onabort)
1280 :
1281 4 : case XHR_ONLOAD:
1282 8 : SET_CBK(onload)
1283 :
1284 0 : case XHR_ONLOADSTART:
1285 0 : SET_CBK(onloadstart)
1286 :
1287 0 : case XHR_ONLOADEND:
1288 0 : SET_CBK(onloadend)
1289 :
1290 0 : case XHR_ONPROGRESS:
1291 0 : SET_CBK(onprogress)
1292 :
1293 4 : case XHR_ONREADYSTATECHANGE:
1294 8 : SET_CBK(onreadystatechange)
1295 :
1296 0 : case XHR_ONTIMEOUT:
1297 0 : SET_CBK(ontimeout)
1298 :
1299 0 : case XHR_TIMEOUT:
1300 0 : if (JS_ToInt32(c, &ctx->timeout, value)) return JS_EXCEPTION;
1301 0 : return JS_TRUE;
1302 :
1303 0 : case XHR_WITHCREDENTIALS:
1304 0 : ctx->withCredentials = JS_ToBool(c, value) ? GF_TRUE : GF_FALSE;
1305 0 : return JS_TRUE;
1306 5 : case XHR_RESPONSETYPE:
1307 : {
1308 : const char *str = JS_ToCString(c, value);
1309 5 : if (!str || !strcmp(str, "")) {
1310 0 : ctx->responseType = XHR_RESPONSETYPE_NONE;
1311 5 : } else if (!strcmp(str, "arraybuffer")) {
1312 0 : ctx->responseType = XHR_RESPONSETYPE_ARRAYBUFFER;
1313 5 : } else if (!strcmp(str, "blob")) {
1314 0 : ctx->responseType = XHR_RESPONSETYPE_BLOB;
1315 5 : } else if (!strcmp(str, "document")) {
1316 4 : ctx->responseType = XHR_RESPONSETYPE_DOCUMENT;
1317 1 : } else if (!strcmp(str, "json")) {
1318 0 : ctx->responseType = XHR_RESPONSETYPE_JSON;
1319 1 : } else if (!strcmp(str, "text")) {
1320 0 : ctx->responseType = XHR_RESPONSETYPE_TEXT;
1321 1 : } else if (!strcmp(str, "sax")) {
1322 1 : ctx->responseType = XHR_RESPONSETYPE_SAX;
1323 0 : } else if (!strcmp(str, "push")) {
1324 0 : ctx->responseType = XHR_RESPONSETYPE_PUSH;
1325 : }
1326 5 : JS_FreeCString(c, str);
1327 5 : return JS_TRUE;
1328 : }
1329 0 : case XHR_CACHE:
1330 : {
1331 : const char *str = JS_ToCString(c, value);
1332 0 : if (!str) return JS_EXCEPTION;
1333 0 : if (!strcmp(str, "normal")) {
1334 0 : ctx->cache = XHR_CACHETYPE_NORMAL;
1335 0 : } else if (!strcmp(str, "none")) {
1336 0 : ctx->cache = XHR_CACHETYPE_NONE;
1337 0 : } else if (!strcmp(str, "memory")) {
1338 0 : ctx->cache = XHR_CACHETYPE_MEMORY;
1339 : }
1340 0 : JS_FreeCString(c, str);
1341 0 : return JS_TRUE;
1342 : }
1343 : /*all other properties are read-only*/
1344 : default:
1345 : break;
1346 : }
1347 0 : return JS_FALSE;
1348 : }
1349 :
1350 : static const JSCFunctionListEntry xhr_Funcs[] =
1351 : {
1352 : JS_CGETSET_MAGIC_DEF("onabort", xml_http_getProperty, xml_http_setProperty, XHR_ONABORT),
1353 : JS_CGETSET_MAGIC_DEF("onerror", xml_http_getProperty, xml_http_setProperty, XHR_ONERROR),
1354 : JS_CGETSET_MAGIC_DEF("onload", xml_http_getProperty, xml_http_setProperty, XHR_ONLOAD),
1355 : JS_CGETSET_MAGIC_DEF("onloadend", xml_http_getProperty, xml_http_setProperty, XHR_ONLOADEND),
1356 : JS_CGETSET_MAGIC_DEF("onloadstart", xml_http_getProperty, xml_http_setProperty, XHR_ONLOADSTART),
1357 : JS_CGETSET_MAGIC_DEF("onprogress", xml_http_getProperty, xml_http_setProperty, XHR_ONPROGRESS),
1358 : JS_CGETSET_MAGIC_DEF("onreadystatechange", xml_http_getProperty, xml_http_setProperty, XHR_ONREADYSTATECHANGE),
1359 : JS_CGETSET_MAGIC_DEF("ontimeout", xml_http_getProperty, xml_http_setProperty, XHR_ONTIMEOUT),
1360 : JS_CGETSET_MAGIC_DEF("readyState", xml_http_getProperty, NULL, XHR_READYSTATE),
1361 : JS_CGETSET_MAGIC_DEF("response", xml_http_getProperty, NULL, XHR_RESPONSE),
1362 : JS_CGETSET_MAGIC_DEF("responseType", xml_http_getProperty, xml_http_setProperty, XHR_RESPONSETYPE),
1363 : JS_CGETSET_MAGIC_DEF("responseText", xml_http_getProperty, NULL, XHR_RESPONSETEXT),
1364 : JS_CGETSET_MAGIC_DEF("responseXML", xml_http_getProperty, NULL, XHR_RESPONSEXML),
1365 : JS_CGETSET_MAGIC_DEF("status", xml_http_getProperty, NULL, XHR_STATUS),
1366 : JS_CGETSET_MAGIC_DEF("statusText", xml_http_getProperty, NULL, XHR_STATUSTEXT),
1367 : JS_CGETSET_MAGIC_DEF("timeout", xml_http_getProperty, xml_http_setProperty, XHR_TIMEOUT),
1368 : JS_CGETSET_MAGIC_DEF("upload", xml_http_getProperty, NULL, XHR_UPLOAD),
1369 : JS_CGETSET_MAGIC_DEF("withCredentials", xml_http_getProperty, xml_http_setProperty, XHR_WITHCREDENTIALS),
1370 : JS_CGETSET_MAGIC_DEF("cache", xml_http_getProperty, xml_http_setProperty, XHR_CACHE),
1371 :
1372 : JS_CFUNC_DEF("abort", 0, xml_http_abort),
1373 : JS_CFUNC_DEF("getAllResponseHeaders", 0, xml_http_get_all_headers),
1374 : JS_CFUNC_DEF("getResponseHeader", 1, xml_http_get_header),
1375 : JS_CFUNC_DEF("open", 2, xml_http_open),
1376 : JS_CFUNC_DEF("overrideMimeType", 1, xml_http_overrideMimeType),
1377 : JS_CFUNC_DEF("send", 0, xml_http_send),
1378 : JS_CFUNC_DEF("setRequestHeader", 2, xml_http_set_header),
1379 :
1380 : /*eventTarget interface*/
1381 : JS_DOM3_EVENT_TARGET_INTERFACE
1382 : };
1383 :
1384 133412 : static void xml_http_gc_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
1385 : {
1386 133412 : XMLHTTPContext *ctx = JS_GetOpaque(val, xhrClass.class_id);
1387 133412 : if (!ctx) return;
1388 :
1389 4 : JS_MarkValue(rt, ctx->onabort, mark_func);
1390 4 : JS_MarkValue(rt, ctx->onerror, mark_func);
1391 4 : JS_MarkValue(rt, ctx->onload, mark_func);
1392 4 : JS_MarkValue(rt, ctx->onloadend, mark_func);
1393 4 : JS_MarkValue(rt, ctx->onloadstart, mark_func);
1394 4 : JS_MarkValue(rt, ctx->onprogress, mark_func);
1395 4 : JS_MarkValue(rt, ctx->onreadystatechange, mark_func);
1396 4 : JS_MarkValue(rt, ctx->ontimeout, mark_func);
1397 : }
1398 :
1399 411 : static JSValue xhr_load_class(JSContext *c)
1400 : {
1401 411 : if (! xhrClass.class_id) {
1402 48 : JS_NewClassID(&xhrClass.class_id);
1403 48 : xhrClass.class.class_name = "XMLHttpRequest";
1404 48 : xhrClass.class.finalizer = xml_http_finalize;
1405 48 : xhrClass.class.gc_mark = xml_http_gc_mark;
1406 48 : JS_NewClass(JS_GetRuntime(c), xhrClass.class_id, &xhrClass.class);
1407 : }
1408 411 : JSValue proto = JS_NewObjectClass(c, xhrClass.class_id);
1409 411 : JS_SetPropertyFunctionList(c, proto, xhr_Funcs, countof(xhr_Funcs));
1410 411 : JS_SetClassProto(c, xhrClass.class_id, proto);
1411 411 : JS_SetPropertyStr(c, proto, "UNSENT", JS_NewInt32(c, XHR_STATIC_UNSENT));
1412 411 : JS_SetPropertyStr(c, proto, "OPENED", JS_NewInt32(c, XHR_STATIC_OPENED));
1413 411 : JS_SetPropertyStr(c, proto, "HEADERS_RECEIVED", JS_NewInt32(c, XHR_STATIC_HEADERS_RECEIVED));
1414 411 : JS_SetPropertyStr(c, proto, "LOADING", JS_NewInt32(c, XHR_STATIC_LOADING));
1415 411 : JS_SetPropertyStr(c, proto, "DONE", JS_NewInt32(c, XHR_STATIC_DONE));
1416 :
1417 411 : return JS_NewCFunction2(c, xml_http_constructor, "XMLHttpRequest", 1, JS_CFUNC_constructor, 0);
1418 : }
1419 :
1420 407 : void qjs_module_init_xhr_global(JSContext *c, JSValue global)
1421 : {
1422 407 : if (c) {
1423 407 : JSValue ctor = xhr_load_class(c);
1424 407 : JS_SetPropertyStr(c, global, "XMLHttpRequest", ctor);
1425 : }
1426 407 : }
1427 :
1428 4 : static int js_xhr_load_module(JSContext *c, JSModuleDef *m)
1429 : {
1430 4 : JSValue global = JS_GetGlobalObject(c);
1431 4 : JSValue ctor = xhr_load_class(c);
1432 : JS_FreeValue(c, global);
1433 4 : JS_SetModuleExport(c, m, "XMLHttpRequest", ctor);
1434 4 : return 0;
1435 : }
1436 64 : void qjs_module_init_xhr(JSContext *ctx)
1437 : {
1438 : JSModuleDef *m;
1439 64 : m = JS_NewCModule(ctx, "xhr", js_xhr_load_module);
1440 64 : if (!m) return;
1441 :
1442 64 : JS_AddModuleExport(ctx, m, "XMLHttpRequest");
1443 :
1444 : #ifdef GPAC_ENABLE_COVERAGE
1445 64 : if (gf_sys_is_cov_mode()) {
1446 : qjs_module_init_xhr_global(NULL, JS_TRUE);
1447 : xhr_get_event_target(NULL, JS_TRUE, NULL, NULL);
1448 : }
1449 : #endif
1450 : return;
1451 : }
1452 :
1453 :
1454 : #endif
1455 :
|