5.6.0 • Published 8 days ago

webdav v5.6.0

Weekly downloads
15,774
License
MIT
Repository
github
Last release
8 days ago

WebDAV

A WebDAV client, written in Typescript, for NodeJS and the browser

build status npm version monthly downloads total downloads Dependents (via libraries.io)

About

WebDAV 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 Nextcloud/ownCloud, Box and Yandex use it as a fallback to their primary interfaces.

This 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.

This 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.

Supported Versions / Environments

Version 5 is under active development. Version 4 is in support mode, and will receive security and stability related bugfixes. Earlier versions are deprecated and will not receive updates.

Version 5 upgrades the library to use ESM (ECMAScript Modules), and so your environment must fit one of the following formats to be able to use this library:

  • NodeJS project with "type": "module" in package.json (ESM mode)
  • Web project bundled with a tool like Webpack that can handle ESM
  • React-native projects (via direct import or using automatic react-native entry)

If you're not ready to upgrade, you may consider using version 4 of this library.

Requests

This library uses @buttercup/fetch to make requests in a cross-platform manner. It uses the browser's native fetch if present, or a polyfill if not. In Node and other environments it uses node-fetch.

Versions before v5 used Axios for requests.

Node support

Support table:

Library Major VersionNode JS Range
v514+
v410-18
v310-16
v26-14
v14-12

Browser support

Browser environments are supported from version 3 onwards of this library.

As mentioned above, v5 introduced ESM and this may require additional configuration when bundling for the browser.

Although you may choose to transpile this library's default entry point (NodeJS) yourself, it is not advised - use the dedicated web version instead.

In version 4 you had to use a different entry-point for the web version, and while this is still possible to use in version 5, you no longer need to:

import { createClient } from "webdav/web";

// or

import { createClient } from "webdav";

// both work fine in supported bundlers

Versions 3/4 supported a UMD-style module in the browser, but this is no longer supported in version 5. Version 5 provides only an ESM-enabled bundle that can be imported into other ESM-supporting projects.

NB: Streams are not available within the browser, so createReadStream and createWriteStream are just stubbed. Calling them will throw an exception.

React-Native support

React-Native is better supported as of version 5.4.0, using a specific build for the platform. The import should be automatic, but can be forced by importing from /react-native directly:

import { createClient } from "webdav/react-native";

Types

Typescript types are exported with this library for the Node build. All of the types can also be directly imported from the module:

import { AuthType, createClient } from "webdav";

const client = createClient("https://some-server.org", {
    authType: AuthType.Digest,
    username: "user",
    password: "pass"
});

Installation

Simple install as a dependency using npm:

npm install webdav --save

Usage

Usage entails creating a client adapter instance by calling the factory function createClient:

const { createClient } = require("webdav");

const client = createClient(
    "https://webdav.example.com/marie123",
    {
        username: "marie",
        password: "myS3curePa$$w0rd"
    }
);

// Get directory contents
const directoryItems = await client.getDirectoryContents("/");
// Outputs a structure like:
// [{
//     filename: "/my-file.txt",
//     basename: "my-file.txt",
//     lastmod: "Mon, 10 Oct 2018 23:24:11 GMT",
//     size: 371,
//     type: "file"
// }]

Authentication & Connection

The 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.

Setting the authType will automatically manage the Authorization header when connecting.

You can set the authType to AuthType.Auto if you're unsure whether the remote server requires digest or password based authentication.

Basic/no authentication

You can use the client without authentication if the server doesn't require it - simply avoid passing any values to username, password in the config.

To use basic authentication, simply pass a username and password in the config.

This 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 and https.Agent respectively.

OAuth tokens

To use a token to authenticate, pass the token data to the token field and specify the authType:

createClient(
    "https://address.com",
    {
        authType: AuthType.Token,
        token: {
            access_token: "2YotnFZFEjr1zCsicMWpAA",
            token_type: "example",
            expires_in: 3600,
            refresh_token: "tGzv3JOkF0XG5Qx2TlKWIA",
            example_parameter: "example_value"
        }
    }
);

