Content-Security-Policy HTTP Header

There’s yet another new means to ‘help’ client User-Agents with preventing XSS on your websites.

In it’s simplest form you can simply use the following HTTP Header(s), the second one is for earlier versions of Webkit (Chrome/Safari):

Content-Security-Policy: default-src 'self'
Webkit-CSP: default-src 'self'

You can also add to the above to permit assets to load from other sources.
For example, if you were to permit javascript files from example.com you could include:

Content-Security-Policy: default-src 'self'; script-src http://example.com

Additionally, while failures are noted in the client’s browser console (that most users are not aware of), you can have them sent back to your server by adding a ‘report-uri’ attribute with an appropriate handler:

Content-Security-Policy: default-src 'self'; report-uri http://example.com/csp-report.php

REFERENCES:

CSS Cursors

The use of CSS cursors within your browser based application or website is a great way to add feedback to the user to increase usability. This is increasingly important for AJAX applications that may be “busy” even when the user is not directly taking action within their browser.

These are all easily appended to classes in your CSS files:

  • default
  • auto
  • inherit
  • pointer
  • crosshair
  • text
  • help
  • move
  • progress
  • wait
  • e-resize
  • ne-resize
  • n-resize
  • nw-resize
  • w-resize
  • sw-resize
  • s-resize
  • se-resize

Partial (CSS3) support in current browsers:

  • none
  • all-scroll
  • context-menu
  • cell
  • vertical-text
  • alias
  • copy
  • no-drop
  • not-allowed
  • col-resize
  • row-resize
  • ew-resize
  • ns-resize
  • nesw-resize
  • nwse-resize

NOTE: for very old browser, you can also set several attributes to allow for the supported one to be used.

.example {
cursor:hand;/* IE5-IE5.5 only support (dropped in IE9) */
cursor:pointer; /* IE6 and later */
}

REFERENCES:

Cross-Origin Resource Sharing (CORS) Header

Crossdomain access can be enabled in JavaScript with a mechanism similar to that in Flash. Instead of hosting a crossdomain.xml file though, crossdomain access is enabled per file, through an additional HTTP response header:

Access-Control-Allow-Origin: *

CORS is a more modern equivalent to JSONP for cross-domain XmlHttpRequests(AJAX) with options to limit domains, subdomains and ports.

Initial browser support:

  • Firefox 3.5
  • Chrome 4
  • Safari 3.2
  • MSIE 8

REFERENCES:

Dynamic Site Navigation with AJAX

Recently a navigation challenge that I encountered was “better” resolved by implementing an AJAX “like” solution. While not a complete AJAX solution, this works by requesting and inserting HTML into the DOM dynamically. The HTML could even be the same content that is used when the page was initially generated allowing for the initial page render to be static HTML, and only the options to be dynamic.

This is an extension on my previous ‘Flexible AJAX Framework’ entry and adds the following features and methods:

  • Two column example page layout.
  • Page level caching of AJAX Responses.
  • JavaScript methods:
    • var pageLoadTime; – variable for page level caching.
    • function ajaxObjPageCache(obj,url,async,callback) – method for page level caching.
    • function menuAjaxHook(obj,customObj); – response handler that updates UI.
    • function xinnerHTML(id,txt); – this is a common component used by the example.
    • function testAjaxMenu(obj,menu); – test method for example.

