Cross Domain Scripting using Flash

I was building a site for a client when I came across this cross domain security issue. My client had users that needed to be able to send data from their website to my clients server which would process the data and return a new set of data to replace the old. I couldn’t just make an ajax call since the browser wont allow you to receive data from another server. My options to get around this were that I could use iframes or use flash. I decided to go with the flash for no particular reason.

I’m going to go through each file I created. First I’m going to go through the .fla file and then the two .as files and then the javascript file.

I am using Flash 8 and actionscript 2.

To start, create a new .fla file.

Import the following class to allow flash and javascript to communicate.


import flash.external.ExternalInterface;

Flash wont allow you to access another domain without adding the following code. The ‘*’ will allow all domains to be able to access the file on your server or you can add individual domains.


System.security.allowDomain("*");

I create a new instance of DebugHelper which is created in a separate .as file which I’ll show later. This is only used to help me print out text to test the code. It creates a text field and then when you call the Log function will print out the string you pass to it.


var deb = new DebugHelper();
DebugHelper.Log("test");

This is a button I created in the .fla. When it is clicked it loads the policy file. This is an xml file that tells the flash which domains are allowed to access the files that are in the same folder the xml file is in and all subfolders on your server. You can use a ‘*’ to allow all domains. The ExternalInterface.call calls a javascript function which takes the content of the webpage and passes it to flash.


The crossdomain.xml file
<?xml version="1.0"?>
   <!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/
dtds/cross-domain-policy.dtd">
   <cross-domain-policy>
       <allow-access-from domain="www.company.com" />
       <allow-access-from domain="*" />
   </cross-domain-policy>

btn.onRelease = function() {
  System.security.loadPolicyFile("http://yourdomain.com/crossdomain.xml");
  var successful = ExternalInterface.call("sendit");
};

This next bit allows the flash element in javascript to send data via the function “sendData” which calls the “xmlhttp” flash function. Then this function creates a new HttpConnection object which is in another .as file which I’ll explain later. You pass to it the filename of where you want to send the data. You then can set the body with the data you passed to the function and whether you want to use POST or GET. I then add the onData event handler to the object so I know when I get data back from the server. When that is fired I set the data to a variable called retText and then pass it to javascript with the call “setData”.


ExternalInterface.addCallback("sendData", this, xmlhttp);
function xmlhttp(body:String) {
	var http = new HttpConnection("http://yoursite.com/crossdomain.php");
	http.setAction("POST");
		http.setBody(body);
		http.setContentType("application/x-www-form-urlencoded");
		http.onData = function(src:String) {
				_root.retText = src;
				ExternalInterface.call("setData", "retText");
		};
		http.send();
}

This last function calls the javascript function “storageOnLoad” so we know the flash is loaded.


var ret = ExternalInterface.call("storageOnLoad");

This next bit is the DebugHelper.as file. This just creates a text field that allows me to call Log() on DebugHelper to help me debug by printing out text to the screen.


class DebugHelper {
	static var log:String;
	static var debug:Boolean;
	function DebugHelper() {
		_root.createTextField("tf", 0, 0, 0, 315, 238);
		log = "";
		var ret;
		_root.tf.text = "Running Flash version: "+getVersion();
		DebugHelper.Debug();
	}

	public static function Debug() {
		debug = true;
	}
	public static function Log(input:String) {
		log = log+" ___ "+input;
		if (debug) {
			_root.tf.text = input+"\r"+_root.tf.text;
		}
	}
}

