1 /*
  2  * Licensed to the Apache Software Foundation (ASF) under one
  3  * or more contributor license agreements.  See the NOTICE file
  4  * distributed with this work for additional information
  5  * regarding copyright ownership.  The ASF licenses this file
  6  * to you under the Apache License, Version 2.0 (the
  7  * "License"); you may not use this file except in compliance
  8  * with the License.  You may obtain a copy of the License at
  9  *
 10  *     http://www.apache.org/licenses/LICENSE-2.0
 11  *
 12  * Unless required by applicable law or agreed to in writing,
 13  * software distributed under the License is distributed on an
 14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  * KIND, either express or implied.  See the License for the
 16  * specific language governing permissions and limitations
 17  * under the License.
 18  */
 19 
 20 /**
 21  * @fileoverview
 22  *
 23  * Provides unified configuration for all features.
 24  *
 25  * This is a custom shindig library that has not yet been submitted for
 26  * standardization. It is designed to make developing of features for the
 27  * opensocial / gadgets platforms easier and is intended as a supplemental
 28  * tool to Shindig's standardized feature loading mechanism.
 29  *
 30  * Usage:
 31  * First, you must register a component that needs configuration:
 32  * <pre>
 33  *   var config = {
 34  *     name : gadgets.config.NonEmptyStringValidator,
 35  *     url : new gadgets.config.RegExValidator(/.+%mySpecialValue%.+/)
 36  *   };
 37  *   gadgets.config.register("my-feature", config, myCallback);
 38  * </pre>
 39  *
 40  * This will register a component named "my-feature" that expects input config
 41  * containing a "name" field with a value that is a non-empty string, and a
 42  * "url" field with a value that matches the given regular expression.
 43  *
 44  * When gadgets.config.init is invoked by the container, it will automatically
 45  * validate your registered configuration and will throw an exception if
 46  * the provided configuration does not match what was required.
 47  *
 48  * Your callback will be invoked by passing all configuration data passed to
 49  * gadgets.config.init, which allows you to optionally inspect configuration
 50  * from other features, if present.
 51  *
 52  * Note that the container may optionally bypass configuration validation for
 53  * performance reasons. This does not mean that you should duplicate validation
 54  * code, it simply means that validation will likely only be performed in debug
 55  * builds, and you should assume that production builds always have valid
 56  * configuration.
 57  */
 58 
 59 var gadgets = gadgets || {};
 60 
 61 gadgets.config = function() {
 62   var components = {};
 63 
 64   return {
 65     /**
 66      * Registers a configurable component and its configuration parameters.
 67      *
 68      * @param {String} component The name of the component to register. Should
 69      *     be the same as the fully qualified name of the <Require> feature or
 70      *     the fully qualified javascript object reference (e.g. gadgets.io).
 71      * @param {Object} opt_validators Mapping of option name to validation
 72      *     functions that take the form function(data) {return isValid(data);}
 73      * @param {Function} opt_callback A function to be invoked when a
 74      *     configuration is registered. If passed, this function will be invoked
 75      *     immediately after a call to init has been made. Do not assume that
 76      *     dependent libraries have been configured until after init is
 77      *     complete. If you rely on this, it is better to defer calling
 78      *     dependent libraries until you can be sure that configuration is
 79      *     complete. Takes the form function(config), where config will be
 80      *     all registered config data for all components. This allows your
 81      *     component to read configuration from other components.
 82      * @throws {Error} If the component has already been registered.
 83      */
 84     register: function(component, opt_validators, opt_callback) {
 85       if (components[component]) {
 86         throw new Error('Component "' + component + '" is already registered.');
 87       }
 88       components[component] = {
 89         validators: opt_validators || {},
 90         callback: opt_callback
 91       };
 92     },
 93 
 94     /**
 95      * Retrieves configuration data on demand.
 96      *
 97      * @param {String} opt_component The component to fetch. If not provided
 98      *     all configuration will be returned.
 99      * @return {Object} The requested configuration.
100      * @throws {Error} If the given component has not been registered
101      */
102     get: function(opt_component) {
103       if (opt_component) {
104         if (!components[opt_component]) {
105           throw new Error('Component "' + opt_component + '" not registered.');
106         }
107         return configuration[opt_component] || {};
108       }
109       return configuration;
110     },
111 
112     /**
113      * Initializes the configuration.
114      *
115      * @param {Object} config The full set of configuration data.
116      * @param {Boolean} opt_noValidation True if you want to skip validation.
117      * @throws {Error} If there is a configuration error.
118      */
119     init: function(config, opt_noValidation) {
120       configuration = config;
121       for (var name in components) if (components.hasOwnProperty(name)) {
122         var component = components[name],
123             conf = config[name],
124             validators = component.validators;
125         if (!opt_noValidation) {
126           for (var v in validators) if (validators.hasOwnProperty(v)) {
127             if (!validators[v](conf[v])) {
128               throw new Error('Invalid config value "' + conf[v] +
129                   '" for parameter "' + v + '" in component "' +
130                   name + '"');
131             }
132           }
133         }
134         if (component.callback) {
135           component.callback(config);
136         }
137       }
138     },
139 
140     // Standard validators go here.
141 
142     /**
143      * Ensures that data is one of a fixed set of items.
144      * @param {Array.<String>} list The list of valid values.
145      * Also supports argument sytax: EnumValidator("Dog", "Cat", "Fish");
146      */
147     EnumValidator: function(list) {
148       var listItems = [];
149       if (arguments.length > 1) {
150         for (var i = 0, arg; arg = arguments[i]; ++i) {
151           listItems.push(arg);
152         }
153       } else {
154         listItems = list;
155       }
156       return function(data) {
157         for (var i = 0, test; test = listItems[i]; ++i) {
158           if (data === listItems[i]) {
159             return true;
160           }
161         }
162       };
163       return false;
164     },
165 
166     /**
167      * Tests the value against a regular expression.
168      */
169     RegExValidator: function(re) {
170       return function(data) {
171         return re.test(data);
172       };
173     },
174 
175     /**
176      * Validates that a value was provided.
177      */
178     ExistsValidator: function(data) {
179       return typeof data !== "undefined";
180     },
181 
182     /**
183      * Validates that a value is a non-empty string.
184      */
185     NonEmptyStringValidator: function(data) {
186       return typeof data === "string" && data.length > 0;
187     },
188 
189     /**
190      * Validates that the value is a boolean.
191      */
192     BooleanValidator: function(data) {
193       return typeof data === "boolean";
194     },
195 
196     /**
197      * Similar to the ECMAScript 4 virtual typing system, ensures that
198      * whatever object was passed in is "like" the existing object.
199      * Doesn't actually do type validation though, but instead relies
200      * on other validators.
201      *
202      * example:
203      *
204      *  var validator = new gadgets.config.LikeValidator(
205      *    "booleanField" : gadgets.config.BooleanValidator,
206      *    "regexField" : new gadgets.config.RegExValidator(/foo.+/);
207      *  );
208      *
209      * This can be used recursively as well to validate sub-objects.
210      *
211      * @param {Object} test The object to test against.
212      */
213     LikeValidator : function(test) {
214       return function(data) {
215         for (var member in test) if (test.hasOwnProperty(member)) {
216           var t = test[member];
217           if (!t(data[member])) {
218             return false;
219           }
220         }
221         return true;
222       };
223     }
224   };
225 }();
226 /*
227  * Licensed to the Apache Software Foundation (ASF) under one
228  * or more contributor license agreements.  See the NOTICE file
229  * distributed with this work for additional information
230  * regarding copyright ownership.  The ASF licenses this file
231  * to you under the Apache License, Version 2.0 (the
232  * "License"); you may not use this file except in compliance
233  * with the License.  You may obtain a copy of the License at
234  *
235  *     http://www.apache.org/licenses/LICENSE-2.0
236  *
237  * Unless required by applicable law or agreed to in writing,
238  * software distributed under the License is distributed on an
239  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
240  * KIND, either express or implied.  See the License for the
241  * specific language governing permissions and limitations
242  * under the License.
243  */
244 
245 var gadgets = {};
246 /*
247  * Licensed to the Apache Software Foundation (ASF) under one
248  * or more contributor license agreements. See the NOTICE file
249  * distributed with this work for additional information
250  * regarding copyright ownership. The ASF licenses this file
251  * to you under the Apache License, Version 2.0 (the
252  * "License"); you may not use this file except in compliance
253  * with the License. You may obtain a copy of the License at
254  *
255  *     http://www.apache.org/licenses/LICENSE-2.0
256  *
257  * Unless required by applicable law or agreed to in writing,
258  * software distributed under the License is distributed on an
259  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
260  * KIND, either express or implied. See the License for the
261  * specific language governing permissions and limitations under the License.
262  */
263 
264 /**
265  * @fileoverview This library augments gadgets.window with functionality
266  * to change the height of a gadget dynamically.
267  */
268 
269 var gadgets = gadgets || {};
270 
271 /**
272  * @static
273  * @class Provides operations for getting information about and modifying the
274  *     window the gadget is placed in.
275  * @name gadgets.window
276  */
277 gadgets.window = gadgets.window || {};
278 
279 // we wrap these in an anonymous function to avoid storing private data
280 // as members of gadgets.window.
281 (function() {
282 
283   var oldHeight;
284 
285   /**
286    * Detects the inner dimensions of a frame.
287    * See: http://www.quirksmode.org/viewport/compatibility.html for more
288    * information.
289    * @returns {Object} An object with width and height properties.
290    * @member gadgets.window
291    */
292   gadgets.window.getViewportDimensions = function() {
293     var x,y;
294     if (self.innerHeight) {
295       // all except Explorer
296       x = self.innerWidth;
297       y = self.innerHeight;
298     } else if (document.documentElement &&
299                document.documentElement.clientHeight) {
300       // Explorer 6 Strict Mode
301       x = document.documentElement.clientWidth;
302       y = document.documentElement.clientHeight;
303     } else if (document.body) {
304       // other Explorers
305       x = document.body.clientWidth;
306       y = document.body.clientHeight;
307     } else {
308       x = 0;
309       y = 0;
310     }
311     return {width: x, height: y};
312   };
313   
314   /**
315    * Sets the gadget title.
316    * @param {String} title The preferred title
317    * @member gadgets.window
318    */
319   gadgets.window.setTitle = function(title){};
320 
321   /**
322    * Adjusts the gadget height
323    * @param {Number} opt_height An optional preferred height in pixels. If not
324    *     specified, will attempt to fit the gadget to its content.
325    * @member gadgets.window
326    */
327   gadgets.window.adjustHeight = function(opt_height) {
328     var newHeight = parseInt(opt_height, 10);
329     var percentage = false;
330     if(isNaN(newHeight)){
331         var vh = gadgets.window.getViewportDimensions().height;
332         var body = document.body;
333         var docEl = document.documentElement;
334         if (document.compatMode == 'CSS1Compat' && docEl.scrollHeight){
335             newHeight = docEl.scrollHeight != vh ? docEl.scrollHeight : docEl.offsetHeight;
336         }
337         else{
338             var sh = docEl.scrollHeight;
339             var oh = docEl.offsetHeight;
340 
341             if(docEl.clientHeight != oh){
342                 sh = body.scrollHeight;
343                 oh = body.offsetHeight;
344             }
345 
346             if(sh > vh){
347                 newHeight = sh > oh ? sh : oh;
348             }
349             else{
350                 newHeight = sh < oh ? sh : oh;
351             }
352             
353             if(newHeight === vh &&
354                     window.navigator &&
355                     window.navigator.userAgent &&
356                     window.navigator.userAgent.toLowerCase().indexOf("safari") >= 0){ //for safari, quite ugly...
357                 var newDiv = document.createElement("div");
358                 newDiv.innerHTML = document.body.innerHTML;
359                 newDiv.style.visibility = "hidden";
360                 newDiv.id = "_temp_____div_for_____adjustHeight";
361                 document.body.appendChild(newDiv);
362                 newHeight = document.getElementById("_temp_____div_for_____adjustHeight").offsetHeight + 15;
363                 newDiv.innerHTML = "";
364                 document.body.removeChild(newDiv);
365             }
366         }
367     }
368     else if(0 === newHeight){
369         newHeight = parseFloat(opt_height);
370         if(!isNaN(newHeight) && (newHeight <= 1 || newHeight > 0)) percentage = true;
371     }
372 
373     if (newHeight != oldHeight || percentage) {
374         oldHeight = newHeight;
375         var p = opensocial.Container.get().params_;
376         _IFPC.call(
377             p.panelId,
378             "resizeWidget",
379             [p.panelId , newHeight],
380             p.remoteRelay,
381             null,
382             p.localRelay,
383             null); 
384     }
385 };
386 }());
387 
388 // Alias for legacy code
389 var _IG_AdjustIFrameHeight = gadgets.window.adjustHeight;
390 
391 // TODO Attach gadgets.window.adjustHeight to the onresize event
392 
393 /*
394 * Licensed to the Apache Software Foundation (ASF) under one
395 * or more contributor license agreements.  See the NOTICE file
396 * distributed with this work for additional information
397 * regarding copyright ownership.  The ASF licenses this file
398 * to you under the Apache License, Version 2.0 (the
399 * "License"); you may not use this file except in compliance
400 * with the License.  You may obtain a copy of the License at
401 *
402 *     http://www.apache.org/licenses/LICENSE-2.0
403 *
404 * Unless required by applicable law or agreed to in writing,
405 * software distributed under the License is distributed on an
406 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
407 * KIND, either express or implied.  See the License for the
408 * specific language governing permissions and limitations
409 * under the License.
410 */
411 
412 var gadgets = gadgets || {};
413 
414 /**
415 * @fileoverview Provides remote content retrieval facilities.
416 *     Available to every gadget.
417 */
418 
419 /**
420 * @static
421 * @class Provides remote content retrieval functions.
422 * @name gadgets.io
423 */
424 
425 gadgets.io = function() {
426     /**
427     * Holds configuration-related data such as proxy urls.
428     */
429     var config = {};
430 
431     /**
432     * Holds state for OAuth.
433     */
434     var oauthState;
435 
436     /**
437     * Internal facility to create an xhr request.
438     */
439     function makeXhr() {
440         if (window.XMLHttpRequest) {
441             return new window.XMLHttpRequest();
442         } else if (window.ActiveXObject) {
443             var x = new ActiveXObject("Msxml2.XMLHTTP");
444             if (!x) {
445                 x = new ActiveXObject("Microsoft.XMLHTTP");
446             }
447             return x;
448         }
449     }
450 
451     /**
452     * Checks the xobj for errors, may call the callback with an error response
453     * if the error is fatal.
454     *
455     * @param {Object} xobj The XHR object to check
456     * @param {Function} callback The callback to call if the error is fatal
457     * @return true if the xobj is not ready to be processed
458     */
459     function hadError(xobj, callback) {
460         if (xobj.readyState !== 4) {
461             return true;
462         }
463         if (xobj.status !== 200) {
464             // TODO Need to work on standardizing errors
465             callback({ errors: ["Error " + xobj.status] });
466             return true;
467         }
468         return false;
469     }
470 
471     /**
472     * Handles non-proxied XHR callback processing.
473     *
474     * @param {String} url
475     * @param {Function} callback
476     * @param {Object} params
477     * @param {Object} xobj
478     */
479     function processNonProxiedResponse(url, callback, params, xobj) {
480         if (hadError(xobj, callback)) {
481             return;
482         }
483         var data = {
484             body: xobj.responseText
485         };
486         callback(transformResponseData(params, data));
487     }
488 
489     var UNPARSEABLE_CRUFT = "throw 1; < don't be evil' >";
490 
491     /**
492     * Handles XHR callback processing.
493     *
494     * @param {String} url
495     * @param {Function} callback
496     * @param {Object} params
497     * @param {Object} xobj
498     */
499     function processResponse(url, callback, params, xobj) {
500         if (hadError(xobj, callback)) {
501             return;
502         }
503         var txt = xobj.responseText;
504         // remove unparseable cruft used to prevent cross-site script inclusion
505         txt = txt.substr(UNPARSEABLE_CRUFT.length);
506         // We are using eval directly here because the outer response comes from a
507         // trusted source, and json parsing is slow in IE.
508         var data = eval("(" + txt + ")");
509         data = data[url];
510         // Save off any transient OAuth state the server wants back later.
511         if (data.oauthState) {
512             oauthState = data.oauthState;
513         }
514         // Update the security token if the server sent us a new one
515         if (data.st) {
516             shindig.auth.updateSecurityToken(data.st);
517         }
518         callback(transformResponseData(params, data));
519     }
520 
521     function transformResponseData(params, data) {
522         var resp = {
523             text: data.body,
524             oauthApprovalUrl: data.oauthApprovalUrl,
525             oauthError: data.oauthError,
526             oauthErrorText: data.oauthErrorText,
527             errors: []
528         };
529         if (resp.text) {
530             switch (params.CONTENT_TYPE) {
531                 case "JSON":
532                 case "FEED":
533                     resp.data = gadgets.json.parse(resp.text);
534                     if (!resp.data) {
535                         resp.errors.push("failed to parse JSON");
536                         resp.data = null;
537                     }
538                     break;
539                 case "DOM":
540                     var dom;
541                     if (window.ActiveXObject) {
542                         dom = new ActiveXObject("Microsoft.XMLDOM");
543                         dom.async = false;
544                         dom.validateOnParse = false;
545                         dom.resolveExternals = false;
546                         if (!dom.loadXML(resp.text)) {
547                             resp.errors.push("failed to parse XML");
548                         } else {
549                             resp.data = dom;
550                         }
551                     } else {
552                         var parser = new DOMParser();
553                         dom = parser.parseFromString(resp.text, "text/xml");
554                         if ("parsererror" === dom.documentElement.nodeName) {
555                             resp.errors.push("failed to parse XML");
556                         } else {
557                             resp.data = dom;
558                         }
559                     }
560                     break;
561                 default:
562                     resp.data = resp.text;
563                     break;
564             }
565         }
566         return resp;
567     }
568 
569     /**
570     * Sends an XHR post or get request
571     *
572     * @param realUrl The url to fetch data from that was requested by the gadget
573     * @param proxyUrl The url to proxy through
574     * @param callback The function to call once the data is fetched
575     * @param postData The data to post to the proxyUrl
576     * @param params The params to use when processing the response
577     * @param processResponseFunction The function that should process the
578     *     response from the sever before calling the callback
579     */
580     function makeXhrRequest(realUrl, proxyUrl, callback, paramData, method,
581       params, processResponseFunction, opt_contentType) {
582         var xhr = makeXhr();
583 
584         xhr.open(method, proxyUrl, true);
585         if (callback) {
586             xhr.onreadystatechange = gadgets.util.makeClosure(
587           null, processResponseFunction, realUrl, callback, params, xhr);
588         }
589         if (paramData != null) {
590             xhr.setRequestHeader('Content-Type', opt_contentType || 'application/x-www-form-urlencoded');
591             xhr.send(paramData);
592         } else {
593             xhr.send(null);
594         }
595     }
596 
597 
598 
599     /**
600     * Satisfy a request with data that is prefetched as per the gadget Preload
601     * directive. The preloader will only satisfy a request for a specific piece
602     * of content once.
603     *
604     * @param postData The definition of the request to be executed by the proxy
605     * @param params The params to use when processing the response
606     * @param callback The function to call once the data is fetched
607     * @return true if the request can be satisfied by the preloaded content
608     *         false otherwise
609     */
610     function respondWithPreload(postData, params, callback) {
611         if (gadgets.io.preloaded_ && gadgets.io.preloaded_[postData.url]) {
612             var preload = gadgets.io.preloaded_[postData.url];
613             if (postData.httpMethod == "GET") {
614                 delete gadgets.io.preloaded_[postData.url];
615                 if (preload.rc !== 200) {
616                     callback({ errors: ["Error " + preload.rc] });
617                 } else {
618                     if (preload.oauthState) {
619                         oauthState = preload.oauthState;
620                     }
621                     var resp = {
622                         body: preload.body,
623                         oauthApprovalUrl: preload.oauthApprovalUrl,
624                         oauthError: preload.oauthError,
625                         oauthErrorText: preload.oauthErrorText,
626                         errors: []
627                     }
628                     callback(transformResponseData(params, resp));
629                 }
630                 return true;
631             }
632         }
633         return false;
634     }
635 
636     /**
637     * @param {Object} configuration Configuration settings
638     * @private
639     */
640     function init(configuration) {
641         config = configuration["core.io"];
642     }
643 
644     var requiredConfig = {
645         proxyUrl: new gadgets.config.RegExValidator(/.*%(raw)?url%.*/),
646         jsonProxyUrl: gadgets.config.NonEmptyStringValidator
647     };
648     gadgets.config.register("core.io", requiredConfig, init);
649 
650     return /** @scope gadgets.io */{
651     /**
652     * Fetches content from the provided URL and feeds that content into the
653     * callback function.
654     *
655     * Example:
656     * <pre>
657     * gadgets.io.makeRequest(url, fn,
658     *    {contentType: gadgets.io.ContentType.FEED});
659     * </pre>
660     *
661     * @param {String} url The URL where the content is located
662     * @param {Function} callback The function to call with the data from the
663     *     URL once it is fetched
664     * @param {Map.<gadgets.io.RequestParameters, Object>} opt_params
665     *     Additional
666     *     <a href="gadgets.io.RequestParameters.html">parameters</a>
667     *     to pass to the request
668     *
669     * @member gadgets.io
670     */
671     makeRequest: function(url, callback, opt_params) {
672         // TODO: This method also needs to respect all members of
673         // gadgets.io.RequestParameters, and validate them.
674 
675         var params = opt_params || {};
676 
677         var httpMethod = params.METHOD || "GET";
678         var refreshInterval = params.REFRESH_INTERVAL;
679 
680         // Check if authorization is requested
681         var auth, st;
682         if (params.AUTHORIZATION && params.AUTHORIZATION !== "NONE") {
683             auth = params.AUTHORIZATION.toLowerCase();
684             st = shindig.auth.getSecurityToken();
685         } else {
686             // Unauthenticated GET requests are cacheable
687             if (httpMethod === "GET" && refreshInterval === undefined) {
688                 refreshInterval = 3600;
689             }
690         }
691 
692         // Include owner information?
693         var signOwner = true;
694         if ("OWNER_SIGNED" in params) {
695             signOwner = params.OWNER_SIGNED;
696         }
697 
698         // Include viewer information?
699         var signViewer = true;
700         if ("VIEWER_SIGNED" in params) {
701             signViewer = params.VIEWER_SIGNED;
702         }
703 
704         var headers = params.HEADERS || {};
705         if (httpMethod === "POST" && !headers["Content-Type"]) {
706             headers["Content-Type"] = "application/x-www-form-urlencoded";
707         }
708 
709         var urlParams = gadgets.util.getUrlParameters();
710 
711         var paramData = {
712             url: url,
713             httpMethod: httpMethod,
714             headers: gadgets.io.encodeValues(headers, false),
715             postData: params.POST_DATA || "",
716             authz: auth || "",
717             st: st || "",
718             contentType: params.CONTENT_TYPE || "TEXT",
719             numEntries: params.NUM_ENTRIES || "3",
720             getSummaries: !!params.GET_SUMMARIES,
721             signOwner: signOwner,
722             signViewer: signViewer,
723             gadget: urlParams.url,
724             container: urlParams.container || urlParams.synd || "default",
725             // should we bypass gadget spec cache (e.g. to read OAuth provider URLs)
726             bypassSpecCache: gadgets.util.getUrlParameters().nocache || ""
727         };
728 
729         // OAuth goodies
730         if (params.AUTHORIZATION === "OAUTH") {
731             paramData.oauthState = oauthState || "";
732             // Just copy the OAuth parameters into the req to the server
733             for (opt in params) if (params.hasOwnProperty(opt)) {
734                 if (opt.indexOf("OAUTH_") === 0) {
735                     paramData[opt] = params[opt];
736                 }
737             }
738         }
739 
740         if (!respondWithPreload(paramData, params, callback, processResponse)) {
741             if (httpMethod === "GET" && refreshInterval > 0) {
742                 // this content should be cached
743                 // Add paramData to the URL
744                 var extraparams = "?refresh=" + refreshInterval + '&'
745               + gadgets.io.encodeValues(paramData);
746 
747                 makeXhrRequest(url, config.jsonProxyUrl + extraparams, callback,
748               null, "GET", params, processResponse);
749 
750             } else {
751                 makeXhrRequest(url, config.jsonProxyUrl, callback,
752               gadgets.io.encodeValues(paramData), "POST", params,
753               processResponse);
754             }
755         }
756     },
757 
758     /**
759     * @private
760     */
761     makeNonProxiedRequest: function(relativeUrl, callback, opt_params, opt_contentType) {
762         var params = opt_params || {};
763         makeXhrRequest(relativeUrl, relativeUrl, callback, params.POST_DATA,
764           params.METHOD, params, processNonProxiedResponse, opt_contentType);
765     },
766 
767     /**
768     * Used to clear out the oauthState, for testing only.
769     *
770     * @private
771     */
772     clearOAuthState: function() {
773         oauthState = undefined;
774     },
775 
776     /**
777     * Converts an input object into a URL-encoded data string.
778     * (key=value&...)
779     *
780     * @param {Object} fields The post fields you wish to encode
781     * @param {Boolean} opt_noEscaping An optional parameter specifying whether
782     *     to turn off escaping of the parameters. Defaults to false.
783     * @return {String} The processed post data in www-form-urlencoded format.
784     *
785     * @member gadgets.io
786     */
787     encodeValues: function(fields, opt_noEscaping) {
788         var escape = !opt_noEscaping;
789 
790         var buf = [];
791         var first = false;
792         for (var i in fields) if (fields.hasOwnProperty(i)) {
793             if (!first) {
794                 first = true;
795             } else {
796                 buf.push("&");
797             }
798             buf.push(escape ? encodeURIComponent(i) : i);
799             buf.push("=");
800             buf.push(escape ? encodeURIComponent(fields[i]) : fields[i]);
801         }
802         return buf.join("");
803     },
804 
805     /**
806     * Gets the proxy version of the passed-in URL.
807     *
808     * @param {String} url The URL to get the proxy URL for
809     * @param {Object} opt_params Optional Parameter Object.
810     *     The following properties are supported:
811     *       .REFRESH_INTERVAL The number of seconds that this
812     *           content should be cached.  Defaults to 3600.
813     *
814     * @return {String} The proxied version of the URL
815     *
816     * @member gadgets.io
817     */
818     getProxyUrl: function(url, opt_params) {
819         var params = opt_params || {};
820         var refresh = params.REFRESH_INTERVAL;
821         if (refresh === undefined) {
822             refresh = "3600";
823         }
824 
825         return config.proxyUrl.replace("%url%", escape(url)).
826           replace("%rawurl%", url).
827           replace("%refresh%", escape(refresh));
828     }  
829 };
830 } ();
831 
832 gadgets.io.RequestParameters = gadgets.util.makeEnum([
833   "METHOD",
834   "CONTENT_TYPE",
835   "POST_DATA",
836   "HEADERS",
837   "AUTHORIZATION",
838   "NUM_ENTRIES",
839   "GET_SUMMARIES",
840   "REFRESH_INTERVAL",
841   "OAUTH_SERVICE_NAME",
842   "OAUTH_TOKEN_NAME",
843   "OAUTH_REQUEST_TOKEN",
844   "OAUTH_REQUEST_TOKEN_SECRET"
845 ]);
846 
847 gadgets.io.ProxyUrlRequestParameters = gadgets.util.makeEnum([
848   "REFRESH_INTERVAL"
849 ]);
850 
851 gadgets.io.MethodType = gadgets.util.makeEnum([
852   "GET", "POST", "PUT", "DELETE", "HEAD"
853 ]);
854 
855 gadgets.io.ContentType = gadgets.util.makeEnum([
856   "TEXT", "DOM", "JSON", "FEED"
857 ]);
858 
859 gadgets.io.AuthorizationType = gadgets.util.makeEnum([
860   "NONE", "SIGNED", "OAUTH"
861 ]);
862 
863 
864 /**
865 * makeRequest implementation using proxy
866 * Fetches content from the provided URL and feeds that content into the callback function
867 * @param {String} url The URL where content is located
868 * @param {Function} callback The function to call with the data returned from url
869 * @param {gadgets.io.RequestParameters} Additional parameters to pass to the request
870 * @internal
871 */
872 gadgets.io.makeRequest = function(url, callback, opt_params) {
873     if (opt_params && opt_params[gadgets.io.RequestParameters.AUTHORIZATION] === gadgets.io.AuthorizationType.OAUTH) {
874         content_errored({ "errorCode": opensocial.ResponseItem.Error.NOT_IMPLEMENTED, "errorMessage": "gadgets.io.AuthorizationType.OAUTH is not supported." }, url);
875         return;
876     }
877 
878     opt_params = opt_params || {};
879 
880     var params = {};
881     params.authType = opt_params[gadgets.io.RequestParameters.AUTHORIZATION] || gadgets.io.AuthorizationType.NONE;
882     params.contentType = opt_params[gadgets.io.RequestParameters.CONTENT_TYPE] || gadgets.io.ContentType.TEXT;
883     params.method = opt_params[gadgets.io.RequestParameters.METHOD] || gadgets.io.MethodType.GET;
884     params.postData = opt_params[gadgets.io.RequestParameters.POST_DATA] || null;
885     params.refresh = opt_params[gadgets.io.ProxyUrlRequestParameters.REFRESH_INTERVAL] || 3600;
886 
887     if (typeof (params.postData) != "string") params.postData = encodeValues(params.postData);
888 
889     params.postDataLength = (params.postData != null && params.postData.length > 0) ? params.postData.length : 0;
890     params.headers = opt_params[gadgets.io.RequestParameters.HEADERS] || null;
891     params.numEntries = opt_params[gadgets.io.RequestParameters.NUM_ENTRIES] || 3;
892     params.summariesOnly = opt_params[gadgets.io.RequestParameters.GET_SUMMARIES] || false;
893     
894     if(url.indexOf('/proxy/relay.proxy') !== -1){
895         var relay_url = url;
896     }
897     else
898     { 
899         var relay_url = "/proxy/relay.proxy?opensocial_token=";
900         relay_url += MyOpenSpace.MySpaceContainer.OSToken;
901         relay_url += "&opensocial_url=";
902         relay_url += escape(url);
903         
904         // Non auth'd requests are cachable
905         if(params.authType === gadgets.io.AuthorizationType.SIGNED){
906             var opensocial_app_url = "";
907             var env = opensocial.getEnvironment();
908             if(env && env.currentApplication){
909                 opensocial_app_url = env.currentApplication.getField(MyOpenSpace.Application.Field.PROFILE_URL);
910             }
911             relay_url += "&opensocial_authtype=SIGNED&opensocial_app_url=" + escape(opensocial_app_url);
912         }
913         else if(params.authType === gadgets.io.AuthorizationType.NONE){
914             relay_url += "&refresh=" + params.refresh;
915         }
916         else{
917             content_errored({ "errorCode": opensocial.ResponseItem.Error.NOT_IMPLEMENTED, "errorMessage": "only gadgets.io.AuthorizationType.NONE and .SIGNED are supported." }, url);
918             return;
919         }
920     }
921 
922     MyOpenSpace.Ajax.sendContentRequest(relay_url, content_completed, content_errored, params);
923 
924     function content_completed(xobj, url, params) {
925         opt_params = opt_params || {};
926 
927         if (xobj.readyState !== 4) {
928             return;
929         }
930         if (xobj.status !== 200) {
931             // TODO Need to work on standardizing errors
932             callback({ errors: ["Error " + xobj.status] });
933             return;
934         }
935         var txt = xobj.responseText;
936         // remove unparseable cruft.
937         // TODO: really remove this by eliminating it. It's not any real security
938         //    to begin with, and we can solve this problem by using post requests
939         //    and / or passing the url in the http headers.
940         // txt = txt.substr(UNPARSEABLE_CRUFT.length);
941 
942         var data = {};
943         data.body = xobj.responseText;
944         var resp = {
945             text: data.body,
946             errors: []
947         };
948 
949         switch (params.contentType) {
950             case "JSON":
951                 resp.data = gadgets.json.parse(resp.text);
952                 if (!resp.data) {
953                     resp.errors.push("failed to parse JSON");
954                     resp.data = null;
955                 }
956                 break;
957             case "FEED":
958                 var dom;
959                 if (window.ActiveXObject) {
960                     dom = new ActiveXObject("Microsoft.XMLDOM");
961                     dom.async = false;
962                     dom.validateOnParse = false;
963                     dom.resolveExternals = false;
964                     if (!dom.loadXML(resp.text)) {
965                         resp.errors.push("failed to parse XML");
966                     } else {
967                         resp.data = dom;
968                     }
969                 }
970                 else {
971                     var parser = new DOMParser();
972                     dom = parser.parseFromString(resp.text, "text/xml");
973                     if ("parsererror" === dom.documentElement.nodeName) {
974                         resp.errors.push("failed to parse XML");
975                     }
976                     else {
977                         resp.data = dom;
978                     }
979                 }
980 
981                 if (params.summariesOnly) {
982                     resp.data = new MyOpenSpace.Feed.RSS2.Channel(resp.data, true, params.numEntries);
983                 }
984                 else {
985                     resp.data = new MyOpenSpace.Feed.RSS2.Channel(resp.data, false, params.numEntries);
986                 }
987                 break;
988             case "DOM":
989                 var dom;
990                 if (window.ActiveXObject) {
991                     dom = new ActiveXObject("Microsoft.XMLDOM");
992                     dom.async = false;
993                     dom.validateOnParse = false;
994                     dom.resolveExternals = false;
995                     if (!dom.loadXML(resp.text)) {
996                         resp.errors.push("failed to parse XML");
997                     }
998                     else {
999                         resp.data = dom;
1000                     }
1001                 }
1002                 else {
1003                     var parser = new DOMParser();
1004                     dom = parser.parseFromString(resp.text, "text/xml");
1005                     if ("parsererror" === dom.documentElement.nodeName) {
1006                         resp.errors.push("failed to parse XML");
1007                     }
1008                     else {
1009                         resp.data = dom;
1010                     }
1011                 }
1012                 break;
1013             default:
1014                 resp.data = resp.text;
1015                 break;
1016         }
1017 
1018         var errored = (resp.errors.length >= 1) ? true : false;
1019         if (!errored) {
1020             if (opt_params["LEGACY"]) {
1021                 resp = resp.data;
1022             }
1023         }
1024         callback(resp, url, errored);
1025     }
1026 
1027     function content_errored(response, url) {
1028         callback(response, url, true);
1029     }
1030 
1031     function encodeValues(fields) {
1032         var buf = [];
1033         var first = false;
1034         for (var i in fields) {
1035             if (!first) {
1036                 first = true;
1037             }
1038             else {
1039                 buf.push("&");
1040             }
1041             buf.push(encodeURIComponent(i));
1042             buf.push("=");
1043             buf.push(encodeURIComponent(fields[i]));
1044         }
1045         return buf.join("");
1046     }
1047 };
1048 /*
1049  * Licensed to the Apache Software Foundation (ASF) under one
1050  * or more contributor license agreements.  See the NOTICE file
1051  * distributed with this work for additional information
1052  * regarding copyright ownership.  The ASF licenses this file
1053  * to you under the Apache License, Version 2.0 (the
1054  * "License"); you may not use this file except in compliance
1055  * with the License.  You may obtain a copy of the License at
1056  *
1057  *     http://www.apache.org/licenses/LICENSE-2.0
1058  *
1059  * Unless required by applicable law or agreed to in writing,
1060  * software distributed under the License is distributed on an
1061  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
1062  * KIND, either express or implied.  See the License for the
1063  * specific language governing permissions and limitations
1064  * under the License.
1065  */
1066 
1067 /**
1068  * @fileoverview
1069  * The global object gadgets.json contains two methods.
1070  *
1071  * gadgets.json.stringify(value) takes a JavaScript value and produces a JSON
1072  * text. The value must not be cyclical.
1073  *
1074  * gadgets.json.parse(text) takes a JSON text and produces a JavaScript value.
1075  * It will return false if there is an error.
1076 */
1077 
1078 var gadgets = gadgets || {};
1079 
1080 /**
1081  * @static
1082  * @class Provides operations for translating objects to and from JSON.
1083  * @name gadgets.json
1084  */
1085 
1086 /**
1087  * Port of the public domain JSON library by Douglas Crockford.
1088  * See: http://www.json.org/json2.js
1089  */
1090 gadgets.json = function () {
1091 
1092   /**
1093    * Formats integers to 2 digits.
1094    * @param {Number} n
1095    */
1096   function f(n) {
1097     return n < 10 ? '0' + n : n;
1098   }
1099 
1100   Date.prototype.toJSON = function () {
1101     return [this.getUTCFullYear(), '-',
1102            f(this.getUTCMonth() + 1), '-',
1103            f(this.getUTCDate()), 'T',
1104            f(this.getUTCHours()), ':',
1105            f(this.getUTCMinutes()), ':',
1106            f(this.getUTCSeconds()), 'Z'].join("");
1107   };
1108 
1109   // table of character substitutions
1110   var m = {
1111     '\b': '\\b',
1112     '\t': '\\t',
1113     '\n': '\\n',
1114     '\f': '\\f',
1115     '\r': '\\r',
1116     '"' : '\\"',
1117     '\\': '\\\\'
1118   };
1119 
1120   /**
1121    * Converts a json object into a string.
1122    */
1123   function stringify(value) {
1124     var a,          // The array holding the partial texts.
1125         i,          // The loop counter.
1126         k,          // The member key.
1127         l,          // Length.
1128         r = /["\\\x00-\x1f\x7f-\x9f]/g,
1129         v;          // The member value.
1130 
1131     switch (typeof value) {
1132     case 'string':
1133     // If the string contains no control characters, no quote characters, and no
1134     // backslash characters, then we can safely slap some quotes around it.
1135     // Otherwise we must also replace the offending characters with safe ones.
1136       return r.test(value) ?
1137           '"' + value.replace(r, function (a) {
1138             var c = m[a];
1139             if (c) {
1140               return c;
1141             }
1142             c = a.charCodeAt();
1143             return '\\u00' + Math.floor(c / 16).toString(16) +
1144                 (c % 16).toString(16);
1145             }) + '"'
1146           : '"' + value + '"';
1147     case 'number':
1148     // JSON numbers must be finite. Encode non-finite numbers as null.
1149       return isFinite(value) ? String(value) : 'null';
1150     case 'boolean':
1151     case 'null':
1152       return String(value);
1153     case 'object':
1154     // Due to a specification blunder in ECMAScript,
1155     // typeof null is 'object', so watch out for that case.
1156       if (!value) {
1157         return 'null';
1158       }
1159       // toJSON check removed; re-implement when it doesn't break other libs.
1160       a = [];
1161       if (typeof value.length === 'number' &&
1162           !(value.propertyIsEnumerable('length'))) {
1163         // The object is an array. Stringify every element. Use null as a
1164         // placeholder for non-JSON values.
1165         l = value.length;
1166         for (i = 0; i < l; i += 1) {
1167           a.push(stringify(value[i]) || 'null');
1168         }
1169         // Join all of the elements together and wrap them in brackets.
1170         return '[' + a.join(',') + ']';
1171       }
1172       // Otherwise, iterate through all of the keys in the object.
1173       for (k in value) if (value.hasOwnProperty(k)) {
1174         if (typeof k === 'string') {
1175           v = stringify(value[k]);
1176           if (v) {
1177             a.push(stringify(k) + ':' + v);
1178           }
1179         }
1180       }
1181       // Join all of the member texts together and wrap them in braces.
1182       return '{' + a.join(',') + '}';
1183     }
1184   }
1185 
1186   return {
1187     stringify: stringify,
1188     parse: function (text) {
1189 // Parsing happens in three stages. In the first stage, we run the text against
1190 // regular expressions that look for non-JSON patterns. We are especially
1191 // concerned with '()' and 'new' because they can cause invocation, and '='
1192 // because it can cause mutation. But just to be safe, we want to reject all
1193 // unexpected forms.
1194 
1195 // We split the first stage into 4 regexp operations in order to work around
1196 // crippling inefficiencies in IE's and Safari's regexp engines. First we
1197 // replace all backslash pairs with '@' (a non-JSON character). Second, we
1198 // replace all simple value tokens with ']' characters. Third, we delete all
1199 // open brackets that follow a colon or comma or that begin the text. Finally,
1200 // we look to see that the remaining characters are only whitespace or ']' or
1201 // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
1202 
1203       if (/^[\],:{}\s]*$/.test(text.replace(/\\["\\\/b-u]/g, '@').
1204           replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
1205           replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
1206         return eval('(' + text + ')');
1207       }
1208       // If the text is not JSON parseable, then return false.
1209 
1210       return false;
1211     }
1212   };
1213 }();
1214 
1215 /*
1216  * Licensed to the Apache Software Foundation (ASF) under one
1217  * or more contributor license agreements.  See the NOTICE file
1218  * distributed with this work for additional information
1219  * regarding copyright ownership.  The ASF licenses this file
1220  * to you under the Apache License, Version 2.0 (the
1221  * "License"); you may not use this file except in compliance
1222  * with the License.  You may obtain a copy of the License at
1223  *
1224  *     http://www.apache.org/licenses/LICENSE-2.0
1225  *
1226  * Unless required by applicable law or agreed to in writing,
1227  * software distributed under the License is distributed on an
1228  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
1229  * KIND, either express or implied.  See the License for the
1230  * specific language governing permissions and limitations
1231  * under the License.
1232  */
1233 
1234 /**
1235  * @fileoverview
1236  *
1237  * Provides access to user prefs, module dimensions, and messages.
1238  *
1239  * Clients can access their preferences by constructing an instance of
1240  * gadgets.Prefs and passing in their module id.  Example:
1241  *
1242  *   var prefs = new gadgets.Prefs();
1243  *   var name = prefs.getString("name");
1244  *   var lang = prefs.getLang();
1245  *
1246  * Modules with type=url can also use this library to parse arguments passed
1247  * by URL, but this is not the common case.
1248  *
1249  *   
1250  */
1251 
1252 var gadgets = gadgets || {};
1253 
1254 (function() {
1255 
1256 var instance = null;
1257 var prefs = {};
1258 var messages = {};
1259 var language = "en";
1260 var country = "US";
1261 var moduleId = 0;
1262 
1263 /**
1264  * Parses all parameters from the url and stores them
1265  * for later use when creating a new gadgets.Prefs object.
1266  */
1267 function parseUrl() {
1268   var params = gadgets.util.getUrlParameters();
1269   for (var i in params) if (params.hasOwnProperty(i)) {
1270     if (i.indexOf("up_") === 0 && i.length > 3) {
1271       prefs[i.substr(3)] = String(params[i]);
1272     } else if (i === "country") {
1273       country = params[i];
1274     } else if (i === "lang") {
1275       language = params[i];
1276     } else if (i === "mid") {
1277       moduleId = params[i];
1278     }
1279   }
1280 };
1281 
1282 /**
1283  * @class
1284  * Provides access to user preferences, module dimensions, and messages.
1285  *
1286  * Clients can access their preferences by constructing an instance of
1287  * gadgets.Prefs and passing in their module id.  Example:
1288  *
1289 <pre>var prefs = new gadgets.Prefs();
1290 var name = prefs.getString("name");
1291 var lang = prefs.getLang();</pre>
1292  *
1293  * @description Creates a new Prefs object.
1294  *
1295  * Note: this is actually a singleton. All prefs are linked. If you're wondering
1296  * why this is a singleton and not just a collection of package functions, the
1297  * simple answer is that it's how the spec is written. The spec is written this
1298  * way for legacy compatibility with igoogle.
1299  */
1300 gadgets.Prefs = function() {
1301   if (!instance) {
1302     parseUrl();
1303     instance = this;
1304   }
1305   return instance;
1306 };
1307 
1308 /**
1309  * Sets internal values
1310  */
1311 gadgets.Prefs.setInternal_ = function(key, value) {
1312   if (typeof key === "string") {
1313     prefs[key] = value;
1314   } else {
1315     for (var k in key) if (key.hasOwnProperty(k)) {
1316       prefs[k] = key[k];
1317     }
1318   }
1319 };
1320 
1321 /**
1322  * Initializes message bundles.
1323  */
1324 gadgets.Prefs.setMessages_ = function(messages) {
1325   msgs = messages;
1326 };
1327 
1328 /**
1329  * Retrieves a preference as a string.
1330  * Returned value will be html entity escaped.
1331  *
1332  * @param {String} key The preference to fetch
1333  * @return {String} The preference; if not set, an empty string
1334  */
1335 gadgets.Prefs.prototype.getString = function(key) {
1336   return prefs[key] ? gadgets.util.escapeString(prefs[key]) : "";
1337 };
1338 
1339 /**
1340  * Retrieves a preference as an integer.
1341  * @param {String} key The preference to fetch
1342  * @return {Number} The preference; if not set, 0
1343  */
1344 gadgets.Prefs.prototype.getInt = function(key) {
1345   var val = parseInt(prefs[key], 10);
1346   return isNaN(val) ? 0 : val;
1347 };
1348 
1349 /**
1350  * Retrieves a preference as a floating-point value.
1351  * @param {String} key The preference to fetch
1352  * @return {Number} The preference; if not set, 0
1353  */
1354 gadgets.Prefs.prototype.getFloat = function(key) {
1355   var val = parseFloat(prefs[key]);
1356   return isNaN(val) ? 0 : val;
1357 };
1358 
1359 /**
1360  * Retrieves a preference as a boolean.
1361  * @param {String} key The preference to fetch
1362  * @return {Boolean} The preference; if not set, false
1363  */
1364 gadgets.Prefs.prototype.getBool = function(key) {
1365   var val = prefs[key];
1366   if (val) {
1367     return val === "true" || val === true || !!parseInt(val, 10);
1368   }
1369   return false;
1370 };
1371 
1372 /**
1373  * Stores a preference.
1374  * To use this call,
1375  * the gadget must require the feature setprefs.
1376  *
1377  * <p class="note">
1378  * <b>Note:</b>
1379  * If the gadget needs to store an Array it should use setArray instead of
1380  * this call.
1381  * </p>
1382  *
1383  * @param {String} key The pref to store
1384  * @param {Object} val The values to store
1385  */
1386 gadgets.Prefs.prototype.set = function(key, value) {
1387   throw new Error("setprefs feature required to make this call.");
1388 };
1389 
1390 /**
1391  * Retrieves a preference as an array.
1392  * UserPref values that were not declared as lists are treated as
1393  * one-element arrays.
1394  *
1395  * @param {String} key The preference to fetch
1396  * @return {Array.<String>} The preference; if not set, an empty array
1397  */
1398 gadgets.Prefs.prototype.getArray = function(key) {
1399   var val = prefs[key];
1400   if (val) {
1401     var arr = val.split("|");
1402     // Decode pipe characters.
1403     var esc = gadgets.util.escapeString;
1404     for (var i = 0, j = arr.length; i < j; ++i) {
1405       arr[i] = esc(arr[i].replace(/%7C/g, "|"));
1406     }
1407     return arr;
1408   }
1409   return [];
1410 };
1411 
1412 /**
1413  * Stores an array preference.
1414  * To use this call,
1415  * the gadget must require the feature setprefs.
1416  *
1417  * @param {String} key The pref to store
1418  * @param {Array} val The values to store
1419  */
1420 gadgets.Prefs.prototype.setArray = function(key, val) {
1421   throw new Error("setprefs feature required to make this call.");
1422 };
1423 
1424 /**
1425  * Fetches an unformatted message.
1426  * @param {String} key The message to fetch
1427  * @return {String} The message
1428  */
1429 gadgets.Prefs.prototype.getMsg = function(key) {
1430   return msgs[key] || "";
1431 };
1432 
1433 /**
1434  * Gets the current country, returned as ISO 3166-1 alpha-2 code.
1435  *
1436  * @return {String} The country for this module instance
1437  */
1438 gadgets.Prefs.prototype.getCountry = function() {
1439   return country;
1440 };
1441 
1442 /**
1443  * Gets the current language the gadget should use when rendering, returned as a
1444  * ISO 639-1 language code.
1445  *
1446  * @return {String} The language for this module instance
1447  */
1448 gadgets.Prefs.prototype.getLang = function() {
1449   return language;
1450 };
1451 
1452 /**
1453  * Gets the module id for the current instance.
1454  *
1455  * @return {String | Number} The module id for this module instance
1456  */
1457 gadgets.Prefs.prototype.getModuleId = function() {
1458   return moduleId;
1459 };
1460 
1461 })();
1462 /*
1463  * Licensed to the Apache Software Foundation (ASF) under one
1464  * or more contributor license agreements.  See the NOTICE file
1465  * distributed with this work for additional information
1466  * regarding copyright ownership.  The ASF licenses this file
1467  * to you under the Apache License, Version 2.0 (the
1468  * "License"); you may not use this file except in compliance
1469  * with the License.  You may obtain a copy of the License at
1470  *
1471  *     http://www.apache.org/licenses/LICENSE-2.0
1472  *
1473  * Unless required by applicable law or agreed to in writing,
1474  * software distributed under the License is distributed on an
1475  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
1476  * KIND, either express or implied.  See the License for the
1477  * specific language governing permissions and limitations
1478  * under the License.
1479  */
1480 
1481 var gadgets = gadgets || {};
1482 
1483 /**
1484  * @fileoverview General purpose utilities that gadgets can use.
1485  */
1486 
1487 /**
1488  * @static
1489  * @class Provides general-purpose utility functions.
1490  * @name gadgets.util
1491  */
1492 
1493 gadgets.util = function() {
1494   /**
1495    * Parses URL parameters into an object.
1496    * @return {Array.<String>} The parameters
1497    */
1498   function parseUrlParams() {
1499     // Get settings from url, 'hash' takes precedence over 'search' component
1500     // don't use document.location.hash due to browser differences.
1501     var query;
1502     var l = document.location.href;
1503     var queryIdx = l.indexOf("?");
1504     var hashIdx = l.indexOf("#");
1505     if (hashIdx === -1) {
1506       query = l.substr(queryIdx + 1);
1507     } else {
1508       // essentially replaces "#" with "&"
1509       query = [l.substr(queryIdx + 1, hashIdx - queryIdx - 1), "&",
1510                l.substr(hashIdx + 1)].join("");
1511     }
1512     return query.split("&");
1513   }
1514 
1515   var parameters = null;
1516   var features = {};
1517   var onLoadHandlers = [];
1518 
1519   // Maps code points to the value to replace them with.
1520   // If the value is "false", the character is removed entirely, otherwise
1521   // it will be replaced with an html entity.
1522   var escapeCodePoints = {
1523    // nul; most browsers truncate because they use c strings under the covers.
1524    0 : false,
1525    // new line
1526    10 : true,
1527    // carriage return
1528    13 : true,
1529    // double quote
1530    34 : true,
1531    // single quote
1532    39 : true,
1533    // less than
1534    60 : true,
1535    // greater than
1536    62 : true,
1537    // Backslash
1538    92 : true,
1539    // line separator
1540    8232 : true,
1541    // paragraph separator
1542    8233 : true
1543   };
1544 
1545   /**
1546    * Regular expression callback that returns strings from unicode code points.
1547    *
1548    * @param {Array} match Ignored
1549    * @param {String} value The codepoint value to convert
1550    * @return {String} The character corresponding to value.
1551    */
1552   function unescapeEntity(match, value) {
1553     return String.fromCharCode(value);
1554   }
1555 
1556   /**
1557    * Initializes feature parameters.
1558    */
1559   function init(config) {
1560     features = config["core.util"] || {};
1561   }
1562   if (gadgets.config) {
1563     gadgets.config.register("core.util", null, init);
1564   }
1565 
1566   return /** @scope gadgets.util */ {
1567 
1568     /**
1569      * Gets the URL parameters.
1570      *
1571      * @return {Object} Parameters passed into the query string
1572      * @member gadgets.util
1573      * @private Implementation detail.
1574      */
1575     getUrlParameters : function () {
1576       if (parameters !== null) {
1577         return parameters;
1578       }
1579       parameters = {};
1580       var pairs = parseUrlParams();
1581       var unesc = window.decodeURIComponent ? decodeURIComponent : unescape;
1582       for (var i = 0, j = pairs.length; i < j; ++i) {
1583         var pos = pairs[i].indexOf('=');
1584         if (pos === -1) {
1585           continue;
1586         }
1587         var argName = pairs[i].substring(0, pos);
1588         var value = pairs[i].substring(pos + 1);
1589         // difference to IG_Prefs, is that args doesn't replace spaces in
1590         // argname. Unclear on if it should do:
1591         // argname = argname.replace(/\+/g, " ");
1592         value = value.replace(/\+/g, " ");
1593         parameters[argName] = unesc(value);
1594       }
1595       return parameters;
1596     },
1597 
1598     /**
1599      * Creates a closure that is suitable for passing as a callback.
1600      * Any number of arguments
1601      * may be passed to the callback;
1602      * they will be received in the order they are passed in.
1603      *
1604      * @param {Object} scope The execution scope; may be null if there is no
1605      *     need to associate a specific instance of an object with this
1606      *     callback
1607      * @param {Function} callback The callback to invoke when this is run;
1608      *     any arguments passed in will be passed after your initial arguments
1609      * @param {Object} var_args Initial arguments to be passed to the callback
1610      *
1611      * @member gadgets.util
1612      * @private Implementation detail.
1613      */
1614     makeClosure : function (scope, callback, var_args) {
1615       // arguments isn't a real array, so we copy it into one.
1616       var baseArgs = [];
1617       for (var i = 2, j = arguments.length; i < j; ++i) {
1618        baseArgs.push(arguments[i]);
1619       }
1620       return function() {
1621         // append new arguments.
1622         var tmpArgs = baseArgs.slice();
1623         for (var i = 0, j = arguments.length; i < j; ++i) {
1624           tmpArgs.push(arguments[i]);
1625         }
1626         return callback.apply(scope, tmpArgs);
1627       };
1628     },
1629 
1630     /**
1631      * Utility function for generating an "enum" from an array.
1632      *
1633      * @param {Array.<String>} values The values to generate.
1634      * @return {Map<String,String>} An object with member fields to handle
1635      *   the enum.
1636      *
1637      * @private Implementation detail.
1638      */
1639     makeEnum : function (values) {
1640       var obj = {};
1641       for (var i = 0, v; v = values[i]; ++i) {
1642         obj[v] = v;
1643       }
1644       return obj;
1645     },
1646 
1647     /**
1648      * Gets the feature parameters.
1649      *
1650      * @param {String} feature The feature to get parameters for
1651      * @return {Object} The parameters for the given feature, or null
1652      *
1653      * @member gadgets.util
1654      */
1655     getFeatureParameters : function (feature) {
1656       return typeof features[feature] === "undefined"
1657           ? null : features[feature];
1658     },
1659 
1660     /**
1661      * Returns whether the current feature is supported.
1662      *
1663      * @param {String} feature The feature to test for
1664      * @return {Boolean} True if the feature is supported
1665      *
1666      * @member gadgets.util
1667      */
1668     hasFeature : function (feature) {
1669       return typeof features[feature] !== "undefined";
1670     },
1671 
1672     /**
1673      * Registers an onload handler.
1674      * @param {Function} callback The handler to run
1675      *
1676      * @member gadgets.util
1677      */
1678     registerOnLoadHandler : function (callback) {
1679       onLoadHandlers.push(callback);
1680     },
1681 
1682     /**
1683      * Runs all functions registered via registerOnLoadHandler.
1684      * @private Only to be used by the container, not gadgets.
1685      */
1686     runOnLoadHandlers : function () {
1687       for (var i = 0, j = onLoadHandlers.length; i < j; ++i) {
1688         onLoadHandlers[i]();
1689       }
1690     },
1691 
1692     /**
1693      * Escapes the input using html entities to make it safer.
1694      *
1695      * If the input is a string, uses gadgets.util.escapeString.
1696      * If it is an array, calls escape on each of the array elements
1697      * if it is an object, will only escape all the mapped keys and values if
1698      * the opt_escapeObjects flag is set. This operation involves creating an
1699      * entirely new object so only set the flag when the input is a simple
1700      * string to string map.
1701      * Otherwise, does not attempt to modify the input.
1702      *
1703      * @param {Object} input The object to escape
1704      * @param {Boolean} opt_escapeObjects Whether to escape objects.
1705      * @return {Object} The escaped object
1706      * @private Only to be used by the container, not gadgets.
1707      */
1708     escape : function(input, opt_escapeObjects) {
1709       if (!input) {
1710         return input;
1711       } else if (typeof input === "string") {
1712         return gadgets.util.escapeString(input);
1713       } else if (typeof input === "array") {
1714         for (var i = 0, j = input.length; i < j; ++i) {
1715           input[i] = gadgets.util.escape(input[i]);
1716         }
1717       } else if (typeof input === "object" && opt_escapeObjects) {
1718         var newObject = {};
1719         for (var field in input) if (input.hasOwnProperty(field)) {
1720           newObject[gadgets.util.escapeString(field)]
1721               = gadgets.util.escape(input[field], true);
1722         }
1723         return newObject;
1724       }
1725       return input;
1726     },
1727 
1728     /**
1729      * Escapes the input using html entities to make it safer.
1730      *
1731      * Currently not in the spec -- future proposals may change
1732      * how this is handled.
1733      *
1734      * TODO: Parsing the string would probably be more accurate and faster than
1735      * a bunch of regular expressions.
1736      *
1737      * @param {String} str The string to escape
1738      * @return {String} The escaped string
1739      */
1740     escapeString : function(str) {
1741       var out = [], ch, shouldEscape;
1742       for (var i = 0, j = str.length; i < j; ++i) {
1743         ch = str.charCodeAt(i);
1744         shouldEscape = escapeCodePoints[ch];
1745         if (shouldEscape === true) {
1746           out.push("&#", ch, ";");
1747         } else if (shouldEscape !== false) {
1748           // undefined or null are OK.
1749           out.push(str.charAt(i));
1750         }
1751       }
1752       return out.join("");
1753     },
1754 
1755     /**
1756      * Reverses escapeString
1757      *
1758      * @param {String} str The string to unescape.
1759      */
1760     unescapeString : function(str) {
1761       return str.replace(/&#([0-9]+);/g, unescapeEntity);
1762     },
1763     
1764     sanitizeHTML : function(text) {
1765         //---------------------------------------------------------------
1766         // Whitelists of HTML elements and attributes.
1767         //---------------------------------------------------------------
1768 
1769         /** @namespace */
1770         var html4 = {};
1771 
1772         /**
1773          * HTML element flags.
1774          * @enum {number}
1775          */
1776         html4.eflags = {
1777           OPTIONAL_ENDTAG: 1,
1778           BREAKS_FLOW: 2,
1779           EMPTY: 4,
1780           NAVIGATES: 8,
1781           CDATA: 0x10,
1782           RCDATA: 0x20,
1783           UNSAFE: 0x40
1784         };
1785 
1786         /**
1787          * HTML attribute flags.
1788          * @enum {number}
1789          */
1790         html4.atype = {
1791           SCRIPT: 1,
1792           STYLE: 2,
1793           IDREF: 3,
1794           NAME: 4,
1795           NMTOKENS: 5,
1796           URI: 6,
1797           FRAME: 7
1798         };
1799 
1800         /**
1801          * Maps HTML4 element names to flag bitsets.
1802          * Since this is a whitelist, be sure to do
1803          * {@code html4.ELEMENTS.hasOwnProperty} to determine whether or not an element
1804          * is allowed.
1805          */
1806         html4.ELEMENTS = {
1807           'a'          : html4.eflags.NAVIGATES,
1808           'abbr'       : 0,
1809           'acronym'    : 0,
1810           'address'    : 0,
1811           'applet'     : html4.eflags.UNSAFE,
1812           'area'       : html4.eflags.EMPTY | html4.eflags.NAVIGATES,
1813           'b'          : 0,
1814           // Changes the meaning of URIs
1815           'base'       : html4.eflags.UNSAFE | html4.eflags.EMPTY,
1816           // Affects global styles.
1817           'basefont'   : html4.eflags.UNSAFE | html4.eflags.EMPTY,
1818           'bdo'        : 0,
1819           'big'        : 0,
1820           'blockquote' : html4.eflags.BREAKS_FLOW,
1821           // Attributes merged into global body.
1822           'body'       : html4.eflags.UNSAFE | html4.eflags.OPTIONAL_ENDTAG,
1823           'br'         : html4.eflags.EMPTY | html4.eflags.BREAKS_FLOW,
1824           'button'     : 0,
1825           'caption'    : 0,
1826           'center'     : html4.eflags.BREAKS_FLOW,
1827           'cite'       : 0,
1828           'code'       : 0,
1829           'col'        : html4.eflags.EMPTY,
1830           'colgroup'   : html4.eflags.OPTIONAL_ENDTAG,
1831           'dd'         : html4.eflags.OPTIONAL_ENDTAG | html4.eflags.BREAKS_FLOW,
1832           'del'        : 0,
1833           'dfn'        : 0,
1834           'dir'        : html4.eflags.BREAKS_FLOW,
1835           'div'        : html4.eflags.BREAKS_FLOW,
1836           'dl'         : html4.eflags.BREAKS_FLOW,
1837           'dt'         : html4.eflags.OPTIONAL_ENDTAG | html4.eflags.BREAKS_FLOW,
1838           'em'         : 0,
1839           'fieldset'   : 0,
1840           'font'       : 0,
1841           'form'       : html4.eflags.BREAKS_FLOW | html4.eflags.NAVIGATES,
1842           'frame'      : html4.eflags.UNSAFE | html4.eflags.EMPTY,
1843           // Attributes merged into global frameset.
1844           'frameset'   : html4.eflags.UNSAFE,
1845           'h1'         : html4.eflags.BREAKS_FLOW,
1846           'h2'         : html4.eflags.BREAKS_FLOW,
1847           'h3'         : html4.eflags.BREAKS_FLOW,
1848           'h4'         : html4.eflags.BREAKS_FLOW,
1849           'h5'         : html4.eflags.BREAKS_FLOW,
1850           'h6'         : html4.eflags.BREAKS_FLOW,
1851           'head'       : (html4.eflags.UNSAFE | html4.eflags.OPTIONAL_ENDTAG
1852                         | html4.eflags.BREAKS_FLOW),
1853           'hr'         : html4.eflags.EMPTY | html4.eflags.BREAKS_FLOW,
1854           'html'       : (html4.eflags.UNSAFE | html4.eflags.OPTIONAL_ENDTAG
1855                         | html4.eflags.BREAKS_FLOW),
1856           'i'          : 0,
1857           'iframe'     : html4.eflags.UNSAFE,
1858           'img'        : html4.eflags.EMPTY,
1859           'input'      : html4.eflags.EMPTY,
1860           'ins'        : 0,
1861           'isindex'    : (html4.eflags.UNSAFE | html4.eflags.EMPTY
1862                         | html4.eflags.BREAKS_FLOW | html4.eflags.NAVIGATES),
1863           'kbd'        : 0,
1864           'label'      : 0,
1865           'legend'     : 0,
1866           'li'         : html4.eflags.OPTIONAL_ENDTAG | html4.eflags.BREAKS_FLOW,
1867           // Can load global styles.
1868           'link'       : html4.eflags.UNSAFE | html4.eflags.EMPTY,
1869           'map'        : 0,
1870           'menu'       : html4.eflags.BREAKS_FLOW,
1871           // Can override document headers and encoding, or cause navigation.
1872           'meta'       : html4.eflags.UNSAFE | html4.eflags.EMPTY,
1873           // Ambiguous tokenization.  Content is CDATA/PCDATA depending on browser.
1874           'noframes'   : html4.eflags.UNSAFE | html4.eflags.BREAKS_FLOW,
1875           // Ambiguous tokenization.  Content is CDATA/PCDATA depending on browser.
1876           'noscript'   : html4.eflags.UNSAFE,
1877           'object'     : html4.eflags.UNSAFE,
1878           'ol'         : html4.eflags.BREAKS_FLOW,
1879           'optgroup'   : 0,
1880           'option'     : html4.eflags.OPTIONAL_ENDTAG,
1881           'p'          : html4.eflags.OPTIONAL_ENDTAG | html4.eflags.BREAKS_FLOW,
1882           'param'      : html4.eflags.UNSAFE | html4.eflags.EMPTY,
1883           'plaintext'  : (html4.eflags.OPTIONAL_ENDTAG | html4.eflags.UNSAFE
1884                         | html4.eflags.CDATA),
1885           'pre'        : html4.eflags.BREAKS_FLOW,
1886           'q'          : 0,
1887           's'          : 0,
1888           'samp'       : 0,
1889           'script'     : html4.eflags.UNSAFE | html4.eflags.CDATA,
1890           'select'     : 0,
1891           'small'      : 0,
1892           'span'       : 0,
1893           'strike'     : 0,
1894           'strong'     : 0,
1895           'style'      : html4.eflags.UNSAFE | html4.eflags.CDATA,
1896           'sub'        : 0,
1897           'sup'        : 0,
1898           'table'      : html4.eflags.BREAKS_FLOW,
1899           'tbody'      : html4.eflags.OPTIONAL_ENDTAG,
1900           'td'         : html4.eflags.OPTIONAL_ENDTAG | html4.eflags.BREAKS_FLOW,
1901           'textarea'   : html4.eflags.RCDATA,
1902           'tfoot'      : html4.eflags.OPTIONAL_ENDTAG,
1903           'th'         : html4.eflags.OPTIONAL_ENDTAG | html4.eflags.BREAKS_FLOW,
1904           'thead'      : html4.eflags.OPTIONAL_ENDTAG,
1905           'title'      : (html4.eflags.UNSAFE | html4.eflags.BREAKS_FLOW
1906                         | html4.eflags.RCDATA),
1907           'tr'         : html4.eflags.OPTIONAL_ENDTAG | html4.eflags.BREAKS_FLOW,
1908           'tt'         : 0,
1909           'u'          : 0,
1910           'ul'         : html4.eflags.BREAKS_FLOW,
1911           'var'        : 0,
1912           'xmp'        : html4.eflags.CDATA
1913         };
1914 
1915         /**
1916          * Maps HTML4 attribute names to flag bitsets.
1917          */
1918         html4.ATTRIBS = {
1919           'abbr'          : 0,
1920           'accept'        : 0,
1921           'accept-charset': 0,
1922           'accesskey'     : 0,
1923           'action'        : html4.atype.URI,
1924           'align'         : 0,
1925           'alink'         : 0,
1926           'alt'           : 0,
1927           'archive'       : html4.atype.URI,
1928           'axis'          : 0,
1929           'background'    : html4.atype.URI,
1930           'bgcolor'       : 0,
1931           'border'        : 0,
1932           'cellpadding'   : 0,
1933           'cellspacing'   : 0,
1934           'char'          : 0,
1935           'charoff'       : 0,
1936           'charset'       : 0,
1937           'checked'       : 0,
1938           'cite'          : html4.atype.URI,
1939           'class'         : html4.atype.NMTOKENS,
1940           'classid'       : html4.atype.URI,
1941           'clear'         : 0,
1942           'code'          : 0,
1943           'codebase'      : html4.atype.URI,
1944           'codetype'      : 0,
1945           'color'         : 0,
1946           'cols'          : 0,
1947           'colspan'       : 0,
1948           'compact'       : 0,
1949           'content'       : 0,
1950           'coords'        : 0,
1951           'data'          : html4.atype.URI,
1952           'datetime'      : 0,
1953           'declare'       : 0,
1954           'defer'         : 0,
1955           'dir'           : 0,
1956           'disabled'      : 0,
1957           'enctype'       : 0,
1958           'face'          : 0,
1959           'for'           : html4.atype.IDREF,
1960           'frame'         : 0,
1961           'frameborder'   : 0,
1962           'headers'       : 0,
1963           'height'        : 0,
1964           'href'          : html4.atype.URI,
1965           'hreflang'      : 0,
1966           'hspace'        : 0,
1967           //'http-equiv'  : 0,   // unsafe
1968           'id'            : html4.atype.IDREF,
1969           'ismap'         : 0,
1970           'label'         : 0,
1971           'lang'          : 0,
1972           'language'      : 0,
1973           'link'          : 0,
1974           'longdesc'      : html4.atype.URI,
1975           'marginheight'  : 0,
1976           'marginwidth'   : 0,
1977           'maxlength'     : 0,
1978           'media'         : 0,
1979           'method'        : 0,
1980           'multiple'      : 0,
1981           'name'          : html4.atype.NAME,
1982           'nohref'        : 0,
1983           'noresize'      : 0,
1984           'noshade'       : 0,
1985           'nowrap'        : 0,
1986           'object'        : 0,
1987           'onblur'        : html4.atype.SCRIPT,
1988           'onchange'      : html4.atype.SCRIPT,
1989           'onclick'       : html4.atype.SCRIPT,
1990           'ondblclick'    : html4.atype.SCRIPT,
1991           'onfocus'       : html4.atype.SCRIPT,
1992           'onkeydown'     : html4.atype.SCRIPT,
1993           'onkeypress'    : html4.atype.SCRIPT,
1994           'onkeyup'       : html4.atype.SCRIPT,
1995           'onload'        : html4.atype.SCRIPT,
1996           'onmousedown'   : html4.atype.SCRIPT,
1997           'onmousemove'   : html4.atype.SCRIPT,
1998           'onmouseout'    : html4.atype.SCRIPT,
1999           'onmouseover'   : html4.atype.SCRIPT,
2000           'onmouseup'     : html4.atype.SCRIPT,
2001           'onreset'       : html4.atype.SCRIPT,
2002           'onselect'      : html4.atype.SCRIPT,
2003           'onsubmit'      : html4.atype.SCRIPT,
2004           'onunload'      : html4.atype.SCRIPT,
2005           'profile'       : html4.atype.URI,
2006           'prompt'        : 0,
2007           'readonly'      : 0,
2008           'rel'           : 0,
2009           'rev'           : 0,
2010           'rows'          : 0,
2011           'rowspan'       : 0,
2012           'rules'         : 0,
2013           'scheme'        : 0,
2014           'scope'         : 0,
2015           'scrolling'     : 0,
2016           'selected'      : 0,
2017           'shape'         : 0,
2018           'size'          : 0,
2019           'span'          : 0,
2020           'src'           : html4.atype.URI,
2021           'standby'       : 0,
2022           'start'         : 0,
2023           'style'         : html4.atype.STYLE,
2024           'summary'       : 0,
2025           'tabindex'      : 0,
2026           'target'        : html4.atype.FRAME,
2027           'text'          : 0,
2028           'title'         : 0,
2029           'type'          : 0,
2030           'usemap'        : html4.atype.URI,
2031           'valign'        : 0,
2032           'value'         : 0,
2033           'valuetype'     : 0,
2034           'version'       : 0,
2035           'vlink'         : 0,
2036           'vspace'        : 0,
2037           'width'         : 0
2038         };
2039 
2040 
2041         //-----------------------------------------------------------
2042         // Provides a factory that allows transformations on HTML.
2043         //-----------------------------------------------------------
2044 
2045         /** @namespace */
2046         var html = (function () {
2047 
2048           var ENTITIES = {
2049             LT   : '<',
2050             GT   : '>',
2051             AMP  : '&',
2052             NBSP : '\240',
2053             QUOT : '"',
2054             APOS : '\''
2055           };
2056 
2057           var decimalEscapeRe = /^#(\d)$/;
2058           var hexEscapeRe = /^#x([0-9A-F])$/;
2059           function lookupEntity(name) {
2060             name = name.toUpperCase();  // TODO: π is different from Π
2061             if (ENTITIES.hasOwnProperty(name)) { return ENTITIES[name]; }
2062             var m = name.match(decimalEscapeRe);
2063             if (m) {
2064               return String.fromCharCode(parseInt(m[1], 10));
2065             } else if (!!(m = name.match(hexEscapeRe))) {
2066               return String.fromCharCode(parseInt(m[1], 16));
2067             }
2068             return '';
2069           }
2070 
2071           function decodeOneEntity(_, name) {
2072             return lookupEntity(name);
2073           }
2074 
2075           var entityRe = /&(#\d+|#x[\da-f]+|\w+);/g;
2076           function unescapeEntities(s) {
2077             return s.replace(entityRe, decodeOneEntity);
2078           }
2079 
2080           var ampRe = /&/g;
2081           var looseAmpRe = /&([^a-z#]|#(?:[^0-9x]|x(?:[^0-9a-f]|$)|$)|$)/gi;
2082           var ltRe = /</g;
2083           var gtRe = />/g;
2084           var quotRe = /\"/g;
2085 
2086           function escapeAttrib(s) {
2087             return s.replace(ampRe, '&').replace(ltRe, '<').replace(gtRe, '>')
2088                 .replace(quotRe, '"');
2089           }
2090 
2091           /**
2092            * Escape entities in RCDATA that can be escaped without changing the meaning.
2093            */
2094           function normalizeRCData(rcdata) {
2095             return rcdata
2096                 .replace(looseAmpRe, '&$1')
2097                 .replace(ltRe, '<')
2098                 .replace(gtRe, '>');
2099           }
2100 
2101 
2102           /** token definitions. */
2103           var INSIDE_TAG_TOKEN = new RegExp(
2104               // Don't capture space.
2105               '^\\s*(?:'
2106               // Capture an attribute name in group 1, and value in groups 2-4.
2107               + ('(?:'
2108                  + '([a-z][a-z-]*)'
2109                  + ('(?:'
2110                     + '\\s*=\\s*'
2111                     + ('(?:'
2112                        + '\"([^\"]*)\"'
2113                        + '|\'([^\']*)\''
2114                        + '|([^>\"\'\\s]*)'
2115                        + ')'
2116                        )
2117                     + ')'
2118                     ) + '?'
2119                  + ')'
2120                  )
2121               // End of tag captured in group 5.
2122               + '|(/?>)'
2123               // Don't capture cruft
2124               + '|[^\\w\\s>]+)',
2125               'i');
2126 
2127           var OUTSIDE_TAG_TOKEN = new RegExp(
2128               '^(?:'
2129               // Entity captured in group 1.
2130               + '&(\\#[0-9]+|\\#[x][0-9a-f]+|\\w+);'
2131               // Comment, doctypes, and processing instructions not captured.
2132               + '|<!--[\\s\\S]*?-->|<!\w[^>]*>|<\\?[^>*]*>'
2133               // '/' captured in group 2 for close tags, and name captured in group 3.
2134               + '|<(/)?([a-z][a-z0-9]*)'
2135               // Text captured in group 4.
2136               + '|([^<&]+)'
2137               // Cruft captured in group 5.
2138               + '|([<&]))',
2139               'i');
2140 
2141           /**
2142            * Given a SAX-like event handler, produce a function that feeds those
2143            * events and a parameter to the event handler.
2144            *
2145            * The event handler has the form:<pre>
2146            * {
2147            *   // Name is an upper-case HTML tag name.  Attribs is an array of
2148            *   // alternating upper-case attribute names, and attribute values.  The
2149            *   // attribs array is reused by the parser.  Param is the value passed to
2150            *   // the saxParser.
2151            *   startTag: function (name, attribs, param) { ... },
2152            *   endTag:   function (name, param) { ... },
2153            *   pcdata:   function (text, param) { ... },
2154            *   rcdata:   function (text, param) { ... },
2155            *   cdata:    function (text, param) { ... },
2156            *   startDoc: function (param) { ... },
2157            *   endDod:   function (param) { ... },
2158            * }</pre>
2159            *
2160            * @param {Object} event handler.
2161            * @return {Function} that takes a chunk of html and a parameter.
2162            *   The parameter is passed on to the handler methods.
2163            */
2164           function makeSaxParser(handler) {
2165             return function parse(htmlText, param) {
2166               htmlText = String(htmlText);
2167               var htmlUpper = null;
2168 
2169               var inTag = false;  // True iff we're currently processing a tag.
2170               var attribs = [];  // Accumulates attribute names and values.
2171               var tagName;  // The name of the tag currently being processed.
2172               var eflags;  // The element flags for the current tag.
2173               var openTag;  // True if the current tag is an open tag.
2174 
2175               handler.startDoc && handler.startDoc(param);
2176 
2177               while (htmlText) {
2178                 var m = htmlText.match(inTag ? INSIDE_TAG_TOKEN : OUTSIDE_TAG_TOKEN);
2179                 htmlText = htmlText.substring(m[0].length);
2180 
2181                 if (inTag) {
2182                   if (m[1]) { // attribute
2183                     // setAttribute with uppercase names doesn't work on IE6.
2184                     var attribName = m[1].toLowerCase();
2185                     var encodedValue = m[2] || m[3] || m[4];
2186                     var decodedValue;
2187                     if (encodedValue != null) {  // Matches null & undefined
2188                       decodedValue = unescapeEntities(encodedValue);
2189                     } else {
2190                       // Use name as value for valueless attribs, so
2191                       //   <input type=checkbox checked>
2192                       // gets attributes ['type', 'checkbox', 'checked', 'checked']
2193                       decodedValue = attribName;
2194                     }
2195                     attribs.push(attribName, decodedValue);
2196                   } else if (m[5]) {
2197                     if (eflags !== undefined) {  // False if not in whitelist.
2198                       if (openTag) {
2199                         handler.startTag && handler.startTag(tagName, attribs, param);
2200                       } else {
2201                         handler.endTag && handler.endTag(tagName, param);
2202                       }
2203                     }
2204 
2205                     if (openTag
2206                         && (eflags & (html4.eflags.CDATA | html4.eflags.RCDATA))) {
2207                       if (htmlUpper === null) {
2208                         htmlUpper = htmlText.toLowerCase();
2209                       } else {
2210                         htmlUpper = htmlUpper.substring(
2211                             htmlUpper.length - htmlText.length);
2212                       }
2213                       var dataEnd = htmlUpper.indexOf('</' + tagName);
2214                       if (dataEnd < 0) { dataEnd = htmlText.length; }
2215                       if (eflags & html4.eflags.CDATA) {
2216                         handler.cdata
2217                             && handler.cdata(htmlText.substring(0, dataEnd), param);
2218                       } else if (handler.rcdata) {
2219                         handler.rcdata(
2220                             normalizeRCData(htmlText.substring(0, dataEnd)), param);
2221                       }
2222                       htmlText = htmlText.substring(dataEnd);
2223                     }
2224 
2225                     tagName = eflags = openTag = undefined;
2226                     attribs.length = 0;
2227                     inTag = false;
2228                   }
2229                 } else {
2230                   if (m[1]) {  // Entity
2231                     handler.pcdata && handler.pcdata(m[0], param);
2232                   } else if (m[3]) {  // Tag
2233                     openTag = !m[2];
2234                     inTag = true;
2235                     tagName = m[3].toLowerCase();
2236                     eflags = html4.ELEMENTS.hasOwnProperty(tagName)
2237                         ? html4.ELEMENTS[tagName] : undefined;
2238                   } else if (m[4]) {  // Text
2239                     handler.pcdata && handler.pcdata(m[4], param);
2240                   } else if (m[5]) {  // Cruft
2241                     handler.pcdata
2242                         && handler.pcdata(m[5] === '&' ? '&' : '<', param);
2243                   }
2244                 }
2245               }
2246 
2247               handler.endDoc && handler.endDoc(param);
2248             };
2249           }
2250 
2251           return {
2252             normalizeRCData: normalizeRCData,
2253             escapeAttrib: escapeAttrib,
2254             unescapeEntities: unescapeEntities,
2255             makeSaxParser: makeSaxParser
2256           };
2257         })();
2258 
2259         /**
2260          * Returns a function that strips unsafe tags and attributes from html.
2261          * @param {Function} sanitizeAttributes
2262          *     from tagName, attribs[]) to null or a sanitized attribute array.
2263          *     The attribs array can be arbitrarily modified, but the same array
2264          *     instance is reused, so should not be held.
2265          * @return {Function} from html to sanitized html
2266          */
2267         html.makeHtmlSanitizer = function (sanitizeAttributes) {
2268           var stack = [];
2269           var ignoring = false;
2270           return html.makeSaxParser({
2271                 startDoc: function (_) {
2272                   stack = [];
2273                   ignoring = false;
2274                 },
2275                 startTag: function (tagName, attribs, out) {
2276                   if (ignoring) { return; }
2277                   if (!html4.ELEMENTS.hasOwnProperty(tagName)) { return; }
2278                   var eflags = html4.ELEMENTS[tagName];
2279                   if (eflags & html4.eflags.UNSAFE) {
2280                     ignoring = !(eflags & html4.eflags.EMPTY);
2281                     return;
2282                   }
2283                   attribs = sanitizeAttributes(tagName, attribs);
2284                   if (attribs) {
2285                     if (!(eflags & html4.eflags.EMPTY)) {
2286                       stack.push(tagName);
2287                     }
2288 
2289                     out.push('<', tagName);
2290                     for (var i = 0, n = attribs.length; i < n; i += 2) {
2291                       var attribName = attribs[i],
2292                           value = attribs[i + 1];
2293                       if (value != null) {  // Skip null or undefined
2294                         out.push(' ', attribName, '="', html.escapeAttrib(value), '"');
2295                       }
2296                     }
2297                     out.push('>');
2298                   }
2299                 },
2300                 endTag: function (tagName, out) {
2301                   if (ignoring) {
2302                     ignoring = false;
2303                     return;
2304                   }
2305                   if (!html4.ELEMENTS.hasOwnProperty(tagName)) { return; }
2306                   var eflags = html4.ELEMENTS[tagName];
2307                   if (!(eflags & (html4.eflags.UNSAFE | html4.eflags.EMPTY))) {
2308                     var index;
2309                     if (eflags & html4.eflags.OPTIONAL_ENDTAG) {
2310                       for (index = stack.length; --index >= 0;) {
2311                         var stackEl = stack[index];
2312                         if (stackEl === tagName) { break; }
2313                         if (!(html4.ELEMENTS[stackEl] & html4.eflags.OPTIONAL_ENDTAG)) {
2314                           // Don't pop non optional end tags looking for a match.
2315                           return;
2316                         }
2317                       }
2318                     } else {
2319                       for (index = stack.length; --index >= 0;) {
2320                         if (stack[index] === tagName) { break; }
2321                       }
2322                     }
2323                     if (index < 0) { return; }  // Not opened.
2324                     for (var i = stack.length; --i > index;) {
2325                       var stackEl = stack[i];
2326                       if (!(html4.ELEMENTS[stackEl] & html4.eflags.OPTIONAL_ENDTAG)) {
2327                         out.push('</', stackEl, '>');
2328                       }
2329                     }
2330                     stack.length = index;
2331                     out.push('</', tagName, '>');
2332                   }
2333                 },
2334                 pcdata: function (text, out) {
2335                   if (!ignoring) { out.push(text); }
2336                 },
2337                 rcdata: function (text, out) {
2338                   if (!ignoring) { out.push(text); }
2339                 },
2340                 cdata: function (text, out) {
2341                   if (!ignoring) { out.push(text); }
2342                 },
2343                 endDoc: function (out) {
2344                   for (var i = stack.length; --i >= 0;) {
2345                     out.push('</', stack[i], '>');
2346                   }
2347                   stack.length = 0;
2348                 }
2349               });
2350         };
2351 
2352         /**
2353          * Strips unsafe tags and attributes from html.
2354          * @param {string} html to sanitize
2355          * @return {string} html
2356          */
2357         function html_sanitize(htmlText) {
2358           var out = [];
2359           html.makeHtmlSanitizer(
2360               function sanitizeAttribs(tagName, attribs) {
2361                 for (var i = 0; i < attribs.length; i += 2) {
2362                   var attribName = attribs[i];
2363                   var value = attribs[i + 1];
2364                   if (html4.ATTRIBS.hasOwnProperty(attribName)) {
2365                     switch (html4.ATTRIBS[attribName]) {
2366                       case html4.atype.SCRIPT:
2367                       case html4.atype.STYLE:
2368                         value = null;
2369                         break;
2370                     }
2371                   } else {
2372                     value = null;
2373                   }
2374                   attribs[i + 1] = value;
2375                 }
2376                 return attribs;
2377               })(htmlText, out);
2378           return out.join('');
2379         }
2380 
2381         return html_sanitize(text);
2382     }
2383   };
2384 }();
2385 
2386 // Initialize url parameters so that hash data is pulled in before it can be
2387 // altered by a click.
2388 gadgets.util.getUrlParameters();
2389 
2390 
2391 
2392 /*
2393 * Licensed to the Apache Software Foundation (ASF) under one
2394 * or more contributor license agreements. See the NOTICE file
2395 * distributed with this work for additional information
2396 * regarding copyright ownership. The ASF licenses this file
2397 * to you under the Apache License, Version 2.0 (the
2398 * "License"); you may not use this file except in compliance
2399 * with the License. You may obtain a copy of the License at
2400 *
2401 * http://www.apache.org/licenses/LICENSE-2.0
2402 *
2403 * Unless required by applicable law or agreed to in writing,
2404 * software distributed under the License is distributed on an
2405 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
2406 * KIND, either express or implied. See the License for the
2407 * specific language governing permissions and limitations under the License.
2408 */
2409 
2410 /**
2411 * @fileoverview This library provides functions for navigating to and dealing
2412 *     with views of the current gadget.
2413 */
2414 
2415 var gadgets = gadgets || {};
2416 
2417 /**
2418 * Implements the gadgets.views API spec. See
2419 * http://code.google.com/apis/gadgets/docs/reference/gadgets.views.html
2420 */
2421 gadgets.views = function() {
2422 
2423     /**
2424     * Reference to the current view object.
2425     */
2426     var currentView = null;
2427 
2428     /**
2429     * Map of all supported views for this container.
2430     */
2431     var supportedViews = {};
2432 
2433     /**
2434     * Map of parameters passed to the current request.
2435     */
2436     var params = {};
2437 
2438     /**
2439     * Initializes views. Assumes that the current view is the "view"
2440     * url parameter (or default if "view" isn't supported), and that
2441     * all view parameters are in the form view-<name>
2442     * TODO: Use unified configuration when it becomes available.
2443     *
2444     */
2445     function init(config) {
2446         var supported = config["views"];
2447 
2448         for (var s in supported) if (supported.hasOwnProperty(s)) {
2449             var obj = supported[s];
2450             if (!obj) {
2451                 continue;
2452             }
2453             supportedViews[s] = new gadgets.views.View(s, obj.isOnlyVisible);
2454             var aliases = obj.aliases || [];
2455             for (var i = 0, alias; alias = aliases[i]; ++i) {
2456                 supportedViews[alias] = new gadgets.views.View(s, obj.isOnlyVisible);
2457             }
2458         }
2459 
2460         var urlParams = gadgets.util.getUrlParameters();
2461         // View parameters are passed as a single parameter.
2462         if (urlParams["p"]) {
2463             var tmpParams = gadgets.json.parse(
2464           decodeURIComponent(urlParams["p"]));
2465             if (tmpParams) {
2466                 params = tmpParams;
2467                 for (var p in params) if (params.hasOwnProperty(p)) {
2468                     params[p] = gadgets.util.escapeString(params[p]);
2469                 }
2470             }
2471         }
2472         if (0 === urlParams.views.indexOf("profile.")) urlParams.views = gadgets.views.ViewType.PROFILE;
2473         currentView = supportedViews[urlParams.views] || supportedViews["default"];
2474     }
2475 
2476     gadgets.config.register("views", null, init);
2477 
2478     return {
2479         /**
2480         * Attempts to navigate to this gadget in a different view. If the container
2481         * supports parameters will pass the optional parameters along to the gadget
2482         * in the new view.
2483         *
2484         * @param {gadgets.views.View} view The view to navigate to
2485         * @param {Map.<String, String>} opt_params Parameters to pass to the
2486         *     gadget after it has been navigated to on the surface
2487         * @param {string} opt_ownerId The ID of the owner of the page to navigate to;
2488         *                 defaults to the current owner.
2489         */
2490         requestNavigateTo: function(view, opt_params, opt_ownerId) {
2491             gadgets.rpc.call(
2492           null, "requestNavigateTo", null, view.getName(), opt_params, opt_ownerId);
2493         },
2494         
2495         /**
2496         * Binds a URL template with variables in the passed environment to produce a URL string.
2497         * @param {String} urlTemplate A url template for a container view
2498         * @param {String} environment A set of named variables (for example, [OWNER | PATH | PARAMS | NAME]) of type string.
2499         *
2500         * @return {gadgets.views.View} The current view
2501         */
2502         bind: function(urlTemplate, environment){},
2503 
2504         /**
2505         * Returns the current view.
2506         *
2507         * @return {gadgets.views.View} The current view
2508         */
2509         getCurrentView: function() {
2510             return currentView;
2511         },
2512 
2513         /**
2514         * Returns a map of all the supported views. Keys each gadgets.view.View by
2515         * its name.
2516         *
2517         * @return {Map<gadgets.views.ViewType | String, gadgets.views.View>}
2518         *   All supported views, keyed by their name attribute.
2519         */
2520         getSupportedViews: function() {
2521             return supportedViews;
2522         },
2523 
2524         /**
2525         * Returns the parameters passed into this gadget for this view. Does not
2526         * include all url parameters, only the ones passed into
2527         * gadgets.views.requestNavigateTo
2528         *
2529         * @return {Map.<String, String>} The parameter map
2530         */
2531         getParams: function() {
2532             return params;
2533         }
2534     };
2535 } ();
2536 
2537 gadgets.views.View = function(name, opt_isOnlyVisible) {
2538     this.name_ = name;
2539     this.isOnlyVisible_ = !!opt_isOnlyVisible;
2540 };
2541 
2542 /**
2543 * @return {String} The view name.
2544 */
2545 gadgets.views.View.prototype.getName = function() {
2546     return this.name_;
2547 };
2548 
2549 /**
2550 * @return {Boolean} True if this is the only visible gadget on the page.
2551 */
2552 gadgets.views.View.prototype.isOnlyVisibleGadget = function() {
2553     return this.isOnlyVisible_;
2554 };
2555 
2556 gadgets.views.View.prototype.bind = function(environment) {};
2557 
2558 gadgets.views.View.prototype.getUrlTemplate = function() {};
2559 
2560 gadgets.views.ViewType = gadgets.util.makeEnum([
2561   "CANVAS", "HOME", "PREVIEW", "PROFILE"
2562 ]);
2563 
2564 gadgets.views.requestNavigateTo = function(view, opt_params, opt_ownerId) {
2565     if (opt_ownerId) {
2566         return { "errorCode": opensocial.ResponseItem.Error.NOT_IMPLEMENTED, "errorMessage": "opt_ownerId in gadgets.views.requestNavigateTo is not supported." };
2567     }
2568 
2569     if (view && view.name_) {
2570         var view_name = view.getName();
2571         if (0 === view_name.indexOf("profile.")) view_name = gadgets.views.ViewType.PROFILE;
2572         var p = opensocial.Container.get().params_;
2573         _IFPC.call(
2574             p.panelId,
2575             "requestNavigateTo",
2576             [p.appid, p.ownerid, view_name.toLowerCase(), opt_params],
2577             p.remoteRelay,
2578             null,
2579             p.localRelay,
2580             null);
2581     }
2582 };
2583