Fix bug 754164 - Bug 650353 breaks Lightning with [TypeError: can't wrap XML objects] due to e4x usage (regression). r=mmecca
authorPhilipp Kewisch <mozilla@kewis.ch>
Tue, 22 May 2012 12:52:43 +0700
changeset 10229 a182ec1d232fa768b2e53ddc7bb6b8fddac42cb8
parent 10228 f0d80c468f7974da551038b72bdfdb635ea8c50c
child 10230 dba50267fd10f621610d3fe5d58466a858a2f7e5
push id7756
push usermozilla@kewis.ch
push dateTue, 22 May 2012 05:54:24 +0000
treeherdercomm-central@a182ec1d232f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmmecca
bugs754164, 650353
Fix bug 754164 - Bug 650353 breaks Lightning with [TypeError: can't wrap XML objects] due to e4x usage (regression). r=mmecca
calendar/base/modules/Makefile.in
calendar/base/modules/calProviderUtils.jsm
calendar/base/modules/calXMLUtils.jsm
calendar/providers/caldav/calDavCalendar.js
calendar/providers/caldav/calDavRequestHandlers.js
calendar/providers/ics/calICSCalendar.js
--- a/calendar/base/modules/Makefile.in
+++ b/calendar/base/modules/Makefile.in
@@ -50,11 +50,12 @@ EXTRA_JS_MODULES = \
     calAuthUtils.jsm \
     calHashedArray.jsm \
     calItemUtils.jsm \
     calIteratorUtils.jsm \
     calItipUtils.jsm \
     calPrintUtils.jsm \
     calProviderUtils.jsm \
     calUtils.jsm \
+    calXMLUtils.jsm \
     $(NULL)
 
 include $(topsrcdir)/config/rules.mk
--- a/calendar/base/modules/calProviderUtils.jsm
+++ b/calendar/base/modules/calProviderUtils.jsm
@@ -117,24 +117,16 @@ cal.convertByteArray = function calConve
     } catch (e) {
         if (aThrow) {
             throw e;
         }
     }
     return null;
 };
 
-cal.safeNewXML = function calSafeNewXML(aStr) {
-    // Restore XML global property defaults as a precaution
-    XML.setSettings();
-
-    // Strip <?xml and surrounding whitespaces (bug 336551)
-    return new XML(aStr.trim().replace(/^<\?xml[^>]*>\s*/g, ""));
-};
-
 /**
  * getInterface method for providers. This should be called in the context of
  * the respective provider, i.e
  *
  * return cal.InterfaceRequestor_getInterface.apply(this, arguments);
  *
  * or
  * ...
new file mode 100644
--- /dev/null
+++ b/calendar/base/modules/calXMLUtils.jsm
@@ -0,0 +1,157 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Helper functions for parsing and serializing XML */
+
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
+
+EXPORTED_SYMBOLS = ["cal"]
+cal.xml = {} || cal.xml;
+
+/**
+ * Evaluate an XPath query for the given node. Be careful with the return value
+ * here, as it may be:
+ *
+ * - null, if there are no results
+ * - a number, string or boolean value
+ * - an array of strings or DOM elements
+ *
+ * @param aNode     The context node to search from
+ * @param aExpr     The XPath expression to search for
+ * @param aResolver (optional) The namespace resolver to use for the expression
+ * @param aType     (optional) Force a result type, must be an XPathResult constant
+ * @return          The result, see above for details.
+ */
+cal.xml.evalXPath = function evaluateXPath(aNode, aExpr, aResolver, aType) {
+    const XPR = Components.interfaces.nsIDOMXPathResult;
+    let doc = (aNode.ownerDocument ? aNode.ownerDocument : aNode);
+    let resolver = aResolver || doc.createNSResolver(doc.documentElement);
+    let resultType = aType || XPR.ANY_TYPE;
+
+    let result = doc.evaluate(aExpr, aNode, resolver, resultType, null);
+    let returnResult, next;
+    switch (result.resultType) {
+        case XPR.NUMBER_TYPE:
+            returnResult = result.numberValue;
+            break;
+        case XPR.STRING_TYPE:
+            returnResult = result.stringValue;
+            break;
+        case XPR.BOOLEAN_TYPE:
+            returnResult = result.booleanValue;
+            break;
+        case XPR.UNORDERED_NODE_ITERATOR_TYPE:
+        case XPR.ORDERED_NODE_ITERATOR_TYPE:
+        case XPR.ORDERED_NODE_ITERATOR_TYPE:
+            returnResult = [];
+            while (next = result.iterateNext()) {
+                if (next instanceof Components.interfaces.nsIDOMText) {
+                    returnResult.push(next.wholeText);
+                } else if (next instanceof Components.interfaces.nsIDOMAttr) {
+                    returnResult.push(next.value);
+                } else {
+                    returnResult.push(next);
+                }
+            }
+            break;
+        case XPR.UNORDERED_NODE_SNAPSHOT_TYPE:
+        case XPR.ORDERED_NODE_SNAPSHOT_TYPE:
+            returnResult = [];
+            for (let i = 0; i < result.snapshotLength; i++) {
+                next = result.snapshotItem(i);
+                if (next instanceof Components.interfaces.nsIDOMText) {
+                    returnResult.push(next.wholeText);
+                } else if (next instanceof Components.interfaces.nsIDOMAttr) {
+                    returnResult.push(next.value);
+                } else {
+                    returnResult.push(next);
+                }
+            }
+            break;
+        case XPR.ANY_UNORDERED_NODE_TYPE:
+        case XPR.FIRST_ORDERED_NODE_TYPE:
+            returnResult = result.singleNodeValue;
+            break;
+        default:
+            returnResult = null;
+            break;
+    }
+
+    if (Array.isArray(returnResult) && returnResult.length == 0) {
+        returnResult = null;
+    }
+
+    return returnResult;
+};
+
+/**
+ * Convenince function to evaluate an XPath expression and return null or the
+ * first result. Helpful if you just expect one value in a text() expression,
+ * but its possible that there will be more than one. The result may be:
+ *
+ * - null, if there are no results
+ * - A string, number, boolean or DOM Element value
+ *
+ * @param aNode     The context node to search from
+ * @param aExpr     The XPath expression to search for
+ * @param aResolver (optional) The namespace resolver to use for the expression
+ * @param aType     (optional) Force a result type, must be an XPathResult constant
+ * @return          The result, see above for details.
+ */
+cal.xml.evalXPathFirst = function evalXPathFirst(aNode, aExpr, aResolver, aType) {
+    let result = cal.xml.evalXPath(aNode, aExpr, aResolver, aType);
+
+    if (Array.isArray(result)) {
+        return result[0];
+    } else {
+        return result;
+    }
+};
+
+/**
+ * Parse the given string into a DOM tree
+ *
+ * @param str       The string to parse
+ * @param docUri    (optional) The document URI to use
+ * @param baseUri   (optional) The base URI to use
+ * @return          The parsed DOM Document
+ */
+cal.xml.parseString = function(str, docUri, baseUri) {
+    let parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
+                 .createInstance(Components.interfaces.nsIDOMParser);
+    parser.init(null, docUri, baseUri);
+    return parser.parseFromString(str, "application/xml");
+};
+
+/**
+ * Serialize the DOM tree into a string.
+ *
+ * @param doc       The DOM document to serialize
+ * @return          The DOM document as a string.
+ */
+cal.xml.serializeDOM = function(doc) {
+    let serializer = Components.classes["@mozilla.org/xmlextras/xmlserializer;1"]
+                               .createInstance(Components.interfaces.nsIDOMSerializer);
+    return serializer.serializeToString(doc);
+};
+
+/**
+ * Escape a string for use in XML
+ *
+ * @param str           The string to escape
+ * @param isAttribute   If true, " and ' are also escaped
+ * @return              The escaped string
+ */
+cal.xml.escapeString = function escapeString(str, isAttribute) {
+    return str.replace(/[&<>'"]/g, function(chr) {
+        switch (chr) {
+            case "&": return "&amp;";
+            case "<": return "&lt;";
+            case ">": return "&gt;";
+            case '"': if (isAttribute) return "&quot;";
+            case "'": if (isAttribute) return "&apos;";
+            default: return chr;
+        }
+    });
+};
--- a/calendar/providers/caldav/calDavCalendar.js
+++ b/calendar/providers/caldav/calDavCalendar.js
@@ -45,27 +45,50 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 Components.utils.import("resource://calendar/modules/calUtils.jsm");
+Components.utils.import("resource://calendar/modules/calXMLUtils.jsm");
 Components.utils.import("resource://calendar/modules/calIteratorUtils.jsm");
 Components.utils.import("resource://calendar/modules/calProviderUtils.jsm");
 Components.utils.import("resource://calendar/modules/calAuthUtils.jsm");
 
 //
 // calDavCalendar.js
 //
 
 const xmlHeader = '<?xml version="1.0" encoding="UTF-8"?>\n';
