Inital commit

This commit is contained in:
Tilman
2011-10-26 09:44:02 +02:00
commit 5971f4b417
1813 changed files with 737837 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
AuthSub Java Sample - README.txt
--------------------------------
The AuthSub sample is a simple Tomcat web-application that demonstrates usage
of the Google Authentication Proxy Interface, AuthSub, to access a user's
private Calendar feed. AuthSub enables a web-application to access a user's
data without ever handling the user's account login information. The provided
sample will retrieve an AuthSub token for the current user, authenticate to
Calendar using the AuthSub token, and retrieve the private calendar feed.
Overview of the sample
----------------------
The sample consists of two main parts. The Java files in src/ handle the
retrieval, (basic) storage, and usage of the AuthSub token for authentication.
The javascript files in the web/ directory handle the parsing and rendering of
the retrieved Calendar data.
The Java files mainly use the com.google.gdata.client.http.AuthSubUtil utility
class to interface with AuthSub. LoginServlet.java forms the URL to redirect
to the Google Accounts page to request an AuthSub token.
HandleTokenServlet.java handles the retrieval of the AuthSub token when Google
Accounts redirects back to the web application. RetrieveFeedServlet.java is a
simple proxy to retrieve the requested feed using the AuthSub token for
authentication. (The proxy is a solution for the same origin policy of
Javascript).
Deploying the sample web application
------------------------------------
The sample web application can be easily deployed on a running instance of
Tomcat. The dist/authsub_sample.war web archive file can just be dropped into
the webapps/ directory of your Tomcat installation. Tomcat should
automatically deploy the web application. The application should be accessible
at the '/authsub_sample' path of your Tomcat server
(eg. 'http://localhost:8080/authsub_sample').
Note: If the application needs to be rebuilt, the servlet.jar property defined
in the build-samples/build.properties file should be updated to point to the
jar file with the servlet definitions.
Securing against URL command attacks
------------------------------------
Due to the same origin policy of Javascript, a basic proxy is required to
retrieve the private Calendar feed. (The proxy's functionality is implemented
in src/RetrieveFeedServlet.java.) The requested URL is specified as a query
parameter. The proxy will use the AuthSub token of the respective user to
retrieve the feed at the provided URL. The proxy also takes a token as another
query parameter to ensure that the request is from an authorized host.
A URL command attack is a cross site scripting vulnerability. It is applicable
when cookies are used for authentication and user commands are done through
HTTP requests (GET or POST). This attack is illustrated through the following
example. Assume goodsite.com and evilsite.com. Assume goodsite.com offers a URL
of the following form http://goodsite.com/addtophonebook?email=hello@gmail.com
that adds hello@gmail.com to the current logged in user's phonebook.
An attack may look like this:
1. User logs into goodsite.com and gets an login cookie
2. User goes to evilsite.com
3. On evilsite.com there is an HTML element with a URL source to some
command for goodsite.com (perhaps through an invisible image) that accesses
http://goodsite.com/addtophonebook?email=viagra@spam.com
4. The browser will fetch the URL and the login cookie will go along with
it
5. The user's account is now modified by an evil site
Thus to protect the GData URL proxy, we require requests to the GData proxy
servlet to contain a secure token that is a hash of the user's login cookie
issued by the web service and the URL to be accessed. Since evilsite.com does
not have access to the login cookie, it will not be able to generate a valid
secure token for this user.
The token used to secure the URL has the following format:
token = HMAC<sub>SHA1(login-cookie)</sub>({data})
where {data} is a string formed in the following manner:
data = http-url SP http-method SP timestamp
SP is a single ASCII space character.
http-url is the GData feed URl being requested.
timestamp is an integer representing the time
the request was sent, in seconds since Jan 1, 1970 UTC,
formatted as an ASCII string (in decimal).
The token used in the URL will be Base64 encoded. The above functionality is
implemented in src/SecureUrl.java. The token is checked in
src/RetrieveFeedServlet.java and generated through JSP in web/main.jsp.
Limitations
-----------
The sample shows the basic structure of how a server that uses AuthSub should
be setup. The current sample may not work well with secure AuthSub with
signatures if the Calendar server sends redirects with the gsessionid in the
URL. In this case, the client should handle the 302, recalculate the signature
for the new URL, and then issue the request.
Binary file not shown.
@@ -0,0 +1,119 @@
/* Copyright (c) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.authsub.src;
import com.google.gdata.client.http.AuthSubUtil;
import com.google.gdata.util.AuthenticationException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Handles the processing of an AuthSub token.
* <p>
* The user will login to the Google account and lend permission for
* this service to impersonate the user. Upon completion of the login
* and permission-grant, the user will be redirected to this servlet
* with the token in the URL.
*
*
*/
public class HandleTokenServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// Retrieve the AuthSub token assigned by Google
String token = AuthSubUtil.getTokenFromReply(req.getQueryString());
if (token == null) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST,
"No token specified.");
return;
}
// Exchange the token for a session token
String sessionToken;
try {
sessionToken =
AuthSubUtil.exchangeForSessionToken(token,
Utility.getPrivateKey());
} catch (IOException e1) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Exception retrieving session token.");
return;
} catch (GeneralSecurityException e1) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Security error while retrieving session token.");
return;
} catch (AuthenticationException e) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Server rejected one time use token.");
return;
}
try {
// Sanity checking usability of token
Map<String, String> info =
AuthSubUtil.getTokenInfo(sessionToken, Utility.getPrivateKey());
for (Iterator<String> iter = info.keySet().iterator(); iter.hasNext();) {
String key = iter.next();
System.out.println("\t(key, value): (" + key + ", " + info.get(key)
+ ")");
}
} catch (IOException e1) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Exception retrieving info for session token.");
return;
} catch (GeneralSecurityException e1) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Security error while retrieving session token info.");
return;
} catch (AuthenticationException e) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Auth error retrieving info for session token: " +
e.getMessage());
return;
}
// Retrieve the authentication cookie to identify user
String principal =
Utility.getCookieValueWithName(req.getCookies(), Utility.LOGIN_COOKIE_NAME);
if (principal == null) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Unidentified principal.");
return;
}
// Store the token
TokenManager.storeToken(principal, sessionToken);
// Redirect to main.jsp where the token will be used
StringBuffer continueUrl = req.getRequestURL();
int index = continueUrl.lastIndexOf("/");
continueUrl.delete(index, continueUrl.length());
continueUrl.append(LoginServlet.NEXT_URL);
resp.sendRedirect(continueUrl.toString());
}
}
@@ -0,0 +1,134 @@
/* Copyright (c) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.authsub.src;
import com.google.gdata.client.calendar.CalendarService;
import com.google.gdata.client.http.AuthSubUtil;
import com.google.gdata.util.common.util.Base64;
import java.io.IOException;
import java.security.SecureRandom;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Logging into this application is trivial and just consists of visiting
* this servlet. This servlet will set an authentication cookie for the
* user and redirect to the page to authorize to Google. Typically, the
* user will be authenticated to the server via the login cookie prior to
* accessing this servlet.
*
*
*/
public class LoginServlet extends HttpServlet {
// On successfully acquiring a token, the servlet will redirect the user to
// the following next URL
/*package*/ static final String NEXT_URL = "/main.jsp";
private static final SecureRandom srng = new SecureRandom();
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// State to indicate if the user parameters have changed.
boolean stateChanged = false;
// Retrieve user specified hosted domain.
boolean useDefaultDomain =
Boolean.parseBoolean(req.getParameter("defaultdomain"));
boolean useSsl =
Boolean.parseBoolean(req.getParameter("secure"));
String userDomain = req.getParameter("domain");
// Save any persistent data to user session
String calendarFeedRootUrl = CalendarService.CALENDAR_ROOT_URL;
if (useSsl && !calendarFeedRootUrl.startsWith("https")) {
calendarFeedRootUrl = calendarFeedRootUrl.replaceFirst("http", "https");
}
HttpSession userSession = req.getSession(true);
String currentRootUrl =
(String) userSession.getAttribute("feedRootUrl");
String currentDomain =
(String) userSession.getAttribute("userDomain");
if ((currentRootUrl != null && !currentRootUrl.equals(calendarFeedRootUrl))
|| (currentDomain != null && !currentDomain.equals(userDomain))) {
stateChanged = true;
}
userSession.putValue("feedRootUrl", calendarFeedRootUrl);
userSession.putValue("userDomain", userDomain);
String authSubToken = null;
String principal =
Utility.getCookieValueWithName(req.getCookies(), Utility.LOGIN_COOKIE_NAME);
if (!stateChanged && (principal != null)) {
authSubToken = TokenManager.retrieveToken(principal);
}
// Form continue URL
StringBuffer continueUrl = req.getRequestURL();
int index = continueUrl.lastIndexOf("/");
continueUrl.delete(index, continueUrl.length());
// If the user doesn't have an AuthSub token yet, redirect the user to
// the Google page to request an AuthSub token. Otherwise redirect to
// the main page
if (authSubToken == null) {
continueUrl.append("/HandleTokenServlet");
// Check whether to use https for authentication
boolean secure = (Utility.getPrivateKey() != null);
String authSubLogin;
if (useDefaultDomain) {
authSubLogin = AuthSubUtil.getRequestUrl(continueUrl.toString(),
calendarFeedRootUrl,
secure,
true /*session*/);
} else {
authSubLogin = AuthSubUtil.getRequestUrl(req.getParameter("domain"),
continueUrl.toString(),
calendarFeedRootUrl,
secure,
true /*session*/);
}
// Set "authentication" cookie. Typically, a user would have an
// login-cookie for the web service which should be associated to the
// AuthSub token retrieved from Google. For this example, a random
// authentication cookie is assigned.
byte[] randomBytes = new byte[12];
srng.nextBytes(randomBytes);
String cookieValue = Base64.encodeWebSafe(randomBytes, false);
resp.addCookie(new Cookie(Utility.LOGIN_COOKIE_NAME, cookieValue));
resp.sendRedirect(authSubLogin);
} else {
continueUrl.append(NEXT_URL);
resp.sendRedirect(continueUrl.toString());
}
}
}
@@ -0,0 +1,251 @@
/* Copyright (c) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.authsub.src;
import com.google.gdata.client.http.AuthSubUtil;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.security.GeneralSecurityException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Acts as a proxy and retrieves the requested feed.
* <p>
* The identity of the user is determined using the provided authentication
* cookie. The authentication cookie will be mapped to the associated Google
* AuthSub token. The token will be used to retrieve the feed.
* <p>
* Special care must be taken to ensure the following:
* - the requested feed belongs to a pre-specified approved list
* - since cookies are used and GData URLs aren't protected, the URL being
* requested by the client should contain a secure hash. This is primarily
* required for POST/PUT/DELETE but shown in the GET example below as an
* example.
*
*
*/
public class RetrieveFeedServlet extends HttpServlet {
private static String[] acceptedFeedPrefixList = {
"http://www.google.com/calendar/feeds",
"https://www.google.com/calendar/feeds"
};
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// Retrieve the authentication cookie to identify user
String principal =
Utility.getCookieValueWithName(req.getCookies(), Utility.LOGIN_COOKIE_NAME);
if (principal == null) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Unidentified principal.");
return;
}
// Check that the user has an AuthSub token
String authSubToken = TokenManager.retrieveToken(principal);
if (authSubToken == null) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST,
"User isn't authorized through AuthSub.");
return;
}
// If no query string, complain
if (req.getQueryString() == null) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Query string is required.");
return;
}
// Parse the query parameters
String queryString = URLDecoder.decode(req.getQueryString(), "UTF-8");
Map<String,String> queryParams = Utility.parseQueryString(queryString);
String queryUri = queryParams.get("href");
String token = queryParams.get("token");
String timestamp = queryParams.get("timestamp");
if ((queryUri == null) || (token == null) || (timestamp == null)) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Missing a query parameter.");
return;
}
// Verify the feed by checking that it's from a known list of feeds and
// that the secure token hasn't expired and is valid.
if (!verifyFeedRequest(principal, queryUri, token, timestamp, "GET")) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Request failed validation.");
return;
}
// Handle a GET request
handleGetRequest(req, resp, queryUri, authSubToken);
}
/**
* Handles a GET request by issuing a GET to the requested feed with the
* AuthSub token attached in a header. The output from the server will
* be proxied back to the requestor.
* POST/PUT/DELETE can be handled in a similar manner except that the XML
* sent as part of the request should be sent to the server.
*/
private void handleGetRequest(HttpServletRequest req,
HttpServletResponse resp,
String queryUri,
String authSubToken)
throws ServletException, IOException {
HttpURLConnection connection = null;
try {
connection = openConnectionFollowRedirects(queryUri, authSubToken);
} catch (GeneralSecurityException e) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Error creating authSub header.");
return;
} catch (MalformedURLException e) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Malformed URL - " + e.getMessage());
return;
} catch (IOException e) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST,
"IOException - " + e.getMessage());
return;
}
int respCode = connection.getResponseCode();
// Handle error from remote server
if (respCode != HttpServletResponse.SC_OK) {
Map<String, List<String>> headers = connection.getHeaderFields();
StringBuffer errorMessage = new StringBuffer(
"Failed to retrive calendar feed from: ");
errorMessage.append(queryUri);
errorMessage.append(".\nServer Error Response:\n");
errorMessage.append(connection.getResponseMessage());
for (Iterator<String> iter = headers.keySet().iterator() ;
iter.hasNext();) {
String header = iter.next();
List<String> headerValues = headers.get(header);
for (Iterator<String> headerIter = headerValues.iterator() ;
headerIter.hasNext(); ) {
String headerVal = headerIter.next();
errorMessage.append(header + ": " + headerVal + ", ");
}
}
resp.sendError(respCode, errorMessage.toString());
return;
}
// Handle success reply from remote server
try {
BufferedReader reader =
new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while((line = reader.readLine()) != null) {
resp.getWriter().write(line);
}
} catch(IOException e) {
// Ignore
}
}
/**
* Open a HTTP connection to the provided URL with the AuthSub token specified
* in the header. Follow redirects returned by the server - a new AuthSub
* signature will be computed for each of the redirected-to URLs.
*/
private HttpURLConnection openConnectionFollowRedirects(String urlStr,
String authSubToken)
throws MalformedURLException, GeneralSecurityException, IOException {
boolean redirectsDone = false;
HttpURLConnection connection = null;
while (!redirectsDone) {
URL url = new URL(urlStr);
// Open connection to requested feed
connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
// Form AuthSub authentication header
String authHeader = null;
authHeader = AuthSubUtil.formAuthorizationHeader(authSubToken,
Utility.getPrivateKey(),
url,
"GET");
connection.setRequestProperty("Authorization", authHeader);
connection.setInstanceFollowRedirects(false);
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_MOVED_PERM ||
responseCode == HttpURLConnection.HTTP_MOVED_TEMP) {
urlStr = connection.getHeaderField("Location");
// If "Location" is not specified, stop following redirects, and
// propagate error to the client of the proxy
if (urlStr == null) {
redirectsDone = true;
}
} else {
redirectsDone = true;
}
}
return connection;
}
/**
* Verifies the request for a feed by:
* a. validating that the request belongs to a known list of feeds
* b. validating the token (to protect against url command attacks)
*<p>
* This verification is in order to prevent the proxy from URL command attacks
* which is a cross site scripting problem.
*/
private boolean verifyFeedRequest(String cookie,
String feed,
String token,
String timestamp,
String method) {
// Check the list of accepted feed URLs that we are proxying
int url_i;
for (url_i = 0; url_i < acceptedFeedPrefixList.length; url_i++) {
if(feed.toLowerCase().startsWith(
acceptedFeedPrefixList[url_i].toLowerCase())) {
break;
}
}
if (url_i == acceptedFeedPrefixList.length) {
return false;
}
return SecureUrl.isTokenValid(token, cookie, feed, method, timestamp);
}
}
@@ -0,0 +1,161 @@
/* Copyright (c) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.authsub.src;
import com.google.gdata.util.common.util.Base64;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.MessageDigest;
import java.util.Date;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
/**
* Generates and verifies tokens to protect URLs.
*
* The URL command attack is a cross site scripting vulnerability. It
* is applicable when cookies are used for authentication and user commands
* are done through HTTP requests (GET or POST).
* This attack is illustrated through the following
* example. Assume goodsite.com and evilsite.com. Assume goodsite.com offers a
* URL of the following form
* http://goodsite.com/addtophonebook?email=hello@gmail.com that adds
* hello@gmail.com to the current logged in user's phonebook.
* An attack may look like this:
* <p>
* 1. User logs into goodsite.com and gets an login cookie
* 2. User goes to evilsite.com
* 3. On evilsite.com there is an HTML element with a URL source to some
* command for goodsite.com (Perhaps through an invisible image).
* 4. The browser will fetch the URL and the login cookie will go along with
* it
* 5. The user's account is now modified by an evil site
* <p>
* This can happen when URL's aren't protected.
* eg. http://goodsite.com/add=temp@gmail.com
* <p>
* Thus to protect the GData URL proxy, we require requests to the GData proxy
* servlet to contain a secure token that is a hash of the user's login cookie
* issued by the web service and the URL to be accessed. Since evilsite.com
* does not have access to the login cookie, it will not be able to generate a
* valid secure token for this user.
* <p>
* The token used to secure the URL has the following format:
* token = HMAC<sub>SHA1(login-cookie)</sub>({data})
* <p>
* where <code>{data}</code> is a string formed in the following manner:
* data = http-url SP http-method SP timestamp
* <code>SP</code> is a single ASCII space character.
* <code>http-url</code> is the GData feed URl being requested.
* <code>timestamp</code> is an integer representing the time
* the request was sent, in seconds since Jan 1, 1970 UTC,
* formatted as an ASCII string (in decimal).
* <p>
* The token used in the URL will be Base64 encoded.
*
*
*/
public class SecureUrl {
private static final int TOKEN_LIFE_SECONDS = 30 * 60;
/**
* Generates a secure token to be used to protect a URL.
*
* @param cookie the authentication cookie of the user
* @param url the URL to protect
* @param method the HTTP method used against the URL
* @param currentTimeSecs the current time in seconds
* @return the secure token
*/
public static String generateToken(String cookie,
String url,
String method,
long currentTimeSecs) {
// Form the data to be HMAC'ed
String data = url + " " + method + " " + currentTimeSecs;
// Compute SHA1-HMAC
byte[] hmac;
try {
hmac = computeSHA1HMac(data, cookie);
} catch (GeneralSecurityException e) {
throw new RuntimeException("Security exception - " + e.getMessage());
}
return Base64.encodeWebSafe(hmac, true);
}
/**
* Checks the validity of the secure token to ensure that it was not created
* by a malicious third party.
*
* @param token the token to verify
* @param cookie the cookie of the user
* @param url the URL requested
* @param method the HTTP method used to request the URL
* @param timestamp the timestamp of the token
*/
public static boolean isTokenValid(String token,
String cookie,
String url,
String method,
String timestamp) {
long createTime = Long.parseLong(timestamp);
long currentTime = (new Date()).getTime() / 1000;
if ((currentTime - createTime) > TOKEN_LIFE_SECONDS) {
return false;
}
String data = url + " " + method + " " + createTime;
byte[] hmac;
try{
hmac = computeSHA1HMac(data, cookie);
} catch (GeneralSecurityException e) {
throw new RuntimeException("Security exception - " + e.getMessage());
}
String hmacEnc = Base64.encodeWebSafe(hmac, true);
return hmacEnc.equals(token);
}
/**
* Computes a SHA1-HMAC. A SHA1 hash of the cookie is used as the key to
* MAC the data.
*
* @param data the data to MAC
* @param cookie the authentication cookie of the user
* @return the SHA1-HMAC of the data given the cookie
*/
public static byte[] computeSHA1HMac(String data,
String cookie)
throws GeneralSecurityException {
// Compute SHA-1 hash of the cookie
byte[] hash;
MessageDigest digest = MessageDigest.getInstance("SHA-1");
hash = digest.digest(cookie.getBytes());
// Compute the HMAC of the data using hash(cookie) as the key
byte[] hmacResult;
Mac hm;
hm = Mac.getInstance("HMacSHA1");
Key k1 = new SecretKeySpec(hash, 0, hash.length, "HMacSHA1");
hm.init(k1);
return hm.doFinal(data.getBytes());
}
}
@@ -0,0 +1,52 @@
/* Copyright (c) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.authsub.src;
import java.util.Hashtable;
/**
* Manages and stores AuthSub tokens.
*
* The TokenManager currently just uses a Hashtable to store the tokens
* in-memory. This is just for the purposes of the sample. Ideally, the
* token should be stored in a database with the same security restrictions
* as other sensitive material like user passwords. Google limits the
* number of AuthSub tokens generated per user per target and thus the tokens
* have to be stored permanently and reused.
*
*
*/
public class TokenManager {
private static Hashtable<String, String> tokenMap;
static {
tokenMap = new Hashtable<String, String>();
}
public static synchronized void storeToken(String principal, String token) {
tokenMap.put(principal, token);
}
public static synchronized String retrieveToken(String principal) {
return tokenMap.get(principal);
}
public static synchronized void removeToken(String principal) {
tokenMap.remove(principal);
}
}
+115
View File
@@ -0,0 +1,115 @@
/* Copyright (c) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.authsub.src;
import java.security.PrivateKey;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.Cookie;
/**
* A class with a set of utility functions.
*
*
*/
public class Utility {
public static final String LOGIN_COOKIE_NAME = "AuthSubSampleCookie";
private static PrivateKey privateKey;
// Make class uninstantiable
private Utility() {
}
/**
* Retrieves the value of the cookie with the corresponding name.
*/
public static String getCookieValueWithName(Cookie[] cookies, String name) {
for (int cookie_i = 0; cookie_i < cookies.length; cookie_i++) {
if (cookies[cookie_i].getName().equals(name)) {
return cookies[cookie_i].getValue();
}
}
return null;
}
/**
* Parse a query string using the '&' character as the delimiter. Handles
* quoted values and is thus different than HttpUtils.parseQueryString.
*/
public static Map<String, String> parseQueryString(String queryString) {
StringBuilder query = new StringBuilder(queryString);
Map<String,String> hash = new HashMap<String,String>();
while (query.length() > 0) {
char c = query.charAt(0);
if (c == '&') {
query.deleteCharAt(0);
continue;
}
int equals = query.indexOf("=");
String key = query.substring(0, equals);
String value;
query.delete(0, equals + 1);
if (query.charAt(0) == '\"') {
int nextQuote = query.indexOf("\"", 1);
if (nextQuote == -1) {
// mismatched quotes -- try to be accommodating
value = query.toString();
query.delete(0, query.length());
} else {
value = query.substring(1, nextQuote);
query.delete(0, nextQuote + 1);
}
} else {
int ampIndex = query.indexOf("&");
if (ampIndex == -1) {
ampIndex = query.length();
}
value = query.substring(0, ampIndex);
query.delete(0, ampIndex);
}
hash.put(key, value);
}
return hash;
}
/**
* Return the private key to use to sign the AuthSub request.
*
* Note: Currently configured to use AuthSub without signatures. Uncomment
* the function below if AuthSub requests need to be signed.
*/
public static PrivateKey getPrivateKey() {
/**
* if (privateKey == null) {
* try {
* privateKey = AuthSubUtil.
* getPrivateKeyFromKeystore("/usr/etc/gdata_keys/AuthSub.jks",
* "passwd",
* "AuthSubAliasName",
* "passwd");
* } catch (Exception e) {
* throw new RuntimeException("Error reading from keystore file - ", e);
* }
* }
*/
return privateKey;
}
}
Binary file not shown.
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>AuthSub Demo Application</display-name>
<description>
This is a simple web application that demonstrates AuthSub
usage.
</description>
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>sample.authsub.src.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/LoginServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>HandleTokenServlet</servlet-name>
<servlet-class>sample.authsub.src.HandleTokenServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HandleTokenServlet</servlet-name>
<url-pattern>/HandleTokenServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>RetrieveFeedServlet</servlet-name>
<servlet-class>sample.authsub.src.RetrieveFeedServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RetrieveFeedServlet</servlet-name>
<url-pattern>/RetrieveFeedServlet</url-pattern>
</servlet-mapping>
</web-app>
@@ -0,0 +1,101 @@
/* The main chrome containing the calendar is spaced this far from the side */
.datePickerDiv {
background: #c3d9ff;
padding: 0px 0px 9px 9px;
line-height: 1em;
}
.DP_monthtable {
width: 100%;
background: #fff;
padding: 0px;
border-bottom: 1px #A2BBDD solid;
font-size: 83%;
}
.DP_monthtable TD {
text-align: center;
padding: 2px;
font-family: Verdana;
font-size: 85%;
}
.DP_heading {
cursor: pointer;
background: rgb(195, 217, 255);
color: #112ABB;
vertical-align: middle;
}
.DP_days {
background: rgb(195, 217, 255);
}
.DP_dayh {
cursor: default;
font-size: 78%;
}
.DP_cur {
font:bold 78%/1em Verdana,Sans-serif;
padding-bottom: 4px;
text-align: center;
}
.DP_prev, .DP_next {
font-size: 125%;
padding-bottom: 6px;
cursor: pointer;
}
.DP_prev { text-align: right; }
.DP_next { text-align: left; }
/* today */
.DP_today {
background : #9ab !important;
border: 1px solid !important;
border-color: #567 #abc #abc #567 !important;
color: #fff;
}
.DP_today_selected {
background : #579 !important;
border: 1px solid !important;
border-color: #246 #9bd #9bd #246 !important;
color: #fff;
}
/* weekday is Gmail blue when selected */
.DP_weekday {
background: rgb(255, 255, 255);
}
.DP_weekday_selected {
background: rgb(170, 204, 238);
}
/* weekend goes from gray to dark blue when selected */
.DP_weekend {
background: #E8EEF7;
}
.DP_weekend_selected {
background: rgb(153, 187, 221);
}
.DP_onmonth {
}
.DP_offmonth {
color: #888;
}
.DP_day_top {
border-top: 1px #A2BBDD solid;
}
.DP_day_right {
border-right: 1px #A2BBDD solid;
}
.DP_day_left {
border-left: 1px #A2BBDD solid;
}
@@ -0,0 +1,178 @@
body {
font-family: arial, sans-serif;
font-size: 83%;
}
.leftcontent {
float:left;
width:175px;
background:#fff;
}
.topimage {
margin-left:13px;
margin-top:0px;
padding:0px;
}
.footerimage {
margin-left:15px;
margin-top:20px;
padding:0px;
}
.toc {
color: #ddad08;
font-weight:bold;
}
.rightcontent {
background:#fff;
margin-left: 199px;
}
p {
margin-right: 5%;
margin-left:3%;
}
ol, ul {
margin-right: 5%;
margin-left:3%;
}
h1 {
font-size:130%;
font-weight:bold;
margin-left:3%;
margin-top:2em;
}
h2 {
font-size:120%;
font-weight:bold;
margin-left:3%;
margin-top:2em;
}
h3 {
font-size:100%;
font-weight:bold;
margin-left:3%;
margin-top:2em;
}
h4 {
font-size:90%;
font-weight:bold;
margin-left:3%;
margin-top:1.5em;
}
.title {
background-color: #FEFADE;
border-top:1px solid #ddad08;
font-weight:bold;
font-size:120%;
text-align:left;
margin-left:0;
}
.subtitle {
background-color: #FEFADE;
border-top:1px solid #ddad08;
font-weight:bold;
font-size:120%;
text-align:left;
}
.toclinks, ul {
margin-bottom:0px;
margin-top:0px;
}
.footer {
text-align: center;
}
code {
font-family:Courier, monospace;
}
pre {
font-family:Courier, monospace;
margin-left:3%;
text-align:left;
}
.content {
border-left:thin dotted #000;
}
table {
margin-left:3%;
border: 1px solid #c3d9ff;
border-spacing:0;
}
th {
font-weight:bold;
text-align:left;
margin-left:3%;
border-right: 1px solid #C3d9ff;
border-bottom: 1px solid #C3d9ff;
border-top: 1px solid #C3d9ff;
text-align: left;
padding: 6px 6px 6px 12px;
background: #aac8f6;
}
td {
border-right: 1px solid #C3d9ff;
border-bottom: 1px solid #C3d9ff;
text-align:left;
margin-left:3%;
padding: 6px 6px 6px 12px;
vertical-align:top;
}
td.alt {
background: #e8eefa;
vertical-align:top;
}
font {
margin-right: 5%;
vertical-align:bottom;
margin-left:3%;
}
.tdfooter {
margin-right: 5%;
text-align:right;
}
.i {
margin-left: 1em;
margin-right: 2em;
}
.q {
margin: 0 10px 0 5px;
padding: 10px 0 0 10px;
color: #000;
background: url("../../images/quote.gif") no-repeat top left;
}
.d {
color:#999999;
text-decoration:italics;
font-size:83%;
}
.tdheading {
font-weight:bold;
font-size:120%;
margin-left:3%;
}
@@ -0,0 +1,44 @@
body { background-color: white; }
h1 td, h1, h2, h3, h4, h5, h6, div.topnav,
div.sidenav, div.sidesearch, div.sidequote, div.bottomnav, div.footer, small,
td#sidebartitle { font-family: arial,sans-serif; }
/* rules for the bottom navigation on the results page */
div.bottomnav { margin-top: 1ex; }
div.bottomnav a, span.bottomnav { font-size: 10pt; }
div.bottomnav a, span.big { font-size: 12pt; color: #0000cc; }
/* standard link colors */
a:link { color: #0000cc; }
a:visited { color: #551a8b; }
a:active { color: #ff0000; }
/* top part of the page */
div.topnav { margin-bottom: 0.1ex; }
h1 td { font-size: .95em; font-weight: bolder; }
h1 td { background-color: #669900; color: white; border: none; padding: 2pt; }
div.side { margin-right: 3ex; padding: 2pt; }
div.sidenav { }
div.sidesearch { margin-top: 1em; font-size: 0.75em; }
div.sidequote { text-align: center; margin-top: 1.5in; border-color: red; border-style: solid; border-top-width: 1px; border-bottom-width: 1px; border-left-width: 0px; border-right-width: 0px; }
div.footer { text-align: center; color: #6f6f6f; padding: 3pt; }
/* sidebar on the right */
table.sidebarborder { margin: 3pt; margin-top: 1pt; }
td#sidebarcontent { background-color: #ffffff; }
/* color schemes for different sections of the site */
body.corporate h1 td, body.corporate table.sidebarborder td { background-color: #339966; }
body.corporate td#sidebartitle { background-color: #d8f1e4; }
body.siteowners h1 td, body.siteowners table.sidebarborder td { background-color: #ddad08; }
body.siteowners td#sidebartitle { background-color: #f1e4d8; }
body.search h1 td, body.search table.sidebarborder td { background-color: #336699; }
body.search td#sidebartitle { background-color: #d8e4f1; }
body.zealots h1 td, body.zealots table.sidebarborder td { background-color: #aa1002; }
body.zealots td#sidebartitle { background-color: #f1e4d8; }
body.gsa h1 td, body.gsa table.sidebarborder td { background-color: #F0C000; }
body.gsa td#sidebartitle { background-color: #fbeab5; }
@@ -0,0 +1,4 @@
ul, ol {
margin-left:3%;
padding-left:3%;
}
+188
View File
@@ -0,0 +1,188 @@
.pbox {
/* position: relative; */
top: 1px;
width: 15em;
/* height: 18em; */
line-height: 1.2em;
margin:0 10px 10px 0;
font-size: 85%;
}
/* =Menu borders (top and bottom) creates rounded corners
----------------------------------------------- */
.pbox .t2, .pbox .b2, .pbox .sb2 {
background-color:#BBBBBB; /* D */
position:relative;
top:-1px;
height:1px;
margin:0 1px;
font-size:1px;
line-height:1px;
}
.pbox .offset, .pbox .b2 {
border-right:1px solid #666666; /* A */
}
.pbox .t2 {
margin-right:2px;
}
.pbox .b2 {
background-color:#BBBBBB; /* D */
}
.pbox .sb2 {
display:block;
background-color:#666666;/* A */
margin-left:2px;
}
.boxbody {
background-color:#DDDDDD; /* E */
position:relative;
top:-1px;
border:solid #BBBBBB; /* D */
border-width:0 1px;
}
.boxbody ul, .boxbody li {
margin:0;
padding:0;
list-style:none;
}
/* =QuickAdd
----------------------------------------------- */
.quick {
width:30em;
font-size:100%;
color:#fff;
z-Index: 42;
}
.quick .t2 {
background-color:#8ac;
}
.quick .b2 {
background-color:#468;
}
.quick .sb2 {
background-color:#246;
}
.quick .offset, .pbox .quick .b2 {
border-right:1px solid #246; /* A */
}
.quick .boxbody {
background-color:#68a;
border-left-color:#8ac;
border-right-color:#468;
padding-left: 6px;
}
.quick form {
margin:0;
padding:2px 0px;
}
.quick label {
display:block;
padding:1px 1px 2px;
}
.quick .txt {
width:27em;
border:1px solid;
border-color:#468 #8ac #8ac #468;
font:100% Arial,Sans-serif;
}
.quick .imgbtn {
vertical-align:middle;
}
.quick p {
margin:0;
padding:2px;
font-size:85%;
line-height:1.2em;
}
/* =Options
----------------------------------------------- */
ul.caloptions {
border-bottom:1px solid #BBBBBB; /* D */
}
.caloptions li {
width:100%;
}
.caloptions li a {
display:block;
padding:3px 8px;
color:#222;
text-decoration:none;
}
* html .caloptions li a {
height:1.2em;
}
.caloptions li a:hover {
background-color:#BBBBBB; /* D */
/*bolinfest*/
text-decoration:underline;
color:#222;
}
/*bolinfest*/
.caloptions li a:visited {
color:#222;
}
/* =CalColor
----------------------------------------------- */
.calcolor {
padding:7px 8px 0;
}
.calcolor ul {
position:relative;
float:left;
width:100%;
padding-bottom:3px;
}
* html .calcolor ul {
padding-bottom:7px;
}
.calcolor li {
display:inline;
font-size:1px;
line-height:1px;
}
.calcolor li a {
float:left;
display:block;
width:13px;
height:13px;
margin:0 4px 4px 0;
border:1px solid #fff;
text-decoration:none;
}
.calcolor li a:hover {
border-color:#000;
}
.cal1 a.cal1-b, .cal1 a.cal1-b:hover,
.cal2 a.cal2-b, .cal2 a.cal2-b:hover,
.cal3 a.cal3-b, .cal3 a.cal3-b:hover,
.cal4 a.cal4-b, .cal4 a.cal4-b:hover,
.cal5 a.cal5-b, .cal5 a.cal5-b:hover,
.cal6 a.cal6-b, .cal6 a.cal6-b:hover,
.cal7 a.cal7-b, .cal7 a.cal7-b:hover,
.cal8 a.cal8-b, .cal8 a.cal8-b:hover,
.cal9 a.cal9-b, .cal9 a.cal9-b:hover,
.cal10 a.cal10-b, .cal10 a.cal10-b:hover,
.cal11 a.cal11-b, .cal11 a.cal11-b:hover,
.cal12 a.cal12-b, .cal12 a.cal12-b:hover,
.cal13 a.cal13-b, .cal13 a.cal13-b:hover,
.cal14 a.cal14-b, .cal14 a.cal14-b:hover,
.cal15 a.cal15-b, .cal15 a.cal15-b:hover,
.cal16 a.cal16-b, .cal16 a.cal16-b:hover,
.cal17 a.cal17-b, .cal17 a.cal17-b:hover,
.cal18 a.cal18-b, .cal18 a.cal18-b:hover,
.cal19 a.cal19-b, .cal19 a.cal19-b:hover,
.cal20 a.cal20-b, .cal20 a.cal20-b:hover,
.cal21 a.cal21-b, .cal21 a.cal21-b:hover {
background-image:url("images/icon_check3.gif");
background-position:-13px 0;
background-repeat:no-repeat;
cursor:default;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 84 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 B

+68
View File
@@ -0,0 +1,68 @@
<html>
<head>
<title>Google Calendar AuthSub demo</title>
<link rel="stylesheet" type="text/css" href="css/google.css">
<link rel="stylesheet" type="text/css" href="css/frameless.css">
<!--[if IE]>
<link rel="stylesheet" type="text/css" href="css/ieonly.css" />
<![endif]-->
</head>
<body>
<div class="leftcontent">
<p class="topimage"><a href="http://www.google.com/">
<img src="http://www.google.com/images/google_sm.gif" border="0" alt="Return to Google homepage" /></a>
</p>
</div>
<%
StringBuffer continueUrl = request.getRequestURL();
int index = continueUrl.lastIndexOf("/");
continueUrl.delete(index, continueUrl.length());
continueUrl.append("/LoginServlet");
%>
<FORM NAME=index METHOD=POST ACTION="<%=continueUrl.toString()%>">
<div class="rightcontent">
<p class=title>Google AuthSub Demo </p>
<div class="content">
<h1>Google AuthSub Demo</h1>
<p>
This page demonstrates a basic version of AuthSub in action.</p>
<p>
The AuthSub token is first retrieved from Google and then used to retrieve your Calendar entries.
</p>
<p>
Note: <br>
If your user account is registered to a domain other than 'gmail.com', specify the domain name as well.
If the calendar service in your domain requires secure connection (ssl) for retrieving feeds, make sure to check the ssl option.
</p>
<p>
<br><b>Specify user domain:</b>
<br>
<input type="radio" name="defaultdomain" value="true" onclick="javascript:document.index.domain.disabled=true" checked>
Use default domain (gmail.com)
<br>
<input type="radio" name="defaultdomain" value="false" onclick="javascript:document.index.domain.disabled=false">
Use my application domain (ex: mydomain.com): <input type=text name=domain disabled>
<br>
<br>
<input type="checkbox" name="secure" value="true">Use secure connection to retrieve calendar feed.
<p><input type=submit value="Authenticate">
</p>
</div>
</div>
</FORM>
<div class="leftcontent">
<p class="footerimage"><img src="http://www.google.com/images/art.gif">
</p></div><div class="rightcontent">
<p>
<img src="http://www.google.com/images/cleardot.gif" width="1" height="45">
</p> </div>
</body>
</html>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,260 @@
Function.prototype.bind=function(a){if(typeof this!="function"){throw new Error("Bind must be called as a method of a function object.");}var b=this;var c=Array.prototype.splice.call(arguments,1,arguments.length);return function(){var d=c.concat();for(var e=0;e<arguments.length;e++){d.push(arguments[e])}return b.apply(a,d)}
}
;var Ta;var Ya;(function(){var a={};var b=0;function c(f){if(!f.Da){f.Da=++b}return f.Da}
function d(f,h,l,n){var k=c(f);var q=c(l);n=!(!n);var s=k+"_"+h+"_"+q+"_"+n;return s}
Ta=function(f,h,l,n){var k=d(f,h,l,n);if(k in a){return k}var q=e.bind(null,k);a[k]={listener:l,proxy:q};if(f.addEventListener){f.addEventListener(h,q,n)}else if(f.attachEvent){f.attachEvent("on"+h,q)}else{throw new Error("Node {"+f+"} does not support event listeners.");}return k}
;Ya=function(f,h,l,n){var k=d(f,h,l,n);if(!(k in a)){return false}var q=a[k].proxy;if(f.removeEventListener){f.removeEventListener(h,q,n)}else if(f.detachEvent){f.detachEvent("on"+h,q)}delete a[k];return true}
;function e(f){var h=Array.prototype.splice.call(arguments,1,arguments.length);return a[f].listener.apply(null,h)}
}
)();var oa=["Su","M","Tu","W","Th","F","Sa"];var Z=[,"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];var aa=[,"January","February","March","April","May","June","July","August","September","October","November","December"];var Aa;var Qa;var Ba;var Ca;var W;var Sa;var Ra;(function(){var a=navigator.userAgent.toLowerCase();Aa=a.indexOf("msie")!=-1;Qa=a.indexOf("msie 5")!=-1&&document.all;Ba=a.indexOf("konqueror")!=-1;Ca=a.indexOf("safari")!=-1||Ba;W=!Aa&&!Ca&&a.indexOf("mozilla"
)!=-1;Sa=a.indexOf("win")!=-1;Ra=!(!window.opera)}
)();function na(a,b){}
;function ca(a){if(!a)return"";return a.replace(/&#(\d+);/g,function(b,c){return String.fromCharCode(parseInt(c,10))}
).replace(/&#x([a-f0-9]+);/gi,function(b,c){return String.fromCharCode(parseInt(c,16))}
).replace(/&(\w+);/g,function(b,c){c=c.toLowerCase();return c in ca.unesc?ca.unesc[c]:"?"}
)}
ca.unesc={lt:"<",gt:">",quot:'"',nbsp:" ",amp:"&"};function Y(a){return Y.Ba[a]}
function ma(a){if(!Y.Ba){var b={};b["\\"]="\\\\";b["'"]="\\047";b["\u0008"]="\\b";b['"']="\\042";b["<"]="\\074";b[">"]="\\076";b["&"]="\\046";b["\n"]="\\n";b["\r"]="\\r";b["\u0085"]="\\205";b["\u2028"]="\\u2028";b["\u2029"]="\\u2029";Y.Ba=b}return"'"+a.toString().replace(/[\'\\\r\n\b\"<>&\u0085\u2028\u2029]/g,Y)+"'"}
function Ea(a){var b={};b.clientX=a.clientX;b.clientY=a.clientY;b.pageX=a.pageX;b.pageY=a.pageY;b.type=a.type;b.srcElement=a.srcElement;b.target=a.target;b.cancelBubble=a.cancelBubble;b.explicitOriginalTarget=a.explicitOriginalTarget;return b}
function Oa(a){return document.getElementById(a)}
function Pa(a){return document.all[a]}
var z=document.getElementById?Oa:Pa;function ba(a){var b;if(!("name" in a)){var c=/\W*function\s+([\w\$]+)\(/.exec(a);if(!c){throw new Error("Cannot extract name from function: "+a);}b=c[1];a.name=b}else{b=a.name}if(!b||b=="anonymous"){throw new Error("Anonymous function has no name: "+a);}return a.name}
function _showLogWindow(){}
function N(a){return a<0?-1:1}
function X(a){return a|0}
function ta(a){DumpError(a);throw a;}
function p(a,b){var c=a.toString();while(c.length<b){c="0"+c}return c}
var ea=[undefined,31,undefined,31,30,31,30,31,31,30,31,30,31];function H(a,b){if(2!==b){return ea[b]}var c=a<<4;var d=ea[c];if(!d){d=Math.round((Date.UTC(a,2,1)-Date.UTC(a,1,1))/86400000);ea[c]=d}return d}
var ua=new Object;function Ka(a,b){var c=a<<4|b;var d=ua[c];if(!d){d=(new Date(a,b-1,1,0,0,0,0)).getDay();ua[c]=d}return d}
function fa(a){return(a.date-1+Ka(a.year,a.month))%7}
function ra(a,b,c,d,e,f){var h;if(a===d){if((h=b-e)===0){return c-f}else if(h<0){h=c-f;do{h-=H(a,b++)}while(b<e);return h}else{h=c-f;do{h+=H(d,e++)}while(e<b);return h}}else{return Math.round((Date.UTC(a,b-1,c)-Date.UTC(d,e-1,f))/86400000)}}
function sa(a,b){return ra(a.year,a.month,a.date,b.year,b.month,b.date)}
function y(a,b,c,d,e,f){if(!isNaN(a)){this.year=a}if(!isNaN(b)){this.month=b}if(!isNaN(c)){this.date=c}if(!isNaN(d)){this.hour=d}if(!isNaN(e)){this.minute=e}if(!isNaN(f)){this.second=f}}
y.prototype.year=NaN;y.prototype.month=NaN;y.prototype.date=NaN;y.prototype.hour=NaN;y.prototype.minute=NaN;y.prototype.second=NaN;y.prototype.Ya=function(){return fa(this)}
;y.prototype.toString=function(){if(this.l!==undefined)return this.l;this.l=this.D();return this.l}
;function K(){}
K.prototype=new y;K.prototype.constructor=K;function j(a,b,c){y.call(this,a,b,c,NaN,NaN,NaN)}
j.prototype=new K;j.prototype.constructor=j;j.now=function(){var a=new Date;return j.create(a.getFullYear(),a.getMonth()+1,a.getDate())}
;j.prototype.type="Date";j.prototype.i=function(){return this}
;j.prototype.P=function(){return new t(this.year,this.month,this.date,0,0,0)}
;j.prototype.a=function(){if(undefined===this.e){this.e=j.wa(this.year,this.month,this.date)}return this.e}
;j.wa=function(a,b,c){return this.e=(((a-1970)*12+b<<5)+c)*86400}
;j.prototype.da=function(){return true}
;j.prototype.D=function(){return p(this.year,4)+p(this.month,2)+p(this.date,2)}
;j.prototype.equals=function(a){return this.constructor===a.constructor&&this.date===a.date&&this.month===a.month&&this.year===a.year}
;j.V={};j.Ra=0;j.Ma=200;j.create=function(a,b,c){var d=j.wa(a,b,c);if(d in j.V){return j.V[d]}else{var e=new j(a,b,c);e.e=d;if(j.Ra<j.Ma){j.V[d]=e}return e}}
;function t(a,b,c,d,e,f){y.call(this,a,b,c,d,e,f)}
t.prototype=new K;t.prototype.constructor=t;t.now=function(){var a=new Date;return new t(a.getFullYear(),a.getMonth()+1,a.getDate(),a.getHours(),a.getMinutes(),a.getSeconds())}
;t.prototype.type="DateTime";t.prototype.i=function(){return j.create(this.year,this.month,this.date)}
;t.prototype.P=function(){return this}
;t.prototype.La=function(){return new F(this.hour,this.minute,this.second)}
;t.prototype.a=function(){if(undefined===this.e){this.e=(((((this.year-1970)*12+this.month<<5)+this.date)*24+this.hour)*60+this.minute)*60+this.second}return this.e}
;t.prototype.da=function(){return true}
;t.prototype.D=function(){return p(this.year,4)+p(this.month,2)+p(this.date,2)+"T"+p(this.hour,2)+p(this.minute,2)+p(this.second,2)}
;t.prototype.equals=function(a){return this.constructor===a.constructor&&this.date===a.date&&this.month===a.month&&this.year===a.year&&this.hour===a.hour&&this.minute===a.minute&&this.second===a.second}
;t.prototype.clone=function(){var a=new t(this.year,this.month,this.date,this.hour,this.minute,this.second);if(this.l!==undefined)a.l=this.l;return a}
;function F(a,b,c){y.call(this,NaN,NaN,NaN,a,b,c)}
F.prototype=new y;F.prototype.constructor=F;F.prototype.type="Time";F.prototype.La=function(){return this}
;F.prototype.D=function(){return"T"+p(this.hour,2)+p(this.minute,2)+p(this.second,2)}
;F.prototype.equals=function(a){return this.constructor===a.constructor&&this.hour===a.hour&&this.minute===a.minute&&this.second===a.second}
;F.prototype.a=function(){return(this.hour*60+this.minute)*60+this.second}
;function E(a,b,c,d){var e=d+60*(c+60*(b+24*a));var f=X(e/86400);e-=f*86400;var h=X(e/3600);e-=h*3600;var l=X(e/60);e-=l*60;var n=X(e);y.call(this,NaN,NaN,f,h,l,n)}
E.prototype=new y;E.prototype.constructor=E;E.prototype.type="Duration";E.prototype.Wb=function(){return this.date}
;E.prototype.Xb=function(){return this.date*24+this.hour}
;E.prototype.Yb=function(){return 1440*this.date+this.hour*60+this.minute}
;E.prototype.Zb=function(){return this.second+this.minute*60+this.hour*3600+86400*this.date}
;E.prototype.a=function(){if(undefined===this.e){this.e=((this.date*24+this.hour)*60+this.minute)*60+this.second}return this.e}
;E.prototype.D=function(){var a=this.year?N(this.year):(this.month?N(this.month):(this.date?N(this.date):(this.hour?N(this.hour):(this.minute?N(this.minute):(this.second?N(this.second):0)))));var b=a<0?"-P":"P";if(this.year){b+=a*this.year+"Y"}if(this.month){b+=a*this.month+"N"}if(this.date){b+=this.date%7?a*this.date+"D":a*this.date/7+"W"}if(this.hour||this.minute||this.second){b+="T"}if(this.hour){b+=a*this.hour+"H"}if(this.minute){b+=a*this.minute+"M"}if(this.second){b+=a*this.second+"S"}if(!a)
{b+="0D"}return b}
;E.prototype.equals=function(a){return this.constructor===a.constructor&&this.date===a.date&&this.hour===a.hour&&this.minute===a.minute&&this.second===a.second}
;function Q(a){var b=new o;b.year=a.year||0;b.month=a.month||0;b.date=a.date||0;b.hour=a.hour||0;b.minute=a.minute||0;b.second=a.second||0;return b}
function $(a,b,c){na(!(isNaN(a)|isNaN(b)|isNaN(c)));var d=new o;d.year=a||0;d.month=b||0;d.date=c||0;return d}
function o(){}
o.prototype=new y;o.prototype.constructor=o;o.prototype.type="DTBuilder";o.prototype.year=(o.prototype.month=(o.prototype.date=(o.prototype.hour=(o.prototype.minute=(o.prototype.second=0)))));o.prototype.a=function(){this.normalize()}
;o.prototype.advance=function(a){if(a.date){this.date+=a.date}if(a.hour){this.hour+=a.hour}if(a.minute){this.minute+=a.minute}if(a.second){this.second+=a.second}}
;o.prototype.normalize=function(){this.ib();this.ia();var a=H(this.year,this.month);while(this.date<1){this.month-=1;this.ia();a=H(this.year,this.month);this.date+=a}while(this.date>a){this.date-=a;this.month+=1;this.ia();a=H(this.year,this.month)}}
;o.prototype.ib=function(){var a;if(this.second<0){a=Math.ceil(this.second/-60);this.second+=60*a;this.minute-=a}else if(this.second>=60){a=Math.floor(this.second/60);this.second-=60*a;this.minute+=a}if(this.minute<0){a=Math.ceil(this.minute/-60);this.minute+=60*a;this.hour-=a}else if(this.minute>=60){a=Math.floor(this.minute/60);this.minute-=60*a;this.hour+=a}if(this.hour<0){a=Math.ceil(this.hour/-24);this.hour+=24*a;this.date-=a}else if(this.hour>=24){a=Math.floor(this.hour/24);this.hour-=24*a;
this.date+=a}}
;o.prototype.ia=function(){var a;if(this.month<1){a=Math.ceil((this.month-1)/-12);this.month+=12*a;this.year-=a}else if(this.month>12){a=Math.floor((this.month-1)/12);this.month-=12*a;this.year+=a}}
;o.prototype.i=function(){this.normalize();return j.create(this.year,this.month,this.date)}
;o.prototype.P=function(){this.normalize();return new t(this.year,this.month,this.date,this.hour,this.minute,this.second)}
;o.prototype.Ja=function(){this.normalize();return new B(isFinite(this.year)?this.year:undefined,isFinite(this.month)?this.month:undefined,isFinite(this.date)?this.date:undefined)}
;o.prototype.Ka=function(){this.normalize();return new C(isFinite(this.year)?this.year:undefined,isFinite(this.month)?this.month:undefined,isFinite(this.date)?this.date:undefined,isFinite(this.hour)?this.hour:undefined,isFinite(this.minute)?this.minute:undefined,isFinite(this.second)?this.second:undefined)}
;o.prototype.La=function(){this.normalize();return new F(this.hour,this.minute,this.second)}
;o.prototype.rb=function(){if(this.year||this.month){ta("Can't convert months or years to ICAL_Duration");return undefined}else{return new E(this.date,this.hour,this.minute,this.second)}}
;o.prototype.sb=function(){return"number"==typeof this.year&&1+this.year%1===1&&"number"==typeof this.month&&1+this.month%1===1&&"number"==typeof this.date&&1+this.date%1===1}
;o.prototype.$b=function(){return this.sb()&&this.tb()}
;o.prototype.tb=function(){return"number"==typeof this.hour&&1+this.hour%1===1&&"number"==typeof this.minute&&1+this.minute%1===1&&"number"==typeof this.second&&1+this.second%1===1}
;o.prototype.toString=function(){return"["+(undefined!==this.year?p(this.year,4):"????")+"/"+(undefined!==this.month?p(this.month,2):"??")+"/"+(undefined!==this.date?p(this.date,2):"??")+" "+(undefined!==this.hour?p(this.hour,2):"??")+" "+(undefined!==this.minute?p(this.minute,2):"??")+" "+(undefined!==this.second?p(this.second,2):"??")+"]"}
;o.prototype.equals=function(a){return this.constructor===a.constructor&&this.date===a.date&&this.month===a.month&&this.year===a.year&&this.hour===a.hour&&this.minute===a.minute&&this.second===a.second}
;function M(a,b){this.start=a;if(b.constructor==E){var c=Q(a);c.advance(b);this.end=this.start instanceof t?c.P():c.i()}else{this.end=b}this.duration=pa(this.end,this.start)}
M.prototype.type="PeriodOfTime";M.prototype.toString=function(){if(this.l!==undefined)return this.l;this.l=this.start+"/"+this.end;return this.l}
;M.prototype.equals=function(a){return this.constructor===a.constructor&&this.start.equals(a.start)&&this.end.equals(a.end)}
;M.prototype.overlaps=function(a){return a.end.a()>this.start.a()&&a.start.a()<this.end.a()}
;M.prototype.Kb=function(a,b){return b.a()>this.start.a()&&a.a()<this.end.a()}
;M.prototype.contains=function(a){return this.start.a()<=a.start.a()&&this.end.a()>=a.end.a()}
;function da(a,b){this.start=a;this.end=b;try{this.duration=pa(this.end,this.start)}catch(c){this.duration=null}}
da.prototype.type="PartialPeriodOfTime";da.prototype.D=function(){return this.start+"/"+this.end}
;da.prototype.equals=function(a){return this.constructor===a.constructor&&this.start.equals(a.start)&&this.end.equals(a.end)}
;function pa(a,b){if(isNaN(a.year)!=isNaN(b.year)||isNaN(a.hour)!=isNaN(b.hour)){ta("diff("+a+", "+b+")");return undefined}var c=Q(a);if(isNaN(a.year)){c.hour-=b.hour;c.minute-=b.minute;c.second-=b.second}else{c.year=NaN;c.month=NaN;c.date=ra(a.year,a.month,a.date,b.year,b.month,b.date);if(!isNaN(a.hour)){c.hour-=b.hour;c.minute-=b.minute;c.second-=b.second}}return c.rb()}
function B(a,b,c){this.year=a;this.month=b;this.date=c}
B.prototype=new K;B.prototype.constructor=B;B.prototype.type="PartialDate";B.prototype.i=function(){return j.create(this.year||0,this.month||1,this.date||1)}
;B.prototype.P=function(){return new t(this.year||0,this.month||1,this.date||1,0,0,0)}
;B.prototype.Ja=function(){return this}
;B.prototype.Ka=function(){return new C(this.year,this.month,this.date,0,0,0)}
;B.prototype.da=function(){return!isNaN(this.a())}
;B.prototype.a=function(){if(undefined===this.e){this.e=(((this.year-1970)*12+this.month<<5)+this.date)*86400}return this.e}
;B.prototype.equals=function(a){return this.constructor===a.constructor&&(this.date===a.date||isNaN(this.date)&&isNaN(a.date))&&(this.month===a.month||isNaN(this.month)&&isNaN(a.month))&&(this.year===a.year||isNaN(this.year)&&isNaN(a.year))}
;B.prototype.D=function(){return(undefined!==this.year?p(this.year,4):"????")+(undefined!==this.month?p(this.month,2):"??")+(undefined!==this.date?p(this.date,2):"??")}
;function C(a,b,c,d,e,f){this.year=a;this.month=b;this.date=c;this.hour=d;this.minute=e;this.second=f}
C.prototype=new K;C.prototype.constructor=C;C.prototype.type="PartialDateTime";C.prototype.i=function(){return j.create(this.year||0,this.month||1,this.date||1)}
;C.prototype.P=function(){return new t(this.year||0,this.month||1,this.date||1,this.hour||0,this.minute||0,this.second||0)}
;C.prototype.Ja=function(){return new B(this.year,this.month,this.date)}
;C.prototype.Ka=function(){return this}
;C.prototype.da=function(){return!isNaN(this.a())}
;C.prototype.a=function(){if(undefined===this.e){this.e=(((((this.year-1970)*12+this.month<<5)+this.date)*24+this.hour)*60+this.minute)*60+this.second}return this.e}
;C.prototype.equals=function(a){return this.constructor===a.constructor&&(this.date===a.date||isNaN(this.date)&&isNaN(a.date))&&(this.month===a.month||isNaN(this.month)&&isNaN(a.month))&&(this.year===a.year||isNaN(this.year)&&isNaN(a.year))&&(this.hour===a.hour||isNaN(this.hour)&&isNaN(a.hour))&&(this.minute===a.minute||isNaN(this.minute)&&isNaN(a.minute))&&(this.second===a.second||isNaN(this.second)&&isNaN(a.second))}
;C.prototype.D=function(){return(undefined!==this.year?p(this.year,4):"????")+(undefined!==this.month?p(this.month,2):"??")+(undefined!==this.date?p(this.date,2):"??")+"T"+(undefined!==this.hour?p(this.hour,2):"??")+(undefined!==this.minute?p(this.minute,2):"??")+(undefined!==this.second?p(this.second,2):"??")}
;var S=undefined;var va=[];function qa(a,b,c){var d=b>2&&29===H(a,2);return qa.Na[b]+d+c-1}
qa.Na=[undefined,0,31,59,90,120,151,181,212,243,273,304,334];function Da(){var a=new Date;var b=S;S=j.create(a.getFullYear(),a.getMonth()+1,a.getDate());if(b&&!b.equals(S)){for(var c=0;c<va.length;++c){var d=va[c];try{d(S)}catch(e){}}}var f=new Date(a.getFullYear(),a.getMonth(),a.getDate(),0,0,0,0);f.setDate(f.getDate()+1);var h=f.getTime()-a.getTime();if(h<0||h>=1800000){h=1800000}setTimeout(Da,h)}
Da();function P(a,b,c){this.x=a;this.y=b;this.coordinateFrame=c}
P.prototype.toString=function(){return"[P "+this.x+","+this.y+"]"}
;P.prototype.clone=function(){return new P(this.x,this.y,this.coordinateFrame)}
;function Ia(a,b){this.dx=a;this.dy=b}
Ia.prototype.toString=function(){return"[D "+this.dx+","+this.dy+"]"}
;function L(a,b,c,d,e){this.x=a;this.y=b;this.w=c;this.h=d;this.coordinateFrame=e}
L.prototype.contains=function(a){return this.x<=a.x&&a.x<this.x+this.w&&this.y<=a.y&&a.y<this.y+this.h}
;L.prototype.toString=function(){return"[R "+this.w+"x"+this.h+"+"+this.x+"+"+this.y+"]"}
;L.prototype.clone=function(){return new L(this.x,this.y,this.w,this.h,this.coordinateFrame)}
;function Ua(a){var b=a.ownerDocument;if(W&&b){var c=b.getBoxObjectFor(a);return new L(c.x,c.y,c.width,c.height,window)}var d=0;var e=0;for(var f=a;f.offsetParent;f=f.offsetParent){d+=f.offsetLeft;e+=f.offsetTop}return new L(d,e,a.offsetWidth,a.offsetHeight,window)}
function Va(a){var b=a.ownerDocument;if(W&&b){var c=b.getBoxObjectFor(a);return c.height}else{return a.offsetHeight}}
function Xa(a){var b=a.ownerDocument;if(W&&b){var c=b.getBoxObjectFor(a);return c.width}else{return a.offsetWidth}}
function Wa(a){var b=a.ownerDocument;if(W&&b){var c=b.getBoxObjectFor(a);return new P(c.x,c.y,window)}var d=0;var e=0;while(a.offsetParent){d+=a.offsetLeft;e+=a.offsetTop;a=a.offsetParent}return new P(d,e,window)}
function Ja(a){var b=0;var c=0;if(a.pageX||a.pageY){b=a.pageX;c=a.pageY}else if(a.clientX||a.clientY){b=a.clientX+document.body.scrollLeft;c=a.clientY+document.body.scrollTop}return new P(b,c,window)}
function g(a,b,c,d,e){this.Q=a;this.g=c?c:this.Q.id+"_";this.c=d?d:"DP_";this.Qa();g.K[this.g]=this;if(e){this.j=e}else{this.j=j.now()}this.r=j.create(this.j.year,this.j.month,1);this.L=0;this.Ua=!(!b);this.T=false;this.Fa=null;this.Ea=null;this.J={};this.t={};this.F={};this.b={};this.n=null;this.N=null;this.ka=new J(this);this.ga=new J(this);this.pa=false;this.G=false;this.f=new G;this.ya=false;this.A=0;this.E=null;this.X=null;this.na=true;this.ja=null;this.W=null;this.ha=null;this.p();this.qa=false;
this.pb(0);this.Ga(0);this.ba=false;this.o=null;this.d=null;this.m=null;this.I=null;this.H=null;this.Ca=null;this.M=false;this.ta=null;this.sa=null;var f=this;var h=function(l){var n=l.startDate;var k=l.endDate;var q;if(!n){q=R[this.O]}else if(!k||n.equals(k)){q="Selected: "+f.$(n,true)}else{q="Selected: "+f.$(n)+" - "+f.$(k)}f.jb(q)}
;if(this.pa)this.ua(h);this.fa=new J(this)}
;g.prototype.Qa=function(){var a=this.c+"day_top ",b=this.c+"day_left ",c=this.c+"day_right ",d=this.c+"onmonth ",e=this.c+"onmonth ",f=this.c+"month_top ",h=this.c+"month_left ",l=this.c+"weekend ",n=this.c+"weekday ",k=this.c+"weekend_selected ",q=this.c+"weekday_selected ";var s={};s[0]="";s[1]=a;s[3]=a+b;s[5]=a+c;s[2]=b;s[4]=c;var u={};for(var i in s){u[i|16|256]=s[i]+d+l;u[i|16|512]=s[i]+d+n;u[i|32|256]=s[i]+e+l;u[i|32|512]=s[i]+e+n;u[i|16|1024]=s[i]+d+k;u[i|16|2048]=s[i]+d+q;u[i|32|1024]=s[
i]+e+k;u[i|32|2048]=s[i]+e+q}var v={};for(var i in u){v[i]=u[i];v[i|64]=u[i]+f;v[i|64|128]=u[i]+f+h}this._classMap=v}
;var R={};R[0]="Select a date";R[1]="Select a range of dates";R[2]="Select dates";R[3]="&nbsp;";g.prototype.Ga=function(a,b){if(a!=0&&a!=1&&a!=7&&a!=30&&a!=-1&&!(b instanceof Function)){throw new Error("Invalid click mode: "+a);}this.Ta=a;this.xa=b}
;g.prototype.Hb=function(){return this.T}
;g.prototype.qb=function(a){if(a!=this.T){this.T=a;this.p()}}
;g.prototype.Wa=function(){return this.Ta}
;g.prototype.pb=function(a){if(this.O==a){return}this.O=a;this.u()}
;g.prototype.aa=function(){return this.O}
;g.prototype.show=function(){this.G=true;this.p()}
;g.prototype.hide=function(){this.Q.innerHTML="";this.G=false}
;g.prototype.gb=function(){return this.G}
;g.prototype.Cb=function(a){return this.t[a.id]}
;g.prototype.Eb=function(a){return this.F[a.id]}
;g.prototype.Xa=function(a){return this.b[a.id]}
;g.prototype.Fb=function(){return z(this.g+"tbl")}
;g.prototype.mb=function(a){this.L=a;this.p()}
;g.prototype.Aa=function(){return this.L}
;g.prototype.Pb=function(a){this.E=a;this.p();return true}
;g.prototype.lb=function(a){this.X=a}
;g.prototype.yb=function(){return this.E}
;g.prototype.Ab=function(){return this.n}
;g.prototype.$a=function(){if(!this.G)return null;return this.b[this.n.id]}
;g.prototype.ab=function(){if(!this.G)return null;var a=z(this.g+"day_"+(this.A-1)+"_6");return this.b[a.id]}
;g.prototype.Ub=function(a){if(a!=this.na){this.na=a;this.p()}}
;g.prototype.ob=function(a){this.ja=a}
;g.prototype.Ob=function(a){this.W=a}
;g.prototype.nb=function(a){this.ha=a}
;g.prototype.bb=function(){return Z}
;g.prototype.Bb=function(){return aa}
;g.prototype.p=function(){if(!this.G){return}var a=this.g;var b;var c=this.r.month;var d=this.r.year;var e=oa.length;var f=[c==1?12:c-1,c,c==12?1:c+1];var h=j.create(this.j.year,this.j.month,1);var l=$(d,c-1,1).i();var n=$(d,c+1,1).i();if(this.ja){f[0]=this.ja(l)}else{var k=l.a()>=h.a()?"&laquo;":"&lsaquo;&nbsp;";f[0]=k+Z[f[0]]}if(this.W){f[1]=this.W(this.r)}else{f[1]=aa[f[1]]+" "+d}if(this.ha){f[2]=this.ha(n)}else{var q=n.a()-h.a()<=0?"&raquo;":"&nbsp;&rsaquo;";f[2]=Z[f[2]]+q}var s=H(d,c);var u=
H(l.year,l.month);var i=new Array(49);var v=this.r.Ya()-this.L;if(v<0)v+=7;if(s<30||v<5)v+=7;for(var r=0;r<v;++r){i[r]=j.create(l.year,l.month,u-v+r+1)}for(var r=v,m=0;m<s;++r){i[r]=j.create(d,c,++m)}var A=v+s;for(var r=A,m=0;r<i.length;++r){i[r]=j.create(n.year,n.month,++m)}this.ta=i[0];this.sa=i[i.length-1];var D=new Array;var O=this.Ua?[2,3,2]:[1,5,1];D.push('<table cols=7 cellspacing="0" cellpadding="3" id="',a,'tbl"',' class="',this.c,'monthtable" ',' style="-moz-user-select:none; cursor:pointer;">'
,'<tr class="',this.c,'heading" id="',a,'header">',"<td colspan=",O[0]," unselectable=on",' onmousedown="'+ba(Ha)+"(",ma(this.g),')"',' id="',a,'mhl" class="',this.c,'prev">',f[0],"</td>","<td colspan=",O[1],' unselectable="on"',' id="',a,'mhc" class="',this.c,'cur">',f[1],"</td>","<td colspan=",O[2],' unselectable="on"',' onmousedown="'+ba(Ga)+"(",ma(this.g),')"',' id="',a,'mhr" class="',this.c,'next">',f[2],"</td>","</tr>");if(this.T){D.push('<tr class="',this.c,'days" id="',a,'dow">');for(var r=
0;r<e;++r){D.push('<td unselectable="on"',' class="',this.c,'dayh" id="',a,"day_",r,'">',oa[(r+this.L)%7],"</td>")}D.push("</tr>")}var T=(7-this.L)%7;var La=(T+6)%7;this.J={};var b=null;var w=null;var Ma=ba(Fa);var wa;var w;var ga=null;if(this.X){ga=this.X.call(null,this.ta,this.sa)}for(var r=0,m=-1;r<7;++r){D.push('<tr id="',a,"week_",r,'">');for(var I=0;I<e;++I){++m;var ha=this.f.contains(i[m]);w=0;if(r==0)w|=1;if(I==0)w|=2;else if(I==6)w|=4;w|=I==T||I==La?(ha?1024:256):(ha?2048:512);if(m<v||m>=
A){w|=32;if(i[m].date<=7){w|=64;if(i[m].date==1&&I!=0){w|=128}}w=this._classMap[w]}else{w|=16;if(i[m].date<=7){w|=64;if(i[m].date==1&&I!=0){w|=128}}if(i[m].date==this.j.date&&c==this.j.month&&d==this.j.year){w=this._classMap[w]+(this.c+"today"+(ha?"_selected ":" "))}else{w=this._classMap[w]}}D.push('<td id="',a,"day_",r,"_",I,'"',' class="',w,'"');if(ga&&(wa=ga[i[m]])){D.push(' style="',wa,'"')}D.push(' onclick="',Ma,'(this)"',' unselectable="on">',i[m].date,"</td>")}D.push("</tr>")}if(this.pa){D.push(
'<tr class="',this.c,'months">','<td colspan="7" id="',a,'sel"></td></tr>')}D.push("</table>");this.Q.innerHTML=D.join("");this.n=z(a+"day_0_0");this.N=z(a+"day_6_6");var b=this.n;var U=b.parentNode;var ia=null;var xa=null;var m=-1;var ja=-1;while(U!=null){++ja;if(ja==7)break;var ya=-1;while(b!=null){++m;++ya;var ka=a+"day_"+ja+"_"+ya;this.b[ka]=i[m];this.J[i[m].toString()]=b;this.F[ka]=ia;if(ia)this.t[xa]=b;ia=b;xa=ka;b=b.nextSibling}U=U.nextSibling;if(U!=null){b=U.firstChild}}this.A=7;if(!this.na)
{var Na=z(a+"week_4");var za=z(a+"week_5");var la=z(a+"week_6");if(this.b[a+"day_4_0"].month!=c){Na.style.display="none";za.style.display="none";la.style.display="none";this.A=4}else if(this.b[a+"day_5_0"].month!=c){za.style.display="none";la.style.display="none";this.A=5}else if(this.b[a+"day_6_0"].month!=c){la.style.display="none";this.A=6}}this.Fa=l;this.Ea=n;if(this.E){this.E.call(null,this)}this.Oa()}
;g.prototype.refresh=function(){if(this.E){this.E.call(null,this)}}
;g.prototype.ua=function(a){return this.ka.add(a)}
;g.prototype.Nb=function(a){return this.ka.remove(a)}
;g.prototype.u=function(a){a=arguments.length===0||a;var b=this.f.U();for(var c=0;c<b.length;++c){var d=this.J[b[c].toString()];this.C(d,false)}this.f.clear();if(!this.M){this.Ha(null);this.la(null)}if(a)this.z()}
;g.prototype.Jb=function(a){return this.f.contains(a)}
;g.prototype.Sa=function(a){if(this.xa){this.xa.call(null,a);return}var b=z(a);var c=this.f;switch(this.O){case 1:var d=this.Wa();if(d==0)break;if(d!=1&&(d!=-1||!c.contains(this.b[a]))){var e=this.b[b.id];var f;switch(d){case -1:if(c.s()>7&&this.R()){var h=b.id.substr(b.id.length-3,1);e=this.b[this.g+"day_"+h+"_0"]}f=c.s()-1;break;case 7:var h=b.id.substr(b.id.length-3,1);e=this.b[this.g+"day_"+h+"_0"];f=6;break;case 30:e=this.b[b.id];e=j.create(e.year,e.month,1);var l=Q(e);f=H(e.year,e.month)-1;
break;default:}var l=Q(e);l.date+=f;var n=l.i();this.ma(e,n);return}na(d==1||d==-1&&c.contains(this.b[a]),"not a case for single date selection");this.u(false);case 0:if(c.s()>0){var k=c.U()[0];c.remove(k);var q=this.J[k.toString()];if(q)this.C(q,false)}c.add(this.b[b.id]);this.C(b);this.z(this.b[b.id]);break;case 2:break;case 3:default:break}}
;g.prototype.Ha=function(a){this.d=a;this.I=a?this.b[a.id]:null}
;g.prototype.la=function(a){this.m=a;this.H=a?this.b[a.id]:null}
;g.prototype.fb=function(){return this.M}
;g.prototype.Vb=function(a,b){this.u(false);this.M=true;this.Ha(this.Y(a));var c=this.b[this.d.id];this.f.add(c);this.C(this.d);this.ma(c);this.la(this.d)}
;g.prototype.xb=function(a,b,c){this.M=false;this.m=this.Y(a);if(this.R()){this.z(this.I,this.H,false);return}var d,e;if(this.b[this.m.id].a()<this.b[this.d.id].a()){d=this.b[this.m.id];e=this.b[this.d.id]}else{d=this.b[this.d.id];e=this.b[this.m.id]}this.z(d,e,false)}
;g.prototype.Ib=function(a,b,c,d){this.Ca=Ea(a);if(this.O!=1||this.ba)return;this.ba=true;var e=this;setTimeout(function(){try{if(e.M){e.Va.call(e,b,c,d)}}finally{e.ba=false}}
,50)}
;g.prototype.Sb=function(a){if(this.qa==a)return;this.qa=!(!a);this.u()}
;g.prototype.R=function(){return this.qa}
;g.prototype.Va=function(a,b,c){var d=this.Ca;var e=this.Y(d);if(e===this.m)return;var f=this.m;this.la(e);var h=this.b;var l=h[f.id].a()<h[e.id].a();var n=h[f.id].a()<h[this.d.id].a();var k=h[e.id].a()<h[this.d.id].a();var q=h[this.d.id].a()<h[e.id].a();var s=h[this.d.id].a()<h[f.id].a();var u,i;var v,r;var m=k?this.m:this.d;var A=k?this.d:this.m;if(this.R()){var D=sa(h[A.id],h[m.id]);if(D>=7){var O,T;O=parseInt(m.id.charAt(m.id.length-3),10);T=parseInt(A.id.charAt(A.id.length-3),10);m=z(this.g+
"day_"+O+"_0");A=z(this.g+"day_"+T+"_6")}this.B(this.n,m,false);this.B(A,this.N,false);this.B(m,A,true);this.I=h[m.id];this.H=h[A.id]}else{if(l){if(n){i=k?this.F[e.id]:this.F[this.d.id];this.B(f,i,false)}if(q){u=s?this.t[f.id]:this.t[this.d.id];this.B(u,e,true)}}else{if(s){u=q?this.t[e.id]:this.t[this.d.id];this.B(u,f,false)}if(k){i=k?this.F[this.d.id]:this.F[f.id];this.B(e,i,true)}}}v=h[m.id];r=h[A.id];this.z(v,r,true)}
;g.prototype.B=function(a,b,c){var d=false;while(a){if(c){d=this.f.add(this.b[a.id])}else{d=this.f.remove(this.b[a.id])}if(d){this.C(a,c)}if(a.id===b.id)break;a=this.t[a.id]}}
;g.LAST_DAY_OF_WEEK={4:"day_3_6",5:"day_4_6",6:"day_5_6",7:"day_6_6"};g.prototype.Rb=function(a){if(a){this.o={};this.o.x=a.x;this.o.y=a.y}else{this.o=null}}
;g.prototype.Pa=function(a,b){if(!this.o)return;if(b){a.x-=this.o.x;a.y-=this.o.y}else{a.x+=this.o.x;a.y+=this.o.y}}
;g.prototype.Y=function(a){var b=Xa(this.n);var c=Va(this.n);var d=this.db();var e=Ja(a);this.Pa(e);var f=7;var h=this.za(d.x,b,f,e.x);var l=this.za(d.y,c,this.A,e.y);return z(this.g+"day_"+l+"_"+h)}
;g.prototype.za=function(a,b,c,d){if(d<a)return 0;var e=Math.floor((d-a)/b);return e>=c?c-1:e}
;g.prototype.db=function(){var a=this.g;var b=this.A;var c=Wa(this.n);var d=Ua(z(a+g.LAST_DAY_OF_WEEK[b]));return new L(c.x,c.y,d.x+d.w-c.x,d.y+d.h-c.y,c.coordinateFrame)}
;g.prototype.$=function(a,b){var c=b?aa:Z;return c[a.month]+" "+a.date}
;g.prototype.z=function(a,b,c){var d={};d.startDate=a;d.endDate=b||a;d.fb=!(!c);d.mode=this.aa();this.ka.Z(d)}
;g.prototype.Gb=function(){return this.j}
;g.prototype.Tb=function(a){if(a.equals(this.j))return;this.j=a;this.p()}
;g.prototype.va=function(a){if(a instanceof j)return a;if(a instanceof t){return j.create(a.year,a.month,a.date)}}
;g.prototype.Qb=function(a){this.ya=!(!a)}
;g.prototype.ma=function(a,b,c){var d=this.aa();c=c!==false;if(a)a=this.va(a);if(b)b=this.va(b);if(a)this.Ia(a);if(!a||d==3){this.u(c);return}if(d==0){this.u(false);var e=this.J[a.toString()];this.f.add(a);this.C(e);if(c)this.z(a)}else if(d==1){if(!b)b=a;var f=sa(b,a);var h=false;if(this.R()&&f>=7){var l=fa(a)+7;var n=fa(b)+7;l=(l-this.Aa())%7;n=(n-this.Aa())%7;var k;k=$(a.year,a.month,a.date-l);a=k.i();k=$(b.year,b.month,b.date+(6-n));b=k.i();h=this.Ia(a)}if(h){this.u(false)}var e=this.n;this.I=
a;this.H=b;var q=this.N;var s=a.a();var u=b.a();var i=new G;for(;e;e=this.t[e.id]){var v=this.b[e.id];var r=this.f.contains(v);var m=v.a()>=s&&v.a()<=u;if(r!=m){this.C(e,m)}if(m){i.add(v)}}this.f=i;if(this.b[q.id].a()<u){q=this.N;var k=Q(this.b[this.N.id]);var A=null;do{k.date+=1;A=k.i();this.f.add(A)}while(!A.equals(b))}if(c)this.z(a,b)}}
;g.prototype.oa=function(a,b,c){if(this.r.month==a.month&&this.r.year==a.year&&!c)return false;b=arguments.length==1||b;this.r=j.create(a.year,a.month,1);this.p();if(b)this.ga.Z();return true}
;g.prototype.Za=function(){return this.r}
;g.prototype.Ia=function(a,b){if(a.a()>=this.ta.a()&&a.a()<=this.sa.a()){return false}return this.oa(a,b)}
;g.prototype.cb=function(){switch(this.aa()){case 0:if(this.f.s()){return this.f.U()[0]}else{return null}case 1:var a=this.I?this.I:null;var b=this.H?this.H:null;if(!a||!b)return null;return[a,b];case 2:return null;case 3:default:return null}}
;g.prototype.Db=function(){return this.f.s()}
;g.prototype.jb=function(a){if(this.pa){z(this.g+"sel").innerHTML=a}}
;g.prototype.C=function(a,b){if(this.ya||!a)return;if(!(typeof b!="undefined"))b=true;var c=[];var d=[];var e=" "+a.className+" ";var f=" "+this.c;if(b){if(-1!=e.indexOf(f+"today ")){c.push(f+"today ");d.push(f+"today_selected ")}if(-1!=e.indexOf(f+"weekday ")){c.push(f+"weekday ");d.push(f+"weekday_selected ")}else if(-1!=e.indexOf(f+"weekend ")){c.push(f+"weekend ");d.push(f+"weekend_selected ")}}else{if(-1!=e.indexOf(f+"today_selected ")){d.push(f+"today ");c.push(f+"today_selected ")}if(-1!=e.indexOf(
f+"weekday_selected ")){d.push(f+"weekday ");c.push(f+"weekday_selected ")}else if(-1!=e.indexOf(f+"weekend_selected ")){d.push(f+"weekend ");c.push(f+"weekend_selected ")}}for(var h=0;h<c.length;++h){e=e.replace(c[h],d[h])}if(c.length!=0){a.className=e}}
;g.prototype.wb=function(a){this.ga.add(a)}
;g.prototype.Mb=function(a){this.ga.remove(a)}
;g.K=new Object;g.prototype.ub=function(){return this.g}
;g.staticGetPickerById=function(a){return g.K[a]}
;function Ha(a){var b=g.K[a];return b.oa(b.Fa)}
function Ga(a){var b=g.K[a];return b.oa(b.Ea)}
function Fa(a){var b=a.id;var c=b.match(/(.*)day_\d+_\d+/);var d=g.K[c[1]];return d.Sa(b)}
g.prototype.Oa=function(){if(this.hb===true)return;this.hb=true}
;g.prototype.vb=function(a){return this.fa.add(a)}
;g.prototype.Lb=function(a){return this.fa.remove(a)}
;g.prototype.log=function(){this.fa.Z(arguments)}
;g.prototype.zb=function(){return this.Q}
;function G(){this.q={};this.S=0}
G.prototype.s=function(){return this.S}
;G.prototype.add=function(a){var b=this.ra(a);if(b in this.q)return false;this.q[b]=a.i();++this.S;return true}
;G.prototype.remove=function(a){var b=this.ra(a);if(!(b in this.q))return false;delete this.q[b];--this.S;return true}
;G.prototype.clear=function(a){this.q={};this.S=0}
;G.prototype.contains=function(a){var b=this.ra(a);return b in this.q}
;G.prototype.U=function(){var a=new Array(this.s());var b=-1;for(var c in this.q)a[++b]=this.q[c];return a}
;G.prototype.ra=function(a){return a.toString().substr(0,9)}
;function J(a){this.kb=a;this.k=[]}
J.prototype.add=function(a){if(!a)return false;for(var b=0;b<this.k.length;++b){if(a===this.k[b])return false}this.k.push(a);return true}
;J.prototype.remove=function(a){if(!a)return false;for(var b=0;b<this.k.length;++b){if(a===this.k[b]){this.k.splice(b,1);return true}}return false}
;J.prototype.Z=function(){for(var a=0;a<this.k.length;++a){this.k[a].apply(this.kb,arguments)}}
;J.prototype.s=function(){return this.k.length}
;J.prototype.iterator=function(){return new V(this)}
;function V(a){this.ea=a;this.ca=0;this.v=null}
V.prototype.eb=function(){return this.ca<this.ea.s()}
;V.prototype.next=function(){if(this.eb()){this.v=this.ea.k[this.ca++]}else{this.v=null}return this.v}
;V.prototype.current=function(){return this.v}
;V.prototype.remove=function(){if(!this.v)throw new Error("no current element!");this.ea.remove(this.v);this.v=null;--this.ca}
;var x=null;function _InitializeDatePicker(a,b,c,d,e){var f=z(a);x=new g(f,false);x.qb(true);x.ob(function(){return"&laquo;"}
);x.nb(function(){return"&raquo;"}
);x.lb(b);x.Ga(1,c);x.mb(d["Sunday"]);x.ua(e);z("pickerContainer").style.display="";x.show()}
function _DatePickerSetSelection(a){x.ma(a)}
function _DatePickerGetSelection(){return x.cb()}
function _DatePickerGetDateForCell(a){return x.Xa(a)}
function _DatePickerGetMonths(){return x.bb()}
function _DatePickerGetFirstDate(){return x.$a()}
function _DatePickerGetLastDate(){return x.ab()}
function _DatePickerPopulateHtml(){return x.p()}
function _DatePickerIsVisible(){return x.gb()}
function _DatePickerGetDisplayedMonth(){return x.Za()}
function _ICAL_Date_create(a,b,c){return j.create(a,b,c)}
function _ICAL_ToDate(a){return a.i()}
function _ICAL_GetComparable(a){return a.a()}
var _ICAL_DateTime=t;var _forid=z;var _ICAL_todaysDate=S;var _ical_builderCopy=Q;var _ToJSString=ma;
+457
View File
@@ -0,0 +1,457 @@
<html>
<head>
<title>Google Calendar AuthSub demo</title>
<!--
This is the page that uses the AuthSub token retrieved from Google for the
respective user. The web service will redirect to this page once an
AuthSub token has been successfully retrieved.
This page makes the following assumptions:
- User has been assigned an login cookie by the web service
- The web service has retrieved from Google a valid AuthSub token for the
respective user
This page displays a calendar widget. The widget is populated with
information retrieved from Google Calendar using the user's private default
feed.
Note: The URL is secured on the server using the token generated in the
formProxyFeedUrl() function.
-->
<link rel="stylesheet" type="text/css" href="css/google.css">
<link rel="stylesheet" type="text/css" href="css/frameless.css">
<!--[if IE]>
<link rel="stylesheet" type="text/css" href="css/ieonly.css" />
<![endif]-->
<link href="css/datepicker.css" type="text/css" rel="stylesheet">
<link href="css/pbox.css" type="text/css" rel="stylesheet">
<script language="JavaScript" type="text/javascript"
src="javascript/util.js" ></script>
<script language="JavaScript" type="text/javascript"
src="javascript/gdata_condensed.js" ></script>
<script language="JavaScript" type="text/javascript">
var dateData = {};
var firstDayMap = {
Sunday : 0,
Monday : 1,
Saturday : 6
};
// The following function generates the URL for the proxy. A secure token
// is first generated using JSP. The token is appended as a query
// parameter to the RetrieveFeed request
function formProxyFeedUrl() {
<%
String feedUrl = (String)(request.getSession(false)).getAttribute("feedRootUrl")
+ "default/private/full";
// Secure the URL by generating a secure token before sending the request
// to the proxy server
long currentTimeSeconds = ((new java.util.Date()).getTime()) / 1000;
String cookie = sample.authsub.src.Utility.getCookieValueWithName(
request.getCookies(), sample.authsub.src.Utility.LOGIN_COOKIE_NAME);
String token = sample.authsub.src.SecureUrl.generateToken(cookie,
feedUrl,
"GET",
currentTimeSeconds);
// Pass values to javascript
feedUrl = "\"" + feedUrl + "\"";
token = "\"" + token + "\"";
%>
return "/authsub_sample/RetrieveFeedServlet?href=\"" + <%=feedUrl%>
+ "\"&timestamp=\"" + <%=currentTimeSeconds%> + "\"&token=\"" +
<%=token%> +"\"";
}
// @param date {ICAL_Date}
// @return {GD_Entry[]} entries for date, sorted
function getEntriesFor(date) {
var data = dateData[date];
var entries = [];
for (var e in data) entries.push(data[e]);
entries.sort(compareEntry);
return entries;
}
// a before b <=> -1
// @param a {GD_Entry}
// @param b {GD_Entry}
// @return {number} satisfying "Comparable" contract for GD_Entry
function compareEntry(a, b) {
var cmp = IsAllDay(b) - IsAllDay(a);
if (cmp) return cmp;
cmp = _ICAL_GetComparable(parseTemporal(a.getStartTime())) -
_ICAL_GetComparable(parseTemporal(b.getStartTime()));
if (cmp) return cmp;
var at = a.getTitle(), bt = b.getTitle();
if (at < bt) return -1;
return (at == bt) ? 0 : 1;
}
function AgendaPopup() {
this.div_ = _forid('agendaDiv');
document.body.appendChild(this.div_);
this.isVisible_ = false;
}
// @param date {ICAL_Date}
// @param cell {Element}
AgendaPopup.prototype.show = function(date, cell) {
_DatePickerSetSelection(date);
}
function _OpenUrl(url) {
open(url, "_BLANK");
}
var HOVER_LINK_JS = ' onmouseout="this.style.textDecoration=\'none\'" ' +
'onmouseover="this.style.textDecoration=\'underline\'" ';
// @param date {ICAL_Date}
// @param entry {GD_Entry}
// @param html {string[]}
function AddEventHtml(date, entry, html) {
html.push("<TR>");
var title = entry.getTitle();
if (IsAllDay(entry)) {
html.push('<TD colspan="2" ',
'style="background-color:#668CD9; color:white; width:100%">');
} else {
html.push('<TD style="text-align: right;" class="eventChip">');
var titlePrefix = "";
var startDateTime = parseDateTime(entry.getStartTime());
if (_ICAL_ToDate(startDateTime).equals(date)) {
// event starts today! show start time
titlePrefix = HumanTime(startDateTime) + "&nbsp;";
} else {
}
html.push(titlePrefix, '</TD><TD style="width:100%">');
}
var onclick = "\"_OpenUrl(" + _ToJSString(entry.getEventPageUrl()) + ")\"";
html.push('<SPAN onclick=', onclick, HOVER_LINK_JS,
'style="cursor:pointer" class="eventChip">', title, '</SPAN>');
html.push("</TD></TR>");
}
var gAgendaPopup;
function _GetAgendaPopup() {
if (!gAgendaPopup) gAgendaPopup = new AgendaPopup();
return gAgendaPopup;
}
// @param cellId {string}
function popupAgenda(cellId) {
var cell = _forid(cellId);
var dt = _DatePickerGetDateForCell(cell);
_GetAgendaPopup().show(dt, cell);
}
// authentication token for reading XAPI feeds
var token;
// @param start {ICAL_Date}
// @param end {ICAL_Date}
function calendarInlineDecorator(start, end) {
var decs = {};
var builder = _ical_builderCopy(start);
for (var dt = _ICAL_ToDate(builder);
_ICAL_GetComparable(dt) <= _ICAL_GetComparable(end);
builder.date += 1, dt = _ICAL_ToDate(builder)) {
var str = dt.toString();
if (str in dateData) decs[str] = 'font-weight:bold';
}
return decs;
}
// @param date {ICAL_Date}
// @return {string} XAPI Date
function formatDate(date) {
var m = date.month;
if (m < 10) m = "0" + m;
var d = date.date;
if (d < 10) d = "0" + d;
return date.year + "-" + m + "-" + d;
}
// @param date {ICAL_Date}
// @return mm dd[, yyyy]
function HumanDate(date) {
var humanDate = _DatePickerGetMonths()[date.month] + " " + date.date;
if (_ICAL_todaysDate.year !== date.year) {
humanDate += ", " + date.year;
}
return humanDate;
}
// @param time {ICAL_DateTime}
// @return hh:mm{a,p}m
function HumanTime(time) {
var h = time.hour;
var m = time.minute;
if (m < 10) m = "0" + m;
var a = (h < 12) ? "am" : "pm";
if (h > 12) {
h -= 12;
} else if (h === 0) {
h = 12;
}
return h + ":" + m + a;
}
// @param yyyy_mm_dd_date {string}
function parseDate(yyyy_mm_dd_date) {
var parts = yyyy_mm_dd_date.substring(0, 10).split('-');
for (var i = 0; i < 3; ++i) {
parts[i] = parseInt(parts[i], 10);
}
return _ICAL_Date_create(parts[0], parts[1], parts[2]);
}
function parseDateTime(str) {
var m = str.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/);
for(var i = 1; i < 7; ++i) {
m[i] = parseInt(m[i], 10);
}
return new _ICAL_DateTime(m[1], m[2], m[3], m[4], m[5], m[6]);
}
function parseTemporal(str) {
if (str.length === 10) return parseDate(str);
return parseDateTime(str);
}
// @param month {ICAL_Date}
function loadEventsForMonth(month) {
loadEventsForRange(_DatePickerGetFirstDate(), _DatePickerGetLastDate());
}
// @param start {ICAL_Date}
// @param end {ICAL_Date}
function loadEventsForRange(start, end) {
loadFeed(formatDate(start), formatDate(end));
}
function IsAllDay(entry) {
if ('allday' in entry) return entry.allday;
var allday = (entry.getStartTime().length == 10);
return (entry.allday = allday);
}
// @param start {string}
// @param end {string}
function loadFeed(start, end) {
var feedUrl = formProxyFeedUrl();
new GD_EventFeed(feedUrl, createLoadFeedCallback());
}
function createLoadFeedCallback() {
return function(feed) {
var entries = feed.getEntries();
for (var i = 0; i < entries.length; ++i) {
var entry = entries[i];
var status = entry.getEventStatus();
if (status == GD_EventEntry.CANCELED) continue;
var startTime = entry.getStartTime();
var endTime = entry.getEndTime();
var startDate = parseDate(startTime);
var endDate = parseDate(endTime);
var endDateCmp;
var builder;
if (IsAllDay(entry)) {
endDateCmp = _ICAL_GetComparable(endDate);
} else {
builder = _ical_builderCopy(endDate);
builder.date += 1;
endDateCmp = _ICAL_GetComparable(_ICAL_ToDate(builder));
}
builder = _ical_builderCopy(startDate);
for (var dt = _ICAL_ToDate(builder);
_ICAL_GetComparable(dt) < endDateCmp;
builder.date += 1, dt = _ICAL_ToDate(builder)) {
var events;
if (dt in dateData) {
events = dateData[dt];
} else {
events = {};
dateData[dt] = events;
}
events[entry.getId()] = entry;
}
}
_DatePickerPopulateHtml();
}
}
function firstLoad() {
if (_DatePickerIsVisible()) {
loadEventsForMonth(_DatePickerGetDisplayedMonth());
} else {
var today = _ICAL_todaysDate;
var builder = _ical_builderCopy(_ICAL_todaysDate);
var start, end;
builder.date -= 14;
start = _ICAL_ToDate(builder);
builder.date += 28;
end = _ICAL_ToDate(builder);
// load 2 weeks before and after the current date
loadEventsForRange(start, end);
}
_nav(0);
}
/**
* Navigate the current selection on the datepicker.
* @param period {int} must be -1, 0, or 1;
* corresponds to previous day, today, next day
*/
function _nav(period) {
if (period === 0) {
// go to today
_DatePickerSetSelection(_ICAL_todaysDate);
} else {
var selectedDate = _DatePickerGetSelection();
var builder = _ical_builderCopy(selectedDate);
builder.date += period;
_DatePickerSetSelection(_ICAL_ToDate(builder));
}
}
function handleDatePickerSelection(event) {
populateAgenda(event.startDate);
}
var gAgendaInitialized = false;
/**
* Initializes the agenda view.
*/
function initAgenda() {
if (!gAgendaInitialized) {
_forid('prevB').src = "images/btn_prev.gif";
_forid('nextB').src = "images/btn_next.gif";
_forid('agenda').style.display = ''; // block
gAgendaInitialized = true;
}
return true;
}
function populateAgenda(opt_date) {
if (!initAgenda()) return;
var date = opt_date || _ICAL_todaysDate;
var entries = getEntriesFor(date);
var html = [];
if (entries.length) {
for (var i = 0; i < entries.length; ++i) {
var entry = entries[i];
AddEventHtml(date, entry, html);
}
} else {
html.push("<TR><TD><I>Nothing to do today!</I></TD></TR>");
}
_forid('selectedDate').innerHTML = HumanDate(date);
_forid('agendaTable').innerHTML = html.join("");
}
function MyOnLoad() {
_InitializeDatePicker('picker', calendarInlineDecorator, popupAgenda,
firstDayMap, handleDatePickerSelection);
firstLoad();
}
</script>
</head>
<body onload="MyOnLoad()">
<div class="leftcontent">
<p class="topimage">
<a href="http://www.google.com/">
<img src="http://www.google.com/images/google_sm.gif" border="0"
alt="Return to Google homepage" /></a></p>
</div>
<div class="rightcontent">
<p class=title>Google AuthSub Demo </p>
<div class="content">
<h1>Google AuthSub Demo</h1>
<p>This page demonstrates a basic version of AuthSub in action.</p>
<p>The AuthSub token is first retrieved from Google and then used
to retrieve your Calendar feed.
</p>
<center>
<table width="65%" border="3">
<tr>
<td height="100%" valign="top">
<center>
<div id="pickerContainer" align="center" style="display:none; width: 13em">
<div id="picker"></div>
</div>
</center>
</td>
</tr>
<tr>
<td height="100%" valign="top">
<center>
<div id="agenda" style="display:none">
<table width="100%" cellspacing="0" cellpadding="0" id="navButtons"><tr>
<td><div align="center" valign="middle">
<img id="prevB" width="33px" height="17px" onmousedown="_nav(-1)"></div></td>
<td><div align="center" valign="middle">
<img id="nextB" width="33px" height="17px" onmousedown="_nav(1)"></div></td>
<td><div align="center" valign="middle">
<button id="todayB" onmousedown="_nav(0)">Today</button></div></td>
<td id="selectedDate" style="padding-left: 1em; font-weight: bold;"></td>
</tr></table>
<table width="100%" id="agendaTable" cellspacing="1"></table>
</div>
</center>
</td>
</tr>
</table>
</center>
<div id="agendaDiv"
style="display:none; position:absolute; width: 20em; background-color: #666666;
padding: 3px 0px 2px 0px; font-size: 83%">
<div style="border: 1px solid gray; background-color:white; width: 100%; margin: -6px 0 0 -4px;">
</div>
</div>
</div>
</div>
<div class="leftcontent">
<p class="footerimage"><img src="http://www.google.com/images/art.gif"></p>
</div>
<div class="rightcontent">
<p><img src="http://www.google.com/images/cleardot.gif" width="1" height="45"></p>
</div>
</body>
</html>