17337: Added another edge case handling
[arvados.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, encodeHash } 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                 config.headers.Destination = encodeHash(config.headers.Destination);
84             }
85
86             const headers = { ...this.defaults.headers, ...config.headers };
87             Object
88                 .keys(headers)
89                 .forEach(key => r.setRequestHeader(key, headers[key]));
90
91             if (config.onUploadProgress) {
92                 r.upload.addEventListener('progress', config.onUploadProgress);
93             }
94
95             // This event gets triggered on *any* server response
96             r.addEventListener('load', () => {
97                 if (r.status >= 400) {
98                     return reject(r);
99                 } else {
100                     return resolve(r);
101                 }
102             });
103
104             // This event gets triggered on network errors
105             r.addEventListener('error', () => {
106                 return reject(r);
107             });
108
109             r.upload.addEventListener('error', () => {
110                 return reject(r);
111             });
112
113             r.send(config.data);
114         });
115     }
116 }
117
118 export interface WebDAVRequestConfig {
119     headers?: {
120         [key: string]: string;
121     };
122     onUploadProgress?: (event: ProgressEvent) => void;
123 }
124
125 interface WebDAVDefaults {
126     baseURL: string;
127     headers: { [key: string]: string };
128 }
129
130 interface RequestConfig {
131     method: string;
132     url: string;
133     headers?: { [key: string]: string };
134     data?: any;
135     onUploadProgress?: (event: ProgressEvent) => void;
136 }