You can also provide the HA1 (see here for details) yourself. This enables you to generate the HA1 at the time the user is logged in and persist it, so you do not have persist the password itself.

createClient("https://address.com", {
    authType: AuthType.Digest,
    username: "someUser",
    password: "",
    ha1: "your previously generated ha1 here"
});

Digest authentication

If a server requires digest-based authentication, you can enable this functionality by the authType configuration parameter, as well as providing a username and password:

createClient(
    "https://address.com",
    {
        authType: AuthType.Digest,
        username: "someUser",
        password: "myS3curePa$$w0rd"
    }
);

Client configuration

The createClient method takes a WebDAV service URL, and a configuration options parameter.

The available configuration options are as follows:

OptionDefaultDescription
authTypenullThe authentication type to use. If not provided, defaults to trying to detect based upon whether username and password were provided.
contactHrefThis URLContact URL used for LOCKs.
headers{}Additional headers provided to all requests. Headers provided here are overridden by method-specific headers, including Authorization.
httpAgentNoneHTTP agent instance. Available only in Node. See http.Agent.
httpsAgentNoneHTTPS agent instance. Available only in Node. See https.Agent.
passwordNonePassword for authentication.
tokenNoneToken object for authentication.
usernameNoneUsername for authentication.
withCredentialsNoneCredentials inclusion setting for the request,

Client methods

The WebDAVClient interface type contains all the methods and signatures for the WebDAV client instance.

copyFile

Copy a file from one location to another.

await client.copyFile(
    "/images/test.jpg",
    "/public/img/test.jpg"
);
(filename: string, destination: string, options?: WebDAVMethodOptions) => Promise<void>
ArgumentRequiredDescription
filenameYesThe source filename.
destinationYesThe destination filename.
optionsNoMethod options.

createDirectory

Create a new directory.

await client.createDirectory("/data/system/storage");
(path: string, options?: CreateDirectoryOptions) => Promise<void>
ArgumentRequiredDescription
pathYesThe path to create.
optionsNoCreate directory options.
options.recursiveNoRecursively create directories if they do not exist.

options extends method options.

Recursive creation

Recursive 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.

For 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).

createReadStream

Synchronously create a readable stream for a remote file.

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.

client
    .createReadStream("/video.mp4")
    .pipe(fs.createWriteStream("~/video.mp4"));

If you want to stream only part of the file, you can specify the range in the options argument:

client
    .createReadStream(
        "/video.mp4", 
        { range: { start: 0, end: 1024 } }
    ).pipe(fs.createWriteStream("~/video.mp4"));
(filename: string, options?: CreateReadStreamOptions) => Stream.Readable
ArgumentRequiredDescription
callbackNoCallback to fire with the response of the request.
filenameYesThe remote file to stream.
optionsNoRead stream options.
options.rangeNoStream range configuration.
options.range.startYesByte-position for the start of the stream.
options.range.endNoByte-position for the end of the stream.

options extends method options.

createWriteStream

Create a write stream targeted at a remote file.

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.

fs
    .createReadStream("~/Music/song.mp3")
    .pipe(client.createWriteStream("/music/song.mp3"));
(filename: string, options?: CreateWriteStreamOptions, callback?: CreateWriteStreamCallback) => Stream.Writable
ArgumentRequiredDescription
filenameYesThe remote file to stream.
optionsNoWrite stream options.
options.overwriteNoWhether or not to overwrite the remote file if it already exists. Defaults to true.
callbackNoCallback to fire once the connection has been made and streaming has started. Callback is called with the response of the request.

options extends method options.

customRequest

Custom 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.

const resp: Response = await this.client.customRequest("/alrighty.jpg", {
    method: "PROPFIND",
    headers: {
        Accept: "text/plain",
        Depth: "0"
    }
});
const result: DAVResult = await parseXML(await resp.text());
const stat: FileStat = parseStat(result, "/alrighty.jpg", false);
(path: string, requestOptions: RequestOptionsCustom) => Promise<Response>
ArgumentRequiredDescription
pathYesThe path to make a custom request against.
requestOptionsYesRequest options - required parameters such as url, method etc. - Refer to the RequestOptionsCustom type definition.

The request options parameter does not extend method options as things like headers can already be specified.

deleteFile

Delete a remote file.

await client.deleteFile("/tmp.dat");
(filename: string, options?: WebDAVMethodOptions) => Promise<void>
ArgumentRequiredDescription
filenameYesThe file to delete.
optionsNoMethod options.

exists

Check if a file or directory exists.

if (await client.exists("/some/path") === false) {
    await client.createDirectory("/some/path");
}
(path: string, options?: WebDAVMethodOptions) => Promise<boolean>
ArgumentRequiredDescription
pathYesThe remote path to check.
optionsNoMethod options.

getDirectoryContents

Get the contents of a remote directory. Returns an array of item stats.

// Get current directory contents:
const contents = await client.getDirectoryContents("/");
// Get all contents:
const contents = await client.getDirectoryContents("/", { deep: true });

Files can be globbed using the glob option (processed using minimatch). When using a glob pattern it is recommended to fetch deep contents:

const images = await client.getDirectoryContents("/", { deep: true, glob: "/**/*.{png,jpg,gif}" });
(path: string, options?: GetDirectoryContentsOptions) => Promise<Array<FileStat> | ResponseDataDetailed<Array<FileStat>>>
ArgumentRequiredDescription
pathYesThe path to fetch the contents of.
optionsNoConfiguration options.
options.deepNoFetch deep results (recursive). Defaults to false.
options.detailsNoFetch detailed results (item stats, headers). Defaults to false.
options.globNoGlob string for matching filenames. Not set by default.

options extends method options.

getFileContents

Fetch the contents of a remote file. Binary contents are returned by default (Buffer):

const buff: Buffer = await client.getFileContents("/package.zip");

It is recommended to use streams if the files being transferred are large.

Text files can also be fetched:

const str: string = await client.getFileContents("/config.json", { format: "text" });
(filename: string, options?: GetFileContentsOptions) => Promise<BufferLike | string | ResponseDataDetailed<BufferLike | string>>
ArgumentRequiredDescription
filenameYesThe file to fetch the contents of.
optionsNoConfiguration options.
options.detailsNoFetch detailed results (additional headers). Defaults to false.
options.formatNoWhether to fetch binary ("binary") data or textual ("text"). Defaults to "binary".

options extends method options.

getFileDownloadLink

Generate a public link where a file can be downloaded. This method is synchronous. Exposes authentication details in the URL.

Not all servers may support this feature. Only Basic authentication and unauthenticated connections support this method.

const downloadLink: string = client.getFileDownloadLink("/image.png");
(filename: string) => string
ArgumentRequiredDescription
filenameYesThe remote file to generate a download link for.

getFileUploadLink

Generate a URL for a file upload. This method is synchronous. Exposes authentication details in the URL.

const uploadLink: string = client.getFileUploadLink("/image.png");
(filename: string) => string
ArgumentRequiredDescription
filenameYesThe remote file to generate an upload link for.

getQuota

Get the quota information for the current account:

const quota: DiskQuota = await client.getQuota();
// {
//     "used": 1938743,
//     "available": "unlimited"
// }
(options?: GetQuotaOptions) => Promise<DiskQuota | null | ResponseDataDetailed<DiskQuota | null>>
ArgumentRequiredDescription
optionsNoConfiguration options.
options.detailsNoReturn detailed results (headers etc.). Defaults to false.
options.pathNoPath used to make the quota request.

options extends method options.

lock

Lock a remote resource (using a write lock).

const lock = await client.lock("/file.doc");

// Later
await client.unlock("/file.doc", lock.token);
(path: string, options?: LockOptions) => Promise<LockResponse>
ArgumentRequiredDescription
pathYesThe path to lock.
optionsNoConfiguration options.
options.timeoutNoWebDAV lock requested timeout. See the WebDAV Timeout header documentation.
options.refreshTokenNoPrevious valid lock token that should be refreshed.

options extends method options.

moveFile

Move a file to another location.

await client.moveFile("/file1.png", "/file2.png");
(filename: string, destinationFilename: string, options?: WebDAVMethodOptions) => Promise<void>
ArgumentRequiredDescription
filenameYesFile to move.
destinationFilenameYesDestination filename.
optionsNoMethod options.

putFileContents

Write data to a remote file. Returns false when file was not written (eg. { overwrite: false } and file exists), and true otherwise.

// Write a buffer:
await client.putFileContents("/my/file.jpg", imageBuffer, { overwrite: false });
// Write a text file:
await client.putFileContents("/my/file.txt", str);
(filename: string, data: string | BufferLike | Stream.Readable, options?: PutFileContentsOptions) => Promise<boolean>
ArgumentRequiredDescription
filenameYesFile to write to.
dataYesThe data to write. Can be a string, buffer or a readable stream.
optionsNoConfiguration options.
options.contentLengthNoData content length override. Either a boolean (true (default) = calculate, false = don't set) or a number indicating the exact byte length of the file.
options.overwriteNoWhether or not to override the remote file if it exists. Defaults to true.

options extends method options.

partialUpdateFileContents

Update a remote file with a partial update. This method is useful for updating a file without having to download and re-upload the entire file.

Note that this method is not standardised and may not be supported by all servers. To use this feature, one of the following must be met:

(filePath: string, start: number, end: number, data: string | BufferLike | Stream.Readable, options?: WebDAVMethodOptions)=> Promise<void>
ArgumentRequiredDescription
filePathYesFile to update.
startYesStart byte position. (inclusive)
endYesEnd byte position. (inclusive)
dataYesThe data to write. Can be a string, buffer or a readable stream.
optionsNoConfiguration options.

search

Perform a WebDAV search as per rfc5323.

const searchRequest = `
<?xml version="1.0" encoding="UTF-8"?>
<d:searchrequest xmlns:d="DAV:" xmlns:f="http://example.com/foo">
    <f:natural-language-query>
    Find files changed last week
    </f:natural-language-query>
</d:searchrequest>
`
const result: SearchResult = await client.search("/some-collection", { data: searchRequest });
(path: string, options?: SearchOptions) => Promise<SearchResult | ResponseDataDetailed<SearchResult>>
ArgumentRequiredDescription
pathYesRemote path to which executes the search.
optionsNoConfiguration options.
options.detailsNoReturn detailed results (headers etc.). Defaults to false.

options extends method options.

stat

Get a file or directory stat object. Returns an item stat.

const stat: FileStat = await client.stat("/some/file.tar.gz");
(path: string, options?: StatOptions) => Promise<FileStat | ResponseDataDetailed<FileStat>>
ArgumentRequiredDescription
pathYesRemote path to stat.
optionsNoConfiguration options.
options.detailsNoReturn detailed results (headers etc.). Defaults to false.

options extends method options.

unlock

Unlock a locked resource using a token.

await client.unlock("/file.doc", lock.token);
(path: string, token:string, options?: WebDAVMethodOptions) => Promise<void>
ArgumentRequiredDescription
pathYesRemote path to unlock.
tokenYesToken string from a previous lock request.
optionsNoConfiguration options.

options extends method options.

Custom properties

For 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

Most WebDAV methods extend WebDAVMethodOptions, which allow setting things like custom headers.

OptionRequiredDescription
dataNoOptional body/data value to send in the request. This overrides the original body of the request, if applicable.
headersNoOptional headers object to apply to the request. These headers override all others, so be careful.
signalNoInstance of AbortSignal, for aborting requests.

Common data structures

Item stats

Item stats are objects with properties that descibe a file or directory. They resemble the following:

{
    "filename": "/test",
    "basename": "test",
    "lastmod": "Tue, 05 Apr 2016 14:39:18 GMT",
    "size": 0,
    "type": "directory",
    "etag": null
}

or:

{
    "filename": "/image.jpg",
    "basename": "image.jpg",
    "lastmod": "Sun, 13 Mar 2016 04:23:32 GMT",
    "size": 42497,
    "type": "file",
    "mime": "image/jpeg",
    "etag": "33a728c7f288ede1fecc90ac6a10e062"
}

Properties:

Property nameTypePresentDescription
filenameStringAlwaysFile path of the remote item
basenameStringAlwaysBase filename of the remote item, no path
lastmodStringAlwaysLast modification date of the item
sizeNumberAlwaysFile size - 0 for directories
typeStringAlwaysItem type - "file" or "directory"
mimeStringFiles onlyMime type - for file items only
etagString / nullWhen supportedETag of the file
propsObjectdetails: trueProps object containing all item properties returned by the server

Detailed responses

Requests that return results, such as getDirectoryContents, getFileContents, getQuota, search 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:

PropertyTypeDescription
data*The data returned by the procedure. Will be whatever type is returned by calling without { details: true }
headersObjectThe response headers.
statusNumberThe numeric status code.
statusTextStringThe status text.

CORS

CORS 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.

It is a known issue that 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.

Projects using this WebDAV client

5.6.0

8 days ago

5.5.1-2

10 days ago

5.6.0-1

10 days ago

5.6.0-2

10 days ago

5.6.0-3

10 days ago

5.6.0-4

10 days ago

5.6.0-5

10 days ago

5.5.1-1

11 days ago

5.5.0

1 month ago

5.4.0

2 months ago

5.3.2

3 months ago

5.3.1

5 months ago

5.3.0

8 months ago

5.1.0

11 months ago

5.2.3

10 months ago

5.2.2

10 months ago

5.2.1

11 months ago

5.2.0

11 months ago

4.11.3

8 months ago

5.0.0

12 months ago

5.0.0-r4

1 year ago

5.0.0-r3

1 year ago

5.0.0-r2

1 year ago

5.0.0-r1

1 year ago

4.11.1

1 year ago

4.11.2

1 year ago

4.11.0

2 years ago

4.10.0

2 years ago

4.9.0

2 years ago

4.8.0

2 years ago

4.7.0

3 years ago

4.6.1

3 years ago

4.4.0

3 years ago

4.6.0

3 years ago

4.5.0

3 years ago

4.3.0

3 years ago

4.2.1

3 years ago

4.2.0

3 years ago

4.1.0

3 years ago

4.0.0

3 years ago

4.0.0-r01

3 years ago

3.6.2

3 years ago

3.6.1

4 years ago

3.6.0

4 years ago

3.5.0

4 years ago

3.4.0

4 years ago

3.3.0

4 years ago

3.2.0

4 years ago

3.1.0

4 years ago

3.0.0

4 years ago

2.10.2

4 years ago

2.10.1

4 years ago

2.10.0

5 years ago

2.9.1

5 years ago

2.9.0

5 years ago

2.8.0

5 years ago

2.7.0

5 years ago

2.6.0

5 years ago

2.5.0

5 years ago

2.4.0

5 years ago

2.3.0

5 years ago

2.2.1

5 years ago

2.2.0

5 years ago

2.1.0

5 years ago

2.0.0

5 years ago

2.0.0-rc1

5 years ago

1.6.1

6 years ago

1.6.0

6 years ago

1.5.5

6 years ago

1.5.4

6 years ago

1.5.3

6 years ago

1.5.2

6 years ago

1.5.1

6 years ago

1.5.0

6 years ago

1.4.0

6 years ago

1.3.0

6 years ago

1.2.1

6 years ago

1.2.0

6 years ago

1.1.2

6 years ago

1.1.1

6 years ago

1.1.0

7 years ago

1.0.1

7 years ago

1.0.0

7 years ago

1.0.0-rc1

7 years ago

0.10.0

7 years ago

0.9.0

7 years ago

0.8.0

7 years ago

0.7.0

7 years ago

0.6.0

7 years ago

0.5.0

7 years ago

0.4.1

7 years ago

0.4.0

7 years ago

0.3.1

7 years ago

0.3.0

7 years ago

0.2.0

7 years ago

0.1.1

8 years ago

0.1.0

8 years ago