| // Copyright 2008 The Closure Library Authors. All Rights Reserved. |
| // |
| // Licensed 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. |
| |
| /** |
| * @fileoverview Contains the NIX (Native IE XDC) method transport for |
| * cross-domain communication. It exploits the fact that Internet Explorer |
| * allows a window that is the parent of an iframe to set said iframe window's |
| * opener property to an object. This object can be a function that in turn |
| * can be used to send a message despite same-origin constraints. Note that |
| * this function, if a pure JavaScript object, opens up the possibilitiy of |
| * gaining a hold of the context of the other window and in turn, attacking |
| * it. This implementation therefore wraps the JavaScript objects used inside |
| * a VBScript class. Since VBScript objects are passed in JavaScript as a COM |
| * wrapper (like DOM objects), they are thus opaque to JavaScript |
| * (except for the interface they expose). This therefore provides a safe |
| * method of transport. |
| * |
| * |
| * Initially based on FrameElementTransport which shares some similarities |
| * to this method. |
| */ |
| |
| goog.provide('goog.net.xpc.NixTransport'); |
| |
| goog.require('goog.log'); |
| goog.require('goog.net.xpc'); |
| goog.require('goog.net.xpc.CfgFields'); |
| goog.require('goog.net.xpc.CrossPageChannelRole'); |
| goog.require('goog.net.xpc.Transport'); |
| goog.require('goog.net.xpc.TransportTypes'); |
| goog.require('goog.reflect'); |
| |
| |
| |
| /** |
| * NIX method transport. |
| * |
| * NOTE(user): NIX method tested in all IE versions starting from 6.0. |
| * |
| * @param {goog.net.xpc.CrossPageChannel} channel The channel this transport |
| * belongs to. |
| * @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for finding |
| * the correct window. |
| * @constructor |
| * @extends {goog.net.xpc.Transport} |
| * @final |
| */ |
| goog.net.xpc.NixTransport = function(channel, opt_domHelper) { |
| goog.net.xpc.NixTransport.base(this, 'constructor', opt_domHelper); |
| |
| /** |
| * The channel this transport belongs to. |
| * @type {goog.net.xpc.CrossPageChannel} |
| * @private |
| */ |
| this.channel_ = channel; |
| |
| /** |
| * The authorization token, if any, used by this transport. |
| * @type {?string} |
| * @private |
| */ |
| this.authToken_ = channel[goog.net.xpc.CfgFields.AUTH_TOKEN] || ''; |
| |
| /** |
| * The authorization token, if any, that must be sent by the other party |
| * for setup to occur. |
| * @type {?string} |
| * @private |
| */ |
| this.remoteAuthToken_ = |
| channel[goog.net.xpc.CfgFields.REMOTE_AUTH_TOKEN] || ''; |
| |
| // Conduct the setup work for NIX in general, if need be. |
| goog.net.xpc.NixTransport.conductGlobalSetup_(this.getWindow()); |
| |
| // Setup aliases so that VBScript can call these methods |
| // on the transport class, even if they are renamed during |
| // compression. |
| this[goog.net.xpc.NixTransport.NIX_HANDLE_MESSAGE] = this.handleMessage_; |
| this[goog.net.xpc.NixTransport.NIX_CREATE_CHANNEL] = this.createChannel_; |
| }; |
| goog.inherits(goog.net.xpc.NixTransport, goog.net.xpc.Transport); |
| |
| |
| // Consts for NIX. VBScript doesn't allow items to start with _ for some |
| // reason, so we need to make these names quite unique, as they will go into |
| // the global namespace. |
| |
| |
| /** |
| * Global name of the Wrapper VBScript class. |
| * Note that this class will be stored in the *global* |
| * namespace (i.e. window in browsers). |
| * @type {string} |
| */ |
| goog.net.xpc.NixTransport.NIX_WRAPPER = 'GCXPC____NIXVBS_wrapper'; |
| |
| |
| /** |
| * Global name of the GetWrapper VBScript function. This |
| * constant is used by JavaScript to call this function. |
| * Note that this function will be stored in the *global* |
| * namespace (i.e. window in browsers). |
| * @type {string} |
| */ |
| goog.net.xpc.NixTransport.NIX_GET_WRAPPER = 'GCXPC____NIXVBS_get_wrapper'; |
| |
| |
| /** |
| * The name of the handle message method used by the wrapper class |
| * when calling the transport. |
| * @type {string} |
| */ |
| goog.net.xpc.NixTransport.NIX_HANDLE_MESSAGE = 'GCXPC____NIXJS_handle_message'; |
| |
| |
| /** |
| * The name of the create channel method used by the wrapper class |
| * when calling the transport. |
| * @type {string} |
| */ |
| goog.net.xpc.NixTransport.NIX_CREATE_CHANNEL = 'GCXPC____NIXJS_create_channel'; |
| |
| |
| /** |
| * A "unique" identifier that is stored in the wrapper |
| * class so that the wrapper can be distinguished from |
| * other objects easily. |
| * @type {string} |
| */ |
| goog.net.xpc.NixTransport.NIX_ID_FIELD = 'GCXPC____NIXVBS_container'; |
| |
| |
| /** |
| * Determines if the installed version of IE supports accessing window.opener |
| * after it has been set to a non-Window/null value. NIX relies on this being |
| * possible. |
| * @return {boolean} Whether window.opener behavior is compatible with NIX. |
| */ |
| goog.net.xpc.NixTransport.isNixSupported = function() { |
| var isSupported = false; |
| try { |
| var oldOpener = window.opener; |
| // The compiler complains (as it should!) if we set window.opener to |
| // something other than a window or null. |
| window.opener = /** @type {Window} */ ({}); |
| isSupported = goog.reflect.canAccessProperty(window, 'opener'); |
| window.opener = oldOpener; |
| } catch (e) { } |
| return isSupported; |
| }; |
| |
| |
| /** |
| * Conducts the global setup work for the NIX transport method. |
| * This function creates and then injects into the page the |
| * VBScript code necessary to create the NIX wrapper class. |
| * Note that this method can be called multiple times, as |
| * it internally checks whether the work is necessary before |
| * proceeding. |
| * @param {Window} listenWindow The window containing the affected page. |
| * @private |
| */ |
| goog.net.xpc.NixTransport.conductGlobalSetup_ = function(listenWindow) { |
| if (listenWindow['nix_setup_complete']) { |
| return; |
| } |
| |
| // Inject the VBScript code needed. |
| var vbscript = |
| // We create a class to act as a wrapper for |
| // a Javascript call, to prevent a break in of |
| // the context. |
| 'Class ' + goog.net.xpc.NixTransport.NIX_WRAPPER + '\n ' + |
| |
| // An internal member for keeping track of the |
| // transport for which this wrapper exists. |
| 'Private m_Transport\n' + |
| |
| // An internal member for keeping track of the |
| // auth token associated with the context that |
| // created this wrapper. Used for validation |
| // purposes. |
| 'Private m_Auth\n' + |
| |
| // Method for internally setting the value |
| // of the m_Transport property. We have the |
| // isEmpty check to prevent the transport |
| // from being overridden with an illicit |
| // object by a malicious party. |
| 'Public Sub SetTransport(transport)\n' + |
| 'If isEmpty(m_Transport) Then\n' + |
| 'Set m_Transport = transport\n' + |
| 'End If\n' + |
| 'End Sub\n' + |
| |
| // Method for internally setting the value |
| // of the m_Auth property. We have the |
| // isEmpty check to prevent the transport |
| // from being overridden with an illicit |
| // object by a malicious party. |
| 'Public Sub SetAuth(auth)\n' + |
| 'If isEmpty(m_Auth) Then\n' + |
| 'm_Auth = auth\n' + |
| 'End If\n' + |
| 'End Sub\n' + |
| |
| // Returns the auth token to the gadget, so it can |
| // confirm a match before initiating the connection |
| 'Public Function GetAuthToken()\n ' + |
| 'GetAuthToken = m_Auth\n' + |
| 'End Function\n' + |
| |
| // A wrapper method which causes a |
| // message to be sent to the other context. |
| 'Public Sub SendMessage(service, payload)\n ' + |
| 'Call m_Transport.' + |
| goog.net.xpc.NixTransport.NIX_HANDLE_MESSAGE + '(service, payload)\n' + |
| 'End Sub\n' + |
| |
| // Method for setting up the inner->outer |
| // channel. |
| 'Public Sub CreateChannel(channel)\n ' + |
| 'Call m_Transport.' + |
| goog.net.xpc.NixTransport.NIX_CREATE_CHANNEL + '(channel)\n' + |
| 'End Sub\n' + |
| |
| // An empty field with a unique identifier to |
| // prevent the code from confusing this wrapper |
| // with a run-of-the-mill value found in window.opener. |
| 'Public Sub ' + goog.net.xpc.NixTransport.NIX_ID_FIELD + '()\n ' + |
| 'End Sub\n' + |
| 'End Class\n ' + |
| |
| // Function to get a reference to the wrapper. |
| 'Function ' + |
| goog.net.xpc.NixTransport.NIX_GET_WRAPPER + '(transport, auth)\n' + |
| 'Dim wrap\n' + |
| 'Set wrap = New ' + goog.net.xpc.NixTransport.NIX_WRAPPER + '\n' + |
| 'wrap.SetTransport transport\n' + |
| 'wrap.SetAuth auth\n' + |
| 'Set ' + goog.net.xpc.NixTransport.NIX_GET_WRAPPER + ' = wrap\n' + |
| 'End Function'; |
| |
| try { |
| listenWindow.execScript(vbscript, 'vbscript'); |
| listenWindow['nix_setup_complete'] = true; |
| } |
| catch (e) { |
| goog.log.error(goog.net.xpc.logger, |
| 'exception caught while attempting global setup: ' + e); |
| } |
| }; |
| |
| |
| /** |
| * The transport type. |
| * @type {number} |
| * @protected |
| * @override |
| */ |
| goog.net.xpc.NixTransport.prototype.transportType = |
| goog.net.xpc.TransportTypes.NIX; |
| |
| |
| /** |
| * Keeps track of whether the local setup has completed (i.e. |
| * the initial work towards setting the channel up has been |
| * completed for this end). |
| * @type {boolean} |
| * @private |
| */ |
| goog.net.xpc.NixTransport.prototype.localSetupCompleted_ = false; |
| |
| |
| /** |
| * The NIX channel used to talk to the other page. This |
| * object is in fact a reference to a VBScript class |
| * (see above) and as such, is in fact a COM wrapper. |
| * When using this object, make sure to not access methods |
| * without calling them, otherwise a COM error will be thrown. |
| * @type {Object} |
| * @private |
| */ |
| goog.net.xpc.NixTransport.prototype.nixChannel_ = null; |
| |
| |
| /** |
| * Connect this transport. |
| * @override |
| */ |
| goog.net.xpc.NixTransport.prototype.connect = function() { |
| if (this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.OUTER) { |
| this.attemptOuterSetup_(); |
| } else { |
| this.attemptInnerSetup_(); |
| } |
| }; |
| |
| |
| /** |
| * Attempts to setup the channel from the perspective |
| * of the outer (read: container) page. This method |
| * will attempt to create a NIX wrapper for this transport |
| * and place it into the "opener" property of the inner |
| * page's window object. If it fails, it will continue |
| * to loop until it does so. |
| * |
| * @private |
| */ |
| goog.net.xpc.NixTransport.prototype.attemptOuterSetup_ = function() { |
| if (this.localSetupCompleted_) { |
| return; |
| } |
| |
| // Get shortcut to iframe-element that contains the inner |
| // page. |
| var innerFrame = this.channel_.getIframeElement(); |
| |
| try { |
| // Attempt to place the NIX wrapper object into the inner |
| // frame's opener property. |
| var theWindow = this.getWindow(); |
| var getWrapper = theWindow[goog.net.xpc.NixTransport.NIX_GET_WRAPPER]; |
| innerFrame.contentWindow.opener = getWrapper(this, this.authToken_); |
| this.localSetupCompleted_ = true; |
| } |
| catch (e) { |
| goog.log.error(goog.net.xpc.logger, |
| 'exception caught while attempting setup: ' + e); |
| } |
| |
| // If the retry is necessary, reattempt this setup. |
| if (!this.localSetupCompleted_) { |
| this.getWindow().setTimeout(goog.bind(this.attemptOuterSetup_, this), 100); |
| } |
| }; |
| |
| |
| /** |
| * Attempts to setup the channel from the perspective |
| * of the inner (read: iframe) page. This method |
| * will attempt to *read* the opener object from the |
| * page's opener property. If it succeeds, this object |
| * is saved into nixChannel_ and the channel is confirmed |
| * with the container by calling CreateChannel with an instance |
| * of a wrapper for *this* page. Note that if this method |
| * fails, it will continue to loop until it succeeds. |
| * |
| * @private |
| */ |
| goog.net.xpc.NixTransport.prototype.attemptInnerSetup_ = function() { |
| if (this.localSetupCompleted_) { |
| return; |
| } |
| |
| try { |
| var opener = this.getWindow().opener; |
| |
| // Ensure that the object contained inside the opener |
| // property is in fact a NIX wrapper. |
| if (opener && goog.net.xpc.NixTransport.NIX_ID_FIELD in opener) { |
| this.nixChannel_ = opener; |
| |
| // Ensure that the NIX channel given to use is valid. |
| var remoteAuthToken = this.nixChannel_['GetAuthToken'](); |
| |
| if (remoteAuthToken != this.remoteAuthToken_) { |
| goog.log.error(goog.net.xpc.logger, |
| 'Invalid auth token from other party'); |
| return; |
| } |
| |
| // Complete the construction of the channel by sending our own |
| // wrapper to the container via the channel they gave us. |
| var theWindow = this.getWindow(); |
| var getWrapper = theWindow[goog.net.xpc.NixTransport.NIX_GET_WRAPPER]; |
| this.nixChannel_['CreateChannel'](getWrapper(this, this.authToken_)); |
| |
| this.localSetupCompleted_ = true; |
| |
| // Notify channel that the transport is ready. |
| this.channel_.notifyConnected(); |
| } |
| } |
| catch (e) { |
| goog.log.error(goog.net.xpc.logger, |
| 'exception caught while attempting setup: ' + e); |
| return; |
| } |
| |
| // If the retry is necessary, reattempt this setup. |
| if (!this.localSetupCompleted_) { |
| this.getWindow().setTimeout(goog.bind(this.attemptInnerSetup_, this), 100); |
| } |
| }; |
| |
| |
| /** |
| * Internal method called by the inner page, via the |
| * NIX wrapper, to complete the setup of the channel. |
| * |
| * @param {Object} channel The NIX wrapper of the |
| * inner page. |
| * @private |
| */ |
| goog.net.xpc.NixTransport.prototype.createChannel_ = function(channel) { |
| // Verify that the channel is in fact a NIX wrapper. |
| if (typeof channel != 'unknown' || |
| !(goog.net.xpc.NixTransport.NIX_ID_FIELD in channel)) { |
| goog.log.error(goog.net.xpc.logger, |
| 'Invalid NIX channel given to createChannel_'); |
| } |
| |
| this.nixChannel_ = channel; |
| |
| // Ensure that the NIX channel given to use is valid. |
| var remoteAuthToken = this.nixChannel_['GetAuthToken'](); |
| |
| if (remoteAuthToken != this.remoteAuthToken_) { |
| goog.log.error(goog.net.xpc.logger, 'Invalid auth token from other party'); |
| return; |
| } |
| |
| // Indicate to the CrossPageChannel that the channel is setup |
| // and ready to use. |
| this.channel_.notifyConnected(); |
| }; |
| |
| |
| /** |
| * Internal method called by the other page, via the NIX wrapper, |
| * to deliver a message. |
| * @param {string} serviceName The name of the service the message is to be |
| * delivered to. |
| * @param {string} payload The message to process. |
| * @private |
| */ |
| goog.net.xpc.NixTransport.prototype.handleMessage_ = |
| function(serviceName, payload) { |
| /** @this {goog.net.xpc.NixTransport} */ |
| var deliveryHandler = function() { |
| this.channel_.xpcDeliver(serviceName, payload); |
| }; |
| this.getWindow().setTimeout(goog.bind(deliveryHandler, this), 1); |
| }; |
| |
| |
| /** |
| * Sends a message. |
| * @param {string} service The name of the service the message is to be |
| * delivered to. |
| * @param {string} payload The message content. |
| * @override |
| */ |
| goog.net.xpc.NixTransport.prototype.send = function(service, payload) { |
| // Verify that the NIX channel we have is valid. |
| if (typeof(this.nixChannel_) !== 'unknown') { |
| goog.log.error(goog.net.xpc.logger, 'NIX channel not connected'); |
| } |
| |
| // Send the message via the NIX wrapper object. |
| this.nixChannel_['SendMessage'](service, payload); |
| }; |
| |
| |
| /** @override */ |
| goog.net.xpc.NixTransport.prototype.disposeInternal = function() { |
| goog.net.xpc.NixTransport.base(this, 'disposeInternal'); |
| this.nixChannel_ = null; |
| }; |