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