| /* | |
| * 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); | |
| } | |
| } |