UNPKG

26.1 kBMarkdownView Raw
1![WebDAV](https://raw.githubusercontent.com/perry-mitchell/webdav-client/master/webdav.jpg)
2
3> A WebDAV client, written in Typescript, for NodeJS and the browser
4
5[![Build Status](https://travis-ci.org/perry-mitchell/webdav-client.svg?branch=master)](https://travis-ci.org/perry-mitchell/webdav-client) [![npm version](https://badge.fury.io/js/webdav.svg)](https://www.npmjs.com/package/webdav) [![monthly downloads](https://img.shields.io/npm/dm/webdav.svg)](https://www.npmjs.com/package/webdav) [![total downloads](https://img.shields.io/npm/dt/webdav.svg?label=total%20downloads)](https://www.npmjs.com/package/webdav)
6
7## About
8
9WebDAV is a well-known, stable and highly flexible protocol for interacting with remote filesystems via an API. Being that it is so widespread, many file hosting services such as **Box**, **Nextcloud**/**ownCloud** and **Yandex** use it as a fallback to their primary interfaces.
10
11This library provides a **WebDAV client** interface that makes interacting with WebDAV enabled services easy. The API returns promises and resolve with the results. It parses and prepares directory-contents requests for easy consumption, as well as providing methods for fetching things like file stats and quotas.
12
13This library's motivation is **not** to follow an RFC or to strictly adhere to standard WebDAV interfaces, but to provide an easy-to-consume client API for working with most WebDAV services from Node or the browser.
14
15### Node support
16
17This library is compatible with **NodeJS version 10** and above (For version 6/8 support, use versions in the range of `2.*`. For version 4 support, use versions in the range of `1.*`). Versions 2.x and 1.x are no longer supported, so use them at your own risk. Version 3.x is deprecated and may receive the odd bugfix.
18
19### Browser support
20
21This WebDAV client is supported in the browser is of version 3. The compilation settings specify a minimum supported browser version of Internet Explorer 11, however testing in this browser is not performed regularly.
22
23_Although you may choose to transpile this library's default entry point (NodeJS) yourself, it is not advised - use the dedicated web version instead._
24
25You can use the web version via a different entry point:
26
27```typescript
28import { createClient } from "webdav/web";
29```
30
31The browser version uses a UMD-style module definition, meaning you can simply load the library within your browser using a `<script>` tag. When using this method the library is made available on the window object as such: `window.WebDAV`. For example:
32
33```javascript
34const client = window.WebDAV.createClient(/* ... */);
35```
36
37**NB:** Streams are not available within the browser, so `createReadStream` and `createWriteStream` are just stubbed. Calling them will throw an exception.
38
39### Types
40
41Typescript types are exported with this library for the Node build. All of the types can also be directly imported from the module:
42
43```typescript
44import { AuthType, createClient } from "webdav";
45
46const client = createClient("https://some-server.org", {
47 authType: AuthType.Digest,
48 username: "user",
49 password: "pass"
50});
51```
52
53## Installation
54
55Simple install as a dependency using npm:
56
57```
58npm install webdav --save
59```
60
61## Usage
62
63Usage entails creating a client adapter instance by calling the factory function `createClient`:
64
65```typescript
66const { createClient } = require("webdav");
67
68const client = createClient(
69 "https://webdav.example.com/marie123",
70 {
71 username: "marie",
72 password: "myS3curePa$$w0rd"
73 }
74);
75
76// Get directory contents
77const directoryItems = await client.getDirectoryContents("/");
78// Outputs a structure like:
79// [{
80// filename: "/my-file.txt",
81// basename: "my-file.txt",
82// lastmod: "Mon, 10 Oct 2018 23:24:11 GMT",
83// size: 371,
84// type: "file"
85// }]
86```
87
88### Authentication & Connection
89
90The WebDAV client automatically detects which authentication to use, between `AuthType.None` and `AuthType.Password`, if no `authType` configuration parameter is provided. For `AuthType.Token` or `AuthType.Digest`, you must specify it explicitly.
91
92Setting the `authType` will automatically manage the `Authorization` header when connecting.
93
94#### Basic/no authentication
95
96You can use the client without authentication if the server doesn't require it - simply avoid passing any values to `username`, `password` in the config.
97
98To use basic authentication, simply pass a `username` and `password` in the config.
99
100This library also allows for overriding the built in HTTP and HTTPS agents by setting the properties `httpAgent` & `httpsAgent` accordingly. These should be instances of node's [http.Agent](https://nodejs.org/api/http.html#http_class_http_agent) and [https.Agent](https://nodejs.org/api/https.html#https_class_https_agent) respectively.
101
102#### OAuth tokens
103
104To use a token to authenticate, pass the token data to the `token` field and specify the `authType`:
105
106```typescript
107createClient(
108 "https://address.com",
109 {
110 authType: AuthType.Token,
111 token: {
112 access_token: "2YotnFZFEjr1zCsicMWpAA",
113 token_type: "example",
114 expires_in: 3600,
115 refresh_token: "tGzv3JOkF0XG5Qx2TlKWIA",
116 example_parameter: "example_value"
117 }
118 }
119);
120```
121
122#### Digest authentication
123
124If a server requires digest-based authentication, you can enable this functionality by the `authType` configuration parameter, as well as providing a `username` and `password`:
125
126```typescript
127createClient(
128 "https://address.com",
129 {
130 authType: AuthType.Digest,
131 username: "someUser",
132 password: "myS3curePa$$w0rd"
133 }
134);
135```
136
137### Client configuration
138
139The `createClient` method takes a WebDAV service URL, and a configuration options parameter.
140
141The available configuration options are as follows:
142
143| Option | Default | Description |
144|---------------|---------------|---------------------------------------------------|
145| `authType` | `null` | The authentication type to use. If not provided, defaults to trying to detect based upon whether `username` and `password` were provided. |
146| `headers` | `{}` | Additional headers provided to all requests. Headers provided here are overridden by method-specific headers, including `Authorization`. |
147| `httpAgent` | _None_ | HTTP agent instance. Available only in Node. See [http.Agent](https://nodejs.org/api/http.html#http_class_http_agent). |
148| `httpsAgent` | _None_ | HTTPS agent instance. Available only in Node. See [https.Agent](https://nodejs.org/api/https.html#https_class_https_agent). |
149| `maxBodyLength` | _None_ | Maximum body length allowed for sending, in bytes. |
150| `maxContentLength` | _None_ | Maximum content length allowed for receiving, in bytes. |
151| `password` | _None_ | Password for authentication. |
152| `token` | _None_ | Token object for authentication. |
153| `username` | _None_ | Username for authentication. |
154| `withCredentials` | _None_ | Credentials inclusion setting for Axios. |
155
156### Client methods
157
158The `WebDAVClient` interface type contains all the methods and signatures for the WebDAV client instance.
159
160#### copyFile
161
162Copy a file from one location to another.
163
164```typescript
165await client.copyFile(
166 "/images/test.jpg",
167 "/public/img/test.jpg"
168);
169```
170
171```typescript
172(filename: string, destination: string, options?: WebDAVMethodOptions) => Promise<void>
173```
174
175| Argument | Required | Description |
176|-------------------|-----------|-----------------------------------------------|
177| `filename` | Yes | The source filename. |
178| `destination` | Yes | The destination filename. |
179| `options` | No | [Method options](#method-options). |
180
181#### createDirectory
182
183Create a new directory.
184
185```typescript
186await client.createDirectory("/data/system/storage");
187```
188
189```typescript
190(path: string, options?: CreateDirectoryOptions) => Promise<void>
191```
192
193| Argument | Required | Description |
194|-----------------------|-----------|-----------------------------------------------|
195| `path` | Yes | The path to create. |
196| `options` | No | Create directory options. |
197| `options.recursive` | No | Recursively create directories if they do not exist. |
198
199_`options` extends [method options](#method-options)._
200
201##### Recursive creation
202
203Recursive directory creation is expensive request-wise. Multiple `stat` requests are made (totalling the depth of the path that exists, +1) to detect what parts of the path already exist, until finding a segment that doesn't exist - where it then only requests the _creation_ method.
204
205For example, a recursive call to create a path `/a/b/c/d/e`, where `/a/b` already exists, will result in **3** `stat` requests (for `/a`, `/a/b` and `/a/b/c`) and **3** `createDirectory` requests (for `/a/b/c`, `/a/b/c/d` and `/a/b/c/d/e`).
206
207#### createReadStream
208
209Synchronously create a readable stream for a remote file.
210
211_Note that although a stream is returned instantly, the connection and fetching of the file is still performed asynchronously in the background. There will be some delay before the stream begins receiving data._
212
213```typescript
214client
215 .createReadStream("/video.mp4")
216 .pipe(fs.createWriteStream("~/video.mp4"));
217```
218
219If you want to stream only part of the file, you can specify the `range` in the options argument:
220
221```typescript
222client
223 .createReadStream(
224 "/video.mp4",
225 { range: { start: 0, end: 1024 } }
226 ).pipe(fs.createWriteStream("~/video.mp4"));
227```
228
229```typescript
230(filename: string, options?: CreateReadStreamOptions) => Stream.Readable
231```
232
233| Argument | Required | Description |
234|-------------------|-----------|-----------------------------------------------|
235| `callback` | No | Callback to fire with the response of the request. |
236| `filename` | Yes | The remote file to stream. |
237| `options` | No | Read stream options. |
238| `options.range` | No | Stream range configuration. |
239| `options.range.start` | Yes | Byte-position for the start of the stream. |
240| `options.range.end` | No | Byte-position for the end of the stream. |
241
242_`options` extends [method options](#method-options)._
243
244#### createWriteStream
245
246Create a write stream targeted at a remote file.
247
248_Note that although a stream is returned instantly, the connection and writing to the remote file is still performed asynchronously in the background. There will be some delay before the stream begins piping data._
249
250```typescript
251fs
252 .createReadStream("~/Music/song.mp3")
253 .pipe(client.createWriteStream("/music/song.mp3"));
254```
255
256```typescript
257(filename: string, options?: CreateWriteStreamOptions, callback?: CreateWriteStreamCallback) => Stream.Writable
258```
259
260| Argument | Required | Description |
261|-------------------|-----------|-----------------------------------------------|
262| `filename` | Yes | The remote file to stream. |
263| `options` | No | Write stream options. |
264| `options.overwrite` | No | Whether or not to overwrite the remote file if it already exists. Defaults to `true`. |
265| `callback` | No | Callback to fire once the connection has been made and streaming has started. Callback is called with the response of the request. |
266
267_`options` extends [method options](#method-options)._
268
269#### customRequest
270
271Custom requests can be made to the attached host by calling `customRequest`. Custom requests provide the boilerplate authentication and other request options used internally within the client.
272
273```typescript
274const resp: Response = await this.client.customRequest("/alrighty.jpg", {
275 method: "PROPFIND",
276 headers: {
277 Accept: "text/plain",
278 Depth: "0"
279 },
280 responseType: "text"
281});
282const result: DAVResult = await parseXML(resp.data);
283const stat: FileStat = parseStat(result, "/alrighty.jpg", false);
284```
285
286```typescript
287(path: string, requestOptions: RequestOptionsCustom) => Promise<Response>
288```
289
290| Argument | Required | Description |
291|-------------------|-----------|-----------------------------------------------|
292| `path` | Yes | The path to make a custom request against. |
293| `requestOptions` | Yes | Request options - required parameters such as `url`, `method` etc. - Refer to the `RequestOptionsCustom` type definition. |
294
295_The request options parameter **does not** extend [method options](#method-options) as things like `headers` can already be specified._
296
297#### deleteFile
298
299Delete a remote file.
300
301```typescript
302await client.deleteFile("/tmp.dat");
303```
304
305```typescript
306(filename: string, options?: WebDAVMethodOptions) => Promise<void>
307```
308
309| Argument | Required | Description |
310|-------------------|-----------|-----------------------------------------------|
311| `filename` | Yes | The file to delete. |
312| `options` | No | [Method options](#method-options). |
313
314#### exists
315
316Check if a file or directory exists.
317
318```typescript
319if (await client.exists("/some/path") === false) {
320 await client.createDirectory("/some/path");
321}
322```
323
324```typescript
325(path: string, options?: WebDAVMethodOptions) => Promise<boolean>
326```
327
328| Argument | Required | Description |
329|-------------------|-----------|-----------------------------------------------|
330| `path` | Yes | The remote path to check. |
331| `options` | No | [Method options](#method-options). |
332
333#### getDirectoryContents
334
335Get the contents of a remote directory. Returns an array of [item stats](#item-stats).
336
337```typescript
338// Get current directory contents:
339const contents = await client.getDirectoryContents("/");
340// Get all contents:
341const contents = await client.getDirectoryContents("/", { deep: true });
342```
343
344Files can be globbed using the `glob` option (processed using [`minimatch`](https://github.com/isaacs/minimatch)). When using a glob pattern it is recommended to fetch `deep` contents:
345
346```typescript
347const images = await client.getDirectoryContents("/", { deep: true, glob: "/**/*.{png,jpg,gif}" });
348```
349
350```typescript
351(path: string, options?: GetDirectoryContentsOptions) => Promise<Array<FileStat> | ResponseDataDetailed<Array<FileStat>>>
352```
353
354| Argument | Required | Description |
355|-------------------|-----------|-----------------------------------------------|
356| `path` | Yes | The path to fetch the contents of. |
357| `options` | No | Configuration options. |
358| `options.deep` | No | Fetch deep results (recursive). Defaults to `false`. |
359| `options.details` | No | Fetch detailed results (item stats, headers). Defaults to `false`. |
360| `options.glob` | No | Glob string for matching filenames. Not set by default. |
361
362_`options` extends [method options](#method-options)._
363
364#### getFileContents
365
366Fetch the contents of a remote file. Binary contents are returned by default (`Buffer`):
367
368```typescript
369const buff: Buffer = await client.getFileContents("/package.zip");
370```
371
372_It is recommended to use streams if the files being transferred are large._
373
374Text files can also be fetched:
375
376```typescript
377const str: string = await client.getFileContents("/config.json", { format: "text" });
378```
379
380Specify the `maxContentLength` option to alter the maximum number of bytes the client can receive in the request (**NodeJS only**).
381
382```typescript
383(filename: string, options?: GetFileContentsOptions) => Promise<BufferLike | string | ResponseDataDetailed<BufferLike | string>>
384```
385
386| Argument | Required | Description |
387|-------------------|-----------|-----------------------------------------------|
388| `filename` | Yes | The file to fetch the contents of. |
389| `options` | No | Configuration options. |
390| `options.details` | No | Fetch detailed results (additional headers). Defaults to `false`. |
391| `options.format` | No | Whether to fetch binary ("binary") data or textual ("text"). Defaults to "binary". |
392
393_`options` extends [method options](#method-options)._
394
395#### getFileDownloadLink
396
397Generate a public link where a file can be downloaded. This method is synchronous. **Exposes authentication details in the URL**.
398
399_Not all servers may support this feature. Only Basic authentication and unauthenticated connections support this method._
400
401```typescript
402const downloadLink: string = client.getFileDownloadLink("/image.png");
403```
404
405```typescript
406(filename: string) => string
407```
408
409| Argument | Required | Description |
410|-------------------|-----------|-----------------------------------------------|
411| `filename` | Yes | The remote file to generate a download link for. |
412
413#### getFileUploadLink
414
415Generate a URL for a file upload. This method is synchronous. **Exposes authentication details in the URL**.
416
417```typescript
418const uploadLink: string = client.getFileUploadLink("/image.png");
419```
420
421```typescript
422(filename: string) => string
423```
424
425| Argument | Required | Description |
426|-------------------|-----------|-----------------------------------------------|
427| `filename` | Yes | The remote file to generate an upload link for. |
428
429#### getQuota
430
431Get the quota information for the current account:
432
433```typescript
434const quota: DiskQuota = await client.getQuota();
435// {
436// "used": 1938743,
437// "available": "unlimited"
438// }
439```
440
441```typescript
442(options?: GetQuotaOptions) => Promise<DiskQuota | null | ResponseDataDetailed<DiskQuota | null>>
443```
444
445| Argument | Required | Description |
446|-------------------|-----------|-----------------------------------------------|
447| `options` | No | Configuration options. |
448| `options.details` | No | Return detailed results (headers etc.). Defaults to `false`. |
449
450_`options` extends [method options](#method-options)._
451
452#### moveFile
453
454Move a file to another location.
455
456```typescript
457await client.moveFile("/file1.png", "/file2.png");
458```
459
460```typescript
461(filename: string, destinationFilename: string, options?: WebDAVMethodOptions) => Promise<void>
462```
463
464| Argument | Required | Description |
465|-------------------|-----------|-----------------------------------------------|
466| `filename` | Yes | File to move. |
467| `destinationFilename` | Yes | Destination filename. |
468| `options` | No | [Method options](#method-options). |
469
470#### putFileContents
471
472Write data to a remote file. Returns `false` when file was not written (eg. `{ overwrite: false }` and file exists), and `true` otherwise.
473
474```typescript
475// Write a buffer:
476await client.putFileContents("/my/file.jpg", imageBuffer, { overwrite: false });
477// Write a text file:
478await client.putFileContents("/my/file.txt", str);
479```
480
481Specify the `maxBodyLength` option to alter the maximum number of bytes the client can send in the request (**NodeJS only**). When using `{ overwrite: false }`, responses with status `412` are caught and no error is thrown.
482
483Handling Upload Progress (browsers only):
484*This uses the axios onUploadProgress callback which uses the native XMLHttpRequest [progress event](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequestEventTarget/onprogress).*
485
486```typescript
487// Upload a file and log the progress to the console:
488await client.putFileContents("/my/file.jpg", imageFile, { onUploadProgress: progress => {
489 console.log(`Uploaded ${progress.loaded} bytes of ${progress.total}`);
490} });
491```
492
493```typescript
494(filename: string, data: string | BufferLike | Stream.Readable, options?: PutFileContentsOptions) => Promise<boolean>
495```
496
497| Argument | Required | Description |
498|-------------------|-----------|-----------------------------------------------|
499| `filename` | Yes | File to write to. |
500| `data` | Yes | The data to write. Can be a string, buffer or a readable stream. |
501| `options` | No | Configuration options. |
502| `options.contentLength` | No | Data content length override. Either a boolean (`true` (**default**) = calculate, `false` = don't set) or a number indicating the exact byte length of the file. |
503| `options.overwrite` | No | Whether or not to override the remote file if it exists. Defaults to `true`. |
504
505_`options` extends [method options](#method-options)._
506
507#### stat
508
509Get a file or directory stat object. Returns an [item stat](#item-stats).
510
511```typescript
512const stat: FileStat = await client.stat("/some/file.tar.gz");
513```
514
515```typescript
516(path: string, options?: StatOptions) => Promise<FileStat | ResponseDataDetailed<FileStat>>
517```
518
519| Argument | Required | Description |
520|-------------------|-----------|-----------------------------------------------|
521| `path` | Yes | Remote path to stat. |
522| `options` | No | Configuration options. |
523| `options.details` | No | Return detailed results (headers etc.). Defaults to `false`. |
524
525_`options` extends [method options](#method-options)._
526
527##### Custom properties
528
529For requests like `stat`, which use the `PROPFIND` method under the hood, it is possible to provide a custom request body to the method so that the server may respond with additional/different data. Overriding of the body can be performed by setting the `data` property in the [method options](#method-options).
530
531### Method options
532
533Most WebDAV methods extend `WebDAVMethodOptions`, which allow setting things like custom headers.
534
535| Option | Required | Description |
536|-------------------|-----------|-----------------------------------------------|
537| `data` | No | Optional body/data value to send in the request. This overrides the original body of the request, if applicable. |
538| `headers` | No | Optional headers object to apply to the request. These headers override all others, so be careful. |
539
540### Common data structures
541
542#### Item stats
543
544Item stats are objects with properties that descibe a file or directory. They resemble the following:
545
546```json
547{
548 "filename": "/test",
549 "basename": "test",
550 "lastmod": "Tue, 05 Apr 2016 14:39:18 GMT",
551 "size": 0,
552 "type": "directory",
553 "etag": null
554}
555```
556
557or:
558
559```json
560{
561 "filename": "/image.jpg",
562 "basename": "image.jpg",
563 "lastmod": "Sun, 13 Mar 2016 04:23:32 GMT",
564 "size": 42497,
565 "type": "file",
566 "mime": "image/jpeg",
567 "etag": "33a728c7f288ede1fecc90ac6a10e062"
568}
569```
570
571Properties:
572
573| Property name | Type | Present | Description |
574|---------------|---------|--------------|---------------------------------------------|
575| filename | String | Always | File path of the remote item |
576| basename | String | Always | Base filename of the remote item, no path |
577| lastmod | String | Always | Last modification date of the item |
578| size | Number | Always | File size - 0 for directories |
579| type | String | Always | Item type - "file" or "directory" |
580| mime | String | Files only | Mime type - for file items only |
581| etag | String / null | When supported | [ETag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) of the file |
582| props | Object | `details: true` | Props object containing all item properties returned by the server |
583
584
585#### Detailed responses
586
587Requests that return results, such as `getDirectoryContents`, `getFileContents`, `getQuota` and `stat`, can be configured to return more detailed information, such as response headers. Pass `{ details: true }` to their options argument to receive an object like the following:
588
589| Property | Type | Description |
590|--------------|-----------------|----------------------------------------|
591| data | * | The data returned by the procedure. Will be whatever type is returned by calling without `{ details: true }` |
592| headers | Object | The response headers. |
593| status | Number | The numeric status code. |
594| statusText | String | The status text. |
595
596### CORS
597CORS is a security enforcement technique employed by browsers to ensure requests are executed to and from expected contexts. It can conflict with this library if the target server doesn't return CORS headers when making requests from a browser. It is your responsibility to handle this.
598
599It is a known issue that ownCloud and Nextcloud servers by default don't return friendly CORS headers, making working with this library within a browser context impossible. You can of course force the addition of CORS headers (Apache or Nginx configs) yourself, but do this at your own risk.