blob: a7639c0c4987f9b0c5622dc68f8cd5ab977d8f68 [file] [log] [blame]
// 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;
};