1 /* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 *
15 * The Original Code is Sun Microsystems code.
16 *
17 * The Initial Developer of the Original Code is
18 * Sun Microsystems, Inc.
19 * Portions created by the Initial Developer are Copyright (C) 2007
20 * the Initial Developer. All Rights Reserved.
21 *
22 * Contributor(s):
23 * Daniel Boelzle <daniel.boelzle@sun.com>
24 * Philipp Kewisch <mozilla@kewis.ch>
25 *
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
37 *
38 * ***** END LICENSE BLOCK ***** */
39
calWcapTimezone
40 function calWcapTimezone(tzProvider, tzid_, component_) {
41 this.wrappedJSObject = this;
42 this.provider = tzProvider;
43 this.component = component_;
44 this.tzid = tzid_;
45 this.isUTC = false;
46 this.isFloating = false;
47 this.latitude = "";
48 this.longitude = "";
49 }
50 calWcapTimezone.prototype = {
toString
51 toString: function() {
52 // xxx todo remove: for some time, we want to know if a calITimezone object
53 // is handled as string...
54 ASSERT(false, "calWcapTimezone.toString!");
55 return this.component.toString();
56 }
57 };
58
59 var g_openWcapSessions = {};
getWcapSessionFor
60 function getWcapSessionFor(cal, uri) {
61 var contextId = cal.getProperty("shared_context");
62 if (!contextId) {
63 contextId = getUUID();
64 cal.setProperty("shared_context", contextId);
65 }
66 var session = g_openWcapSessions[contextId];
67 if (!session) {
68 session = new calWcapSession(contextId, uri);
69 g_openWcapSessions[contextId] = session;
70 // install a mandatory default calendar:
71 var defaultCal = cal;
72 for each (var regCal in session.getRegisteredCalendars()) {
73 if (regCal.isDefaultCalendar) {
74 defaultCal = regCal;
75 session.credentials.userId = defaultCal.getProperty("user_id");
76 break;
77 }
78 }
79 session.defaultCalendar = defaultCal;
80 }
81 return session;
82 }
83
calWcapSession
84 function calWcapSession(contextId, thatUri) {
85 this.wrappedJSObject = this;
86 this.m_contextId = contextId;
87 this.m_loginQueue = [];
88
89 this.m_uri = thatUri.clone();
90 this.m_sessionUri = thatUri.clone();
91 this.m_sessionUri.userPass = "";
92 log("new session", this);
93
94 // listen for shutdown, being logged out:
95 var observerService = Components.classes["@mozilla.org/observer-service;1"]
96 .getService(Components.interfaces.nsIObserverService);
97 observerService.addObserver(this, "quit-application", false /* don't hold weakly */);
98 getCalendarManager().addObserver(this);
99 }
100 calWcapSession.prototype = {
101 m_ifaces: [ calIWcapSession,
102 calIFreeBusyProvider,
103 calICalendarSearchProvider,
104 Components.interfaces.calITimezoneProvider,
105 Components.interfaces.calICalendarManagerObserver,
106 Components.interfaces.nsIInterfaceRequestor,
107 Components.interfaces.nsIClassInfo,
108 nsISupports ],
109
110 // nsISupports:
calWcapSession_QueryInterface
111 QueryInterface: function calWcapSession_QueryInterface(iid) {
112 ensureIID(this.m_ifaces, iid); // throws
113 return this;
114 },
115
116 // nsIClassInfo:
calWcapSession_getInterfaces
117 getInterfaces: function calWcapSession_getInterfaces(count)
118 {
119 count.value = this.m_ifaces.length;
120 return this.m_ifaces;
121 },
get_classDescription
122 get classDescription() {
123 return calWcapCalendarModule.WcapSessionInfo.classDescription;
124 },
get_contractID
125 get contractID() {
126 return calWcapCalendarModule.WcapSessionInfo.contractID;
127 },
get_classID
128 get classID() {
129 return calWcapCalendarModule.WcapSessionInfo.classID;
130 },
131 getHelperForLanguage:
calWcapSession_getHelperForLanguage
132 function calWcapSession_getHelperForLanguage(language) { return null; },
133 implementationLanguage:
134 Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
135 flags: 0,
136
137 // nsIInterfaceRequestor:
calWcapSession_getInterface
138 getInterface: function calWcapSession_getInterface(iid, instance) {
139 if (iid.equals(Components.interfaces.nsIAuthPrompt)) {
140 // use the window watcher service to get a nsIAuthPrompt impl
141 return getWindowWatcher().getNewAuthPrompter(null);
142 }
143 else if (iid.equals(Components.interfaces.nsIPrompt)) {
144 // use the window watcher service to get a nsIPrompt impl
145 return getWindowWatcher().getNewPrompter(null);
146 }
147 Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
148 return null;
149 },
150
calWcapSession_toString
151 toString: function calWcapSession_toString(msg)
152 {
153 var str = ("context-id: " + this.m_contextId + ", uri: " + this.uri.spec);
154 if (this.credentials.userId) {
155 str += (", userId=" + this.credentials.userId);
156 }
157 if (!this.m_sessionId) {
158 str += (getIOService().offline ? ", offline" : ", not logged in");
159 }
160 return str;
161 },
calWcapSession_notifyError
162 notifyError: function calWcapSession_notifyError(err, suppressOnError)
163 {
164 if (this.defaultCalendar) {
165 this.defaultCalendar.notifyError_(err, this, suppressOnError);
166 } else {
167 logError("no default calendar!", this);
168 logError(err, this);
169 }
170 },
171
172 // calITimezoneProvider:
173 m_serverTimezones: null,
get_timezoneIds
174 get timezoneIds() {
175 var tzids = [];
176 tzids.push("floating");
177 tzids.push("UTC");
178 for (var tz in this.m_serverTimezones) {
179 tzids.push(tz.tzid);
180 }
181 return {
182 // nsIUTF8StringEnumerator:
183 m_index: 0,
184 getNext: function() {
185 if (this.m_index >= tzids) {
186 ASSERT(false, "calWcapSession::timezoneIds enumerator!");
187 throw Components.results.NS_ERROR_UNEXPECTED;
188 }
189 return tzids[this.m_index++];
190 },
191 hasMoreElements: function() {
192 return (this.m_index < tzids);
193 }
194 };
195 },
calWcapSession_getTimezone
196 getTimezone: function calWcapSession_getTimezone(tzid) {
197 switch (tzid) {
198 case "floating":
199 return floating();
200 case "UTC":
201 return UTC();
202 default:
203 if (this.m_serverTimezones) {
204 return this.m_serverTimezones[tzid];
205 }
206 return null;
207 }
208 },
209
210 m_serverTimeDiff: null,
calWcapSession_getServerTime
211 getServerTime: function calWcapSession_getServerTime(localTime)
212 {
213 if (this.m_serverTimeDiff === null) {
214 throw new Components.Exception(
215 "early run into getServerTime()!",
216 Components.results.NS_ERROR_NOT_AVAILABLE);
217 }
218 var ret = (localTime ? localTime.clone() : getTime());
219 ret.addDuration(this.m_serverTimeDiff);
220 return ret;
221 },
222
223 m_sessionId: null,
224 m_loginQueue: null,
225 m_loginLock: false,
226
227 getSessionId:
calWcapSession_getSessionId
228 function calWcapSession_getSessionId(request, respFunc, timedOutSessionId)
229 {
230 if (getIOService().offline) {
231 log("in offline mode.", this);
232 respFunc(new Components.Exception(
233 "The requested action could not be completed while the " +
234 "networking library is in the offline state.",
235 NS_ERROR_OFFLINE));
236 return;
237 }
238
239 log("login queue lock: " + this.m_loginLock +
240 ", length: " + this.m_loginQueue.length, this);
241
242 if (this.m_loginLock) {
243 this.m_loginQueue.push(respFunc);
244 log("login queue: " + this.m_loginQueue.length);
245 }
246 else {
247 if (this.m_sessionId && this.m_sessionId != timedOutSessionId) {
248 respFunc(null, this.m_sessionId);
249 return;
250 }
251
252 this.m_loginLock = true;
253 log("locked login queue.", this);
254 this.m_sessionId = null; // invalidate for relogin
255
256 if (timedOutSessionId) {
257 log("reconnecting due to session timeout...", this);
258 getFreeBusyService().removeProvider(this);
259 getCalendarSearchService().removeProvider(this);
260 }
261
262 var this_ = this;
263 this.getSessionId_(
264 request,
265 function getSessionId_resp_(err, sessionId) {
266 log("getSessionId_resp_(): " + sessionId, this_);
267 if (!err) {
268 this_.m_sessionId = sessionId;
269 getFreeBusyService().addProvider(this_);
270 getCalendarSearchService().addProvider(this_);
271 }
272
273 var queue = this_.m_loginQueue;
274 this_.m_loginLock = false;
275 this_.m_loginQueue = [];
276 log("unlocked login queue.", this_);
277
278 function getSessionId_exec(func) {
279 try {
280 func(err, sessionId);
281 }
282 catch (exc) { // unexpected
283 this_.notifyError(exc);
284 }
285 }
286 // answer first request:
287 getSessionId_exec(respFunc);
288 // and any remaining:
289 queue.forEach(getSessionId_exec);
290 });
291 }
292 },
293
calWcapSession_getSessionId_
294 getSessionId_: function calWcapSession_getSessionId_(request, respFunc)
295 {
296 var this_ = this;
297 this.checkServerVersion(
298 request,
299 // probe whether server is accessible and responds:
300 function checkServerVersion_resp(err) {
301 if (err) {
302 respFunc(err);
303 return;
304 }
305 // lookup password manager, then try login or prompt/login:
306 log("attempting to get a session id for " + this_.sessionUri.spec, this_);
307
308 if (!this_.sessionUri.schemeIs("https") &&
309 !confirmInsecureLogin(this_.sessionUri)) {
310 log("user rejected insecure login on " + this_.sessionUri.spec, this_);
311 respFunc(new Components.Exception(
312 "Login failed. Invalid session ID.",
313 calIWcapErrors.WCAP_LOGIN_FAILED));
314 return;
315 }
316
317 var outUser = { value: this_.credentials.userId };
318 var outPW = { value: this_.credentials.pw };
319 var outSavePW = { value: false };
320
321 // pw mgr host names must not have a trailing slash
322 var passwordManager =
323 Components.classes["@mozilla.org/passwordmanager;1"]
324 .getService(Components.interfaces.nsIPasswordManager);
325 var pwHost = this_.uri.spec;
326 if (pwHost[pwHost.length - 1] == '/') {
327 pwHost = pwHost.substr(0, pwHost.length - 1);
328 }
329 if (outUser.value && !outPW.value) { // lookup pw manager
330 log("looking in pw db for: " + pwHost, this_);
331 try {
332 var enumerator = passwordManager.enumerator;
333 while (enumerator.hasMoreElements()) {
334 var pwEntry = enumerator.getNext().QueryInterface(
335 Components.interfaces.nsIPassword);
336 if (LOG_LEVEL > 1) {
337 log("pw entry:\n\thost=" + pwEntry.host +
338 "\n\tuser=" + pwEntry.user, this_);
339 }
340 if ((pwEntry.host == pwHost) &&
341 (pwEntry.user == outUser.value)){
342 // found an entry matching URI:
343 outPW.value = pwEntry.password;
344 log("password entry found for host " + pwHost +
345 "\nuser is " + outUser.value, this_);
346 break;
347 }
348 }
349 }
350 catch (exc) { // just log error
351 logError("[password manager lookup] " + errorToString(exc), this_);
352 }
353 }
354
355 function promptAndLoginLoop_resp(err, sessionId) {
356 if (checkErrorCode(err, calIWcapErrors.WCAP_LOGIN_FAILED)) {
357 log("prompting for [user/]pw...", this_);
358 var prompt = getWindowWatcher().getNewPrompter(null);
359 var bAck;
360 if (this_.credentials.userId) { // fixed user id, prompt for password only:
361 bAck = prompt.promptPassword(
362 calGetString("wcap", "loginDialog.label"),
363 calGetString("wcap", "loginDialogPasswordOnly.text",
364 [outUser.value, this_.sessionUri.hostPort]),
365 outPW,
366 (getPref("signon.rememberSignons", true)
367 ? calGetString("wcap", "loginDialog.check.text") : null),
368 outSavePW);
369 } else {
370 bAck = prompt.promptUsernameAndPassword(
371 calGetString("wcap", "loginDialog.label"),
372 calGetString("wcap", "loginDialog.text",
373 [this_.sessionUri.hostPort]),
374 outUser, outPW,
375 (getPref("signon.rememberSignons", true)
376 ? calGetString("wcap", "loginDialog.check.text") : null),
377 outSavePW);
378 }
379 if (bAck) {
380 this_.login(request, promptAndLoginLoop_resp,
381 outUser.value, outPW.value);
382 }
383 else {
384 log("login prompt cancelled.", this_);
385 respFunc(new Components.Exception(
386 "Login failed. Invalid session ID.",
387 calIWcapErrors.WCAP_LOGIN_FAILED));
388 }
389 }
390 else if (err)
391 respFunc(err);
392 else {
393 if (outSavePW.value) {
394 // so try to remove old pw from db first:
395 try {
396 passwordManager.removeUser(pwHost, outUser.value);
397 log("removed from pw db: " + pwHost, this_);
398 }
399 catch (exc) {
400 }
401 try { // to save pw under session uri:
402 passwordManager.addUser(pwHost, outUser.value, outPW.value);
403 log("added to pw db: " + pwHost, this_);
404 }
405 catch (exc) {
406 logError("[adding pw to db] " + errorToString(exc), this_);
407 }
408 }
409 this_.credentials.userId = outUser.value;
410 this_.credentials.pw = outPW.value;
411 this_.setupSession(sessionId,
412 request,
413 function setupSession_resp(err) {
414 respFunc(err, sessionId);
415 });
416 }
417 }
418
419 if (outPW.value) {
420 this_.login(request, promptAndLoginLoop_resp,
421 outUser.value, outPW.value);
422 }
423 else {
424 promptAndLoginLoop_resp(calIWcapErrors.WCAP_LOGIN_FAILED);
425 }
426 });
427 },
428
calWcapSession_login
429 login: function calWcapSession_login(request, respFunc, user, pw)
430 {
431 var this_ = this;
432 issueNetworkRequest(
433 request,
434 function netResp(err, str) {
435 var sessionId;
436 try {
437 if (err)
438 throw err;
439 // currently, xml parsing at an early stage during
440 // process startup does not work reliably, so use
441 // libical parsing for now:
442 var icalRootComp = stringToIcal(this_, str);
443 var prop = icalRootComp.getFirstProperty("X-NSCP-WCAP-SESSION-ID");
444 if (!prop) {
445 throw new Components.Exception(
446 "missing X-NSCP-WCAP-SESSION-ID in\n" + str);
447 }
448 sessionId = prop.value;
449 log("login succeeded: " + sessionId, this_);
450 }
451 catch (exc) {
452 err = exc;
453 if (checkErrorCode(err, calIWcapErrors.WCAP_LOGIN_FAILED)) {
454 log("error: " + errorToString(exc), this_); // log login failure
455 }
456 else if (getErrorModule(err) == NS_ERROR_MODULE_NETWORK) {
457 // server seems unavailable:
458 err = new Components.Exception(
459 calGetString( "wcap", "accessingServerFailedError.text",
460 [this_.sessionUri.hostPort]),
461 exc);
462 }
463 }
464 respFunc(err, sessionId);
465 },
466 this_.sessionUri.spec + "login.wcap?fmt-out=text%2Fcalendar&user=" +
467 encodeURIComponent(user) + "&password=" + encodeURIComponent(pw),
468 false /* no logging */);
469 },
470
calWcapSession_logout
471 logout: function calWcapSession_logout(listener)
472 {
473 var this_ = this;
474 var request = new calWcapRequest(
475 function logout_resp(request, err) {
476 if (err)
477 logError(err, this_);
478 else
479 log("logout succeeded.", this_);
480 if (listener)
481 listener.onResult(request, null);
482 },
483 log("logout", this));
484
485 var url = null;
486 if (this.m_sessionId) {
487 log("attempting to log out...", this);
488 // although io service's offline flag is already
489 // set BEFORE notification
490 // (about to go offline, nsIOService.cpp).
491 // WTF.
492 url = (this.sessionUri.spec + "logout.wcap?fmt-out=text%2Fxml&id=" + this.m_sessionId);
493 this.m_sessionId = null;
494 getFreeBusyService().removeProvider(this);
495 getCalendarSearchService().removeProvider(this);
496 }
497 this.m_credentials = null;
498
499 if (url) {
500 issueNetworkRequest(
501 request,
502 function netResp(err, str) {
503 if (err)
504 throw err;
505 stringToXml(this_, str, -1 /* logout successfull */);
506 }, url);
507 }
508 else {
509 request.execRespFunc();
510 }
511 return request;
512 },
513
calWcapSession_checkServerVersion
514 checkServerVersion: function calWcapSession_checkServerVersion(request, respFunc)
515 {
516 // currently, xml parsing at an early stage during process startup
517 // does not work reliably, so use libical:
518 var this_ = this;
519 issueNetworkRequest(
520 request,
521 function netResp(err, str) {
522 try {
523 var icalRootComp;
524 if (!err) {
525 try {
526 icalRootComp = stringToIcal(this_, str);
527 }
528 catch (exc) {
529 err = exc;
530 }
531 }
532 if (err) {
533 if (checkErrorCode(err, calIErrors.OPERATION_CANCELLED)) {
534 throw err;
535 } else { // soft error; request denied etc.
536 // map into localized message:
537 throw new Components.Exception(
538 calGetString("wcap", "accessingServerFailedError.text",
539 [this_.sessionUri.hostPort]),
540 calIWcapErrors.WCAP_LOGIN_FAILED);
541 }
542 }
543 var prop = icalRootComp.getFirstProperty("X-NSCP-WCAPVERSION");
544 if (!prop)
545 throw new Components.Exception("missing X-NSCP-WCAPVERSION!");
546 var wcapVersion = parseInt(prop.value);
547 if (wcapVersion < 3) {
548 var strVers = prop.value;
549 var vars = [this_.sessionUri.hostPort];
550 prop = icalRootComp.getFirstProperty("PRODID");
551 vars.push(prop ? prop.value : "<unknown>");
552 prop = icalRootComp.getFirstProperty("X-NSCP-SERVERVERSION");
553 vars.push(prop ? prop.value : "<unknown>");
554 vars.push(strVers);
555
556 var prompt = getWindowWatcher().getNewPrompter(null);
557 var labelText = calGetString(
558 "wcap", "insufficientWcapVersionConfirmation.label");
559 if (!prompt.confirm(
560 labelText,
561 calGetString("wcap", "insufficientWcapVersionConfirmation.text", vars))) {
562 throw new Components.Exception(labelText,
563 calIWcapErrors.WCAP_LOGIN_FAILED);
564 }
565 }
566 }
567 catch (exc) {
568 err = exc;
569 }
570 respFunc(err);
571 },
572 this_.sessionUri.spec + "version.wcap?fmt-out=text%2Fcalendar");
573 },
574
575 setupSession:
calWcapSession_setupSession
576 function calWcapSession_setupSession(sessionId, request_, respFunc)
577 {
578 var this_ = this;
579 var request = new calWcapRequest(
580 function setupSession_resp(request_, err) {
581 log("setupSession_resp finished: " + errorToString(err), this_);
582 respFunc(err);
583 },
584 log("setupSession", this));
585 request_.attachSubRequest(request);
586
587 request.lockPending();
588 try {
589 var this_ = this;
590 this.issueNetworkRequest_(
591 request,
592 function userprefs_resp(err, data) {
593 if (err)
594 throw err;
595 this_.credentials.userPrefs = data;
596 log("installed user prefs.", this_);
597
598 // get calprops for all registered calendars:
599 var cals = this_.getRegisteredCalendars(true);
600
601 var calprops_resp = null;
602 var defaultCal = this_.defaultCalendar;
603 if (defaultCal && cals[defaultCal.calId] && // default calendar is registered
604 getPref("calendar.wcap.subscriptions", true) &&
605 !defaultCal.getProperty("subscriptions_registered")) {
606
607 var hasSubscriptions = false;
608 // post register subscribed calendars:
609 var list = this_.getUserPreferences("X-NSCP-WCAP-PREF-icsSubscribed");
610 for each (var item in list) {
611 var ar = item.split(',');
612 // ',', '$' are not encoded. ',' can be handled here. WTF.
613 for each (var a in ar) {
614 var dollar = a.indexOf('$');
615 if (dollar >= 0) {
616 var calId = a.substring(0, dollar);
617 if (calId != this_.defaultCalId) {
618 cals[calId] = null;
619 hasSubscriptions = true;
620 }
621 }
622 }
623 }
624
625 if (hasSubscriptions) {
626 calprops_resp = function(cal) {
627 if (cal.isDefaultCalendar) {
628 // tweak name:
629 cal.setProperty("name", cal.displayName);
630 }
631 else {
632 log("registering subscribed calendar: " + cal.calId, this_);
633 getCalendarManager().registerCalendar(cal);
634 }
635 }
636 // do only once:
637 defaultCal.setProperty("account_name", defaultCal.name);
638 defaultCal.setProperty("subscriptions_registered", true);
639 }
640 }
641
642 if (!defaultCal.getProperty("user_id")) { // nail once:
643 defaultCal.setProperty("user_id", this_.credentials.userId);
644 }
645
646 if (getPref("calendar.wcap.no_get_calprops", false)) {
647 // hack around the get/search calprops mess:
648 this_.installCalProps_search_calprops(calprops_resp, sessionId, cals, request);
649 }
650 else {
651 this_.installCalProps_get_calprops(calprops_resp, sessionId, cals, request);
652 }
653 },
654 stringToXml, "get_userprefs",
655 "&fmt-out=text%2Fxml&userid=" + encodeURIComponent(this.credentials.userId),
656 sessionId);
657 this.installServerTimeDiff(sessionId, request);
658 this.installServerTimezones(sessionId, request);
659 }
660 finally {
661 request.unlockPending();
662 }
663 },
664
665 installCalProps_get_calprops:
calWcapSession_installCalProps_get_calprops
666 function calWcapSession_installCalProps_get_calprops(respFunc, sessionId, cals, request)
667 {
668 var this_ = this;
669 function calprops_resp(err, data) {
670 if (err)
671 throw err;
672 // string to xml converter func without WCAP errno check:
673 if (!data || data.length == 0) { // assuming time-out
674 throw new Components.Exception("Login failed. Invalid session ID.",
675 calIWcapErrors.WCAP_LOGIN_FAILED);
676 }
677 var xml = getDomParser().parseFromString(data, "text/xml");
678 var nodeList = xml.getElementsByTagName("iCal");
679 for (var i = 0; i < nodeList.length; ++i) {
680 try {
681 var node = nodeList.item(i);
682 checkWcapXmlErrno(node);
683 var ar = filterXmlNodes("X-NSCP-CALPROPS-RELATIVE-CALID", node);
684 if (ar.length > 0) {
685 var calId = ar[0];
686 var cal = cals[calId];
687 if (cal === null) {
688 cal = new calWcapCalendar(this_);
689 var uri = this_.uri.clone();
690 uri.path += ("?calid=" + encodeURIComponent(calId));
691 cal.uri = uri;
692 }
693 if (cal) {
694 cal.m_calProps = node;
695 if (respFunc) {
696 respFunc(cal);
697 }
698 }
699 }
700 }
701 catch (exc) { // ignore but log any errors on subscribed calendars:
702 logError(exc, this_);
703 }
704 }
705 }
706
707 var calidParam = "";
708 for (var calId in cals) {
709 if (calidParam.length > 0)
710 calidParam += ";";
711 calidParam += encodeURIComponent(calId);
712 }
713 this_.issueNetworkRequest_(request, calprops_resp,
714 null, "get_calprops",
715 "&fmt-out=text%2Fxml&calid=" + calidParam,
716 sessionId);
717 },
718
719 installCalProps_search_calprops:
calWcapSession_installCalProps_search_calprops
720 function calWcapSession_installCalProps_search_calprops(respFunc, sessionId, cals, request)
721 {
722 var this_ = this;
723 var retrievedCals = {};
724 var issuedSearchRequests = {};
725 for (var calId in cals) {
726 if (!retrievedCals[calId]) {
727 var listener = {
728 onResult: function search_onResult(request, result) {
729 try {
730 if (!Components.isSuccessCode(request.status))
731 throw request.status;
732 if (result.length < 1)
733 throw Components.results.NS_ERROR_UNEXPECTED;
734 for each (var cal in result) {
735 // user may have dangling users referred in his subscription list, so
736 // retrieve each by each, don't break:
737 try {
738 var calId = cal.calId;
739 if ((cals[calId] !== undefined) && !retrievedCals[calId]) {
740 retrievedCals[calId] = cal;
741 if (respFunc) {
742 respFunc(cal);
743 }
744 }
745 }
746 catch (exc) { // ignore but log any errors on subscribed calendars:
747 logError(exc, this_);
748 }
749 }
750 }
751 catch (exc) { // ignore but log any errors on subscribed calendars:
752 logError(exc, this_);
753 }
754 }
755 };
756
757 var colon = calId.indexOf(':');
758 if (colon >= 0) // searching for secondary calendars doesn't work. WTF.
759 calId = calId.substring(0, colon);
760 if (!issuedSearchRequests[calId]) {
761 issuedSearchRequests[calId] = true;
762 this.searchForCalendars(
763 calId, calICalendarSearchProvider.HINT_EXACT_MATCH, 20, listener);
764 }
765 }
766 }
767 },
768
769 installServerTimeDiff:
calWcapSession_installServerTimeDiff
770 function calWcapSession_installServerTimeDiff(sessionId, request)
771 {
772 var this_ = this;
773 this.issueNetworkRequest_(
774 request,
775 function netResp(err, data) {
776 if (err)
777 throw err;
778 // xxx todo: think about
779 // assure that locally calculated server time is smaller
780 // than the current (real) server time:
781 var localTime = getTime();
782 var serverTime = getDatetimeFromIcalProp(
783 data.getFirstProperty("X-NSCP-WCAPTIME"));
784 this_.m_serverTimeDiff = serverTime.subtractDate(localTime);
785 log("server time diff is: " + this_.m_serverTimeDiff, this_);
786 },
787 stringToIcal, "gettime", "&fmt-out=text%2Fcalendar",
788 sessionId);
789 },
790
791 installServerTimezones:
calWcapSession_installServerTimezones
792 function calWcapSession_installServerTimezones(sessionId, request)
793 {
794 this.m_serverTimezones = {};
795 var this_ = this;
796 this_.issueNetworkRequest_(
797 request,
798 function netResp(err, data) {
799 if (err)
800 throw err;
801 var tzids = [];
802 forEachIcalComponent(
803 data, "VTIMEZONE",
804 function eachComp(subComp) {
805 try {
806 var tzid = subComp.getFirstProperty("TZID").value;
807 this_.m_serverTimezones[tzid] = new calWcapTimezone(this_, tzid, subComp);
808 }
809 catch (exc) { // ignore but errors:
810 logError(exc, this_);
811 }
812 });
813 log("installed timezones.", this_);
814 },
815 stringToIcal, "get_all_timezones", "&fmt-out=text%2Fcalendar",
816 sessionId);
817 },
818
calWcapSession_getCommandUrl
819 getCommandUrl: function calWcapSession_getCommandUrl(wcapCommand, params, sessionId)
820 {
821 var url = this.sessionUri.spec;
822 url += (wcapCommand + ".wcap?appid=mozilla-calendar&id=");
823 url += sessionId;
824 url += params;
825 return url;
826 },
827
calWcapSession_issueNetworkRequest
828 issueNetworkRequest: function calWcapSession_issueNetworkRequest(
829 request, respFunc, dataConvFunc, wcapCommand, params)
830 {
831 var this_ = this;
832 function getSessionId_resp(err, sessionId) {
833 if (err)
834 request.execSubRespFunc(respFunc, err);
835 else {
836 // else have session uri and id:
837 this_.issueNetworkRequest_(
838 request,
839 function issueNetworkRequest_resp(err, data) {
840 // timeout?
841 if (checkErrorCode(err, calIWcapErrors.WCAP_LOGIN_FAILED)) {
842 // try again:
843 this_.getSessionId(
844 request,
845 getSessionId_resp,
846 sessionId/* (old) timed-out session */);
847 return;
848 }
849 request.execSubRespFunc(respFunc, err, data);
850 },
851 dataConvFunc, wcapCommand, params, sessionId);
852 }
853 }
854 this.getSessionId(request, getSessionId_resp);
855 },
856
calWcapSession_issueNetworkRequest_
857 issueNetworkRequest_: function calWcapSession_issueNetworkRequest_(
858 request, respFunc, dataConvFunc, wcapCommand, params, sessionId)
859 {
860 var url = this.getCommandUrl(wcapCommand, params, sessionId);
861 var this_ = this;
862 issueNetworkRequest(
863 request,
864 function netResp(err, str) {
865 var data;
866 if (!err) {
867 try {
868 if (dataConvFunc)
869 data = dataConvFunc(this_, str);
870 else
871 data = str;
872 }
873 catch (exc) {
874 err = exc;
875 }
876 }
877 request.execSubRespFunc(respFunc, err, data);
878 }, url);
879 },
880
881 m_credentials: null,
get_credentials
882 get credentials() {
883 if (!this.m_credentials) {
884 this.m_credentials = {};
885 }
886 return this.m_credentials;
887 },
888
889 // calIWcapSession:
890
891 m_contextId: null,
892 m_uri: null,
893 m_sessionUri: null,
get_uri
894 get uri() { return this.m_uri; },
get_sessionUri
895 get sessionUri() { return this.m_sessionUri; },
896
get_userId
897 get userId() { return this.credentials.userId; },
898
get_defaultCalId
899 get defaultCalId() {
900 var list = this.getUserPreferences("X-NSCP-WCAP-PREF-icsCalendar");
901 var id = null;
902 for each (var item in list) {
903 if (item.length > 0) {
904 id = item;
905 break;
906 }
907 }
908 return (id ? id : this.credentials.userId);
909 },
910
get_isLoggedIn
911 get isLoggedIn() {
912 return (this.m_sessionId != null);
913 },
914
915 defaultCalendar: null,
916
calWcapSession_belongsTo
917 belongsTo: function calWcapSession_belongsTo(cal) {
918 try {
919 // xxx todo hack to get the unwrapped wcap calendar instance:
920 cal = cal.getProperty("private.wcapCalendar")
921 .QueryInterface(calIWcapCalendar).wrappedJSObject;
922 if (cal && (cal.session.m_contextId == this.m_contextId)) {
923 return cal;
924 }
925 }
926 catch (exc) {
927 }
928 return null;
929 },
930
calWcapSession_getRegisteredCalendars
931 getRegisteredCalendars: function calWcapSession_getRegisteredCalendars(asAssocObj) {
932 var registeredCalendars = (asAssocObj ? {} : []);
933 var cals = getCalendarManager().getCalendars({});
934 for each (var cal in cals) {
935 cal = this.belongsTo(cal);
936 if (cal) {
937 if (asAssocObj) {
938 registeredCalendars[cal.calId] = cal;
939 } else {
940 registeredCalendars.push(cal);
941 }
942 }
943 }
944 return registeredCalendars;
945 },
946
calWcapSession_getUserPreferences
947 getUserPreferences: function calWcapSession_getUserPreferences(prefName) {
948 var prefs = filterXmlNodes(prefName, this.credentials.userPrefs);
949 return prefs;
950 },
951
get_defaultAlarmStart
952 get defaultAlarmStart() {
953 var alarmStart = null;
954 var ar = this.getUserPreferences("X-NSCP-WCAP-PREF-ceDefaultAlarmStart");
955 if (ar.length > 0 && ar[0].length > 0) {
956 // workarounding cs duration bug, missing "T":
957 var dur = ar[0].replace(/(^P)(\d+[HMS]$)/, "$1T$2");
958 alarmStart = new CalDuration();
959 alarmStart.icalString = dur;
960 alarmStart.isNegative = !alarmStart.isNegative;
961 }
962 return alarmStart;
963 },
964
calWcapSession_getDefaultAlarmEmails
965 getDefaultAlarmEmails: function calWcapSession_getDefaultAlarmEmails(out_count)
966 {
967 var ret = [];
968 var ar = this.getUserPreferences("X-NSCP-WCAP-PREF-ceDefaultAlarmEmail");
969 if (ar.length > 0 && ar[0].length > 0) {
970 for each (var i in ar) {
971 ret = ret.concat( i.split(/[;,]/).map(trimString) );
972 }
973 }
974 out_count.value = ret.length;
975 return ret;
976 },
977
978 // calICalendarSearchProvider:
979 searchForCalendars:
calWcapSession_searchForCalendars
980 function calWcapSession_searchForCalendars(searchString, hints, maxResults, listener)
981 {
982 var this_ = this;
983 var request = new calWcapRequest(
984 function searchForCalendars_resp(request, err, data) {
985 if (err && !checkErrorCode(err, calIErrors.OPERATION_CANCELLED)) {
986 this_.notifyError(err);
987 }
988 if (listener) {
989 listener.onResult(request, data);
990 }
991 },
992 log("searchForCalendars, searchString=" + searchString, this));
993
994 try {
995 var registeredCalendars = this.getRegisteredCalendars(true);
996
997 var params = ("&fmt-out=text%2Fxml&search-string=" +
998 encodeURIComponent(searchString));
999 if (maxResults > 0) {
1000 params += ("&maxResults=" + maxResults);
1001 }
1002 params += ("&name=1&calid=1&primaryOwner=1&searchOpts=" +
1003 ((hints & calICalendarSearchProvider.HINT_EXACT_MATCH) ? "3" : "0"));
1004
1005 this.issueNetworkRequest(
1006 request,
1007 function searchForCalendars_netResp(err, data) {
1008 if (err)
1009 throw err;
1010 // string to xml converter func without WCAP errno check:
1011 if (!data || data.length == 0) { // assuming time-out
1012 throw new Components.Exception("Login failed. Invalid session ID.",
1013 calIWcapErrors.WCAP_LOGIN_FAILED);
1014 }
1015 var xml = getDomParser().parseFromString(data, "text/xml");
1016 var ret = [];
1017 var nodeList = xml.getElementsByTagName("iCal");
1018 for ( var i = 0; i < nodeList.length; ++i ) {
1019 var node = nodeList.item(i);
1020 try {
1021 checkWcapXmlErrno(node);
1022 var ar = filterXmlNodes("X-NSCP-CALPROPS-RELATIVE-CALID", node);
1023 if (ar.length > 0) {
1024 var calId = ar[0];
1025 var cal = registeredCalendars[calId];
1026 if (cal) {
1027 cal.m_calProps = node; // update calprops
1028 }
1029 else {
1030 cal = new calWcapCalendar(this_, node);
1031 var uri = this_.uri.clone();
1032 uri.path += ("?calid=" + encodeURIComponent(calId));
1033 cal.uri = uri;
1034 }
1035 ret.push(cal);
1036 }
1037 }
1038 catch (exc) {
1039 switch (getResultCode(exc)) {
1040 case calIWcapErrors.WCAP_NO_ERRNO: // workaround
1041 case calIWcapErrors.WCAP_ACCESS_DENIED_TO_CALENDAR:
1042 log("searchForCalendars_netResp() ignored error: " +
1043 errorToString(exc), this_);
1044 break;
1045 default:
1046 this_.notifyError(exc);
1047 break;
1048 }
1049 }
1050 }
1051 log("search done. number of found calendars: " + ret.length, this_);
1052 request.execRespFunc(null, ret);
1053 },
1054 null, "search_calprops", params);
1055 }
1056 catch (exc) {
1057 request.execRespFunc(exc);
1058 }
1059 return request;
1060 },
1061
1062 // calIFreeBusyProvider:
calWcapCalendar_getFreeBusyIntervals
1063 getFreeBusyIntervals: function calWcapCalendar_getFreeBusyIntervals(
1064 calId, rangeStart, rangeEnd, busyTypes, listener)
1065 {
1066 // assure DATETIMEs:
1067 if (rangeStart && rangeStart.isDate) {
1068 rangeStart = rangeStart.clone();
1069 rangeStart.isDate = false;
1070 }
1071 if (rangeEnd && rangeEnd.isDate) {
1072 rangeEnd = rangeEnd.clone();
1073 rangeEnd.isDate = false;
1074 }
1075 var zRangeStart = getIcalUTC(rangeStart);
1076 var zRangeEnd = getIcalUTC(rangeEnd);
1077
1078 var this_ = this;
1079 var request = new calWcapRequest(
1080 function _resp(request, err, data) {
1081 var rc = getResultCode(err);
1082 switch (rc) {
1083 case calIWcapErrors.WCAP_NO_ERRNO: // workaround
1084 case calIWcapErrors.WCAP_ACCESS_DENIED_TO_CALENDAR:
1085 case calIWcapErrors.WCAP_CALENDAR_DOES_NOT_EXIST:
1086 log("getFreeBusyIntervals_resp() error: " + errorToString(err), this_);
1087 break;
1088 default:
1089 if (!Components.isSuccessCode(rc))
1090 this_.notifyError(err);
1091 break;
1092 }
1093 if (listener)
1094 listener.onResult(request, data);
1095 },
1096 log("getFreeBusyIntervals():\n\tcalId=" + calId +
1097 "\n\trangeStart=" + zRangeStart + ",\n\trangeEnd=" + zRangeEnd, this));
1098
1099 try {
1100 var params = ("&calid=" + encodeURIComponent(calId));
1101 params += ("&busyonly=" + ((busyTypes & calIFreeBusyInterval.FREE) ? "0" : "1"));
1102 params += ("&dtstart=" + zRangeStart);
1103 params += ("&dtend=" + zRangeEnd);
1104 params += "&fmt-out=text%2Fxml";
1105
1106 // cannot use stringToXml here, because cs 6.3 returns plain nothing
1107 // on invalid user freebusy requests. WTF.
1108 function stringToXml_(session, data) {
1109 if (!data || data.length == 0) { // assuming invalid user
1110 throw new Components.Exception(
1111 wcapErrorToString(calIWcapErrors.WCAP_CALENDAR_DOES_NOT_EXIST),
1112 calIWcapErrors.WCAP_CALENDAR_DOES_NOT_EXIST);
1113 }
1114 return stringToXml(session, data);
1115 }
1116 this.issueNetworkRequest(
1117 request,
1118 function net_resp(err, xml) {
1119 if (err)
1120 throw err;
1121 if (LOG_LEVEL > 0) {
1122 log("getFreeBusyIntervals net_resp(): " +
1123 getWcapRequestStatusString(xml), this_);
1124 }
1125 if (listener) {
1126 var ret = [];
1127 var nodeList = xml.getElementsByTagName("FB");
1128
1129 var fbTypeMap = {};
1130 fbTypeMap["FREE"] = calIFreeBusyInterval.FREE;
1131 fbTypeMap["BUSY"] = calIFreeBusyInterval.BUSY;
1132 fbTypeMap["BUSY-UNAVAILABLE"] = calIFreeBusyInterval.BUSY_UNAVAILABLE;
1133 fbTypeMap["BUSY-TENTATIVE"] = calIFreeBusyInterval.BUSY_TENTATIVE;
1134
1135 for (var i = 0; i < nodeList.length; ++i) {
1136 var node = nodeList.item(i);
1137 var fbType = fbTypeMap[node.attributes.getNamedItem("FBTYPE").nodeValue];
1138 if (fbType & busyTypes) {
1139 var str = node.textContent;
1140 var slash = str.indexOf('/');
1141 var period = new CalPeriod();
1142 period.start = getDatetimeFromIcalString(str.substr(0, slash));
1143 period.end = getDatetimeFromIcalString(str.substr(slash + 1));
1144 period.makeImmutable();
1145 var fbInterval = {
1146 QueryInterface: function fbInterval_QueryInterface(iid) {
1147 ensureIID([calIFreeBusyInterval, nsISupports], iid);
1148 return this;
1149 },
1150 calId: calId,
1151 interval: period,
1152 freeBusyType: fbType
1153 };
1154 ret.push(fbInterval);
1155 }
1156 }
1157 request.execRespFunc(null, ret);
1158 }
1159 },
1160 stringToXml_, "get_freebusy", params);
1161 }
1162 catch (exc) {
1163 request.execRespFunc(exc);
1164 }
1165 return request;
1166 },
1167
1168 // nsIObserver:
calWcapSession_observer
1169 observe: function calWcapSession_observer(subject, topic, data)
1170 {
1171 log("observing: " + topic + ", data: " + data, this);
1172 if (topic == "quit-application") {
1173 g_bShutdown = true;
1174 this.logout(null);
1175 // xxx todo: valid upon notification?
1176 getCalendarManager().removeObserver(this);
1177 var observerService = Components.classes["@mozilla.org/observer-service;1"]
1178 .getService(Components.interfaces.nsIObserverService);
1179 observerService.removeObserver(this, "quit-application");
1180 }
1181 },
1182
1183 // calICalendarManagerObserver:
1184
1185 // called after the calendar is registered
calWcapSession_onCalendarRegistered
1186 onCalendarRegistered: function calWcapSession_onCalendarRegistered(cal)
1187 {
1188 try {
1189 // make sure the calendar belongs to this session:
1190 if (this.belongsTo(cal)) {
1191
1192 function assureDefault(pref, val) {
1193 if (cal.getProperty(pref) === null) {
1194 cal.setProperty(pref, val);
1195 }
1196 }
1197
1198 assureDefault("shared_context", this.m_contextId);
1199 assureDefault("name", cal.name);
1200
1201 const s_colors = ["#FFCCCC", "#FFCC99", "#FFFF99", "#FFFFCC", "#99FF99",
1202 "#99FFFF", "#CCFFFF", "#CCCCFF", "#FFCCFF", "#FF6666",
1203 "#FF9966", "#FFFF66", "#FFFF33", "#66FF99", "#33FFFF",
1204 "#66FFFF", "#9999FF", "#FF99FF", "#FF0000", "#FF9900",
1205 "#FFCC66", "#FFFF00", "#33FF33", "#66CCCC", "#33CCFF",
1206 "#6666CC", "#CC66CC", "#CC0000", "#FF6600", "#FFCC33",
1207 "#FFCC00", "#33CC00", "#00CCCC", "#3366FF", "#6633FF",
1208 "#CC33CC", "#990000", "#CC6600", "#CC9933", "#999900",
1209 "#009900", "#339999", "#3333FF", "#6600CC", "#993399",
1210 "#660000", "#993300", "#996633", "#666600", "#006600",
1211 "#336666", "#000099", "#333399", "#663366", "#330000",
1212 "#663300", "#663333", "#333300", "#003300", "#003333",
1213 "#000066", "#330099", "#330033"];
1214 assureDefault("color", s_colors[(new Date()).getUTCMilliseconds() % s_colors.length]);
1215 }
1216 }
1217 catch (exc) { // never break the listener chain
1218 this.notifyError(exc);
1219 }
1220 },
1221
1222 // called before the unregister actually takes place
calWcapSession_onCalendarUnregistering
1223 onCalendarUnregistering: function calWcapSession_onCalendarUnregistering(cal)
1224 {
1225 try {
1226 // make sure the calendar belongs to this session and is the default calendar,
1227 // then remove all subscribed calendars:
1228 cal = this.belongsTo(cal);
1229 if (cal && cal.isDefaultCalendar) {
1230 getFreeBusyService().removeProvider(this);
1231 getCalendarSearchService().removeProvider(this);
1232 var registeredCalendars = this.getRegisteredCalendars();
1233 for each (var regCal in registeredCalendars) {
1234 try {
1235 if (!regCal.isDefaultCalendar) {
1236 getCalendarManager().unregisterCalendar(regCal);
1237 }
1238 }
1239 catch (exc) {
1240 this.notifyError(exc);
1241 }
1242 }
1243 }
1244 }
1245 catch (exc) { // never break the listener chain
1246 this.notifyError(exc);
1247 }
1248 },
1249
1250 // called before the delete actually takes place
calWcapSession_onCalendarDeleting
1251 onCalendarDeleting: function calWcapSession_onCalendarDeleting(cal)
1252 {
1253 }
1254 };
1255
1256 var g_confirmedHttpLogins = null;
confirmInsecureLogin
1257 function confirmInsecureLogin(uri)
1258 {
1259 if (!g_confirmedHttpLogins) {
1260 g_confirmedHttpLogins = {};
1261 var confirmedHttpLogins = getPref(
1262 "calendar.wcap.confirmed_http_logins", "");
1263 var tuples = confirmedHttpLogins.split(',');
1264 for each (var tuple in tuples) {
1265 var ar = tuple.split(':');
1266 g_confirmedHttpLogins[ar[0]] = ar[1];
1267 }
1268 }
1269
1270 var bConfirmed = false;
1271
1272 var host = uri.hostPort;
1273 var encodedHost = encodeURIComponent(host);
1274 var confirmedEntry = g_confirmedHttpLogins[encodedHost];
1275 if (confirmedEntry) {
1276 bConfirmed = (confirmedEntry == "1");
1277 }
1278 else {
1279 var prompt = getWindowWatcher().getNewPrompter(null);
1280 var out_dontAskAgain = { value: false };
1281 var bConfirmed = prompt.confirmCheck(
1282 calGetString("wcap", "noHttpsConfirmation.label"),
1283 calGetString("wcap", "noHttpsConfirmation.text", [host]),
1284 calGetString("wcap", "noHttpsConfirmation.check.text"),
1285 out_dontAskAgain);
1286
1287 if (out_dontAskAgain.value) {
1288 // save decision for all running calendars and
1289 // all future confirmations:
1290 var confirmedHttpLogins = getPref("calendar.wcap.confirmed_http_logins", "");
1291 if (confirmedHttpLogins.length > 0)
1292 confirmedHttpLogins += ",";
1293 confirmedEntry = (bConfirmed ? "1" : "0");
1294 confirmedHttpLogins += (encodedHost + ":" + confirmedEntry);
1295 setPref("calendar.wcap.confirmed_http_logins", "CHAR", confirmedHttpLogins);
1296 getPref("calendar.wcap.confirmed_http_logins"); // log written entry
1297 g_confirmedHttpLogins[encodedHost] = confirmedEntry;
1298 }
1299 }
1300
1301 log("returned: " + bConfirmed, "confirmInsecureLogin(" + host + ")");
1302 return bConfirmed;
1303 }