The full example (in PHP) follows:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>AJAX Menu Example</title>
<script type="text/javascript">
var xbusy = true;
var xhr = null;
var otherHost = "http://example.giantgeek.com"; // do not use training slash!
var pageLoadTime = xmillis();// set this once per page for some caching solutions!
/**
* Check for existance on DOM browsers (Mozilla, etc.)
* @return xhr
*/
function ajaxCheckDOM(){
var myxhr = null;
if(window.XMLHttpRequest) {
try {
myxhr = new XMLHttpRequest();
}
catch(e) {}
}
return myxhr;
}
/**
* Check for existance on Windows/MSIE (prior to MSIE7 which is now DOM)
* Evaluate using - new ActiveXObject("Microsoft.XMLDOM");
* @return xhr
*/
function ajaxCheckActiveX(){
var myxhr = null;
//if(window.ActiveXObject){
try {
myxhr = new ActiveXObject("Msxml2.XMLHTTP");
}
catch(e) {
try {
myxhr = new ActiveXObject("Microsoft.XMLHTTP");
}
catch(e) {}
}
//}
return myxhr;
}
/**
* This is a default response hook for AJAX, you SHOULD create your own as it's only for DEMO
* @param obj - the clicked item
* @param customObj - user defined
*/
function ajaxResponseHook(obj,customObj){
var txt = xhr.responseText;// NOTE: xhr.responseXML is also valid
popStatus('AJAX Response value [' + txt + '|' + customObj +']','');
}
/**
* This is a common Response handler AJAX, you SHOULD NOT need to modify, use a 'custom' ajaxhook function to process the responses.
* @param obj - the clicked item
* @param ajaxhook Function (default will be used if undefined)
* @param customObj (optional - left for developer implementation, passed to the ajaxhook)
*/
function ajaxResponse(obj,ajaxhook,customObj){
var status='';
if(xhr!=undefined){
var state=xhr.readyState;
if(state!=undefined){
if(state==4){
var code = xhr.status;
status = xhr.statusText;
if(code==200){
popStatus('AJAX Response Received.','');
if(ajaxhook==undefined){
ajaxhook = function(){ ajaxResponseHook(obj,customObj); }
}
ajaxhook(obj,customObj);
ajaxBusy(obj,false);
} else {
popStatus('AJAX Error ' + code + ' ' + status + '.','');
}
ajaxBusy(obj,false);
xhr=null; /* MSIE6 leak fix */
}
}
}
}
/**
* NOTE: MSIE6-7 supports XSS url's (be careful!)
* @param obj - the clicked item
* @param url - the GET url params (FQDN)
* @param async (true or false) - false will LOCK browser until response - use cautiously!
* @param callback Function - allows for customized response handling
*/
function ajaxObj(obj,url,async,callback){
if(xbusy == true){
popStatus('AJAX BUSY, Please Retry.','');
} else {
if(callback==undefined){
callback = function(){ ajaxResponse(obj,hook,''); }
}
ajaxInit(obj,url,async,callback);
}
}
/**
* This is a Non-Caching implementation of ajaxObj() to show how you can avoid the MSIE caching issue.
* NOTE: You can use similar approaches to cache per page or session.
* @param obj - the clicked item
* @param url - the GET url params (FQDN)
* @param async (true or false) - false will LOCK browser until response - use cautiously!
* @param callback Function - allows for customized response handling
*/
function ajaxObjNoCache(obj,url,async,callback){
var cacheBuster=uniqueUrl(url);// damn MSIE!
ajaxObj(obj,cacheBuster,async,callback)
}
/**
* This is a Page-Caching implementation of ajaxObj() to allow for requests to be cached per pageload.
* @param obj - the clicked item
* @param url - the GET url params (FQDN)
* @param async (true or false) - false will LOCK browser until response - use cautiously!
* @param callback Function - allows for customized response handling
*/
function ajaxObjPageCache(obj,url,async,callback){
var cacheBuster=urlAppender(url,'.cache',pageLoadTime); // pageLoadTime should remain unchanged for page life.
ajaxObj(obj,cacheBuster,async,callback)
}
/**
* Initializes the AJAX operation
* @param obj - item clicked
* @param url - FQDN
* @param async (true/false) - for locking
* @param callback Function - provided for customization.
*/
function ajaxInit(obj,url,async,callback){
xhr=ajaxCheckDOM();
if(!xhr){
xhr=ajaxCheckActiveX();
}
if(xhr){
ajaxBusy(obj,true);
popStatus('AJAX Start.','');
xhr.onreadystatechange = callback; // method call
try {
xhr.open('GET',url,async); /* POST */
//xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
xhr.send(''); /* null */
}
catch(e)
{
if(xhr){
var code = xhr.status;
var status = xhr.statusText;
popStatus('AJAX Client SECURITY ' + navigator.appName +' '+ navigator.appVersion + ' ' + code + ' ' + status + '.','');
}
}
ajaxBusy(obj,false);
} else {
popStatus('AJAX Client ERROR.','');
}
}
function ajaxBusy(obj,yn){
if(yn){
swapStyleObj(obj,'idle','busy');
} else {
swapStyleObj(obj,'busy','idle');
}
xbusyInd(yn,'');
}
function xmillis(){
return new Date().getTime();
}
function swapStyleObj(obj,oldCSS,newCSS){
if(obj!=undefined) {
var current=obj.className;
if(current!=undefined){
var txtOld = current.replace(newCSS,' ');//no doubles
var txtMid = txtOld.replace(oldCSS, ' ');
var txtNew = (txtMid + ' ' + newCSS);
obj.className = txtNew;
} else {
obj.className = newCSS;
}
}
}
function xbusyInd(valnew, msg){
xbusy = valnew;
if(xbusy==true){
document.body.style.cursor='wait';
if(msg != ''){
top.window.defaultStatus=msg;
popStatus(msg,'');
}
showDiv('throbber');
//blockScreen();
} else {
document.body.style.cursor='default';
hideDiv('throbber');
//unBlockScreen();
}
}
function showDiv(id) { //show a div
var obj=xgetHelper(id);
if(obj!=null){ obj.style.display="block";}
//xrestart();
}
function hideDiv(id) { //hide a div
var obj=xgetHelper(id);
if(obj!=null){ obj.style.display="none";}
//xrestart();
}
function popStatus(txt,title){
popit('statusdyn','statusarrow','statusul','statusdiv',txt,false,title,'');
}
function popit(dynId,arrowId,ulId,divId,txt,expand,title,cssCls){
popText(ulId,txt,title,cssCls);
showDiv(divId);
if(expand == true){
var arrowObj=xgetHelper(arrowId); if(arrowObj!=null){ arrowObj.style.backgroundPosition='2px -106px'; }
showDiv(dynId);
}
}
function popText(id,txt,title,cssCls){
var obj = xgetHelper(id);
if(obj != null){
var oldHTML = obj.innerHTML;
var cls=''; if(cssCls!=''){ cls=' class="' + cls +'"'; }
var htm = '<'+'li'+ cls +'>' + txt + '<'+'/'+'li'+'>' + oldHTML;
obj.innerHTML = htm;
}
}
function xgetHelper(id){
var obj = null;
try {
obj = document.getElementById(id);
} catch(z) {
var dummy=alert("Error:" + z);
}
return obj;
}
function arrowTog(objectID,arrow) {
var obj = xgetHelper(objectID);
if(obj!=null){
if (obj.style.display =='block') {
arrow.style.backgroundPosition = '2px -21px';}
else {arrow.style.backgroundPosition = '2px -106px';}
objTog(objectID);
}
return false;
}
function objTog(objectID) {
var obj = xgetHelper(objectID);
if(obj!=null){
if (obj.style.display =='block') obj.style.display='none';
else {obj.style.display='block';}
}
return false;
}
function headTog(objectID,arrow) {
var obj = xgetHelper(objectID);
if(obj!=null){
if (obj.style.display =='block') {
arrow.style.borderBottomWidth = '1px';}
else {arrow.style.borderBottomWidth = '0px';}
arrowTog(objectID,arrow);
}
return false;
}
/*
* adds timestamp to URLs to make them unique
* @param URL String
*/
function uniqueUrl(x){
return urlAppender(x,'.cache',xmillis());
}
/*
* helps to add parms to the url
* @param URL String
* @param aname String
* @param avalue String
*/
function urlAppender(x,aname,avalue){
var delim = "?";
if(x.indexOf("?") >=0) { delim = "&"; }
return x + delim + aname + '=' + avalue;
}
function xload(){
hideDiv('throbber');
xbusy=false;
}
function testAjax(obj){
var callback=function(){ ajaxResponse(obj,null,'testAjax'); }
var x = ajaxObjNoCache(obj,'/ajax.php',true,callback);
}
function testAjaxParms(obj){
var callback=function(){ ajaxResponse(obj,null,'testAjaxParms'); }
var x = ajaxObjNoCache(obj,'/ajax.php?testing=Y',true,callback);
}
function testAjaxXSS(obj){
var callback=function(){ ajaxResponse(obj,null,'testAjaxXSS'); }
var x = ajaxObjNoCache(obj,otherHost+'/ajax.php',true,callback,otherHost);
}
function testAjaxHook(obj){
var hook=function(){ customAjaxHook(obj,'ajaxhookTestMessageObect'); }
var callback=function(){ ajaxResponse(obj,hook,'testAjaxHook'); }
var x = ajaxObjNoCache(obj,'/ajax.php',true,callback);
}
function testAjaxHookXSS(obj){
var hook=function(){ customAjaxHook(obj,'ajaxhookXSSMessageObect'); }
var callback=function(){ ajaxResponse(obj,hook,'testAjaxHookXSS'); }
var x = ajaxObjNoCache(obj,otherHost+'/ajax.php',true,callback);
}
function testAjaxMenu(obj,menu){
var mms_start=xmillis();
var hook=function(){ menuAjaxHook(obj,mms_start); }
var callback=function(){ ajaxResponse(obj,hook,'testAjaxHookXSS'); }
var x = ajaxObjPageCache(obj,'/ajaxmenu.php?menu='+menu,true,callback);
}
/**
* This is a custom implemenation, the customObj COULD be used for anything (perhaps delay measurement!)
* @param obj - the clicked item
* @param customObj - user defined
*/
function menuAjaxHook(obj,customObj){
var diff = xmillis() - customObj; // difference from click to now!
popStatus('AJAX Menu Response time [' + diff +'mms]','');
var htm = xhr.responseText;// NOTE: xhr.responseXMLis also valid
xinnerHTML('menu',htm);
}
/**
* This is a custom implemenation, the customObj COULD be used for anything (perhaps delay measurement!)
* @param obj - the clicked item
* @param customObj - user defined
*/
function customAjaxHook(obj,customObj){
var xml = xhr.responseXML;// NOTE: xhr.responseText is also valid
var tmp = 'DEMO customAjaxHook [' + customObj + ']\n' + xml;
alert(tmp);
}
function xinnerHTML(id,txt){
var obj = xgetHelper(id);
if(obj!=null){
if((txt != null) && (txt != "")){
obj.innerHTML = txt;
}
}
}
</script>
<style type="text/css">
div#container {position:absolute;top:0;left:0;padding-right:10px;}
div#nav_col {position:absolute;left:0;
float:left;
width:140px;padding-left:7px;
}
div#container > div#nav_col {position:relative;}

