/** * davclient.js - XHTTPRequest Low-level WebDAV Client API. * * WebDAV Ajax client api by XhrIo and XhrManager of google closure library. * (HTTP: GET, PUT, HEAD, OPTIONS, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK) * * @license Copyright 2011 The xhrdavclient library authors. * All rights reserved. */ goog.provide('xhrdav.Client'); goog.require('goog.Uri'); goog.require('goog.dom'); goog.require('goog.net.XhrIo'); goog.require('xhrdav.Conf'); /** * WebDAV Client library by Google Closure library. * * @constructor * @param {{scheme:string=, domain:stirng=, port:nubmer=, auth:string=}=} opt_uri * URI Parameters(opt_uri: scheme, domain, port) * @see #initialize_ */ xhrdav.Client = function(opt_uri) { this.initialize_(opt_uri); }; /** * WebDAV Client initialize * * @private * @param {{scheme:string=, domain:stirng=, port:nubmer=, auth:string=}=} opt_uri * URI Parameters(opt_uri: scheme, domain, port) */ xhrdav.Client.prototype.initialize_ = function(opt_uri) { if (!goog.isDefAndNotNull(opt_uri)) { opt_uri = {}; } var locationUrl = goog.Uri.parse(location); /** @type {string} */ this.scheme_ = opt_uri.scheme || locationUrl.getScheme() || 'http'; /** @type {string} */ this.domain_ = opt_uri.domain || locationUrl.getDomain(); /** @type {number} */ this.port_ = opt_uri.port || locationUrl.getPort(); /** @type {?string} */ this.auth_ = opt_uri.auth; }; /** * Has Authorization credentials * * @return {boolean} stored auth data(Basic/Digest auth hash string). */ xhrdav.Client.prototype.hasAuthCredentials = function() { return !!this.auth_; }; /** * Get Authorization credentials * * @return {string} stored auth data. */ xhrdav.Client.prototype.getAuthCredentials = function() { return this.auth_; }; /** * Set Authorization credentials * * @param {string} auth set auth data. */ xhrdav.Client.prototype.setAuthCredentials = function(auth) { this.auth_ = auth; }; /** * Clear Authorization credentials */ xhrdav.Client.prototype.clearAuthCredentials = function() { this.auth_ = null; }; /** * Parse Response All Headers * * @private * @param {string} headerStrings Response all header strings. * @return {Object} converted header object(associated array). */ xhrdav.Client.prototype.parseHeaders_ = function(headerStrings) { var headers = {}; var headerListWithoutEmpty = goog.array.filter( headerStrings.split(/\n/), function(v, i) { return !(goog.string.isEmptySafe(v)); } ); goog.array.forEach(headerListWithoutEmpty, function(v, i) { var chunks = v.split(': '); var key = goog.string.trim(chunks.shift()); var obj = goog.string.trim(chunks.join(': ')); headers[key] = obj; }); return headers; }; /** * Have xml parse function? <goog.mixin(this, parseFunc)> * * @return {boolean} true: can parse xml, false: can't parse xml. */ xhrdav.Client.prototype.canParseXml = function() { return (goog.isDef(this.parseXml)) ? true : false; }; /** * Set xml parser function Object. * * @param {Object} funcObj * Xml Parse function Object(defined function: parseXml). */ xhrdav.Client.prototype.setXmlParseFunction = function(funcObj) { goog.mixin(this, funcObj); }; /** * Callback XHTTPRequest Processing * * @private * @param {Function} handler Callback chain function. * @param {Function} onXhrComplete onXhrComplete callback function. * @param {Object} event XHR Event Object. */ // TODO: Testcase of HTTP Request Error xhrdav.Client.prototype.processRequest_ = function( handler, onXhrComplete, event) { if (onXhrComplete && goog.isFunction(onXhrComplete)) onXhrComplete(event); var xhr = event.target; var xssGuard = 'while(1);'; var statusCode = xhr.getStatus() || 0; var headers = {}; var content = xhr.getResponse(xssGuard); if (!xhr.isSuccess() && xhr.getStatus() != xhrdav.HttpStatus.MULTI_STATUS) { xhrdav.Conf.logging({'name': 'Client#processRequest_', 'uri': xhr.getLastUri(), 'errStatus': xhr.getLastErrorCode(), 'errStatusText': goog.net.ErrorCode.getDebugMessage(xhr.getLastErrorCode()), 'errMessage': xhr.getLastError()}, 'warning'); } else { headers = this.parseHeaders_(xhr.getAllResponseHeaders()); if (goog.string.contains(headers['Content-Type'], 'xml')) { var xml = xhr.getResponseXml(xssGuard); // HACK: Convert native Document in IE9. if (typeof Document !== 'undefined') { if (!(xml instanceof Document)) { // IE9. xml = goog.dom.xml.loadXml(content); } } if (this.canParseXml()) { xml = this.parseXml(xml); } if (!goog.isDefAndNotNull(xml)) { statusCode = 500; } content = xml; } } if (handler) handler((statusCode < 1) ? 500 : statusCode, content, headers); }; /** * Generate URL from path and location * * @private * @param {string} path Path(<code>/foo</code>, <code>/foo/bar.txt</code>). * @return {goog.Uri} Generated URL object. */ xhrdav.Client.prototype.generateUrl_ = function(path) { xhrdav.Conf.logging({'name': 'Client#generateUrl_', 'RequestURL': path}); // scheme, userinfo, domain, port, path, query, fragment return goog.Uri.create( this.scheme_, null, // username:password(Basic認証?) this.domain_, this.port_, path, null, // query(a=1&b2) null); }; /** * Set Query parameters for url. * * @private * @param {goog.net.Uri} url Uri object. * @param {Object=} query Json/Hash object for query. * @return {goog.net.Uri} created uri object. */ xhrdav.Client.prototype.setParameters_ = function(url, query) { if (goog.isDefAndNotNull(query) && !goog.object.isEmpty(query)) { goog.object.forEach(query, function(val, key) { if (goog.isArray(val) && !goog.array.isEmpty(val)) { url.setParameterValues( xhrdav.utils.string.camelize(key, {with_dasherize: true}), val); } else if (!goog.string.isEmptySafe(val)) { url.setParameterValue( xhrdav.utils.string.camelize(key, {with_dasherize: true}), val); } }); } return url; }; /** * convert headers keys. * * @private * @param {Object=} headers HTTP headers object. * @return {Object} converted HTTP headers object. */ xhrdav.Client.prototype.convertHeadersKeys_ = function(headers) { var converted = {}; if (goog.isDefAndNotNull(headers) && !goog.object.isEmpty(headers)) { goog.object.forEach(headers, function(val, key) { var convKey = xhrdav.utils.string.camelize(key, {with_dasherize: true}); goog.object.set(converted, convKey, val); }); } return converted; }; /** * Send XHTTPRequest * * @private * @param {string} method HTTP method(GET, POST, etc). * @param {string} url request url. * @param {Function} handler option function of processing after XHTTPRequest. * @param {{xhrMgr:goog.net.XhrManager, xhrId, headers:Object, query:Object}=} * opt_request Option params(xhrId, xhrManager, etc); * @param {Function=} onXhrComplete onXhrComplete callback function. */ xhrdav.Client.prototype.request_ = function( method, url, handler, opt_request, onXhrComplete) { if (!goog.isDefAndNotNull(opt_request)) opt_request = {}; if (!goog.isDefAndNotNull(opt_request.headers)) opt_request.headers = {}; var auth = goog.object.get(opt_request.headers, 'Authorization'); if (!goog.isDefAndNotNull(auth)) { goog.object.set(opt_request.headers, 'Authorization', this.auth_); } if (goog.isDefAndNotNull(opt_request.xhrMgr)) { opt_request.xhrMgr.send( opt_request.xhrId || goog.string.createUniqueString(), url, method, opt_request.body, opt_request.headers, opt_request.priority || 0, goog.bind(this.processRequest_, this, handler, onXhrComplete), opt_request.maxRetries || 1); } else { goog.net.XhrIo.send( url, goog.bind(this.processRequest_, this, handler, onXhrComplete), method, opt_request.body, opt_request.headers); } }; /** * Find out which HTTP methods are understood by the server.(WebDAV: OPTIONS) * * @param {string} path Path(<code>/foo/</code>, <code>/foo/bar.txt</code>). * @param {Function} handler Callback chain after request processing. * @param {{xhrMgr:goog.net.XhrManager, xhrId, headers:Object, query:Object}=} * opt_request Option params(xhrId, xhrManager, etc); * @param {Function=} onXhrComplete onXhrComplete callback function. */ xhrdav.Client.prototype.options = function( path, handler, opt_request, onXhrComplete) { if (!goog.isDefAndNotNull(opt_request)) opt_request = {}; var url = this.generateUrl_(goog.string.urlDecode(path || '')); this.setParameters_(url, opt_request.query); opt_request.headers = this.convertHeadersKeys_(opt_request.headers || {}); goog.object.extend(opt_request.headers, {'Cache-Control': 'no-cache'}); this.request_('OPTIONS', url, handler, opt_request, onXhrComplete); }; /** * Check Resource(WebDAV: HEAD) * * @param {string} path Path(<code>/foo/</code>, <code>/foo/bar.txt</code>). * @param {Function} handler Callback chain after request processing. * @param {{xhrMgr:goog.net.XhrManager, xhrId, headers:Object, query:Object}=} * opt_request Option params(xhrId, xhrManager, etc); * @param {Function=} onXhrComplete onXhrComplete callback function. */ xhrdav.Client.prototype.head = function( path, handler, opt_request, onXhrComplete) { if (!goog.isDefAndNotNull(opt_request)) opt_request = {}; var url = this.generateUrl_(goog.string.urlDecode(path || '')); this.setParameters_(url, opt_request.query); opt_request.headers = this.convertHeadersKeys_(opt_request.headers || {}); goog.object.extend(opt_request.headers, {'Cache-Control': 'no-cache'}); this.request_('HEAD', url, handler, opt_request, onXhrComplete); }; /** * Get Resource(WebDAV: GET) * * @param {string} path * Path(<code>/foo/bar.xml</code>, <code>/foo/bar.txt</code>). * @param {Function} handler Callback chain after request processing. * @param {{xhrMgr:goog.net.XhrManager, xhrId, headers:Object, query:Object}=} * opt_request Option params(xhrId, xhrManager, etc); * @param {Function=} onXhrComplete onXhrComplete callback function. */ xhrdav.Client.prototype.get = function( path, handler, opt_request, onXhrComplete) { if (!goog.isDefAndNotNull(opt_request)) opt_request = {}; var url = this.generateUrl_(goog.string.urlDecode(path || '')); this.setParameters_(url, opt_request.query); opt_request.headers = this.convertHeadersKeys_(opt_request.headers || {}); this.request_('GET', url, handler, opt_request, onXhrComplete); }; /** * Upload Resource(WebDAV: PUT) * * @param {string} path * Path(<code>/foo/bar.xml</code>, <code>/foo/bar.txt</code>). * @param {Object} data Upload filedata(text OR binary). * @param {Function} handler Callback chain after request processing. * @param {{xhrMgr:goog.net.XhrManager, xhrId, headers:Object, query:Object}=} * opt_request Option params(xhrId, xhrManager, etc); * @param {Function=} onXhrComplete onXhrComplete callback function. */ xhrdav.Client.prototype.put = function( path, data, handler, opt_request, onXhrComplete) { if (!goog.isDefAndNotNull(opt_request)) opt_request = {}; var decodedPath = goog.string.urlDecode( xhrdav.utils.path.removeLastSlash(path || '')); var url = this.generateUrl_(decodedPath); this.setParameters_(url, opt_request.query); opt_request.headers = this.convertHeadersKeys_(opt_request.headers || {}); goog.object.extend(opt_request.headers, {'Cache-Control': 'no-cache'}); opt_request.body = data; this.request_('PUT', url, handler, opt_request, onXhrComplete); }; /** * Get Collection list and Resource property(WebDAV: PROPFIND) * * @param {string} path Path(<code>/foo/</code>, <code>/foo/bar.txt</code>). * @param {Function} handler Callback chain after request processing. * @param {{xhrMgr:goog.net.XhrManager, xhrId, headers:Object, query:Object}=} * opt_request Option params(xhrId, xhrManager, etc); * @param {Function=} onXhrComplete onXhrComplete callback function. */ xhrdav.Client.prototype.propfind = function( path, handler, opt_request, onXhrComplete) { if (!goog.isDefAndNotNull(opt_request)) opt_request = {}; var url = this.generateUrl_(goog.string.urlDecode(path || '')); this.setParameters_(url, opt_request.query); opt_request.headers = this.convertHeadersKeys_(opt_request.headers || {}); // 0(path only) or 1(current directory) goog.object.extend(opt_request.headers, { 'Content-Type': 'text/xml', 'Depth': goog.isDefAndNotNull(opt_request.headers['Depth']) ? opt_request.headers['Depth'] : 0}); goog.object.extend(opt_request, {body: '<?xml version="1.0" encoding="UTF-8"?>' + '<D:propfind xmlns:D="DAV:"><D:allprop /></D:propfind>'}); this.request_('PROPFIND', url, handler, opt_request, onXhrComplete); }; /** * Set Collection list and Resource property(WebDAV: PROPPATCH) * * @param {string} path Path(<code>/foo/</code>, <code>/foo/bar.txt</code>). * @param {Function} handler Callback chain after request processing. * @param {{xhrMgr:goog.net.XhrManager, xhrId, headers:Object, query:Object}=} * opt_request Option params(xhrId, xhrManager, etc); * @param {Function=} onXhrComplete onXhrComplete callback function. */ // TODO: UNFIXED xhrdav.Client.prototype.proppatch = function( path, handler, opt_request, onXhrComplete) { if (!goog.isDefAndNotNull(opt_request)) opt_request = {}; var url = this.generateUrl_(goog.string.urlDecode(path || '')); this.setParameters_(url, opt_request.query); opt_request.headers = this.convertHeadersKeys_(opt_request.headers || {}); }; /** * Lock resource(WebDAV: LOCK) * * @param {string} path Path(<code>/foo/</code>, <code>/foo/bar.txt</code>). * @param {Function} handler Callback chain after request processing. * @param {{xhrMgr:goog.net.XhrManager, xhrId, headers:Object, query:Object}=} * opt_request Option params(xhrId, xhrManager, etc); * @param {Function=} onXhrComplete onXhrComplete callback function. */ // TODO: UNFIXED xhrdav.Client.prototype.lock = function( path, handler, opt_request, onXhrComplete) { if (!goog.isDefAndNotNull(opt_request)) opt_request = {}; var url = this.generateUrl_(goog.string.urlDecode(path || '')); this.setParameters_(url, opt_request.query); opt_request.headers = this.convertHeadersKeys_(opt_request.headers || {}); goog.object.extend(opt_request.headers, { 'Content-Type': 'text/xml', 'Depth': goog.isDefAndNotNull(opt_request.headers['Depth']) ? opt_request.headers['Depth'] : 0}); goog.object.extend(opt_request, {body: '<?xml version="1.0" encoding="UTF-8"?>' + '<D:lockinfo xmlns:D="DAV:">\n' + '<D:lockscope><D:' + (opt_request.scope || 'exclusive') + ' /></D:lockscope>\n' + '<D:locktype><D:' + (opt_request.type || 'write') + ' /></D:locktype>\n' + '<D:owner></D:owner>\n</D:lockinfo>\n'}); this.request_('LOCK', url, handler, opt_request, onXhrComplete); }; /** * Create Collection(WebDAV: MKCOL) * * @param {string} path Path(<code>/foo/</code>, <code>/foo/bar/</code>). * @param {Function} handler Callback chain after request processing. * @param {{xhrMgr:goog.net.XhrManager, xhrId, headers:Object, query:Object}=} * opt_request Option params(xhrId, xhrManager, etc); * @param {Function=} onXhrComplete onXhrComplete callback function. */ xhrdav.Client.prototype.mkcol = function( path, handler, opt_request, onXhrComplete) { if (!goog.isDefAndNotNull(opt_request)) opt_request = {}; path = goog.string.endsWith(path, '/') ? path : path + '/'; // Preserve GET var url = this.generateUrl_(goog.string.urlDecode(path || '')); this.setParameters_(url, opt_request.query); opt_request.headers = this.convertHeadersKeys_(opt_request.headers || {}); this.request_('MKCOL', url, handler, opt_request, onXhrComplete); }; /** * Delete Collection or Resource(WebDAV: DELETE) * * @param {string} path Path(<code>/foo/</code>, <code>/foo/bar.txt</code>). * @param {Function} handler Callback chain after request processing. * @param {{xhrMgr:goog.net.XhrManager, xhrId, headers:Object, query:Object}=} * opt_request Option params(xhrId, xhrManager, etc); * @param {Function=} onXhrComplete onXhrComplete callback function. */ xhrdav.Client.prototype._delete = function( path, handler, opt_request, onXhrComplete) { if (!goog.isDefAndNotNull(opt_request)) opt_request = {}; var url = this.generateUrl_(goog.string.urlDecode(path || '')); this.setParameters_(url, opt_request.query); opt_request.headers = this.convertHeadersKeys_(opt_request.headers || {}); this.request_('DELETE', url, handler, opt_request, onXhrComplete); }; /** * Private method of Copy or Move Collection(WebDAV: COPY/MOVE) * * @private * @param {string} method HTTP method of WebDAV. * @param {string} path Source Path(<code>/foo/</code>). * @param {string} dstPath Destination Path(<code>/bar/</code>). * @param {Function} handler Callback chain after request processing. * @param {{xhrMgr:goog.net.XhrManager, xhrId, headers:Object, query:Object}=} * opt_request Option params(xhrId, xhrManager, etc); * @param {Function=} onXhrComplete onXhrComplete callback function. */ xhrdav.Client.prototype.copyOrMovePath_ = function( method, path, dstPath, handler, opt_request, onXhrComplete) { if (!goog.isDefAndNotNull(opt_request)) opt_request = {}; var url = this.generateUrl_(goog.string.urlDecode(path || '')); this.setParameters_(url, opt_request.query); opt_request.headers = this.convertHeadersKeys_(opt_request.headers || {}); goog.object.extend(opt_request.headers, { 'Cache-Control': 'no-cache', 'Destination': this.generateUrl_(goog.string.urlDecode(dstPath || '')), 'Overwrite': !!opt_request.headers['Overwrite'] ? 'T' : 'F'}); this.request_(method, url, handler, opt_request, onXhrComplete); }; /** * Move Collection(WebDAV: MOVE) * * @see #copyOrMovePath_ */ xhrdav.Client.prototype.move = function( path, dstPath, handler, opt_request, onXhrComplete) { this.copyOrMovePath_('MOVE', path, dstPath, handler, opt_request, onXhrComplete); }; /** * Copy Collection(WebDAV: COPY) * * @see #copyOrMovePath_ */ xhrdav.Client.prototype.copy = function( path, dstPath, handler, opt_request, onXhrComplete) { this.copyOrMovePath_('COPY', path, dstPath, handler, opt_request, onXhrComplete); }; /** * Error Handler of exception[Mix-in function] * * @param {Error} e Error object. */ xhrdav.Client.prototype.errorHandler = function(e) { xhrdav.Conf.logging({'Client#errorHandler': e.name, 'message': e.message}, 'warning'); xhrdav.Conf.getInstance().getLogger().warning(e.name, e); }; /* Entry Point for closure compiler */ goog.exportSymbol('xhrdav.Client', xhrdav.Client); goog.exportProperty(xhrdav.Client.prototype, 'hasAuthCredentials', xhrdav.Client.prototype.hasAuthCredentials); goog.exportProperty(xhrdav.Client.prototype, 'getAuthCredentials', xhrdav.Client.prototype.getAuthCredentials); goog.exportProperty(xhrdav.Client.prototype, 'setAuthCredentials', xhrdav.Client.prototype.setAuthCredentials); goog.exportProperty(xhrdav.Client.prototype, 'clearAuthCredentials', xhrdav.Client.prototype.clearAuthCredentials); goog.exportProperty(xhrdav.Client.prototype, 'canParseXml', xhrdav.Client.prototype.canParseXml); goog.exportProperty(xhrdav.Client.prototype, 'setXmlParseFunction', xhrdav.Client.prototype.setXmlParseFunction); goog.exportProperty(xhrdav.Client.prototype, 'options', xhrdav.Client.prototype.options); goog.exportProperty(xhrdav.Client.prototype, 'head', xhrdav.Client.prototype.head); goog.exportProperty(xhrdav.Client.prototype, 'get', xhrdav.Client.prototype.get); goog.exportProperty(xhrdav.Client.prototype, 'put', xhrdav.Client.prototype.put); goog.exportProperty(xhrdav.Client.prototype, 'propfind', xhrdav.Client.prototype.propfind); goog.exportProperty(xhrdav.Client.prototype, 'mkcol', xhrdav.Client.prototype.mkcol); goog.exportProperty(xhrdav.Client.prototype, '_delete', xhrdav.Client.prototype._delete); goog.exportProperty(xhrdav.Client.prototype, 'move', xhrdav.Client.prototype.move); goog.exportProperty(xhrdav.Client.prototype, 'copy', xhrdav.Client.prototype.copy); goog.exportProperty(xhrdav.Client.prototype, 'errorHandler', xhrdav.Client.prototype.errorHandler);