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