Skip to content

Instantly share code, notes, and snippets.

@reid
Created October 12, 2011 22:47
Show Gist options
  • Save reid/1282872 to your computer and use it in GitHub Desktop.
Save reid/1282872 to your computer and use it in GitHub Desktop.
Source of https://www.me.com/move while logged in.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- IMPORTANT NOTE: This file is licensed only for use in providing the MobileMe service,
or any part thereof, and is subject to the MobileMe Terms and Conditions. You may not
port this file to another platform without Apple's written consent. -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name = "viewport" content="initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
<title>Move</title>
<link href="/my/move/en-us/1F17/stylesheet-packed.css" rel="stylesheet" type="text/css" />
<style>
.loading-spinner {
position: absolute;
top: 50%;
left: 0;
right: 0;
text-align: center;
}
.img-spinner{
width: 42px;
height: 42px;
display: inline-block;
background-image: url(data:image/gif;base64,R0lGODlhKgAqAPfZAImLjoiKjf///7i5u+3t7dvc3KaoqpWXmcnKzMnKy2Rna1daXoeJjJSWmVVYXFlcYFNWWmVoa6WnqYaIi1teYtzc3V5hZW1wc1xfY1RXW3V3eoGDhoGEh1ZZXW5xdFhbX4aIioiKjISGiV5gZGhrbmRmanR2eoKEh25wdF9iZWBiZl1gY3Z4fGFkZ3t+gYWHinFzdlpdYW9ydWxucoyOkVteYX+BhLS2t9jY2X1/gnx+gZKUloCChZKUl3t9gGdqbYOFiGBjZ6Olp3+ChX2Ag2tucXN2eXl7fmpscHV4e2xvcmhqbmZobHl8f6SmqMXGx+fo6H6Ag5GTltbX2Hp8f2lsb2NmaXd6fa2usGZpbejo6ZqcnrW2uKGjpaqsrpucn7m6vJCSlIqMj3F0d6qrrYuNkJSWmMfIyaGipbCys6ytr+Xm5qmqrLe4upOVl9TV1qCipMPExvv7+5CSlY2PkmJlabO0tvn5+Z+ho4qMjvj4+KKkptfY2Z2focbHyOnq6qOkptHS08TFxp2eocDBwnh6fZudn66vsY6QkmJkaOzs7bq7vZmbnb2+v7y9v+3u7oyOkJyeoMvMzqusrri6u56govb297/AweHi4vf393J0eN/g4MHCw5ianJmancLDxNXW16+wsra4uWptcLCxs9LS05eYm3N1eObm54+RlNPU1erq68fIyvLy87K0tZeZm7KztZaYmnd5fNra28zNzunp6p+go8PExbq8vc3Oz9na28HCxKyusN7e372+wPDw8bS1t76/wePk5M/Q0fr6+srLzNDQ0fT09dLT1I6Qk7GytHBydsvMzeLi46iqrOXl5qWmqMXGyMjJyuDg4aeoqru8vvPz86epq9zd3lJVWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wUmlnaHRzPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvcmlnaHRzLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcFJpZ2h0czpNYXJrZWQ9IkZhbHNlIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InV1aWQ6QTI3MzY2MDI3NDJDMTFEQjlCNjc4QjY2MjY3QzRGMjciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUZBMzM0QTc2MkFEMTFFMDlGMEM5QTdBMTBBMkU1OUMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUZBMzM0QTY2MkFEMTFFMDlGMEM5QTdBMTBBMkU1OUMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpBODk0NDA5OTZEMjA2ODExQUIwOERDODA4RUNCODU1MSIgc3RSZWY6ZG9jdW1lbnRJRD0idXVpZDpBMjczNjYwMjc0MkMxMURCOUI2NzhCNjYyNjdDNEYyNyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHA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);
}
</style>
<!--[if IE 7]>
<style>
.img-spinner{
background-image: url();
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=90)";
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=90);
}
</style>
<![endif]-->
</head>
<body class="migration empty-theme focus">
<style>
body {
background: #111114;
}
</style>
<div id="loading">
<!-- Load a bare-bones layout to show something to the user until the app
is ready to be shown. -->
<div class="loading-spinner">
<div class="img-spinner"></div>
</div>
</div>
<script type="text/javascript">window.SC = window.SC || { MODULE_INFO: {}, LAZY_INSTANTIATION: {} }; SC.buildMode = "production";</script>
<script type="text/javascript">String.preferredLanguage = "en-us";</script>
<script type="text/javascript">
/* >>>>>>>>>> BEGIN source/core.js */
// ==========================================================================
// Project: SproutCore - JavaScript Application Framework
// Copyright: ©2006-2011 Strobe Inc. and contributors.
// Portions ©2008-2011 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
/* >>>>>>>>>> BEGIN source/system/browser.js */
// ==========================================================================
// Project: SproutCore - JavaScript Application Framework
// Copyright: ©2006-2011 Strobe Inc. and contributors.
// Portions ©2008-2011 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
window.SC = window.SC || { MODULE_INFO: {}, LAZY_INSTANTIATION: {} };
SC._detectBrowser = function(userAgent, language) {
var version, webkitVersion, browser = {};
userAgent = (userAgent || navigator.userAgent).toLowerCase();
language = language || navigator.language || navigator.browserLanguage;
// Gibberish at the end is to determine when the browser version is done
version = browser.version = (userAgent.match( /.*(?:rv|chrome|webkit|opera|ie)[\/: ](.+?)([ \);]|$)/ ) || [])[1];
webkitVersion = (userAgent.match( /webkit\/(.+?) / ) || [])[1];
/**
@name SC.browser.isWindows
@type Boolean
*/
browser.windows = browser.isWindows = !!/windows/.test(userAgent);
/**
@name SC.browser.isMac
@type Boolean
*/
browser.mac = browser.isMac = !!/macintosh/.test(userAgent) || (/mac os x/.test(userAgent) && !/like mac os x/.test(userAgent));
/**
@name SC.browser.isLion
@type Boolean
*/
browser.lion = browser.isLion = !!(/mac os x 10_7/.test(userAgent) && !/like mac os x 10_7/.test(userAgent));
/**
@name SC.browser.isiPhone
@type Boolean
*/
browser.iPhone = browser.isiPhone = !!/iphone/.test(userAgent);
/**
@name SC.browser.isiPod
@type Boolean
*/
browser.iPod = browser.isiPod = !!/ipod/.test(userAgent);
/**
@name SC.browser.isiPad
@type Boolean
*/
browser.iPad = browser.isiPad = !!/ipad/.test(userAgent);
/**
@name SC.browser.isiOS
@type Boolean
*/
browser.iOS = browser.isiOS = browser.iPhone || browser.iPod || browser.iPad;
/**
@name SC.browser.isAndroid
@type Boolean
*/
browser.android = browser.isAndroid = !!/android/.test(userAgent);
/**
@name SC.browser.opera
@type String
*/
browser.opera = /opera/.test(userAgent) ? version : 0;
/**
@name SC.browser.isOpera
@type Boolean
*/
browser.isOpera = !!browser.opera;
/**
@name SC.browser.msie
@type String
*/
browser.msie = /msie/.test(userAgent) && !browser.opera ? version : 0;
/**
@name SC.browser.isIE
@type Boolean
*/
browser.isIE = !!browser.msie;
/**
@name SC.browser.isIE8OrLower
@type Boolean
*/
browser.isIE8OrLower = !!(browser.msie && parseInt(browser.msie, 10) <= 8);
/**
@name SC.browser.mozilla
@type String
*/
browser.mozilla = /mozilla/.test(userAgent) && !/(compatible|webkit|msie)/.test(userAgent) ? version : 0;
/**
@name SC.browser.isMozilla
@type Boolean
*/
browser.isMozilla = !!browser.mozilla;
/**
@name SC.browser.webkit
@type String
*/
browser.webkit = /webkit/.test(userAgent) ? webkitVersion : 0;
/**
@name SC.browser.isWebkit
@type Boolean
*/
browser.isWebkit = !!browser.webkit;
/**
@name SC.browser.chrome
@type String
*/
browser.chrome = /chrome/.test(userAgent) ? version: 0;
/**
@name SC.browser.isChrome
@type Boolean
*/
browser.isChrome = !!browser.chrome;
/**
@name SC.browser.mobileSafari
@type String
*/
browser.mobileSafari = /apple.*mobile/.test(userAgent) && browser.iOS ? webkitVersion : 0;
/**
@name SC.browser.isMobileSafari
@type Boolean
*/
browser.isMobileSafari = !!browser.mobileSafari;
/**
@name SC.browser.iPadSafari
@type String
*/
browser.iPadSafari = browser.iPad && browser.isMobileSafari ? webkitVersion : 0;
/**
@name SC.browser.isiPadSafari
@type Boolean
*/
browser.isiPadSafari = !!browser.iPadSafari;
/**
@name SC.browser.iPhoneSafari
@type String
*/
browser.iPhoneSafari = browser.iPhone && browser.isMobileSafari ? webkitVersion : 0;
/**
@name SC.browser.isiPhoneSafari
@type Boolean
*/
browser.isiPhoneSafari = !!browser.iphoneSafari;
/**
@name SC.browser.iPodSafari
@type String
*/
browser.iPodSafari = browser.iPod && browser.isMobileSafari ? webkitVersion : 0;
/**
@name SC.browser.isiPodSafari
@type Boolean
*/
browser.isiPodSafari = !!browser.iPodSafari;
/**
@name SC.browser.isiOSHomeScreen
@type Boolean
*/
browser.isiOSHomeScreen = browser.isMobileSafari && !/apple.*mobile.*safari/.test(userAgent);
/**
@name SC.browser.safari
@type String
*/
browser.safari = browser.webkit && !browser.chrome && !browser.iOS && !browser.android ? webkitVersion : 0;
/**
@name SC.browser.isSafari
@type Boolean
*/
browser.isSafari = !!browser.safari;
/**
@name SC.browser.language
@type String
*/
browser.language = language.split('-', 1)[0];
/**
Possible values:
- 'msie'
- 'mozilla'
- 'chrome'
- 'safari'
- 'opera'
- 'mobile-safari'
- 'unknown'
@name SC.browser.current
@type String
@default 'unknown'
*/
browser.current = browser.msie ? 'msie' : browser.mozilla ? 'mozilla' : browser.chrome ? 'chrome' : browser.safari ? 'safari' : browser.opera ? 'opera' : browser.mobileSafari ? 'mobile-safari' : browser.android ? 'android' : 'unknown';
return browser;
};
/** @class
Contains information about the browser environment that SproutCore
is running in. String properties, such as `SC.browser.webkit` or
`SC.browser.msie`, will have a value that represents the browser build
number if that browser is being used. Otherwise, they will have a
falsey value. For convenience, Boolean counterparts for all of the
versioned properties are provided.
@since SproutCore 1.0
*/
SC.browser = SC._detectBrowser();
/* >>>>>>>>>> BEGIN source/system/bench.js */
// ==========================================================================
// Project: SproutCore - JavaScript Application Framework
// Copyright: ©2006-2011 Strobe Inc. and contributors.
// Portions ©2008-2011 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
/*global SC_benchmarkPreloadEvents*/
// sc_require("system/browser")
if (typeof SC_benchmarkPreloadEvents !== "undefined") {
SC.benchmarkPreloadEvents = SC_benchmarkPreloadEvents;
SC_benchmarkPreloadEvents = undefined;
} else {
SC.benchmarkPreloadEvents = { headStart: new Date().getTime() };
}
/* >>>>>>>>>> BEGIN source/system/loader.js */
// ==========================================================================
// Project: SproutCore - JavaScript Application Framework
// Copyright: ©2006-2011 Strobe Inc. and contributors.
// Portions ©2008-2011 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
// sc_require("system/browser");
SC.setupBodyClassNames = function() {
var el = document.body ;
if (!el) return ;
var browser, platform, shadows, borderRad, classNames, style;
browser = SC.browser.current ;
platform = SC.browser.windows ? 'windows' : SC.browser.mac ? 'mac' : 'other-platform' ;
style = document.documentElement.style;
shadows = (style.MozBoxShadow !== undefined) ||
(style.webkitBoxShadow !== undefined) ||
(style.oBoxShadow !== undefined) ||
(style.boxShadow !== undefined);
borderRad = (style.MozBorderRadius !== undefined) ||
(style.webkitBorderRadius !== undefined) ||
(style.oBorderRadius !== undefined) ||
(style.borderRadius !== undefined);
classNames = el.className ? el.className.split(' ') : [] ;
if(shadows) classNames.push('box-shadow');
if(borderRad) classNames.push('border-rad');
classNames.push(browser) ;
if (browser === 'chrome') classNames.push('safari');
classNames.push(platform) ;
var ieVersion = parseInt(SC.browser.msie,10);
if (ieVersion) {
if (ieVersion === 7) {
classNames.push('ie7');
}
else if (ieVersion === 8) {
classNames.push('ie8');
}
else if (ieVersion === 9) {
classNames.push('ie9');
}
}
if (SC.browser.mobileSafari) classNames.push('mobile-safari') ;
if ('createTouch' in document) classNames.push('touch');
el.className = classNames.join(' ') ;
} ;
</script>
<script type="text/javascript">
/* >>>>>>>>>> BEGIN source/lib/bootstrap.js */
//sc_resource('bootstrap');
/*global ActiveXObject utf8_encode utf8_decode unescape Me localStorage*/
/**
IMPORTANT: The content of this file is copied from the bootstrap file
normally contained in the (MobileMe-specific) Shared framework.
It is copied here because the Migration application requires
CoreWeb and the latest SproutCore, but needs to live on me.com
rather than Castle.
*/
// TODO: when the real /my/global-config.js is used, remove this redefinition of window.systemConfig,
// merging any Savannah and Miami specific changes made here.
/* Apple Global System Configuration File */
window.systemConfig = function() {
//If systemConfig is acquired from /my/global-config.js, just use that
if(window.systemConfig) return window.systemConfig ;
var milli = 1, second = 1000, minute = 60000, hour = 3600000, day = 86400000; return {
global: {
enabled: true,
pollMin: 2*minute,
pollMax: 8*minute,
helpBaseURL: {
chooseLoc: true, //bootstrap option to configure this as the appropriate string
en:"http://help.apple.com/mac/1/help/",
fr:"http://help.apple.com/mac/3/help/",
de:"http://help.apple.com/mac/4/help/",
ja:"http://help.apple.com/mac/2/help/"
},
SSOLogoutURL: "https://auth.me.com/logout",
SSOLoginURL: {
"calendar.me.com": "https://auth.me.com/authenticate",
"www.me.com": "https://auth.me.com/authenticate",
"secure.me.com": "https://auth.me.com/authenticate"
}
},
// TODO: fix this
move: {
iPadCompatible: true
},
gallery: {
enableSlideshow: false,
imageDownerThreadCount: 4, //default number of threads in the image downloader (currently only used in Gallery). Increase this if using multi-domaining to allow more than 2 open HTTP connections.
imageDownerThreadDelay: 0*milli //amount of forced delay in between image requests in the image downloader (currently only used in Gallery). Increase this if you're... desperate to the point of not caring about user experience. Good to keep it at zero.
},
calendar_owner: {
SSOLogoutURL: "/ca/logout",
iPadCompatible: true,
errorCatcher: true,
errorCatcherEmail: '[email protected]'
},
account: {
rootURL: "/wo/WebObjects/Account2.woa",
SSOLogoutURL: "/wo/WebObjects/Account2.woa/wa/logout",
initialTimeoutInterval: 10*second, // time to wait before showing an "app is slow in responding" page
finalTimeoutInterval: 1*minute // time to wait after showing the "app is slow in responding" page is shown, before you show the down page.
},
visitorGallery: {
marketingURL: "http://www.apple.com/mobileme"
},
idisk: {
aFS: false
}
};}();
/****************************************************************************
* BEGIN
* GENERAL PREREQUISITES
*****************************************************************************/
/**
Capture the window load start time.
*/
window._loadStartTime = new Date().getTime();
window.stats = {};
/**
Set isMe flag so that sign-out button gets enabled
*/
window.isMe = true;
/**
If window.console is not defined on the browser, then handle it.
*/
if (!window.console) {
var Console = function() {
this.error = this.info = this.warn = this.log = function(it) {
if (window.jash) jash.print(it, false);
} ;
};
window.console = new Console() ;
}
/**
Browser detection library.
*/
var Browser = {
IE: navigator.userAgent.indexOf('MSIE') > -1,
Opera: navigator.userAgent.indexOf('Opera') > -1,
WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
Gecko: navigator.userAgent.indexOf('Gecko') > -1 &&
navigator.userAgent.indexOf('KHTML') == -1,
iPhoneSafari: !!navigator.userAgent.match(/.*iPhone.*AppleWebKit.*Mobile/),
iPadSafari: !!navigator.userAgent.match(/.*iPad.*AppleWebKit.*Mobile/),
Safari:navigator.userAgent.indexOf('Safari') > -1,
Chrome: navigator.userAgent.indexOf('Chrome') > -1,
Firefox:navigator.userAgent.indexOf('Firefox') > -1
};
/**
Create a basic XHR during the bootstrapping code of MobileMe apps.
@param {String} url
@param {String} method ie. 'GET'
@param {Boolean} isAsync YES if asych.
@param {Object} headers Object containing key-pairs of headers.
*/
var simpleXHR = function(url, method, isAsync, headers) {
var t;
// Create the XHR if possible.
if (window.XMLHttpRequest) {
t = new XMLHttpRequest();
}
else if (window.ActiveXObject) {
try {
t = new ActiveXObject("Msxml2.XMLHTTP");
} catch(e) {
try {
t = new ActiveXObject("Microsoft.XMLHTTP");
} catch(e1) {
console.error('XMLHttpRequest not supported');
}
}
}
// If there is a transport, then set the url, method, and async as well as
// the request headers.
if(t) {
t.open(method, url, isAsync);
for(var key in headers) {
t.setRequestHeader(key, headers[key]);
}
}
return t;
};
/****************************************************************************
* END
* GENERAL PREREQUISITES
*****************************************************************************/
/****************************************************************************
* BEGIN
* GLOBAL CONFIG DEFAULTS
*****************************************************************************/
/**
Apple Global JavaScript Application Bootstrap
*/
var defaultGlobalConfig = function() {
var milli = 1,
second = 1000,
minute = 60000,
hour = 3600000,
day = 86400000;
return {
/**
default enabled. If disabled, clicking the application in the dock will
bring up a dialog telling a user the application is not available.
@property
@type {Boolean}
@default true
*/
enabled: true,
/**
default pollStyle. Options are "linear" or "exponential-decay" - This
determines whether autonomous polling will occur a set number of times
on an interval, or whether it will decay exponentially.
@property
@type {String}
@default "exponential-decay"
*/
pollStyle: "exponential-decay",
/**
default pollMin time. Respected only by exponential polling style.
Ignored by linear polling style.
@property
@type {Number}
@default 5*minute
*/
pollMin: 5 * minute,
/**
default pollMax time. Respected only by exponential polling style.
Ignored by linear polling style.
@property
@type {Number}
@default 1 * hour
*/
pollMax: 1 * hour,
/**
default pollInterval. Respected only by linear polling style.
Ignored by exponential polling style.
@property
@type {Number}
@default 5*minute
*/
pollInterval: 5 * minute,
/**
default maxPollCount. Respected only by linear polling style. Ignored by
exponential polling style.
@property
@type {Number}
@default 4
*/
maxPollCount: 4,
/**
default focusPollMin. This is the minimum amount of time required after
a refresh for a window onfocus to trigger a new refresh.
@property
@type {Number}
@default 4
*/
focusPollMin: 1*second,
/**
News URLs.
en: "http://www.apple.com/mobileme/news/", // News URL
fr: "http://www.apple.com/fr/mobileme/news/",
de: "http://www.apple.com/de/mobileme/news/",
ja: "http://www.apple.com/jp/mobileme/news/",
chooseLoc: true
@type {Object}
@property
*/
newsURL: {
en: "http://www.apple.com/mobileme/news/", // News URL
fr: "http://www.apple.com/fr/mobileme/news/",
de: "http://www.apple.com/de/mobileme/news/",
ja: "http://www.apple.com/jp/mobileme/news/",
chooseLoc: true
}
};}();
/****************************************************************************
* END
* GLOBAL CONFIG DEFAULTS
*****************************************************************************/
/****************************************************************************
* BEGIN
* BASE 64 ENCODING FUNCTIONS
*****************************************************************************/
/**
Base 64 string.
*/
var _b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
/**
Encode string in base64 format.
@param {String} data The data to be encoded.
@returns {String} Returns the encoded string.
*/
var base64_encode = function( data ) {
// http://kevin.vanzonneveld.net
// + original by: Tyler Akins (http://rumkin.com)
// + improved by: Bayron Guevara
// + improved by: Thunder.m
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + bugfixed by: Pellentesque Malesuada
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// - depends on: utf8_encode
// * example 1: base64_encode('Kevin van Zonneveld');
// * returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA=='
// mozilla has this native
// - but breaks in 2.0.0.12!
//if (typeof this.window['atob'] == 'function') {
// return atob(data);
//}
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc="", tmp_arr = [];
if (!data) {
return data;
}
data = utf8_encode(data+'');
do { // pack three octets into four hexets
o1 = data.charCodeAt(i++);
o2 = data.charCodeAt(i++);
o3 = data.charCodeAt(i++);
bits = o1<<16 | o2<<8 | o3;
h1 = bits>>18 & 0x3f;
h2 = bits>>12 & 0x3f;
h3 = bits>>6 & 0x3f;
h4 = bits & 0x3f;
// use hexets to index into b64, and append result to encoded string
tmp_arr[ac++] = _b64.charAt(h1) + _b64.charAt(h2) +
_b64.charAt(h3) + _b64.charAt(h4);
} while (i < data.length);
enc = tmp_arr.join('');
switch( data.length % 3 ){
case 1:
enc = enc.slice(0, -2) + '==';
break;
case 2:
enc = enc.slice(0, -1) + '=';
break;
}
return enc;
};
/**
Decode string in base64 format.
@param {String} input The data to be decoded.
@returns {String} Returns the decoded string.
*/
var base64_decode = function(input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (i < input.length) {
enc1 = _b64.indexOf(input.charAt(i++));
enc2 = _b64.indexOf(input.charAt(i++));
enc3 = _b64.indexOf(input.charAt(i++));
enc4 = _b64.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
}
output = utf8_decode(output);
return output;
};
/**
Encode string in utf8 format.
Base64 Decoding from http://www.webtoolkit.info/javascript-base64.html
private method for UTF-8 encoding
@param {String} string The string to be encoded.
@returns {String} Returns the encoded string.
*/
var utf8_encode = function (string) {
string = string.replace(/\r\n/g,"\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
};
/**
Decode string in utf8 format.
Base64 Decoding from http://www.webtoolkit.info/javascript-base64.html
private method for UTF-8 encoding
@param {String} utftext The string to be decoded.
@returns {String} Returns the decoded string.
*/
var utf8_decode = function(utftext) {
var string = "";
var i = 0;
var c,c1,c2,c3;
c=c1=c2=c3=0;
while ( i < utftext.length ) {
c = utftext.charCodeAt(i);
if (c < 128) {
string += String.fromCharCode(c);
i++;
} else if((c > 191) && (c < 224)) {
c2 = utftext.charCodeAt(i+1);
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
} else {
c2 = utftext.charCodeAt(i+1);
c3 = utftext.charCodeAt(i+2);
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
}
return string;
} ;
/****************************************************************************
* END
* BASE 64 ENCODING FUNCTIONS
*****************************************************************************/
/****************************************************************************
* BEGIN
* BOOTSTRAP HANDLER
*****************************************************************************/
/**
Handle for the currently active application.
This will be set to bootstrap.env, which will contain all information
for all applications
*/
var _env = null;
/**
The bootstrap object.
This object handles the login for the applications.
*/
var bootstrap = {
/**
List of known applications.
@type {Array}
*/
knownApps: ['gallery','idisk','icloud','account'],
/**
These applications should trigger the re-authentication dialog before switching to them
@type {Array}
*/
reauthApplications: ['findmyiphone','account'],
/**
The fully qualified base url for the known applications.
Use the getUrlForApp(known app name) method to get the fully decorated url.
@type {Object} where the key is an item from the knownApps array.
*/
appUrls: {
'gallery' : 'https://www.me.com/gallery/',
'idisk' : 'https://www.me.com/idisk/',
'icloud' : 'https://www.icloud.com/',
'account' : 'https://secure.me.com/account/'
},
/**
If true, ignore the processing of the Aic cookie.
@property
@default false
@type {Boolean}
*/
ignoreAicCookie: false,
/**
The initial application name.
@property
@default window.location.pathname
@type {String}
*/
app: window.location.pathname,
/**
List of known prod domains.
[(/me.com$/),(/mac.com$/)]
@property
@type {Array}
*/
domains: [(/me.com$/),(/mac.com$/)],
/**
The application name.
@property
@type {String}
*/
appName: '',
/**
The environment information.
@property
@type {Object}
*/
env: {},
/**
If true, the application is redirecting.
@property
@default false
@type {Boolean}
*/
isRedirecting: false,
/**
The restricted applications.
@property
@type {Object}
*/
restricted: null,
/**
If true, authentication is disabled.
@property
@default false
@type {Boolean}
*/
authenticationDisabled: false,
/**
Array containing the configured applications.
@property
@type {Array}
*/
configuredApps: [],
/**
If true, login cookie exists.
@property
@default false
@type {Boolean}
*/
hasLoginCookie: false,
/**
Reverse timezone lookup table.
0:"Pacific/Pago_Pago",
1:"Pacific/Tahiti",
2:"Pacific/Honolulu",
3:"America/Anchorage",
4:"America/Los_Angeles",
5:"America/Denver",
6:"America/Chicago",
7:"America/New_York",
8:"Canada/Atlantic",
9:"Canada/Newfoundland",
10:"America/Buenos_Aires",
11:"America/Sao_Paulo",
12:"America/Cayenne",
13:"America/Noronha",
14:"Atlantic/Cape_Verde",
15:"UTC",
16:"Europe/Amsterdam",
17:"Europe/Berlin",
18:"Europe/Brussels",
19:"Europe/Madrid",
20:"Europe/Oslo",
21:"Europe/Paris",
22:"Europe/Vienna",
23:"Europe/Athens",
24:"Africa/Cairo",
25:"Africa/Djibouti",
26:"Africa/Addis_Ababa",
27:"Europe/Samara",
28:"Asia/Kabul",
29:"Asia/Aqtau",
30:"Indian/Maldives",
31:"Asia/Yekaterinburg",
32:"Asia/Calcutta",
33:"Asia/Almaty",
34:"Asia/Novosibirsk",
35:"Asia/Jakarta",
36:"Asia/Bangkok",
37:"Australia/West",
38:"Asia/Hong_Kong",
39:"Asia/Brunei",
40:"Asia/Singapore",
41:"Asia/Tokyo",
42:"Asia/Vladivostok",
43:"Australia/Queensland",
44:"Australia/Adelaide",
45:"Australia/NSW",
46:"Pacific/Kosrae",
47:"Asia/Kamchatka",
48:"Pacific/Wallis",
49:"Pacific/Auckland",
50:"Pacific/Chatham",
51:"Etc/GMT+12",
52:"Pacific/Midway",
53:"Pacific/Marquesas",
54:"Pacific/Gambier",
55:"America/Adak",
56:"Pacific/Pitcairn",
57:"US/Pacific",
58:"US/Mountain",
59:"Pacific/Galapagos",
60:"America/Mexico_City",
61:"America/Winnipeg",
62:"America/Montreal",
63:"America/Puerto_Rico",
64:"America/St_Lucia",
66:"Atlantic/Bermuda",
67:"Atlantic/South_Georgia",
68:"Atlantic/Jan_Mayen",
69:"Africa/Timbuktu",
70:"Europe/London",
71:"Europe/Lisbon",
72:"Africa/Algiers",
73:"Africa/Johannesburg",
74:"Europe/Istanbul",
75:"Asia/Tel_Aviv",
76:"Europe/Bucharest",
77:"Africa/Nairobi",
78:"Asia/Dubai",
79:"Europe/Moscow",
80:"Asia/Katmandu",
81:"Asia/Aqtobe",
82:"Asia/Colombo",
83:"Asia/Saigon",
84:"Asia/Shanghai",
85:"Australia/Perth",
86:"Asia/Taipei",
87:"Asia/Seoul",
88:"Australia/Melbourne",
89:"Australia/South",
90:"Australia/Sydney",
91:"Pacific/Guam",
92:"Australia/Lord_Howe",
93:"Pacific/Noumea",
94:"Pacific/Norfolk",
95:"Pacific/Fiji",
96:"Pacific/Tongatapu",
97:"Pacific/Kiritimati",
98:"Canada/Saskatchewan",
99:"America/Phoenix",
111:"US/Pacific",
112:"Canada/Mountain",
113:"Canada/Eastern",
113:"Canada/Eastern",
114:"Brazil/East",
115:"Atlantic/Azores",
116:"Europe/Dublin",
117:"Atlantic/Reykjavik",
118:"Europe/Belgrade",
119:"Europe/Budapest",
120:"Europe/Copenhagen",
121:"Europe/Ljubljana",
122:"Europe/Prague",
123:"Europe/Rome",
124:"Europe/Stockholm",
125:"Europe/Warsaw",
126:"Europe/Zagreb",
127:"Europe/Zurich",
128:"Asia/Amman",
129:"Asia/Beirut",
130:"Asia/Damascus",
131:"Europe/Helsinki",
132:"Asia/Jerusalem",
133:"Europe/Kiev",
134:"Europe/Sofia",
135:"Indian/Antananarivo",
136:"Asia/Baghdad",
137:"Asia/Bahrain",
138:"Asia/Kuwait",
139:"Asia/Qatar",
140:"Asia/Riyadh",
141:"Asia/Tehran",
142:"Asia/Muscat",
143:"Asia/Karachi",
144:"Asia/Tashkent",
145:"Asia/Dhaka",
146:"Asia/Omsk",
147:"Asia/Rangoon",
148:"Asia/Krasnoyarsk",
149:"Asia/Phnom_Penh",
150:"Asia/Kuala_Lumpur",
151:"Asia/Manila",
152:"Asia/Ulaanbaatar",
153:"Asia/Tokyo",
154:"Asia/Pyongyang",
155:"Asia/Yakutsk",
156:"Australia/Brisbane",
157:"Australia/Canberra",
158:"Asia/Magadan",
159:"Asia/Anadyr",
222:"US/Central",
333:"US/Eastern",
500:"America/Vancouver",
501:"America/Guatemala",
502:"America/Managua",
503:"America/Costa_Rica",
504:"America/El_Salvador",
505:"America/Tegucigalpa",
506:"America/Bogota",
507:"America/Detroit",
508:"America/Havana",
509:"America/Indianapolis",
510:"America/Lima",
511:"America/Panama",
512:"America/Port-au-Prince",
513:"America/Guayaquil",
514:"America/Asuncion",
515:"America/Barbados",
516:"America/Caracas",
517:"America/Guyana",
518:"America/La_Paz",
519:"America/Santiago",
520:"America/Santo_Domingo",
521:"America/Montevideo",
522:"America/Godthab",
523:"America/Paramaribo",
524:"America/Recife",
525:"Africa/Accra",
527:"Africa/Conakry",
528:"Africa/Dakar",
529:"Africa/Freetown",
530:"Africa/Monrovia",
531:"Africa/Nouakchott",
532:"Africa/Ouagadougou",
533:"Africa/Casablanca",
534:"Africa/Bangui",
535:"Europe/Bratislava",
536:"Africa/Douala",
537:"Africa/Kinshasa",
538:"Africa/Lagos",
539:"Africa/Luanda",
540:"Africa/Ndjamena",
541:"Africa/Tripoli",
542:"Africa/Tunis",
543:"Africa/Harare",
544:"Africa/Khartoum",
545:"Africa/Lusaka",
546:"Africa/Maputo",
547:"Africa/Asmera",
548:"Africa/Dar_es_Salaam",
549:"Africa/Kampala",
550:"Africa/Mogadishu",
551:"Asia/Aden",
552:"Indian/Mauritius",
553:"Indian/Mahe",
554:"Asia/Ashgabat",
555:"GMT",
556:"Asia/Yerevan",
557:"Africa/Bamako",
558:"Australia/Darwin",
559:"Australia/Hobart",
560:"Asia/Baku"
@property
@type {Object}
*/
tzValueToStringTable: {
0:"Pacific/Pago_Pago",
1:"Pacific/Tahiti",
2:"Pacific/Honolulu",
3:"America/Anchorage",
4:"America/Los_Angeles",
5:"America/Denver",
6:"America/Chicago",
7:"America/New_York",
8:"Canada/Atlantic",
9:"Canada/Newfoundland",
10:"America/Buenos_Aires",
11:"America/Sao_Paulo",
12:"America/Cayenne",
13:"America/Noronha",
14:"Atlantic/Cape_Verde",
15:"UTC",
16:"Europe/Amsterdam",
17:"Europe/Berlin",
18:"Europe/Brussels",
19:"Europe/Madrid",
20:"Europe/Oslo",
21:"Europe/Paris",
22:"Europe/Vienna",
23:"Europe/Athens",
24:"Africa/Cairo",
25:"Africa/Djibouti",
26:"Africa/Addis_Ababa",
27:"Europe/Samara",
28:"Asia/Kabul",
29:"Asia/Aqtau",
30:"Indian/Maldives",
31:"Asia/Yekaterinburg",
32:"Asia/Calcutta",
33:"Asia/Almaty",
34:"Asia/Novosibirsk",
35:"Asia/Jakarta",
36:"Asia/Bangkok",
37:"Australia/West",
38:"Asia/Hong_Kong",
39:"Asia/Brunei",
40:"Asia/Singapore",
41:"Asia/Tokyo",
42:"Asia/Vladivostok",
43:"Australia/Queensland",
44:"Australia/Adelaide",
45:"Australia/NSW",
46:"Pacific/Kosrae",
47:"Asia/Kamchatka",
48:"Pacific/Wallis",
49:"Pacific/Auckland",
50:"Pacific/Chatham",
51:"Etc/GMT+12",
52:"Pacific/Midway",
53:"Pacific/Marquesas",
54:"Pacific/Gambier",
55:"America/Adak",
56:"Pacific/Pitcairn",
57:"US/Pacific",
58:"US/Mountain",
59:"Pacific/Galapagos",
60:"America/Mexico_City",
61:"America/Winnipeg",
62:"America/Montreal",
63:"America/Puerto_Rico",
64:"America/St_Lucia",
66:"Atlantic/Bermuda",
67:"Atlantic/South_Georgia",
68:"Atlantic/Jan_Mayen",
69:"Africa/Timbuktu",
70:"Europe/London",
71:"Europe/Lisbon",
72:"Africa/Algiers",
73:"Africa/Johannesburg",
74:"Europe/Istanbul",
75:"Asia/Tel_Aviv",
76:"Europe/Bucharest",
77:"Africa/Nairobi",
78:"Asia/Dubai",
79:"Europe/Moscow",
80:"Asia/Katmandu",
81:"Asia/Aqtobe",
82:"Asia/Colombo",
83:"Asia/Saigon",
84:"Asia/Shanghai",
85:"Australia/Perth",
86:"Asia/Taipei",
87:"Asia/Seoul",
88:"Australia/Melbourne",
89:"Australia/South",
90:"Australia/Sydney",
91:"Pacific/Guam",
92:"Australia/Lord_Howe",
93:"Pacific/Noumea",
94:"Pacific/Norfolk",
95:"Pacific/Fiji",
96:"Pacific/Tongatapu",
97:"Pacific/Kiritimati",
98:"Canada/Saskatchewan",
99:"America/Phoenix",
111:"US/Pacific",
112:"Canada/Mountain",
113:"Canada/Eastern",
113:"Canada/Eastern",
114:"Brazil/East",
115:"Atlantic/Azores",
116:"Europe/Dublin",
117:"Atlantic/Reykjavik",
118:"Europe/Belgrade",
119:"Europe/Budapest",
120:"Europe/Copenhagen",
121:"Europe/Ljubljana",
122:"Europe/Prague",
123:"Europe/Rome",
124:"Europe/Stockholm",
125:"Europe/Warsaw",
126:"Europe/Zagreb",
127:"Europe/Zurich",
128:"Asia/Amman",
129:"Asia/Beirut",
130:"Asia/Damascus",
131:"Europe/Helsinki",
132:"Asia/Jerusalem",
133:"Europe/Kiev",
134:"Europe/Sofia",
135:"Indian/Antananarivo",
136:"Asia/Baghdad",
137:"Asia/Bahrain",
138:"Asia/Kuwait",
139:"Asia/Qatar",
140:"Asia/Riyadh",
141:"Asia/Tehran",
142:"Asia/Muscat",
143:"Asia/Karachi",
144:"Asia/Tashkent",
145:"Asia/Dhaka",
146:"Asia/Omsk",
147:"Asia/Rangoon",
148:"Asia/Krasnoyarsk",
149:"Asia/Phnom_Penh",
150:"Asia/Kuala_Lumpur",
151:"Asia/Manila",
152:"Asia/Ulaanbaatar",
153:"Asia/Tokyo",
154:"Asia/Pyongyang",
155:"Asia/Yakutsk",
156:"Australia/Brisbane",
157:"Australia/Canberra",
158:"Asia/Magadan",
159:"Asia/Anadyr",
222:"US/Central",
333:"US/Eastern",
500:"America/Vancouver",
501:"America/Guatemala",
502:"America/Managua",
503:"America/Costa_Rica",
504:"America/El_Salvador",
505:"America/Tegucigalpa",
506:"America/Bogota",
507:"America/Detroit",
508:"America/Havana",
509:"America/Indianapolis",
510:"America/Lima",
511:"America/Panama",
512:"America/Port-au-Prince",
513:"America/Guayaquil",
514:"America/Asuncion",
515:"America/Barbados",
516:"America/Caracas",
517:"America/Guyana",
518:"America/La_Paz",
519:"America/Santiago",
520:"America/Santo_Domingo",
521:"America/Montevideo",
522:"America/Godthab",
523:"America/Paramaribo",
524:"America/Recife",
525:"Africa/Accra",
527:"Africa/Conakry",
528:"Africa/Dakar",
529:"Africa/Freetown",
530:"Africa/Monrovia",
531:"Africa/Nouakchott",
532:"Africa/Ouagadougou",
533:"Africa/Casablanca",
534:"Africa/Bangui",
535:"Europe/Bratislava",
536:"Africa/Douala",
537:"Africa/Kinshasa",
538:"Africa/Lagos",
539:"Africa/Luanda",
540:"Africa/Ndjamena",
541:"Africa/Tripoli",
542:"Africa/Tunis",
543:"Africa/Harare",
544:"Africa/Khartoum",
545:"Africa/Lusaka",
546:"Africa/Maputo",
547:"Africa/Asmera",
548:"Africa/Dar_es_Salaam",
549:"Africa/Kampala",
550:"Africa/Mogadishu",
551:"Asia/Aden",
552:"Indian/Mauritius",
553:"Indian/Mahe",
554:"Asia/Ashgabat",
555:"GMT",
556:"Asia/Yerevan",
557:"Africa/Bamako",
558:"Australia/Darwin",
559:"Australia/Hobart",
560:"Asia/Baku"
},
/**
Lookup the timezone string from the value.
@param {Number} value The value.
*/
tzValueToString: function(value) {
var tz = this.tzValueToStringTable[value];
if(tz === undefined) {
tz = 'US/Pacific';
}
return tz;
},
/**
Returns true if in production.
*/
isProduction: function() {
if(this._isProduction === undefined) {
this._isProduction = false;
var i, len;
var domains = this.domains;
var host = window.location.host;
for(i = 0, len = domains.length; i < len; ++i) {
if (host.match(domains[i])) {
this._isProduction = true;
break;
}
}
}
return this._isProduction;
},
/**
Checks the brower compatiability and returns true if compatible.
@returns {Boolean} true if compatible.
*/
checkBrowserCompatibility: function() {
var ua= navigator.userAgent.toLowerCase();
var version = navigator.appVersion;
var versionMinor, versionSafari;
var language = navigator.language ? navigator.language : navigator.userLanguage;
var isMac = (ua.indexOf('mac') != -1);
var isWin = (ua.indexOf('win') != -1);
var isVista = (ua.indexOf('nt 6') != -1);
var isWinXP = (ua.indexOf('nt 5') != -1);
var isOldWin = (isWin && !isVista && !isWinXP) ? true:false;
var isLinux = (ua.indexOf('linux') != -1);
var hash = window.location.hash;
if (this.readCookie('browser') !== null) return true;
if (Browser.IE) {
version = parseFloat( ua.substring( ua.indexOf('msie ') + 5 ) );
} else if (Browser.WebKit) {
version = parseFloat( ua.match(/AppleWebKit\/(\d+[.]*\d*)/i)[1] );
} else if (Browser.Firefox) {
versionMinor = parseFloat( ua.match(/Firefox\/(\d+[.]*\d*)/i)[1] );
version = parseInt(versionMinor,10);
}
if (Browser.Safari || Browser.iPadSafari) {
versionSafari = ua.match(/Version\/(\d+[.]*\d*)/i);
if (!versionSafari) {
versionSafari = 2;
} else {
versionSafari = parseFloat( versionSafari[1] );
}
}
// Write the version and minor versions to the Browser hash, for use by
// other functions.
Browser.version = version;
Browser.versionMinor = versionMinor;
Browser.versionSafari = versionSafari;
var isCalenderInvite = (this.appName === 'calendar' && hash.length && hash.indexOf("#i") === 0);
var iphone_page = isCalenderInvite ? 'calendar_ios' : 'iphone_welcome';
var ipad_page = isCalenderInvite ? 'calendar_ipad' : 'ipad_welcome';
var dst_page = Browser.iPhoneSafari ? iphone_page : (Browser.iPadSafari?ipad_page:'unsupported_browser');
if ((Browser.IE && version > 9) || (Browser.IE && version < 7) ||
(Browser.Safari && versionSafari < 3.1 && !Browser.Chrome) ||
(Browser.Firefox && versionMinor < 3.5 ) ||
(Browser.Firefox && versionMinor >= 3.5 && (isOldWin || isLinux) ) ||
Browser.Opera ||
(Browser.WebKit && !Browser.Safari && !Browser.iPadSafari && !Browser.iPhoneSafari && !Browser.Chrome) ||
Browser.iPhoneSafari ||
(Browser.iPadSafari && (!bootstrap.getConfig('iPadCompatible') || isCalenderInvite)) ) {
var langCode = language.substring(0, 2) ;
switch(langCode) {
case 'en':
case 'ja':
case 'fr':
case 'de':
window.location.href = "http://www.me.com/"+dst_page+"/"+langCode+"/"+hash;
this.isRedirecting = true;
return false;
default:
window.location.href = "http://www.me.com/"+dst_page+"/"+hash;
this.isRedirecting = true;
return false;
}
return true ;
}
return true ;
},
/**
Given the name of a cookie (or regular expression), read and return the
value.
In the case of giving a regular expression, we will only return the cookie
value of the first match.
@param {String} cookieName The name of the cookie or string representation
of a regular expression (as long as isRegExp is true)
@param {Boolean} isRegExp
@returns {String} The cookie value
*/
readCookie: function(cookieName, isRegExp) {
var cookies = document.cookie,
indexStart, indexEnd, matches;
if (cookies.length>0) {
if(isRegExp) {
// by regular expression
matches = cookies.match(new RegExp('(' + cookieName + ')='));
if(matches) cookieName = matches[1];
}
// by string
indexStart = cookies.indexOf(cookieName + "=");
if (indexStart!==-1) {
indexStart = indexStart + cookieName.length+1;
indexEnd = cookies.indexOf(";", indexStart);
if (indexEnd===-1) indexEnd = cookies.length;
return unescape(cookies.substring(indexStart, indexEnd));
}
}
return null;
},
/**
Writes information to a cookie.
The expiration time is set to the browser session.
@param {String} c_name The name of the cookie.
@param {String} c_value The value of the cookie.
@param {String} c_path The path to scope the cookie. Defaults to root. e.g. '/'
@param {String} c_domain The domain to scope the cookie. Defaults to hostname on url. e.g. '.www.me.com'
@returns {String} The value of the cookie.
*/
writeCookie: function(c_name, c_value, c_path, c_domain) {
if (!c_name) return undefined;
if (c_value === undefined || c_value === null) return undefined;
if (!c_path) c_path = '/';
if (!c_domain) c_domain = '.' + window.location.hostname;
document.cookie = c_name + "=" + c_value + ";path=" + c_path + ';domain=' + c_domain + ";" ;
return c_value;
},
/**
Given the name (or regular expression) of a cookie, delete it.
@param {String} cookieName The name of the cookie.
@param {Boolean} isRegExp - should treat cookieName as a regular expression
*/
deleteCookie: function(cookieName, isRegExp) {
// get the value...
var cookies = document.cookie,
value = this.readCookie(cookieName, isRegExp),
matches;
// nothing to delete.
if (value === null) return;
if(isRegExp) {
// find the cookie name
matches = cookies.match(new RegExp('(' + cookieName + ')='));
if(matches) cookieName = matches[1];
}
// delete it!
document.cookie = cookieName + "=;path=/;domain=." + window.location.hostname + ";expires=Thu, 01-Jan-1970 00:00:01 GMT" ;
},
/**
Attempt to retrieve the 'aic' cookie from davproc if we have the
'mma-<app>' artifact cookie. Needed for non-woa apps such as idisk and
gallery that require during bootstrap the username, which is only in the
aic cookie.
The aic cookie is obtained by making a synchronous XHR call to:
http://www.me.com/[ix|ro]/?protocol=roap&item=properties&mode=find&depth=1
The request must send the single-use mma-<app> cookie which is the server
validates. The response will expire the mma-<app> cookie and return the
aic cookie.
@see <rdar://problem/6772547> for more information.
@param {String} authCookie The auth cookie.
@param {String} appName Name of the currently Loaded app.
@return value of aic cookie or null if it was not retrieved.
*/
requestAicCookie: function(authCookie, appName) {
// safegaurd
if(!authCookie) {
console.error('authCookie cookie missing');
return null;
}
var url = '/AppNotSupported'; // url that returns an AIC cookie
if(appName === 'gallery') {
url = '/ro/?protocol=roap&item=properties&mode=find&depth=1';
} else if(appName === 'idisk') {
url = '/ix/?protocol=roap&item=properties&mode=find&depth=1';
}
var t = simpleXHR(url, 'GET', false, {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Mobileme-Version': '1.0'
});
if(t) {
t.onreadystatechange = function() {
if (t.readyState == 4) {
if (t.status == 200) {
console.warn('getAicCookie got 200 Success');
}
else {
console.error('getAicCookie request did not return 200 Success');
}
}
};
t.send();
}
return this.readCookie('aic');
},
/**
Extract the username from the AIC cookie and attempt to set it back
to the Me.dockController.
*/
extractUserInfo: function() {
var aic = this.readCookie('aic'),
appName = this.appName,
dc;
if(aic) {
this.processAicCookie(aic, this.systemConfig);
bootstrap._configs = null;
}
this.hasExtractedUserInfo = true;
if(window.SC && window.Me && window.Me.dockController) {
dc = Me.dockController;
dc.set('hasExtractedUserInfo', true);
dc.applyAppConfiguration(appName);
// Because the state of the apps may have changed, ask the dock controller
// to recompute them the next time they're asked for.
dc.notifyPropertyChange('apps');
}
// Now that we have extracted the user information, invoke the “environment
// did become valid” callback if we have processed all necessary
// information.
this._triggerEnvironmentDidBecomeValidIfAppropriate();
},
/**
Decode the following information from the mmi cookie:
- language id
- timezone
- calstatus - as defined in:
http://da.apple.com/confluence/display/LG/Calendar+Status+Definitions
- mailstatus - as defined in:
http://da.apple.com/confluence/display/QA/App+behavior
- classicsyncmigrationstatus - as defined in:
<rdar://problem/9046935> Classic AuthServer should communicate syncMigration status to UI apps
See <rdar://problem/6772547> related to Truffle.
@param {String} mmiCookie The mmi cookie.
@param {Object} config The configuration object for the app.
*/
processMmiCookie: function(mmiCookie, config) {
// Safeguard.
if(!mmiCookie) {
console.error('mmi cookie missing');
return;
}
var globalConfig = config.global,
values = mmiCookie.split(':'), // ['lang=en', 'tz=111', etc]
i, len, nvp, key, value;
for(i = 0, len = values.length; i < len; i++) {
nvp = values[i].split('='); // ['lang', 'en']
key = nvp[0];
value = nvp[1];
switch (key) {
case 'lang':
globalConfig.lang = value;
break;
case 'tz':
if (!isNaN(parseInt(value, 10))) {
globalConfig.timeZoneID = value;
globalConfig.timeZoneString = this.tzValueToString(value);
}
break;
case 'calstatus':
globalConfig.calstatus = parseInt(value, 10);
break;
case 'mailstatus':
globalConfig.mailstatus = parseInt(value, 10);
break;
case 'classicsyncmigrationstatus':
globalConfig.classicsyncmigrationstatus = value;
break;
}
}
this.hasProcessedMMICookie = true;
// Now that we have processed the MMI cookie, invoke the “environment did
// become valid” callback if we have processed all necessary information.
this._triggerEnvironmentDidBecomeValidIfAppropriate();
},
/**
The mmiv cookie is used in the visitor experience, for instance under
calendar.me.com, and is set broadly on .me.com .
Cookie is in the format 'lang=en:tz=US/Pacific'
Decode the following information from the mmi cookie:
- language
- timezone
These two values are written during the first-run visitor experience.
@param {String} mmivCookie The mmi cookie.
@param {Object} config The configuration object for the app.
*/
processMmivCookie: function(mmivCookie, config) {
// Safeguard.
if (!mmivCookie) {
console.error('mmiv cookie missing');
return;
}
var globalConfig = config.global,
values = mmivCookie.split(':'), // ['lang=en', 'tz=US/Pacific', etc]
i, len, nvp, key, value;
for(i = 0, len = values.length; i < len; i++) {
nvp = values[i].split('='); // ['lang', 'en']
key = nvp[0];
value = nvp[1];
switch (key) {
case 'lang':
globalConfig.lang = ['en', 'de', 'fr', 'jp'].indexOf(value)!==-1 ? value : 'en';
break;
case 'tz':
if (!isNaN(parseInt(value, 10))) {
globalConfig.timeZoneString = value;
}
break;
case 'classicsyncmigrationstatus':
globalConfig.classicsyncmigrationstatus = value;
break;
}
}
this.hasProcessedMMIVCookie = true;
// Now that we have processed the MMIV cookie, invoke the “environment did
// become valid” callback if we have processed all necessary information.
this._triggerEnvironmentDidBecomeValidIfAppropriate();
},
/**
Decode the following information from the aic cookie:
- username
- firstname
- lastname
- language
- timezone
- list of enabled apps
See <rdar://problem/6772547> related to Truffle.
@param {String} aicCookie The aic cookie.
@param {Object} config The configuration object for the app.
*/
processAicCookie: function(aicCookie, config) {
if (!aicCookie) return;
var triggerEnvironmentIsValidCallback;
// If this is our first time processing the AIC cookie, then we might
// trigger a callback at the end so that interested parties know that we now
// have valid environment information.
if (!this.isAicProcessed && !this.ignoreAicCookie) triggerEnvironmentIsValidCallback = true;
this.isAicProcessed = true;
var globalConfig = config.global ;
var aicCookieLength = 4;
aicCookie = base64_decode(aicCookie).replace(/^\s+|\s+$/g,"").split("\n");
globalConfig.username = aicCookie[0];
globalConfig.firstName = aicCookie[1] ;
globalConfig.lastName = aicCookie[2] ;
globalConfig.lang = aicCookie[3];
if (aicCookie[4]) {
aicCookieLength = 5;
globalConfig.timeZoneID = aicCookie[4];
}
// collect user account enabled apps
var enabled = [] ;
var idx = aicCookie.length ;
while(--idx >= aicCookieLength) {
var appName = aicCookie[idx] ;
if (appName && appName.length > 0) enabled.push(appName) ;
}
// find all configured applications.
var configuredApps = this.configuredApps ;
// enable/disable apps...
if (enabled.length > 0) {
this.restricted = {};
idx = configuredApps.length;
while(--idx >= 0) {
var thisDisabledAppName = configuredApps[idx];
if(!config[thisDisabledAppName])
{config[thisDisabledAppName] = {};}
config[thisDisabledAppName].enabled = false;
}
idx = enabled.length ;
var enabledApp;
while(--idx >= 0) {
enabledApp = this.getAppNameForService(enabled[idx]);
config[enabledApp].enabled = true ;
this.restricted[enabledApp] = true;
}
}
// Update the loading page now that we have the member's name
this.updateLoadingUsername(config);
// Many clients want to do certain things only after we have a “complete
// environment”; that is, valid MMI[V] and AIC information, with the user’s
// information properly extracted and ready to get in from
// bootstrap.getConfig().
//
// If we have processed all this information, then we can notify any such
// clients now. (See 7066774 for details on when we would ignore the
// cookie.)
if (triggerEnvironmentIsValidCallback) this._triggerEnvironmentDidBecomeValidIfAppropriate();
},
getAppNameForService: function(serviceName) {
// create a serviceName -> appName map if this is the first time
if(!this._serviceToAppNameMap) {
this._serviceToAppNameMap = {};
var configuredApps = this.configuredApps ;
var config = this.systemConfig;
var idx = configuredApps.length;
var appName, thisServiceName;
while(--idx >= 0) {
appName = configuredApps[idx];
thisServiceName = (config[appName] && config[appName].serviceName) || appName;
this._serviceToAppNameMap[thisServiceName] = appName;
}
}
return this._serviceToAppNameMap[serviceName];
},
/**
Called once the membername has been loaded from the aic cookie.
Design has requested that we show the member's name in the upper corner
as soon as possible, so if we have an element with id loading-username in
the DOM, we insert the user's first and last names.
*/
updateLoadingUsername: function(config) {
var globalConfig = config.global,
firstName = globalConfig.firstName,
lastName = globalConfig.lastName,
username = globalConfig.username,
ret = [],
elem;
// TODO: flip for Japanese
if(firstName) ret.push(firstName);
if(lastName) ret.push(lastName);
if(!firstName && !lastName) ret.push(username);
elem = document.getElementById('loading-username');
if (elem) {
elem = elem.childNodes[1];
if (elem.textContent) {
elem.textContent = ret.join(' ');
} else {
elem.innerText = ret.join(' ');
}
}
},
/*
Initializes and configures application environment.
- check browser compatibility
- process http://www.me.com/my/global-config.js data
- figure out which applications are available
- setup login and logout url variables per Truffle specification
- check existance of auth cookies, and request aic cookie if not
present, see <rdar://problem/6772471>
- figure out which language to use
@return true if application should continue loading which may include
redirection to the login page in /lib/index.html
false if application should stop loading, used in catostrophic
errors
*/
setup: function() {
var app = this.app,
hostName = window.location.hostname.toString(),
systemConfig = window.systemConfig,
defaultGlobalConfig = window.defaultGlobalConfig;
// calendarVisitor = hostName.indexOf('calendar') !== -1;
// this.isVisitorApp = false;
// If there are multiple slashes, replace them with one as
// they are meaningless.
// <rdar://problem/8063951> Doubling "/" in URL allow to access deactivated accounts
app = app.replace(/\/\/+/g, '/');
// if the url has a trailing slash, remove it, e.g. '/mail/' becomes '/mail'
if(app.match(/\/$/)) {
app = app.substring(0, app.length - 1);
}
// if(calendarVisitor) { // if http://calendar.me.com
// app = '';
// }
// else
if(!app || app === '/') { // if http://www.me.com/ (no app), default to http://www.me.com/mail
app = '/' + this.knownApps[0];
}
// convert from '/mail' or '/mail/fr' to 'mail'
var appName = app.split('/')[1];
// if in dev mode (localhost:4020/calendar_visitor,_owner) or calendar visitor (calendar.me.com)
// if(calendarVisitor || appName.indexOf('calendar_') !== -1) {
// appName = 'calendar';
// if(calendarVisitor) this.isVisitorApp = true;
// }
// Save the appName and the fully qualified app url.
this.appName = appName;
this.app = app;
// If there is no system config, there is no global-config.
if(!systemConfig) {
console.error("Could not load www.me.com/my/global-config.js");
}
// Bring config in... add global if not already there...
this.systemConfig = systemConfig ? systemConfig : {};
this.defaultGlobalConfig = defaultGlobalConfig ? defaultGlobalConfig : {};
// clean up and remove them from global window object
if (systemConfig) window.systemConfig = null ;
if (defaultGlobalConfig) window.defaultGlobalConfig = null ;
var config = this.systemConfig;
if (!config.global) config.global = {};
if (config.global.enabled === undefined) config.global.enabled = true ;
var serviceName = (config[appName] && config[appName].serviceName) || appName;
this.prepareEnv();
// If not compatible, exit.
if (!this.checkBrowserCompatibility()) return false;
// For older browsers, we need to do a bit of work before we can use local
// storage.
this.setUpLocalStorage();
// Find the login url for this hostname from global-config.js
var global = config.global ;
var isDev = !this.isProduction();
if (global && global.SSOLoginURL || !isDev) {
var authHost = global.SSOLoginURL[hostName];
if(!authHost && !isDev) {
console.error('global-config.js: SSOLoginURL not defined for host:' + hostName);
return false; // do not proceed
}
var isSecure = hostName.indexOf('secure.me')===0;
this.loginUrl = authHost + '?service=' + serviceName +
'&ssoNamespace=' + 'appleid' +
'&formID=' + (isSecure?'reauthorizeForm':'loginForm')+
(isSecure?'&reauthorize=Y':'');
// serviceName + '&ssoNamespace=' + (this.isVisitorApp ? 'appleid' : 'appleid&formID=loginForm');
var anchor = window.location.hash;
if(!anchor) anchor = '';
this.returnUrl = base64_encode(window.location.protocol + '//' + hostName + app + '/' + anchor);
this.loginUrl += '&returnURL=' + this.returnUrl;
var authAnchor = config[appName] && config[appName].anchorName;
this.loginUrl += '&anchor=' + authAnchor;
if(anchor.indexOf('#') === 0) anchor = anchor.substring(1);
// HACK: in ase user has bookmarked navIndex url, convert it to
// friendly-url so that auth-
// server can understand it. See <rdar://problem/7195944> and
// <rdar://problem/7192353>
if(anchor) {
var anchorStrings = {
'&navIndex=1': 'summary',
'&navIndex=2': 'info',
'&navIndex=3': 'options',
'&navIndex=4': 'billing',
'&navIndex=5': 'password',
'&navIndex=6': 'storage',
'&navIndex=7': 'domains',
'&navIndex=8': 'seccert'
};
this.loginUrl += '&anchor=' + anchorStrings[anchor];
}
var reauthApps = this.reauthApplications,
requiresReauth = false;
for(var i=0, iLen=reauthApps.length; i<iLen; i++) {
if(reauthApps[i] === appName) {
requiresReauth = true;
break;
}
}
if(requiresReauth && document.referrer) {
this.loginUrl += '&cancelURL=' + document.referrer;
}
this.logoutUrl = this.getConfig("SSOLogoutURL", appName);
if(this.logoutUrl.indexOf('returnURL') == -1) {
this.logoutUrl += (this.logoutUrl.indexOf('?') == -1) ? '?' : '&';
var returnUrl = window.location.protocol + '//' + hostName + '/';
// do not append appName for visitor sites
// if(!this.isVisitorApp)
returnUrl += appName + '/';
this.logoutUrl += 'returnURL=' + base64_encode(returnUrl);
}
}
// process auth cookies, see <rdar://problem/6772471>
var ret = this.processCookies(appName, config);
// If we have loaded the AIC cookie in addition to the MMI/MMIV cookies,
// then we have properly set up our environment. Any clients that need to
// wait until that has happened can can now be invoked. (See 7066774 for
// details on when we would ignore the cookie.)
this._triggerEnvironmentDidBecomeValidIfAppropriate();
return ret;
},
/**
Process the the mma, lsc, isc, and aic cookies.
@param {String} appName The application name.
@param {Object} config The configuration object for the app.
@returns {Boolean} Returns true if a redirect is needed.
*/
processCookies: function(appName, config) {
// process auth cookies, see <rdar://problem/6772471>
var serviceName = (config[appName] && config[appName].serviceName) || appName;
// e.g. mma-<mail|idisk|gallery|contacts|calendar>
var mma = this.readCookie('mma-' + serviceName);
// e.g. lsc-<mail|idisk|gallery|contacts|calendar>
var lsc = this.readCookie('lsc-' + serviceName);
var isc = this.readCookie('isc-www.me.com') ||
this.readCookie('isc-secure.me.com') ||
this.readCookie('isc-apple.com') || '';
var aic = this.readCookie('aic');
// asc is used for calendar.me.com (AppleID integration)
var asc = this.readCookie('asc--?[0-9]+', true);
// Check for a valid auth cookie
if(mma || isc || lsc || asc) {
this.hasLoginCookie = true;
// gallery and idisk require a username for server requests which
// exists only in the aic cookie
if(appName === 'gallery' || appName === 'idisk') {
if(!aic) {
aic = this.requestAicCookie(mma || isc || lsc, appName);
if(!aic) {
// no aic cookie, required for gallery and idisk, redirect to login page.
this.hasLoginCookie = false;
return true;
}
// else fall through
}
// else fall through
}
// <rdar://problem/7223873> use the aic cookie when being
// redirected from logout
else if(!appName) {
// this space intentionally empty
}
// <rdar://problem/7066774> do not trust aic if we have mma, isc or
// lsc cookies, except for gallery and idisk
else if(appName !== 'calendar') {
this.ignoreAicCookie = true;
}
} else {
this.hasLoginCookie = false;
return true;
}
// Process the Aic cookie if possible to get the username, etc.
if(aic && !this.ignoreAicCookie) {
this.processAicCookie(aic, config);
bootstrap._configs = null;
}
// Process the mmi cookie if needed.
var mmi = this.readCookie('mmi') ;
if(mmi) {
this.processMmiCookie(mmi, config);
}
// Process the mmi cookie if needed.
var mmiv = this.readCookie('mmiv') ;
if(mmiv) {
this.processMmivCookie(mmiv, config);
}
var canContinue = this.checkPreferredLanguage();
return canContinue;
},
/**
Prepare the environment and load the configurations for the apps.
*/
prepareEnv: function() {
// find all configured applications.
var configuredApps = this.configuredApps,
config = this.systemConfig;
for(var key in config) {
if (config.hasOwnProperty(key) && key !== 'global') {
configuredApps.push(key) ;
}
}
var prepareStart = new Date().getTime(), app, i;
for(i=0; i<this.configuredApps.length; i++) {
app = this.configuredApps[i];
this.env[app] = this.configForApplication(app);
this.env[app].name = app;
}
var knownApps = this.knownApps;
for(i=0; i<knownApps.length; i++) {
app = knownApps[i];
if(!this.env[app]) {
this.env[app] = this.configForApplication(app);
this.env[app].name = app;
}
}
//for duplicate names 'photos' and 'gallery'
this.env.photos = this.env.gallery;
//for duplicate names 'idisk' and 'files'
this.env.files = this.env.idisk;
// if Restricted Account, disable all non-enabled apps
if (this.restricted) {
for (app in this.env) {
app = this.env[app];
if (!this.restricted[app.name]) {
this.env[app.name].enabled = false;
}
}
}
// Access your environment by _env.calendar, _env.mail, _env.idisk,
// _env.gallery, _env.idisk, _env.account, etc.
window._env = this.env;
var prepareFinish = new Date().getTime();
this.preparationTime = prepareFinish - prepareStart;
},
/** @private
This function will check whether we have all the information that one would
normally consider a valid environment upon which to base user-specific
logic. If all of this information is in place, then the (public)
environmentDidBecomeValid() callback will be invoked.
The necessary pieces of information are:
* MMI or MMIV cookie (for things like 'classicsyncmigrationstatus')
* AIC cookie (to know user credentials, like the username)
* Extracted user information (so code can call things like bootstrap.getConfig('username'))
*/
_triggerEnvironmentDidBecomeValidIfAppropriate: function() {
var hasMMI = (this.hasProcessedMMICookie || this.hasProcessedMMIVCookie),
hasAIC = (this.isAicProcessed && !this.ignoreAicCookie),
hasExtracted = this.hasExtractedUserInfo,
haveTriggered = this.hasTriggeredEnvironmentDidBecomeValidCallback;
if (hasMMI && hasAIC && hasExtracted && !haveTriggered) {
this.environmentDidBecomeValid();
}
},
/**
This callback will be invoked whenever we have successfully initialized
the environment (configuration information about which applications are
available, etc.) from the AIC cookie. That means that by this point,
things such as the enabled list of applications, the logged-in user's
name, etc., will be available.
The necessary pieces of information are:
* MMI or MMIV cookie (for things like 'classicsyncmigrationstatus')
* AIC cookie (to know user credentials, like the username)
* Extracted user information (so code can call things like bootstrap.getConfig('username'))
If you need to perform work based on those things — such as prompting
Thunderbolt users to upgrade — you can add your tasks to this method.
*/
environmentDidBecomeValid: function() {
// We only ever want to trigger this once!
this.hasTriggeredEnvironmentDidBecomeValidCallback = true;
// Reset the date to show the iCloud Nag. We should do this only after the
// AIC cookie is available, because we need the username for storing the
//date in local storage.
this.resetDateForOptin();
},
/**
Set the active application name.
@param {String} appName The application name.
*/
setActiveApplication: function(appName) {
this.env._active = this.env[appName];
this.markAppAsLastUsed(appName);
},
/**
Mark the application name as last used, so Login will default to it when arriving from https://www.me.com/, https://me.com/, or https://secure.me.com/.
@param {String} appName The application name.
*/
markAppAsLastUsed: function(appName) { //Calling this function marks the last used app in the lua cookie, both immediatly and again onbeforeunload.
var app = ({ account : 0,
mail : 1,
contacts : 2,
calendar : 3,
photos : 4,
gallery : 4,
idisk : 5,
files : 5,
find : 6,
findmyiphone : 6 })[appName] || NaN;
if(isNaN(app)) return;
var setter = function() {
document.cookie = 'lua='+app+';expires='+(new Date(Math.floor(new Date())+365*24*60*60*1000)).toGMTString()+';path=/;domain=.me.com';
};
setter();
if(window.addEventListener) window.addEventListener('beforeunload',setter,false);
},
/**
Returns the configuration for a given app.
@param {String} appName The application name.
@returns {Object} Returns the configuration for a given app.
*/
configForApplication: function(appName) {
if (!this._configs) this._configs = {} ;
var ret = this._configs[appName] ;
if (ret) return ret;
var config = this.systemConfig;
if (!config) return {} ;
var dGlobal = this.defaultGlobalConfig;
var global = config.global;
var app = config[appName] ;
var key ;
ret = {} ;
if(dGlobal) {
for(key in dGlobal) {
if(dGlobal.hasOwnProperty(key)) ret[key] = dGlobal[key];
}
}
if(global) {
for(key in global) {
if(global.hasOwnProperty(key)) ret[key] = global[key];
}
}
if(app && app !== global) {
for(key in app) {
if(app.hasOwnProperty(key)) ret[key] = app[key];
}
}
for(key in ret) {
var value = ret[key];
// THERE ARE LOCALIZATION BUGS. FOR SOME REASON THEY ALL WANT TO RETURN
// LANG = 'en' AND NOT FR OR DE OR JA. FIX IT. THIS IS WHAT YOU WERE
// WORKING ON!
//
// YOU KNOW WHO *YOU* ARE, RIGHT? BECAUSE I SURE DON'T!
if(value && value.chooseLoc===true)
{ret[key] = value[this.systemConfig.global.lang] || value.en || value;}
}
this._configs[appName] = ret ;
return ret ;
},
/**
Returns a specific configuration option for a given app. If no appName,
return the global config if possible.
@param {String} key The key for the configuration option.
@param {String} appName The application name.
@returns {Object} Returns the configuration option for a given key and app.
*/
getConfig: function(key, appName) {
if (appName === undefined) appName = bootstrap.appName || 'global';
var value = this.configForApplication(appName)[key];
if(value === undefined) value = this.configForApplication('global')[key];
return value;
},
/**
If there is no login cookie set, redirect to the login page.
*/
loginIfNeeded: function() {
if (!this.hasLoginCookie) {
// Disable this for now.
// Based on:
// <rdar://problem/8503570> HOTFIX: LocalStorage: Thunderbolt user sees calendars of Savannah user in offline mode
// <rdar://problem/8497661> HOTFIX: Thunderbolt users are being directed to Savannah web
//
// do not require login if offline is supported and has been turned on
// if(this.offlineEnabledAndNotLoggedIn()) {
// console.log('No login cookies found, but app has offline support and it is enabled so let user carry on');
// return false;
// }
this.login();
return true ;
} else return false ;
},
/**
Returns true if offline is supported for this application and user has
turned on localStorage and user has not logged in
@returns {Boolean}
*/
offlineEnabledAndNotLoggedIn: function() {
var ls = window.localStorage;
return (this.getConfig('offlineSupported') && !!ls && !!ls.getItem(this.appName + ':username') && !this.hasLoginCookie);
},
/**
If there is a login URL set, redirect to it.
*/
login: function() {
if(!this.isProduction()) {
return;
}
if(this.loginUrl) {
this.isRedirecting = true;
window.location.href = this.loginUrl;
} else {
console.error('Login URL undefined. Stop.');
}
},
/**
Logout a given application.
@param {String} appName The application name.
*/
logout: function(appName) {
this.deleteCookie('aic');
// also delete asc cookie (from Apple ID)
this.deleteCookie('asc--?[0-9]+', true);
window.location.href = this.logoutUrl;
this.isRedirecting = true;
},
/**
Checks the preferred language based on the URL and cookie.
If needed, redirect to the correct place.
@returns {Boolean} Returns true if the language is correct.
*/
checkPreferredLanguage: function() {
var lang = this.getConfig('lang') ;
if (!lang) return true ; // nothing to do.
lang = lang.toLowerCase();
var loc = window.location;
var urlLang = loc.pathname.toString().match(/\/.+\/(..)\/?/);
urlLang = (urlLang) ? urlLang[1] : null;
if (urlLang !== null && (urlLang != 'en' && urlLang != 'de' &&
urlLang != 'ja' && urlLang != 'fr')) {
urlLang = null;
}
urlLang = (lang == 'en' && urlLang === null) ? 'en' : urlLang;
if (!urlLang && (urlLang !== lang)) {
var pathName = loc.pathname.toString();
var appName = pathName.match(/^\/([^\/]+)/);
appName = (appName) ? appName[1] : 'mail' ;
var subAppName = '/';
if(pathName.match(/\/beta/)) { subAppName = '/beta/'; }
if(pathName.match(/\/upgrade/)) { subAppName = '/upgrade/'; }
if(pathName.match(/\/downgrade/)) { subAppName = '/downgrade/'; }
loc.href = ['/', appName, subAppName, lang, '/', loc.search, loc.hash].join('');
this.isRedirecting = true;
return false ;
} else {
this.systemConfig.global.lang = urlLang || lang;
}
return true ;
},
/**
Process the Aic cookie if needed.
@returns {Boolean} Returns true if the app load should continue.
*/
handleAicCookie: function() {
var shouldContinue = true;
var aic = this.readCookie('aic') ;
var appName = this.appName;
if(aic && !this.isAicProcessed && !this.ignoreAicCookie && appName) {
var config = this.systemConfig || {};
bootstrap.processAicCookie(aic, config);
bootstrap._configs = null;
shouldContinue = this.checkIfEnabled(appName);
// Because the aic cookie contains the list of disabled apps, we
// should invalidate the cached app property if SproutCore has
// bootstrapped by the time we crack the aic cookie.
if (window.Me && Me.dockController) {
Me.dockController.notifyPropertyChange('apps');
}
}
return shouldContinue;
},
/**
Return the first available application that is enabled in the array of knownApps.
@returns {String} appName The enabled app name.
*/
getFirstAvailableApp: function() {
var choices = this.knownApps;
for(var i=0; i<choices.length; i++)
{
if(this.isApplicationEnabled(choices[i])) return choices[i];
}
return undefined;
},
/*
Checks if user has enrolled for beta for a particular application.
@param {String} appName The name of the app. i.e. 'mail' or 'calendar'
@return true if user has been enrolled in a particular application.
*/
isUserEnrolledInBetaForApp: function(appName)
{
var result = false;
// App specific logic goes here.
// For example, Calendar's logic worked like this when it was still in beta:
// if(appName === 'calendar')
// {
// var calstatus = bootstrap.getConfig('calstatus');
// if(calstatus && calstatus >= 1000000000) result = true;
// }
return result;
},
/*
Checks if user should use the legacy version of a particular application.
The check is performed using logic that is specific to each application.
For the case of Calendar, it is the calstatus cookie.
@param {String} appName The name of the app. i.e. 'mail' or 'calendar'
@return true if user should use the legacy version
*/
isLegacyUser: function(appName)
{
var result = false;
if(appName === 'calendar')
{
var calstatus = bootstrap.getConfig('calstatus');
if(calstatus && calstatus < 1000000000) result = true;
}
return result;
},
/**
Returns the fully qualified, localized url for an app.
Example for 'mail': https://www.me.com/mail/beta/en/
Example for 'account': https://secure.me.com/account/fr/
@param {String} appName The application name.
@returns {String} destination url for the appName
*/
getUrlForApp: function(appName) {
var url = this.appUrls[appName],
lang = this.getConfig('lang');
if(lang) lang = lang.toLowerCase();
if(this.isUserEnrolledInBetaForApp(appName)) url += 'beta/';
if(this.isLegacyUser(appName)) url += 'old/';
if(lang !== 'en') url += lang + '/';
return url;
},
/**
Check if the application is enabled.
@param {String} appName The application name.
@returns {Boolean} Returns true if the application is enabled.
*/
checkIfEnabled: function(appName) {
// make sure this application is enabled. If it isn't, redirect to the
// next available app.
var handleRedirect = this.authenticationDisabled ? false : true ;
if (handleRedirect && !this.isApplicationEnabled(appName)) {
var availableAppName = this.getFirstAvailableApp();
var url = this.getUrlForApp(availableAppName);
window.location.href = url;
return false;
}
return true;
},
/**
Returns true if the application is enabled.
@param {String} appName The application name.
@returns {Boolean} Returns true if the application is enabled.
*/
isApplicationEnabled: function(appName) {
var appConfig = this.configForApplication(appName);
return (appConfig && appConfig.enabled);
},
// ..........................................................
// LOCAL STORAGE SUPPORT
//
/** @private
This method should be called before using any of the local storage -related
functions.
*/
setUpLocalStorage: function() {
var browserVersion = parseInt(Browser.version, 10),
versionSafari = parseInt(Browser.versionSafari, 10), // Only if in Safari
myDB, me;
// Implementation note: Most of this code is taken from SC.UserDefaults.
if (Browser.IE && browserVersion == 7) {
// Add user behavior userData. This works in all versions of IE.
// Adding to the body as is the only element never removed.
document.body.addBehavior('#default#userData');
}
else if (Browser.Safari && versionSafari > 523 && versionSafari < 528) {
// Safari 3.1 doesn't support local storage.
this._HTML5DB_noLocalStorage = true;
try {
if (window.openDatabase) {
myDB = openDatabase("mme", "1.0", "MobileMe database", 65536 /* Max size, in bytes */);
}
}
catch(e) {
myDB = null;
}
if (myDB) {
me = this;
myDB.transaction(
function (transaction) {
transaction.executeSql("CREATE TABLE IF NOT EXISTS MMeLocalStorage" + "(key TEXT NOT NULL PRIMARY KEY, value TEXT NOT NULL);",
[], me._safari3KillTransaction, me._safari3NullDataHandler);
}
);
myDB.transaction(
function (transaction) {
transaction.parent = me;
transaction.executeSql("SELECT * from MMeLocalStorage;",
[], function(transaction, results) {
var hash = {},
rows = results.rows,
row;
for(var i=0, iLen=rows.length; i < iLen; ++i) {
row = rows.item(i);
hash[row['key']] = row['value'];
}
transaction.parent._safari3DataHash = hash;
}, me._safari3KillTransaction);
}
);
this._safari3DB = myDB;
}
}
},
/**
Reads the specified key from local storage (or, if this is an older browser,
the nearest equivalent). Will return undefined if the value cannot be
found, there is no local storage support on the current browser, or there
was an error reading the value.
Note that this method is effectively a “poor man’s”
SC.UserDefaults#readDefault, without the namespacing, etc.
@param {String} key
@returns {Object}
*/
getValueFromLocalStorage: function(key) {
// Because Safari 3 writes asynchronously, we keep track of all written
// values. This way, if a client reads immediately after writing, we return
// the expected value.
var writtenValues = this._writtenToLocalStorage,
ret = writtenValues ? writtenValues[key] : undefined,
isIE7 = Browser.IE && Browser.version == 7,
userKeyName, localStorage, storageSafari3;
// If we didn’t find the value inside our “written values” hash, then
// consult the browser.
if (ret === undefined) {
// Attempt to read from localStorage
if (isIE7) {
localStorage = document.body;
try {
localStorage.load("MobileMeLocalStorage");
}
catch(e) {}
}
else if (this._HTML5DB_noLocalStorage) {
storageSafari3 = this._safari3DB;
}
else{
localStorage = window.localStorage ;
if (!localStorage && window.globalStorage) {
localStorage = window.globalStorage[window.location.hostname];
}
}
if (localStorage || storageSafari3) {
if (isIE7) {
ret = localStorage.getAttribute(key.replace(/\W/gi, ''));
}
else if(storageSafari3){
ret = this._safari3DataHash[key];
}
else{
ret = localStorage[key];
}
}
}
return ret;
},
/**
Writes the specified value for the specified key to local storage (or, if
this is an older browser, the nearest equivalent).
Note that this method is effectively a “poor man’s”
SC.UserDefaults#writeDefault, without the namespacing, etc.
@param {String} key
@param {Object} value
*/
setValueInLocalStorage: function(key, value) {
var writtenValues = this._writtenToLocalStorage,
isIE7 = Browser.IE && Browser.version == 7,
userKeyName, localStorage, storageSafari3, me;
// Because Safari 3 writes asynchronously, we keep track of all written
// values. This way, if a client reads immediately after writing, we return
// the expected value.
//
// We’ll also lazily create the hash.
if (!writtenValues) writtenValues = this._writtenToLocalStorage = {};
writtenValues[key] = value;
// Now, actually save the value to local storage.
if (isIE7) {
localStorage = document.body;
}
else if (this._HTML5DB_noLocalStorage) {
storageSafari3 = this._safari3DB;
}
else{
localStorage = window.localStorage ;
if (!localStorage && window.globalStorage) {
localStorage = window.globalStorage[window.location.hostname];
}
}
if (localStorage || storageSafari3) {
if (isIE7) {
try {
localStorage.setAttribute(key.replace(/\W/gi, ''), value);
localStorage.save("MobileMeLocalStorage");
}
catch (e) {}
}
else if (storageSafari3) {
me = this;
storageSafari3.transaction(
function (t) {
t.executeSql("delete from MMeLocalStorage where key = ?", [key],
function (){
t.executeSql("insert into MMeLocalStorage(key, value)"+
" VALUES ('"+key+"', '"+value+"');",
[], me._nullDataHandler, me._killTransaction
);
}
);
}
);
this._safari3DataHash[key] = value;
}
else {
try {
localStorage[key] = value;
}
catch(e) {}
}
}
},
/** @private
Private method to use if the local storage support uses the Safari 3
database.
*/
_safari3KillTransaction: function(transaction, error){
return true; // fatal transaction error
},
/** @private
Private method to use if the local storage support uses the Safari 3
database.
*/
_safari3NullDataHandler: function(transaction, results) {},
/** @private
Properties used when we’re in Safari 3 and have to use its database support
rather than true local storage.
*/
_writtenToLocalStorage: null,
_HTML5DB_noLocalStorage: null,
_safari3DB: null,
_safari3DataHash: null,
/**
Set the date for the iCloud Nag. If the user comes to the Migration app
and then goes back to MobileMe, we don't want to show them the Nag.
So every time the user enters the migration app, the date for optin will be reset,
i.e. the Nag will not be shown for next 30 days on the same un-reseted(is that a word?) browser
*/
resetDateForOptin: function() {
var username, encodedUsername;
//Today's day is recored in the local storage of the browser.
username = bootstrap.getConfig('username');
encodedUsername = SHA1.hex_sha1(username);
bootstrap.setValueInLocalStorage("iCloudOptin:" + encodedUsername, new Date().getTime());
}
};
/**
This is the Calendar (Thunderbolt) end of life notice application.
Call showIfNeeded() and it will show notice IFF:
end of life date - today <= 30 days AND
calStatus is 1234
OR if the app URL has the proper hash code for testing. (ie. #eol=2011,2,1)
(which means that February 1st, 2011 is the end of life date)
@author Peter Bergstrom
@author William Kakes
*/
var CalendarEndOfLifeNotice = {
/**
The end of life date broken up with year, month (1-12), and date (1-31)
components. This may be updated by determineDaysToEOL() if a URL override
is being used.
*/
eolDateHash: {year: 2011, month: 5, day: 5},
/**
The calStatus to check against.
Decided upon in <rdar://problem/8952168> Classic Calendar Sync EOL | Need calstatus value to use to trigger dialog
*/
checkCalStatus: 7000,
/**
Reference to iFrame.
*/
popupFrameElement: null,
/**
The URL to pop up, if we have decided that we need it.
*/
popupFrameURL: null,
/**
Reflects the number of days until the EOL date. If the EOL date is in the
past, this value will be negative. This will be set when you invoke
determineDaysToEOL().
*/
daysToEOL: null,
/**
Reflects the current calstatus, or the overridden value, if a URL override
is being used.
*/
calStatus: null,
/**
Reflects whether the EOL date is being overridden by a hash in the URL.
This will be set to true or false when you invoke determineDaysToEOL().
*/
isUsingURLOverride: null,
/**
Reflects whether or not we have determined whether to show the EOL popup.
Per <rdar://problem/9090528>, we can't make the decision until we have
processed the AIC cookie.
*/
haveDeterminedWhetherToShowPopup: false,
/**
Reflects that the client application has determined that this is a good
time to show the EOL popup (regardless of whether we'll actually show it).
*/
applicationIsReadyToShowPopup: false,
/**
Calculates the number of days between now and the EOL date.
*/
determineDaysToEOL: function() {
// If we've already determined the number of days to EOL, no need to do it
// again.
if (this.daysToEOL !== null) return;
var eolDateHash = this.eolDateHash,
eolDate = new Date(),
calStatus = parseInt(bootstrap.getConfig('calstatus'),10),
hash = window.location.hash,
isUsingOverride = false,
components;
// Dev and QA override. The format is #eol=year,month,day,calstatus
if (hash && hash.indexOf('#eol=') !== -1) {
// split it and extract
components = hash.split(',');
// Here, remove the #eol= before parsing the int.
eolDateHash.year = parseInt(components[0].replace('#eol=', ''),10);
eolDateHash.month = parseInt(components[1],10);
eolDateHash.day = parseInt(components[2],10);
if (components[3]) {
calStatus = parseInt(components[3],10);
}
isUsingOverride = true;
}
eolDate.setFullYear(eolDateHash.year);
// The date object starts the month at 0 instead of 1, just fix it
// now for the sake of human sanity which starts at 1, not 0.
eolDate.setMonth(Math.max(0, eolDateHash.month-1));
eolDate.setDate(eolDateHash.day);
// Update the EOL date and calStatus with the month changes (and
// potentially the URL override).
this.eolDateHash = {
year: eolDate.getFullYear(),
month: eolDate.getMonth(),
day: eolDate.getDate()
};
this.daysToEOL = Math.ceil((eolDate.getTime()-(new Date().getTime()))/86400000);
this.isUsingURLOverride = isUsingOverride;
this.calStatus = calStatus;
},
/**
Show the EOL popup notice if needed as described above.
*/
showIfNeeded: function() {
// First off, determine the the number of days to EOL, if we haven't
// already.
this.determineDaysToEOL();
var calendarIsEnabled = bootstrap.isApplicationEnabled('calendar'),
isNOTCalendar = window.location.href.indexOf('calendar') === -1,
calStatus = this.calStatus,
checkCalStatus = this.checkCalStatus,
daysToEOL = this.daysToEOL,
eolDateHash = this.eolDateHash;
// NOTE: THIS IMPLEMENTATION IS DIFFERENT FOR THE 1.0 APPS THAN FOR THE
// 0.9 APPS. (Because Thunderbolt is a 0.9 app.)
if ((calStatus === checkCalStatus) && calendarIsEnabled && isNOTCalendar && (daysToEOL <= 30)) {
this.initPopupFrame('/calendar/eol/', ('eol=' + eolDateHash.year + ',' + eolDateHash.month + ',' + eolDateHash.day + ',' + daysToEOL));
}
this.haveDeterminedWhetherToShowPopup = true;
// If the application has already said that it's ready to show it, show it
// immediately.
if (this.applicationIsReadyToShowPopup) this.showPopupIfAppropriate();
},
/*
Configures the url to be used for the inline popup frame that is displayed
once the app initializes.
*/
initPopupFrame: function(url, hash)
{
url = url || "about:blank";
if(url.match('^/'))
{
url = window.location.protocol + '//' + window.location.hostname + url;
var newlanguage = bootstrap.getConfig('lang') || 'en';
if(newlanguage !== 'en') url += newlanguage + '/';
url += '#'+hash;
this.popupFrameURL = url;
}
},
/**
If there is a popupFrameURL, then call this to show the popup from main.js
*/
showPopupIfAppropriate: function() {
// We can't show the popup until we've detemined whether we should. We
// assume that whoever called this method is doing so when their
// application is sufficiently loaded.
this.applicationIsReadyToShowPopup = true;
if (!this.haveDeterminedWhetherToShowPopup) return;
var url = this.popupFrameURL;
if (!url) return;
var el = document.createElement('iframe');
el.border = 0;
el.name = 'popupPage';
el.id = 'popupPage';
el.frameBorder = '0';
el.allowTransparency = 'true'; // IE specific way to do transparency without side effects
el.src = url;
// The desire is to set the height to 100% once the iframe has loaded.
// Unfortunately, IE (including IE8) does not fire 'onload' for iframe
// elements without a bit of legwork. This is rdar://problem/9090492
var callback = function() {
// On load, then show the full height of the iframe.
el.style.height = '100%';
};
if (el.attachEvent){
el.attachEvent("onload", callback);
}
else {
el.onload = callback;
}
document.body.appendChild(el);
this.popupFrameElement = el;
},
/**
Hide and forward to the upgrade app.
*/
forwardToUpgrade: function() {
this.hide();
window.location.hash = '';
this.navigateToPage('/calendar/upgrade/');
},
/*
Hides currently displayed inline popup frame if it is showing.
@return void
*/
hide: function()
{
var el = this.popupFrameElement;
if(el)
{
document.body.removeChild(el);
this.popupFrameElement = null;
}
},
/*
Redirects the window to the specified url, if it is different from current url.
Normalizes url for language. Existing hash is appended to destination url.
@return true if redirecting, false otherwise
*/
navigateToPage: function(url)
{
var result = false;
var currentUrl = window.location.pathname;
if(url && currentUrl.indexOf(url) === -1)
{
url = window.location.protocol + '//' + window.location.hostname + url;
if(!url.match(/\/$/)) url += '/';
var newlanguage = bootstrap.getConfig('lang') || 'en';
if(newlanguage !== 'en') url += newlanguage + '/';
// Do not include the hash in this case!
// <rdar://problem/8974817> Thunderbolt EOL: IE8: Error on clicking 'Upgrade Calendar' button
//url += window.location.hash;
bootstrap.isRedirecting = true;
window.location.href = url;
result = true;
}
return result;
}
};
// ..........................................................
// SHA1
//
/*
* A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
* in FIPS 180-1
* Version 2.2 Copyright Paul Johnston 2000 - 2009.
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for details.
Note: This version has the unused base64 variants removed for download size.
*/
/*global SHA1*/
SHA1 = {
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
hexcase: 1, /* hex output format. 0 - lowercase; 1 - uppercase */
b64pad: '', /* base-64 pad character. "=" for strict RFC compliance */
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
hex_sha1: function(s) {
return this.rstr2hex(this.rstr_sha1(this.str2rstr_utf8(s)));
},
/*
* Calculate the SHA1 of a raw string
*/
rstr_sha1: function(s) {
return this.binb2rstr(this.binb_sha1(this.rstr2binb(s), s.length * 8));
},
/*
* Convert a raw string to a hex string
*/
rstr2hex: function(input) {
var hex_tab = this.hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var output = "";
var x;
for(var i = 0; i < input.length; i++)
{
x = input.charCodeAt(i);
output += hex_tab.charAt((x >>> 4) & 0x0F)
+ hex_tab.charAt( x & 0x0F);
}
return output;
},
/*
* Encode a string as utf-8.
* For efficiency, this assumes the input is valid utf-16.
*/
str2rstr_utf8: function(input) {
var output = "";
var i = -1;
var x, y;
while(++i < input.length)
{
/* Decode utf-16 surrogate pairs */
x = input.charCodeAt(i);
y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
{
x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
i++;
}
/* Encode output as utf-8 */
if(x <= 0x7F) {
output += String.fromCharCode(x);
} else if(x <= 0x7FF) {
output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
0x80 | ( x & 0x3F));
} else if(x <= 0xFFFF) {
output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
0x80 | ((x >>> 6 ) & 0x3F),
0x80 | ( x & 0x3F));
} else if(x <= 0x1FFFFF) {
output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
0x80 | ((x >>> 12) & 0x3F),
0x80 | ((x >>> 6 ) & 0x3F),
0x80 | ( x & 0x3F));
}
}
return output;
},
/*
* Convert a raw string to an array of big-endian words
* Characters >255 have their high-byte silently ignored.
*/
rstr2binb: function(input) {
var i ;
var output = Array(input.length >> 2);
for(i = 0; i < output.length; i++) {
output[i] = 0;
}
for(i = 0; i < input.length * 8; i += 8) {
output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32);
}
return output;
},
/*
* Convert an array of big-endian words to a string
*/
binb2rstr: function(input) {
var output = "";
for(var i = 0; i < input.length * 32; i += 8) {
output += String.fromCharCode((input[i>>5] >>> (24 - i % 32)) & 0xFF);
}
return output;
},
/*
* Calculate the SHA-1 of an array of big-endian words, and a bit length
*/
binb_sha1: function(x, len) {
/* append padding */
x[len >> 5] |= 0x80 << (24 - len % 32);
x[((len + 64 >> 9) << 4) + 15] = len;
var w = Array(80);
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
var e = -1009589776;
for(var i = 0; i < x.length; i += 16)
{
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
var olde = e;
for(var j = 0; j < 80; j++)
{
if(j < 16) w[j] = x[i + j];
else w[j] = this.bit_rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
var t = this.safe_add(this.safe_add(this.bit_rol(a, 5),
this.sha1_ft(j, b, c, d)),
this.safe_add(this.safe_add(e, w[j]),
this.sha1_kt(j)));
e = d;
d = c;
c = this.bit_rol(b, 30);
b = a;
a = t;
}
a = this.safe_add(a, olda);
b = this.safe_add(b, oldb);
c = this.safe_add(c, oldc);
d = this.safe_add(d, oldd);
e = this.safe_add(e, olde);
}
return Array(a, b, c, d, e);
},
/*
* Perform the appropriate triplet combination function for the current
* iteration
*/
sha1_ft: function(t, b, c, d) {
if(t < 20) return (b & c) | ((~b) & d);
if(t < 40) return b ^ c ^ d;
if(t < 60) return (b & c) | (b & d) | (c & d);
return b ^ c ^ d;
},
/*
* Determine the appropriate additive constant for the current iteration
*/
sha1_kt: function(t) {
return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
(t < 60) ? -1894007588 : -899497514;
},
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
safe_add: function(x, y) {
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
},
/*
* Bitwise rotate a 32-bit number to the left.
*/
bit_rol: function(num, cnt) {
return (num << cnt) | (num >>> (32 - cnt));
}
};
// ..........................................................
// End of SHA1
//
/****************************************************************************
* END
* BOOTSTRAP HANDLER
*****************************************************************************/
// Check if this is a redirect to iCloud.com
if (window.location.href.indexOf('redirectToiCloud') !== -1) {
var param = window.location.search;
var userName = "";
if(param.indexOf('=') != -1) {
userName = param.substring(param.indexOf('=') + 1);
userName = userName? "/#/username="+userName : "";
}
window.location.href = "https://www.icloud.com" + userName;
}
// Run the bootstrapper setup!!
if(bootstrap.setup()) {
bootstrap.loginIfNeeded();
if(!bootstrap.isRedirecting) {
CalendarEndOfLifeNotice.showIfNeeded();
}
}
</script>
<script type="text/javascript">
if (SC.setupBodyClassNames) SC.setupBodyClassNames() ;
</script>
<script type="text/javascript" src="/my/move/en-us/1F17/javascript-packed.js"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment