1 /* ***** BEGIN LICENSE BLOCK *****
  2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3  *
  4  * The contents of this file are subject to the Mozilla Public License Version
  5  * 1.1 (the "License"); you may not use this file except in compliance with
  6  * the License. You may obtain a copy of the License at
  7  * http://www.mozilla.org/MPL/
  8  *
  9  * Software distributed under the License is distributed on an "AS IS" basis,
 10  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 11  * for the specific language governing rights and limitations under the
 12  * License.
 13  *
 14  * The Original Code is gContactSync.
 15  *
 16  * The Initial Developer of the Original Code is
 17  * Josh Geenen <gcontactsync@pirules.org>.
 18  * Portions created by the Initial Developer are Copyright (C) 2008-2009
 19  * the Initial Developer. All Rights Reserved.
 20  *
 21  * Contributor(s):
 22  *
 23  * Alternatively, the contents of this file may be used under the terms of
 24  * either the GNU General Public License Version 2 or later (the "GPL"), or
 25  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 26  * in which case the provisions of the GPL or the LGPL are applicable instead
 27  * of those above. If you wish to allow use of your version of this file only
 28  * under the terms of either the GPL or the LGPL, and not to allow others to
 29  * use your version of this file under the terms of the MPL, indicate your
 30  * decision by deleting the provisions above and replace them with the notice
 31  * and other provisions required by the GPL or the LGPL. If you do not delete
 32  * the provisions above, a recipient may use your version of this file under
 33  * the terms of any one of the MPL, the GPL or the LGPL.
 34  *
 35  * ***** END LICENSE BLOCK ***** */
 36 
 37 if (!com) var com = {}; // A generic wrapper variable
 38 // A wrapper for all GCS functions and variables
 39 if (!com.gContactSync) com.gContactSync = {};
 40 
 41 /**
 42  * Sets up an HTTP request to send to Google.
 43  * After calling this constructor and setting up any additional data, call the
 44  * send method.
 45  * 
 46  * @param aType      {string} The type of request.  Must be one of the following
 47  *                            authenticate, getAll, get, update, add, delete,
 48  *                            getGroups
 49  * @param aAuth      {string} The authorization token.
 50  * @param aUrl       {string} The url for the request, if unique for the type of
 51  *                            request.  Not required for authenticate, getAll,
 52  *                            getGroups, and add.
 53  * @param aBody      {string} The body of the request.
 54  * @param aUsername  {string} Optional.  Replaces "default" in the URL.
 55  * @param aOther     {string} Additional parameter to use when needed.
 56  *                            Currently this is only used for GET requests for
 57  *                            obtaining contacts in a specified group (pass the
 58  *                            Group ID in that case)
 59  * @constructor
 60  * @class
 61  * @extends com.gContactSync.HttpRequest
 62  */
 63 com.gContactSync.GHttpRequest = function gCS_GHttpRequest(aType, aAuth, aUrl, aBody, aUsername, aOther) {
 64   com.gContactSync.HttpRequest.call(this);  // call the superclass' constructor
 65   this.mBody = aBody;
 66   // all urls in gdata use SSL.  If a URL is supplied, make sure it uses SSL
 67   if (aUrl && aUrl.indexOf("https://") < 0)
 68     aUrl = aUrl.replace("http://", "https://");
 69   switch (aType) {
 70   case "AUTH_SUB_SESSION":
 71   case "authsubsession":
 72     this.mContentType = this.CONTENT_TYPES.URL_ENC;
 73     this.mUrl         = com.gContactSync.gdata.AUTH_SUB_SESSION_URL;
 74     this.mType        = com.gContactSync.gdata.AUTH_SUB_SESSION_TYPE;
 75     break;
 76   case "AUTHENTICATE":
 77   case "authenticate":
 78     this.mContentType = this.CONTENT_TYPES.URL_ENC;
 79     this.mUrl         = com.gContactSync.gdata.AUTH_URL;
 80     this.mType        = com.gContactSync.gdata.AUTH_REQUEST_TYPE;
 81     break;
 82   case "GETALL":
 83   case "getAll":
 84     this.mContentType = this.CONTENT_TYPES.ATOM;
 85     this.mUrl         = com.gContactSync.gdata.contacts.GET_ALL_URL +
 86                         com.gContactSync.Preferences.mSyncPrefs.maxContacts
 87                                                                    .value;
 88     this.mType        = com.gContactSync.gdata.contacts.requestTypes.GET_ALL;
 89     this.addHeaderItem("Authorization", aAuth);
 90     break;
 91   case "getFromGroup":
 92     this.mContentType = this.CONTENT_TYPES.ATOM;
 93     this.mUrl         = com.gContactSync.gdata.contacts.GET_ALL_URL +
 94                         com.gContactSync.Preferences.mSyncPrefs.maxContacts.value +
 95                         "&group=" + encodeURIComponent(aOther);
 96     this.mType        = com.gContactSync.gdata.contacts.requestTypes.GET_ALL;
 97     this.addHeaderItem("Authorization", aAuth);
 98     break;
 99   case "GETGROUPS":
100   case "getGroups":
101     this.mContentType = this.CONTENT_TYPES.ATOM;
102     this.mUrl         = com.gContactSync.gdata.contacts.GROUPS_URL;
103     this.mType        = com.gContactSync.gdata.contacts.requestTypes.GET;
104     this.addHeaderItem("Authorization", aAuth);
105     break;
106   case "GET":
107   case "get":
108     this.mContentType = this.CONTENT_TYPES.ATOM;
109     this.mUrl         = aUrl; // the URL is unique and needs to be passed in
110     this.mType        = com.gContactSync.gdata.contacts.requestTypes.GET;
111     this.addHeaderItem("Authorization", aAuth);
112     break;
113   case "UPDATE":
114   case "update":
115   case "updategroup":
116     this.mContentType = this.CONTENT_TYPES.ATOM;
117     this.mUrl         = aUrl;
118     this.mType        = "POST";  // for firewalls that block PUT requests
119     this.addContentOverride(com.gContactSync.gdata.contacts.requestTypes.UPDATE);
120     this.addHeaderItem("Authorization", aAuth);
121     break;
122   case "ADD":
123   case "add":
124     this.mContentType = this.CONTENT_TYPES.ATOM;
125     this.mUrl         = com.gContactSync.gdata.contacts.ADD_URL;
126     this.mType        = com.gContactSync.gdata.contacts.requestTypes.ADD;
127     this.addHeaderItem("Authorization", aAuth);
128     break;
129   case "addGroup":
130     this.mContentType = this.CONTENT_TYPES.ATOM;
131     this.mUrl         = com.gContactSync.gdata.contacts.ADD_GROUP_URL;
132     this.mType        = com.gContactSync.gdata.contacts.requestTypes.ADD;
133     this.addHeaderItem("Authorization", aAuth);
134     break;
135   case "DELETE":
136   case "delete":
137     this.mContentType = this.CONTENT_TYPES.URL_ENC;
138     this.mUrl         = aUrl;
139     this.mType        = "POST"; // for firewalls that block DELETE
140     this.addContentOverride(com.gContactSync.gdata.contacts.requestTypes.DELETE);
141     this.addHeaderItem("Content-length", 0); // required or there will be an error
142     this.addHeaderItem("Authorization", aAuth);
143     break;
144   default:
145     // if the input doesn't match one of the above throw an error
146     throw "Invalid aType parameter supplied to the " +
147           "com.gContactSync.GHttpRequest constructor" +
148           com.gContactSync.StringBundle.getStr("pleaseReport");
149   }
150   // use version 3 of the contacts api
151   this.addHeaderItem("GData-Version", "3");
152   // handle Token Expired errors
153   this.mOn401 = com.gContactSync.handle401;
154   if (!this.mUrl)
155     throw "Error - no URL was found for the HTTP Request";
156   if (aUsername && this.mUrl)
157     this.mUrl = this.mUrl.replace("default",
158                                   encodeURIComponent(com.gContactSync.fixUsername(aUsername)));
159 };
160 
161 // get the superclass' prototype
162 com.gContactSync.GHttpRequest.prototype = new com.gContactSync.HttpRequest();
163 
164 /**
165  * Handles 'Token Expired' errors.
166  * If a sync is in progress:
167  *  - Get the username
168  *  - Remove the auth token
169  *  - Alert the user
170  *  - Prompt for the password
171  *  - Get a new auth token to replace the old one
172  *  - Restart the sync
173  */
174 com.gContactSync.handle401 = function gCS_handle401(httpRequest) {
175   com.gContactSync.LOGGER.LOG("***Found an expired token***");
176   // If there is a synchronization in process
177   if (com.gContactSync.Preferences.mSyncPrefs.synchronizing.value) {
178     // Get the current username
179     var username = com.gContactSync.Sync.mCurrentUsername;
180     // Remove the auth token if it wasn't already
181     if (com.gContactSync.LoginManager.mAuthTokens[username]) {
182       com.gContactSync.LOGGER.VERBOSE_LOG(" * Removing old auth token");
183       com.gContactSync.LoginManager.removeAuthToken(username);
184     }
185     com.gContactSync.alertWarning(com.gContactSync.StringBundle.getStr("tokenExpiredMsg"));
186     // Prompt for the username and password
187     var prompt   = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
188                              .getService(Components.interfaces.nsIPromptService)
189                              .promptUsernameAndPassword,
190         password = {};
191     // set the username
192     username = { value: username };
193     com.gContactSync.LOGGER.VERBOSE_LOG(" * Showing a username/password prompt");
194     // opens a username/password prompt
195     var ok = prompt(window, com.gContactSync.StringBundle.getStr("loginTitle"),
196                     com.gContactSync.StringBundle.getStr("loginText"), username,
197                     password, null, {value: false});
198     if (!ok) {
199       com.gContactSync.LOGGER.VERBOSE_LOG(" * User canceled the prompt");
200       com.gContactSync.Sync.finish(com.gContactSync.StringBundle.getStr("tokenExpired"), false);
201       return false;
202     }
203     // This is a primitive way of validating an e-mail address, but Google takes
204     // care of the rest.  It seems to allow getting an auth token w/ only the
205     // username, but returns an error when trying to do anything w/ that token
206     // so this makes sure it is a full e-mail address.
207     if (username.value.indexOf("@") < 1) {
208       com.gContactSync.alertError(com.gContactSync.StringBundle.getStr("invalidEmail"));
209       return com.gContactSync.handle401();
210     }
211     // fix the username before authenticating
212     username.value = com.gContactSync.fixUsername(username.value);
213     var body    = com.gContactSync.gdata.makeAuthBody(username.value, password.value);
214     var httpReq = new com.gContactSync.GHttpRequest("authenticate", null, null, body);
215     // if it succeeds and Google returns the auth token, store it and then start
216     // a new sync
217     httpReq.mOnSuccess = function fix401Success(httpReq) {
218       com.gContactSync.LOGGER.VERBOSE_LOG(com.gContactSync
219                                              .serializeFromText(httpReq.responseText));
220       com.gContactSync.finish401(username.value,
221                                  httpReq.responseText.split("\n")[2]);
222     };
223     // if it fails, alert the user and prompt them to try again
224     httpReq.mOnError   = function fix401Error(httpReq) {
225       com.gContactSync.alertError(com.gContactSync.StringBundle.getStr('authErr'));
226       com.gContactSync.LOGGER.LOG_ERROR('Authentication Error - ' +
227                                          httpReq.status,
228                                          httpReq.responseText);
229       com.gContactSync.handle401();
230     };
231     // if the user is offline, alert them and quit
232     httpReq.mOnOffline = function fix401Offline(httpReq) {
233       com.gContactSync.alertError(com.gContactSync.StringBundle.getStr('offlineErr'));
234       com.gContactSync.LOGGER.LOG_ERROR(com.gContactSync.StringBundle.getStr('offlineErr'));
235     };
236     httpReq.send();
237   }
238 };
239 
240 /**
241  * Called after the re-authentication HTTP request is sent after a 401 error
242  * @param aUsername {string}  The account's username.
243  * @param aAuthToken {string} An authentication token for the account.
244  */
245 com.gContactSync.finish401 = function gCS_finish401(aUsername, aAuthToken) {
246   com.gContactSync.LOGGER.VERBOSE_LOG(" * finish401 called: " + aUsername +
247                                       " - " + aAuthToken);
248   if (aUsername && aAuthToken) {
249     var token = 'GoogleLogin ' + aAuthToken;
250     com.gContactSync.LoginManager.addAuthToken(aUsername, token);
251     com.gContactSync.Sync.mCurrentAuthToken = token;
252     if (com.gContactSync.Preferences.mSyncPrefs.syncGroups.value ||
253         com.gContactSync.Preferences.mSyncPrefs.myContacts)
254       com.gContactSync.Sync.getGroups();
255     else
256       com.gContactSync.Sync.getContacts();
257   }
258 };
259