keeweb/app/scripts/storage/storage-webdav.js

233 lines
9.2 KiB
JavaScript
Raw Normal View History

2017-01-31 07:50:28 +01:00
const StorageBase = require('./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
2016-03-13 17:08:25 +01:00
needShowOpenConfig: function() {
return true;
},
2016-03-13 17:45:55 +01:00
getOpenConfig: function() {
return {
fields: [
{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:08:25 +01:00
},
2016-03-12 12:22:35 +01:00
2017-04-16 21:23:18 +02:00
getSettingsConfig: function() {
return {
fields: [
{ id: 'webdavSaveMethod', title: 'webdavSaveMethod', type: 'select',
value: this.appSettings.get('webdavSaveMethod') || 'default',
options: { default: 'webdavSaveMove', put: 'webdavSavePut' } }
]
};
},
applySetting: function(key, value) {
this.appSettings.set(key, value);
},
2016-03-12 17:49:52 +01:00
load: function(path, opts, callback) {
this._request({
op: 'Load',
method: 'GET',
path: path,
user: opts ? opts.user : null,
password: opts ? opts.password : null
2016-07-17 13:30:38 +02:00
}, callback ? (err, xhr, stat) => {
2016-03-12 17:49:52 +01:00
callback(err, xhr.response, stat);
} : null);
2016-03-12 12:22:35 +01:00
},
2016-03-12 17:49:52 +01:00
stat: function(path, opts, callback) {
this._request({
op: 'Stat',
method: 'HEAD',
path: path,
user: opts ? opts.user : null,
password: opts ? opts.password : null
2016-07-17 13:30:38 +02:00
}, callback ? (err, xhr, stat) => {
2016-03-12 17:49:52 +01:00
callback(err, stat);
} : null);
2016-03-12 12:22:35 +01:00
},
2016-03-12 17:49:52 +01:00
save: function(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 = {
2016-03-12 17:49:52 +01:00
path: path,
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;
2016-03-13 09:34:36 +01:00
this._request(_.defaults({
op: 'Save:stat', method: 'HEAD'
2016-07-17 13:30:38 +02:00
}, saveOpts), (err, xhr, stat) => {
2016-03-13 09:34:36 +01:00
if (err) { return cb(err); }
if (stat.rev !== rev) {
2016-03-27 09:06:23 +02:00
that.logger.debug('Save error', path, 'rev conflict', stat.rev, rev);
2016-03-13 09:34:36 +01:00
return cb({ revConflict: true }, xhr, stat);
}
2017-04-16 21:23:18 +02:00
const useTmpPath = this.appSettings.get('webdavSaveMethod') !== 'put';
if (useTmpPath) {
2016-03-13 09:34:36 +01:00
that._request(_.defaults({
2017-04-16 21:23:18 +02:00
op: 'Save:put', method: 'PUT', path: tmpPath, data: data, nostat: true
}, saveOpts), (err) => {
if (err) { return cb(err); }
2016-03-13 09:34:36 +01:00
that._request(_.defaults({
2017-04-16 21:23:18 +02:00
op: 'Save:stat', method: 'HEAD'
}, saveOpts), (err, xhr, stat) => {
if (err) {
that._request(_.defaults({op: 'Save:delete', method: 'DELETE', path: tmpPath}, saveOpts));
return cb(err, xhr, stat);
}
if (stat.rev !== rev) {
that.logger.debug('Save error', path, 'rev conflict', stat.rev, rev);
that._request(_.defaults({op: 'Save:delete', method: 'DELETE', path: tmpPath}, saveOpts));
return cb({revConflict: true}, xhr, stat);
}
let movePath = path;
if (movePath.indexOf('://') < 0) {
if (movePath.indexOf('/') === 0) {
movePath = location.protocol + '//' + location.host + movePath;
} else {
movePath = location.href.replace(/\?(.*)/, '').replace(/[^/]*$/, movePath);
}
}
2016-03-13 09:34:36 +01:00
that._request(_.defaults({
2017-04-16 21:23:18 +02:00
op: 'Save:move', method: 'MOVE', path: tmpPath, nostat: true,
headers: {Destination: movePath, 'Overwrite': 'T'}
}, saveOpts), (err) => {
if (err) { return cb(err); }
that._request(_.defaults({
op: 'Save:stat', method: 'HEAD'
}, saveOpts), (err, xhr, stat) => {
cb(err, xhr, stat);
});
2016-03-13 09:34:36 +01:00
});
});
});
2017-04-16 21:23:18 +02:00
} else {
that._request(_.defaults({
op: 'Save:put', method: 'PUT', data: data, nostat: true
}, saveOpts), (err) => {
if (err) { return cb(err); }
that._request(_.defaults({
op: 'Save:stat', method: 'HEAD'
}, saveOpts), (err, xhr, stat) => {
cb(err, xhr, stat);
});
});
}
2016-03-13 09:34:36 +01:00
});
2016-03-12 17:49:52 +01:00
},
2016-03-19 13:43:50 +01:00
fileOptsToStoreOpts: function(opts, file) {
2017-01-31 07:50:28 +01: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++) {
2016-03-19 13:43:50 +01:00
encpass += String.fromCharCode(password.charCodeAt(i) ^ fileId.charCodeAt(i % fileId.length));
}
result.encpass = btoa(encpass);
}
return result;
},
storeOptsToFileOpts: function(opts, file) {
2017-01-31 07:50:28 +01: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++) {
2016-03-19 13:43:50 +01:00
password += String.fromCharCode(encpass.charCodeAt(i) ^ fileId.charCodeAt(i % fileId.length));
}
2016-03-23 18:15:39 +01:00
result.password = password;
2016-03-19 13:43:50 +01:00
}
return result;
},
2016-03-12 17:49:52 +01:00
_request: function(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) {
2016-03-27 09:06:23 +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;
}
2016-03-13 09:34:36 +01: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) {
2016-03-27 09:06:23 +02:00
that.logger.debug(config.op + ' error', config.path, 'no headers', that.logger.ts(ts));
2016-03-13 09:34:36 +01:00
if (callback) { callback('No Last-Modified header', xhr); callback = null; }
2016-03-12 17:49:52 +01:00
return;
}
2017-01-31 07:50:28 +01: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));
2016-03-12 17:49:52 +01:00
if (callback) { callback(null, xhr, rev ? { rev: rev } : null); callback = null; }
});
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));
2016-03-13 09:34:36 +01: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));
2016-03-13 09:34:36 +01: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) {
xhr.setRequestHeader('Authorization', 'Basic ' + btoa(config.user + ':' + config.password));
}
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
}
if (['GET', 'HEAD'].indexOf(config.method) >= 0) {
xhr.setRequestHeader('Cache-Control', 'no-cache');
}
2016-03-12 17:49:52 +01:00
if (config.data) {
2017-01-31 07:50:28 +01: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
2016-03-27 09:46:43 +02:00
module.exports = new StorageWebDav();