Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
196 lines (165 sloc) 7.77 KB
//
// easyXDM
// http://easyxdm.net/
// Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Security model:
* SWF's can be loaded from any of the two domains, and messages are only received from these two.
* Alternatively a single SWF can be loaded from a common domain (CDN).
* security.allowXDomain is used to let the page interact with the SWF.
* */
import flash.external.ExternalInterface;
import System.security;
/**
* This class facilitates flash based communication between domains.
* @author Øyvind Sean Kinsey
*/
class Main
{
private static var INITIALIZED:Boolean = false;
// only allow javascript accessors
private static function Validate(input:String):Boolean {
var i = input.length;
while (i--) {
var charCode = input.charCodeAt(i);
if ( (charCode >= 64 && charCode <= 90 /*Uppercase*/) || (charCode >= 97 && charCode <= 122 /*Lowercase*/) ) continue;
if (charCode >= 48 && charCode <= 57 /*Numbers*/) continue;
if (charCode == 95/*_*/ || charCode == 36 /*$*/ || charCode == 45 /*-*/ || charCode == 46 /*.*/ || charCode == 58 /*:*/) continue;
return false;
}
return true;
}
// docs at http://livedocs.adobe.com/flash/9.0/main/wwhelp/wwhimpl/js/html/wwhelp.htm
public static function main(swfRoot:MovieClip):Void
{
// this is so that main can only be run once - this ensures that the domain passed really is the one
// being used to communicate with the SWF.
if (Main.INITIALIZED) return; else Main.INITIALIZED = true;
// validate the passed arguments
if (!Validate(_root.ns) || !Validate(_root.proto) || !Validate(_root.domain) || !Validate(_root.port) || !Validate(_root.callback)) return;
// LocalConnection has a max length
var maxMessageLength = 40000;
// map of all the senders
var sendMap = { }, connectionMap = { };
// set up the prefix as a string based accessor to remove the risk of XSS
var prefix:String = _root.ns ? _root.ns + "." : "";
// this will be our origin
var origin = _root.proto + "//" + _root.domain + _root.port;
// set up the logger, if any
var log = _root.log == "true" ? function(msg) {
ExternalInterface.call(prefix + "easyXDM.Debug.trace", " swf: " + msg);
} : function() {
};
log("enabling communication with " + _root.domain);
// allow javascript in the page to interact with the SWF
security[_root.proto == "http:" ? "allowInsecureDomain" : "allowDomain"](_root.domain);
// add the postMessage method
ExternalInterface.addCallback("postMessage", { }, function(channel:String, message:String) {
sendMap[channel](message);
});
// add the createChannel method
ExternalInterface.addCallback("createChannel", { }, function(channel:String, secret:String, remoteOrigin:String, isHost:Boolean) {
if (!Main.Validate(channel) || !Main.Validate(secret)) return;
log("creating channel " + channel);
// get the remote domain
var remoteDomain:String = remoteOrigin.substr(remoteOrigin.indexOf("://") + 3);
if (remoteDomain.indexOf(":") != -1) remoteDomain = remoteDomain.substr(0, remoteDomain.indexOf(":"));
// the sending channel has _ prepended so that all allowed domains can use it
var sendingChannelName:String = "_" + channel + "_" + secret + "_" + (isHost ? "consumer" : "provider");
var receivingChannelName:String = "_" + channel + "_" + secret + "_" + (isHost ? "provider" : "consumer");
var incommingFragments = [];
// set up the listening connection
var listeningConnection:LocalConnection = connectionMap[channel] = new LocalConnection();
// set up the sending connection
var sendingConnection:LocalConnection = new LocalConnection();
// domain of the current SWF (for cdn support)
var localSwfDomain:String = listeningConnection.domain();
// allow messages from only the two possible domains
listeningConnection.allowDomain =
listeningConnection.allowInsecureDomain = function(domain) {
var allowed:Boolean = (domain == remoteDomain || domain == _root.domain || domain == localSwfDomain);
log("allowDomain: " + allowed.toString());
return allowed;
};
// set up the onMessage handler - this combines fragmented messages
listeningConnection.onMessage = function(message, fromOrigin, remaining) {
if (fromOrigin !== remoteOrigin) {
log("received message from " + fromOrigin + ", expected from " + remoteOrigin);
return;
}
incommingFragments.push(message);
if (remaining <= 0) {
log("received final fragment");
// escape \\ and pass on
ExternalInterface.call(prefix + "easyXDM.Fn.get(\"flash_" + channel + "_onMessage\")", incommingFragments.join("").split("\\").join("\\\\"), remoteOrigin);
incommingFragments = [];
}else {
log("received fragment, length is " + message.length + " remaining is " + remaining);
}
};
// the host must delay calling channel_init until the other end is ready
listeningConnection.onReady = function() {
log("received ready");
if (isHost) {
log("calling ready");
sendingConnection.send(sendingChannelName, "onReady");
ExternalInterface.call(prefix + "easyXDM.Fn.get(\"flash_" + channel + "_init\")");
}
};
// connect
if (listeningConnection.connect(receivingChannelName)) {
log("listening on " + receivingChannelName);
} else {
log("could not listen on " + receivingChannelName);
}
sendingConnection.onStatus = function(infoObject:Object) {
log("level: " + infoObject.level);
};
sendMap[channel] = function(message:String) {
log("sending to " + sendingChannelName + ", length is " + message.length);
var fragments = [], fragment, length = message.length, pos = 0;
while (pos <= length) {
fragment = message.substr(pos, maxMessageLength);;
pos += maxMessageLength;
log("fragmentlength: " + fragment.length + ", remaining: " + (length - pos))
if (!sendingConnection.send(sendingChannelName, "onMessage", fragment, origin, length - pos)) {
log("sending failed");
}
}
};
// start the channel
if (!isHost) {
log("calling ready");
sendingConnection.send(sendingChannelName, "onReady");
ExternalInterface.call(prefix + "easyXDM.Fn.get(\"flash_" + channel + "_init\")");
}
});
// add the destroyChannel method
ExternalInterface.addCallback("destroyChannel", { }, function(channel:String) {
delete sendMap[channel];
connectionMap[channel].close();
delete connectionMap[channel];
});
// kick things off
log("calling init");
ExternalInterface.call(prefix + "easyXDM.Fn.get(\"" + _root.callback + "\")");
}
}
You can’t perform that action at this time.