PageSourceSearch

https://dorfzentrum-leuscheid.de/teamfiles/core/js/files/client.js?v=f6a583b5e40f05bd9d51fc4e6da7964d

js dorfzentrum-leuscheid.de collected 2026-10-07 20:18:58 UTC 24,062 bytes, 958 lines download raw bytes

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.