source: trip-planner-front/node_modules/node-forge/lib/xhr.js@ 8d391a1

Last change on this file since 8d391a1 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 21.6 KB
RevLine 
[6a3a178]1/**
2 * XmlHttpRequest implementation that uses TLS and flash SocketPool.
3 *
4 * @author Dave Longley
5 *
6 * Copyright (c) 2010-2013 Digital Bazaar, Inc.
7 */
8var forge = require('./forge');
9require('./socket');
10require('./http');
11
12/* XHR API */
13var xhrApi = module.exports = forge.xhr = forge.xhr || {};
14
15(function($) {
16
17// logging category
18var cat = 'forge.xhr';
19
20/*
21XMLHttpRequest interface definition from:
22http://www.w3.org/TR/XMLHttpRequest
23
24interface XMLHttpRequest {
25 // event handler
26 attribute EventListener onreadystatechange;
27
28 // state
29 const unsigned short UNSENT = 0;
30 const unsigned short OPENED = 1;
31 const unsigned short HEADERS_RECEIVED = 2;
32 const unsigned short LOADING = 3;
33 const unsigned short DONE = 4;
34 readonly attribute unsigned short readyState;
35
36 // request
37 void open(in DOMString method, in DOMString url);
38 void open(in DOMString method, in DOMString url, in boolean async);
39 void open(in DOMString method, in DOMString url,
40 in boolean async, in DOMString user);
41 void open(in DOMString method, in DOMString url,
42 in boolean async, in DOMString user, in DOMString password);
43 void setRequestHeader(in DOMString header, in DOMString value);
44 void send();
45 void send(in DOMString data);
46 void send(in Document data);
47 void abort();
48
49 // response
50 DOMString getAllResponseHeaders();
51 DOMString getResponseHeader(in DOMString header);
52 readonly attribute DOMString responseText;
53 readonly attribute Document responseXML;
54 readonly attribute unsigned short status;
55 readonly attribute DOMString statusText;
56};
57*/
58
59// readyStates
60var UNSENT = 0;
61var OPENED = 1;
62var HEADERS_RECEIVED = 2;
63var LOADING = 3;
64var DONE = 4;
65
66// exceptions
67var INVALID_STATE_ERR = 11;
68var SYNTAX_ERR = 12;
69var SECURITY_ERR = 18;
70var NETWORK_ERR = 19;
71var ABORT_ERR = 20;
72
73// private flash socket pool vars
74var _sp = null;
75var _policyPort = 0;
76var _policyUrl = null;
77
78// default client (used if no special URL provided when creating an XHR)
79var _client = null;
80
81// all clients including the default, key'd by full base url
82// (multiple cross-domain http clients are permitted so there may be more
83// than one client in this map)
84// TODO: provide optional clean up API for non-default clients
85var _clients = {};
86
87// the default maximum number of concurrents connections per client
88var _maxConnections = 10;
89
90var net = forge.net;
91var http = forge.http;
92
93/**
94 * Initializes flash XHR support.
95 *
96 * @param options:
97 * url: the default base URL to connect to if xhr URLs are relative,
98 * ie: https://myserver.com.
99 * flashId: the dom ID of the flash SocketPool.
100 * policyPort: the port that provides the server's flash policy, 0 to use
101 * the flash default.
102 * policyUrl: the policy file URL to use instead of a policy port.
103 * msie: true if browser is internet explorer, false if not.
104 * connections: the maximum number of concurrent connections.
105 * caCerts: a list of PEM-formatted certificates to trust.
106 * cipherSuites: an optional array of cipher suites to use,
107 * see forge.tls.CipherSuites.
108 * verify: optional TLS certificate verify callback to use (see forge.tls
109 * for details).
110 * getCertificate: an optional callback used to get a client-side
111 * certificate (see forge.tls for details).
112 * getPrivateKey: an optional callback used to get a client-side private
113 * key (see forge.tls for details).
114 * getSignature: an optional callback used to get a client-side signature
115 * (see forge.tls for details).
116 * persistCookies: true to use persistent cookies via flash local storage,
117 * false to only keep cookies in javascript.
118 * primeTlsSockets: true to immediately connect TLS sockets on their
119 * creation so that they will cache TLS sessions for reuse.
120 */
121xhrApi.init = function(options) {
122 forge.log.debug(cat, 'initializing', options);
123
124 // update default policy port and max connections
125 _policyPort = options.policyPort || _policyPort;
126 _policyUrl = options.policyUrl || _policyUrl;
127 _maxConnections = options.connections || _maxConnections;
128
129 // create the flash socket pool
130 _sp = net.createSocketPool({
131 flashId: options.flashId,
132 policyPort: _policyPort,
133 policyUrl: _policyUrl,
134 msie: options.msie || false
135 });
136
137 // create default http client
138 _client = http.createClient({
139 url: options.url || (
140 window.location.protocol + '//' + window.location.host),
141 socketPool: _sp,
142 policyPort: _policyPort,
143 policyUrl: _policyUrl,
144 connections: options.connections || _maxConnections,
145 caCerts: options.caCerts,
146 cipherSuites: options.cipherSuites,
147 persistCookies: options.persistCookies || true,
148 primeTlsSockets: options.primeTlsSockets || false,
149 verify: options.verify,
150 getCertificate: options.getCertificate,
151 getPrivateKey: options.getPrivateKey,
152 getSignature: options.getSignature
153 });
154 _clients[_client.url.full] = _client;
155
156 forge.log.debug(cat, 'ready');
157};
158
159/**
160 * Called to clean up the clients and socket pool.
161 */
162xhrApi.cleanup = function() {
163 // destroy all clients
164 for(var key in _clients) {
165 _clients[key].destroy();
166 }
167 _clients = {};
168 _client = null;
169
170 // destroy socket pool
171 _sp.destroy();
172 _sp = null;
173};
174
175/**
176 * Sets a cookie.
177 *
178 * @param cookie the cookie with parameters:
179 * name: the name of the cookie.
180 * value: the value of the cookie.
181 * comment: an optional comment string.
182 * maxAge: the age of the cookie in seconds relative to created time.
183 * secure: true if the cookie must be sent over a secure protocol.
184 * httpOnly: true to restrict access to the cookie from javascript
185 * (inaffective since the cookies are stored in javascript).
186 * path: the path for the cookie.
187 * domain: optional domain the cookie belongs to (must start with dot).
188 * version: optional version of the cookie.
189 * created: creation time, in UTC seconds, of the cookie.
190 */
191xhrApi.setCookie = function(cookie) {
192 // default cookie expiration to never
193 cookie.maxAge = cookie.maxAge || -1;
194
195 // if the cookie's domain is set, use the appropriate client
196 if(cookie.domain) {
197 // add the cookies to the applicable domains
198 for(var key in _clients) {
199 var client = _clients[key];
200 if(http.withinCookieDomain(client.url, cookie) &&
201 client.secure === cookie.secure) {
202 client.setCookie(cookie);
203 }
204 }
205 } else {
206 // use the default domain
207 // FIXME: should a null domain cookie be added to all clients? should
208 // this be an option?
209 _client.setCookie(cookie);
210 }
211};
212
213/**
214 * Gets a cookie.
215 *
216 * @param name the name of the cookie.
217 * @param path an optional path for the cookie (if there are multiple cookies
218 * with the same name but different paths).
219 * @param domain an optional domain for the cookie (if not using the default
220 * domain).
221 *
222 * @return the cookie, cookies (if multiple matches), or null if not found.
223 */
224xhrApi.getCookie = function(name, path, domain) {
225 var rval = null;
226
227 if(domain) {
228 // get the cookies from the applicable domains
229 for(var key in _clients) {
230 var client = _clients[key];
231 if(http.withinCookieDomain(client.url, domain)) {
232 var cookie = client.getCookie(name, path);
233 if(cookie !== null) {
234 if(rval === null) {
235 rval = cookie;
236 } else if(!forge.util.isArray(rval)) {
237 rval = [rval, cookie];
238 } else {
239 rval.push(cookie);
240 }
241 }
242 }
243 }
244 } else {
245 // get cookie from default domain
246 rval = _client.getCookie(name, path);
247 }
248
249 return rval;
250};
251
252/**
253 * Removes a cookie.
254 *
255 * @param name the name of the cookie.
256 * @param path an optional path for the cookie (if there are multiple cookies
257 * with the same name but different paths).
258 * @param domain an optional domain for the cookie (if not using the default
259 * domain).
260 *
261 * @return true if a cookie was removed, false if not.
262 */
263xhrApi.removeCookie = function(name, path, domain) {
264 var rval = false;
265
266 if(domain) {
267 // remove the cookies from the applicable domains
268 for(var key in _clients) {
269 var client = _clients[key];
270 if(http.withinCookieDomain(client.url, domain)) {
271 if(client.removeCookie(name, path)) {
272 rval = true;
273 }
274 }
275 }
276 } else {
277 // remove cookie from default domain
278 rval = _client.removeCookie(name, path);
279 }
280
281 return rval;
282};
283
284/**
285 * Creates a new XmlHttpRequest. By default the base URL, flash policy port,
286 * etc, will be used. However, an XHR can be created to point at another
287 * cross-domain URL.
288 *
289 * @param options:
290 * logWarningOnError: If true and an HTTP error status code is received then
291 * log a warning, otherwise log a verbose message.
292 * verbose: If true be very verbose in the output including the response
293 * event and response body, otherwise only include status, timing, and
294 * data size.
295 * logError: a multi-var log function for warnings that takes the log
296 * category as the first var.
297 * logWarning: a multi-var log function for warnings that takes the log
298 * category as the first var.
299 * logDebug: a multi-var log function for warnings that takes the log
300 * category as the first var.
301 * logVerbose: a multi-var log function for warnings that takes the log
302 * category as the first var.
303 * url: the default base URL to connect to if xhr URLs are relative,
304 * eg: https://myserver.com, and note that the following options will be
305 * ignored if the URL is absent or the same as the default base URL.
306 * policyPort: the port that provides the server's flash policy, 0 to use
307 * the flash default.
308 * policyUrl: the policy file URL to use instead of a policy port.
309 * connections: the maximum number of concurrent connections.
310 * caCerts: a list of PEM-formatted certificates to trust.
311 * cipherSuites: an optional array of cipher suites to use, see
312 * forge.tls.CipherSuites.
313 * verify: optional TLS certificate verify callback to use (see forge.tls
314 * for details).
315 * getCertificate: an optional callback used to get a client-side
316 * certificate.
317 * getPrivateKey: an optional callback used to get a client-side private key.
318 * getSignature: an optional callback used to get a client-side signature.
319 * persistCookies: true to use persistent cookies via flash local storage,
320 * false to only keep cookies in javascript.
321 * primeTlsSockets: true to immediately connect TLS sockets on their
322 * creation so that they will cache TLS sessions for reuse.
323 *
324 * @return the XmlHttpRequest.
325 */
326xhrApi.create = function(options) {
327 // set option defaults
328 options = $.extend({
329 logWarningOnError: true,
330 verbose: false,
331 logError: function() {},
332 logWarning: function() {},
333 logDebug: function() {},
334 logVerbose: function() {},
335 url: null
336 }, options || {});
337
338 // private xhr state
339 var _state = {
340 // the http client to use
341 client: null,
342 // request storage
343 request: null,
344 // response storage
345 response: null,
346 // asynchronous, true if doing asynchronous communication
347 asynchronous: true,
348 // sendFlag, true if send has been called
349 sendFlag: false,
350 // errorFlag, true if a network error occurred
351 errorFlag: false
352 };
353
354 // private log functions
355 var _log = {
356 error: options.logError || forge.log.error,
357 warning: options.logWarning || forge.log.warning,
358 debug: options.logDebug || forge.log.debug,
359 verbose: options.logVerbose || forge.log.verbose
360 };
361
362 // create public xhr interface
363 var xhr = {
364 // an EventListener
365 onreadystatechange: null,
366 // readonly, the current readyState
367 readyState: UNSENT,
368 // a string with the response entity-body
369 responseText: '',
370 // a Document for response entity-bodies that are XML
371 responseXML: null,
372 // readonly, returns the HTTP status code (i.e. 404)
373 status: 0,
374 // readonly, returns the HTTP status message (i.e. 'Not Found')
375 statusText: ''
376 };
377
378 // determine which http client to use
379 if(options.url === null) {
380 // use default
381 _state.client = _client;
382 } else {
383 var url = http.parseUrl(options.url);
384 if(!url) {
385 var error = new Error('Invalid url.');
386 error.details = {
387 url: options.url
388 };
389 }
390
391 // find client
392 if(url.full in _clients) {
393 // client found
394 _state.client = _clients[url.full];
395 } else {
396 // create client
397 _state.client = http.createClient({
398 url: options.url,
399 socketPool: _sp,
400 policyPort: options.policyPort || _policyPort,
401 policyUrl: options.policyUrl || _policyUrl,
402 connections: options.connections || _maxConnections,
403 caCerts: options.caCerts,
404 cipherSuites: options.cipherSuites,
405 persistCookies: options.persistCookies || true,
406 primeTlsSockets: options.primeTlsSockets || false,
407 verify: options.verify,
408 getCertificate: options.getCertificate,
409 getPrivateKey: options.getPrivateKey,
410 getSignature: options.getSignature
411 });
412 _clients[url.full] = _state.client;
413 }
414 }
415
416 /**
417 * Opens the request. This method will create the HTTP request to send.
418 *
419 * @param method the HTTP method (i.e. 'GET').
420 * @param url the relative url (the HTTP request path).
421 * @param async always true, ignored.
422 * @param user always null, ignored.
423 * @param password always null, ignored.
424 */
425 xhr.open = function(method, url, async, user, password) {
426 // 1. validate Document if one is associated
427 // TODO: not implemented (not used yet)
428
429 // 2. validate method token
430 // 3. change method to uppercase if it matches a known
431 // method (here we just require it to be uppercase, and
432 // we do not allow the standard methods)
433 // 4. disallow CONNECT, TRACE, or TRACK with a security error
434 switch(method) {
435 case 'DELETE':
436 case 'GET':
437 case 'HEAD':
438 case 'OPTIONS':
439 case 'PATCH':
440 case 'POST':
441 case 'PUT':
442 // valid method
443 break;
444 case 'CONNECT':
445 case 'TRACE':
446 case 'TRACK':
447 throw new Error('CONNECT, TRACE and TRACK methods are disallowed');
448 default:
449 throw new Error('Invalid method: ' + method);
450 }
451
452 // TODO: other validation steps in algorithm are not implemented
453
454 // 19. set send flag to false
455 // set response body to null
456 // empty list of request headers
457 // set request method to given method
458 // set request URL
459 // set username, password
460 // set asychronous flag
461 _state.sendFlag = false;
462 xhr.responseText = '';
463 xhr.responseXML = null;
464
465 // custom: reset status and statusText
466 xhr.status = 0;
467 xhr.statusText = '';
468
469 // create the HTTP request
470 _state.request = http.createRequest({
471 method: method,
472 path: url
473 });
474
475 // 20. set state to OPENED
476 xhr.readyState = OPENED;
477
478 // 21. dispatch onreadystatechange
479 if(xhr.onreadystatechange) {
480 xhr.onreadystatechange();
481 }
482 };
483
484 /**
485 * Adds an HTTP header field to the request.
486 *
487 * @param header the name of the header field.
488 * @param value the value of the header field.
489 */
490 xhr.setRequestHeader = function(header, value) {
491 // 1. if state is not OPENED or send flag is true, raise exception
492 if(xhr.readyState != OPENED || _state.sendFlag) {
493 throw new Error('XHR not open or sending');
494 }
495
496 // TODO: other validation steps in spec aren't implemented
497
498 // set header
499 _state.request.setField(header, value);
500 };
501
502 /**
503 * Sends the request and any associated data.
504 *
505 * @param data a string or Document object to send, null to send no data.
506 */
507 xhr.send = function(data) {
508 // 1. if state is not OPENED or 2. send flag is true, raise
509 // an invalid state exception
510 if(xhr.readyState != OPENED || _state.sendFlag) {
511 throw new Error('XHR not open or sending');
512 }
513
514 // 3. ignore data if method is GET or HEAD
515 if(data &&
516 _state.request.method !== 'GET' &&
517 _state.request.method !== 'HEAD') {
518 // handle non-IE case
519 if(typeof(XMLSerializer) !== 'undefined') {
520 if(data instanceof Document) {
521 var xs = new XMLSerializer();
522 _state.request.body = xs.serializeToString(data);
523 } else {
524 _state.request.body = data;
525 }
526 } else {
527 // poorly implemented IE case
528 if(typeof(data.xml) !== 'undefined') {
529 _state.request.body = data.xml;
530 } else {
531 _state.request.body = data;
532 }
533 }
534 }
535
536 // 4. release storage mutex (not used)
537
538 // 5. set error flag to false
539 _state.errorFlag = false;
540
541 // 6. if asynchronous is true (must be in this implementation)
542
543 // 6.1 set send flag to true
544 _state.sendFlag = true;
545
546 // 6.2 dispatch onreadystatechange
547 if(xhr.onreadystatechange) {
548 xhr.onreadystatechange();
549 }
550
551 // create send options
552 var options = {};
553 options.request = _state.request;
554 options.headerReady = function(e) {
555 // make cookies available for ease of use/iteration
556 xhr.cookies = _state.client.cookies;
557
558 // TODO: update document.cookie with any cookies where the
559 // script's domain matches
560
561 // headers received
562 xhr.readyState = HEADERS_RECEIVED;
563 xhr.status = e.response.code;
564 xhr.statusText = e.response.message;
565 _state.response = e.response;
566 if(xhr.onreadystatechange) {
567 xhr.onreadystatechange();
568 }
569 if(!_state.response.aborted) {
570 // now loading body
571 xhr.readyState = LOADING;
572 if(xhr.onreadystatechange) {
573 xhr.onreadystatechange();
574 }
575 }
576 };
577 options.bodyReady = function(e) {
578 xhr.readyState = DONE;
579 var ct = e.response.getField('Content-Type');
580 // Note: this null/undefined check is done outside because IE
581 // dies otherwise on a "'null' is null" error
582 if(ct) {
583 if(ct.indexOf('text/xml') === 0 ||
584 ct.indexOf('application/xml') === 0 ||
585 ct.indexOf('+xml') !== -1) {
586 try {
587 var doc = new ActiveXObject('MicrosoftXMLDOM');
588 doc.async = false;
589 doc.loadXML(e.response.body);
590 xhr.responseXML = doc;
591 } catch(ex) {
592 var parser = new DOMParser();
593 xhr.responseXML = parser.parseFromString(ex.body, 'text/xml');
594 }
595 }
596 }
597
598 var length = 0;
599 if(e.response.body !== null) {
600 xhr.responseText = e.response.body;
601 length = e.response.body.length;
602 }
603 // build logging output
604 var req = _state.request;
605 var output =
606 req.method + ' ' + req.path + ' ' +
607 xhr.status + ' ' + xhr.statusText + ' ' +
608 length + 'B ' +
609 (e.request.connectTime + e.request.time + e.response.time) +
610 'ms';
611 var lFunc;
612 if(options.verbose) {
613 lFunc = (xhr.status >= 400 && options.logWarningOnError) ?
614 _log.warning : _log.verbose;
615 lFunc(cat, output,
616 e, e.response.body ? '\n' + e.response.body : '\nNo content');
617 } else {
618 lFunc = (xhr.status >= 400 && options.logWarningOnError) ?
619 _log.warning : _log.debug;
620 lFunc(cat, output);
621 }
622 if(xhr.onreadystatechange) {
623 xhr.onreadystatechange();
624 }
625 };
626 options.error = function(e) {
627 var req = _state.request;
628 _log.error(cat, req.method + ' ' + req.path, e);
629
630 // 1. set response body to null
631 xhr.responseText = '';
632 xhr.responseXML = null;
633
634 // 2. set error flag to true (and reset status)
635 _state.errorFlag = true;
636 xhr.status = 0;
637 xhr.statusText = '';
638
639 // 3. set state to done
640 xhr.readyState = DONE;
641
642 // 4. asyc flag is always true, so dispatch onreadystatechange
643 if(xhr.onreadystatechange) {
644 xhr.onreadystatechange();
645 }
646 };
647
648 // 7. send request
649 _state.client.send(options);
650 };
651
652 /**
653 * Aborts the request.
654 */
655 xhr.abort = function() {
656 // 1. abort send
657 // 2. stop network activity
658 _state.request.abort();
659
660 // 3. set response to null
661 xhr.responseText = '';
662 xhr.responseXML = null;
663
664 // 4. set error flag to true (and reset status)
665 _state.errorFlag = true;
666 xhr.status = 0;
667 xhr.statusText = '';
668
669 // 5. clear user headers
670 _state.request = null;
671 _state.response = null;
672
673 // 6. if state is DONE or UNSENT, or if OPENED and send flag is false
674 if(xhr.readyState === DONE || xhr.readyState === UNSENT ||
675 (xhr.readyState === OPENED && !_state.sendFlag)) {
676 // 7. set ready state to unsent
677 xhr.readyState = UNSENT;
678 } else {
679 // 6.1 set state to DONE
680 xhr.readyState = DONE;
681
682 // 6.2 set send flag to false
683 _state.sendFlag = false;
684
685 // 6.3 dispatch onreadystatechange
686 if(xhr.onreadystatechange) {
687 xhr.onreadystatechange();
688 }
689
690 // 7. set state to UNSENT
691 xhr.readyState = UNSENT;
692 }
693 };
694
695 /**
696 * Gets all response headers as a string.
697 *
698 * @return the HTTP-encoded response header fields.
699 */
700 xhr.getAllResponseHeaders = function() {
701 var rval = '';
702 if(_state.response !== null) {
703 var fields = _state.response.fields;
704 $.each(fields, function(name, array) {
705 $.each(array, function(i, value) {
706 rval += name + ': ' + value + '\r\n';
707 });
708 });
709 }
710 return rval;
711 };
712
713 /**
714 * Gets a single header field value or, if there are multiple
715 * fields with the same name, a comma-separated list of header
716 * values.
717 *
718 * @return the header field value(s) or null.
719 */
720 xhr.getResponseHeader = function(header) {
721 var rval = null;
722 if(_state.response !== null) {
723 if(header in _state.response.fields) {
724 rval = _state.response.fields[header];
725 if(forge.util.isArray(rval)) {
726 rval = rval.join();
727 }
728 }
729 }
730 return rval;
731 };
732
733 return xhr;
734};
735
736})(jQuery);
Note: See TracBrowser for help on using the repository browser.