diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index d7f22825d..26e574307 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -167,12 +167,19 @@ var archiveDocument = function (Env, Server, cb, data) { // Env.blobStore.archive.proof(userSafeKey, blobId, cb) }; -var restoreArchivedDocument = function (Env, Server, cb) { - // Env.msgStore.restoreArchivedChannel(channelName, cb) - // Env.blobStore.restore.blob(blobId, cb) - // Env.blobStore.restore.proof(userSafekey, blobId, cb) +var restoreArchivedDocument = function (Env, Server, cb, data) { + var id = Array.isArray(data) && data[1]; + if (typeof(id) !== 'string' || id.length < 32) { return void cb("EINVAL"); } - cb("NOT_IMPLEMENTED"); + switch (id.length) { + case 32: + return void Env.msgStore.restoreArchivedChannel(id, cb); + case 48: + // Env.blobStore.restore.proof(userSafekey, id, cb) // XXX .... + return void Env.blobStore.restore.blob(id, cb); + default: + return void cb("INVALID_ID_LENGTH"); + } }; // CryptPad_AsyncStore.rpc.send('ADMIN', ['CLEAR_CACHED_CHANNEL_INDEX', documentID], console.log) diff --git a/lib/storage/file.js b/lib/storage/file.js index b1ccfde0f..03bbaa8b4 100644 --- a/lib/storage/file.js +++ b/lib/storage/file.js @@ -681,9 +681,9 @@ var unarchiveChannel = function (env, channelName, cb) { // restore the metadata log Fse.move(archiveMetadataPath, metadataPath, w(function (err) { // if there's nothing to move, you're done. - if (err && err.code === 'ENOENT') { + /*if (err && err.code === 'ENOENT') { return CB(); - } + }*/ // XXX make sure removing this part won't break anything // call back with an error if something goes wrong if (err) { w.abort(); diff --git a/www/admin/app-admin.less b/www/admin/app-admin.less index 5112fd9e2..651e9dddd 100644 --- a/www/admin/app-admin.less +++ b/www/admin/app-admin.less @@ -97,5 +97,15 @@ color: @colortheme_logo-2; } } + + input.cp-admin-inval { + border-color: red !important; + } + .cp-admin-nopassword { + .cp-admin-pw { + display: none !important; + } + } + } diff --git a/www/admin/inner.js b/www/admin/inner.js index db5afca54..d9bebc097 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -43,6 +43,8 @@ define([ 'general': [ 'cp-admin-flush-cache', 'cp-admin-update-limit', + 'cp-admin-archive', + 'cp-admin-unarchive', // 'cp-admin-registration', ], 'quota': [ @@ -107,6 +109,141 @@ define([ }); return $div; }; + Messages.admin_archiveTitle = "Archive documents"; // XXX + Messages.admin_archiveHint = "Make a document unavailable without deleting it permanently. It will be placed in an 'archive' directory and deleted after a few days (configurable in the server configuration file)."; // XXX + Messages.admin_archiveButton = "Archive"; + + Messages.admin_unarchiveTitle = "Restore archived documents"; // XXX + Messages.admin_unarchiveHint = "Restore a document that has previously been archived"; + Messages.admin_unarchiveButton = "Restore"; + + Messages.admin_archiveInput = "Document URL"; + Messages.admin_archiveInput2 = "Document password"; + Messages.admin_archiveInval = "Invalid document"; + Messages.restoredFromServer = "Pad restored"; + + var archiveForm = function (archive, $div, $button) { + var label = h('label', { for: 'cp-admin-archive' }, Messages.admin_archiveInput); + var input = h('input#cp-admin-archive', { + type: 'text' + }); + + var label2 = h('label.cp-admin-pw', { + for: 'cp-admin-archive-pw' + }, Messages.admin_archiveInput2); + var input2 = UI.passwordInput({ + id: 'cp-admin-archive-pw', + placeholder: Messages.login_password + }); + var $pw = $(input2); + $pw.addClass('cp-admin-pw'); + var $pwInput = $pw.find('input'); + + + $button.before(h('div.cp-admin-setlimit-form', [ + label, + input, + label2, + input2 + ])); + + $div.addClass('cp-admin-nopassword'); + + var parsed; + var $input = $(input).on('keypress change paste', function () { + setTimeout(function () { + $input.removeClass('cp-admin-inval'); + var val = $input.val().trim(); + if (!val) { + $div.toggleClass('cp-admin-nopassword', true); + return; + } + + parsed = Hash.isValidHref(val); + $pwInput.val(''); + + if (!parsed || !parsed.hashData) { + $div.toggleClass('cp-admin-nopassword', true); + return void $input.addClass('cp-admin-inval'); + } + + var pw = parsed.hashData.version !== 3 && parsed.hashData.password; + $div.toggleClass('cp-admin-nopassword', !pw); + }); + }); + $pw.on('keypress change', function () { + setTimeout(function () { + $pw.toggleClass('cp-admin-inval', !$pwInput.val()); + }); + }); + + var clicked = false; + $button.click(function () { + if (!parsed || !parsed.hashData) { + UI.warn(Messages.admin_archiveInval); + return; + } + var pw = parsed.hashData.password ? $pwInput.val() : undefined; + var channel; + if (parsed.hashData.version === 3) { + channel = parsed.hashData.channel; + } else { + var secret = Hash.getSecrets(parsed.type, parsed.hash, pw); + channel = secret && secret.channel; + } + + if (!channel) { + UI.warn(Messages.admin_archiveInval); + return; + } + + if (clicked) { return; } + clicked = true; + + nThen(function (waitFor) { + if (!archive) { return; } + common.getFileSize(channel, waitFor(function (err, size) { + if (!err && size === 0) { + clicked = false; + waitFor.abort(); + return void UI.warn(Messages.admin_archiveInval); + } + })); + }).nThen(function () { + sFrameChan.query('Q_ADMIN_RPC', { + cmd: archive ? 'ARCHIVE_DOCUMENT' : 'RESTORE_ARCHIVED_DOCUMENT', + data: channel + }, function (err, obj) { + var e = err || (obj && obj.error); + clicked = false; + if (e) { + UI.warn(Messages.error); + console.error(e); + return; + } + UI.log(archive ? Messages.deletedFromServer : Messages.restoredFromServer); + $input.val(''); + $pwInput.val(''); + }); + }); + }); + }; + + create['archive'] = function () { + var key = 'archive'; + var $div = makeBlock(key, true); + var $button = $div.find('button'); + archiveForm(true, $div, $button); + return $div; + }; + create['unarchive'] = function () { + var key = 'unarchive'; + var $div = makeBlock(key, true); + var $button = $div.find('button'); + archiveForm(false, $div, $button); + return $div; + }; + create['registration'] = function () { var key = 'registration'; var $div = makeBlock(key, true); diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 199c2cb54..4f7290b81 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -464,6 +464,8 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app) }; if (!/^https*:\/\//.test(href)) { + // If it doesn't start with http(s), it should be a relative href + if (!/^\//.test(href)) { return ret; } idx = href.indexOf('/#'); ret.type = href.slice(1, idx); if (idx === -1) { return ret; } @@ -661,7 +663,7 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app) if (parsed.hashData.key && !/^[a-zA-Z0-9+-/=]+$/.test(parsed.hashData.key)) { return; } } } - return true; + return parsed; }; Hash.decodeDataOptions = function (opts) {