This next file is the HttpConnection.as file. I start out by defining some variables and functions. The sendAndLoad and Load functions I set to the default LoadVars.prototype.sendAndLoad and LoadVars.prototype.load function which allows me to send this objects data to the given url using the LoadVars class.


 class HttpConnection {
	private var action:String = "POST";
	private var body:String = "";
	private var url:String = "";
	var contentType:String;
	var sendAndLoad:Function = LoadVars.prototype.sendAndLoad;
	var load:Function = LoadVars.prototype.load;
	// note: many headers can't be added. http://livedocs.macromedia.com/
       //   flash/8/main/wwhelp/wwhimpl/common/html/
       //   wwhelp.htm?context=LiveDocs_Parts&file=00002324.html
	var addRequestHeader:Function = LoadVars.prototype.addRequestHeader;
	var onData:Function;

This function creates an instance of this class.


	function HttpConnection(_urls:String) {
		url = _urls;
	}

The next four function allow me to set the url, action, body and contentType.


	function setUrl(_urls:String) {
		if (_urls) {
			url = _urls;
		}
	}
	function setAction(_action:String) {
		if (_action) {
			action = _action;
		}
	}
	function setBody(_body:String) {
		if (_body) {
			body = _body;
		}
	}
	function setContentType(_contentType:String) {
		if (_contentType) {
			contentType = _contentType;
		}
	}

This function is overriding the default toString() function. This automatically gets called when the sendAndLoad function is called. If I wasn’t using an object to call the sendAndLoad function I would have to create an instance of LoadVars and set the variables like this:

var my_lv:LoadVars = new LoadVars();
var result_lv:LoadVars = new LoadVars();
my_lv.id=10;
my_lv.text = “some text”;
my_lv.sendAndLoad(url, result_lv, ‘POST’);

The toString() function would get called to convert my_lv to “id=10&text=some+text”. Since I already have the body in that format I don’t need to do that so I just return the body.


	function toString():String {
		return body;
	}

This is the function that sends the data to the server.


	function send() {
		DebugHelper.Log("HttpConnection send this(action="+action+")");
		// note: if we did a sendAndLoad on a GET, the Flash API would add
                //a question mark '?' at the end of the querystring

		if (action == "GET") {
			load(url);
		} else {
			sendAndLoad(url, this, action);
		}
	}
}

Now the javascript file.

I create a javacript object to hold the flash.


var FlashObject = new Object();
FlashObject.height = 138;
FlashObject.width = 215;

This function will check to see if the flash is installed.


FlashObject.isFlashInstalled = function() {
	var ret;
	if (typeof(this.isFlashInstalledMemo) != "undefined") {
            return this.isFlashInstalledMemo;
        }
	if (typeof(ActiveXObject) != "undefined") {
  	   try {
		var ieObj = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
	    } catch (e) { }
	     ret = (ieObj != null);
	} else {
		var plugin = navigator.mimeTypes["application/x-shockwave-flash"];
		ret = (plugin != null) && (plugin.enabledPlugin != null);
	}
	this.isFlashInstalledMemo = ret;
	return ret;
}

This simply gets the flash element.


FlashObject.getFlash = function() {
	return document.getElementById("flash");
}

This function will write the flash object on your page. For me, I needed to send the entire content of the body to flash and then replace it without replacing the flash object. So I needed to add a wrapper div around the entire content of the body. That way when I get the data back from flash I would replace everything inside that wrapper div. This will keep the flash from getting over written which can cause some errors. If you don’t need to replace the entire page then it’s not necessary.


