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