Fix bug 455262 - Use PROPFIND instead of REPORT in CalDAV refresh()es. r=dbo
authorBruno Browning <browning@uwalumni.com>
Mon, 29 Sep 2008 20:11:28 +0200
changeset 462 71e7cad5c95abd53f03c35ed720d7cacc27d2631
parent 461 f95b15892209021649676464b167fe4192c86014
child 463 ddaeff038effb89ae2572889ace678c09dd7ff73
push idunknown
push userunknown
push dateunknown
reviewersdbo
bugs455262
Fix bug 455262 - Use PROPFIND instead of REPORT in CalDAV refresh()es. r=dbo
calendar/providers/caldav/calDavCalendar.js
--- a/calendar/providers/caldav/calDavCalendar.js
+++ b/calendar/providers/caldav/calDavCalendar.js
@@ -151,17 +151,17 @@ calDavCalendar.prototype = {
         throw NS_ERROR_NOT_IMPLEMENTED;
     },
 
 
     // calIChangeLog interface
     resetLog: function caldav_resetLog() {
         if (this.isCached && this.mTargetCalendar) {
             this.mTargetCalendar.startBatch();
-            try { 
+            try {
                 for (var itemId in this.mItemInfoCache) {
                     this.mTargetCalendar.deleteMetaData(itemId);
                     delete this.mItemInfoCache[itemId];
                 }
             } finally {
                 this.mTargetCalendar.endBatch();
             }
         }
@@ -810,18 +810,17 @@ calDavCalendar.prototype = {
             // it quick
             var headchannel = calPrepHttpChannel(this.calendarUri, null, null,
                                                  this);
             headchannel.requestMethod = "HEAD";
             headchannel.open();
         }
 
         if (!this.mCtag || !this.mFirstRefreshDone) {
-            var refreshEvent = this.prepRefresh();
-            this.getUpdatedItems(refreshEvent, aChangeLogListener);
+            this.getUpdatedItems(this.calendarUri, aChangeLogListener);
             return;
         }
         var thisCalendar = this;
 
         var D = new Namespace("D", "DAV:");
         var CS = new Namespace("CS", "http://calendarserver.org/ns/");
         var queryXml = <D:propfind xmlns:D={D} xmlns:CS={CS}>
                         <D:prop>
@@ -866,18 +865,17 @@ calDavCalendar.prototype = {
                 return;
             }
 
             var ctag = multistatus..CS::getctag.toString();
             if (!ctag.length || ctag != thisCalendar.mCtag) {
                 // ctag mismatch, need to fetch calendar-data
                 thisCalendar.mCtag = ctag;
                 thisCalendar.mTargetCalendar.setMetaData("ctag", ctag);
-                var refreshEvent = thisCalendar.prepRefresh();
-                thisCalendar.getUpdatedItems(refreshEvent, aChangeLogListener);
+                thisCalendar.getUpdatedItems(thisCalendar.calendarUri, aChangeLogListener);
                 if (thisCalendar.verboseLogging()) {
                     LOG("CalDAV: ctag mismatch on refresh, fetching data for calendar "
                         + thisCalendar.name);
                 }
             } else {
                 if (thisCalendar.verboseLogging()) {
                     LOG("CalDAV: ctag matches, no need to fetch data for calendar "
                         + thisCalendar.name);
@@ -920,63 +918,37 @@ calDavCalendar.prototype = {
                     return true;
                 }
                 break;
             }
         }
         return false;
     },
 
-    prepRefresh: function caldav_prepRefresh() {
-
-        var itemTypes = this.supportedItemTypes.concat([]);
-        var typesCount = itemTypes.length;
-        var refreshEvent = {};
-        refreshEvent.itemTypes = itemTypes;
-        refreshEvent.typesCount = typesCount;
-        refreshEvent.queryStatuses = [];
-        refreshEvent.itemsNeedFetching = [];
-        refreshEvent.itemsReported = [];
-        refreshEvent.uri = this.calendarUri;
-
-        return refreshEvent;
-    },
-
-    getUpdatedItems: function caldav_getUpdatedItems(aRefreshEvent, aChangeLogListener) {
+    getUpdatedItems: function caldav_getUpdatedItems(aUri, aChangeLogListener) {
         if (this.mDisabled) {
             // check if maybe our calendar has become available
             this.checkDavResourceType(aChangeLogListener);
             return;
         }
 
-        if (!aRefreshEvent.itemTypes.length) {
-            return;
-        }
-        var itemType = aRefreshEvent.itemTypes.pop();
+        var itemsReported = {};
+        var itemsNeedFetching = [];
+        unhandledErrors = 0;
 
         var C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
         var D = new Namespace("D", "DAV:");
         default xml namespace = C;
 
-        var queryXml =
-          <calendar-query xmlns:D={D}>
-            <D:prop>
-              <D:getetag/>
-            </D:prop>
-            <filter>
-              <comp-filter name="VCALENDAR">
-                <comp-filter/>
-              </comp-filter>
-            </filter>
-          </calendar-query>;
-
-        queryXml[0].C::filter.C::["comp-filter"]
-                        .C::["comp-filter"] =
-                        <comp-filter name={itemType}/>;
-
+        var queryXml = <D:propfind xmlns:D="DAV:">
+                        <D:prop>
+                            <D:getcontenttype/>
+                            <D:getetag/>
+                        </D:prop>
+                       </D:propfind>;
 
         var queryString = xmlHeader + queryXml.toXMLString();
 
         var multigetQueryXml =
           <calendar-multiget xmlns:D={D} xmlns={C}>
             <D:prop>
               <D:getetag/>
               <calendar-data/>
@@ -1001,150 +973,163 @@ calDavCalendar.prototype = {
                 responseStatus = "none";
             }
 
             if (responseStatus == 207) {
                 // We only need to parse 207's, anything else is probably a
                 // server error (i.e 50x).
                 var str = convertByteArray(aResult, aResultLength);
                 if (!str) {
-                    LOG("CAlDAV: Failed to parse getetag REPORT");
+                    LOG("CAlDAV: Failed to parse getetag PROPFIND");
                 } else if (thisCalendar.verboseLogging()) {
                     LOG("CalDAV: recv: " + str);
                 }
 
                 if (str.substr(0,6) == "<?xml ") {
                     str = str.substring(str.indexOf('<', 2));
                 }
                 var multistatus = new XML(str);
                 for (var i = 0; i < multistatus.*.length(); i++) {
                     var response = new XML(multistatus.*[i]);
                     var etag = response..D::["getetag"];
                     if (etag.length() == 0) {
                         continue;
                     }
+                    var contenttype = null;
+                    contenttype = response..D::["getcontenttype"];
+                    // workaround for a Scalix bug which causes incorrect
+                    // contenttype to be returned. Remove when that's fixed
+                    if (contenttype == "message/rfc822") {
+                        contenttype = "text/calendar";
+                    }
+                    // end Scalix workaround
+                    if (contenttype.toString().substr(0, 13) != "text/calendar") {
+                          continue;
+                    }
                     var href = response..D::["href"];
                     var resourcePath = thisCalendar.ensurePath(href);
-                    aRefreshEvent.itemsReported.push(resourcePath.toString());
+                    itemsReported[resourcePath.toString()] = true;
 
                     var itemuid = thisCalendar.mHrefIndex[resourcePath];
                     if (!itemuid || etag != thisCalendar.mItemInfoCache[itemuid].etag) {
-                        aRefreshEvent.itemsNeedFetching.push(resourcePath);
+                        itemsNeedFetching.push(resourcePath);
                     }
                 }
             } else if (Math.floor(responseStatus / 100) == 4) {
                 // A 4xx error probably means the server doesn't support this
                 // type of query. Disable it for this session. This doesn't
                 // really match the spec (which requires a 207), but it works
                 // around bugs in Google and eGroupware 1.6.
                 LOG("CalDAV: Server doesn't support " + itemType + "s");
                 var itemTypeIndex = thisCalendar.supportedItemTypes.indexOf(itemType);
                 if (itemTypeIndex > -1) {
                     thisCalendar.supportedItemTypes.splice(itemTypeIndex, 1);
                 }
             } else {
-                aRefreshEvent.unhandledErrors++;
+                unhandledErrors++;
             }
 
-            aRefreshEvent.queryStatuses.push(responseStatus);
             var needsRefresh = false;
-            if (aRefreshEvent.queryStatuses.length == aRefreshEvent.typesCount) {
-                if (aRefreshEvent.unhandledErrors) {
-                    LOG("CalDAV: Error fetching item etags");
-                    thisCalendar.reportDavError(Components.interfaces.calIErrors.DAV_REPORT_ERROR);
-                    if (thisCalendar.isCached && aChangeLogListener) {
-                        aChangeLogListener.onResult({ status: Components.results.NS_ERROR_FAILURE },
-                                                    Components.results.NS_ERROR_FAILURE);
-                    }
-                    return;
-                }
-                if (thisCalendar.isCached) {
-                    thisCalendar.superCalendar.startBatch();
-                }
-                try { 
-                    // if an item has been deleted from the server, delete it here too
-                    for (var path in thisCalendar.mHrefIndex) {
-                        if (aRefreshEvent.itemsReported.indexOf(path) < 0 &&
-                            path.indexOf(aRefreshEvent.uri.path) == 0) {
 
-                            var getItemListener = {};
-                            getItemListener.onGetResult = function caldav_gUIs_oGR(aCalendar,
-                                aStatus, aItemType, aDetail, aCount, aItems) {
-                                var itemToDelete = aItems[0];
-
-                                var wasInBoxItem = thisCalendar.mItemInfoCache[itemToDelete.id].isInBoxItem;
-                                if ((wasInBoxItem && thisCalendar.isInBox(aRefreshEvent.uri.spec)) ||
-                                    (wasInBoxItem === false && !thisCalendar.isInBox(aRefreshEvent.uri.spec))) {
-                                    delete thisCalendar.mItemInfoCache[itemToDelete.id];
-                                    thisCalendar.mTargetCalendar.deleteItem(itemToDelete,
-                                                                            getItemListener);
-                                }
-                                delete thisCalendar.mHrefIndex[path];
-                                needsRefresh = true;
-                            }
-                            getItemListener.onOperationComplete = function
-                                caldav_gUIs_oOC(aCalendar, aStatus, aOperationType,
-                                                aId, aDetail) {}
-                            thisCalendar.mTargetCalendar.getItem(thisCalendar.mHrefIndex[path],
-                                                                 getItemListener);
-                        }
+            if (unhandledErrors) {
+                LOG("CalDAV: Error fetching item etags");
+                thisCalendar.reportDavError(Components.interfaces.calIErrors.DAV_REPORT_ERROR);
+                if (thisCalendar.isCached && aChangeLogListener) {
+                    aChangeLogListener.onResult({ status: Components.results.NS_ERROR_FAILURE },
+                                                Components.results.NS_ERROR_FAILURE);
+                }
+                return;
+            }
+            if (thisCalendar.isCached) {
+                thisCalendar.superCalendar.startBatch();
+            }
+            try {
+                for (var path in thisCalendar.mHrefIndex) {
+                    if (path in itemsReported || path.indexOf(aUri.path) != 0) {
+                        // If the item is also on the server, check the next.
+                        continue;
                     }
-                } finally { 
-                    if (thisCalendar.isCached) {
-                        thisCalendar.superCalendar.endBatch();
-                    }
+                    // if an item has been deleted from the server, delete it
+                    // here too. Since the target calendar's operations are
+                    // synchronous, we can safely set variables from this
+                    // function (i.e needsRefresh)
+                    var getItemListener = {
+                        onGetResult: function caldav_gUIs_oGR(aCalendar,
+                                                              aStatus,
+                                                              aItemType,
+                                                              aDetail,
+                                                              aCount,
+                                                              aItems) {
+                            var itemToDelete = aItems[0];
+                            var wasInBoxItem = thisCalendar.mItemInfoCache[itemToDelete.id].isInBoxItem;
+                            if ((wasInBoxItem && thisCalendar.isInBox(aUri.spec)) ||
+                                (wasInBoxItem === false && !thisCalendar.isInBox(aUri.spec))) {
+                                delete thisCalendar.mItemInfoCache[itemToDelete.id];
+                                thisCalendar.mTargetCalendar.deleteItem(itemToDelete,
+                                                                        getItemListener);
+                            }
+                            delete thisCalendar.mHrefIndex[path];
+                            needsRefresh = true;
+                        },
+                        onOperationComplete: function caldav_gUIs_oOC(aCalendar,
+                                                                      aStatus,
+                                                                      aOperationType,
+                                                                      aId,
+                                                                      aDetail) {}
+                    };
+                    thisCalendar.mTargetCalendar.getItem(thisCalendar.mHrefIndex[path],
+                                                         getItemListener);
                 }
+            } finally {
+                if (thisCalendar.isCached) {
+                    thisCalendar.superCalendar.endBatch();
+                }
+            }
 
-                // avoid sending empty multiget requests
-                // update views if something has been deleted server-side
-                if (!aRefreshEvent.itemsNeedFetching.length) {
-                    if (thisCalendar.isCached && aChangeLogListener) {
-                        aChangeLogListener.onResult({ status: Components.results.NS_OK },
-                                                    Components.results.NS_OK);
-                    }
-                    if (needsRefresh) {
-                        thisCalendar.mObservers.notify("onLoad", [thisCalendar]);
-                    }
-                    // but do poll the inbox;
-                    if (thisCalendar.hasScheduling &&
-                        !thisCalendar.isInBox(aRefreshEvent.uri.spec)) {
-                        thisCalendar.pollInBox();
-                    }
-                    return;
+            // Avoid sending empty multiget requests
+            // update views if something has been deleted server-side
+            if (!itemsNeedFetching.length) {
+                if (thisCalendar.isCached && aChangeLogListener) {
+                    aChangeLogListener.onResult({ status: Components.results.NS_OK },
+                                                Components.results.NS_OK);
                 }
+                if (needsRefresh) {
+                    thisCalendar.mObservers.notify("onLoad", [thisCalendar]);
+                }
+                // but do poll the inbox;
+                if (thisCalendar.hasScheduling &&
+                    !thisCalendar.isInBox(aUri.spec)) {
+                    thisCalendar.pollInBox();
+                }
+                return;
+            }
 
-                while (aRefreshEvent.itemsNeedFetching.length > 0) {
-                    var locpath = aRefreshEvent.itemsNeedFetching.pop().toString();
-                    var hrefXml = <hr xmlns:D={D}/>
-                    hrefXml.D::href = locpath;
-                    multigetQueryXml[0].appendChild(hrefXml.D::href);
-                }
+            while (itemsNeedFetching.length > 0) {
+                var locpath = itemsNeedFetching.pop().toString();
+                multigetQueryXml.D::prop += <D:href xmlns:D={D}>{locpath}</D:href>;
+            }
 
-                var multigetQueryString = xmlHeader +
-                                          multigetQueryXml.toXMLString();
-                thisCalendar.getCalendarData(aRefreshEvent.uri,
-                                             multigetQueryString,
-                                             null,
-                                             null,
-                                             aChangeLogListener);
-
-            } else {
-                thisCalendar.getUpdatedItems(aRefreshEvent, aChangeLogListener);
-            }
+            var multigetQueryString = xmlHeader +
+                                      multigetQueryXml.toXMLString();
+            thisCalendar.getCalendarData(aUri,
+                                         multigetQueryString,
+                                         null,
+                                         null,
+                                         aChangeLogListener);
         };
 
         if (this.verboseLogging()) {
             LOG("CalDAV: send: " + queryString);
         }
 
-        var httpchannel = calPrepHttpChannel(aRefreshEvent.uri,
+        var httpchannel = calPrepHttpChannel(aUri,
                                              queryString,
                                              "text/xml; charset=utf-8",
                                              this);
-        httpchannel.requestMethod = "REPORT";
+        httpchannel.requestMethod = "PROPFIND";
         httpchannel.setRequestHeader("Depth", "1", false);
         var streamLoader = createStreamLoader();
         calSendHttpRequest(streamLoader, httpchannel, etagListener);
     },
 
     getCalendarData: function caldav_getCalendarData(aUri, aQuery, aItem, aListener, aChangeLogListener) {
         this.ensureTargetCalendar();
 
@@ -1272,17 +1257,17 @@ calDavCalendar.prototype = {
                         thisCalendar.mTargetCalendar.modifyItem(item, null, aListener);
                     }
 
                     if (thisCalendar.isCached) {
                         thisCalendar.setMetaData(item.id, resourcePath, etag, isInboxItem);
                     }
                 }
                 LOG("refresh completed with status " + responseStatus + " at " + aUri.spec);
-            } finally { 
+            } finally {
                 if (thisCalendar.isCached) {
                     thisCalendar.superCalendar.endBatch();
                 }
             }
 
             if (thisCalendar.isCached) {
                 if (aChangeLogListener)
                     aChangeLogListener.onResult({ status: Components.results.NS_OK },
@@ -1327,17 +1312,17 @@ calDavCalendar.prototype = {
     //
 
     /**
      * Checks that the calendar URI exists and is a CalDAV calendar. This is the
      * beginning of a chain of asynchronous calls. This function will, when
      * done, call the next function related to checking resource type, server
      * capabilties, etc.
      *
-     * checkDavResourceType                        * You are here 
+     * checkDavResourceType                        * You are here
      * checkServerCaps
      * findPrincipalNS
      * checkPrincipalsNameSpace
      * completeCheckServerInfo
      */
     checkDavResourceType: function caldav_checkDavResourceType(aChangeLogListener) {
         this.ensureTargetCalendar();
 
@@ -1394,30 +1379,30 @@ calDavCalendar.prototype = {
                 var realmChop = wwwauth.split("realm=\"")[1];
                 thisCalendar.mAuthRealm = realmChop.split("\", ")[0];
                 LOG("CalDAV: realm " + thisCalendar.mAuthRealm);
             }
 
             var str = convertByteArray(aResult, aResultLength);
             if (!str) {
                 LOG("CalDAV: Failed to determine resource type");
-                thisCalendar.completeCheckServerInfo(aChangeLogListener, 
+                thisCalendar.completeCheckServerInfo(aChangeLogListener,
                                                      Components.interfaces.calIErrors.DAV_NOT_DAV);
                 return;
             } else if (thisCalendar.verboseLogging()) {
                 LOG("CalDAV: recv: " + str);
             }
 
             if (str.substr(0,6) == "<?xml ") {
                 str = str.substring(str.indexOf('<', 2));
             }
             try {
                 var multistatus = new XML(str);
             } catch (ex) {
-                thisCalendar.completeCheckServerInfo(aChangeLogListener, 
+                thisCalendar.completeCheckServerInfo(aChangeLogListener,
                                                      Components.interfaces.calIErrors.DAV_NOT_DAV);
                 return;
             }
 
             // check for server-side ctag support
             var ctag = multistatus..CS::["getctag"].toString();
             if (ctag.length) {
                 thisCalendar.mCtag = ctag;
@@ -1443,24 +1428,24 @@ calDavCalendar.prototype = {
             // versions with fixed principal-URL PROPFIND bug are out there
             if (resourceTypeXml.toString().indexOf("groupdav") != -1) {
                 thisCalendar.mPrincipalUrl = null;
             }
             // end of SOGo specialcasing
 
             if (resourceType == kDavResourceTypeNone &&
                 !thisCalendar.mDisabled) {
-                thisCalendar.completeCheckServerInfo(aChangeLogListener, 
+                thisCalendar.completeCheckServerInfo(aChangeLogListener,
                                                      Components.interfaces.calIErrors.DAV_NOT_DAV);
                 return;
             }
 
             if ((resourceType == kDavResourceTypeCollection) &&
                 !thisCalendar.mDisabled) {
-                thisCalendar.completeCheckServerInfo(aChangeLogListener, 
+                thisCalendar.completeCheckServerInfo(aChangeLogListener,
                                                      Components.interfaces.calIErrors.DAV_DAV_NOT_CALDAV);
                 return;
             }
 
             // if this calendar was previously offline we want to recover
             if ((resourceType == kDavResourceTypeCalendar) &&
                 thisCalendar.mDisabled) {
                 thisCalendar.mDisabled = false;
@@ -1473,17 +1458,17 @@ calDavCalendar.prototype = {
         var streamLoader = createStreamLoader();
         calSendHttpRequest(streamLoader, httpchannel, streamListener);
     },
 
     /**
      * Checks server capabilities.
      *
      * checkDavResourceType
-     * checkServerCaps                              * You are here 
+     * checkServerCaps                              * You are here
      * findPrincipalNS
      * checkPrincipalsNameSpace
      * completeCheckServerInfo
      */
     checkServerCaps: function caldav_checkServerCaps(aChangeLogListener) {
         var homeSet = this.mCalHomeSet.clone();
         var thisCalendar = this;
 
@@ -1627,17 +1612,17 @@ calDavCalendar.prototype = {
     },
 
     /**
      * Checks the principals namespace for scheduling info. This function should
      * soely be called from findPrincipalNS
      *
      * checkDavResourceType
      * checkServerCaps
-     * findPrincipalNS              
+     * findPrincipalNS
      * checkPrincipalsNameSpace                     * You are here
      * completeCheckServerInfo
      *
      * @param aNameSpaceList    List of available namespaces
      */
     checkPrincipalsNameSpace: function caldav_checkPrincipalsNameSpace(aNameSpaceList, aChangeLogListener) {
         var thisCalendar = this;
         function doesntSupportScheduling() {
@@ -1824,17 +1809,17 @@ calDavCalendar.prototype = {
 
     /**
      * This is called to complete checking the server info. It should be the
      * final call when checking server options. This will either report the
      * error or if it is a success then refresh the calendar.
      *
      * checkDavResourceType
      * checkServerCaps
-     * findPrincipalNS              
+     * findPrincipalNS
      * checkPrincipalsNameSpace
      * completeCheckServerInfo                      * You are here
      */
     completeCheckServerInfo: function caldav_completeCheckServerInfo(aChangeLogListener, aError) {
         if (Components.isSuccessCode(aError)) {
             // "undefined" is a successcode, so all is good
             this.mCheckedServerInfo = true;
 
@@ -2094,27 +2079,17 @@ calDavCalendar.prototype = {
     pollInBox: function caldav_pollInBox() {
         // If polling the inbox was switched off, no need to poll the inbox.
         // Also, if we have more than one calendar in this CalDAV account, we
         // want only one of them to be checking the inbox.
         if (!this.hasScheduling || !this.mShouldPollInbox || !this.firstInRealm()) {
             return;
         }
 
-        var itemTypes = this.supportedItemTypes.concat([]);
-        var typesCount = itemTypes.length;
-        var refreshEvent = {};
-        refreshEvent.itemTypes = itemTypes;
-        refreshEvent.typesCount = typesCount;
-        refreshEvent.queryStatuses = [];
-        refreshEvent.itemsNeedFetching = [];
-        refreshEvent.itemsReported = [];
-        refreshEvent.uri = this.mInBoxUrl;
-
-        this.getUpdatedItems(refreshEvent);
+        this.getUpdatedItems(this.mInBoxUrl, null);
     },
 
     //
     // take calISchedulingSupport interface base implementation (calProviderBase.js)
     //
 
     processItipReply: function caldav_processItipReply(aItem, aPath) {
         // modify partstat for in-calendar item