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