client.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954
  1. /*
  2. * Copyright (c) 2015
  3. *
  4. * This file is licensed under the Affero General Public License version 3
  5. * or later.
  6. *
  7. * See the COPYING-README file.
  8. *
  9. */
  10. /* global dav */
  11. (function(OC, FileInfo) {
  12. /**
  13. * @class OC.Files.Client
  14. * @classdesc Client to access files on the server
  15. *
  16. * @param {Object} options
  17. * @param {String} options.host host name
  18. * @param {int} [options.port] port
  19. * @param {boolean} [options.useHTTPS] whether to use https
  20. * @param {String} [options.root] root path
  21. * @param {String} [options.userName] user name
  22. * @param {String} [options.password] password
  23. *
  24. * @since 8.2
  25. */
  26. var Client = function(options) {
  27. this._root = options.root;
  28. if (this._root.charAt(this._root.length - 1) === '/') {
  29. this._root = this._root.substr(0, this._root.length - 1);
  30. }
  31. var url = Client.PROTOCOL_HTTP + '://';
  32. if (options.useHTTPS) {
  33. url = Client.PROTOCOL_HTTPS + '://';
  34. }
  35. url += options.host + this._root;
  36. this._host = options.host;
  37. this._defaultHeaders = options.defaultHeaders || {
  38. 'X-Requested-With': 'XMLHttpRequest',
  39. 'requesttoken': OC.requestToken
  40. };
  41. this._baseUrl = url;
  42. var clientOptions = {
  43. baseUrl: this._baseUrl,
  44. xmlNamespaces: {
  45. 'DAV:': 'd',
  46. 'http://owncloud.org/ns': 'oc',
  47. 'http://nextcloud.org/ns': 'nc',
  48. 'http://open-collaboration-services.org/ns': 'ocs'
  49. }
  50. };
  51. if (options.userName) {
  52. clientOptions.userName = options.userName;
  53. }
  54. if (options.password) {
  55. clientOptions.password = options.password;
  56. }
  57. this._client = new dav.Client(clientOptions);
  58. this._client.xhrProvider = _.bind(this._xhrProvider, this);
  59. this._fileInfoParsers = [];
  60. };
  61. Client.NS_OWNCLOUD = 'http://owncloud.org/ns';
  62. Client.NS_NEXTCLOUD = 'http://nextcloud.org/ns';
  63. Client.NS_DAV = 'DAV:';
  64. Client.NS_OCS = 'http://open-collaboration-services.org/ns';
  65. Client.PROPERTY_GETLASTMODIFIED = '{' + Client.NS_DAV + '}getlastmodified';
  66. Client.PROPERTY_GETETAG = '{' + Client.NS_DAV + '}getetag';
  67. Client.PROPERTY_GETCONTENTTYPE = '{' + Client.NS_DAV + '}getcontenttype';
  68. Client.PROPERTY_RESOURCETYPE = '{' + Client.NS_DAV + '}resourcetype';
  69. Client.PROPERTY_INTERNAL_FILEID = '{' + Client.NS_OWNCLOUD + '}fileid';
  70. Client.PROPERTY_PERMISSIONS = '{' + Client.NS_OWNCLOUD + '}permissions';
  71. Client.PROPERTY_SIZE = '{' + Client.NS_OWNCLOUD + '}size';
  72. Client.PROPERTY_GETCONTENTLENGTH = '{' + Client.NS_DAV + '}getcontentlength';
  73. Client.PROPERTY_ISENCRYPTED = '{' + Client.NS_DAV + '}is-encrypted';
  74. Client.PROPERTY_SHARE_PERMISSIONS = '{' + Client.NS_OCS + '}share-permissions';
  75. Client.PROTOCOL_HTTP = 'http';
  76. Client.PROTOCOL_HTTPS = 'https';
  77. Client._PROPFIND_PROPERTIES = [
  78. /**
  79. * Modified time
  80. */
  81. [Client.NS_DAV, 'getlastmodified'],
  82. /**
  83. * Etag
  84. */
  85. [Client.NS_DAV, 'getetag'],
  86. /**
  87. * Mime type
  88. */
  89. [Client.NS_DAV, 'getcontenttype'],
  90. /**
  91. * Resource type "collection" for folders, empty otherwise
  92. */
  93. [Client.NS_DAV, 'resourcetype'],
  94. /**
  95. * File id
  96. */
  97. [Client.NS_OWNCLOUD, 'fileid'],
  98. /**
  99. * Letter-coded permissions
  100. */
  101. [Client.NS_OWNCLOUD, 'permissions'],
  102. //[Client.NS_OWNCLOUD, 'downloadURL'],
  103. /**
  104. * Folder sizes
  105. */
  106. [Client.NS_OWNCLOUD, 'size'],
  107. /**
  108. * File sizes
  109. */
  110. [Client.NS_DAV, 'getcontentlength'],
  111. /**
  112. * Preview availability
  113. */
  114. [Client.NS_NEXTCLOUD, 'has-preview'],
  115. /**
  116. * Mount type
  117. */
  118. [Client.NS_NEXTCLOUD, 'mount-type'],
  119. /**
  120. * Encryption state
  121. */
  122. [Client.NS_NEXTCLOUD, 'is-encrypted'],
  123. /**
  124. * Share permissions
  125. */
  126. [Client.NS_OCS, 'share-permissions']
  127. ];
  128. /**
  129. * @memberof OC.Files
  130. */
  131. Client.prototype = {
  132. /**
  133. * Root path of the Webdav endpoint
  134. *
  135. * @type string
  136. */
  137. _root: null,
  138. /**
  139. * Client from the library
  140. *
  141. * @type dav.Client
  142. */
  143. _client: null,
  144. /**
  145. * Array of file info parsing functions.
  146. *
  147. * @type Array<OC.Files.Client~parseFileInfo>
  148. */
  149. _fileInfoParsers: [],
  150. /**
  151. * Returns the configured XHR provider for davclient
  152. * @return {XMLHttpRequest}
  153. */
  154. _xhrProvider: function() {
  155. var headers = this._defaultHeaders;
  156. var xhr = new XMLHttpRequest();
  157. var oldOpen = xhr.open;
  158. // override open() method to add headers
  159. xhr.open = function() {
  160. var result = oldOpen.apply(this, arguments);
  161. _.each(headers, function(value, key) {
  162. xhr.setRequestHeader(key, value);
  163. });
  164. return result;
  165. };
  166. OC.registerXHRForErrorProcessing(xhr);
  167. return xhr;
  168. },
  169. /**
  170. * Prepends the base url to the given path sections
  171. *
  172. * @param {...String} path sections
  173. *
  174. * @return {String} base url + joined path, any leading or trailing slash
  175. * will be kept
  176. */
  177. _buildUrl: function() {
  178. var path = this._buildPath.apply(this, arguments);
  179. if (path.charAt([path.length - 1]) === '/') {
  180. path = path.substr(0, path.length - 1);
  181. }
  182. if (path.charAt(0) === '/') {
  183. path = path.substr(1);
  184. }
  185. return this._baseUrl + '/' + path;
  186. },
  187. /**
  188. * Append the path to the root and also encode path
  189. * sections
  190. *
  191. * @param {...String} path sections
  192. *
  193. * @return {String} joined path, any leading or trailing slash
  194. * will be kept
  195. */
  196. _buildPath: function() {
  197. var path = OC.joinPaths.apply(this, arguments);
  198. var sections = path.split('/');
  199. var i;
  200. for (i = 0; i < sections.length; i++) {
  201. sections[i] = encodeURIComponent(sections[i]);
  202. }
  203. path = sections.join('/');
  204. return path;
  205. },
  206. /**
  207. * Parse headers string into a map
  208. *
  209. * @param {string} headersString headers list as string
  210. *
  211. * @return {Object.<String,Array>} map of header name to header contents
  212. */
  213. _parseHeaders: function(headersString) {
  214. var headerRows = headersString.split('\n');
  215. var headers = {};
  216. for (var i = 0; i < headerRows.length; i++) {
  217. var sepPos = headerRows[i].indexOf(':');
  218. if (sepPos < 0) {
  219. continue;
  220. }
  221. var headerName = headerRows[i].substr(0, sepPos);
  222. var headerValue = headerRows[i].substr(sepPos + 2);
  223. if (!headers[headerName]) {
  224. // make it an array
  225. headers[headerName] = [];
  226. }
  227. headers[headerName].push(headerValue);
  228. }
  229. return headers;
  230. },
  231. /**
  232. * Parses the etag response which is in double quotes.
  233. *
  234. * @param {string} etag etag value in double quotes
  235. *
  236. * @return {string} etag without double quotes
  237. */
  238. _parseEtag: function(etag) {
  239. if (etag.charAt(0) === '"') {
  240. return etag.split('"')[1];
  241. }
  242. return etag;
  243. },
  244. /**
  245. * Parse Webdav result
  246. *
  247. * @param {Object} response XML object
  248. *
  249. * @return {Array.<FileInfo>} array of file info
  250. */
  251. _parseFileInfo: function(response) {
  252. var path = decodeURIComponent(response.href);
  253. if (path.substr(0, this._root.length) === this._root) {
  254. path = path.substr(this._root.length);
  255. }
  256. if (path.charAt(path.length - 1) === '/') {
  257. path = path.substr(0, path.length - 1);
  258. }
  259. if (response.propStat.length === 0 || response.propStat[0].status !== 'HTTP/1.1 200 OK') {
  260. return null;
  261. }
  262. var props = response.propStat[0].properties;
  263. var data = {
  264. id: props[Client.PROPERTY_INTERNAL_FILEID],
  265. path: OC.dirname(path) || '/',
  266. name: OC.basename(path),
  267. mtime: (new Date(props[Client.PROPERTY_GETLASTMODIFIED])).getTime()
  268. };
  269. var etagProp = props[Client.PROPERTY_GETETAG];
  270. if (!_.isUndefined(etagProp)) {
  271. data.etag = this._parseEtag(etagProp);
  272. }
  273. var sizeProp = props[Client.PROPERTY_GETCONTENTLENGTH];
  274. if (!_.isUndefined(sizeProp)) {
  275. data.size = parseInt(sizeProp, 10);
  276. }
  277. sizeProp = props[Client.PROPERTY_SIZE];
  278. if (!_.isUndefined(sizeProp)) {
  279. data.size = parseInt(sizeProp, 10);
  280. }
  281. var hasPreviewProp = props['{' + Client.NS_NEXTCLOUD + '}has-preview'];
  282. if (!_.isUndefined(hasPreviewProp)) {
  283. data.hasPreview = hasPreviewProp === 'true';
  284. } else {
  285. data.hasPreview = true;
  286. }
  287. var isEncryptedProp = props['{' + Client.NS_NEXTCLOUD + '}is-encrypted'];
  288. if (!_.isUndefined(isEncryptedProp)) {
  289. data.isEncrypted = isEncryptedProp === '1';
  290. } else {
  291. data.isEncrypted = false;
  292. }
  293. var isFavouritedProp = props['{' + Client.NS_OWNCLOUD + '}favorite'];
  294. if (!_.isUndefined(isFavouritedProp)) {
  295. data.isFavourited = isFavouritedProp === '1';
  296. } else {
  297. data.isFavourited = false;
  298. }
  299. var contentType = props[Client.PROPERTY_GETCONTENTTYPE];
  300. if (!_.isUndefined(contentType)) {
  301. data.mimetype = contentType;
  302. }
  303. var resType = props[Client.PROPERTY_RESOURCETYPE];
  304. if (!data.mimetype && resType) {
  305. var xmlvalue = resType[0];
  306. if (xmlvalue.namespaceURI === Client.NS_DAV && xmlvalue.nodeName.split(':')[1] === 'collection') {
  307. data.mimetype = 'httpd/unix-directory';
  308. }
  309. }
  310. data.permissions = OC.PERMISSION_NONE;
  311. var permissionProp = props[Client.PROPERTY_PERMISSIONS];
  312. if (!_.isUndefined(permissionProp)) {
  313. var permString = permissionProp || '';
  314. data.mountType = null;
  315. for (var i = 0; i < permString.length; i++) {
  316. var c = permString.charAt(i);
  317. switch (c) {
  318. // FIXME: twisted permissions
  319. case 'C':
  320. case 'K':
  321. data.permissions |= OC.PERMISSION_CREATE;
  322. break;
  323. case 'G':
  324. data.permissions |= OC.PERMISSION_READ;
  325. break;
  326. case 'W':
  327. case 'N':
  328. case 'V':
  329. data.permissions |= OC.PERMISSION_UPDATE;
  330. break;
  331. case 'D':
  332. data.permissions |= OC.PERMISSION_DELETE;
  333. break;
  334. case 'R':
  335. data.permissions |= OC.PERMISSION_SHARE;
  336. break;
  337. case 'M':
  338. if (!data.mountType) {
  339. // TODO: how to identify external-root ?
  340. data.mountType = 'external';
  341. }
  342. break;
  343. case 'S':
  344. // TODO: how to identify shared-root ?
  345. data.mountType = 'shared';
  346. break;
  347. }
  348. }
  349. }
  350. var sharePermissionsProp = props[Client.PROPERTY_SHARE_PERMISSIONS];
  351. if (!_.isUndefined(sharePermissionsProp)) {
  352. data.sharePermissions = parseInt(sharePermissionsProp);
  353. }
  354. var mounTypeProp = props['{' + Client.NS_NEXTCLOUD + '}mount-type'];
  355. if (!_.isUndefined(mounTypeProp)) {
  356. data.mountType = mounTypeProp;
  357. }
  358. // extend the parsed data using the custom parsers
  359. _.each(this._fileInfoParsers, function(parserFunction) {
  360. _.extend(data, parserFunction(response, data) || {});
  361. });
  362. return new FileInfo(data);
  363. },
  364. /**
  365. * Parse Webdav multistatus
  366. *
  367. * @param {Array} responses
  368. */
  369. _parseResult: function(responses) {
  370. var self = this;
  371. return _.map(responses, function(response) {
  372. return self._parseFileInfo(response);
  373. });
  374. },
  375. /**
  376. * Returns whether the given status code means success
  377. *
  378. * @param {int} status status code
  379. *
  380. * @return true if status code is between 200 and 299 included
  381. */
  382. _isSuccessStatus: function(status) {
  383. return status >= 200 && status <= 299;
  384. },
  385. /**
  386. * Parse the Sabre exception out of the given response, if any
  387. *
  388. * @param {Object} response object
  389. * @return {Object} array of parsed message and exception (only the first one)
  390. */
  391. _getSabreException: function(response) {
  392. var result = {};
  393. var xml = response.xhr.responseXML;
  394. if (xml === null) {
  395. return result;
  396. }
  397. var messages = xml.getElementsByTagNameNS('http://sabredav.org/ns', 'message');
  398. var exceptions = xml.getElementsByTagNameNS('http://sabredav.org/ns', 'exception');
  399. if (messages.length) {
  400. result.message = messages[0].textContent;
  401. }
  402. if (exceptions.length) {
  403. result.exception = exceptions[0].textContent;
  404. }
  405. return result;
  406. },
  407. /**
  408. * Returns the default PROPFIND properties to use during a call.
  409. *
  410. * @return {Array.<Object>} array of properties
  411. */
  412. getPropfindProperties: function() {
  413. if (!this._propfindProperties) {
  414. this._propfindProperties = _.map(Client._PROPFIND_PROPERTIES, function(propDef) {
  415. return '{' + propDef[0] + '}' + propDef[1];
  416. });
  417. }
  418. return this._propfindProperties;
  419. },
  420. /**
  421. * Lists the contents of a directory
  422. *
  423. * @param {String} path path to retrieve
  424. * @param {Object} [options] options
  425. * @param {boolean} [options.includeParent=false] set to true to keep
  426. * the parent folder in the result list
  427. * @param {Array} [options.properties] list of Webdav properties to retrieve
  428. *
  429. * @return {Promise} promise
  430. */
  431. getFolderContents: function(path, options) {
  432. if (!path) {
  433. path = '';
  434. }
  435. options = options || {};
  436. var self = this;
  437. var deferred = $.Deferred();
  438. var promise = deferred.promise();
  439. var properties;
  440. if (_.isUndefined(options.properties)) {
  441. properties = this.getPropfindProperties();
  442. } else {
  443. properties = options.properties;
  444. }
  445. this._client.propFind(
  446. this._buildUrl(path),
  447. properties,
  448. 1
  449. ).then(function(result) {
  450. if (self._isSuccessStatus(result.status)) {
  451. var results = self._parseResult(result.body);
  452. if (!options || !options.includeParent) {
  453. // remove root dir, the first entry
  454. results.shift();
  455. }
  456. deferred.resolve(result.status, results);
  457. } else {
  458. result = _.extend(result, self._getSabreException(result));
  459. deferred.reject(result.status, result);
  460. }
  461. });
  462. return promise;
  463. },
  464. /**
  465. * Fetches a flat list of files filtered by a given filter criteria.
  466. * (currently system tags and circles are supported)
  467. *
  468. * @param {Object} filter filter criteria
  469. * @param {Object} [filter.systemTagIds] list of system tag ids to filter by
  470. * @param {bool} [filter.favorite] set it to filter by favorites
  471. * @param {Object} [options] options
  472. * @param {Array} [options.properties] list of Webdav properties to retrieve
  473. *
  474. * @return {Promise} promise
  475. */
  476. getFilteredFiles: function(filter, options) {
  477. options = options || {};
  478. var self = this;
  479. var deferred = $.Deferred();
  480. var promise = deferred.promise();
  481. var properties;
  482. if (_.isUndefined(options.properties)) {
  483. properties = this.getPropfindProperties();
  484. } else {
  485. properties = options.properties;
  486. }
  487. if (!filter ||
  488. (!filter.systemTagIds && _.isUndefined(filter.favorite) && !filter.circlesIds) ) {
  489. throw 'Missing filter argument';
  490. }
  491. // root element with namespaces
  492. var body = '<oc:filter-files ';
  493. var namespace;
  494. for (namespace in this._client.xmlNamespaces) {
  495. body += ' xmlns:' + this._client.xmlNamespaces[namespace] + '="' + namespace + '"';
  496. }
  497. body += '>\n';
  498. // properties query
  499. body += ' <' + this._client.xmlNamespaces['DAV:'] + ':prop>\n';
  500. _.each(properties, function(prop) {
  501. var property = self._client.parseClarkNotation(prop);
  502. body += ' <' + self._client.xmlNamespaces[property.namespace] + ':' + property.name + ' />\n';
  503. });
  504. body += ' </' + this._client.xmlNamespaces['DAV:'] + ':prop>\n';
  505. // rules block
  506. body += ' <oc:filter-rules>\n';
  507. _.each(filter.systemTagIds, function(systemTagIds) {
  508. body += ' <oc:systemtag>' + escapeHTML(systemTagIds) + '</oc:systemtag>\n';
  509. });
  510. _.each(filter.circlesIds, function(circlesIds) {
  511. body += ' <oc:circle>' + escapeHTML(circlesIds) + '</oc:circle>\n';
  512. });
  513. if (filter.favorite) {
  514. body += ' <oc:favorite>' + (filter.favorite ? '1': '0') + '</oc:favorite>\n';
  515. }
  516. body += ' </oc:filter-rules>\n';
  517. // end of root
  518. body += '</oc:filter-files>\n';
  519. this._client.request(
  520. 'REPORT',
  521. this._buildUrl(),
  522. {},
  523. body
  524. ).then(function(result) {
  525. if (self._isSuccessStatus(result.status)) {
  526. var results = self._parseResult(result.body);
  527. deferred.resolve(result.status, results);
  528. } else {
  529. result = _.extend(result, self._getSabreException(result));
  530. deferred.reject(result.status, result);
  531. }
  532. });
  533. return promise;
  534. },
  535. /**
  536. * Returns the file info of a given path.
  537. *
  538. * @param {String} path path
  539. * @param {Array} [options.properties] list of Webdav properties to retrieve
  540. *
  541. * @return {Promise} promise
  542. */
  543. getFileInfo: function(path, options) {
  544. if (!path) {
  545. path = '';
  546. }
  547. options = options || {};
  548. var self = this;
  549. var deferred = $.Deferred();
  550. var promise = deferred.promise();
  551. var properties;
  552. if (_.isUndefined(options.properties)) {
  553. properties = this.getPropfindProperties();
  554. } else {
  555. properties = options.properties;
  556. }
  557. // TODO: headers
  558. this._client.propFind(
  559. this._buildUrl(path),
  560. properties,
  561. 0
  562. ).then(
  563. function(result) {
  564. if (self._isSuccessStatus(result.status)) {
  565. deferred.resolve(result.status, self._parseResult([result.body])[0]);
  566. } else {
  567. result = _.extend(result, self._getSabreException(result));
  568. deferred.reject(result.status, result);
  569. }
  570. }
  571. );
  572. return promise;
  573. },
  574. /**
  575. * Returns the contents of the given file.
  576. *
  577. * @param {String} path path to file
  578. *
  579. * @return {Promise}
  580. */
  581. getFileContents: function(path) {
  582. if (!path) {
  583. throw 'Missing argument "path"';
  584. }
  585. var self = this;
  586. var deferred = $.Deferred();
  587. var promise = deferred.promise();
  588. this._client.request(
  589. 'GET',
  590. this._buildUrl(path)
  591. ).then(
  592. function(result) {
  593. if (self._isSuccessStatus(result.status)) {
  594. deferred.resolve(result.status, result.body);
  595. } else {
  596. result = _.extend(result, self._getSabreException(result));
  597. deferred.reject(result.status, result);
  598. }
  599. }
  600. );
  601. return promise;
  602. },
  603. /**
  604. * Puts the given data into the given file.
  605. *
  606. * @param {String} path path to file
  607. * @param {String} body file body
  608. * @param {Object} [options]
  609. * @param {String} [options.contentType='text/plain'] content type
  610. * @param {bool} [options.overwrite=true] whether to overwrite an existing file
  611. *
  612. * @return {Promise}
  613. */
  614. putFileContents: function(path, body, options) {
  615. if (!path) {
  616. throw 'Missing argument "path"';
  617. }
  618. var self = this;
  619. var deferred = $.Deferred();
  620. var promise = deferred.promise();
  621. options = options || {};
  622. var headers = {};
  623. var contentType = 'text/plain;charset=utf-8';
  624. if (options.contentType) {
  625. contentType = options.contentType;
  626. }
  627. headers['Content-Type'] = contentType;
  628. if (_.isUndefined(options.overwrite) || options.overwrite) {
  629. // will trigger 412 precondition failed if a file already exists
  630. headers['If-None-Match'] = '*';
  631. }
  632. this._client.request(
  633. 'PUT',
  634. this._buildUrl(path),
  635. headers,
  636. body || ''
  637. ).then(
  638. function(result) {
  639. if (self._isSuccessStatus(result.status)) {
  640. deferred.resolve(result.status);
  641. } else {
  642. result = _.extend(result, self._getSabreException(result));
  643. deferred.reject(result.status, result);
  644. }
  645. }
  646. );
  647. return promise;
  648. },
  649. _simpleCall: function(method, path) {
  650. if (!path) {
  651. throw 'Missing argument "path"';
  652. }
  653. var self = this;
  654. var deferred = $.Deferred();
  655. var promise = deferred.promise();
  656. this._client.request(
  657. method,
  658. this._buildUrl(path)
  659. ).then(
  660. function(result) {
  661. if (self._isSuccessStatus(result.status)) {
  662. deferred.resolve(result.status);
  663. } else {
  664. result = _.extend(result, self._getSabreException(result));
  665. deferred.reject(result.status, result);
  666. }
  667. }
  668. );
  669. return promise;
  670. },
  671. /**
  672. * Creates a directory
  673. *
  674. * @param {String} path path to create
  675. *
  676. * @return {Promise}
  677. */
  678. createDirectory: function(path) {
  679. return this._simpleCall('MKCOL', path);
  680. },
  681. /**
  682. * Deletes a file or directory
  683. *
  684. * @param {String} path path to delete
  685. *
  686. * @return {Promise}
  687. */
  688. remove: function(path) {
  689. return this._simpleCall('DELETE', path);
  690. },
  691. /**
  692. * Moves path to another path
  693. *
  694. * @param {String} path path to move
  695. * @param {String} destinationPath destination path
  696. * @param {boolean} [allowOverwrite=false] true to allow overwriting,
  697. * false otherwise
  698. * @param {Object} [headers=null] additional headers
  699. *
  700. * @return {Promise} promise
  701. */
  702. move: function(path, destinationPath, allowOverwrite, headers) {
  703. if (!path) {
  704. throw 'Missing argument "path"';
  705. }
  706. if (!destinationPath) {
  707. throw 'Missing argument "destinationPath"';
  708. }
  709. var self = this;
  710. var deferred = $.Deferred();
  711. var promise = deferred.promise();
  712. headers = _.extend({}, headers, {
  713. 'Destination' : this._buildUrl(destinationPath)
  714. });
  715. if (!allowOverwrite) {
  716. headers.Overwrite = 'F';
  717. }
  718. this._client.request(
  719. 'MOVE',
  720. this._buildUrl(path),
  721. headers
  722. ).then(
  723. function(result) {
  724. if (self._isSuccessStatus(result.status)) {
  725. deferred.resolve(result.status);
  726. } else {
  727. result = _.extend(result, self._getSabreException(result));
  728. deferred.reject(result.status, result);
  729. }
  730. }
  731. );
  732. return promise;
  733. },
  734. /**
  735. * Copies path to another path
  736. *
  737. * @param {String} path path to copy
  738. * @param {String} destinationPath destination path
  739. * @param {boolean} [allowOverwrite=false] true to allow overwriting,
  740. * false otherwise
  741. *
  742. * @return {Promise} promise
  743. */
  744. copy: function (path, destinationPath, allowOverwrite) {
  745. if (!path) {
  746. throw 'Missing argument "path"';
  747. }
  748. if (!destinationPath) {
  749. throw 'Missing argument "destinationPath"';
  750. }
  751. var self = this;
  752. var deferred = $.Deferred();
  753. var promise = deferred.promise();
  754. var headers = {
  755. 'Destination' : this._buildUrl(destinationPath)
  756. };
  757. if (!allowOverwrite) {
  758. headers.Overwrite = 'F';
  759. }
  760. this._client.request(
  761. 'COPY',
  762. this._buildUrl(path),
  763. headers
  764. ).then(
  765. function(response) {
  766. if (self._isSuccessStatus(response.status)) {
  767. deferred.resolve(response.status);
  768. } else {
  769. deferred.reject(response.status);
  770. }
  771. }
  772. );
  773. return promise;
  774. },
  775. /**
  776. * Add a file info parser function
  777. *
  778. * @param {OC.Files.Client~parseFileInfo} parserFunction
  779. */
  780. addFileInfoParser: function(parserFunction) {
  781. this._fileInfoParsers.push(parserFunction);
  782. },
  783. /**
  784. * Returns the dav.Client instance used internally
  785. *
  786. * @since 11.0.0
  787. * @return {dav.Client}
  788. */
  789. getClient: function() {
  790. return this._client;
  791. },
  792. /**
  793. * Returns the user name
  794. *
  795. * @since 11.0.0
  796. * @return {String} userName
  797. */
  798. getUserName: function() {
  799. return this._client.userName;
  800. },
  801. /**
  802. * Returns the password
  803. *
  804. * @since 11.0.0
  805. * @return {String} password
  806. */
  807. getPassword: function() {
  808. return this._client.password;
  809. },
  810. /**
  811. * Returns the base URL
  812. *
  813. * @since 11.0.0
  814. * @return {String} base URL
  815. */
  816. getBaseUrl: function() {
  817. return this._client.baseUrl;
  818. },
  819. /**
  820. * Returns the host
  821. *
  822. * @since 13.0.0
  823. * @return {String} base URL
  824. */
  825. getHost: function() {
  826. return this._host;
  827. }
  828. };
  829. /**
  830. * File info parser function
  831. *
  832. * This function receives a list of Webdav properties as input and
  833. * should return a hash array of parsed properties, if applicable.
  834. *
  835. * @callback OC.Files.Client~parseFileInfo
  836. * @param {Object} XML Webdav properties
  837. * @return {Array} array of parsed property values
  838. */
  839. if (!OC.Files) {
  840. /**
  841. * @namespace OC.Files
  842. *
  843. * @since 8.2
  844. */
  845. OC.Files = {};
  846. }
  847. /**
  848. * Returns the default instance of the files client
  849. *
  850. * @return {OC.Files.Client} default client
  851. *
  852. * @since 8.2
  853. */
  854. OC.Files.getClient = function() {
  855. if (OC.Files._defaultClient) {
  856. return OC.Files._defaultClient;
  857. }
  858. var client = new OC.Files.Client({
  859. host: OC.getHost(),
  860. port: OC.getPort(),
  861. root: OC.linkToRemoteBase('dav') + '/files/' + OC.getCurrentUser().uid,
  862. useHTTPS: OC.getProtocol() === 'https'
  863. });
  864. OC.Files._defaultClient = client;
  865. return client;
  866. };
  867. OC.Files.Client = Client;
  868. })(OC, OC.Files.FileInfo);