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