FlashObject.writeFlash = function() {
	var swfName = "http://yourdomain.com/crossdomain.swf";
	if (window.ActiveXObject && !FlashObject.isFlashInstalled())
	{
	  // browser supports ActiveX
	 // Create object element with
	 // download URL for IE OCX
	 document.body.innerHTML="<div id='wrapperdiv'>"+document.body.innerHTML+
                                                    "</div>";
	 document.write('<object classid=
                           "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"');
	 document.write(' codebase="http://download.macromedia.com');
	 document.write('/pub/shockwave/cabs/flash/swflash.cab#version=8,5,0,0"');
	 document.write(' height="'+this.height+'" width="'+this.width+'"
                                   id="flash">');
	 document.write(' <param name="movie" value="' + swfName + '">');
	 document.write(' <param name="quality" value="high">');
	 document.write(' <param name="allowScriptAccess" value="always">');
	 document.write(' <param name="swliveconnect" value="true">');
	 document.write('<\/object>');
	}
	else
	{
	 // browser supports Netscape Plugin API
	 document.body.innerHTML="<div id='wrapperdiv'>"+document.body.innerHTML+
                                           "</div>";
         document.write('<object id="flash" data="' + swfName + '"');
	 document.write(' type="application/x-shockwave-flash"');
	 document.write(' height="'+this.height+'" width="'+this.width+'">');
	 document.write('<param name="movie" value="'+swfName+'">');
	 document.write('<param name="quality" value="high">');
	 document.write('<param name="swliveconnect" value="true">');
	 document.write('<param name="pluginurl"
                    value="http://www.macromedia.com/go/getflashplayer">');
	 document.write('<param name="pluginspage"
                     value="http://www.macromedia.com/go/getflashplayer">');
	 document.write(' <param name="allowScriptAccess" value="always">');
	 document.write('<p>You need Flash for this.');
	 document.write(' Get the latest version from');
	 document.write(' <a href="http://www.macromedia.com/software/
                                    flashplayer/">here<\/a>.');
	 document.write('<\/p>');
	 document.write('<\/object>');
	}
}

The next few functions are used to load up the flash.


FlashObject.load = function() {
	if (typeof(FlashObject.onload) != "function") { return; }

	if (FlashObject.isFlashInstalled()) {
		// if we expect Flash to work, wait for both flash and the document to be loaded
		var finishedLoading = this.flashLoaded && this.documentLoaded;
		if (!finishedLoading) { return; }
	}
	// todo: cancel timer
	var fs = FlashObject.getFlash();

	if ((!FlashObject.isFlashInstalled() || this.flashLoaded) && fs) {
		if (FlashObject.checkFlash()) {
			callAppOnLoad(fs);
		} else {
			callAppOnLoad(null);
		}
	} else {
		callAppOnLoad(null);
	}

	function callAppOnLoad(fs) {
		if (FlashHelper.onloadCalled) { return; } // todo: figure out why this case gets hit
		FlashHelper.onloadCalled = true;
		FlashHelper.onload(fs);
	}
}

function storageOnLoad() {
	FlashObject.flashLoaded = true;
	FlashObject.load();
}

This function is nice because if you already have a window.onload event and need to add more onload events this will add them.


FlashObject.addLoadEvent = function(func) {
	var oldonload = window.onload;
	if (typeof window.onload != 'function') {
		window.onload = func;
	} else {
		window.onload = function() {
			oldonload();
			func();
		}
	}
}

This initializes the flash object variables and onload functions. If you wanted the flash to automatically run on page load you could simply add the function call (in this case “sendit()”) inside the onload function.


FlashObject.init = function() {
	this.flashLoaded = false;
	this.documentLoaded = false;
	// attach to the window.onload event
	this.addLoadEvent(onload);
	function onload() {
		FlashObject.documentLoaded = true;
		FlashObject.load();
	}
}

FlashObject.init();

The function that actually gets the data you want to send and then calls the flash function “sendData”.


 function sendit() {
    var body = 'uid='+testdata;
    body += '&pagetext='+
       encodeURIComponent(document.getElementById('wrapperdiv').innerHTML);
    FlashObject.getFlash().sendData(body);
 }

When the data gets returned it stores it in a variable which we set in the http.onData function. We then pass the name of the variable to this function and call GetVariable which returns the data.


function setData(varname){
	var str = FlashObject.getFlash().GetVariable(varname);
	document.getElementById("wrapperdiv").innerHTML = str
}

And finally this writes out the flash object to the page.


FlashObject.writeFlash();

Now all anyone would need to do would be to include this javascript file at then end of their page right before the body tag.

Here are the .fla, .as and .js files.
CrossDomain.zip