div#main {margin-left:160px;border:1px dotted #fff;/*vertical-align:top; causes table gaps bug*/
width:99.9%;
voice-family: “\”}\””;
voice-family:inherit;
width:auto;}
.busy { color:red; }
.idle { color:black; }
</style>
</head>
<body onload=”xload();”><!– onbeforeunload=”alert(‘before’);” onunload=”alert(‘after’);” –>
<div id=”throbber”>WORKING!</div>
<div id=”container”>
<div id=”nav_col”>
<div id=”menu”>EMPTY Menu</div>
</div>
<div id=”main”>
<div id=”statusdiv” class=”dyn” style=”display:none;”>
<h3><a id=”statusarrow” onclick=”headTog(‘statusdyn’,this);” href=”javascript:void(0);”>Status</a></h3>
<fieldset id=”statusdyn” style=”display:block;background-color:#ffffcc;”>
<div id=”statusBox” class=”box”>
<ul id=”statusul” class=”error”>
<li id=”ajaxStatus” style=”list-style:none;display:none;”>FILLER</li>
</ul>
</div>
</fieldset>
</div>
<h3><a href=”javascript:void(0);” onclick=”testAjax(this);”>TEST</a></h3>
<h3><a href=”javascript:void(0);” onclick=”testAjaxParms(this);”>TESTPARMS</a></h3>
<h3><a href=”javascript:void(0);” onclick=”testAjaxXSS(this);”>TESTXSS</a></h3>
<h3><a href=”javascript:void(0);” onclick=”testAjaxHook(this);”>TESTHOOK</a></h3>
<h3><a href=”javascript:void(0);” onclick=”testAjaxHookXSS(this);”>TESTHOOKXSS</a></h3>
<h3><a href=”javascript:void(0);” onclick=”testAjaxMenu(this,’ACCT’);”>TESTMenuACCT</a></h3>
<h3><a href=”javascript:void(0);” onclick=”testAjaxMenu(this,’USER’);”>TESTMenuUSER</a></h3>
<p><a href=”index.php”>RELOAD</a></p>
</div><!– main –>
</div><!– container –>
</body>
</html>

ajax.php – for the original test code:

<?php
//header("Cache-Control: no-store");
header("Charset: UTF-8");
header("Content-Type: text/xml");
echo("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
?>
<test><?php echo( gmdate("D, d M Y H:i:s") ) ?> </test>

ajaxmenu.php – example menu behavior:

<?php
//header("Cache-Control: no-store");
header("Charset: UTF-8");
header("Content-Type: text/html");
?>
<ul>
<li>MENU:</li>
<li><?php echo($_GET['menu']) ?> </li>
<li><?php echo( gmdate("D, d M Y H:i:s") ) ?> </li>
</ul>

Cheers!

A Flexible AJAX Framework

AJAX = Asyncronous (but not always), JavaScript, XML (Text or JSON). These technologies have been around almost as long as the Internet itself, but have only recently been used together to change the way that web applications are built.

XMLHTTPRequest is an API used mainly by browser-based Javascript applications to send and retrieve data from servers. It was developed originally by Microsoft for Exchange Server’s Outlook Web Access, but it has since been widely supported in browsers and is the heart of AJAX dynamic Web applications.

I’ve messed with many of the frameworks available online, and while they are very good, I’ve routinely found issues or features that they lack. Over time I’ve cobbled together my own framework, I fully expect this to change over time, but publish it here for your consideration!

The XSS methods (and “otherHost” variable) are provided as MSIE6 allows for Cross-site Scripting here. MSIE7 and Mozilla will indicate errors if connections are attempted to a ‘non-originating host’. I’ve exploited this vulnerability in a few applications, as such I provide the test for you too!

NOTE: The example response program is written in PHP, but should be easily ported to any other language.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html>

<head>

<title>AJAX Example</title>

<script type=”text/javascript”>

var xbusy = true;

var otherHost = ‘http://www.giantgeek.com’; // DO NOT USE the ending slash!

var xhr = null;

/**

* Check for existance on DOM browsers (Mozilla, etc.)

* @return xhr

*/

function ajaxCheckDOM(){

var myxhr = null;

if(window.XMLHttpRequest) {

try {

myxhr = new XMLHttpRequest();

}

catch(e) {}

}

return myxhr;

}

/**

* Check for existance on Windows/MSIE (prior to MSIE7 which is now DOM)

* Evaluate using – new ActiveXObject(“Microsoft.XMLDOM”);

* @return xhr

*/

function ajaxCheckActiveX(){

var myxhr = null;

//if(window.ActiveXObject){

try {

myxhr = new ActiveXObject(“Msxml2.XMLHTTP”);

}

catch(e) {

try {

myxhr = new ActiveXObject(“Microsoft.XMLHTTP”);

}

catch(e) {}

}

//}

return myxhr;

}

/**

* This is a default response hook for AJAX, you SHOULD create your own as it’s only for DEMO

* @param obj – the clicked item

* @param customObj – user defined

*/

function ajaxResponseHook(obj,customObj){

var txt = xhr.responseText;// NOTE: xhr.responseXML is also valid

popStatus(‘AJAX Response value [‘ + txt + ‘|’ + customObj +’]’,”);

}

/**

* This is a common Response handler AJAX, you SHOULD NOT need to modify, use a ‘custom’ ajaxhook function to process the responses.

* @param obj – the clicked item

* @param ajaxhook Function (default will be used if undefined)

* @param customObj (optional – left for developer implementation, passed to the ajaxhook)

*/

function ajaxResponse(obj,ajaxhook,customObj){

var status=”;

if(xhr!=undefined){

var state=xhr.readyState;

if(state!=undefined){

if(state==4){

var code = xhr.status;

status = xhr.statusText;

if(code==200){

popStatus(‘AJAX Response Received.’,”);

if(ajaxhook==undefined){

ajaxhook = function(){ ajaxResponseHook(obj,customObj); }

}

ajaxhook(obj,customObj);

ajaxBusy(obj,false);

} else {

popStatus(‘AJAX Error ‘ + code + ‘ ‘ + status + ‘.’,”);

}

ajaxBusy(obj,false);

xhr=null; /* MSIE6 leak fix */

}

}

}

}

/**

* NOTE: MSIE6-7 supports XSS url’s (be careful!)

* @param obj – the clicked item

* @param url – the GET url params (FQDN)

* @param async (true or false) – false will LOCK browser until response – use cautiously!

* @param callback Function – allows for customized response handling

*/

function ajaxObj(obj,url,async,callback){

if(xbusy == true){

popStatus(‘AJAX BUSY, Please Retry.’,”);

} else {

if(callback==undefined){

callback = function(){ ajaxResponse(obj,hook,”); }

}

ajaxInit(obj,url,async,callback);

}

}

/**

* This is a Non-Caching implementation of ajaxObj() to show how you can avoid the MSIE caching issue.

* NOTE: You can use similar approaches to cache per page or session.

* @param obj – the clicked item

* @param url – the GET url params (FQDN)

* @param async (true or false) – false will LOCK browser until response – use cautiously!

* @param callback Function – allows for customized response handling

*/

function ajaxObjNoCache(obj,url,async,callback){

var cacheBuster=uniqueUrl(url);// damn MSIE!

ajaxObj(obj,cacheBuster,async,callback)

}

/**

* Initializes the AJAX operation

* @param obj – item clicked

* @param url – FQDN

* @param async (true/false) – for locking

* @param callback Function – provided for customization.

*/

function ajaxInit(obj,url,async,callback){

xhr=ajaxCheckDOM();

if(!xhr){

xhr=ajaxCheckActiveX();

}

if(xhr){

ajaxBusy(obj,true);

popStatus(‘AJAX Start.’,”);

xhr.onreadystatechange = callback; // method call

try {

xhr.open(‘GET’,url,async); /* POST */

//xhr.setRequestHeader(‘Content-Type’,’application/x-www-form-urlencoded’);

xhr.send(”); /* null */

}

catch(e)

{

if(xhr){

var code = xhr.status;

var status = xhr.statusText;

popStatus(‘AJAX Client SECURITY ‘ + navigator.appName +’ ‘+ navigator.appVersion + ‘ ‘ + code + ‘ ‘ + status + ‘.’,”);

}

}

ajaxBusy(obj,false);

} else {

popStatus(‘AJAX Client ERROR.’,”);

}

}

function ajaxBusy(obj,yn){

if(yn){

swapStyleObj(obj,’idle’,’busy’);

} else {

swapStyleObj(obj,’busy’,’idle’);

}

xbusyInd(yn,”);

}

function xmillis(){

return new Date().getTime();

}

function swapStyleObj(obj,oldCSS,newCSS){

if(obj!=undefined) {

var current=obj.className;

if(current!=undefined){

var txtOld = current.replace(newCSS,’ ‘);//no doubles

var txtMid = txtOld.replace(oldCSS, ‘ ‘);

var txtNew = (txtMid + ‘ ‘ + newCSS);

obj.className = txtNew;

} else {

obj.className = newCSS;

}

}

}

function xbusyInd(valnew, msg){

xbusy = valnew;

if(xbusy==true){

document.body.style.cursor=’wait’;

if(msg != ”){

top.window.defaultStatus=msg;

popStatus(msg,”);

}

showDiv(‘throbber’);

//blockScreen();

} else {

document.body.style.cursor=’default’;

hideDiv(‘throbber’);

//unBlockScreen();

}

}

function showDiv(id) { //show a div

var obj=xgetHelper(id);

if(obj!=null){ obj.style.display=”block”;}

//xrestart();

}

function hideDiv(id) { //hide a div

var obj=xgetHelper(id);

if(obj!=null){ obj.style.display=”none”;}

//xrestart();

}

function popStatus(txt,title){

popit(‘statusdyn’,’statusarrow’,’statusul’,’statusdiv’,txt,false,title,”);

}

function popit(dynId,arrowId,ulId,divId,txt,expand,title,cssCls){

popText(ulId,txt,title,cssCls);

showDiv(divId);

if(expand == true){

var arrowObj=xgetHelper(arrowId); if(arrowObj!=null){ arrowObj.style.backgroundPosition=’2px -106px’; }

showDiv(dynId);

}

}

function popText(id,txt,title,cssCls){

var obj = xgetHelper(id);

if(obj != null){

var oldHTML = obj.innerHTML;

var cls=”; if(cssCls!=”){ cls=’ class=”‘ + cls +'”‘; }

var htm = ‘<‘+’li’+ cls +’>’ + txt + ‘<‘+’/’+’li’+’>’ + oldHTML;

obj.innerHTML = htm;

}

}

function xgetHelper(id){

var obj = null;

try {

obj = document.getElementById(id);

} catch(z) {

var dummy=alert(“Error:” + z);

}

return obj;

}

function arrowTog(objectID,arrow) {

var obj = xgetHelper(objectID);

if(obj!=null){

if (obj.style.display ==’block’) {

arrow.style.backgroundPosition = ‘2px -21px’;}

else {arrow.style.backgroundPosition = ‘2px -106px’;}

objTog(objectID);

}

return false;

}

function objTog(objectID) {

var obj = xgetHelper(objectID);

if(obj!=null){

if (obj.style.display ==’block’) obj.style.display=’none’;

else {obj.style.display=’block’;}

}

return false;

}

function headTog(objectID,arrow) {

var obj = xgetHelper(objectID);

if(obj!=null){

if (obj.style.display ==’block’) {

arrow.style.borderBottomWidth = ‘1px’;}

else {arrow.style.borderBottomWidth = ‘0px’;}

arrowTog(objectID,arrow);

}

return false;

}

/*

* adds timestamp to URLs to make them unique

* @param URL String

*/

function uniqueUrl(x){

return urlAppender(x,’.cache’,xmillis());

}

/*

* helps to add parms to the url

* @param URL String

* @param aname String

* @param avalue String

*/

function urlAppender(x,aname,avalue){

var delim = “?”;

if(x.indexOf(“?”) >=0) { delim = “&”; }

return x + delim + aname + ‘=’ + avalue;

}

function xload(){

hideDiv(‘throbber’);

xbusy=false;

}

function testAjax(obj){

var callback=function(){ ajaxResponse(obj,null,’testAjax’); }

var x = ajaxObjNoCache(obj,’/ajax.php’,true,callback);

}

function testAjaxParms(obj){

var callback=function(){ ajaxResponse(obj,null,’testAjaxParms’); }

var x = ajaxObjNoCache(obj,’/ajax.php?testing=Y’,true,callback);

}

function testAjaxXSS(obj){

var callback=function(){ ajaxResponse(obj,null,’testAjaxXSS’); }

var x = ajaxObjNoCache(obj,otherHost+’/ajax.php’,true,callback,otherHost);

}

function testAjaxHook(obj){

var hook=function(){ customAjaxHook(obj,’ajaxhookTestMessageObect’); }

var callback=function(){ ajaxResponse(obj,hook,’testAjaxHook’); }

var x = ajaxObjNoCache(obj,’/ajax.php’,true,callback);

}

function testAjaxHookXSS(obj){

var hook=function(){ customAjaxHook(obj,’ajaxhookXSSMessageObect’); }

var callback=function(){ ajaxResponse(obj,hook,’testAjaxHookXSS’); }

var x = ajaxObjNoCache(obj,otherHost+’/ajax.php’,true,callback);

}

/**

* This is a custom implemenation, the customObj COULD be used for anything (perhaps delay measurement!)

* @param obj – the clicked item

* @param customObj – user defined

*/

function customAjaxHook(obj,customObj){

var xml = xhr.responseXML;// NOTE: xhr.responseText is also valid

var tmp = ‘DEMO customAjaxHook [‘ + customObj + ‘]\n’ + xml;

alert(tmp);

}

</script>

<style type=”text/css”>

.busy { color:red; }

.idle { color:black; }

</style>

</head>

<body onload=”xload();”><!– onbeforeunload=”alert(‘before’);” onunload=”alert(‘after’);” –>

<div id=”throbber”>WORKING!</div>

<div id=”statusdiv” class=”dyn” style=”display:none;”>

<h3><a id=”statusarrow” onclick=”headTog(‘statusdyn’,this);” href=”javascript:void(0);”>Status</a></h3>

<fieldset id=”statusdyn” style=”display:block;background-color:#ffffcc;”>

<div id=”statusBox” class=”box”>

<ul id=”statusul” class=”error”>

<li id=”ajaxStatus” style=”list-style:none;display:none;”>FILLER</li>

</ul>

</div>

</fieldset>

</div>

<h3><a href=”javascript:void(0);” onclick=”testAjax(this);”>TEST</a></h3>

<h3><a href=”javascript:void(0);” onclick=”testAjaxParms(this);”>TESTPARMS</a></h3>

<h3><a href=”javascript:void(0);” onclick=”testAjaxXSS(this);”>TESTXSS</a></h3>

<h3><a href=”javascript:void(0);” onclick=”testAjaxHook(this);”>TESTHOOK</a></h3>

<h3><a href=”javascript:void(0);” onclick=”testAjaxHookXSS(this);”>TESTHOOKXSS</a></h3>

<p><a href=”index.php”>RELOAD</a></p>

</body>

</html>

Response program (ajax.php)

<?php

header(“Cache-Control: no-store”);

header(“Charset: UTF-8”);

header(“Content-Type: text/xml”);

echo(“<?xml version=\”1.0\” encoding=\”UTF-8\”?>\n”);

?>

<test><?php echo( gmdate(“D, d M Y H:i:s”) ) ?> </test>

What are the MSIE Bugs you might ask… these are all in the ActiveX implementation of XmlHttpRequest, they are:

  1. Caching – for some reason, unless you define a ‘unique’ URL for each request, MSIE6 will respond from the cache, even if the server is unreachable!
  2. XSS – I discussed this above, MSIE6 will allow you to connect to ANY HOST with AJAX!
  3. Memory leak – if you don’t nullify the XHR object (for MSIE6), the application will leak memory.

Cheers!