123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- if (typeof dav == 'undefined') { dav = {}; };
- dav._XML_CHAR_MAP = {
- '<': '<',
- '>': '>',
- '&': '&',
- '"': '"',
- "'": '''
- };
- dav._escapeXml = function(s) {
- return s.replace(/[<>&"']/g, function (ch) {
- return dav._XML_CHAR_MAP[ch];
- });
- };
- dav.Client = function(options) {
- var i;
- for(i in options) {
- this[i] = options[i];
- }
- };
- dav.Client.prototype = {
- baseUrl : null,
- userName : null,
- password : null,
- xmlNamespaces : {
- 'DAV:' : 'd'
- },
- /**
- * Generates a propFind request.
- *
- * @param {string} url Url to do the propfind request on
- * @param {Array} properties List of properties to retrieve.
- * @param {Object} [headers] headers
- * @return {Promise}
- */
- propFind : function(url, properties, depth, headers) {
- if(typeof depth == "undefined") {
- depth = 0;
- }
- headers = headers || {};
- headers['Depth'] = depth;
- headers['Content-Type'] = 'application/xml; charset=utf-8';
- var body =
- '<?xml version="1.0"?>\n' +
- '<d:propfind ';
- var namespace;
- for (namespace in this.xmlNamespaces) {
- body += ' xmlns:' + this.xmlNamespaces[namespace] + '="' + namespace + '"';
- }
- body += '>\n' +
- ' <d:prop>\n';
- for(var ii in properties) {
- var property = this.parseClarkNotation(properties[ii]);
- if (this.xmlNamespaces[property.namespace]) {
- body+=' <' + this.xmlNamespaces[property.namespace] + ':' + property.name + ' />\n';
- } else {
- body+=' <x:' + property.name + ' xmlns:x="' + property.namespace + '" />\n';
- }
- }
- body+=' </d:prop>\n';
- body+='</d:propfind>';
- return this.request('PROPFIND', url, headers, body).then(
- function(result) {
- if (depth===0) {
- return {
- status: result.status,
- body: result.body[0],
- xhr: result.xhr
- };
- } else {
- return {
- status: result.status,
- body: result.body,
- xhr: result.xhr
- };
- }
- }.bind(this)
- );
- },
- /**
- * Generates a propPatch request.
- *
- * @param {string} url Url to do the proppatch request on
- * @param {Array} properties List of properties to store.
- * @param {Object} [headers] headers
- * @return {Promise}
- */
- propPatch : function(url, properties, headers) {
- headers = headers || {};
- headers['Content-Type'] = 'application/xml; charset=utf-8';
- var body =
- '<?xml version="1.0"?>\n' +
- '<d:propertyupdate ';
- var namespace;
- for (namespace in this.xmlNamespaces) {
- body += ' xmlns:' + this.xmlNamespaces[namespace] + '="' + namespace + '"';
- }
- body += '>\n' +
- ' <d:set>\n' +
- ' <d:prop>\n';
- for(var ii in properties) {
- var property = this.parseClarkNotation(ii);
- var propName;
- var propValue = properties[ii];
- if (this.xmlNamespaces[property.namespace]) {
- propName = this.xmlNamespaces[property.namespace] + ':' + property.name;
- } else {
- propName = 'x:' + property.name + ' xmlns:x="' + property.namespace + '"';
- }
- body += ' <' + propName + '>' + dav._escapeXml(propValue) + '</' + propName + '>\n';
- }
- body+=' </d:prop>\n';
- body+=' </d:set>\n';
- body+='</d:propertyupdate>';
- return this.request('PROPPATCH', url, headers, body).then(
- function(result) {
- return {
- status: result.status,
- body: result.body,
- xhr: result.xhr
- };
- }.bind(this)
- );
- },
- /**
- * Performs a HTTP request, and returns a Promise
- *
- * @param {string} method HTTP method
- * @param {string} url Relative or absolute url
- * @param {Object} headers HTTP headers as an object.
- * @param {string} body HTTP request body.
- * @return {Promise}
- */
- request : function(method, url, headers, body) {
- var self = this;
- var xhr = this.xhrProvider();
- headers = headers || {};
-
- if (this.userName) {
- headers['Authorization'] = 'Basic ' + btoa(this.userName + ':' + this.password);
- // xhr.open(method, this.resolveUrl(url), true, this.userName, this.password);
- }
- xhr.open(method, this.resolveUrl(url), true);
- var ii;
- for(ii in headers) {
- xhr.setRequestHeader(ii, headers[ii]);
- }
- // Work around for edge
- if (body === undefined) {
- xhr.send();
- } else {
- xhr.send(body);
- }
- return new Promise(function(fulfill, reject) {
- xhr.onreadystatechange = function() {
- if (xhr.readyState !== 4) {
- return;
- }
- var resultBody = xhr.response;
- if (xhr.status === 207) {
- resultBody = self.parseMultiStatus(xhr.response);
- }
- fulfill({
- body: resultBody,
- status: xhr.status,
- xhr: xhr
- });
- };
- xhr.ontimeout = function() {
- reject(new Error('Timeout exceeded'));
- };
- });
- },
- /**
- * Returns an XMLHttpRequest object.
- *
- * This is in its own method, so it can be easily overridden.
- *
- * @return {XMLHttpRequest}
- */
- xhrProvider : function() {
- return new XMLHttpRequest();
- },
- /**
- * Parses a property node.
- *
- * Either returns a string if the node only contains text, or returns an
- * array of non-text subnodes.
- *
- * @param {Object} propNode node to parse
- * @return {string|Array} text content as string or array of subnodes, excluding text nodes
- */
- _parsePropNode: function(propNode) {
- var content = null;
- if (propNode.childNodes && propNode.childNodes.length > 0) {
- var subNodes = [];
- // filter out text nodes
- for (var j = 0; j < propNode.childNodes.length; j++) {
- var node = propNode.childNodes[j];
- if (node.nodeType === 1) {
- subNodes.push(node);
- }
- }
- if (subNodes.length) {
- content = subNodes;
- }
- }
- return content || propNode.textContent || propNode.text || '';
- },
- /**
- * Parses a multi-status response body.
- *
- * @param {string} xmlBody
- * @param {Array}
- */
- parseMultiStatus : function(xmlBody) {
- var parser = new DOMParser();
- var doc = parser.parseFromString(xmlBody, "application/xml");
- var resolver = function(foo) {
- var ii;
- for(ii in this.xmlNamespaces) {
- if (this.xmlNamespaces[ii] === foo) {
- return ii;
- }
- }
- }.bind(this);
- var responseIterator = doc.evaluate('/d:multistatus/d:response', doc, resolver, XPathResult.ANY_TYPE, null);
- var result = [];
- var responseNode = responseIterator.iterateNext();
- while(responseNode) {
- var response = {
- href : null,
- propStat : []
- };
- response.href = doc.evaluate('string(d:href)', responseNode, resolver, XPathResult.ANY_TYPE, null).stringValue;
- var propStatIterator = doc.evaluate('d:propstat', responseNode, resolver, XPathResult.ANY_TYPE, null);
- var propStatNode = propStatIterator.iterateNext();
- while(propStatNode) {
- var propStat = {
- status : doc.evaluate('string(d:status)', propStatNode, resolver, XPathResult.ANY_TYPE, null).stringValue,
- properties : [],
- };
- var propIterator = doc.evaluate('d:prop/*', propStatNode, resolver, XPathResult.ANY_TYPE, null);
- var propNode = propIterator.iterateNext();
- while(propNode) {
- var content = this._parsePropNode(propNode);
- propStat.properties['{' + propNode.namespaceURI + '}' + propNode.localName] = content;
- propNode = propIterator.iterateNext();
- }
- response.propStat.push(propStat);
- propStatNode = propStatIterator.iterateNext();
- }
- result.push(response);
- responseNode = responseIterator.iterateNext();
- }
- return result;
- },
- /**
- * Takes a relative url, and maps it to an absolute url, using the baseUrl
- *
- * @param {string} url
- * @return {string}
- */
- resolveUrl : function(url) {
- // Note: this is rudamentary.. not sure yet if it handles every case.
- if (/^https?:\/\//i.test(url)) {
- // absolute
- return url;
- }
- var baseParts = this.parseUrl(this.baseUrl);
- if (url.charAt('/')) {
- // Url starts with a slash
- return baseParts.root + url;
- }
- // Url does not start with a slash, we need grab the base url right up until the last slash.
- var newUrl = baseParts.root + '/';
- if (baseParts.path.lastIndexOf('/')!==-1) {
- newUrl = newUrl = baseParts.path.subString(0, baseParts.path.lastIndexOf('/')) + '/';
- }
- newUrl+=url;
- return url;
- },
- /**
- * Parses a url and returns its individual components.
- *
- * @param {String} url
- * @return {Object}
- */
- parseUrl : function(url) {
- var parts = url.match(/^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/);
- var result = {
- url : parts[0],
- scheme : parts[1],
- host : parts[3],
- port : parts[4],
- path : parts[5],
- query : parts[6],
- fragment : parts[7],
- };
- result.root =
- result.scheme + '://' +
- result.host +
- (result.port ? ':' + result.port : '');
- return result;
- },
- parseClarkNotation : function(propertyName) {
- var result = propertyName.match(/^{([^}]+)}(.*)$/);
- if (!result) {
- return;
- }
- return {
- name : result[2],
- namespace : result[1]
- };
- }
- };
|