+
+const davNS = "DAV:"
+const caldavNS = "urn:ietf:params:xml:ns:caldav";
+const calservNS = "http://calendarserver.org/ns/";
+
 const cICL = Components.interfaces.calIChangeLog;
 
+function caldavNSResolver(prefix) {
+    const ns = {
+        D: davNS,
+        C: caldavNS,
+        CS: calservNS
+    };
+
+    return ns[prefix] || null;
+}
+
+function caldavXPath(aNode, aExpr, aType) {
+    return cal.xml.evalXPath(aNode, aExpr, caldavNSResolver, aType);
+}
+function caldavXPathFirst(aNode, aExpr, aType) {
+    return cal.xml.evalXPathFirst(aNode, aExpr, caldavNSResolver, aType);
+}
+
 function calDavCalendar() {
     this.initProviderBase();
     this.unmappedProperties = [];
     this.mUriParams = null;
     this.mItemInfoCache = {};
     this.mDisabled = false;
     this.mCalHomeSet = null;
     this.mInboxUrl = null;
@@ -600,17 +623,17 @@ calDavCalendar.prototype = {
                 thisCalendar.adoptOfflineItem(aItem, aListener);
             } else {
                 if (status > 999) {
                     status = "0x" + status.toString(16);
                 }
                 let responseBody="";
                 try {
                     responseBody = cal.convertByteArray(aResult, aResultLength);
-                } catch(e) {}
+                } catch (e) {}
 
                 cal.ERROR("CalDAV: Unexpected status adding item to " +
                           thisCalendar.name + ": " + status + "\n" +
                           responseBody + "\n" +
                           serializedItem);
 
                 thisCalendar.reportDavError(Components.interfaces.calIErrors.DAV_PUT_ERROR,
                                             status,
@@ -753,17 +776,17 @@ calDavCalendar.prototype = {
             } else {
                 if (status > 999) {
                     status = "0x " + status.toString(16);
                 }
 
                 let responseBody="";
                 try {
                     responseBody = cal.convertByteArray(aResult, aResultLength);
-                } catch(e) {}
+                } catch (e) {}
 
                 cal.ERROR("CalDAV: Unexpected status modifying item to " +
                         thisCalendar.name + ": " + status + "\n" +
                         responseBody + "\n" +
                         modifiedItemICS);
 
                 thisCalendar.reportDavError(Components.interfaces.calIErrors.DAV_PUT_ERROR,
                                             status,
@@ -931,17 +954,17 @@ calDavCalendar.prototype = {
                                           Components.interfaces.calIOperationListener.GET, aItem.id, aItem);
                     }
                 };
                 thisCalendar.deleteOfflineItem(aItem, opListener);
             } else {
                 let responseBody="";
                 try {
                     responseBody = cal.convertByteArray(aResult, aResultLength);
-                } catch(e) {}
+                } catch (e) {}
 
                 cal.ERROR("CalDAV: Unexpected status deleting item from " +
                           thisCalendar.name + ": " + status + "\n" +
                           responseBody + "\n" +
                           "uri: " + eventUri.spec);
 
                 thisCalendar.reportDavError(Components.interfaces.calIErrors.DAV_REMOVE_ERROR,
                                             status,
@@ -1320,25 +1343,25 @@ calDavCalendar.prototype = {
         // *OR* if webdav Sync is enabled (It is redundant to send a request
         // to get the collection tag (getctag) on a calendar if it supports
         // webdav sync, the sync request will only return data if something
         // changed).
         if (!this.mCtag || !this.mFirstRefreshDone || this.mHasWebdavSyncSupport ) {
             this.getUpdatedItems(this.calendarUri, aChangeLogListener);
             return;
         }
-        var thisCalendar = this;
+        let thisCalendar = this;
+        let queryXml =
+            xmlHeader +
+            '<D:propfind xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">' +
+              '<D:prop>' +
+                '<CS:getctag/>' +
+              '</D:prop>' +
+            '</D:propfind>';
 
-        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>
-                            <CS:getctag/>
-                        </D:prop>
-                        </D:propfind>;
         if (this.verboseLogging()) {
             cal.LOG("CalDAV: send(" + this.makeUri().spec + "): " + queryXml);
         }
         let httpchannel = cal.prepHttpChannel(this.makeUri(),
                                               queryXml,
                                               "text/xml; charset=utf-8",
                                               this);
         httpchannel.setRequestHeader("Depth", "0", false);
@@ -1380,29 +1403,29 @@ calDavCalendar.prototype = {
             if (!str) {
                 cal.LOG("CalDAV: Failed to get ctag from server for calendar " +
                         thisCalendar.name);
             } else if (thisCalendar.verboseLogging()) {
                 cal.LOG("CalDAV: recv: " + str);
             }
 
             try {
-                var multistatus = cal.safeNewXML(str);
+                var multistatus = cal.xml.parseString(str);
             } catch (ex) {
                 cal.LOG("CalDAV: Failed to get ctag from server for calendar " +
                         thisCalendar.name);
                 if (thisCalendar.isCached && aChangeLogListener) {
                     aChangeLogListener.onResult({ status: Components.results.NS_OK },
                                                 Components.results.NS_OK);
                 }
                 return;
             }
 
-            var ctag = multistatus..CS::getctag.toString();
-            if (!ctag.length || ctag != thisCalendar.mCtag) {
+            let ctag = caldavXPathFirst(multistatus, "/D:multistatus/D:response/D:propstat/D:prop/CS:getctag/text()");
+            if (!ctag || ctag != thisCalendar.mCtag) {
                 // ctag mismatch, need to fetch calendar-data
                 thisCalendar.mCtag = ctag;
                 thisCalendar.saveCalendarProperties();
                 thisCalendar.getUpdatedItems(thisCalendar.calendarUri,
                                              aChangeLogListener);
                 if (thisCalendar.verboseLogging()) {
                     cal.LOG("CalDAV: ctag mismatch on refresh, fetching data for " +
                             "calendar " + thisCalendar.name);
@@ -1473,36 +1496,33 @@ calDavCalendar.prototype = {
         }
 
         if (this.mHasWebdavSyncSupport) {
             webDavSync = new webDavSyncHandler(this,aUri,aChangeLogListener);
             webDavSync.doWebDAVSync();
             return;
         }
 
-        let C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
-        let D = new Namespace("D", "DAV:");
-        default xml namespace = C;
+        let queryXml =
+            xmlHeader +
+            '<D:propfind xmlns:D="DAV:">' +
+              '<D:prop>' +
+                '<D:getcontenttype/>' +
+                '<D:resourcetype/>' +
+                '<D:getetag/>' +
+              '</D:prop>' +
+            '</D:propfind>';
 
-        let queryXml = <D:propfind xmlns:D="DAV:">
-                        <D:prop>
-                            <D:getcontenttype/>
-                            <D:resourcetype/>
-                            <D:getetag/>
-                        </D:prop>
-                       </D:propfind>;
-
-        let queryString = xmlHeader + queryXml.toXMLString();
         let requestUri = this.makeUri(null, aUri);
         if (this.verboseLogging()) {
-            cal.LOG("CalDAV: send(" + requestUri.spec + "): " + queryString);
+            cal.LOG("CalDAV: send(" + requestUri.spec + "): " + queryXml);
         }
 
         let httpchannel = cal.prepHttpChannel(requestUri,
-                                              queryString,
+                                              queryXml,
                                               "text/xml; charset=utf-8",
                                               this);
         httpchannel.requestMethod = "PROPFIND";
         httpchannel.setRequestHeader("Depth", "1", false);
 
         // Submit the request
         let streamListener = new etagsHandler(this, aUri, aChangeLogListener);
         httpchannel.asyncOpen(streamListener, httpchannel);
@@ -1528,32 +1548,32 @@ calDavCalendar.prototype = {
      * checkServerCaps
      * findPrincipalNS
      * checkPrincipalsNameSpace
      * completeCheckServerInfo
      */
     checkDavResourceType: function caldav_checkDavResourceType(aChangeLogListener) {
         this.ensureTargetCalendar();
 
-        var resourceTypeXml = null;
-        var resourceType = kDavResourceTypeNone;
-        var thisCalendar = this;
+        let resourceTypeXml = null;
+        let resourceType = kDavResourceTypeNone;
+        let thisCalendar = this;
 
-        var C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
-        var D = new Namespace("D", "DAV:");
-        var CS = new Namespace("CS", "http://calendarserver.org/ns/");
-        var queryXml = <D:propfind xmlns:D="DAV:" xmlns:CS={CS} xmlns:C={C}>
-                        <D:prop>
-                            <D:resourcetype/>
-                            <D:owner/>
-                            <D:supported-report-set/>
-                            <C:supported-calendar-component-set/>
-                            <CS:getctag/>
-                        </D:prop>
-                        </D:propfind>;
+        let queryXml =
+            xmlHeader +
+            '<D:propfind xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/" xmlns:C="urn:ietf:params:xml:ns:caldav">' +
+              '<D:prop>' +
+                '<D:resourcetype/>' +
+                '<D:owner/>' +
+                '<D:supported-report-set/>' +
+                '<C:supported-calendar-component-set/>' +
+                '<CS:getctag/>' +
+              '</D:prop>' +
+            '</D:propfind>';
+
         if (this.verboseLogging()) {
             cal.LOG("CalDAV: send: " + queryXml);
         }
         let httpchannel = cal.prepHttpChannel(this.makeUri(),
                                               queryXml,
                                               "text/xml; charset=utf-8",
                                               this);
         httpchannel.setRequestHeader("Depth", "0", false);
@@ -1613,91 +1633,90 @@ calDavCalendar.prototype = {
                 thisCalendar.completeCheckServerInfo(aChangeLogListener,
                                                      Components.interfaces.calIErrors.DAV_NOT_DAV);
                 return;
             } else if (thisCalendar.verboseLogging()) {
                 cal.LOG("CalDAV: recv: " + str);
             }
 
             try {
-                var multistatus = cal.safeNewXML(str);
+                var multistatus = cal.xml.parseString(str);
             } catch (ex) {
+                cal.LOG("CalDAV: Failed to determine resource type for" +
+                        thisCalendar.name + ": " + ex);
                 thisCalendar.completeCheckServerInfo(aChangeLogListener,
                                                      Components.interfaces.calIErrors.DAV_NOT_DAV);
                 return;
             }
 
             // check for webdav-sync capability
             // http://tools.ietf.org/html/draft-daboo-webdav-sync
-            if (multistatus..D::["supported-report-set"]..D::["sync-collection"].length() > 0) {
+            if (caldavXPath(multistatus, "/D:multistatus/D:response/D:propstat/D:prop" +
+                                 "/D:supported-report-set/D:supported-report/D:report/D:sync-collection")) {
                 cal.LOG("CalDAV: Collection has webdav sync support");
                 thisCalendar.mHasWebdavSyncSupport = true;
             }
 
             // check for server-side ctag support only if webdav sync is not available
-            var ctag = multistatus..CS::["getctag"].toString();
-            if (!thisCalendar.mHasWebdavSyncSupport && ctag.length) {
+            let ctag = caldavXPathFirst(multistatus, "/D:multistatus/D:response/D:propstat/D:prop/CS:getctag/text()");
+            if (!thisCalendar.mHasWebdavSyncSupport && ctag) {
                 // We compare the stored ctag with the one we just got, if
                 // they don't match, we update the items in safeRefresh.
                 if (ctag == thisCalendar.mCtag) {
                     thisCalendar.mFirstRefreshDone = true;
                 }
 
                 thisCalendar.mCtag = ctag;
                 thisCalendar.saveCalendarProperties();
                 if (thisCalendar.verboseLogging()) {
                     cal.LOG("CalDAV: initial ctag " + ctag + " for calendar " +
                             thisCalendar.name);
                 }
             }
 
-            supportedComponentsXml = multistatus..C::["supported-calendar-component-set"];
-            // use supported-calendar-component-set if the server supports it; some do not
-            if (supportedComponentsXml.C::comp.length() > 0) {
-                thisCalendar.mSupportedItemTypes.length = 0;
-                for each (let sc in supportedComponentsXml.C::comp) {
-                    // accept name attribute from all namespaces to workaround Cosmo bug
-                    // see bug 605378 comment 6
-                    let comp = sc.@*::name.toString();
 
-                    if (thisCalendar.mGenerallySupportedItemTypes.indexOf(comp) >= 0) {
-                        cal.LOG("Adding supported item: " + comp + " for calendar: " + thisCalendar.name);
-                        thisCalendar.mSupportedItemTypes.push(comp);
-                    }
-                }
+            // Use supported-calendar-component-set if the server supports it; some do not
+            // Accept name attribute from all namespaces to workaround Cosmo bug see bug 605378 comment 6
+            let supportedComponents = caldavXPath(multistatus,
+                "/D:multistatus/D:response/D:propstat/D:prop/C:supported-calendar-component-set/C:comp/@*[local-name()='name']");
+            if (supportedComponents && supportedComponents.length) {
+                thisCalendar.mSupportedItemTypes = [ compName
+                    for each (compName in supportedComponents)
+                    if (thisCalendar.mGenerallySupportedItemTypes.indexOf(compName) >= 0)
+                ];
+                cal.LOG("Adding supported items: " + thisCalendar.mSupportedItemTypes.join(",") + " for calendar: " + thisCalendar.name);
             }
 
             // check if owner is specified; might save some work
-            thisCalendar.mPrincipalUrl = multistatus..D::["owner"]..D::href.toString() || null;
+            let owner = caldavXPathFirst(multistatus, "/D:multistatus/D:response/D:propstat/D:prop/D:owner/D:href/text()");
+            if (owner) {
+                thisCalendar.mPrincipalUrl = owner;
+                cal.LOG("CalDAV: Found principal url " + thisCalendar.mPrincipalUrl);
+            }
 
-            var resourceTypeXml = multistatus..D::["resourcetype"];
-            if (resourceTypeXml.length() == 0) {
+            let resourceTypeXml = caldavXPath(multistatus, "/D:multistatus/D:response/D:propstat/D:prop/D:resourcetype");
+            if (!resourceTypeXml) {
                 resourceType = kDavResourceTypeNone;
-            } else if (resourceTypeXml.toString().indexOf("calendar") != -1) {
+            } else if (caldavXPath(resourceTypeXml[0], "C:calendar")) {
                 resourceType = kDavResourceTypeCalendar;
-            } else if (resourceTypeXml.toString().indexOf("collection") != -1) {
+            } else if (caldavXPath(resourceTypeXml[0], "D:collection")) {
                 resourceType = kDavResourceTypeCollection;
             }
 
-            // specialcasing so as not to break older SOGo revs. Remove when
-            // 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) {
+                cal.LOG("CalDAV: No resource type received, " + thisCalendar.name + " doesn't seem to point to a DAV resource");
                 thisCalendar.completeCheckServerInfo(aChangeLogListener,
                                                      Components.interfaces.calIErrors.DAV_NOT_DAV);
                 return;
             }
 
             if ((resourceType == kDavResourceTypeCollection) &&
                 !thisCalendar.mDisabled) {
+                cal.LOG("CalDAV: " + thisCalendar.name + " points to a DAV resource, but not a CalDAV calendar");
                 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) {
@@ -1818,25 +1837,25 @@ calDavCalendar.prototype = {
         if (this.principalUrl) {
             // We already have a principal namespace, use it.
             this.checkPrincipalsNameSpace([this.principalUrl],
                                           aChangeLogListener);
             return;
         }
 
         let homeSet = this.makeUri(null, this.mCalHomeSet);
-        var thisCalendar = this;
+        let thisCalendar = this;
 
-        var D = new Namespace("D", "DAV:");
-        var queryXml =
-            <D:propfind xmlns:D="DAV:">
-                <D:prop>
-                    <D:principal-collection-set/>
-                </D:prop>
-            </D:propfind>;
+        let queryXml =
+            xmlHeader +
+            '<D:propfind xmlns:D="DAV:">' +
+              '<D:prop>' +
+                '<D:principal-collection-set/>' +
+              '</D:prop>' +
+            '</D:propfind>';
 
         if (this.verboseLogging()) {
             cal.LOG("CalDAV: send: " + homeSet.spec + "\n"  + queryXml);
         }
         let httpchannel = cal.prepHttpChannel(homeSet,
                                               queryXml,
                                               "text/xml; charset=utf-8",
                                               this);
@@ -1862,22 +1881,29 @@ calDavCalendar.prototype = {
                 cal.LOG("CalDAV: Failed to propstat principal namespace for " + thisCalendar.name);
                 thisCalendar.completeCheckServerInfo(aChangeLogListener,
                                                      Components.results.NS_ERROR_FAILURE);
                 return;
             } else if (thisCalendar.verboseLogging()) {
                 cal.LOG("CalDAV: recv: " + str);
             }
 
-            let multistatus = cal.safeNewXML(str);
-            var pcs = multistatus..D::["principal-collection-set"]..D::href;
-            var nsList = [];
-            for (var ns in pcs) {
-                var nsPath = thisCalendar.ensureDecodedPath(pcs[ns].toString());
-                nsList.push(nsPath);
+            try {
+                var multistatus = cal.xml.parseString(str);
+            } catch (ex) {
+                cal.LOG("CalDAV: Failed to propstat principal namespace for " + thisCalendar.name);
+                thisCalendar.completeCheckServerInfo(aChangeLogListener,
+                                                     Components.results.NS_ERROR_FAILURE);
+                return;
+            }
+
+            let pcs = caldavXPath(multistatus, "/D:multistatus/D:response/D:propstat/D:prop/D:principal-collection-set/D:href/text()");
+            let nsList = [];
+            if (pcs) {
+                nsList = pcs.map(function(x) thisCalendar.ensureDecodedPath(x));
             }
 
             thisCalendar.checkPrincipalsNameSpace(nsList, aChangeLogListener);
         };
 
         cal.sendHttpRequest(cal.createStreamLoader(), httpchannel, streamListener);
     },
 
@@ -1907,53 +1933,48 @@ calDavCalendar.prototype = {
                 cal.LOG("CalDAV: principal namespace list empty, calendar " +
                         this.name + " doesn't support scheduling");
             }
             doesntSupportScheduling();
             return;
         }
 
         // Remove trailing slash, if its there
-        var homePath = this.ensureEncodedPath(this.mCalHomeSet.spec.replace(/\/$/,""));
-
-        var C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
-        var D = new Namespace("D", "DAV:");
-        default xml namespace = C;
-
-        var queryXml;
-        var queryMethod;
-        var queryDepth;
+        let homePath = this.ensureEncodedPath(this.mCalHomeSet.spec.replace(/\/$/,""));
+        let queryXml, queryMethod, queryDepth;
         if (this.mPrincipalUrl) {
-            queryXml = <D:propfind xmlns:D="DAV:"
-                                   xmlns:C="urn:ietf:params:xml:ns:caldav">
-                <D:prop>
-                    <C:calendar-home-set/>
-                    <C:calendar-user-address-set/>
-                    <C:schedule-inbox-URL/>
-                    <C:schedule-outbox-URL/>
-                </D:prop>
-            </D:propfind>;
+            queryXml =
+                xmlHeader +
+                '<D:propfind xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">' +
+                  '<D:prop>' +
+                    '<C:calendar-home-set/>' +
+                    '<C:calendar-user-address-set/>' +
+                    '<C:schedule-inbox-URL/>' +
+                    '<C:schedule-outbox-URL/>' +
+                  '</D:prop>' +
+                '</D:propfind>';
             queryMethod = "PROPFIND";
             queryDepth = 0;
         } else {
-            queryXml = <D:principal-property-search xmlns:D="DAV:"
-                                                    xmlns:C="urn:ietf:params:xml:ns:caldav">
-            <D:property-search>
-                <D:prop>
-                    <C:calendar-home-set/>
-                </D:prop>
-                <D:match>{homePath}</D:match>
-            </D:property-search>
-                <D:prop>
-                    <C:calendar-home-set/>
-                    <C:calendar-user-address-set/>
-                    <C:schedule-inbox-URL/>
-                    <C:schedule-outbox-URL/>
-                </D:prop>
-            </D:principal-property-search>;
+            queryXml =
+                xmlHeader +
+                '<D:principal-property-search xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">' +
+                '<D:property-search>' +
+                    '<D:prop>' +
+                        '<C:calendar-home-set/>' +
+                    '</D:prop>' +
+                    '<D:match>' + cal.xml.escapeString(homePath) + '</D:match>' +
+                '</D:property-search>' +
+                    '<D:prop>' +
+                        '<C:calendar-home-set/>' +
+                        '<C:calendar-user-address-set/>' +
+                        '<C:schedule-inbox-URL/>' +
+                        '<C:schedule-outbox-URL/>' +
+                    '</D:prop>' +
+                '</D:principal-property-search>';
             queryMethod = "REPORT";
             queryDepth = 1;
         }
 
         // We want a trailing slash, ensure it.
         let nextNS = aNameSpaceList.pop().replace(/([^\/])$/, "$1/");
         let requestUri = makeURL(this.calendarUri.prePath + this.ensureEncodedPath(nextNS));
 
@@ -1989,75 +2010,71 @@ calDavCalendar.prototype = {
 
             if (request.responseStatus != 207) {
                 cal.LOG("CalDAV: Bad response to in/outbox query, status " +
                     request.responseStatus);
                 doesntSupportScheduling();
                 return;
             }
 
-            let multistatus = cal.safeNewXML(str);
-            let multistatusLength = multistatus.*::response.length();
-
-            for each (let response in multistatus.*::response) {
-                let responseCHS = null;
-                try {
-                    responseCHS = response..*::["calendar-home-set"]..*::href[0]
-                                          .toString().replace(/([^\/])$/, "$1/");
-                } catch (ex) {}
+            try {
+                var multistatus = cal.xml.parseString(str);
+            } catch (ex) {
+                cal.LOG("CalDAV: Could not parse multistatus response: " + ex + "\n" + str);
+                doesntSupportScheduling();
+                return;
+            }
 
-                if (multistatusLength > 1 &&
-                    (responseCHS != thisCalendar.mCalHomeSet.path &&
-                     responseCHS != thisCalendar.mCalHomeSet.spec)) {
-                    // If there are multiple home sets, then we need to match
-                    // the home url. If there is only one, we can assume its the
-                    // correct one, even if the home set doesn't quite match.
-                    continue;
-                }
-                for each (let addrHref in response..*::["calendar-user-address-set"]..*::href) {
-                    if (addrHref.toString().substr(0, 7).toLowerCase() == "mailto:") {
-                        thisCalendar.mCalendarUserAddress = addrHref.toString();
+            let homeSets = caldavXPath(multistatus, "/D:multistatus/D:response/D:propstat/D:prop/C:calendar-home-set/D:href/text()");
+            function homeSetMatches(homeSet) {
+                let normalized = homeSet.replace(/([^\/])$/, "$1/");
+                let chs = thisCalendar.mCalHomeSet;
+                return normalized == chs.path || normalized == chs.spec;
+            }
+
+            // If there are multiple home sets, we need to match the email addresses for scheduling.
+            // If there is only one, assume its the right one.
+            // TODO with multiple address sets, we should just use the ACL manager.
+            if (homeSets && (homeSets.length == 1 || homeSets.some(homeSetMatches))) {
+                let cuaSets = caldavXPath(multistatus, "/D:multistatus/D:response/D:propstat/D:prop/C:calendar-user-address-set/D:href/text()");
+                for each (let addr in cuaSets) {
+                    if (addr.match(/^mailto:/i)) {
+                        thisCalendar.mCalendarUserAddress = addr;
                     }
                 }
-                let ibUrl = thisCalendar.mUri.clone();
-                try {
-                    ibUrl.path = thisCalendar.ensureDecodedPath(response..*::["schedule-inbox-URL"]..*::href[0].toString());
-                } catch (ex) {
-                    // most likely this is a Kerio server that omits the "href"
-                    ibUrl.path = thisCalendar.ensureDecodedPath(response..*::["schedule-inbox-URL"].toString());
+
+                function createBoxUrl(path) {
+                    let url = thisCalendar.mUri.clone();
+                    url.path = thisCalendar.ensureDecodedPath(path);
+                    // Make sure the uri has a / at the end, as we do with the calendarUri.
+                    if (url.path.charAt(url.path.length - 1) != '/') {
+                        url.path += "/";
+                    }
+                    return url;
                 }
 
-                // Make sure the inbox uri has a / at the end, as we do with the
-                // calendarUri.
-                if (ibUrl.path.charAt(ibUrl.path.length - 1) != '/') {
-                    ibUrl.path += "/";
+                let inboxPath = caldavXPathFirst(multistatus, "/D:multistatus/D:response/D:propstat/D:prop/C:schedule-inbox-URL/D:href/text()");
+                if (!inboxPath) {
+                    // most likely this is a Kerio server that omits the "href"
+                    inboxPath = caldavXPathFirst(multistatus, "/D:multistatus/D:response/D:propstat/D:prop/C:schedule-inbox-URL/text()");
                 }
+                thisCalendar.mInboxUrl = createBoxUrl(inboxPath);
 
-                thisCalendar.mInboxUrl = ibUrl;
-                if (thisCalendar.calendarUri.spec == ibUrl.spec) {
+                if (thisCalendar.calendarUri.spec == thisCalendar.mInboxUrl.spec) {
                     // If the inbox matches the calendar uri (i.e SOGo), then we
                     // don't need to poll the inbox.
                     thisCalendar.mShouldPollInbox = false;
                 }
 
-                let obUrl = thisCalendar.mUri.clone();
-                try {
-                    obUrl.path = thisCalendar.ensureDecodedPath(response..*::["schedule-outbox-URL"]..*::href[0].toString());
-                } catch (ex) {
+                let outboxPath = caldavXPathFirst(multistatus, "/D:multistatus/D:response/D:propstat/D:prop/C:schedule-outbox-URL/D:href/text()");
+                if (!outboxPath) {
                     // most likely this is a Kerio server that omits the "href"
-                    obUrl.path = thisCalendar.ensureDecodedPath(response..*::["schedule-outbox-URL"].toString());
+                    outboxPath = caldavXPathFirst(multistatus, "/D:multistatus/D:response/D:propstat/D:prop/C:schedule-outbox-URL/text()");
                 }
-
-                // Make sure the outbox uri has a / at the end, as we do with
-                // the calendarUri.
-                if (obUrl.path.charAt(obUrl.path.length - 1) != '/') {
-                    obUrl.path += "/";
-                }
-
-                thisCalendar.mOutboxUrl = obUrl;
+                thisCalendar.mOutboxUrl = createBoxUrl(outboxPath);
             }
 
             if (!thisCalendar.calendarUserAddress ||
                 !thisCalendar.mInboxUrl ||
                 !thisCalendar.mOutboxUrl) {
                 if (aNameSpaceList.length) {
                     // Check the next namespace to find the info we need.
                     thisCalendar.checkPrincipalsNameSpace(aNameSpaceList, aChangeLogListener);
@@ -2262,35 +2279,40 @@ calDavCalendar.prototype = {
 
             if (request.responseStatus == 200) {
                 var periodsToReturn = [];
                 var fbTypeMap = {};
                 fbTypeMap["FREE"] = calIFreeBusyInterval.FREE;
                 fbTypeMap["BUSY"] = calIFreeBusyInterval.BUSY;
                 fbTypeMap["BUSY-UNAVAILABLE"] = calIFreeBusyInterval.BUSY_UNAVAILABLE;
                 fbTypeMap["BUSY-TENTATIVE"] = calIFreeBusyInterval.BUSY_TENTATIVE;
-                var C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
-                var D = new Namespace("D", "DAV:");
 
-                let response = cal.safeNewXML(str);
-                let status = response..C::response..C::["request-status"];
-                if (status.substr(0,1) != 2) {
+                try {
+                    var fbResult = cal.xml.parseString(str);
+                } catch (ex) {
+                    cal.LOG("CalDAV: Could not parse freebusy response " + ex);
+                    aListener.onResult(null, null);
+                    return;
+                }
+
+                let status = caldavXPathFirst(fbResult, "/C:schedule-response/C:response/C:request-status/text()");
+                if (!status || status.substr(0,1) != "2") {
                     cal.LOG("CalDAV: Got status " + status + " in response to " +
                             "freebusy query for " + thisCalendar.name) ;
                     aListener.onResult(null, null);
                     return;
                 }
                 if (status.substr(0,3) != "2.0") {
                     cal.LOG("CalDAV: Got status " + status + " in response to " +
                             "freebusy query for" + thisCalendar.name);
                 }
 
-                var caldata = response..C::response..C::["calendar-data"];
+                let caldata = caldavXPathFirst(fbResult, "/C:schedule-response/C:response/C:calendar-data/text()");
                 try {
-                    let calComp = getIcsService().parseICS(caldata, null);
+                    let calComp = cal.getIcsService().parseICS(caldata, null);
                     for (let fbComp in cal.ical.calendarComponentIterator(calComp)) {
                         let interval;
 
                         let replyRangeStart = fbComp.startTime;
                         if (replyRangeStart && (aRangeStart.compare(replyRangeStart) == -1)) {
                             interval = new cal.FreeBusyInterval(aCalId,
                                                                 calIFreeBusyInterval.UNKNOWN,
                                                                 aRangeStart,
@@ -2589,29 +2611,36 @@ calDavCalendar.prototype = {
                         if (thisCalendar.verboseLogging()) {
                             cal.LOG("CalDAV: recv: " + str);
                         }
                     } else {
                         cal.LOG("CalDAV: Failed to parse iTIP response for" +
                                 thisCalendar.name);
                     }
 
-                    var C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
-                    var D = new Namespace("D", "DAV:");
-                    let responseXML = cal.safeNewXML(str);
+                    try {
+                        var responseXML = cal.xml.parseString(str);
+                    } catch (ex) {
+                        cal.LOG("CalDAV: Could not parse multistatus response: " + ex + "\n" + str);
+                        return;
+                    }
 
                     var remainingAttendees = [];
-                    for each (let response in responseXML.*::response) {
-                        var recip = response..C::recipient..D::href;
-                        var status = response..C::["request-status"];
+                    // TODO The following XPath expressions are currently
+                    // untested code, as I don't have a caldav-sched server
+                    // available. If you find someone who does, please test!
+                    let responses = caldavXPath(responseXML, "/C:schedule-response/C:response");
+                    for each (let response in responses) {
+                        let recip = caldavXPathFirst(response, "C:recipient/D:href/text()");
+                        let status = caldavXPathFirst(response, "C:request-status/text()");
                         if (status.substr(0, 1) != "2") {
                             if (thisCalendar.verboseLogging()) {
-                                cal.LOG("CalDAV: failed delivery to " + recip);
+                                cal.LOG("CalDAV: Failed scheduling delivery to " + recip);
                             }
-                            for each (var att in aRecipients) {
+                            for each (let att in aRecipients) {
                                 if (att.id.toLowerCase() == recip.toLowerCase()) {
                                     remainingAttendees.push(att);
                                     break;
                                 }
                             }
                         }
                     }
 
@@ -2681,17 +2710,17 @@ calDavCalendar.prototype = {
         aOldChannel.QueryInterface(Components.interfaces.nsIHttpChannel);
 
         function copyHeader(aHdr) {
             try {
                 let hdrValue = aOldChannel.getRequestHeader(aHdr);
                 if (hdrValue) {
                     aNewChannel.setRequestHeader(aHdr, hdrValue, false);
                 }
-            } catch(e) {
+            } catch (e) {
                 if (e.code != Components.results.NS_ERROR_NOT_AVAILIBLE) {
                     // The header could possibly not be availible, ignore that
                     // case but throw otherwise
                     throw e;
                }
             }
         }
 
--- a/calendar/providers/caldav/calDavRequestHandlers.js
+++ b/calendar/providers/caldav/calDavRequestHandlers.js
@@ -363,41 +363,41 @@ webDavSyncHandler.prototype = {
 
     doWebDAVSync: function doWebDAVSync() {
         if (this.calendar.mDisabled) {
             // check if maybe our calendar has become available
             this.calendar.checkDavResourceType(this.changeLogListener);
             return;
         }
 
-        let C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
-        let D = new Namespace("D", "DAV:");
-        default xml namespace = D;
-        let queryXml =
-          <sync-collection>
-            <sync-token/>
-            <prop>
-              <getcontenttype/>
-              <getetag/>
-            </prop>
-          </sync-collection>;
 
+        let syncTokenString = "<sync-token/>";
         if (this.calendar.mWebdavSyncToken && this.calendar.mWebdavSyncToken.length > 0) {
-            queryXml.D::["sync-token"] = this.calendar.mWebdavSyncToken;
+            let syncToken = cal.xml.escapeString(this.calendar.mWebdavSyncToken);
+            syncTokenString = "<sync-token>" + syncToken + "</sync-token>";
         }
 
-        let queryString = xmlHeader + queryXml.toXMLString();
+        let queryXml =
+          xmlHeader +
+          '<sync-collection xmlns="DAV:">' +
+            syncTokenString +
+            '<prop>' +
+              '<getcontenttype/>' +
+              '<getetag/>' +
+            '</prop>' +
+          '</sync-collection>';
+
         let requestUri = this.calendar.makeUri(null, this.baseUri);
 
         if (this.calendar.verboseLogging()) {
-            cal.LOG("CalDAV: send(" + requestUri.spec + "): " + queryString);
+            cal.LOG("CalDAV: send(" + requestUri.spec + "): " + queryXml);
         }
         cal.LOG("CalDAV: webdav-sync Token: " + this.calendar.mWebdavSyncToken);
         let httpchannel = cal.prepHttpChannel(requestUri,
-                                              queryString,
+                                              queryXml,
                                               "text/xml; charset=utf-8",
                                               this.calendar);
         httpchannel.setRequestHeader("Depth", "1", false);
         httpchannel.requestMethod = "REPORT";
         // Submit the request
         httpchannel.asyncOpen(this, httpchannel);
     },
 
@@ -703,43 +703,43 @@ multigetSyncHandler.prototype = {
     },
 
     doMultiGet: function doMultiGet() {
         if (this.calendar.mDisabled) {
             // check if maybe our calendar has become available
             this.calendar.checkDavResourceType(this.changeLogListener);
             return;
         }
-        let C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
-        let D = new Namespace("D", "DAV:");
-        let queryXml =
-          <calendar-multiget xmlns:D={D} xmlns={C}>
-            <D:prop>
-              <D:getetag/>
-              <calendar-data/>
-            </D:prop>
-          </calendar-multiget>;
 
         let batchSize = cal.getPrefSafe("calendar.caldav.multigetBatchSize", 100);
+        let hrefString = "";
         while (this.itemsNeedFetching.length && batchSize > 0) {
-            batchSize --;
+            batchSize--;
             // ensureEncodedPath extracts only the path component of the item and
             // encodes it before it is sent to the server
             let locpath = this.calendar.ensureEncodedPath(this.itemsNeedFetching.pop());
-            queryXml.D::prop += <D:href xmlns:D={D}>{locpath}</D:href>;
+            hrefString += "<D:href>" + cal.xml.escapeString(locpath) + "</D:href>";
         }
 
-        let multigetQueryString = xmlHeader + queryXml.toXMLString();
+        let queryXml =
+          xmlHeader +
+          '<C:calendar-multiget xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">' +
+            hrefString +
+            '<D:prop>' +
+              '<D:getetag/>' +
+              '<C:calendar-data/>' +
+            '</D:prop>' +
+          '</C:calendar-multiget>';
 
         let requestUri = this.calendar.makeUri(null, this.baseUri);
         if (this.calendar.verboseLogging()) {
             cal.LOG("CalDAV: send(" + requestUri.spec + "): " + queryXml);
         }
         let httpchannel = cal.prepHttpChannel(requestUri,
-                                              multigetQueryString,
+                                              queryXml,
                                               "text/xml; charset=utf-8",
                                               this.calendar);
         httpchannel.setRequestHeader("Depth", "1", false);
         httpchannel.requestMethod = "REPORT";
         // Submit the request
         httpchannel.asyncOpen(this, httpchannel);
     },
 
--- a/calendar/providers/ics/calICSCalendar.js
+++ b/calendar/providers/ics/calICSCalendar.js
@@ -39,32 +39,48 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 Components.utils.import("resource://calendar/modules/calUtils.jsm");
+Components.utils.import("resource://calendar/modules/calXMLUtils.jsm");
 Components.utils.import("resource://calendar/modules/calProviderUtils.jsm");
 
 //
 // calICSCalendar.js
 //
 // This is a non-sync ics file. It reads the file pointer to by uri when set,
 // then writes it on updates. External changes to the file will be
 // ignored and overwritten.
 //
 // XXX Should do locks, so that external changes are not overwritten.
 
 const CI = Components.interfaces;
 const calIOperationListener = Components.interfaces.calIOperationListener;
 const calICalendar = Components.interfaces.calICalendar;
 const calIErrors = Components.interfaces.calIErrors;
 
+function icsNSResolver(prefix) {
+    const ns = {
+        D: "DAV:"
+    };
+
+    return ns[prefix] || null;
+}
+
+function icsXPath(aNode, aExpr, aType) {
+    return cal.xml.evalXPath(aNode, aExpr, icsNSResolver, aType);
+}
+function icsXPathFirst(aNode, aExpr, aType) {
+    return cal.xml.evalXPathFirst(aNode, aExpr, icsNSResolver, aType);
+}
+
 function calICSCalendar() {
     this.initProviderBase();
     this.initICSCalendar();
 
     this.unmappedComponents = [];
     this.unmappedProperties = [];
     this.queue = new Array();
     this.mModificationActions = [];
@@ -1005,37 +1021,32 @@ httpHooks.prototype = {
             etagListener.onStreamComplete =
                 function ics_etLoSC(aLoader, aContext, aStatus, aResultLength,
                                     aResult) {
                 var resultConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
                                                 .createInstance(Components
                                                 .interfaces.nsIScriptableUnicodeConverter);
                 resultConverter.charset = "UTF-8";
 
-                var str;
                 try {
-                    str = resultConverter.convertFromByteArray(aResult, aResultLength);
+                    var str = resultConverter.convertFromByteArray(aResult, aResultLength);
+                    var multistatus = cal.xml.parseString(str);
                 } catch (e) {
                     cal.LOG("[calICSCalendar] Failed to fetch channel etag");
                 }
-                var multistatus = cal.safeNewXML(str);
-                try {
-                    thisHook.mEtag = multistatus..D::getetag;
-                } catch (e) {
-                    thisHook.mEtag = null;
-                }
+
+                thisHook.mEtag = icsXPathFirst(multistatus, "/D:propfind/D:response/D:propstat/D:prop/D:getetag");
                 aRespFunc();
             }
-            var D = new Namespace("D", "DAV:");
-            default xml namespace = D;
-            var queryXml = <D:propfind xmlns:D="DAV:">
-                    <D:prop>
-                      <D:getetag/>
-                    </D:prop>
-                  </D:propfind>;
+            let queryXml =
+                '<D:propfind xmlns:D="DAV:">' +
+                  '<D:prop>' +
+                    '<D:getetag/>' +
+                  '</D:prop>' +
+                '</D:propfind>';
 
             let etagChannel = cal.prepHttpChannel(aChannel.URI, queryXml,
                                                   "text/xml; charset=utf-8",
                                                   this);
             etagChannel.setRequestHeader("Depth", "0", false);
             etagChannel.requestMethod = "PROPFIND";
             var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
                                          .createInstance(Components.interfaces