1 /**
2 This module implements the XMLHttpRequest class.
3 
4 ────────────────────────────────────────────────────────────────────────────────
5 
6 Copyright (C) 2021 pillager86.rf.gd
7 
8 This program is free software: you can redistribute it and/or modify it under 
9 the terms of the GNU General Public License as published by the Free Software 
10 Foundation, either version 3 of the License, or (at your option) any later 
11 version.
12 
13 This program is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15 PARTICULAR PURPOSE.  See the GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License along with 
18 this program.  If not, see <https://www.gnu.org/licenses/>.
19 */
20 module mildew.stdlib.xmlhttprequest;
21 
22 import std.concurrency;
23 import std.conv: to;
24 import std.net.curl;
25 debug import std.stdio;
26 import std..string: indexOf;
27 import std.typecons;
28 import std.uni: toLower;
29 
30 import mildew.environment;
31 import mildew.exceptions;
32 import mildew.interpreter;
33 import mildew.stdlib.buffers;
34 import mildew.stdlib.json;
35 import mildew.types;
36 import mildew.vm;
37 
38 /**
39  * Initializes the XMLHttpRequest class library.
40  * Params:
41  *   interpreter = The Interpreter instance to store the XMLHttpRequest constructor in
42  */
43 void initializeXMLHttpRequestLibrary(Interpreter interpreter)
44 {
45     auto ctor = new ScriptFunction("XMLHttpRequest", &native_XMLHttpRequest_ctor, true);
46 
47     ctor["prototype"] = getXMLHttpRequestPrototype();
48     ctor["prototype"]["constructor"] = ctor;
49 
50     interpreter.forceSetGlobal("XMLHttpRequest", ctor, false);
51 }
52 
53 private class ScriptXMLHttpRequest
54 {
55     enum ReadyState : ushort {
56         UNSENT = 0,
57         OPENED = 1,
58         HEADERS_RECEIVED = 2,
59         LOADING = 3,
60         DONE = 4
61     }
62 
63     enum EventType {
64         LOADSTART,
65         PROGRESS,
66         ABORT,
67         ERROR,
68         LOAD,
69         TIMEOUT,
70         LOADEND,
71 
72         RS_CHANGE
73     }
74 
75     alias Event = Tuple!(EventType, ScriptAny);
76 
77     void checkResponseType()
78     {
79         try 
80         {
81             if(_readyState == ReadyState.DONE && "content-type" in _responseHeaders)
82             {
83                 immutable mimeType = _overriddenMimeType != "" ? _overriddenMimeType : _responseHeaders["content-type"];
84                 if(indexOf(mimeType, "json") != -1)
85                 {
86                     _response = JSONReader.consumeValue(cast(string)_responseText);
87                 }
88                 else if(indexOf(mimeType, "text/") != - 1)
89                 {
90                     _response = ScriptAny(cast(string)_responseText);
91                 }
92                 else
93                 {
94                     _response = ScriptAny(
95                         new ScriptObject("ArrayBuffer",
96                             getArrayBufferPrototype(), 
97                             new ScriptArrayBuffer(_responseText)));
98                 }
99             }
100         } catch(Exception ex)
101         {
102             throw new ScriptRuntimeException(ex.msg);
103         }       
104     }
105 
106 private:
107     HTTP _http;
108     bool _async;
109     bool _abort = false;
110     bool _done = false;
111     bool _started = false;
112     long _timeout = 0;
113     string _overriddenMimeType = "";
114 
115     ReadyState _readyState = ReadyState.UNSENT;
116     ScriptFunction _onReadyStateChange;
117 
118     // event
119     ScriptFunction _onLoadStart;
120     ScriptFunction _onProgress;
121     ScriptFunction _onAbort;
122     ScriptFunction _onError;
123     ScriptFunction _onLoad;
124     ScriptFunction _onTimeout;
125     ScriptFunction _onLoadEnd;
126     Event[] _eventQueue; // for async
127 
128     // response
129     Exception _exception;
130     ushort _status;
131     string[string] _responseHeaders;
132     ScriptAny _response;
133     ubyte[] _responseText; // just a string for now
134 }
135 
136 private ScriptObject _XMLHttpRequestPrototype;
137 
138 private ScriptObject getXMLHttpRequestPrototype()
139 {
140     if(_XMLHttpRequestPrototype is null)
141     {
142         _XMLHttpRequestPrototype = new ScriptObject("XMLHttpRequest", null);
143         _XMLHttpRequestPrototype["abort"] = new ScriptFunction("XMLHttpRequest.prototype.abort",
144                 &native_XMLHttpRequest_abort);
145         _XMLHttpRequestPrototype["getAllResponseHeaders"] = new ScriptFunction(
146                 "XMLHttpRequest.prototype.getAllResponseHeaders", 
147                 &native_XMLHttpRequest_getAllResponseHeaders);
148         _XMLHttpRequestPrototype["getResponseHeader"] = new ScriptFunction(
149                 "XMLHttpRequest.prototype.getResponseHeader",
150                 &native_XMLHttpRequest_getResponseHeader);
151         _XMLHttpRequestPrototype.addGetterProperty("onabort", new ScriptFunction(
152                 "XMLHttpRequest.prototype.onabort",
153                 &native_XMLHttpRequest_p_onabort));
154         _XMLHttpRequestPrototype.addSetterProperty("onabort", new ScriptFunction(
155                 "XMLHttpRequest.prototype.onabort",
156                 &native_XMLHttpRequest_p_onabort));
157         _XMLHttpRequestPrototype.addGetterProperty("onerror", new ScriptFunction(
158                 "XMLHttpRequest.prototype.onerror",
159                 &native_XMLHttpRequest_p_onerror));
160         _XMLHttpRequestPrototype.addSetterProperty("onerror", new ScriptFunction(
161                 "XMLHttpRequest.prototype.onerror",
162                 &native_XMLHttpRequest_p_onerror));
163         _XMLHttpRequestPrototype.addGetterProperty("onload", new ScriptFunction(
164                 "XMLHttpRequest.prototype.onload",
165                 &native_XMLHttpRequest_p_onload));
166         _XMLHttpRequestPrototype.addSetterProperty("onload", new ScriptFunction(
167                 "XMLHttpRequest.prototype.onload",
168                 &native_XMLHttpRequest_p_onload));
169         _XMLHttpRequestPrototype.addGetterProperty("onloadend", new ScriptFunction(
170                 "XMLHttpRequest.prototype.onloadend",
171                 &native_XMLHttpRequest_p_onloadend));
172         _XMLHttpRequestPrototype.addSetterProperty("onloadend", new ScriptFunction(
173                 "XMLHttpRequest.prototype.onloadend",
174                 &native_XMLHttpRequest_p_onloadend));
175         _XMLHttpRequestPrototype.addGetterProperty("onloadstart", new ScriptFunction(
176                 "XMLHttpRequest.prototype.onloadstart",
177                 &native_XMLHttpRequest_p_onloadstart));
178         _XMLHttpRequestPrototype.addSetterProperty("onloadstart", new ScriptFunction(
179                 "XMLHttpRequest.prototype.onloadstart",
180                 &native_XMLHttpRequest_p_onloadstart));
181         _XMLHttpRequestPrototype.addGetterProperty("onprogress", new ScriptFunction(
182                 "XMLHttpRequest.prototype.onprogress",
183                 &native_XMLHttpRequest_p_onprogress));
184         _XMLHttpRequestPrototype.addSetterProperty("onprogress", new ScriptFunction(
185                 "XMLHttpRequest.prototype.onprogress",
186                 &native_XMLHttpRequest_p_onprogress));
187         _XMLHttpRequestPrototype.addGetterProperty("onreadystatechange", new ScriptFunction(
188                 "XMLHttpRequest.prototype.onreadystatechange",
189                 &native_XMLHttpRequest_p_onreadystatechange));
190         _XMLHttpRequestPrototype.addSetterProperty("onreadystatechange", new ScriptFunction(
191                 "XMLHttpRequest.prototype.onreadystatechange",
192                 &native_XMLHttpRequest_p_onreadystatechange));
193         _XMLHttpRequestPrototype.addGetterProperty("ontimeout", new ScriptFunction(
194                 "XMLHttpRequest.prototype.ontimeout",
195                 &native_XMLHttpRequest_p_ontimeout));
196         _XMLHttpRequestPrototype.addSetterProperty("ontimeout", new ScriptFunction(
197                 "XMLHttpRequest.prototype.ontimeout",
198                 &native_XMLHttpRequest_p_ontimeout));
199         _XMLHttpRequestPrototype["open"] = new ScriptFunction("XMLHttpRequest.prototype.open",
200                 &native_XMLHttpRequest_open);
201         _XMLHttpRequestPrototype["overrideMimeType"] = new ScriptFunction("XMLHttpRequest.prototype.overrideMimeType",
202                 &native_XMLHttpRequest_overrideMimeType);
203         _XMLHttpRequestPrototype.addGetterProperty("readyState", new ScriptFunction(
204                 "XMLHttpRequest.prototype.readyState",
205                 &native_XMLHttpRequest_p_readyState));                
206         _XMLHttpRequestPrototype.addGetterProperty("response", new ScriptFunction(
207                 "XMLHttpRequest.prototype.response",
208                 &native_XMLHttpRequest_p_response));
209         _XMLHttpRequestPrototype.addGetterProperty("responseText", new ScriptFunction(
210                 "XMLHttpRequest.prototype.responseText",
211                 &native_XMLHttpRequest_p_responseText));
212         _XMLHttpRequestPrototype["send"] = new ScriptFunction("XMLHttpRequest.prototype.send",
213                 &native_XMLHttpRequest_send);
214         _XMLHttpRequestPrototype["setRequestHeader"] = new ScriptFunction(
215                 "XMLHttpRequest.prototype.setRequestHeader",
216                 &native_XMLHttpRequest_setRequestHeader);
217         _XMLHttpRequestPrototype.addGetterProperty("status", new ScriptFunction(
218                 "XMLHttpRequest.prototype.status",
219                 &native_XMLHttpRequest_p_status));
220         _XMLHttpRequestPrototype.addGetterProperty("timeout", new ScriptFunction(
221                 "XMLHttpRequest.prototype.timeout",
222                 &native_XMLHttpRequest_p_timeout));
223         _XMLHttpRequestPrototype.addSetterProperty("timeout", new ScriptFunction(
224                 "XMLHttpRequest.prototype.timeout",
225                 &native_XMLHttpRequest_p_timeout));
226     }
227     return _XMLHttpRequestPrototype;
228 }
229 
230 private ScriptAny native_XMLHttpRequest_ctor(Environment env, ScriptAny* thisObj,
231                                              ScriptAny[] args, ref NativeFunctionError nfe)
232 {
233     if(!thisObj.isObject)
234         throw new ScriptRuntimeException("XMLHttpRequest must be called with new");
235     
236     auto http = new ScriptXMLHttpRequest();
237     (cast(ScriptObject)*thisObj).nativeObject = http;
238     return ScriptAny.UNDEFINED;
239 }
240 
241 private ScriptAny native_XMLHttpRequest_abort(Environment env, ScriptAny* thisObj,
242                                               ScriptAny[] args, ref NativeFunctionError nfe)
243 {
244     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
245     if(req is null)
246         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
247     req._status = 0;
248     req._abort = true;
249     req._readyState = ScriptXMLHttpRequest.ReadyState.UNSENT;
250     return ScriptAny.UNDEFINED;
251 }
252 
253 private ScriptAny native_XMLHttpRequest_getAllResponseHeaders(Environment env, ScriptAny* thisObj,
254                                                             ScriptAny[] args, ref NativeFunctionError nfe)
255 {
256     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
257     if(req is null)
258         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
259     auto headers = "";
260     foreach(key, value ; req._responseHeaders)
261     {
262         headers ~= key ~ ": " ~ value ~ "\n";
263     }
264     return ScriptAny(headers);
265 }
266 
267 private ScriptAny native_XMLHttpRequest_getResponseHeader(Environment env, ScriptAny* thisObj,
268                                                           ScriptAny[] args, ref NativeFunctionError nfe)
269 {
270     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
271     if(req is null)
272         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
273     if(args.length < 1)
274     {
275         nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
276         return ScriptAny.UNDEFINED;
277     }
278     auto key = toLower(args[0].toString());
279     if(key in req._responseHeaders)
280         return ScriptAny(req._responseHeaders[key]);
281     return ScriptAny.UNDEFINED;
282 }
283 
284 private ScriptAny native_XMLHttpRequest_p_onabort(Environment env, ScriptAny* thisObj,
285                                                      ScriptAny[] args, ref NativeFunctionError nfe)
286 {
287     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
288     if(req is null)
289         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
290     if(args.length < 1)
291     {
292         if(req._onAbort)
293             return ScriptAny(req._onAbort);
294         else
295             return ScriptAny(null);
296     }
297     if(args[0].type != ScriptAny.Type.FUNCTION)
298     {
299         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
300         return ScriptAny.UNDEFINED;
301     }
302     req._onAbort = args[0].toValue!ScriptFunction;
303     return args[0];
304 }
305 
306 private ScriptAny native_XMLHttpRequest_p_onerror(Environment env, ScriptAny* thisObj,
307                                                      ScriptAny[] args, ref NativeFunctionError nfe)
308 {
309     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
310     if(req is null)
311         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
312     if(args.length < 1)
313     {
314         if(req._onError)
315             return ScriptAny(req._onError);
316         else
317             return ScriptAny(null);
318     }
319     if(args[0].type != ScriptAny.Type.FUNCTION)
320     {
321         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
322         return ScriptAny.UNDEFINED;
323     }
324     req._onError = args[0].toValue!ScriptFunction;
325     return args[0];
326 }
327 
328 private ScriptAny native_XMLHttpRequest_p_onload(Environment env, ScriptAny* thisObj,
329                                                      ScriptAny[] args, ref NativeFunctionError nfe)
330 {
331     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
332     if(req is null)
333         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
334     if(args.length < 1)
335     {
336         if(req._onLoad)
337             return ScriptAny(req._onLoad);
338         else
339             return ScriptAny(null);
340     }
341     if(args[0].type != ScriptAny.Type.FUNCTION)
342     {
343         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
344         return ScriptAny.UNDEFINED;
345     }
346     req._onLoad = args[0].toValue!ScriptFunction;
347     return args[0];
348 }
349 
350 private ScriptAny native_XMLHttpRequest_p_onloadend(Environment env, ScriptAny* thisObj,
351                                                      ScriptAny[] args, ref NativeFunctionError nfe)
352 {
353     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
354     if(req is null)
355         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
356     if(args.length < 1)
357     {
358         if(req._onLoadEnd)
359             return ScriptAny(req._onLoadEnd);
360         else
361             return ScriptAny(null);
362     }
363     if(args[0].type != ScriptAny.Type.FUNCTION)
364     {
365         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
366         return ScriptAny.UNDEFINED;
367     }
368     req._onLoadEnd = args[0].toValue!ScriptFunction;
369     return args[0];
370 }
371 
372 private ScriptAny native_XMLHttpRequest_p_onloadstart(Environment env, ScriptAny* thisObj,
373                                                      ScriptAny[] args, ref NativeFunctionError nfe)
374 {
375     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
376     if(req is null)
377         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
378     if(args.length < 1)
379     {
380         if(req._onLoadStart)
381             return ScriptAny(req._onLoadStart);
382         else
383             return ScriptAny(null);
384     }
385     if(args[0].type != ScriptAny.Type.FUNCTION)
386     {
387         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
388         return ScriptAny.UNDEFINED;
389     }
390     req._onLoadStart = args[0].toValue!ScriptFunction;
391     return args[0];
392 }
393 
394 private ScriptAny native_XMLHttpRequest_p_onprogress(Environment env, ScriptAny* thisObj,
395                                                      ScriptAny[] args, ref NativeFunctionError nfe)
396 {
397     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
398     if(req is null)
399         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
400     if(args.length < 1)
401     {
402         if(req._onProgress)
403             return ScriptAny(req._onProgress);
404         else
405             return ScriptAny(null);
406     }
407     if(args[0].type != ScriptAny.Type.FUNCTION)
408     {
409         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
410         return ScriptAny.UNDEFINED;
411     }
412     req._onProgress = args[0].toValue!ScriptFunction;
413     return args[0];
414 }
415 
416 private ScriptAny native_XMLHttpRequest_p_onreadystatechange(Environment env,
417                                                              ScriptAny* thisObj,
418                                                              ScriptAny[] args,
419                                                              ref NativeFunctionError nfe)
420 {
421     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
422     if(req is null)
423         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
424     if(args.length < 1)
425     {
426         if(req._onReadyStateChange)
427             return ScriptAny(req._onReadyStateChange);
428         else
429             return ScriptAny(null);
430     }
431     if(args[0].type != ScriptAny.Type.FUNCTION)
432     {
433         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
434         return ScriptAny.UNDEFINED;
435     }
436     req._onReadyStateChange = args[0].toValue!ScriptFunction;
437     return args[0];
438 }
439 
440 private ScriptAny native_XMLHttpRequest_p_ontimeout(Environment env,
441                                                     ScriptAny* thisObj,
442                                                     ScriptAny[] args,
443                                                     ref NativeFunctionError nfe)
444 {
445     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
446     if(req is null)
447         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
448     if(args.length < 1)
449     {
450         if(req._onTimeout)
451             return ScriptAny(req._onTimeout);
452         else
453             return ScriptAny(null);
454     }
455     if(args[0].type != ScriptAny.Type.FUNCTION)
456     {
457         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
458         return ScriptAny.UNDEFINED;
459     }
460     req._onTimeout = args[0].toValue!ScriptFunction;
461     return args[0];
462 }
463 
464 private ScriptAny native_XMLHttpRequest_open(Environment env, ScriptAny* thisObj,
465                                              ScriptAny[] args, ref NativeFunctionError nfe)
466 {
467     import std.uni: toUpper;
468     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
469     if(req is null)
470         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
471 
472     auto vm = env.g.interpreter.vm;
473     
474     if(args.length < 2)
475     {
476         nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
477         return ScriptAny.UNDEFINED;
478     }
479 
480     auto method = args[0].toString();
481     auto url = args[1].toString();
482     req._http = HTTP(url);
483     req._done = false;
484     req._abort = false;
485     switch(method.toUpper)
486     {
487     case "GET":
488         req._http.method = HTTP.Method.get;
489         break;
490     case "POST":
491         req._http.method = HTTP.Method.post;
492         break;
493     case "PUT":
494         req._http.method = HTTP.Method.put;
495         break;
496     case "DELETE":
497         req._http.method = HTTP.Method.del;
498         break;
499     case "HEAD":
500         req._http.method = HTTP.Method.head;
501         break;
502     case "PATCH":
503         req._http.method = HTTP.Method.patch;
504         break;
505     case "OPTIONS":
506         req._http.method = HTTP.Method.options;
507         break;
508     case "TRACE":
509         req._http.method = HTTP.Method.trace;
510         break;
511     case "CONNECT":
512         req._http.method = HTTP.Method.connect;
513         break;
514     default:
515         throw new ScriptRuntimeException("Invalid HTTP method specified");
516     }
517 
518     req._async = args.length > 2 ? cast(bool)args[2] : true;
519     string user = args.length > 3 ? args[3].toString() : null;
520     string pass = args.length > 4 ? args[4].toString() : null;
521 
522     if(user)
523         req._http.addRequestHeader("user", user);
524     if(pass)
525         req._http.addRequestHeader("password", pass);
526 
527     // TODO all events
528     req._http.onReceiveStatusLine = (HTTP.StatusLine line) {
529         synchronized(req)
530         {
531             req._status = line.code;
532         }
533     };
534 
535     req._http.onReceive = (ubyte[] data) {
536         import core.thread: Thread;
537 
538         if(req._responseText.length == 0)
539         {
540             if(!req._async)
541             {
542                 req._readyState = ScriptXMLHttpRequest.ReadyState.HEADERS_RECEIVED;
543                 if(req._onReadyStateChange)
544                     vm.runFunction(req._onReadyStateChange, *thisObj, []);
545             }
546             else
547             {
548                 synchronized(req)
549                 {
550                     req._readyState = ScriptXMLHttpRequest.ReadyState.HEADERS_RECEIVED;
551                     req._eventQueue ~= tuple(ScriptXMLHttpRequest.EventType.RS_CHANGE, 
552                             ScriptAny(ScriptXMLHttpRequest.ReadyState.HEADERS_RECEIVED));
553                 }
554             }
555         }
556         req._responseText ~= data;
557         if(req._readyState == ScriptXMLHttpRequest.ReadyState.HEADERS_RECEIVED)
558         {
559             req._readyState = ScriptXMLHttpRequest.ReadyState.LOADING;
560             if(!req._async)
561             {
562                 if(req._onReadyStateChange)
563                     vm.runFunction(req._onReadyStateChange, *thisObj, []);
564             }
565             else
566             {
567                 synchronized(req)
568                 {
569                     req._eventQueue ~= tuple(ScriptXMLHttpRequest.EventType.RS_CHANGE, 
570                         ScriptAny(ScriptXMLHttpRequest.ReadyState.LOADING));
571                 }
572             }
573         }
574 
575         return data.length;
576     };
577 
578     req._http.onProgress = (size_t dlTotal, size_t dlNow, size_t ulTotal, size_t ulNow) {
579         synchronized(req)
580         {
581             ScriptObject event = new ScriptObject(
582                 "ProgressEvent", 
583                 null);
584             event["loaded"] = ScriptAny(dlNow);
585             event["total"] = ScriptAny(dlTotal);
586             event["lengthComputable"] = dlTotal == 0 ? ScriptAny(false) : ScriptAny(true);
587             if(!req._async)
588             {
589                 if(req._onLoadStart && req._started)
590                 {
591                     req._started = false;
592                     vm.runFunction(req._onLoadStart, *thisObj, [ScriptAny(event)]);
593                 }
594 
595                 if(req._abort)
596                 {
597                     if(req._onAbort)
598                         vm.runFunction(req._onAbort, *thisObj, [ScriptAny(event)]);
599                 }
600                 else if(req._onProgress)
601                     vm.runFunction(req._onProgress, *thisObj, [ScriptAny(event)]);
602             }
603             else
604             {
605                 if(req._started)
606                 {
607                     req._started = false;
608                     req._eventQueue ~= tuple(ScriptXMLHttpRequest.EventType.LOADSTART,
609                         ScriptAny(event));
610                 }
611                 if(req._abort)
612                 {
613                     req._eventQueue ~= tuple(ScriptXMLHttpRequest.EventType.ABORT,
614                         ScriptAny(event));
615                 }
616                 else req._eventQueue ~= tuple(ScriptXMLHttpRequest.EventType.PROGRESS,
617                     ScriptAny(event));
618             }
619 
620             if(req._abort)
621             {    
622                 return HTTP.requestAbort;
623             }
624         }
625         return 0;
626     };
627 
628     req._http.onReceiveHeader = (in char[] key, in char[] value) {
629         synchronized(req) 
630         {
631             req._responseHeaders[toLower(key)] = value.idup;
632         }
633     };
634 
635     req._http.onSend = (void[] data) { // this is never called even with send params
636         if(req._abort)
637         {
638             return HTTP.requestAbort;
639         }
640         return data.length;
641     };
642 
643     req._readyState = ScriptXMLHttpRequest.ReadyState.OPENED;
644     if(!req._async)
645     {
646         if(req._onReadyStateChange)
647             vm.runFunction(req._onReadyStateChange, *thisObj, []);
648     }
649     else
650     {
651         synchronized(req)
652         {
653             req._eventQueue ~= tuple(ScriptXMLHttpRequest.EventType.RS_CHANGE, 
654                 ScriptAny(ScriptXMLHttpRequest.ReadyState.OPENED));
655         }
656     }
657 
658     return ScriptAny.UNDEFINED;
659 }
660 
661 private ScriptAny native_XMLHttpRequest_overrideMimeType(Environment env, ScriptAny* thisObj,
662                                                          ScriptAny[] args, ref NativeFunctionError nfe)
663 {
664     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
665     if(req is null)
666         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
667     if(args.length < 1)
668     {
669         nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
670         return ScriptAny.UNDEFINED;
671     }
672     req._overriddenMimeType = args[0].toString();
673     return ScriptAny.UNDEFINED;
674 }
675 
676 private ScriptAny native_XMLHttpRequest_p_readyState(Environment env, ScriptAny* thisObj,
677                                                      ScriptAny[] args, ref NativeFunctionError nfe)
678 {
679     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
680     if(req is null)
681         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
682     return ScriptAny(req._readyState);
683 }
684 
685 private ScriptAny native_XMLHttpRequest_p_response(Environment env, ScriptAny* thisObj,
686                                                    ScriptAny[] args, ref NativeFunctionError nfe)
687 {
688     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
689     if(req is null)
690         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
691     return ScriptAny(req._response);
692 }
693 
694 private ScriptAny native_XMLHttpRequest_p_responseText(Environment env, ScriptAny* thisObj,
695                                                    ScriptAny[] args, ref NativeFunctionError nfe)
696 {
697     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
698     if(req is null)
699         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
700     return ScriptAny(cast(string)req._responseText);
701 }
702 
703 private ScriptAny native_XMLHttpRequest_send(Environment env, ScriptAny* thisObj,
704                                              ScriptAny[] args, ref NativeFunctionError nfe)
705 {
706     import std..string: indexOf;
707     
708     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
709     if(req is null)
710         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
711     req._done = false;
712     req._started = true;
713     auto vm = env.g.interpreter.vm;
714     if(args.length > 0)
715     {
716         auto content = args[0].toString();
717         req._http.setPostData(content, "text/plain");
718     }
719     if(req._async)
720     {
721         auto fiberFunc = new ScriptFunction("http", 
722             function ScriptAny(Environment env, ScriptAny* thisObj,
723                 ScriptAny[] args, ref NativeFunctionError nfe) 
724         {
725             auto vm = env.g.interpreter.vm;
726             auto request = thisObj.toNativeObject!ScriptXMLHttpRequest;
727             auto tid = spawn({
728                 receive((shared ScriptXMLHttpRequest httpRequest)
729                 {
730                     auto httpRequestUnshared = cast(ScriptXMLHttpRequest)httpRequest;
731                     try 
732                     {
733                         httpRequestUnshared._http.perform();
734                         synchronized(httpRequestUnshared)
735                         {
736                             httpRequestUnshared._readyState = ScriptXMLHttpRequest.ReadyState.DONE;
737                             httpRequestUnshared._eventQueue ~= tuple(ScriptXMLHttpRequest.EventType.RS_CHANGE,
738                                     ScriptAny(ScriptXMLHttpRequest.ReadyState.DONE));
739                             
740                             auto loadedEvent = new ScriptObject("ProgressEvent", null);
741                             loadedEvent["lengthComputable"] = ScriptAny(true);
742                             loadedEvent["loaded"] = ScriptAny(httpRequestUnshared._responseText.length);
743                             loadedEvent["total"] = ScriptAny(httpRequestUnshared._responseText.length);
744                             httpRequestUnshared._eventQueue ~= tuple(ScriptXMLHttpRequest.EventType.LOAD,
745                                     ScriptAny(loadedEvent));
746 
747                             httpRequestUnshared._eventQueue ~= tuple(ScriptXMLHttpRequest.EventType.LOADEND,
748                                     ScriptAny(loadedEvent));
749                             
750                             httpRequestUnshared._done = true;
751                         }
752                     }
753                     catch(CurlException cex)
754                     {
755                         synchronized(httpRequestUnshared)
756                         {
757                             if(indexOf(cex.msg, "aborted") == -1)
758                             {
759                                 auto event = new ScriptObject("ProgressEvent", null);
760                                 event["lengthComputable"] = ScriptAny(false);
761                                 event["loaded"] = ScriptAny(httpRequestUnshared._responseText.length);
762                                 event["total"] = ScriptAny(0);
763                                 httpRequestUnshared._eventQueue ~= tuple(ScriptXMLHttpRequest.EventType.ERROR,
764                                         ScriptAny(event));
765                                 httpRequestUnshared._eventQueue ~= tuple(ScriptXMLHttpRequest.EventType.LOADEND,
766                                         ScriptAny(event));
767                                 if(indexOf(cex.msg, "Timeout") != -1)
768                                 {
769                                     httpRequestUnshared._eventQueue ~= tuple(ScriptXMLHttpRequest.EventType.TIMEOUT,
770                                         ScriptAny(event));
771                                 }
772                             }
773                             httpRequestUnshared._done = true;
774                         }
775                     } 
776                     catch(Exception ex)
777                     {
778                         synchronized(httpRequestUnshared)
779                         {
780                             httpRequestUnshared._exception = ex;
781                         }
782                     }
783                 });
784             });
785             send(tid, cast(shared)request);
786 
787             ScriptXMLHttpRequest.Event[] deferredEvents;
788             while(!request._done && request._exception is null)
789             {
790                 synchronized(request)
791                 {
792                     if(request._eventQueue.length > 0)
793                     {
794                         auto event = request._eventQueue[0];
795                         request._eventQueue = request._eventQueue[1..$];
796                         if(event[0] == ScriptXMLHttpRequest.EventType.PROGRESS)
797                         {
798                             if(request._onProgress)
799                                 vm.runFunction(request._onProgress, *thisObj, [event[1]]);
800                         }
801                         else if(event[0] == ScriptXMLHttpRequest.EventType.ABORT)
802                         {
803                             if(request._onAbort)
804                                 vm.runFunction(request._onAbort, *thisObj, [event[1]]);
805                         }
806                         else if(event[0] == ScriptXMLHttpRequest.EventType.ERROR)
807                         {
808                             if(request._onError)
809                                 vm.runFunction(request._onError, *thisObj, [event[1]]);
810                         }
811                         else if(event[0] == ScriptXMLHttpRequest.EventType.LOADEND)
812                         {
813                             if(request._onLoadEnd)
814                                 vm.runFunction(request._onLoadEnd, *thisObj, [event[1]]);
815                         }
816                         else if(event[0] == ScriptXMLHttpRequest.EventType.LOADSTART)
817                         {
818                             if(request._onLoadStart)
819                                 vm.runFunction(request._onLoadStart, *thisObj, [event[1]]);
820                         }
821                         else if(event[0] == ScriptXMLHttpRequest.EventType.TIMEOUT)
822                         {
823                             if(request._onTimeout)
824                                 vm.runFunction(request._onTimeout, *thisObj, [event[1]]);
825                         }
826                         else
827                         {
828                             deferredEvents ~= event;
829                         }
830                     }
831                 }
832                 yield();
833             }
834 
835             deferredEvents ~= request._eventQueue;
836 
837             if(request._exception !is null)
838                 throw new ScriptRuntimeException(request._exception.msg);
839 
840             if(deferredEvents.length > 0)
841             {
842                 foreach(event ; deferredEvents)
843                 {
844                     if(event[0] == ScriptXMLHttpRequest.EventType.RS_CHANGE)
845                     {
846                         // resync
847                         request._readyState = cast(ScriptXMLHttpRequest.ReadyState)event[1].toValue!ushort;
848                         request.checkResponseType();
849                         if(request._onReadyStateChange)
850                             vm.runFunction(request._onReadyStateChange, *thisObj, []);
851                     }
852                     else if(event[0] == ScriptXMLHttpRequest.EventType.LOAD)
853                     {
854                         if(request._onLoad)
855                             vm.runFunction(request._onLoad, *thisObj, [event[1]]);
856                     }
857                     else if(event[0] == ScriptXMLHttpRequest.EventType.LOADEND)
858                     {
859                         if(request._onLoadEnd)
860                             vm.runFunction(request._onLoadEnd, *thisObj, [event[1]]);
861                     }
862                     else if(event[0] == ScriptXMLHttpRequest.EventType.ERROR)
863                     {
864                         if(request._onError)
865                             vm.runFunction(request._onError, *thisObj, [event[1]]);
866                     }
867                     else if(event[0] == ScriptXMLHttpRequest.EventType.TIMEOUT)
868                     {
869                         if(request._onTimeout)
870                             vm.runFunction(request._onTimeout, *thisObj, [event[1]]);
871                     }
872                     /*else if(event[0] == ScriptXMLHttpRequest.EventType.PROGRESS)
873                     {
874                         if(request._onProgress)
875                             vm.runFunction(request._onProgress, *thisObj, [event[1]]);
876                     }
877                     else if(event[0] == ScriptXMLHttpRequest.EventType.ABORT)
878                     {
879                         if(request._onAbort)
880                             vm.runFunction(request._onAbort, *thisObj, [event[1]]);
881                     }
882                     */
883                 }
884                 deferredEvents = null;
885             }
886 
887             return ScriptAny();
888         }, false);
889 
890         vm.addFiber("http", fiberFunc, *thisObj, []);
891     }
892     else 
893     {
894         if(req._readyState != ScriptXMLHttpRequest.ReadyState.OPENED)
895             throw new ScriptRuntimeException("Invalid ready state (not opened)");
896         try 
897         {
898             req._http.perform();
899         }
900         catch(CurlException ex)
901         {
902             auto event = new ScriptObject(
903                 "ProgressEvent", 
904                 null
905             );
906             event["lengthComputable"] = ScriptAny(false);
907             event["loaded"] = ScriptAny(req._responseText.length);
908             event["total"] = ScriptAny(0);
909             if(indexOf(ex.msg, "aborted") != -1)
910             {
911                 if(req._onAbort)
912                 {
913                     vm.runFunction(req._onAbort, *thisObj, [ScriptAny(event)]);
914                 }
915                 if(req._onLoadEnd)
916                 {
917                     vm.runFunction(req._onLoadEnd, *thisObj, [ScriptAny(event)]);
918                 }
919              }
920              else
921              {
922                 if(indexOf(ex.msg, "Timeout") != -1 && req._onTimeout)
923                 {
924                     vm.runFunction(req._onTimeout, *thisObj, [ScriptAny(event)]);
925                 }
926                 else if(req._onError)
927                 {
928                     vm.runFunction(req._onError, *thisObj, [ScriptAny(event)]);
929                 }
930              }
931             return ScriptAny.UNDEFINED;
932         }
933         catch(Exception ex)
934         {
935             throw new ScriptRuntimeException(ex.msg);
936         }
937         req.checkResponseType();
938         if(req._readyState == ScriptXMLHttpRequest.ReadyState.LOADING)
939         {
940             auto event = new ScriptObject("ProgressEvent", null);
941             event["lengthComputable"] = ScriptAny(true);
942             event["loaded"] = ScriptAny(req._responseText.length);
943             event["total"] = ScriptAny(req._responseText.length);
944             req._readyState = ScriptXMLHttpRequest.ReadyState.DONE;
945             req.checkResponseType();
946             if(req._onReadyStateChange)
947             {
948                 vm.runFunction(req._onReadyStateChange, *thisObj, []);
949             }
950             if(req._onLoad)
951             {
952                 vm.runFunction(req._onLoad, *thisObj, [ScriptAny(event)]);
953             }
954             if(req._onLoadEnd)
955             {
956                 vm.runFunction(req._onLoadEnd, *thisObj, [ScriptAny(event)]);
957             }
958         }
959     }
960     return ScriptAny.UNDEFINED;
961 }
962 
963 private ScriptAny native_XMLHttpRequest_setRequestHeader(Environment env, ScriptAny* thisObj,
964                                                          ScriptAny[] args, ref NativeFunctionError nfe)
965 {
966     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
967     if(req is null)
968         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
969     if(args.length < 2)
970     {
971         nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
972         return ScriptAny.UNDEFINED;
973     }
974     auto key = args[0].toString();
975     auto value = args[1].toString();
976     req._http.addRequestHeader(key, value);
977     return ScriptAny.UNDEFINED;
978 }
979 
980 private ScriptAny native_XMLHttpRequest_p_status(Environment env, ScriptAny* thisObj,
981                                                ScriptAny[] args, ref NativeFunctionError nfe)
982 {
983     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
984     if(req is null)
985         throw new ScriptRuntimeException("Invalid XMLHttpRequest object");
986     return ScriptAny(req._status);
987 }
988 
989 private ScriptAny native_XMLHttpRequest_p_timeout(Environment env, ScriptAny* thisObj,
990                                                   ScriptAny[] args, ref NativeFunctionError nfe)
991 {
992     import core.time: dur;
993 
994     auto req = thisObj.toNativeObject!ScriptXMLHttpRequest;
995     if(args.length > 0)
996     {
997         try 
998         {
999             immutable timeout = args[0].toValue!long;
1000             req._timeout = timeout;
1001             req._http.operationTimeout(dur!"msecs"(timeout));
1002         }
1003         catch(CurlException ex)
1004         {
1005             throw new ScriptRuntimeException("Must call open before setting timeout");
1006         }
1007     }
1008     return ScriptAny(req._timeout);
1009 }
1010