nixtransport.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Contains the NIX (Native IE XDC) method transport for
  16. * cross-domain communication. It exploits the fact that Internet Explorer
  17. * allows a window that is the parent of an iframe to set said iframe window's
  18. * opener property to an object. This object can be a function that in turn
  19. * can be used to send a message despite same-origin constraints. Note that
  20. * this function, if a pure JavaScript object, opens up the possibilitiy of
  21. * gaining a hold of the context of the other window and in turn, attacking
  22. * it. This implementation therefore wraps the JavaScript objects used inside
  23. * a VBScript class. Since VBScript objects are passed in JavaScript as a COM
  24. * wrapper (like DOM objects), they are thus opaque to JavaScript
  25. * (except for the interface they expose). This therefore provides a safe
  26. * method of transport.
  27. *
  28. *
  29. * Initially based on FrameElementTransport which shares some similarities
  30. * to this method.
  31. */
  32. goog.provide('goog.net.xpc.NixTransport');
  33. goog.require('goog.log');
  34. goog.require('goog.net.xpc');
  35. goog.require('goog.net.xpc.CfgFields');
  36. goog.require('goog.net.xpc.CrossPageChannelRole');
  37. goog.require('goog.net.xpc.Transport');
  38. goog.require('goog.net.xpc.TransportTypes');
  39. goog.require('goog.reflect');
  40. /**
  41. * NIX method transport.
  42. *
  43. * NOTE(user): NIX method tested in all IE versions starting from 6.0.
  44. *
  45. * @param {goog.net.xpc.CrossPageChannel} channel The channel this transport
  46. * belongs to.
  47. * @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for finding
  48. * the correct window.
  49. * @constructor
  50. * @extends {goog.net.xpc.Transport}
  51. * @final
  52. */
  53. goog.net.xpc.NixTransport = function(channel, opt_domHelper) {
  54. goog.net.xpc.NixTransport.base(this, 'constructor', opt_domHelper);
  55. /**
  56. * The channel this transport belongs to.
  57. * @type {goog.net.xpc.CrossPageChannel}
  58. * @private
  59. */
  60. this.channel_ = channel;
  61. /**
  62. * The authorization token, if any, used by this transport.
  63. * @type {?string}
  64. * @private
  65. */
  66. this.authToken_ = channel[goog.net.xpc.CfgFields.AUTH_TOKEN] || '';
  67. /**
  68. * The authorization token, if any, that must be sent by the other party
  69. * for setup to occur.
  70. * @type {?string}
  71. * @private
  72. */
  73. this.remoteAuthToken_ =
  74. channel[goog.net.xpc.CfgFields.REMOTE_AUTH_TOKEN] || '';
  75. // Conduct the setup work for NIX in general, if need be.
  76. goog.net.xpc.NixTransport.conductGlobalSetup_(this.getWindow());
  77. // Setup aliases so that VBScript can call these methods
  78. // on the transport class, even if they are renamed during
  79. // compression.
  80. this[goog.net.xpc.NixTransport.NIX_HANDLE_MESSAGE] = this.handleMessage_;
  81. this[goog.net.xpc.NixTransport.NIX_CREATE_CHANNEL] = this.createChannel_;
  82. };
  83. goog.inherits(goog.net.xpc.NixTransport, goog.net.xpc.Transport);
  84. // Consts for NIX. VBScript doesn't allow items to start with _ for some
  85. // reason, so we need to make these names quite unique, as they will go into
  86. // the global namespace.
  87. /**
  88. * Global name of the Wrapper VBScript class.
  89. * Note that this class will be stored in the *global*
  90. * namespace (i.e. window in browsers).
  91. * @type {string}
  92. */
  93. goog.net.xpc.NixTransport.NIX_WRAPPER = 'GCXPC____NIXVBS_wrapper';
  94. /**
  95. * Global name of the GetWrapper VBScript function. This
  96. * constant is used by JavaScript to call this function.
  97. * Note that this function will be stored in the *global*
  98. * namespace (i.e. window in browsers).
  99. * @type {string}
  100. */
  101. goog.net.xpc.NixTransport.NIX_GET_WRAPPER = 'GCXPC____NIXVBS_get_wrapper';
  102. /**
  103. * The name of the handle message method used by the wrapper class
  104. * when calling the transport.
  105. * @type {string}
  106. */
  107. goog.net.xpc.NixTransport.NIX_HANDLE_MESSAGE = 'GCXPC____NIXJS_handle_message';
  108. /**
  109. * The name of the create channel method used by the wrapper class
  110. * when calling the transport.
  111. * @type {string}
  112. */
  113. goog.net.xpc.NixTransport.NIX_CREATE_CHANNEL = 'GCXPC____NIXJS_create_channel';
  114. /**
  115. * A "unique" identifier that is stored in the wrapper
  116. * class so that the wrapper can be distinguished from
  117. * other objects easily.
  118. * @type {string}
  119. */
  120. goog.net.xpc.NixTransport.NIX_ID_FIELD = 'GCXPC____NIXVBS_container';
  121. /**
  122. * Determines if the installed version of IE supports accessing window.opener
  123. * after it has been set to a non-Window/null value. NIX relies on this being
  124. * possible.
  125. * @return {boolean} Whether window.opener behavior is compatible with NIX.
  126. */
  127. goog.net.xpc.NixTransport.isNixSupported = function() {
  128. var isSupported = false;
  129. try {
  130. var oldOpener = window.opener;
  131. // The compiler complains (as it should!) if we set window.opener to
  132. // something other than a window or null.
  133. window.opener = /** @type {Window} */ ({});
  134. isSupported = goog.reflect.canAccessProperty(window, 'opener');
  135. window.opener = oldOpener;
  136. } catch (e) {
  137. }
  138. return isSupported;
  139. };
  140. /**
  141. * Conducts the global setup work for the NIX transport method.
  142. * This function creates and then injects into the page the
  143. * VBScript code necessary to create the NIX wrapper class.
  144. * Note that this method can be called multiple times, as
  145. * it internally checks whether the work is necessary before
  146. * proceeding.
  147. * @param {Window} listenWindow The window containing the affected page.
  148. * @private
  149. */
  150. goog.net.xpc.NixTransport.conductGlobalSetup_ = function(listenWindow) {
  151. if (listenWindow['nix_setup_complete']) {
  152. return;
  153. }
  154. // Inject the VBScript code needed.
  155. var vbscript =
  156. // We create a class to act as a wrapper for
  157. // a Javascript call, to prevent a break in of
  158. // the context.
  159. 'Class ' + goog.net.xpc.NixTransport.NIX_WRAPPER + '\n ' +
  160. // An internal member for keeping track of the
  161. // transport for which this wrapper exists.
  162. 'Private m_Transport\n' +
  163. // An internal member for keeping track of the
  164. // auth token associated with the context that
  165. // created this wrapper. Used for validation
  166. // purposes.
  167. 'Private m_Auth\n' +
  168. // Method for internally setting the value
  169. // of the m_Transport property. We have the
  170. // isEmpty check to prevent the transport
  171. // from being overridden with an illicit
  172. // object by a malicious party.
  173. 'Public Sub SetTransport(transport)\n' +
  174. 'If isEmpty(m_Transport) Then\n' +
  175. 'Set m_Transport = transport\n' +
  176. 'End If\n' +
  177. 'End Sub\n' +
  178. // Method for internally setting the value
  179. // of the m_Auth property. We have the
  180. // isEmpty check to prevent the transport
  181. // from being overridden with an illicit
  182. // object by a malicious party.
  183. 'Public Sub SetAuth(auth)\n' +
  184. 'If isEmpty(m_Auth) Then\n' +
  185. 'm_Auth = auth\n' +
  186. 'End If\n' +
  187. 'End Sub\n' +
  188. // Returns the auth token to the gadget, so it can
  189. // confirm a match before initiating the connection
  190. 'Public Function GetAuthToken()\n ' +
  191. 'GetAuthToken = m_Auth\n' +
  192. 'End Function\n' +
  193. // A wrapper method which causes a
  194. // message to be sent to the other context.
  195. 'Public Sub SendMessage(service, payload)\n ' +
  196. 'Call m_Transport.' + goog.net.xpc.NixTransport.NIX_HANDLE_MESSAGE +
  197. '(service, payload)\n' +
  198. 'End Sub\n' +
  199. // Method for setting up the inner->outer
  200. // channel.
  201. 'Public Sub CreateChannel(channel)\n ' +
  202. 'Call m_Transport.' + goog.net.xpc.NixTransport.NIX_CREATE_CHANNEL +
  203. '(channel)\n' +
  204. 'End Sub\n' +
  205. // An empty field with a unique identifier to
  206. // prevent the code from confusing this wrapper
  207. // with a run-of-the-mill value found in window.opener.
  208. 'Public Sub ' + goog.net.xpc.NixTransport.NIX_ID_FIELD + '()\n ' +
  209. 'End Sub\n' +
  210. 'End Class\n ' +
  211. // Function to get a reference to the wrapper.
  212. 'Function ' + goog.net.xpc.NixTransport.NIX_GET_WRAPPER +
  213. '(transport, auth)\n' +
  214. 'Dim wrap\n' +
  215. 'Set wrap = New ' + goog.net.xpc.NixTransport.NIX_WRAPPER + '\n' +
  216. 'wrap.SetTransport transport\n' +
  217. 'wrap.SetAuth auth\n' +
  218. 'Set ' + goog.net.xpc.NixTransport.NIX_GET_WRAPPER + ' = wrap\n' +
  219. 'End Function';
  220. try {
  221. listenWindow.execScript(vbscript, 'vbscript');
  222. listenWindow['nix_setup_complete'] = true;
  223. } catch (e) {
  224. goog.log.error(
  225. goog.net.xpc.logger,
  226. 'exception caught while attempting global setup: ' + e);
  227. }
  228. };
  229. /**
  230. * The transport type.
  231. * @type {number}
  232. * @protected
  233. * @override
  234. */
  235. goog.net.xpc.NixTransport.prototype.transportType =
  236. goog.net.xpc.TransportTypes.NIX;
  237. /**
  238. * Keeps track of whether the local setup has completed (i.e.
  239. * the initial work towards setting the channel up has been
  240. * completed for this end).
  241. * @type {boolean}
  242. * @private
  243. */
  244. goog.net.xpc.NixTransport.prototype.localSetupCompleted_ = false;
  245. /**
  246. * The NIX channel used to talk to the other page. This
  247. * object is in fact a reference to a VBScript class
  248. * (see above) and as such, is in fact a COM wrapper.
  249. * When using this object, make sure to not access methods
  250. * without calling them, otherwise a COM error will be thrown.
  251. * @type {Object}
  252. * @private
  253. */
  254. goog.net.xpc.NixTransport.prototype.nixChannel_ = null;
  255. /**
  256. * Connect this transport.
  257. * @override
  258. */
  259. goog.net.xpc.NixTransport.prototype.connect = function() {
  260. if (this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.OUTER) {
  261. this.attemptOuterSetup_();
  262. } else {
  263. this.attemptInnerSetup_();
  264. }
  265. };
  266. /**
  267. * Attempts to setup the channel from the perspective
  268. * of the outer (read: container) page. This method
  269. * will attempt to create a NIX wrapper for this transport
  270. * and place it into the "opener" property of the inner
  271. * page's window object. If it fails, it will continue
  272. * to loop until it does so.
  273. *
  274. * @private
  275. */
  276. goog.net.xpc.NixTransport.prototype.attemptOuterSetup_ = function() {
  277. if (this.localSetupCompleted_) {
  278. return;
  279. }
  280. // Get shortcut to iframe-element that contains the inner
  281. // page.
  282. var innerFrame = this.channel_.getIframeElement();
  283. try {
  284. // Attempt to place the NIX wrapper object into the inner
  285. // frame's opener property.
  286. var theWindow = this.getWindow();
  287. var getWrapper = theWindow[goog.net.xpc.NixTransport.NIX_GET_WRAPPER];
  288. innerFrame.contentWindow.opener = getWrapper(this, this.authToken_);
  289. this.localSetupCompleted_ = true;
  290. } catch (e) {
  291. goog.log.error(
  292. goog.net.xpc.logger, 'exception caught while attempting setup: ' + e);
  293. }
  294. // If the retry is necessary, reattempt this setup.
  295. if (!this.localSetupCompleted_) {
  296. this.getWindow().setTimeout(goog.bind(this.attemptOuterSetup_, this), 100);
  297. }
  298. };
  299. /**
  300. * Attempts to setup the channel from the perspective
  301. * of the inner (read: iframe) page. This method
  302. * will attempt to *read* the opener object from the
  303. * page's opener property. If it succeeds, this object
  304. * is saved into nixChannel_ and the channel is confirmed
  305. * with the container by calling CreateChannel with an instance
  306. * of a wrapper for *this* page. Note that if this method
  307. * fails, it will continue to loop until it succeeds.
  308. *
  309. * @private
  310. */
  311. goog.net.xpc.NixTransport.prototype.attemptInnerSetup_ = function() {
  312. if (this.localSetupCompleted_) {
  313. return;
  314. }
  315. try {
  316. var opener = this.getWindow().opener;
  317. // Ensure that the object contained inside the opener
  318. // property is in fact a NIX wrapper.
  319. if (opener && goog.net.xpc.NixTransport.NIX_ID_FIELD in opener) {
  320. this.nixChannel_ = opener;
  321. // Ensure that the NIX channel given to use is valid.
  322. var remoteAuthToken = this.nixChannel_['GetAuthToken']();
  323. if (remoteAuthToken != this.remoteAuthToken_) {
  324. goog.log.error(
  325. goog.net.xpc.logger, 'Invalid auth token from other party');
  326. return;
  327. }
  328. // Complete the construction of the channel by sending our own
  329. // wrapper to the container via the channel they gave us.
  330. var theWindow = this.getWindow();
  331. var getWrapper = theWindow[goog.net.xpc.NixTransport.NIX_GET_WRAPPER];
  332. this.nixChannel_['CreateChannel'](getWrapper(this, this.authToken_));
  333. this.localSetupCompleted_ = true;
  334. // Notify channel that the transport is ready.
  335. this.channel_.notifyConnected();
  336. }
  337. } catch (e) {
  338. goog.log.error(
  339. goog.net.xpc.logger, 'exception caught while attempting setup: ' + e);
  340. return;
  341. }
  342. // If the retry is necessary, reattempt this setup.
  343. if (!this.localSetupCompleted_) {
  344. this.getWindow().setTimeout(goog.bind(this.attemptInnerSetup_, this), 100);
  345. }
  346. };
  347. /**
  348. * Internal method called by the inner page, via the
  349. * NIX wrapper, to complete the setup of the channel.
  350. *
  351. * @param {Object} channel The NIX wrapper of the
  352. * inner page.
  353. * @private
  354. */
  355. goog.net.xpc.NixTransport.prototype.createChannel_ = function(channel) {
  356. // Verify that the channel is in fact a NIX wrapper.
  357. if (typeof channel != 'unknown' ||
  358. !(goog.net.xpc.NixTransport.NIX_ID_FIELD in channel)) {
  359. goog.log.error(
  360. goog.net.xpc.logger, 'Invalid NIX channel given to createChannel_');
  361. }
  362. this.nixChannel_ = channel;
  363. // Ensure that the NIX channel given to use is valid.
  364. var remoteAuthToken = this.nixChannel_['GetAuthToken']();
  365. if (remoteAuthToken != this.remoteAuthToken_) {
  366. goog.log.error(goog.net.xpc.logger, 'Invalid auth token from other party');
  367. return;
  368. }
  369. // Indicate to the CrossPageChannel that the channel is setup
  370. // and ready to use.
  371. this.channel_.notifyConnected();
  372. };
  373. /**
  374. * Internal method called by the other page, via the NIX wrapper,
  375. * to deliver a message.
  376. * @param {string} serviceName The name of the service the message is to be
  377. * delivered to.
  378. * @param {string} payload The message to process.
  379. * @private
  380. */
  381. goog.net.xpc.NixTransport.prototype.handleMessage_ = function(
  382. serviceName, payload) {
  383. /** @this {goog.net.xpc.NixTransport} */
  384. var deliveryHandler = function() {
  385. this.channel_.xpcDeliver(serviceName, payload);
  386. };
  387. this.getWindow().setTimeout(goog.bind(deliveryHandler, this), 1);
  388. };
  389. /**
  390. * Sends a message.
  391. * @param {string} service The name of the service the message is to be
  392. * delivered to.
  393. * @param {string} payload The message content.
  394. * @override
  395. */
  396. goog.net.xpc.NixTransport.prototype.send = function(service, payload) {
  397. // Verify that the NIX channel we have is valid.
  398. if (typeof(this.nixChannel_) !== 'unknown') {
  399. goog.log.error(goog.net.xpc.logger, 'NIX channel not connected');
  400. }
  401. // Send the message via the NIX wrapper object.
  402. this.nixChannel_['SendMessage'](service, payload);
  403. };
  404. /** @override */
  405. goog.net.xpc.NixTransport.prototype.disposeInternal = function() {
  406. goog.net.xpc.NixTransport.base(this, 'disposeInternal');
  407. this.nixChannel_ = null;
  408. };