/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ import flash.external.ExternalInterface; import System.security; /** * XPC Flash Based Transport * Original design by evn@google.com (Eduardo Vela) */ class Main { // Ensures that the callbacks installed by the SWF are installed only once per page context. private static var SINGLETON:Boolean = false; // Constructor: unused in this case. public function Main() { } /** * Simple helper function to replace instances of the given from_str in the provided * first argument with the given to_str string. * @param str {String} String whose contents to replace. * @param from_str {String} Token to replace in str. * @param to_str {String} String to put in place of from_str in str. * @returns Modified string. */ public static function replace(str:String, from_str:String, to_str:String):String { var out_str:String = ""; var search_ix:Number = 0; while (search_ix < str.length) { var found_ix:Number = str.indexOf(from_str, search_ix); if (found_ix != -1) { out_str = out_str.concat(str.substring(search_ix, found_ix)).concat(to_str); search_ix = found_ix + from_str.length; } else { out_str = out_str.concat(str.substring(search_ix)); search_ix = str.length; } } return out_str; } public static function esc(str:String):String { return replace(str, "\\", "\\\\"); } /** * Removes the port piece of an assumed well-structured provided host:port pair. * @param {String} str String representing a URI authority. * @returns The host-only (minus port) piece of the inbound authority String. */ public static function stripPortIfPresent(str:String):String { var col_ix:Number = str.indexOf(":"); if (col_ix == -1) { return str; } return str.substring(0, col_ix); } /** * Implementation of handlers facilitating cross-domain communication through * Flash's ExternalInterface and LocalConnection facilities, offering sender * domain verification. * * This method may only be run once such that it has any effect, a fact enforced * by a static SINGLETON boolean. This prevents confusion if the SWF is accidentally * loaded more than once in a given Window or page. * * The method whitelists HTTP-to-SWF access only for the domain provided in the * inbound 'origin' argument. This argument in turn is passed along in each * call made that passes along cross-domain messages on the caller's behalf, ensuring * that domain verification works as intended. * * It installs a single 'setup' handler, callable from JS, which in turn sets up * a sendMessage one-way communication method, while starting to listen to * an equivalent channel set up by a party on the opposite side of an IFRAME * boundary. A child context should pass in "INNER" for the role argument to setup; * the parent passes "OUTER". rpc_key is a unique key provided on the IFRAME URL's * hash disambiguating it from all other IFRAMEs on-page, window, or machine, while * channel_id is the child's ID. * * This SWF is written specifically for the gadgets.rpc cross-domain communication * library, but could be rather easily adapted - with passed-in callback method * names, for instance - to use by other libraries as well. */ public static function main(swfRoot:MovieClip):Void { var escFn:Function = esc; var replaceFn:Function = replace; if (SINGLETON) return; SINGLETON = true; var my_origin:String; if (_level0.origin == undefined){ // No origin: accept from all HTTP callers. // Domain verification will not apply. my_origin = "http://*"; } else { // Get origin from the query string. my_origin = _level0.origin; } var ready_method:String = "gadgets.rpctx.flash._ready"; var recv_method:String = "gadgets.rpctx.flash._receiveMessage"; var setup_done_method:String = "gadgets.rpctx.flash._setupDone"; if (_level0.jsl == "1") { // Use 'safe-exported' methods. ready_method = "___jsl._fm.ready"; recv_method = "___jsl._fm.receiveMessage"; setup_done_method = "___jsl._fm.setupDone"; } // Flash doesn't accept/honor ports, so we strip one if present // for canonicalization. var domain:String = stripPortIfPresent( my_origin.substr(my_origin.indexOf("//") + 2, my_origin.length)); // Whitelist access to this SWF for the sending HTTP domain. // The my_origin field from which domain derives is passed along // with each sent message. Together, these ensure domain verification // of all sent messages is possible. if (my_origin.substr(0,5) === "http:") { security.allowInsecureDomain(domain); } else { security.allowDomain(domain); } // Install global communication channel setup method. ExternalInterface.addCallback("setup", { }, function(rpc_key:String, channel_id:String, role:String) { var other_role:String; if (role == "INNER") { other_role = "OUTER"; } else { other_role = "INNER"; role = "OUTER"; } var receiving_lc:LocalConnection = new LocalConnection(); var sending_lc:LocalConnection = new LocalConnection(); receiving_lc.receiveMessage = function(to_origin:String, from_origin:String, in_rpc_key:String, message:String) { if ((to_origin === "*" || to_origin === my_origin) && (in_rpc_key == rpc_key)) { ExternalInterface.call(recv_method, escFn(message), escFn(from_origin), escFn(to_origin)); } }; ExternalInterface.addCallback("sendMessage_" + channel_id + "_" + rpc_key + "_" + role, { }, function(message:String, to_origin:String) { if (!to_origin) to_origin = "*"; var sendId:String = replaceFn("channel_" + channel_id + "_" + rpc_key + "_" + other_role, ":", ""); sending_lc.send(sendId, "receiveMessage", to_origin, my_origin, rpc_key, message); }); var recvId:String = replaceFn("channel_" + channel_id + "_" + rpc_key + "_" + role, ":", ""); receiving_lc.connect(recvId); if (role == "INNER") { // In child context, trigger notice that the setup method is complete. // This in turn initiates a child-to-parent polling procedure to complete a bidirectional // communication handshake, since otherwise meaningful messages could be passed and dropped // before the receiving end was ready. ExternalInterface.call(setup_done_method); } }); // Signal completion of the setup callback to calling-context JS for proper ordering. ExternalInterface.call(ready_method); } }