2019-09-15 14:16:32 +02:00
|
|
|
import { StorageBase } from 'storage/storage-base';
|
2016-03-12 12:22:35 +01:00
|
|
|
|
2017-01-31 07:50:28 +01:00
|
|
|
const StorageWebDav = StorageBase.extend({
|
2016-03-12 12:22:35 +01:00
|
|
|
name: 'webdav',
|
|
|
|
icon: 'server',
|
|
|
|
enabled: true,
|
2016-03-13 17:08:25 +01:00
|
|
|
uipos: 10,
|
2016-03-12 12:22:35 +01:00
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
needShowOpenConfig() {
|
2016-03-13 17:08:25 +01:00
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
getOpenConfig() {
|
2016-03-13 17:45:55 +01:00
|
|
|
return {
|
|
|
|
fields: [
|
2019-08-16 23:05:39 +02:00
|
|
|
{ id: 'path', title: 'openUrl', desc: 'openUrlDesc', type: 'text', required: true },
|
|
|
|
{
|
|
|
|
id: 'user',
|
|
|
|
title: 'openUser',
|
|
|
|
desc: 'openUserDesc',
|
|
|
|
placeholder: 'openUserPlaceholder',
|
|
|
|
type: 'text'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 'password',
|
|
|
|
title: 'openPass',
|
|
|
|
desc: 'openPassDesc',
|
|
|
|
placeholder: 'openPassPlaceholder',
|
|
|
|
type: 'password'
|
|
|
|
}
|
2016-03-13 17:45:55 +01:00
|
|
|
]
|
|
|
|
};
|
2016-03-13 17:08:25 +01:00
|
|
|
},
|
2016-03-12 12:22:35 +01:00
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
getSettingsConfig() {
|
2017-04-16 21:23:18 +02:00
|
|
|
return {
|
|
|
|
fields: [
|
2019-08-16 23:05:39 +02:00
|
|
|
{
|
|
|
|
id: 'webdavSaveMethod',
|
|
|
|
title: 'webdavSaveMethod',
|
|
|
|
type: 'select',
|
2017-04-16 21:23:18 +02:00
|
|
|
value: this.appSettings.get('webdavSaveMethod') || 'default',
|
2019-08-16 23:05:39 +02:00
|
|
|
options: { default: 'webdavSaveMove', put: 'webdavSavePut' }
|
|
|
|
}
|
2017-04-16 21:23:18 +02:00
|
|
|
]
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
applySetting(key, value) {
|
2017-04-16 21:23:18 +02:00
|
|
|
this.appSettings.set(key, value);
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
load(path, opts, callback) {
|
2019-08-16 23:05:39 +02:00
|
|
|
this._request(
|
|
|
|
{
|
|
|
|
op: 'Load',
|
|
|
|
method: 'GET',
|
2019-08-18 10:17:09 +02:00
|
|
|
path,
|
2019-08-16 23:05:39 +02:00
|
|
|
user: opts ? opts.user : null,
|
|
|
|
password: opts ? opts.password : null
|
|
|
|
},
|
|
|
|
callback
|
|
|
|
? (err, xhr, stat) => {
|
|
|
|
callback(err, xhr.response, stat);
|
|
|
|
}
|
|
|
|
: null
|
|
|
|
);
|
2016-03-12 12:22:35 +01:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
stat(path, opts, callback) {
|
2019-08-16 23:05:39 +02:00
|
|
|
this._request(
|
|
|
|
{
|
|
|
|
op: 'Stat',
|
|
|
|
method: 'HEAD',
|
2019-08-18 10:17:09 +02:00
|
|
|
path,
|
2019-08-16 23:05:39 +02:00
|
|
|
user: opts ? opts.user : null,
|
|
|
|
password: opts ? opts.password : null
|
|
|
|
},
|
|
|
|
callback
|
|
|
|
? (err, xhr, stat) => {
|
|
|
|
callback(err, stat);
|
|
|
|
}
|
|
|
|
: null
|
|
|
|
);
|
2016-03-12 12:22:35 +01:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
save(path, opts, data, callback, rev) {
|
2017-01-31 07:50:28 +01:00
|
|
|
const cb = function(err, xhr, stat) {
|
2016-03-13 09:34:36 +01:00
|
|
|
if (callback) {
|
|
|
|
callback(err, stat);
|
|
|
|
callback = null;
|
|
|
|
}
|
|
|
|
};
|
2017-01-31 07:50:28 +01:00
|
|
|
const tmpPath = path.replace(/[^\/]+$/, m => '.' + m) + '.' + Date.now();
|
|
|
|
const saveOpts = {
|
2019-08-18 10:17:09 +02:00
|
|
|
path,
|
2016-03-12 17:49:52 +01:00
|
|
|
user: opts ? opts.user : null,
|
2016-03-13 09:34:36 +01:00
|
|
|
password: opts ? opts.password : null
|
2016-03-12 17:49:52 +01:00
|
|
|
};
|
2017-01-31 07:50:28 +01:00
|
|
|
const that = this;
|
2019-08-16 23:05:39 +02:00
|
|
|
this._request(
|
|
|
|
_.defaults(
|
|
|
|
{
|
|
|
|
op: 'Save:stat',
|
|
|
|
method: 'HEAD'
|
|
|
|
},
|
|
|
|
saveOpts
|
|
|
|
),
|
|
|
|
(err, xhr, stat) => {
|
|
|
|
let useTmpPath = this.appSettings.get('webdavSaveMethod') !== 'put';
|
|
|
|
if (err) {
|
|
|
|
if (!err.notFound) {
|
|
|
|
return cb(err);
|
|
|
|
} else {
|
|
|
|
that.logger.debug('Save: not found, creating');
|
|
|
|
useTmpPath = false;
|
|
|
|
}
|
|
|
|
} else if (stat.rev !== rev) {
|
|
|
|
that.logger.debug('Save error', path, 'rev conflict', stat.rev, rev);
|
|
|
|
return cb({ revConflict: true }, xhr, stat);
|
2017-11-27 22:11:00 +01:00
|
|
|
}
|
2019-08-16 23:05:39 +02:00
|
|
|
if (useTmpPath) {
|
|
|
|
that._request(
|
|
|
|
_.defaults(
|
|
|
|
{
|
|
|
|
op: 'Save:put',
|
|
|
|
method: 'PUT',
|
|
|
|
path: tmpPath,
|
2019-08-18 10:17:09 +02:00
|
|
|
data,
|
2019-08-16 23:05:39 +02:00
|
|
|
nostat: true
|
|
|
|
},
|
|
|
|
saveOpts
|
|
|
|
),
|
|
|
|
err => {
|
|
|
|
if (err) {
|
|
|
|
return cb(err);
|
|
|
|
}
|
|
|
|
that._request(
|
|
|
|
_.defaults(
|
|
|
|
{
|
|
|
|
op: 'Save:stat',
|
|
|
|
method: 'HEAD'
|
|
|
|
},
|
|
|
|
saveOpts
|
|
|
|
),
|
|
|
|
(err, xhr, stat) => {
|
|
|
|
if (err) {
|
|
|
|
that._request(
|
2019-08-18 08:05:38 +02:00
|
|
|
_.defaults(
|
|
|
|
{
|
|
|
|
op: 'Save:delete',
|
|
|
|
method: 'DELETE',
|
|
|
|
path: tmpPath
|
|
|
|
},
|
|
|
|
saveOpts
|
|
|
|
)
|
2019-08-16 23:05:39 +02:00
|
|
|
);
|
|
|
|
return cb(err, xhr, stat);
|
|
|
|
}
|
|
|
|
if (stat.rev !== rev) {
|
2019-08-18 08:05:38 +02:00
|
|
|
that.logger.debug(
|
|
|
|
'Save error',
|
|
|
|
path,
|
|
|
|
'rev conflict',
|
|
|
|
stat.rev,
|
|
|
|
rev
|
|
|
|
);
|
2019-08-16 23:05:39 +02:00
|
|
|
that._request(
|
2019-08-18 08:05:38 +02:00
|
|
|
_.defaults(
|
|
|
|
{
|
|
|
|
op: 'Save:delete',
|
|
|
|
method: 'DELETE',
|
|
|
|
path: tmpPath
|
|
|
|
},
|
|
|
|
saveOpts
|
|
|
|
)
|
2019-08-16 23:05:39 +02:00
|
|
|
);
|
|
|
|
return cb({ revConflict: true }, xhr, stat);
|
|
|
|
}
|
|
|
|
let movePath = path;
|
2019-09-12 20:09:45 +02:00
|
|
|
if (movePath.indexOf('://') < 0) {
|
|
|
|
if (movePath.indexOf('/') === 0) {
|
|
|
|
movePath =
|
|
|
|
location.protocol + '//' + location.host + movePath;
|
|
|
|
} else {
|
|
|
|
movePath = location.href
|
|
|
|
.replace(/\?(.*)/, '')
|
|
|
|
.replace(/[^/]*$/, movePath);
|
|
|
|
}
|
2019-08-16 23:05:39 +02:00
|
|
|
}
|
|
|
|
that._request(
|
|
|
|
_.defaults(
|
|
|
|
{
|
|
|
|
op: 'Save:move',
|
|
|
|
method: 'MOVE',
|
|
|
|
path: tmpPath,
|
|
|
|
nostat: true,
|
2019-08-24 07:47:13 +02:00
|
|
|
headers: {
|
|
|
|
Destination: encodeURI(movePath),
|
|
|
|
'Overwrite': 'T'
|
|
|
|
}
|
2019-08-16 23:05:39 +02:00
|
|
|
},
|
|
|
|
saveOpts
|
|
|
|
),
|
|
|
|
err => {
|
|
|
|
if (err) {
|
|
|
|
return cb(err);
|
|
|
|
}
|
|
|
|
that._request(
|
|
|
|
_.defaults(
|
|
|
|
{
|
|
|
|
op: 'Save:stat',
|
|
|
|
method: 'HEAD'
|
|
|
|
},
|
|
|
|
saveOpts
|
|
|
|
),
|
|
|
|
(err, xhr, stat) => {
|
|
|
|
cb(err, xhr, stat);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
2017-04-16 21:23:18 +02:00
|
|
|
}
|
2019-08-16 23:05:39 +02:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
that._request(
|
|
|
|
_.defaults(
|
|
|
|
{
|
|
|
|
op: 'Save:put',
|
|
|
|
method: 'PUT',
|
2019-08-18 10:17:09 +02:00
|
|
|
data,
|
2019-08-16 23:05:39 +02:00
|
|
|
nostat: true
|
|
|
|
},
|
|
|
|
saveOpts
|
|
|
|
),
|
|
|
|
err => {
|
|
|
|
if (err) {
|
|
|
|
return cb(err);
|
2017-04-16 21:23:18 +02:00
|
|
|
}
|
2019-08-16 23:05:39 +02:00
|
|
|
that._request(
|
|
|
|
_.defaults(
|
|
|
|
{
|
|
|
|
op: 'Save:stat',
|
|
|
|
method: 'HEAD'
|
|
|
|
},
|
|
|
|
saveOpts
|
|
|
|
),
|
|
|
|
(err, xhr, stat) => {
|
|
|
|
cb(err, xhr, stat);
|
|
|
|
}
|
|
|
|
);
|
2017-04-16 21:23:18 +02:00
|
|
|
}
|
2019-08-16 23:05:39 +02:00
|
|
|
);
|
|
|
|
}
|
2017-04-16 21:23:18 +02:00
|
|
|
}
|
2019-08-16 23:05:39 +02:00
|
|
|
);
|
2016-03-12 17:49:52 +01:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
fileOptsToStoreOpts(opts, file) {
|
2019-08-16 23:05:39 +02:00
|
|
|
const result = { user: opts.user, encpass: opts.encpass };
|
2016-03-19 13:43:50 +01:00
|
|
|
if (opts.password) {
|
2017-01-31 07:50:28 +01:00
|
|
|
const fileId = file.get('uuid');
|
|
|
|
const password = opts.password;
|
|
|
|
let encpass = '';
|
|
|
|
for (let i = 0; i < password.length; i++) {
|
2019-08-18 08:05:38 +02:00
|
|
|
encpass += String.fromCharCode(
|
|
|
|
password.charCodeAt(i) ^ fileId.charCodeAt(i % fileId.length)
|
|
|
|
);
|
2016-03-19 13:43:50 +01:00
|
|
|
}
|
|
|
|
result.encpass = btoa(encpass);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
storeOptsToFileOpts(opts, file) {
|
2019-08-16 23:05:39 +02:00
|
|
|
const result = { user: opts.user, password: opts.password };
|
2016-03-19 13:43:50 +01:00
|
|
|
if (opts.encpass) {
|
2017-01-31 07:50:28 +01:00
|
|
|
const fileId = file.get('uuid');
|
|
|
|
const encpass = atob(opts.encpass);
|
|
|
|
let password = '';
|
|
|
|
for (let i = 0; i < encpass.length; i++) {
|
2019-08-18 08:05:38 +02:00
|
|
|
password += String.fromCharCode(
|
|
|
|
encpass.charCodeAt(i) ^ fileId.charCodeAt(i % fileId.length)
|
|
|
|
);
|
2016-03-19 13:43:50 +01:00
|
|
|
}
|
2016-03-23 18:15:39 +01:00
|
|
|
result.password = password;
|
2016-03-19 13:43:50 +01:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
_request(config, callback) {
|
2017-01-31 07:50:28 +01:00
|
|
|
const that = this;
|
2016-03-13 09:34:36 +01:00
|
|
|
if (config.rev) {
|
2016-03-27 09:06:23 +02:00
|
|
|
that.logger.debug(config.op, config.path, config.rev);
|
2016-03-13 09:34:36 +01:00
|
|
|
} else {
|
2016-03-27 09:06:23 +02:00
|
|
|
that.logger.debug(config.op, config.path);
|
2016-03-13 09:34:36 +01:00
|
|
|
}
|
2017-01-31 07:50:28 +01:00
|
|
|
const ts = that.logger.ts();
|
|
|
|
const xhr = new XMLHttpRequest();
|
2016-07-17 13:30:38 +02:00
|
|
|
xhr.addEventListener('load', () => {
|
2016-03-13 09:34:36 +01:00
|
|
|
if ([200, 201, 204].indexOf(xhr.status) < 0) {
|
2019-08-18 08:05:38 +02:00
|
|
|
that.logger.debug(
|
|
|
|
config.op + ' error',
|
|
|
|
config.path,
|
|
|
|
xhr.status,
|
|
|
|
that.logger.ts(ts)
|
|
|
|
);
|
2017-01-31 07:50:28 +01:00
|
|
|
let err;
|
2016-03-12 17:49:52 +01:00
|
|
|
switch (xhr.status) {
|
|
|
|
case 404:
|
|
|
|
err = { notFound: true };
|
|
|
|
break;
|
|
|
|
case 412:
|
|
|
|
err = { revConflict: true };
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
err = 'HTTP status ' + xhr.status;
|
|
|
|
break;
|
|
|
|
}
|
2019-08-16 23:05:39 +02:00
|
|
|
if (callback) {
|
|
|
|
callback(err, xhr);
|
|
|
|
callback = null;
|
|
|
|
}
|
2016-03-12 17:49:52 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-01-31 07:50:28 +01:00
|
|
|
const rev = xhr.getResponseHeader('Last-Modified');
|
2016-03-13 09:34:36 +01:00
|
|
|
if (!rev && !config.nostat) {
|
2019-08-18 08:05:38 +02:00
|
|
|
that.logger.debug(
|
|
|
|
config.op + ' error',
|
|
|
|
config.path,
|
|
|
|
'no headers',
|
|
|
|
that.logger.ts(ts)
|
|
|
|
);
|
2019-08-16 23:05:39 +02:00
|
|
|
if (callback) {
|
|
|
|
callback('No Last-Modified header', xhr);
|
|
|
|
callback = null;
|
|
|
|
}
|
2016-03-12 17:49:52 +01:00
|
|
|
return;
|
|
|
|
}
|
2019-08-18 08:05:38 +02:00
|
|
|
const completedOpName =
|
|
|
|
config.op + (config.op.charAt(config.op.length - 1) === 'e' ? 'd' : 'ed');
|
2016-03-27 09:06:23 +02:00
|
|
|
that.logger.debug(completedOpName, config.path, rev, that.logger.ts(ts));
|
2019-08-16 23:05:39 +02:00
|
|
|
if (callback) {
|
2019-08-18 10:17:09 +02:00
|
|
|
callback(null, xhr, rev ? { rev } : null);
|
2019-08-16 23:05:39 +02:00
|
|
|
callback = null;
|
|
|
|
}
|
2016-03-12 17:49:52 +01:00
|
|
|
});
|
2016-07-17 13:30:38 +02:00
|
|
|
xhr.addEventListener('error', () => {
|
2016-03-27 09:06:23 +02:00
|
|
|
that.logger.debug(config.op + ' error', config.path, that.logger.ts(ts));
|
2019-08-16 23:05:39 +02:00
|
|
|
if (callback) {
|
|
|
|
callback('network error', xhr);
|
|
|
|
callback = null;
|
|
|
|
}
|
2016-03-12 17:49:52 +01:00
|
|
|
});
|
2016-07-17 13:30:38 +02:00
|
|
|
xhr.addEventListener('abort', () => {
|
2016-03-27 09:06:23 +02:00
|
|
|
that.logger.debug(config.op + ' error', config.path, 'aborted', that.logger.ts(ts));
|
2019-08-16 23:05:39 +02:00
|
|
|
if (callback) {
|
|
|
|
callback('aborted', xhr);
|
|
|
|
callback = null;
|
|
|
|
}
|
2016-03-12 17:49:52 +01:00
|
|
|
});
|
|
|
|
xhr.open(config.method, config.path);
|
2016-06-10 19:31:16 +02:00
|
|
|
xhr.responseType = 'arraybuffer';
|
2016-03-12 17:49:52 +01:00
|
|
|
if (config.user) {
|
2019-08-18 08:05:38 +02:00
|
|
|
xhr.setRequestHeader(
|
|
|
|
'Authorization',
|
|
|
|
'Basic ' + btoa(config.user + ':' + config.password)
|
|
|
|
);
|
2016-03-12 17:49:52 +01:00
|
|
|
}
|
2016-03-13 09:34:36 +01:00
|
|
|
if (config.headers) {
|
2016-07-17 13:30:38 +02:00
|
|
|
_.forEach(config.headers, (value, header) => {
|
2016-03-13 09:34:36 +01:00
|
|
|
xhr.setRequestHeader(header, value);
|
|
|
|
});
|
2016-03-12 17:49:52 +01:00
|
|
|
}
|
2016-09-11 20:40:34 +02:00
|
|
|
if (['GET', 'HEAD'].indexOf(config.method) >= 0) {
|
|
|
|
xhr.setRequestHeader('Cache-Control', 'no-cache');
|
|
|
|
}
|
2016-03-12 17:49:52 +01:00
|
|
|
if (config.data) {
|
2019-08-16 23:05:39 +02:00
|
|
|
const blob = new Blob([config.data], { type: 'application/octet-stream' });
|
2016-03-12 17:49:52 +01:00
|
|
|
xhr.send(blob);
|
|
|
|
} else {
|
|
|
|
xhr.send();
|
|
|
|
}
|
2016-03-12 12:22:35 +01:00
|
|
|
}
|
2016-03-27 09:06:23 +02:00
|
|
|
});
|
2016-03-12 12:22:35 +01:00
|
|
|
|
2019-09-15 14:16:32 +02:00
|
|
|
export { StorageWebDav };
|