Download note
The complete package which includes test files takes up about 110Mb of disk space (when unpacked), as there are two large files needed to test the XMLHttpRequest progress functionality.
Introduction
The XMLHttpRequest object is implemented in all major web browsers to allow developers benefit from the AJAX technology. The advantages of AJAX are indisputable, however XMLHttpRequest is often awkward to use, especially due to its asynchronous nature: not only is the onreadystatechange
event triggered asynchronously, but the same is true for all other important events, such as onloadstart
, onloadend
or onerror
, to name a few. Vital information is not to be found in one place, there is no easy way to learn what is going on with an AJAX request at a given time and debugging may be a real nightmare.
Secondly, there are a number of configuration options which may and/or have to be defined for each XMLHttpRequest connection. If a given site relies on AJAX heavily redefining all the necessary settings in various parts of our code may make it less legible and more problematic to modify. Moreover, any configuration related problems can usually be detected only after an attempt to open a connection, send data or retrieve a server response has been made.
Finally, there are serious security concerns related to spying and phishing attacks which may be conducted by exploiting some weaknesses of the XMLHttpRequest object and JavaScript. The risks include:
- overwriting and wrapping an instance of the XMLHttpRequest object (dangerous if a singleton instance is used),
- overwriting and wrapping an XMLHttpRequest.prototype.open method (dangerous if there is a chance of some malicious script having been executed before most "AJAXian" communication takes place, i.e. if new XMLHttpRequest instances are created after a page and a malicious script have been loaded),
- using an unmodified XMLHttpRequest instance to send some private information displayed on a web page to a remote site controlled by an attacker.
SecureXHR addresses all the issues presented above as it provides the developer with a solution which offers:
- a user friendly interface that lets one identify, control, handle and debug AJAX connections and gather all the necessary information related to each XMLHttpRequest instance in a much more convenient way;
- a method to validate and reuse XMLHttpRequest configuration options;
- a fix to effectively prevent all three types of phishing attacks which have been previously described.
SecureXHR does not prevent cross-site scripting attacks but protects the XMLHttpRequest object in case of a successful XSS attack.
Background
SecureXHR is a self executing anonymous function which is invisible and inaccessible from the global scope. Its interface is exposed to the global scope in the form of a getter function so there is no way to overwrite private methods or properties used by SecureXHR.
However, there is a risk of its interface being overwritten either if it is not deleted after no longer needed (the developer should delete the SecureXHR interface after all scripts which use AJAX technology have been loaded) or if other scripts which employ SecureXHR do not properly hide their code from the global scope (i.e. if a second party variable or property storing the SecureXHR interface getter function is somehow exposed to the global scope). Even if a SecureXHR interface copy gets modified by a malicious script there still remains a tool to effectively prevent phishing attacks: the developer can turn on the SecureXHR's sites filter and either allow AJAX connections only to the current browser window site or block XMLHttpRequest requests to sites which are not on the filter list. The sites filter can be used either internally by SecureXHR or globally by the means of SecureXHR modifying (overwriting and wrapping) the original XMLHttpRequest.prototype.open
method.
Similarly, SecureXHR may optionally attempt to remove the window.XMLHttpRequest
from the global scope to prevent any malicious scripts from having access to the XMLHttpRequest object. With some browsers there might be no way to effectively hide the host object from the global scope, but SecureXHR can optionally attempt to modify (overwrite and wrap) the original XMLHttpRequest.prototype.open
method so that it will expect a secret token as its additional (6th) parameter. In this way, SecureXHR will be the only script which is able to directly access and run AJAX requests. Other scripts will have to rely on the SecureXHR interface.
Letting SecureXHR modify and wrap the original XMLHttpRequest.prototype.open
function is strongly advised as a way to provide an ultimate safety to an AJAX dependent web page, but not all browsers allow native object prototype modifications (e.g. at least some versions of Internet Explorer) and one must bear in mind that any prototype modifications are impossible to revert.
A quick look
A short example might be useful to show the flexibility and user-friendliness of SecureXHR in terms of running AJAX requests:
var xhr = getSecureXHRInterface();
var xhrConfig = xhr.conf({
"getType" : "json" ,
"path" : "/ajax/index.php" ,
"onDone" : xhrComplete
});
var ok = xhr.exec( {"id":453, "action":"getUserDataById"} , xhrConfig.inf.cfgId );
if( ok.err.fatal )
alert( "The request could not be initiated due to a fatal error at " + ok.err.at );
function xhrComplete( requestInfo )
{
if( requestInfo.err.fatal )
{
return;
}
}
With minimalistic approach the following will also work:
getSecureXHRInterface().exec( {"id":453, "action":"getUserDataById"} , 0 , {
"getType" : "json" ,
"path" : "/ajax/index.php" ,
"onDone" : function( result ) {
displayUserData( result.xhr.response );
}
} );
Using the code
Loading subsequent scripts
In order to properly use SecureXHR, the developer has to load at least 4 scripts in the following way:
- the SecureXHR configuration script containing an object with setup options, some of them to be used by all XMLHttpRequest instances. This script is automatically removed from the global scope by SecureXHR as soon as it has been parsed. This step can be skipped if the developer decides to use default values of SecureXHR setup options;
- the SecureXHR script containing the main code of the library;
- one or more scripts which will make use of the XMLHttpRequest object via the SecureXHR interface;
- a clean up script which will delete the SecureXHR interface so as to remove it from the global scope. This step is optional if the developer wants to make the SecureXHR interface available to other scripts which may be executed later on, but leaving this interface in the global scope involves security risks.
For instance:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" charset="utf-8">
<!--
<!--
<script type="text/javascript" src="securexhr_ini.js"></script>
<!--
<script type="text/javascript" src="securexhr.js"></script>
<!--
<script type="text/javascript" src="your_script_using_ajax_1.js"></script>
<script type="text/javascript" src="your_script_using_ajax_2.js"></script>
<!--
<!--
<!--
<script type="text/javascript">
getSecureXHRInterface = undefined;
</script>
<!--
Preparing the SecureXHR setup script
An object with default setup options used to initialise SecureXHR looks like the following:
var _SecureXHRIni = {
"interfaceExposeFuncName" : "getSecureXHRInterface" ,
"deleteWindowXHR" : true ,
"allowXHRViaActiveX" : false ,
"onXHRPrototypeForged" : null ,
"allowOtherSites" : false ,
"modifyXHRToFilterSites" : true ,
"modifyXHRToRequireToken" : true ,
"maxOpen" : 4 ,
"rerunPendingMs" : 25 ,
"testing" : false
};
By default, SecureXHR will look for a variable named _SecureXHRIni
which should contain its setup configuration options. If the object is not found or if it doesn't have some of the fields presented above SecureXHR will use default values for each setup property which has not been defined. If the developer wants to store SecureXHR setup options in a variable of a different name (other than _SecureXHRIni
) he has to modify the last part of the script containing the main SecureXHR code. For example, instead of the original ...})("_SecureXHRIni")
code the developer can change it to something like ...})("SecureXhrSetupOptions")
.
SecureXHR setup options explained in more detail:
interfaceExposeFuncName
(string, tested against the /^[a-zA-Z_\$]{1}[a-zA-Z0-9_\$]*$/
regular expression) - the name a getter function which will be created by SecureXHR in the global scope so as to make its interface available to other scripts. Default value: "getSecureXHRInterface"
. deleteWindowXHR
(boolean) - a flag telling SecureXHR whether or not it should attempt to remove the window.XMLHttpRequest
property so as to hide the XMLHttpRequest object from the global scope in order to increase its security. Such attempt will most likely be successful with Firefox and/or Chrome browsers and fail with Internet Explorer. Default value: true
. allowXHRViaActiveX
(boolean) - a flag telling SecureXHR whether or not it should allow creating XMLHttpRequest instances via the ActiveX object. Setting this option to true
will make SecureXHR work with older Internet Explorer versions, but at the same time, it will be a breach of security, because other safety related options, such as deleteWindowXHR
, onXHRPrototypeForged
, modifyXHRToFilterSites
and modifyXHRToRequireToken
, will become ineffective with older Internet Explorer browsers. Default value: false
. onXHRPrototypeForged
(function) - an optional delegate function which is to be triggered in case SecureXHR detects any unauthorised modification of the XMLHttpRequest.prototype
methods and/or properties (apart from modifications made by SecureXHR itself). SecureXHR will check for XMLHttpRequest.prototype
modifications only in case the XMLHttpRequest object remains available to the global scope. For more details see SecureXHR delegate functions below. Default value: null
. allowOtherSites
(boolean / array) - an option used to apply a site filter. If the value is set to true
no filter will be used. If the value is false
SecureXHR will allow XMLHttpRequest connections to be made only with the current browser window site. If the value is an array containing site names SecureXHR will block outgoing XMLHttpRequest requests to any site which is not on the list (the current browser window site is always allowed by default). If such an array contains at least one badly formed site name SecureXHR will throw an error and the script will be terminated. Default value: false
. modifyXHRToFilterSites
(boolean) - a flag telling SecureXHR whether or not it should modify the XMLHttpRequest.prototype.open
method so that each new XMLHttpRequest instance will internally implement the site filter defined via the allowOtherSites
SecureXHR setup option. Default value: true
. modifyXHRToRequireToken
(boolean) - a flag telling SecureXHR whether or not it should modify the XMLHttpRequest.prototype.open
method so that it will expect a secret token (generated and accessed only by SecureXHR) as its additional (6th) argument. Default value: true
. maxOpen
(unsigned integer) - the maximum number of simultaneous (open) XMLHttpRequest connections. Normally, browsers limit the number of concurrent connections per server, but SecureXHR can only limit the number of all AJAX connection per browser tab (other requests are placed on the pending requests list). The developer might want to set this value just below the limit adopted by the browser to avoid browser hanging (in case the connection limit to a server has been reached and requests cannot be completed any time soon) and prevent potential DoS attacks via XMLHttpRequest connections. Default value: 4
. rerunPendingMs
(unsigned integer) - the time interval (in milliseconds) in which SecureXHR checks whether the number of concurrent connections has fallen below the maxOpen
value (see the point above) so that subsequent requests waiting on the pending list can be run (open). Default value: 25
. testing
(boolean) - a flag telling SecureXHR whether or not to add an extra function to its interface getter method (see information on the interfaceExposeFuncName
setup option above). When this value is set to true
an object returned by getSecureXHRInterface
will contain the priv
function which exposes some private SecureXHR properties which should never be disclosed in the production mode (for more details see getSecureXHRInterface().priv()
function description below). Default value: false
.
A script (securexhr_ini.js
in the example above) containing the SecureXHR setup configuration object (_SecureXHRIni
) does not have to be loaded if the developer decides to adopt default values in case of each setup option. Otherwise, the developer has to define only those setup options whose values should be different. For instance:
var _SecureXHRIni = {
"allowOtherSites" : [
"http://www.yoursite.com" ,
"https://www.yoursite.com" ,
"http://yoursite.com" ,
"https://yoursite.com"
] ,
"maxOpen" : 6
};
In this example, the number of all concurrent connections per browser tab has been limited to 6 and any AJAX connections to sites other than "yoursite.com" will be blocked (http/https and www/no-www variants have to taken into account as the current window address migh be "www.yoursite.com" while a script attempts to access "yoursite.com" - or vice versa - which would not work if only one of these site names were included in the allowOtherSites
filter).
An error will be thrown and SecureXHR will not start if the following setup option problems are encountered:
- the value of the
interfaceExposeFuncName
option is not a valid JavaScript function name - the value of the
interfaceExposeFuncName
option is the same as the name of an existing variable / function - at least one site name listed in the
allowOtherSites
array is malformed
Even if the developer supplies correct values for setup options SecureXHR may still fail to set some of them. This might be especially the case if:
- the developer sets both/either the
modifyXHRToFilterSites
option and/or the modifyXHRToRequireToken
to true
but the browser does not allow SecureXHR to overwrite the XMLHttpRequest.prototype.open
method - the value is set to false
- the developer sets the
deleteWindowXHR
option to true
but the browser does not allow SecureXHR to successfully remove the XMLHttpRequest
object from the global scope - the value is set to false
In order to check what values have been actually adopted by SecureXHR and which user-defined values could not be set the developer has to use the getSecureXHRInterface().info("setup")
method (see SecureXHR interface description below for more details).
Request configuration settings
SecureXHR offers a number of options related to AJAX requests:
onDone
(function) - a mandatory delegate function to be triggered when a request has been completed or terminated for whatever reason. This is the only obligatory parameter which will not accept its default value and has to be defined by the developer - otherwise the getSecureXHRInterface().conf()
or the getSecureXHRInterface().exec()
methods will return an error. For more details see SecureXHR delegate functions below. Default value: null
. onProg
(function) - an optional delegate function to be triggered whenever a computable amount of data has been uploaded and/or downloaded during an asynchronous request. It is useful for measuring and reporting on data upload and/or download progress. For more details see SecureXHR delegate functions below. Default value: null
. onStat
(function) - an optional delegate function to be triggered whenever a request readyState property changes (4 states) or when a request passes into one of its subsequent stages:
- "pending" - placed on the pending requests list
- "setting" - setting up request parameters
- "sending" - attempting to send a request
- "loading" -
XMLHttpRequest.onloadstart
event triggered (or the point just after the XMLHttpRequest.send() method execution reached if the onloadstart event is not supported by the browser) - "closing" - the request has been completed or terminated and a response information object is being prepared
It gives the developer a better idea of what is going on with a running request. For more details see SecureXHR delegate functions below. Default value: null
. onTime
(function) - an optional delegate function to be triggered when an open asynchronous request has not been completed within the number of milliseconds set in the wait
request configuration option (see below). For more details see SecureXHR delegate functions below. Default value: null
. wait
(integer) - the number of milliseconds (counting from the time of the XMLHttpRequest.send
method execution) after which the onTime
delegate method (see above) is to be triggered. The value must be greater than 0. If no onTime
callback is defined SecureXHR will attempt to abort an asynchronous request. Default value: 60000
(i.e. 60 seconds). sameSite
(boolean) - a flag which tells SecureXHR whether or not to force the same protocol, host and port for AJAX connections as the window.location
object points to. It is advised to leave the parameter with its default value (true
), as sometimes users may encounter problems when trying to open an XMLHttpRequest connection to an address like "http://examplesite.com" (no "www" prefix) from something like "http://www.examplesite.com" ("www" prefix used) or vice versa (both attempts would be blocked by the browser for security reasons). Setting this parameter to true will take care of the problem without the developer having to worry about it. When attempting cross-site requests this option should be set to false
. Default value: true
. site
(string) - the full name of a site (protocol, host name and port, if any) to which an AJAX connection is to be made. If sameSite
parameter is set to true the site
option will always be assigned the same protocol, host name and port as the current browser window. Default value: "http://localhost"
. path
(string) - the full name of a path which forms the second part of a URL address to which an AJAX connection is to be made. Default value: "/index.php"
. noCache
(boolean) - a flag which tells SecureXHR whether or not to take some extra measures to prevent request results from being cached by a web browser. If the option is set to true, a Unix timestamp (in milliseconds) and a request counter will be appended to the full URL address of a target site. Default value: true
. reqIdChar
(string) - the name of a variable (appended to the full URL address of a target site) which stores a unique request ID if the noCache
option (see above) is set to true
. Default value: _
(an underscore). method
(string) - the name of a method which is to be employed by a request ("HEAD","GET","POST" and other methods accepted in XMLHttpRequest connections). Default value: "GET"
. ifBadMet
(integer) - the code of an action to be taken on discovering a connection method and connection data inconsistency, i.e. there is either no data to send and the "POST" (precisely: not "GET" or "HEAD") method is used or there is some data to be sent and the "GET" (or "HEAD") method is used. The options are:
- (
getSecureXHRInterface().BAD_METHOD_IGNORE
) no action will be taken on discovering such inconsistencies, but if the user submits some data to send and a connection method is set to "GET" (or "HEAD") SecureXHR will append the data to a target URL address if the data is either of the "string" type or a literal object in the form of { parameter_1 : value_1 , parameter_2 : value_2 }
- such object will be URL encoded if the dataKV
configuration option is set to true
. - (
getSecureXHRInterface().BAD_METHOD_REPAIR
) SecureXHR will force the "GET" method in case there is no data to send (and a method other than "GET" or "HEAD" is set) and the "POST" method in case there is some data to send (and a method set is either "GET" or "HEAD"). - (
getSecureXHRInterface().BAD_METHOD_RESIGN
) SecureXHR exec()
method will return an error if the inconsistency is detected.
Default value: 0
(getSecureXHRInterface().BAD_METHOD_IGNORE
). dataKV
(boolean) - a flag which tells whether or not to treat data to be sent as a literal object of the { parameter_1 : value_1 , parameter_2 : value_2 }
structure so that SecureXHR should convert it into a URI encoded string in the form of "parameter_1=value_1¶meter_2=value_2"
. Default value: true
. showDataRaw
(boolean) - a flag which tells whether or not to include the original (initial, not URI encoded or otherwise modified) data in an object which is passed to the onDone
delegate method. For more details see SecureXHR delegate functions below. Default value: false
. showDataURI
(boolean) - a flag which tells whether or not to include data which has been sent (in the form of a URI encoded string) in an object which is passed to the onDone
delegate method. This option applies only if the dataKV
configuration option is set to true
. For more details see SecureXHR delegate functions below. Default value: false
. getType
(string) - the name of an expected response content type ("arraybuffer"
, "blob"
, "document"
, "json"
, "text"
or ""
- an empty string is the same as "text"). If an option other than "text" or "" is set SecureXHR will attempt to convert the response string into a proper JavaScript object instance (e.g. Document or JSON). Forcing a response type (other than "" or "text") is possible for asynchronous requests only. Default value: "text"
. setHead
(object) - a literal object in the form of { "Header-Name-A" : "Header-Value-A" , "Header-Name-B" : "Header-Value-B" }
which defines request headers to be sent. If the method
option is "POST" and the "Content-Type" header is not defined SecureXHR will set it automatically and assign it the value of "application/x-www-form-urlencoded; charset=UTF-8"
(if window.document.characterSet
or window.document.charSet
defines a different charset it will be adopted accordingly). Default value: null
. getHead
(boolean / array) - a flag which tells whether or not to retrieve response headers:
false
- do not retrieve headers, true
- retrieve all headers as a string, [ "Header-Name-A" , "Header-Name-B" ]
- retrieve selected headers as an object in the form of { "Header-Name-A" : "Header-Value-A" , "Header-Name-B" : "Header-Value-B" }
.
Default value: false
. async
(boolean) - a flag which tells whether or not a request should be asynchronous. The developer must be aware of dangers and limitations related to synchronous requests (no way to abort or time out a request, problems related to benchmarking or monitoring a request, the possibility of browser window freezing). Default value: true
. newXHR
(boolean) - a flag which tells whether to create a new XMLHttpRequest
object instance or use a singleton XMLHttpRequest
instance to run a request. A singleton instance can handle only one request at a time so all other requests with the newXHR
option set to true
are placed on the pending list (requests using new instances can also be queued but only if the number of all concurrent requests reaches the limit defined by the maxOpen
SecureXHR setup option - see above). For this reason, using new XHR instances for requests results in much faster performance. However, in some cases, relying on a singleton instance is preferable, e.g. when the developer wants to make sure that separate AJAX requests are run subsequently and not simultaneously. Default value: true
. token
(any type) - a temporary (single cycle use) temporary password required to access those methods of the SecureXHR interface which make it possible to terminate a request or obtain information of its current state. If not supplied SecureXHR will generate a relatively strong password itself. Default value: null
. login
and password
(string) - a login and a password to be used with a request if accessing a target address requires authentication. In practice, this information is prepended to the host name, e.g. "http://login:password@examplesite.com/path/". Default value (in both cases): undefined
. cookies
(boolean) - a flag which tells whether or not a cross-site Access-Control request should be made using credentials (cookies or authorization headers). The value applies to asynchronous requests only and does not affect same-site requests. For a cross-origin request to be successful (no matter what value is assigned to this option) the server has to respond with Access-Control-Allow-Origin
header which may have either of the two values:
"*"
(an asterisk) meaning that any site is allowed to access a given resource. However, using the "*"
value won't be enough if a request is made with credentials, i.e. when the cookies
flag is set to true
; - a site (or a list of sites) allowed to access the content (e.g.
"Access-Control-Allow-Origin: http://foo.example"
). Such a solution should be applied if a request is made with credentials, i.e. when the cookies
flag is set to true
).
Moreover, for this option to work, the server must respond with the "Access-Control-Allow-Credentials: true"
header. Default value: false
.
Interface methods (API)
SecureXHR interface is returned by a function of the name defined in the interfaceExposeFuncName
option of the setup configuration object (see information on preparing the SecureXHR setup script above for more details), e.g.:
var sxhr = getSecureXHRInterface();
For security reasons the getSecureXHRInterface
method should be deleted from the global scope as soon as no longer needed. The SecureXHR API consists of the following methods:
info()
- obtain some information on SecureXHR conf()
- define a new request configuration (reusable) exec()
- execute a request (setup, open, send, get data) stat()
- get the current state of an ongoing request stop()
- try to abort an ongoing request priv()
- get access to some private methods (in the "testing" mode)
and constants:
BAD_METHOD_IGNORE
- to be used with the ifBadMet
request configuration option (see the the "Request configuration settings" section) BAD_METHOD_REPAIR
- see above BAD_METHOD_RESIGN
- see above
More detailed description with examples:
info( string type_of_information )
- used to retrieve some SecureXHR specific data like the SecureXHR version (supplying "version"
as an argument) or SecureXHR setup process results (passing "setup"
as an argument). Examples:
var secureXhrVersion = sxhr.info( "version" );
var secureXhrSetupResults = sxhr.info( "setup" );
conf( object configuration_settings )
- used to create a new validated and reusable AJAX configuration set to be applied in multiple requests. The argument should be a literal object containing request configuration options and their values as described in the "Request configuration settings" section (see above). If an option is not defined it will be assigned its default value. The method returns an object containing three fields:
err
which itself includes an object with three fields:
fatal
- false
if no request configuration errors encountered, otherwise true
, msg
- null
or a request configuration error message generated by SecureXHR, at
- null
or the name of a request configuration option whose value is unacceptable,
inf
which itself includes an object with one field:
cfgId
- the ID assigned to a new request configuration set (it is later used as an argument of the exec
method - see below) - in case of an error cfgId
is given value 0
which points to the default request configuration options set,
cfg
which itself includes an object containing the names all possible request configuration options and values which have been assigned to them.
Examples:
var myConfig =
{
"onDone" : setAtomicTime ,
"wait" : 3000 ,
"onTime" : xhrTimedOut ,
"sameSite" : false ,
"site" : "http://www.getatomictime.com"
"path" : "/getAsTimestamp.php" ,
}
var myConfigXhr = sxhr.conf( myConfig );
if( myConfigXhr.err.fatal )
{
alert( "An AJAX request configuration error encountered: " + myConfigXhr.err.msg );
return;
}
exec( string | object data_to_send , integer configuration_settings_id , object additional_configuration_settings , string unique_request_name )
- used to execute an AJAX request (define XMLHttpRequest event listeners' functions, open a connection, send data, collect information etc.). The method takes three arguments:
- obligatory: data to be sent - if empty (null, {}, [], undefined, NaN, 0, "", false) it will be converted to
null
. Otherwise, the data should be an object or a URI encoded string. If the data is a literal object of the { parameter_1 : value_1 , parameter_2 : value_2 }
structure and the dataKV
configuration option is set to true
SecureXHR will convert the object into a URI encoded string in the form of "parameter_1=value_1¶meter_2=value_2"
, - obligatory: a configuration setting ID which defines parameters to be used with the request - this is the id found in an object (
{ inf : { cfgId : VALUE } }
) returned by the getSecureXHRInterface().conf()
method (see the conf
method description above for more details), however 0
should be used as the value if the default SecureXHR request configuration settings are to be used as basic settings for the request, - optional: some additional temporary configuration settings (in the form of
{ option_1 : value_1 , option_2 : value_2 }
) to be used with this particular request only, i.e. these settings overwrite values defined and created by the conf
method (see the "Request configuration settings" section above for more details), - optional: a unique request name whose purpose is to recognise and/or distinguish between various requests which might share the same
"onDone"
(or any other: "onStat"
, "onProg"
, "onTime"
) delegate function - this name is later to be found in the inf.name
field of the object passed to the "onDone"
method (the same is true for "onStat"
, "onProg"
and "onTime"
).
The function returns an object of the following structure:
err
which itself includes an object with three fields:
fatal
- a flag set to false
if there are no additional request configuration errors and an XMLHttpRequest instance is obtained, otherwise true
, msg
- a message with error details or null
if no error encountered, at
- the name of a step at which the problem has been encountered, if any, otherwise null
,
ids
which itself includes an object with three fields:
id
- the ID (the number / counter) of the current XMLHttpRequest
object instance (a singleton instance has always ID 1
), cycle
- the number of all requests (including the current one) which have been performed via the instance of a given ID (non-singleton instances will always have their cycle value 1
as they are destroyed as soon as the request has been dealt with), token
- a secret temporary (one cycle use) password needed to terminate a request or access information on its current state (see the token
configuration option description above or information on stop()
and stat()
methods below for more details),
Examples:
var xhrInitiated = sxhr.exec( null , myConfig.inf.cfgId );
var xhrInitiated = sxhr.exec( null , myConfig.inf.cfgId , null , "getFileDescr" );
var xhrInitiated = sxhr.exec( null , myConfig.inf.cfgId , {
"sameSite" : true ,
"path" : "/log_activity.php"
} );
var xhrInitiated = sxhr.exec( { timezone : -2 } , myConfig.inf.cfgId );
var xhrInitiated = sxhr.exec( { "img_id" : 4253 } , myConfig.inf.cfgId , {
"getType" : "json" ,
"path" : "/get_img_by_id.php"
} );
var xhrInitiated = sxhr.exec( { "img_id" : 4253 } , 0 , {
"getType" : "json" ,
"path" : "/get_img_by_id.php"
} );
if( xhrInitiated.err.fatal )
{
alert( "AJAX request could not be initiated. Error message: " + xhrInitiated.err.msg );
}
stat( integer xhr_instance_id , integer xhr_instance_cycle_nr , any xhr_instance_token )
- used to obtain information on the current state of an ongoing request. The method takes three arguments which all can be obtained from an object returned by the getSecureXHRInterface().exec()
method (see above):
- obligatory: the ID / number / counter of an XMLHttpRequest instance - this is the id found in an object (
{ ids : { id : VALUE } }
) returned by the exec
method, - obligatory: the number / counter of cycles an XMLHttpRequest instance has been used to run a request - this is the cycle number found in an object (
{ ids : { cycle : VALUE } }
) returned by the exec
method, - obligatory: a secret token associated with a given XMLHttpRequest instance cycle - this is the token found in an object (
{ ids : { token : VALUE } }
) returned by the exec
method,
The function returns an object of the following structure:
xhr
which itself includes an object with one field:
readyState
- the current readyState property value of an ongoing request - normally the value is an integer (1, 2, 3 or 4), but if a request is not running it will hold value 0
,
inf
which itself includes an object with one fields:
maxStage
- the current stage of an ongoing request as defined by SecureXHR - normally the value is one of the set: "pending"
, "setting"
, "sending"
, "loading"
, "closing"
. However, if a request is not running it will hold value ""
(an empty string).
States and stages are registered typically in the following order:
- readyState 0: marked by SecureXHR right after an XMLHttpRequest instance is created and/or ready
- "pending" stage: recorded not long after readyState 0 (usually the same millisecond) just before a request is either placed on the pending list or prepared for execution (binding event listeners' methods etc.)
- "setting" stage: recorded as soon as a request setup process begins (preparing for execution) - this might happen in the same millisecond as the two previous states/stages if there are no requests on the pending list
- readyState 1: recorded right after the
XMLHttpRequest.open()
method is invoked - might happen in the same millisecond as the "setting" stage if the browser is fast enough - "sending" stage: recorded just before the
XMLHttpRequest.send()
method is invoked - usually takes place in the same millisecond as the readyState 1 is recorded - "loading" stage: recorded as soon as the
XMLHttpRequest.onloadstart
event is triggered or right after the XMLHttpRequest.send()
method is invoked - often takes place in the same millisecond as the "sending" stage, especially if there is no data to send - readyState 2: recorded as soon as response headers are received from the server
- readyState 3: recorded as soon as some partial response data is received from the server
- readyState 4: recorded as soon as the request is complete and response data is available (this state is not always reached in some browsers in case of certain errors)
- "closing" stage: recorded as soon as the request has been completed and response data is about to be parsed and converted, if required - usually takes place in the same millisecond as readyState 4 (if reached)
Example:
var currentXhrState = sxhr.stat( xhrInitiated.ids.id , xhrInitiated.ids.cycle , xhrInitiated.ids.token );
if( currentXhrState.xhr.readyState === 1 && currentXhrState.inf.stage === "loading" )
{
setTimeout( function(){
sxhr.stop( xhrInitiated.ids.id , xhrInitiated.ids.cycle , xhrInitiated.ids.token )
} , 10000 );
}
stop( integer xhr_instance_id , integer xhr_instance_cycle_nr , any xhr_instance_token )
- used to try to terminate an ongoing request. The method takes three arguments which all can be obtained from an object returned by the getSecureXHRInterface().exec()
method (see above). These are the very same arguments which are necessary to invoke the getSecureXHRInterface().stat()
method (see the description above for more details). The function returns true
if a request termination is possible and has been started, otherwise the function returns false
. The method is unsuccessful in either of the three cases:
- a request is synchronous
- the "loading" stage has not yet been reached
- the request has been completed
Example:
var xhrTerminated = sxhr.stop( xhrInitiated.ids.id , xhrInitiated.ids.cycle , xhrInitiated.ids.token );
if( xhrTerminated )
alert( "The request has been aborted" );
else
alert( "Could not abort the request" );
priv( string secure_xhr_private_property_type )
- used to get access to some SecureXHR private properties which may be necessary for debugging purposes. By default, this method returns undefined
. However, if the _SecureXHRIni.testing
option is set to true
(see the "Preparing the SecureXHR setup script" section for more details) the method returns a number of private SecureXHR properties, depending on their name defined in the argument:
"settingsGroups"
- an array containing all request configuration sets. The default request configuration settings are stored at index 0, other configuration settings are stored at indices of greater values. Index values are typically obtained via getSecureXHRInterface().conf( object ).inf.cfgId
; "xhrInstances"
- an array containing all existing SexureXHR instances. The singleton instance, if created, is stored at index 1, other instances are stored at indices greater than 1 (they are deleted as soon as the request has been done with so there will be typically gaps in the array). Index values are typically obtained via getSecureXHRInterface().exec( object , integer , object ).ids.id
. Each index of the xhrInstances
array (in case of existing instances) contains an object with two fields:
xhr
- at which an XMLHttpRequest instance object is stored; - an instance cycle number - which stores an object containing all the necessary information related to a given request (this is the cycle number which is typically obtained via
getSecureXHRInterface().exec( object , integer , object ).ids.cycle
);
"runningXhrsNr"
- the number of currently open XMLHttpRequest connections; "singleton"
- the object containing information on an XMLHttpRequest singleton instance. It has the following fields:
cycles
- the number of times the singleton instance has been used to perform a request (0 if no singleton instance created); current
- the number of the current singleton instance cycle (0 if no currently running request using the singleton instance); next
- the number of the next cycle of the singleton instance waiting to be dealt with; pending
- an object containing all pending cycles' numbers waiting for their execution via the singleton instance;
"xhrOpenToken"
- the value of a secret token to be used as an additional argument of the XMLHttpRequest.open()
method (required if the _SecureXHRIni.modifyXHRToRequireToken
option is set to true
- see the "Preparing the SecureXHR setup script" section for more details); "xhrSeq"
- the ID of the most recently created non-singleton XMLHttpRequest instance.
Examples:
var lastXhrId = sxhr.priv( "xhrSeq" );
var allXhrs = sxhr.priv( "xhrInstances" );
if( allXhrs[ lastXhrId ] )
{
console.log( allXhrs[ lastXhrId ]["1"] );
}
SecureXHR delegate functions
Request configuration settings (see above) allow to define the following delegate functions:
"onDone"
- obligatory, to be triggered as soon as the request has been done with or terminated "onProg"
- optional, to be triggered as soon as some computable amount of data has been uploaded or downloaded "onStat"
- optional, to be triggered as soon as a request changes its ready state or stage as defined by SecureXHR "onTime"
- optional, to be triggered as soon as the request has been timed out
More detailed description:
"onDone"
- the most important delegate function - it is triggered as soon as the request has been dealt with. The method is passed just one parameter - a literal object consisting of the following fields:
cfg
- configuration request settings applied to the request (see the "Request configuration settings" section and the SecureXHR interface conf()
method description); err
- information on potential errors encountered while preparing and performing the request:
fatal
- false
if the request ended in success, true
if there occurred a critical error resulting in failure msg
- null
or a request runtime error message generated either by XMLHttpRequest or by SecureXHR at
- null
or the name of an XMLHttpRequest method which caused the problem type
- null
or one of the following error types:
"error"
- an error (other than "abort" and "timeout") generated by XMLHttpRequest "exception"
- an unexpected error generated by SecureXHR "timeout"
- information about the request having been timed out "abort"
- information about the request having been aborted "parsererror"
- information about problems converting response data to a desired response type (usually "xml" or "json")
ids
- values (id
, cycle
and token
) which help identify and access the request (see the SecureXHR interface exec()
method description) inf
- additional information about the request:
name
(string) - an optional request name which might be useful to recognise and/or distinguish various requests which might share the same "onDone"
delegate function (this is the name which is the optional 4th argument of the getSecureXHRInterface().exec()
method) cfgId
(object) - a request configuration settings ID which served as a basic configuration for the request (its values may have been overwritten via the exec()
method) noData
(boolean) - whether or not SecureXHR passed null
as the argument of the XMLHttpRequest.send()
method (even if the developer supplied some data in the form of a literal object it may have been URI encoded and appended to the URL address if the "GET"
method was chosen, the dataKV
request configuration option was set to true
and the ifBadMet
was set to 0
, i.e. getSecureXHRInterface().BAD_METHOD_IGNORE
) data
(object | string) - original data to have been sent which was supplied as the first argument of the exec()
method; null
if the showDataRaw
request configuration option has been set to false
dataURI
(string) - data to have been sent in the form of a URI encoded string; null
if either the showDataURI
or the dataKV
request configuration options have been set to false
url
(string) - the complete URL address which was passed as an argument to the XMLHttpRequest.open()
method autoHeaders
(object) - request headers (and their values), if any, which were automatically set by SecureXHR resHeaders
(string | object) - response headers; this property's value depends on the getHead
request configuration option states
(array) - an array with indices of 0 to 4 which correspond to the 5 XMLHttpRequest ready states whose values are Unix timestamps recorded when the request passed through the subsequent states stages
(object) - an object with indices which correspond to request stages recognized by SecureXHR ("pending", "setting", "sending", "loading", "closing") whose values are Unix timestamps recorded when the request passed through the subsequent stages maxStage
(string) - the maximum stage reached during the request (see the previous point) uploaded
(integer) - the number of bytes uploaded during the request (measured if the "onProg"
delegate function is set) downloaded
(integer) - the number of bytes downloaded during the request (excluding response headers) timeoutId
(integer) - the ID returned by the setTimeout
method which is invoked by SecureXHR to implement the request timeout functionality; if the value is greater than 0
it means that the request had been completed before the time when the function defined in the setTimeout
method was to be triggered requestId
(integer) - the internal request counter used by SecureXHR, employed primarily to effectively implement the no-cache mechanism
xhr
- contains copies of some properties of the XMLHttpRequest instance responsible for performing the request:
readyState
response
responseType
status
statusText
Example (here all requests share the same delegate function triggered once they have been completed - that's why a container with request ids+cycles and their associated names is created -, but the developer can choose to define different "onDone"
methods for various requests):
var xhrActions = {};
function getTopProd()
{
var xhrInitiated = sxhr.exec( null , 0 , {"onDone":xhrDone, "path":"/xhr/action_id=374"} );
xhrActions[ xhrInitiated.ids.id + ":" + xhrInitiated.ids.cycle ] = "getTopRatedProduct";
}
function discountProd( productId, discountPercent )
{
var xhrInitiated = sxhr.exec(
{ prodId=productId , discPerc=discountPercent } ,
0 ,
{"onDone":xhrDone, "path":"/xhr/action_id=24"}
);
xhrActions[ xhrInitiated.ids.id + ":" + xhrInitiated.ids.cycle ] = "discountProduct";
}
getTopProd();
function xhrDone( respObj )
{
switch( xhrActions[ respObj.ids.id + ":" + respObj.ids.cycle ] )
{
case "getTopRatedProduct":
if( respObj.err.fatal )
{
alert( "Could not obtain the top rated product (error: " + respObj.err.msg + ")" );
return;
}
discountProd( respObj.xhr.response , 15 );
break;
case "discountProduct":
if( respObj.err.fatal )
{
alert( "Could put the top rated product on a discount (error: " + respObj.err.msg + ")" );
return;
}
break;
}
}
"onStat"
- it is triggered as soon as the request has changed its ready stage or stage (as defined by SecureXHR: "pending"
, "setting"
, "sending"
, "loading"
, "closing"
). The method is passed just one parameter - a literal object consisting of the following fields (a reduced version of the object passed to the "onDone"
delegate function - see its description for more details):
ids
- values (id
, cycle
and token
) which help identify and access the request (see the SecureXHR interface exec()
method description) inf
- additional information about the request:
name
(string) - an optional request name which might be useful to recognise and/or distinguish various requests which might share the same "onStat"
delegate function (this is the name which is the optional 4th argument of the getSecureXHRInterface().exec()
method) maxStage
(string) - the maximum stage reached during the request
xhr
- contains copies of some properties of the XMLHttpRequest instance responsible for performing the request:
Example:
var inputWord = document.getElementById("search_word").value;
var ajaxInfoLabel = document.getElementById("search_info");
var xhrIni = sxhr.exec(
{word:inputWord, lang:"English"} ,
0 ,
{"onDone":xhrSerachComplete, "onStat":xhrInfo, "path":"/search.php"} ,
"Searching for the word '" + inputWord + "'"
);
function xhrInfo( infoObj ) {
ajaxInfoLabel.innerHTML = infoObj.inf.name + " (stage: " + infoObj.inf.maxStage + ")";
};
"onProg"
- it is triggered as soon as some computable amount of data has been uploaded or downloaded during an asynchronous request. The method is passed just one parameter - a literal object consisting of the following fields:
ids
- values (id
, cycle
and token
) which help identify and access the request (see the SecureXHR interface exec()
method description) inf
- additional information about the request:
name
(string) - an optional request name which might be useful to recognise and/or distinguish various requests which might share the same "onProg"
delegate function (this is the name which is the optional 4th argument of the getSecureXHRInterface().exec()
method) lengthComputable
(boolean) - whether or not the total size of uploaded or downloaded data is known total
(integer) - the total size of an uploaded or downloaded file (in bytes) loaded
(integer) - the amount of bytes which have been uploaded or downloaded so far during the request download
(boolean) - whether it is an upload or download process
Example:
function submitCanvasImg()
{
var imgUrlString = imgCanvas.toDataURL("image/png");
sxhr.exec(
imgUrlString ,
0 ,
{
"method" : "POST" ,
"path" : "/save_new_img.php" ,
"onDone" : function( responseObj )
{
if( responseObj.err.fatal || !responseObj.xhr.response )
alert( "Failed to save the image on the server" );
else
alert( "The image has been successfully saved" );
}
"onProg" : function( progressObj )
{
if( progressObj.inf.download )
return;
setProgressBarPercentage( progressObj.inf.loaded * 100 / imgUrlString.length );
}
}
);
}
"onTime"
- it is triggered if an asynchronous request has not been completed within the number of milliseconds defined in the wait
request configuration option. If the browser is busy with executing some consuming JavaScript code the "onTime"
function may occasionally not be invoked despite the timeout period having been reached - this might happen especially on slower machines. The method is passed just one parameter - a literal object consisting of the following fields:
ids
- values (id
, cycle
and token
) which help identify and access the request (see the SecureXHR interface exec()
method description) inf
- additional information about the request:
name
(string) - an optional request name which might be useful to recognise and/or distinguish various requests which might share the same "onTime"
delegate function (this is the name which is the optional 4th argument of the getSecureXHRInterface().exec()
method)
cfg
- some request configuration options:
wait
(integer) - the number of milliseconds (counting from the time of the XMLHttpRequest.send
method execution) after which the "onTime"
delegate method is to be triggered.
Example:
function checkRemoteHost( hostName , continueAtCallback )
{
var ini = sxhr.exec(
null ,
0 ,
{
"method" : "HEAD" ,
"path" : "" ,
"sameSite" : false ,
"site" : hostName ,
"wait" : 1000 ,
"onDone" : function( responseObj )
{
if( responseObj.err.fatal )
continueAtCallback( false );
else
continueAtCallback( responseObj.inf.stages.closing - responseObj.inf.stages.sending );
}
"onTime" : function( timeoutObj )
{
var statObj = sxhr.stat( ini.ids.id , ini.ids.cycle , ini.ids.token );
var stopped = false;
if( confirm( "Checking host '" + hostName +
"' - current ready state: " + statObj.xhr.readyState + ". Terminate?") )
{
stopped = sxhr.stop( ini.ids.id , ini.ids.cycle , ini.ids.token );
}
if( !stopped )
{
setTimeout( function(){ arguments.callee() } , 1000 );
}
}
}
);
if(ini.err.fatal)
throw new Error( "Could not initialize an AJAX request. Error message: " + ini.err.msg );
}
Browser compatibility
SecureXHR works perfectly with most current browser versions. However, some browsers do not support all functionalities offered by SecureXHR, like overwriting XMLHttpRequest object prototype methods or reporting on data upload progress. This is especially true of Internet Explorer and many older versions of popular browsers. Nevertheless, SecureXHR is still usable with older browsers (most unit tests end in success):
- Mozilla Firefox 3.6.17 - passes 131 out of 135 unit tests - 97%
- Google Chrome 13 - passes 135 out of 135 unit tests - 100%
- Internet Explorer 7 - passes 125 out of 135 unit tests - 93%
- Safari 4 - passes 131 out of 135 unit tests - 97%
- Opera 10.61 - passes 127 out of 135 unit tests - 94%
SecureXHR unit tests files and instructions on running the test are included in the complete SecureXHR package.
History
2013.06.09 - version 3.2013.06.09
- setting the default request
site
option to the window site name - placing analysis of the
sameSite
option before the site
option in order to overwrite the site
parameter if necessary - correcting the
site
option validation - adding the
reqIdChar
request configuration option (in order to prevent passing a numeric request ID as a parameter name in a URL string as numeric names are not allowed in some environments, e.g. Joomla) - adding examples showing usage of the default request configuration settings
2013.04.13 - version 2.2013.04.13
- effectively removing the
XMLHttpRequest
object from the global scope in Google Chrome, if required - SecureXHR constants added:
BAD_METHOD_IGNORE
, BAD_METHOD_REPAIR
, BAD_METHOD_RESIGN
(to be used with the ifBadMet
request configuration option) - removing minor errors from the code and the documentation
2013.03.08 - version 1.2013.03.08