Browse Source
Added OC.Files.Client Webdav-based files client
remotes/origin/fix-delete-homeidr-on-userdelete
Added OC.Files.Client Webdav-based files client
remotes/origin/fix-delete-homeidr-on-userdelete
committed by
Lukas Reschke
7 changed files with 1533 additions and 5 deletions
-
2buildjsdocs.sh
-
673core/js/files/client.js
-
143core/js/files/fileinfo.js
-
1core/js/js.js
-
1core/js/tests/specHelper.js
-
712core/js/tests/specs/files/clientSpec.js
-
6tests/karma.config.js
@ -0,0 +1,673 @@ |
|||||
|
/* |
||||
|
* Copyright (c) 2015 |
||||
|
* |
||||
|
* This file is licensed under the Affero General Public License version 3 |
||||
|
* or later. |
||||
|
* |
||||
|
* See the COPYING-README file. |
||||
|
* |
||||
|
*/ |
||||
|
|
||||
|
/* global dav */ |
||||
|
|
||||
|
(function(OC, FileInfo) { |
||||
|
/** |
||||
|
* @class OC.Files.Client |
||||
|
* @classdesc Client to access files on the server |
||||
|
* |
||||
|
* @param {Object} options |
||||
|
* @param {String} options.host host name |
||||
|
* @param {int} [options.port] port |
||||
|
* @param {boolean} [options.useHTTPS] whether to use https |
||||
|
* @param {String} [options.root] root path |
||||
|
* @param {String} [options.userName] user name |
||||
|
* @param {String} [options.password] password |
||||
|
* |
||||
|
* @since 8.2 |
||||
|
*/ |
||||
|
var Client = function(options) { |
||||
|
this._root = options.root; |
||||
|
if (this._root.charAt(this._root.length - 1) === '/') { |
||||
|
this._root = this._root.substr(0, this._root.length - 1); |
||||
|
} |
||||
|
|
||||
|
if (!options.port) { |
||||
|
// workaround in case port is null or empty
|
||||
|
options.port = undefined; |
||||
|
} |
||||
|
var url = ''; |
||||
|
var port = ''; |
||||
|
if (options.useHTTPS) { |
||||
|
url += 'https://'; |
||||
|
if (options.port && options.port !== 443) { |
||||
|
port = ':' + options.port; |
||||
|
} |
||||
|
} else { |
||||
|
url += 'http://'; |
||||
|
if (options.port && options.port !== 80) { |
||||
|
port = ':' + options.port; |
||||
|
} |
||||
|
} |
||||
|
var credentials = ''; |
||||
|
if (options.userName) { |
||||
|
credentials += encodeURIComponent(options.userName); |
||||
|
} |
||||
|
if (options.password) { |
||||
|
credentials += ':' + encodeURIComponent(options.password); |
||||
|
} |
||||
|
if (credentials.length > 0) { |
||||
|
url += credentials + '@'; |
||||
|
} |
||||
|
|
||||
|
url += options.host + port + this._root; |
||||
|
this._defaultHeaders = options.defaultHeaders || {'X-Requested-With': 'XMLHttpRequest'}; |
||||
|
this._baseUrl = url; |
||||
|
this._client = new dav.Client({ |
||||
|
baseUrl: this._baseUrl, |
||||
|
xmlNamespaces: { |
||||
|
'DAV:': 'd', |
||||
|
'http://owncloud.org/ns': 'oc' |
||||
|
} |
||||
|
}); |
||||
|
this._client.xhrProvider = _.bind(this._xhrProvider, this); |
||||
|
}; |
||||
|
|
||||
|
Client.NS_OWNCLOUD = 'http://owncloud.org/ns'; |
||||
|
Client.NS_DAV = 'DAV:'; |
||||
|
Client._PROPFIND_PROPERTIES = [ |
||||
|
/** |
||||
|
* Modified time |
||||
|
*/ |
||||
|
[Client.NS_DAV, 'getlastmodified'], |
||||
|
/** |
||||
|
* Etag |
||||
|
*/ |
||||
|
[Client.NS_DAV, 'getetag'], |
||||
|
/** |
||||
|
* Mime type |
||||
|
*/ |
||||
|
[Client.NS_DAV, 'getcontenttype'], |
||||
|
/** |
||||
|
* Resource type "collection" for folders, empty otherwise |
||||
|
*/ |
||||
|
[Client.NS_DAV, 'resourcetype'], |
||||
|
/** |
||||
|
* Compound file id, contains fileid + server instance id |
||||
|
*/ |
||||
|
[Client.NS_OWNCLOUD, 'id'], |
||||
|
/** |
||||
|
* Letter-coded permissions |
||||
|
*/ |
||||
|
[Client.NS_OWNCLOUD, 'permissions'], |
||||
|
//[Client.NS_OWNCLOUD, 'downloadURL'],
|
||||
|
/** |
||||
|
* Folder sizes |
||||
|
*/ |
||||
|
[Client.NS_OWNCLOUD, 'size'], |
||||
|
/** |
||||
|
* File sizes |
||||
|
*/ |
||||
|
[Client.NS_DAV, 'getcontentlength'] |
||||
|
]; |
||||
|
|
||||
|
/** |
||||
|
* @memberof OC.Files |
||||
|
*/ |
||||
|
Client.prototype = { |
||||
|
|
||||
|
/** |
||||
|
* Root path of the Webdav endpoint |
||||
|
* |
||||
|
* @type string |
||||
|
*/ |
||||
|
_root: null, |
||||
|
|
||||
|
/** |
||||
|
* Client from the library |
||||
|
* |
||||
|
* @type nl.sara.webdav.Client |
||||
|
*/ |
||||
|
_client: null, |
||||
|
|
||||
|
/** |
||||
|
* Returns the configured XHR provider for davclient |
||||
|
* @return {XMLHttpRequest} |
||||
|
*/ |
||||
|
_xhrProvider: function() { |
||||
|
var headers = this._defaultHeaders; |
||||
|
var xhr = new XMLHttpRequest(); |
||||
|
var oldOpen = xhr.open; |
||||
|
// override open() method to add headers
|
||||
|
xhr.open = function() { |
||||
|
var result = oldOpen.apply(this, arguments); |
||||
|
_.each(headers, function(value, key) { |
||||
|
xhr.setRequestHeader(key, value); |
||||
|
}); |
||||
|
return result; |
||||
|
}; |
||||
|
return xhr; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Prepends the base url to the given path sections |
||||
|
* |
||||
|
* @param {...String} path sections |
||||
|
* |
||||
|
* @return {String} base url + joined path, any leading or trailing slash |
||||
|
* will be kept |
||||
|
*/ |
||||
|
_buildUrl: function() { |
||||
|
var path = this._buildPath.apply(this, arguments); |
||||
|
if (path.charAt([path.length - 1]) === '/') { |
||||
|
path = path.substr(0, path.length - 1); |
||||
|
} |
||||
|
if (path.charAt(0) === '/') { |
||||
|
path = path.substr(1); |
||||
|
} |
||||
|
return this._baseUrl + '/' + path; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Append the path to the root and also encode path |
||||
|
* sections |
||||
|
* |
||||
|
* @param {...String} path sections |
||||
|
* |
||||
|
* @return {String} joined path, any leading or trailing slash |
||||
|
* will be kept |
||||
|
*/ |
||||
|
_buildPath: function() { |
||||
|
var path = OC.joinPaths.apply(this, arguments); |
||||
|
var sections = path.split('/'); |
||||
|
var i; |
||||
|
for (i = 0; i < sections.length; i++) { |
||||
|
sections[i] = encodeURIComponent(sections[i]); |
||||
|
} |
||||
|
path = sections.join('/'); |
||||
|
return path; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Parse headers string into a map |
||||
|
* |
||||
|
* @param {string} headersString headers list as string |
||||
|
* |
||||
|
* @return {Object.<String,Array>} map of header name to header contents |
||||
|
*/ |
||||
|
_parseHeaders: function(headersString) { |
||||
|
var headerRows = headersString.split('\n'); |
||||
|
var headers = {}; |
||||
|
for (var i = 0; i < headerRows.length; i++) { |
||||
|
var sepPos = headerRows[i].indexOf(':'); |
||||
|
if (sepPos < 0) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
var headerName = headerRows[i].substr(0, sepPos); |
||||
|
var headerValue = headerRows[i].substr(sepPos + 2); |
||||
|
|
||||
|
if (!headers[headerName]) { |
||||
|
// make it an array
|
||||
|
headers[headerName] = []; |
||||
|
} |
||||
|
|
||||
|
headers[headerName].push(headerValue); |
||||
|
} |
||||
|
return headers; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Parses the compound file id |
||||
|
* |
||||
|
* @param {string} compoundFileId compound file id as returned by the server |
||||
|
* |
||||
|
* @return {int} local file id, stripped of the instance id |
||||
|
*/ |
||||
|
_parseFileId: function(compoundFileId) { |
||||
|
if (!compoundFileId || compoundFileId.length < 8) { |
||||
|
return null; |
||||
|
} |
||||
|
return parseInt(compoundFileId.substr(0, 8), 10); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Parses the etag response which is in double quotes. |
||||
|
* |
||||
|
* @param {string} etag etag value in double quotes |
||||
|
* |
||||
|
* @return {string} etag without double quotes |
||||
|
*/ |
||||
|
_parseEtag: function(etag) { |
||||
|
if (etag.charAt(0) === '"') { |
||||
|
return etag.split('"')[1]; |
||||
|
} |
||||
|
return etag; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Parse Webdav result |
||||
|
* |
||||
|
* @param {Object} response XML object |
||||
|
* |
||||
|
* @return {Array.<FileInfo>} array of file info |
||||
|
*/ |
||||
|
_parseFileInfo: function(response) { |
||||
|
var path = response.href; |
||||
|
if (path.substr(0, this._root.length) === this._root) { |
||||
|
path = path.substr(this._root.length); |
||||
|
} |
||||
|
|
||||
|
if (path.charAt(path.length - 1) === '/') { |
||||
|
path = path.substr(0, path.length - 1); |
||||
|
} |
||||
|
|
||||
|
path = '/' + decodeURIComponent(path); |
||||
|
|
||||
|
if (response.propStat.length === 1 && response.propStat[0].status !== 200) { |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var props = response.propStat[0].properties; |
||||
|
|
||||
|
var data = { |
||||
|
id: this._parseFileId(props['{' + Client.NS_OWNCLOUD + '}id']), |
||||
|
path: OC.dirname(path) || '/', |
||||
|
name: OC.basename(path), |
||||
|
mtime: new Date(props['{' + Client.NS_DAV + '}getlastmodified']), |
||||
|
_props: props |
||||
|
}; |
||||
|
|
||||
|
var etagProp = props['{' + Client.NS_DAV + '}getetag']; |
||||
|
if (!_.isUndefined(etagProp)) { |
||||
|
data.etag = this._parseEtag(etagProp); |
||||
|
} |
||||
|
|
||||
|
var sizeProp = props['{' + Client.NS_DAV + '}getcontentlength']; |
||||
|
if (!_.isUndefined(sizeProp)) { |
||||
|
data.size = parseInt(sizeProp, 10); |
||||
|
} |
||||
|
|
||||
|
sizeProp = props['{' + Client.NS_OWNCLOUD + '}size']; |
||||
|
if (!_.isUndefined(sizeProp)) { |
||||
|
data.size = parseInt(sizeProp, 10); |
||||
|
} |
||||
|
|
||||
|
var contentType = props['{' + Client.NS_DAV + '}getcontenttype']; |
||||
|
if (!_.isUndefined(contentType)) { |
||||
|
data.mimetype = contentType; |
||||
|
} |
||||
|
|
||||
|
var resType = props['{' + Client.NS_DAV + '}resourcetype']; |
||||
|
var isFile = true; |
||||
|
if (!data.mimetype && resType) { |
||||
|
var xmlvalue = resType[0]; |
||||
|
if (xmlvalue.namespaceURI === Client.NS_DAV && xmlvalue.nodeName.split(':')[1] === 'collection') { |
||||
|
data.mimetype = 'httpd/unix-directory'; |
||||
|
isFile = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
data.permissions = OC.PERMISSION_READ; |
||||
|
var permissionProp = props['{' + Client.NS_OWNCLOUD + '}permissions']; |
||||
|
if (!_.isUndefined(permissionProp)) { |
||||
|
var permString = permissionProp || ''; |
||||
|
data.mountType = null; |
||||
|
for (var i = 0; i < permString.length; i++) { |
||||
|
var c = permString.charAt(i); |
||||
|
switch (c) { |
||||
|
// FIXME: twisted permissions
|
||||
|
case 'C': |
||||
|
case 'K': |
||||
|
data.permissions |= OC.PERMISSION_CREATE; |
||||
|
if (!isFile) { |
||||
|
data.permissions |= OC.PERMISSION_UPDATE; |
||||
|
} |
||||
|
break; |
||||
|
case 'W': |
||||
|
if (isFile) { |
||||
|
// also add create permissions
|
||||
|
data.permissions |= OC.PERMISSION_CREATE; |
||||
|
} |
||||
|
data.permissions |= OC.PERMISSION_UPDATE; |
||||
|
break; |
||||
|
case 'D': |
||||
|
data.permissions |= OC.PERMISSION_DELETE; |
||||
|
break; |
||||
|
case 'R': |
||||
|
data.permissions |= OC.PERMISSION_SHARE; |
||||
|
break; |
||||
|
case 'M': |
||||
|
if (!data.mountType) { |
||||
|
// TODO: how to identify external-root ?
|
||||
|
data.mountType = 'external'; |
||||
|
} |
||||
|
break; |
||||
|
case 'S': |
||||
|
// TODO: how to identify shared-root ?
|
||||
|
data.mountType = 'shared'; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return new FileInfo(data); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Parse Webdav multistatus |
||||
|
* |
||||
|
* @param {Array} responses |
||||
|
*/ |
||||
|
_parseResult: function(responses) { |
||||
|
var self = this; |
||||
|
return _.map(responses, function(response) { |
||||
|
return self._parseFileInfo(response); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Returns whether the given status code means success |
||||
|
* |
||||
|
* @param {int} status status code |
||||
|
* |
||||
|
* @return true if status code is between 200 and 299 included |
||||
|
*/ |
||||
|
_isSuccessStatus: function(status) { |
||||
|
return status >= 200 && status <= 299; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Returns the default PROPFIND properties to use during a call. |
||||
|
* |
||||
|
* @return {Array.<Object>} array of properties |
||||
|
*/ |
||||
|
_getPropfindProperties: function() { |
||||
|
if (!this._propfindProperties) { |
||||
|
this._propfindProperties = _.map(Client._PROPFIND_PROPERTIES, function(propDef) { |
||||
|
return '{' + propDef[0] + '}' + propDef[1]; |
||||
|
}); |
||||
|
} |
||||
|
return this._propfindProperties; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Lists the contents of a directory |
||||
|
* |
||||
|
* @param {String} path path to retrieve |
||||
|
* @param {Object} [options] options |
||||
|
* @param {boolean} [options.includeParent=false] set to true to keep |
||||
|
* the parent folder in the result list |
||||
|
* |
||||
|
* @return {Promise} promise |
||||
|
*/ |
||||
|
getFolderContents: function(path, options) { |
||||
|
if (!path) { |
||||
|
path = ''; |
||||
|
} |
||||
|
var self = this; |
||||
|
var deferred = $.Deferred(); |
||||
|
var promise = deferred.promise(); |
||||
|
|
||||
|
// TODO: headers
|
||||
|
this._client.propFind( |
||||
|
this._buildUrl(path), |
||||
|
this._getPropfindProperties(), |
||||
|
1 |
||||
|
).then(function(result) { |
||||
|
if (self._isSuccessStatus(result.status)) { |
||||
|
var results = self._parseResult(result.body); |
||||
|
if (!options || !options.includeParent) { |
||||
|
// remove root dir, the first entry
|
||||
|
results.shift(); |
||||
|
} |
||||
|
deferred.resolve(result.status, results); |
||||
|
} else { |
||||
|
deferred.reject(result.status); |
||||
|
} |
||||
|
}); |
||||
|
return promise; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Returns the file info of a given path. |
||||
|
* |
||||
|
* @param {String} path path |
||||
|
* @param {Array} [properties] list of webdav properties to |
||||
|
* retrieve |
||||
|
* |
||||
|
* @return {Promise} promise |
||||
|
*/ |
||||
|
getFileInfo: function(path) { |
||||
|
if (!path) { |
||||
|
path = ''; |
||||
|
} |
||||
|
var self = this; |
||||
|
var deferred = $.Deferred(); |
||||
|
var promise = deferred.promise(); |
||||
|
|
||||
|
// TODO: headers
|
||||
|
this._client.propFind( |
||||
|
this._buildUrl(path), |
||||
|
this._getPropfindProperties(), |
||||
|
0 |
||||
|
).then( |
||||
|
function(result) { |
||||
|
if (self._isSuccessStatus(result.status)) { |
||||
|
deferred.resolve(result.status, self._parseResult([result.body])[0]); |
||||
|
} else { |
||||
|
deferred.reject(result.status); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
return promise; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Returns the contents of the given file. |
||||
|
* |
||||
|
* @param {String} path path to file |
||||
|
* |
||||
|
* @return {Promise} |
||||
|
*/ |
||||
|
getFileContents: function(path) { |
||||
|
if (!path) { |
||||
|
throw 'Missing argument "path"'; |
||||
|
} |
||||
|
var self = this; |
||||
|
var deferred = $.Deferred(); |
||||
|
var promise = deferred.promise(); |
||||
|
|
||||
|
this._client.request( |
||||
|
'GET', |
||||
|
this._buildUrl(path), |
||||
|
this._defaultHeaders |
||||
|
).then( |
||||
|
function(result) { |
||||
|
if (self._isSuccessStatus(result.status)) { |
||||
|
deferred.resolve(result.status, result.body); |
||||
|
} else { |
||||
|
deferred.reject(result.status); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
return promise; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Puts the given data into the given file. |
||||
|
* |
||||
|
* @param {String} path path to file |
||||
|
* @param {String} body file body |
||||
|
* @param {Object} [options] |
||||
|
* @param {String} [options.contentType='text/plain'] content type |
||||
|
* @param {bool} [options.overwrite=true] whether to overwrite an existing file |
||||
|
* |
||||
|
* @return {Promise} |
||||
|
*/ |
||||
|
putFileContents: function(path, body, options) { |
||||
|
if (!path) { |
||||
|
throw 'Missing argument "path"'; |
||||
|
} |
||||
|
var self = this; |
||||
|
var deferred = $.Deferred(); |
||||
|
var promise = deferred.promise(); |
||||
|
options = options || {}; |
||||
|
var headers = _.extend({}, this._defaultHeaders); |
||||
|
var contentType = 'text/plain'; |
||||
|
if (options.contentType) { |
||||
|
contentType = options.contentType; |
||||
|
} |
||||
|
|
||||
|
headers['Content-Type'] = contentType; |
||||
|
|
||||
|
if (_.isUndefined(options.overwrite) || options.overwrite) { |
||||
|
// will trigger 412 precondition failed if a file already exists
|
||||
|
headers['If-None-Match'] = '*'; |
||||
|
} |
||||
|
|
||||
|
this._client.request( |
||||
|
'PUT', |
||||
|
this._buildUrl(path), |
||||
|
headers, |
||||
|
body || '' |
||||
|
).then( |
||||
|
function(result) { |
||||
|
if (self._isSuccessStatus(result.status)) { |
||||
|
deferred.resolve(result.status); |
||||
|
} else { |
||||
|
deferred.reject(result.status); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
return promise; |
||||
|
}, |
||||
|
|
||||
|
_simpleCall: function(method, path) { |
||||
|
if (!path) { |
||||
|
throw 'Missing argument "path"'; |
||||
|
} |
||||
|
|
||||
|
var self = this; |
||||
|
var deferred = $.Deferred(); |
||||
|
var promise = deferred.promise(); |
||||
|
|
||||
|
this._client.request( |
||||
|
method, |
||||
|
this._buildUrl(path), |
||||
|
this._defaultHeaders |
||||
|
).then( |
||||
|
function(result) { |
||||
|
if (self._isSuccessStatus(result.status)) { |
||||
|
deferred.resolve(result.status); |
||||
|
} else { |
||||
|
deferred.reject(result.status); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
return promise; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Creates a directory |
||||
|
* |
||||
|
* @param {String} path path to create |
||||
|
* |
||||
|
* @return {Promise} |
||||
|
*/ |
||||
|
createDirectory: function(path) { |
||||
|
return this._simpleCall('MKCOL', path); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Deletes a file or directory |
||||
|
* |
||||
|
* @param {String} path path to delete |
||||
|
* |
||||
|
* @return {Promise} |
||||
|
*/ |
||||
|
remove: function(path) { |
||||
|
return this._simpleCall('DELETE', path); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Moves path to another path |
||||
|
* |
||||
|
* @param {String} path path to move |
||||
|
* @param {String} destinationPath destination path |
||||
|
* @param {boolean} [allowOverwrite=false] true to allow overwriting, |
||||
|
* false otherwise |
||||
|
* |
||||
|
* @return {Promise} promise |
||||
|
*/ |
||||
|
move: function(path, destinationPath, allowOverwrite) { |
||||
|
if (!path) { |
||||
|
throw 'Missing argument "path"'; |
||||
|
} |
||||
|
if (!destinationPath) { |
||||
|
throw 'Missing argument "destinationPath"'; |
||||
|
} |
||||
|
|
||||
|
var self = this; |
||||
|
var deferred = $.Deferred(); |
||||
|
var promise = deferred.promise(); |
||||
|
var headers = |
||||
|
_.extend({ |
||||
|
'Destination' : this._buildUrl(destinationPath) |
||||
|
}, this._defaultHeaders); |
||||
|
|
||||
|
if (!allowOverwrite) { |
||||
|
headers['Overwrite'] = 'F'; |
||||
|
} |
||||
|
|
||||
|
this._client.request( |
||||
|
'MOVE', |
||||
|
this._buildUrl(path), |
||||
|
headers |
||||
|
).then( |
||||
|
function(response) { |
||||
|
if (self._isSuccessStatus(response.status)) { |
||||
|
deferred.resolve(response.status); |
||||
|
} else { |
||||
|
deferred.reject(response.status); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
return promise; |
||||
|
} |
||||
|
|
||||
|
}; |
||||
|
|
||||
|
if (!OC.Files) { |
||||
|
/** |
||||
|
* @namespace OC.Files |
||||
|
* |
||||
|
* @since 8.2 |
||||
|
*/ |
||||
|
OC.Files = {}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns the default instance of the files client |
||||
|
* |
||||
|
* @return {OC.Files.Client} default client |
||||
|
* |
||||
|
* @since 8.2 |
||||
|
*/ |
||||
|
OC.Files.getClient = function() { |
||||
|
if (OC.Files._defaultClient) { |
||||
|
return OC.Files._defaultClient; |
||||
|
} |
||||
|
|
||||
|
var client = new OC.Files.Client({ |
||||
|
host: OC.getHost(), |
||||
|
port: OC.getPort(), |
||||
|
root: OC.linkToRemoteBase('webdav'), |
||||
|
useHTTPS: OC.getProtocol() === 'https' |
||||
|
}); |
||||
|
OC.Files._defaultClient = client; |
||||
|
return client; |
||||
|
}; |
||||
|
|
||||
|
OC.Files.Client = Client; |
||||
|
})(OC, OC.Files.FileInfo); |
||||
|
|
||||
@ -0,0 +1,143 @@ |
|||||
|
/* |
||||
|
* Copyright (c) 2015 |
||||
|
* |
||||
|
* This file is licensed under the Affero General Public License version 3 |
||||
|
* or later. |
||||
|
* |
||||
|
* See the COPYING-README file. |
||||
|
* |
||||
|
*/ |
||||
|
|
||||
|
(function(OC) { |
||||
|
|
||||
|
/** |
||||
|
* @class OC.Files.FileInfo |
||||
|
* @classdesc File information |
||||
|
* |
||||
|
* @param {Object} data file data, see attributes for details |
||||
|
* |
||||
|
* @since 8.2 |
||||
|
*/ |
||||
|
var FileInfo = function(data) { |
||||
|
if (!_.isUndefined(data.id)) { |
||||
|
this.id = parseInt(data.id, 10); |
||||
|
} |
||||
|
|
||||
|
// TODO: normalize path
|
||||
|
this.path = data.path || ''; |
||||
|
this.name = data.name; |
||||
|
|
||||
|
this.mtime = data.mtime; |
||||
|
this.etag = data.etag; |
||||
|
this.permissions = data.permissions; |
||||
|
this.size = data.size; |
||||
|
this.mimetype = data.mimetype || 'application/octet-stream'; |
||||
|
this.mountType = data.mountType; |
||||
|
this.icon = data.icon; |
||||
|
this._props = data._props; |
||||
|
|
||||
|
if (data.type) { |
||||
|
this.type = data.type; |
||||
|
} else if (this.mimetype === 'httpd/unix-directory') { |
||||
|
this.type = 'dir'; |
||||
|
} else { |
||||
|
this.type = 'file'; |
||||
|
} |
||||
|
|
||||
|
if (!this.mimetype) { |
||||
|
if (this.type === 'dir') { |
||||
|
this.mimetype = 'httpd/unix-directory'; |
||||
|
} else { |
||||
|
this.mimetype = 'application/octet-stream'; |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* @memberof OC.Files |
||||
|
*/ |
||||
|
FileInfo.prototype = { |
||||
|
/** |
||||
|
* File id |
||||
|
* |
||||
|
* @type int |
||||
|
*/ |
||||
|
id: null, |
||||
|
|
||||
|
/** |
||||
|
* File name |
||||
|
* |
||||
|
* @type String |
||||
|
*/ |
||||
|
name: null, |
||||
|
|
||||
|
/** |
||||
|
* Path leading to the file, without the file name, |
||||
|
* and with a leading slash. |
||||
|
* |
||||
|
* @type String |
||||
|
*/ |
||||
|
path: null, |
||||
|
|
||||
|
/** |
||||
|
* Mime type |
||||
|
* |
||||
|
* @type String |
||||
|
*/ |
||||
|
mimetype: null, |
||||
|
|
||||
|
/** |
||||
|
* Icon URL. |
||||
|
* |
||||
|
* Can be used to override the mime type icon. |
||||
|
* |
||||
|
* @type String |
||||
|
*/ |
||||
|
icon: null, |
||||
|
|
||||
|
/** |
||||
|
* File type. 'file' for files, 'dir' for directories. |
||||
|
* |
||||
|
* @type String |
||||
|
* @deprecated rely on mimetype instead |
||||
|
*/ |
||||
|
type: 'file', |
||||
|
|
||||
|
/** |
||||
|
* Permissions. |
||||
|
* |
||||
|
* @see OC#PERMISSION_ALL for permissions |
||||
|
* @type int |
||||
|
*/ |
||||
|
permissions: null, |
||||
|
|
||||
|
/** |
||||
|
* Modification time |
||||
|
* |
||||
|
* @type int |
||||
|
*/ |
||||
|
mtime: null, |
||||
|
|
||||
|
/** |
||||
|
* Etag |
||||
|
* |
||||
|
* @type String |
||||
|
*/ |
||||
|
etag: null, |
||||
|
|
||||
|
/** |
||||
|
* Mount type. |
||||
|
* |
||||
|
* One of null, "external-root", "shared" or "shared-root" |
||||
|
* |
||||
|
* @type string |
||||
|
*/ |
||||
|
mountType: null |
||||
|
}; |
||||
|
|
||||
|
if (!OC.Files) { |
||||
|
OC.Files = {}; |
||||
|
} |
||||
|
OC.Files.FileInfo = FileInfo; |
||||
|
})(OC); |
||||
|
|
||||
@ -0,0 +1,712 @@ |
|||||
|
/** |
||||
|
* ownCloud |
||||
|
* |
||||
|
* @author Vincent Petry |
||||
|
* @copyright 2015 Vincent Petry <pvince81@owncloud.com> |
||||
|
* |
||||
|
* This library is free software; you can redistribute it and/or |
||||
|
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
* License as published by the Free Software Foundation; either |
||||
|
* version 3 of the License, or any later version. |
||||
|
* |
||||
|
* This library is distributed in the hope that it will be useful, |
||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
* GNU AFFERO GENERAL PUBLIC LICENSE for more details. |
||||
|
* |
||||
|
* You should have received a copy of the GNU Affero General Public |
||||
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
* |
||||
|
*/ |
||||
|
|
||||
|
describe('OC.Files.Client tests', function() { |
||||
|
var Client = OC.Files.Client; |
||||
|
var baseUrl; |
||||
|
var client; |
||||
|
|
||||
|
beforeEach(function() { |
||||
|
baseUrl = 'https://testhost:999/owncloud/remote.php/webdav/'; |
||||
|
|
||||
|
client = new Client({ |
||||
|
host: 'testhost', |
||||
|
port: 999, |
||||
|
root: '/owncloud/remote.php/webdav', |
||||
|
useHTTPS: true |
||||
|
}); |
||||
|
}); |
||||
|
afterEach(function() { |
||||
|
client = null; |
||||
|
}); |
||||
|
|
||||
|
/** |
||||
|
* Send an status response and check that the given |
||||
|
* promise gets its success handler called with the error |
||||
|
* status code |
||||
|
* |
||||
|
* @param {Promise} promise promise |
||||
|
* @param {int} status status to test |
||||
|
*/ |
||||
|
function respondAndCheckStatus(promise, status) { |
||||
|
var successHandler = sinon.stub(); |
||||
|
var failHandler = sinon.stub(); |
||||
|
promise.done(successHandler); |
||||
|
promise.fail(failHandler); |
||||
|
|
||||
|
fakeServer.requests[0].respond( |
||||
|
status, |
||||
|
{'Content-Type': 'application/xml'}, |
||||
|
'' |
||||
|
); |
||||
|
|
||||
|
promise.then(function() { |
||||
|
expect(successHandler.calledOnce).toEqual(true); |
||||
|
expect(successHandler.getCall(0).args[0]).toEqual(status); |
||||
|
|
||||
|
expect(failHandler.notCalled).toEqual(true); |
||||
|
}); |
||||
|
|
||||
|
return promise; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Send an error response and check that the given |
||||
|
* promise gets its fail handler called with the error |
||||
|
* status code |
||||
|
* |
||||
|
* @param {Promise} promise promise object |
||||
|
* @param {int} status error status to test |
||||
|
*/ |
||||
|
function respondAndCheckError(promise, status) { |
||||
|
var successHandler = sinon.stub(); |
||||
|
var failHandler = sinon.stub(); |
||||
|
promise.done(successHandler); |
||||
|
promise.fail(failHandler); |
||||
|
|
||||
|
fakeServer.requests[0].respond( |
||||
|
status, |
||||
|
{'Content-Type': 'application/xml'}, |
||||
|
'' |
||||
|
); |
||||
|
|
||||
|
promise.then(function() { |
||||
|
expect(failHandler.calledOnce).toEqual(true); |
||||
|
expect(failHandler.calledWith(status)).toEqual(true); |
||||
|
|
||||
|
expect(successHandler.notCalled).toEqual(true); |
||||
|
|
||||
|
fulfill(); |
||||
|
}); |
||||
|
|
||||
|
return promise; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns a list of request properties parsed from the given request body. |
||||
|
* |
||||
|
* @param {string} requestBody request XML |
||||
|
* |
||||
|
* @return {Array.<String>} array of request properties in the format |
||||
|
* "{NS:}propname" |
||||
|
*/ |
||||
|
function getRequestedProperties(requestBody) { |
||||
|
var doc = (new window.DOMParser()).parseFromString( |
||||
|
requestBody, |
||||
|
'application/xml' |
||||
|
); |
||||
|
var propRoots = doc.getElementsByTagNameNS('DAV:', 'prop'); |
||||
|
var propsList = propRoots.item(0).childNodes; |
||||
|
return _.map(propsList, function(propNode) { |
||||
|
return '{' + propNode.namespaceURI + '}' + propNode.localName; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function makePropBlock(props) { |
||||
|
var s = '<d:prop>\n'; |
||||
|
|
||||
|
_.each(props, function(value, key) { |
||||
|
s += '<' + key + '>' + value + '</' + key + '>\n'; |
||||
|
}); |
||||
|
|
||||
|
return s + '</d:prop>\n'; |
||||
|
} |
||||
|
|
||||
|
function makeResponseBlock(href, props, failedProps) { |
||||
|
var s = '<d:response>\n'; |
||||
|
s += '<d:href>' + href + '</d:href>\n'; |
||||
|
s += '<d:propstat>\n'; |
||||
|
s += makePropBlock(props); |
||||
|
s += '<d:status>HTTP/1.1 200 OK</d:status>'; |
||||
|
s += '</d:propstat>\n'; |
||||
|
if (failedProps) { |
||||
|
s += '<d:propstat>\n'; |
||||
|
_.each(failedProps, function(prop) { |
||||
|
s += '<' + prop + '/>\n'; |
||||
|
}); |
||||
|
s += '<d:status>HTTP/1.1 404 Not Found</d:status>\n'; |
||||
|
s += '</d:propstat>\n'; |
||||
|
} |
||||
|
return s + '</d:response>\n'; |
||||
|
} |
||||
|
|
||||
|
describe('file listing', function() { |
||||
|
|
||||
|
var folderContentsXml = |
||||
|
'<?xml version="1.0" encoding="utf-8"?>' + |
||||
|
'<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' + |
||||
|
makeResponseBlock( |
||||
|
'/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/', |
||||
|
{ |
||||
|
'd:getlastmodified': 'Fri, 10 Jul 2015 10:00:05 GMT', |
||||
|
'd:getetag': '"56cfcabd79abb"', |
||||
|
'd:resourcetype': '<d:collection/>', |
||||
|
'oc:id': '00000011oc2d13a6a068', |
||||
|
'oc:permissions': 'RDNVCK', |
||||
|
'oc:size': 120 |
||||
|
}, |
||||
|
[ |
||||
|
'd:getcontenttype', |
||||
|
'd:getcontentlength' |
||||
|
] |
||||
|
) + |
||||
|
makeResponseBlock( |
||||
|
'/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt', |
||||
|
{ |
||||
|
'd:getlastmodified': 'Fri, 10 Jul 2015 13:38:05 GMT', |
||||
|
'd:getetag': '"559fcabd79a38"', |
||||
|
'd:getcontenttype': 'text/plain', |
||||
|
'd:getcontentlength': 250, |
||||
|
'd:resourcetype': '', |
||||
|
'oc:id': '00000051oc2d13a6a068', |
||||
|
'oc:permissions': 'RDNVW' |
||||
|
}, |
||||
|
[ |
||||
|
'oc:size', |
||||
|
] |
||||
|
) + |
||||
|
makeResponseBlock( |
||||
|
'/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/sub', |
||||
|
{ |
||||
|
'd:getlastmodified': 'Fri, 10 Jul 2015 14:00:00 GMT', |
||||
|
'd:getetag': '"66cfcabd79abb"', |
||||
|
'd:resourcetype': '<d:collection/>', |
||||
|
'oc:id': '00000015oc2d13a6a068', |
||||
|
'oc:permissions': 'RDNVCK', |
||||
|
'oc:size': 100 |
||||
|
}, |
||||
|
[ |
||||
|
'd:getcontenttype', |
||||
|
'd:getcontentlength' |
||||
|
] |
||||
|
) + |
||||
|
'</d:multistatus>'; |
||||
|
|
||||
|
it('sends PROPFIND with explicit properties to get file list', function() { |
||||
|
client.getFolderContents('path/to space/文件夹'); |
||||
|
|
||||
|
expect(fakeServer.requests.length).toEqual(1); |
||||
|
expect(fakeServer.requests[0].method).toEqual('PROPFIND'); |
||||
|
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9'); |
||||
|
expect(fakeServer.requests[0].requestHeaders.Depth).toEqual(1); |
||||
|
|
||||
|
var props = getRequestedProperties(fakeServer.requests[0].requestBody); |
||||
|
expect(props).toContain('{DAV:}getlastmodified'); |
||||
|
expect(props).toContain('{DAV:}getcontentlength'); |
||||
|
expect(props).toContain('{DAV:}getcontenttype'); |
||||
|
expect(props).toContain('{DAV:}getetag'); |
||||
|
expect(props).toContain('{DAV:}resourcetype'); |
||||
|
expect(props).toContain('{http://owncloud.org/ns}id'); |
||||
|
expect(props).toContain('{http://owncloud.org/ns}size'); |
||||
|
expect(props).toContain('{http://owncloud.org/ns}permissions'); |
||||
|
}); |
||||
|
it('sends PROPFIND to base url when empty path given', function() { |
||||
|
client.getFolderContents(''); |
||||
|
expect(fakeServer.requests.length).toEqual(1); |
||||
|
expect(fakeServer.requests[0].url).toEqual(baseUrl); |
||||
|
}); |
||||
|
it('sends PROPFIND to base url when root path given', function() { |
||||
|
client.getFolderContents('/'); |
||||
|
expect(fakeServer.requests.length).toEqual(1); |
||||
|
expect(fakeServer.requests[0].url).toEqual(baseUrl); |
||||
|
}); |
||||
|
it('parses the result list into a FileInfo array', function() { |
||||
|
var promise = client.getFolderContents('path/to space/文件夹'); |
||||
|
|
||||
|
expect(fakeServer.requests.length).toEqual(1); |
||||
|
|
||||
|
fakeServer.requests[0].respond( |
||||
|
207, |
||||
|
{'Content-Type': 'application/xml'}, |
||||
|
folderContentsXml |
||||
|
); |
||||
|
|
||||
|
promise.then(function(status, response) { |
||||
|
expect(status).toEqual(207); |
||||
|
expect(_.isArray(response)).toEqual(true); |
||||
|
|
||||
|
expect(response.length).toEqual(2); |
||||
|
|
||||
|
// file entry
|
||||
|
var info = response[0]; |
||||
|
expect(info instanceof OC.Files.FileInfo).toEqual(true); |
||||
|
expect(info.id).toEqual(51); |
||||
|
expect(info.path).toEqual('/path/to space/文件夹'); |
||||
|
expect(info.name).toEqual('One.txt'); |
||||
|
expect(info.permissions).toEqual(31); |
||||
|
expect(info.size).toEqual(250); |
||||
|
expect(info.mtime.getTime()).toEqual(1436535485000); |
||||
|
expect(info.mimetype).toEqual('text/plain'); |
||||
|
expect(info.etag).toEqual('559fcabd79a38'); |
||||
|
|
||||
|
// sub entry
|
||||
|
info = response[1]; |
||||
|
expect(info instanceof OC.Files.FileInfo).toEqual(true); |
||||
|
expect(info.id).toEqual(15); |
||||
|
expect(info.path).toEqual('/path/to space/文件夹'); |
||||
|
expect(info.name).toEqual('sub'); |
||||
|
expect(info.permissions).toEqual(31); |
||||
|
expect(info.size).toEqual(100); |
||||
|
expect(info.mtime.getTime()).toEqual(1436536800000); |
||||
|
expect(info.mimetype).toEqual('httpd/unix-directory'); |
||||
|
expect(info.etag).toEqual('66cfcabd79abb'); |
||||
|
}); |
||||
|
return promise.promise(); |
||||
|
}); |
||||
|
it('returns parent node in result if specified', function() { |
||||
|
var promise = client.getFolderContents('path/to space/文件夹', {includeParent: true}); |
||||
|
|
||||
|
expect(fakeServer.requests.length).toEqual(1); |
||||
|
|
||||
|
fakeServer.requests[0].respond( |
||||
|
207, |
||||
|
{'Content-Type': 'application/xml'}, |
||||
|
folderContentsXml |
||||
|
); |
||||
|
|
||||
|
promise.then(function(status, response) { |
||||
|
expect(status).toEqual(207); |
||||
|
expect(_.isArray(response)).toEqual(true); |
||||
|
|
||||
|
expect(response.length).toEqual(3); |
||||
|
|
||||
|
// root entry
|
||||
|
var info = response[0]; |
||||
|
expect(info instanceof OC.Files.FileInfo).toEqual(true); |
||||
|
expect(info.id).toEqual(11); |
||||
|
expect(info.path).toEqual('/path/to space'); |
||||
|
expect(info.name).toEqual('文件夹'); |
||||
|
expect(info.permissions).toEqual(31); |
||||
|
expect(info.size).toEqual(120); |
||||
|
expect(info.mtime.getTime()).toEqual(1436522405000); |
||||
|
expect(info.mimetype).toEqual('httpd/unix-directory'); |
||||
|
expect(info.etag).toEqual('56cfcabd79abb'); |
||||
|
|
||||
|
// the two other entries follow
|
||||
|
expect(response[1].id).toEqual(51); |
||||
|
expect(response[2].id).toEqual(15); |
||||
|
}); |
||||
|
|
||||
|
return promise; |
||||
|
}); |
||||
|
it('rejects promise when an error occurred', function() { |
||||
|
var promise = client.getFolderContents('path/to space/文件夹', {includeParent: true}); |
||||
|
return respondAndCheckError(promise, 404); |
||||
|
}); |
||||
|
it('throws exception if arguments are missing', function() { |
||||
|
// TODO
|
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('file info', function() { |
||||
|
var responseXml = |
||||
|
'<?xml version="1.0" encoding="utf-8"?>' + |
||||
|
'<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' + |
||||
|
makeResponseBlock( |
||||
|
'/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/', |
||||
|
{ |
||||
|
'd:getlastmodified': 'Fri, 10 Jul 2015 10:00:05 GMT', |
||||
|
'd:getetag': '"56cfcabd79abb"', |
||||
|
'd:resourcetype': '<d:collection/>', |
||||
|
'oc:id': '00000011oc2d13a6a068', |
||||
|
'oc:permissions': 'RDNVCK', |
||||
|
'oc:size': 120 |
||||
|
}, |
||||
|
[ |
||||
|
'd:getcontenttype', |
||||
|
'd:getcontentlength' |
||||
|
] |
||||
|
) + |
||||
|
'</d:multistatus>'; |
||||
|
|
||||
|
it('sends PROPFIND with zero depth to get single file info', function() { |
||||
|
client.getFileInfo('path/to space/文件夹'); |
||||
|
|
||||
|
expect(fakeServer.requests.length).toEqual(1); |
||||
|
expect(fakeServer.requests[0].method).toEqual('PROPFIND'); |
||||
|
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9'); |
||||
|
expect(fakeServer.requests[0].requestHeaders.Depth).toEqual(0); |
||||
|
|
||||
|
var props = getRequestedProperties(fakeServer.requests[0].requestBody); |
||||
|
expect(props).toContain('{DAV:}getlastmodified'); |
||||
|
expect(props).toContain('{DAV:}getcontentlength'); |
||||
|
expect(props).toContain('{DAV:}getcontenttype'); |
||||
|
expect(props).toContain('{DAV:}getetag'); |
||||
|
expect(props).toContain('{DAV:}resourcetype'); |
||||
|
expect(props).toContain('{http://owncloud.org/ns}id'); |
||||
|
expect(props).toContain('{http://owncloud.org/ns}size'); |
||||
|
expect(props).toContain('{http://owncloud.org/ns}permissions'); |
||||
|
}); |
||||
|
it('parses the result into a FileInfo', function() { |
||||
|
var promise = client.getFileInfo('path/to space/文件夹'); |
||||
|
|
||||
|
expect(fakeServer.requests.length).toEqual(1); |
||||
|
|
||||
|
fakeServer.requests[0].respond( |
||||
|
207, |
||||
|
{'Content-Type': 'application/xml'}, |
||||
|
responseXml |
||||
|
); |
||||
|
|
||||
|
promise.then(function(status, response) { |
||||
|
expect(status).toEqual(207); |
||||
|
expect(_.isArray(response)).toEqual(false); |
||||
|
|
||||
|
var info = response; |
||||
|
expect(info instanceof OC.Files.FileInfo).toEqual(true); |
||||
|
expect(info.id).toEqual(11); |
||||
|
expect(info.path).toEqual('/path/to space'); |
||||
|
expect(info.name).toEqual('文件夹'); |
||||
|
expect(info.permissions).toEqual(31); |
||||
|
expect(info.size).toEqual(120); |
||||
|
expect(info.mtime.getTime()).toEqual(1436522405000); |
||||
|
expect(info.mimetype).toEqual('httpd/unix-directory'); |
||||
|
expect(info.etag).toEqual('56cfcabd79abb'); |
||||
|
}); |
||||
|
|
||||
|
return promise; |
||||
|
}); |
||||
|
it('properly parses entry inside root', function() { |
||||
|
var responseXml = |
||||
|
'<?xml version="1.0" encoding="utf-8"?>' + |
||||
|
'<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' + |
||||
|
makeResponseBlock( |
||||
|
'/owncloud/remote.php/webdav/in%20root', |
||||
|
{ |
||||
|
'd:getlastmodified': 'Fri, 10 Jul 2015 10:00:05 GMT', |
||||
|
'd:getetag': '"56cfcabd79abb"', |
||||
|
'd:resourcetype': '<d:collection/>', |
||||
|
'oc:id': '00000011oc2d13a6a068', |
||||
|
'oc:permissions': 'RDNVCK', |
||||
|
'oc:size': 120 |
||||
|
}, |
||||
|
[ |
||||
|
'd:getcontenttype', |
||||
|
'd:getcontentlength' |
||||
|
] |
||||
|
) + |
||||
|
'</d:multistatus>'; |
||||
|
|
||||
|
var promise = client.getFileInfo('in root'); |
||||
|
|
||||
|
expect(fakeServer.requests.length).toEqual(1); |
||||
|
|
||||
|
fakeServer.requests[0].respond( |
||||
|
207, |
||||
|
{'Content-Type': 'application/xml'}, |
||||
|
responseXml |
||||
|
); |
||||
|
|
||||
|
promise.then(function(status, response) { |
||||
|
expect(status).toEqual(207); |
||||
|
expect(_.isArray(response)).toEqual(false); |
||||
|
|
||||
|
var info = response; |
||||
|
expect(info instanceof OC.Files.FileInfo).toEqual(true); |
||||
|
expect(info.id).toEqual(11); |
||||
|
expect(info.path).toEqual('/'); |
||||
|
expect(info.name).toEqual('in root'); |
||||
|
expect(info.permissions).toEqual(31); |
||||
|
expect(info.size).toEqual(120); |
||||
|
expect(info.mtime.getTime()).toEqual(1436522405000); |
||||
|
expect(info.mimetype).toEqual('httpd/unix-directory'); |
||||
|
expect(info.etag).toEqual('56cfcabd79abb'); |
||||
|
}); |
||||
|
|
||||
|
return promise; |
||||
|
}); |
||||
|
it('rejects promise when an error occurred', function() { |
||||
|
var promise = client.getFileInfo('path/to space/文件夹'); |
||||
|
return respondAndCheckError(promise, 404); |
||||
|
}); |
||||
|
it('throws exception if arguments are missing', function() { |
||||
|
// TODO
|
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('permissions', function() { |
||||
|
|
||||
|
function getFileInfoWithPermission(webdavPerm, isFile) { |
||||
|
var props = { |
||||
|
'd:getlastmodified': 'Fri, 10 Jul 2015 13:38:05 GMT', |
||||
|
'd:getetag': '"559fcabd79a38"', |
||||
|
'd:getcontentlength': 250, |
||||
|
'oc:id': '00000051oc2d13a6a068', |
||||
|
'oc:permissions': webdavPerm, |
||||
|
}; |
||||
|
|
||||
|
if (isFile) { |
||||
|
props['d:getcontenttype'] = 'text/plain'; |
||||
|
} else { |
||||
|
props['d:resourcetype'] = '<d:collection/>'; |
||||
|
} |
||||
|
|
||||
|
var responseXml = |
||||
|
'<?xml version="1.0" encoding="utf-8"?>' + |
||||
|
'<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' + |
||||
|
makeResponseBlock( |
||||
|
'/owncloud/remote.php/webdav/file.txt', |
||||
|
props |
||||
|
) + |
||||
|
'</d:multistatus>'; |
||||
|
var promise = client.getFileInfo('file.txt'); |
||||
|
|
||||
|
expect(fakeServer.requests.length).toEqual(1); |
||||
|
fakeServer.requests[0].respond( |
||||
|
207, |
||||
|
{'Content-Type': 'application/xml'}, |
||||
|
responseXml |
||||
|
); |
||||
|
|
||||
|
fakeServer.restore(); |
||||
|
fakeServer = sinon.fakeServer.create(); |
||||
|
|
||||
|
return promise; |
||||
|
} |
||||
|
|
||||
|
function testPermission(permission, isFile, expectedPermissions) { |
||||
|
var promise = getFileInfoWithPermission(permission, isFile); |
||||
|
promise.then(function(result) { |
||||
|
expect(result.permissions).toEqual(expectedPermissions); |
||||
|
}); |
||||
|
return promise; |
||||
|
} |
||||
|
|
||||
|
function testMountType(permission, isFile, expectedMountType) { |
||||
|
var promise = getFileInfoWithPermission(permission, isFile); |
||||
|
promise.then(function(result) { |
||||
|
expect(result.mountType).toEqual(expectedMountType); |
||||
|
}); |
||||
|
return promise; |
||||
|
} |
||||
|
|
||||
|
it('properly parses file permissions', function() { |
||||
|
// permission, isFile, expectedPermissions
|
||||
|
var testCases = [ |
||||
|
['', true, OC.PERMISSION_READ], |
||||
|
['C', true, OC.PERMISSION_READ | OC.PERMISSION_CREATE], |
||||
|
['K', true, OC.PERMISSION_READ | OC.PERMISSION_CREATE], |
||||
|
['W', true, OC.PERMISSION_READ | OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE], |
||||
|
['D', true, OC.PERMISSION_READ | OC.PERMISSION_DELETE], |
||||
|
['R', true, OC.PERMISSION_READ | OC.PERMISSION_SHARE], |
||||
|
['CKWDR', true, OC.PERMISSION_ALL] |
||||
|
]; |
||||
|
return Promise.all( |
||||
|
_.map(testCases, function(testCase) { |
||||
|
return testPermission.apply(testCase); |
||||
|
}) |
||||
|
); |
||||
|
}); |
||||
|
it('properly parses folder permissions', function() { |
||||
|
var testCases = [ |
||||
|
['', false, OC.PERMISSION_READ], |
||||
|
['C', false, OC.PERMISSION_READ | OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE], |
||||
|
['K', false, OC.PERMISSION_READ | OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE], |
||||
|
['W', false, OC.PERMISSION_READ | OC.PERMISSION_UPDATE], |
||||
|
['D', false, OC.PERMISSION_READ | OC.PERMISSION_DELETE], |
||||
|
['R', false, OC.PERMISSION_READ | OC.PERMISSION_SHARE], |
||||
|
['CKWDR', false, OC.PERMISSION_ALL] |
||||
|
]; |
||||
|
|
||||
|
return Promise.all( |
||||
|
_.map(testCases, function(testCase) { |
||||
|
return testPermission.apply(testCase); |
||||
|
}) |
||||
|
); |
||||
|
}); |
||||
|
it('properly parses mount types', function() { |
||||
|
var testCases = [ |
||||
|
['CKWDR', false, null], |
||||
|
['M', false, 'external'], |
||||
|
['S', false, 'shared'], |
||||
|
['SM', false, 'shared'] |
||||
|
]; |
||||
|
|
||||
|
return Promise.all( |
||||
|
_.map(testCases, function(testCase) { |
||||
|
return testMountType.apply(testCase); |
||||
|
}) |
||||
|
); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('get file contents', function() { |
||||
|
it('returns file contents', function() { |
||||
|
var promise = client.getFileContents('path/to space/文件夹/One.txt'); |
||||
|
|
||||
|
expect(fakeServer.requests.length).toEqual(1); |
||||
|
expect(fakeServer.requests[0].method).toEqual('GET'); |
||||
|
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt'); |
||||
|
|
||||
|
fakeServer.requests[0].respond( |
||||
|
200, |
||||
|
{'Content-Type': 'text/plain'}, |
||||
|
'some contents' |
||||
|
); |
||||
|
|
||||
|
promise.then(function(status, response) { |
||||
|
expect(status).toEqual(200); |
||||
|
expect(response).toEqual('some contents'); |
||||
|
}); |
||||
|
|
||||
|
return promise; |
||||
|
}); |
||||
|
it('rejects promise when an error occurred', function() { |
||||
|
var promise = client.getFileContents('path/to space/文件夹/One.txt'); |
||||
|
return respondAndCheckError(promise, 409); |
||||
|
}); |
||||
|
it('throws exception if arguments are missing', function() { |
||||
|
// TODO
|
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('put file contents', function() { |
||||
|
it('sends PUT with file contents', function() { |
||||
|
var promise = client.putFileContents( |
||||
|
'path/to space/文件夹/One.txt', |
||||
|
'some contents' |
||||
|
); |
||||
|
|
||||
|
expect(fakeServer.requests.length).toEqual(1); |
||||
|
expect(fakeServer.requests[0].method).toEqual('PUT'); |
||||
|
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt'); |
||||
|
expect(fakeServer.requests[0].requestBody).toEqual('some contents'); |
||||
|
expect(fakeServer.requests[0].requestHeaders['If-None-Match']).toEqual('*'); |
||||
|
expect(fakeServer.requests[0].requestHeaders['Content-Type']).toEqual('text/plain;charset=utf-8'); |
||||
|
|
||||
|
return respondAndCheckStatus(promise, 201); |
||||
|
}); |
||||
|
it('sends PUT with file contents with headers matching options', function() { |
||||
|
var promise = client.putFileContents( |
||||
|
'path/to space/文件夹/One.txt', |
||||
|
'some contents', |
||||
|
{ |
||||
|
overwrite: false, |
||||
|
contentType: 'text/markdown' |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
expect(fakeServer.requests.length).toEqual(1); |
||||
|
expect(fakeServer.requests[0].method).toEqual('PUT'); |
||||
|
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt'); |
||||
|
expect(fakeServer.requests[0].requestBody).toEqual('some contents'); |
||||
|
expect(fakeServer.requests[0].requestHeaders['If-None-Match']).not.toBeDefined(); |
||||
|
expect(fakeServer.requests[0].requestHeaders['Content-Type']).toEqual('text/markdown;charset=utf-8'); |
||||
|
|
||||
|
return respondAndCheckStatus(promise, 201); |
||||
|
}); |
||||
|
it('rejects promise when an error occurred', function() { |
||||
|
var promise = client.putFileContents( |
||||
|
'path/to space/文件夹/One.txt', |
||||
|
'some contents' |
||||
|
); |
||||
|
return respondAndCheckError(promise, 409); |
||||
|
}); |
||||
|
it('throws exception if arguments are missing', function() { |
||||
|
// TODO
|
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('create directory', function() { |
||||
|
it('sends MKCOL with specified path', function() { |
||||
|
var promise = client.createDirectory('path/to space/文件夹/new dir'); |
||||
|
|
||||
|
expect(fakeServer.requests.length).toEqual(1); |
||||
|
expect(fakeServer.requests[0].method).toEqual('MKCOL'); |
||||
|
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/new%20dir'); |
||||
|
|
||||
|
return respondAndCheckStatus(promise, 201); |
||||
|
}); |
||||
|
it('rejects promise when an error occurred', function() { |
||||
|
var promise = client.createDirectory('path/to space/文件夹/new dir'); |
||||
|
return respondAndCheckError(promise, 404); |
||||
|
}); |
||||
|
it('throws exception if arguments are missing', function() { |
||||
|
// TODO
|
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('deletion', function() { |
||||
|
it('sends DELETE with specified path', function() { |
||||
|
var promise = client.remove('path/to space/文件夹'); |
||||
|
|
||||
|
expect(fakeServer.requests.length).toEqual(1); |
||||
|
expect(fakeServer.requests[0].method).toEqual('DELETE'); |
||||
|
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9'); |
||||
|
|
||||
|
return respondAndCheckStatus(promise, 201); |
||||
|
}); |
||||
|
it('rejects promise when an error occurred', function() { |
||||
|
var promise = client.remove('path/to space/文件夹'); |
||||
|
return respondAndCheckError(promise, 404); |
||||
|
}); |
||||
|
it('throws exception if arguments are missing', function() { |
||||
|
// TODO
|
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('move', function() { |
||||
|
it('sends MOVE with specified paths with fail on overwrite by default', function() { |
||||
|
var promise = client.move( |
||||
|
'path/to space/文件夹', |
||||
|
'path/to space/anotherdir/文件夹' |
||||
|
); |
||||
|
|
||||
|
expect(fakeServer.requests.length).toEqual(1); |
||||
|
expect(fakeServer.requests[0].method).toEqual('MOVE'); |
||||
|
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9'); |
||||
|
expect(fakeServer.requests[0].requestHeaders.Destination) |
||||
|
.toEqual(baseUrl + 'path/to%20space/anotherdir/%E6%96%87%E4%BB%B6%E5%A4%B9'); |
||||
|
expect(fakeServer.requests[0].requestHeaders.Overwrite) |
||||
|
.toEqual('F'); |
||||
|
|
||||
|
return respondAndCheckStatus(promise, 201); |
||||
|
}); |
||||
|
it('sends MOVE with silent overwrite mode when specified', function() { |
||||
|
var promise = client.move( |
||||
|
'path/to space/文件夹', |
||||
|
'path/to space/anotherdir/文件夹', |
||||
|
{allowOverwrite: true} |
||||
|
); |
||||
|
|
||||
|
expect(fakeServer.requests.length).toEqual(1); |
||||
|
expect(fakeServer.requests[0].method).toEqual('MOVE'); |
||||
|
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9'); |
||||
|
expect(fakeServer.requests[0].requestHeaders.Destination) |
||||
|
.toEqual(baseUrl + 'path/to%20space/anotherdir/%E6%96%87%E4%BB%B6%E5%A4%B9'); |
||||
|
expect(fakeServer.requests[0].requestHeaders.Overwrite) |
||||
|
.not.toBeDefined(); |
||||
|
|
||||
|
return respondAndCheckStatus(promise, 201); |
||||
|
}); |
||||
|
it('rejects promise when an error occurred', function() { |
||||
|
var promise = client.move( |
||||
|
'path/to space/文件夹', |
||||
|
'path/to space/anotherdir/文件夹', |
||||
|
{allowOverwrite: true} |
||||
|
); |
||||
|
return respondAndCheckError(promise, 404); |
||||
|
}); |
||||
|
it('throws exception if arguments are missing', function() { |
||||
|
// TODO
|
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue