17337: Added more tests to cover edge cases
[arvados-workbench2.git] / src / common / webdav.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { customEncodeURI } from "./url";
6
7 export class WebDAV {
8
9     defaults: WebDAVDefaults = {
10         baseURL: '',
11         headers: {},
12     };
13
14     constructor(config?: Partial<WebDAVDefaults>, private createRequest = () => new XMLHttpRequest()) {
15         if (config) {
16             this.defaults = { ...this.defaults, ...config };
17         }
18     }
19
20     propfind = (url: string, config: WebDAVRequestConfig = {}) =>
21         this.request({
22             ...config, url,
23             method: 'PROPFIND'
24         })
25
26     put = (url: string, data?: any, config: WebDAVRequestConfig = {}) =>
27         this.request({
28             ...config, url,
29             method: 'PUT',
30             data
31         })
32
33     upload = (url: string, files: File[], config: WebDAVRequestConfig = {}) => {
34         return Promise.all(
35             files.map(file => this.request({
36                 ...config, url,
37                 method: 'PUT',
38                 data: file
39             }))
40         );
41     }
42
43     copy = (url: string, destination: string, config: WebDAVRequestConfig = {}) =>
44         this.request({
45             ...config, url,
46             method: 'COPY',
47             headers: {
48                 ...config.headers,
49                 Destination: this.defaults.baseURL
50                     ? this.defaults.baseURL.replace(/\/+$/, '') + '/' + destination.replace(/^\/+/, '')
51                     : destination
52             }
53         })
54
55     move = (url: string, destination: string, config: WebDAVRequestConfig = {}) =>
56         this.request({
57             ...config, url,
58             method: 'MOVE',
59             headers: {
60                 ...config.headers,
61                 Destination: this.defaults.baseURL
62                     ? this.defaults.baseURL.replace(/\/+$/, '') + '/' + destination.replace(/^\/+/, '')
63                     : destination
64             }
65         })
66
67     delete = (url: string, config: WebDAVRequestConfig = {}) =>
68         this.request({
69             ...config, url,
70             method: 'DELETE'
71         })
72
73     private request = (config: RequestConfig) => {
74         return new Promise<XMLHttpRequest>((resolve, reject) => {
75             const r = this.createRequest();
76             this.defaults.baseURL = this.defaults.baseURL.replace(/\/+$/, '');
77             r.open(config.method,
78                 `${this.defaults.baseURL
79                     ? this.defaults.baseURL+'/'
80                     : ''}${customEncodeURI(config.url)}`);
81
82             if (config.headers && config.headers.Destination) {
83                 const regexp = /(http[s]?:\/\/)?([^\/\s]+\/)(.*)/;
84                 const match = decodeURIComponent(config.headers.Destination).match(regexp) || {};
85                 config.headers.Destination = `${match[1]}${match[2]}${customEncodeURI(match[3])}`;
86             }
87
88             const headers = { ...this.defaults.headers, ...config.headers };
89             Object
90                 .keys(headers)
91                 .forEach(key => r.setRequestHeader(key, headers[key]));
92
93             if (config.onUploadProgress) {
94                 r.upload.addEventListener('progress', config.onUploadProgress);
95             }
96
97             // This event gets triggered on *any* server response
98             r.addEventListener('load', () => {
99                 if (r.status >= 400) {
100                     return reject(r);
101                 } else {
102                     return resolve(r);
103                 }
104             });
105
106             // This event gets triggered on network errors
107             r.addEventListener('error', () => {
108                 return reject(r);
109             });
110
111             r.upload.addEventListener('error', () => {
112                 return reject(r);
113             });
114
115             r.send(config.data);
116         });
117     }
118 }
119
120 export interface WebDAVRequestConfig {
121     headers?: {
122         [key: string]: string;
123     };
124     onUploadProgress?: (event: ProgressEvent) => void;
125 }
126
127 interface WebDAVDefaults {
128     baseURL: string;
129     headers: { [key: string]: string };
130 }
131
132 interface RequestConfig {
133     method: string;
134     url: string;
135     headers?: { [key: string]: string };
136     data?: any;
137     onUploadProgress?: (event: ProgressEvent) => void;
138 }