480 lines
15 KiB
JavaScript
480 lines
15 KiB
JavaScript
// 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.net.xpc');
|
|
goog.require('goog.net.xpc.CrossPageChannelRole');
|
|
goog.require('goog.net.xpc.Transport');
|
|
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}
|
|
*/
|
|
goog.net.xpc.NixTransport = function(channel, opt_domHelper) {
|
|
goog.base(this, 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.base(this, 'disposeInternal');
|
|
this.nixChannel_ = null;
|
|
};
|