Merge branch 'staging' into newCk

This commit is contained in:
yflory
2017-06-20 10:57:40 +02:00
59 changed files with 1651 additions and 671 deletions

View File

@@ -20,5 +20,25 @@ define([], function () {
};
}
var failStore = function () {
console.error(new Error('wut'));
require(['jquery'], function ($) {
$.ajax({
type: 'HEAD',
url: '/common/feedback.html?NO_LOCALSTORAGE=' + (+new Date()),
});
});
window.alert("CryptPad needs localStorage to work, try a different browser");
};
try {
var test_key = 'localStorage_test';
var testval = Math.random().toString();
localStorage.setItem(test_key, testval);
if (localStorage.getItem(test_key) !== testval) {
failStore();
}
} catch (e) { console.error(e); failStore(); }
require([document.querySelector('script[data-bootload]').getAttribute('data-bootload')]);
});

294
www/common/common-file.js Normal file
View File

@@ -0,0 +1,294 @@
define([
'jquery',
'/file/file-crypto.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function ($, FileCrypto) {
var Nacl = window.nacl;
var module = {};
module.create = function (common, config) {
var File = {};
var Messages = common.Messages;
var queue = File.queue = {
queue: [],
inProgress: false
};
var uid = function () {
return 'file-' + String(Math.random()).substring(2);
};
var $table = File.$table = $('<table>', { id: 'uploadStatus' });
var $thead = $('<tr>').appendTo($table);
$('<td>').text(Messages.upload_name).appendTo($thead);
$('<td>').text(Messages.upload_size).appendTo($thead);
$('<td>').text(Messages.upload_progress).appendTo($thead);
$('<td>').text(Messages.cancel).appendTo($thead);
var createTableContainer = function ($body) {
File.$container = $('<div>', { id: 'uploadStatusContainer' }).append($table).appendTo($body);
return File.$container;
};
var getData = function (file, href) {
var data = {};
data.name = file.metadata.name;
data.url = href;
if (file.metadata.type.slice(0,6) === 'image/') {
data.mediatag = true;
}
return data;
};
var upload = function (file) {
var blob = file.blob;
var metadata = file.metadata;
var id = file.id;
if (queue.inProgress) { return; }
queue.inProgress = true;
var $row = $table.find('tr[id="'+id+'"]');
$row.find('.upCancel').html('-');
var $pv = $row.find('.progressValue');
var $pb = $row.find('.progressContainer');
var $link = $row.find('.upLink');
var updateProgress = function (progressValue) {
$pv.text(Math.round(progressValue*100)/100 + '%');
$pb.css({
width: (progressValue/100)*188+'px'
});
};
var u8 = new Uint8Array(blob);
var key = Nacl.randomBytes(32);
var next = FileCrypto.encrypt(u8, metadata, key);
var estimate = FileCrypto.computeEncryptedSize(blob.byteLength, metadata);
var sendChunk = function (box, cb) {
var enc = Nacl.util.encodeBase64(box);
common.rpc.send.unauthenticated('UPLOAD', enc, function (e, msg) {
console.log(box);
cb(e, msg);
});
};
var actual = 0;
var again = function (err, box) {
if (err) { throw new Error(err); }
if (box) {
actual += box.length;
var progressValue = (actual / estimate * 100);
updateProgress(progressValue);
return void sendChunk(box, function (e) {
if (e) { return console.error(e); }
next(again);
});
}
if (actual !== estimate) {
console.error('Estimated size does not match actual size');
}
// if not box then done
common.uploadComplete(function (e, id) {
if (e) { return void console.error(e); }
var uri = ['', 'blob', id.slice(0,2), id].join('/');
console.log("encrypted blob is now available as %s", uri);
var b64Key = Nacl.util.encodeBase64(key);
var hash = common.getFileHashFromKeys(id, b64Key);
var href = '/file/#' + hash;
$link.attr('href', href)
.click(function (e) {
e.preventDefault();
window.open($link.attr('href'), '_blank');
});
// TODO add button to table which copies link to clipboard?
//APP.toolbar.addElement(['fileshare'], {});
var title = metadata.name;
common.renamePad(title || "", href, function (err) {
if (err) { return void console.error(err); } // TODO
console.log(title);
common.log(Messages._getKey('upload_success', [title]));
common.prepareFeedback('upload')();
if (config.onUploaded) {
var data = getData(file, href);
config.onUploaded(file.dropEvent, data);
}
queue.inProgress = false;
queue.next();
});
//Title.updateTitle(title || "", href);
//APP.toolbar.title.show();
});
};
common.uploadStatus(estimate, function (e, pending) {
if (e) {
queue.inProgress = false;
queue.next();
if (e === 'TOO_LARGE') {
// TODO update table to say too big?
return void common.alert(Messages.upload_tooLarge);
}
if (e === 'NOT_ENOUGH_SPACE') {
// TODO update table to say not enough space?
return void common.alert(Messages.upload_notEnoughSpace);
}
console.error(e);
return void common.alert(Messages.upload_serverError);
}
if (pending) {
// TODO keep this message in case of pending files in another window?
return void common.confirm(Messages.upload_uploadPending, function (yes) {
if (!yes) { return; }
common.uploadCancel(function (e, res) {
if (e) {
return void console.error(e);
}
console.log(res);
next(again);
});
});
}
next(again);
});
};
var prettySize = function (bytes) {
var kB = common.bytesToKilobytes(bytes);
if (kB < 1024) { return kB + Messages.KB; }
var mB = common.bytesToMegabytes(bytes);
return mB + Messages.MB;
};
queue.next = function () {
if (queue.queue.length === 0) {
queue.to = window.setTimeout(function () {
if (config.keepTable) { return; }
File.$container.fadeOut();
}, 3000);
return;
}
if (queue.inProgress) { return; }
File.$container.show();
var file = queue.queue.shift();
upload(file);
};
queue.push = function (obj) {
var id = uid();
obj.id = id;
queue.queue.push(obj);
$table.show();
var estimate = FileCrypto.computeEncryptedSize(obj.blob.byteLength, obj.metadata);
var $progressBar = $('<div>', {'class':'progressContainer'});
var $progressValue = $('<span>', {'class':'progressValue'}).text(Messages.upload_pending);
var $tr = $('<tr>', {id: id}).appendTo($table);
var $cancel = $('<span>', {'class': 'cancel fa fa-times'}).click(function () {
queue.queue = queue.queue.filter(function (el) { return el.id !== id; });
$cancel.remove();
$tr.find('.upCancel').text('-');
$tr.find('.progressValue').text(Messages.upload_cancelled);
});
var $link = $('<a>', {
'class': 'upLink',
'rel': 'noopener noreferrer'
}).text(obj.metadata.name);
$('<td>').append($link).appendTo($tr);
$('<td>').text(prettySize(estimate)).appendTo($tr);
$('<td>', {'class': 'upProgress'}).append($progressBar).append($progressValue).appendTo($tr);
$('<td>', {'class': 'upCancel'}).append($cancel).appendTo($tr);
queue.next();
};
var handleFile = File.handleFile = function (file, e) {
var reader = new FileReader();
reader.onloadend = function () {
queue.push({
blob: this.result,
metadata: {
name: file.name,
type: file.type,
},
dropEvent: e
});
};
reader.readAsArrayBuffer(file);
};
var onFileDrop = File.onFileDrop = function (file, e) {
Array.prototype.slice.call(file).forEach(function (d) {
handleFile(d, e);
});
};
var createAreaHandlers = File.createDropArea = function ($area, $hoverArea) {
var counter = 0;
if (!$hoverArea) { $hoverArea = $area; }
$hoverArea
.on('dragenter', function (e) {
e.preventDefault();
e.stopPropagation();
counter++;
$hoverArea.addClass('hovering');
})
.on('dragleave', function (e) {
e.preventDefault();
e.stopPropagation();
counter--;
if (counter <= 0) {
$hoverArea.removeClass('hovering');
}
});
$area
.on('drag dragstart dragend dragover drop dragenter dragleave', function (e) {
e.preventDefault();
e.stopPropagation();
})
.on('drop', function (e) {
e.stopPropagation();
var dropped = e.originalEvent.dataTransfer.files;
counter = 0;
$hoverArea.removeClass('hovering');
onFileDrop(dropped, e);
});
};
var createUploader = function ($area, $hover, $body) {
if (!config.noHandlers) {
createAreaHandlers($area, null);
}
createTableContainer($body);
};
createUploader(config.dropArea, config.hoverArea, config.body);
return File;
};
return module;
});

View File

@@ -37,6 +37,17 @@ define([
var parsed = config.href ? common.parsePadUrl(config.href) : {};
var secret = common.getSecrets(parsed.type, parsed.hash);
History.readOnly = 1;
if (!secret.keys) {
secret.keys = secret.key;
History.readOnly = 2;
}
else if (!secret.keys.validateKey) {
secret.keys.validateKey = true;
History.readOnly = 0;
}
var crypto = Crypto.createEncryptor(secret.keys);
var to = window.setTimeout(function () {
@@ -185,6 +196,7 @@ define([
'class':'revertHistory buttonSuccess',
title: Messages.history_restoreTitle
}).text(Messages.history_restore).appendTo($nav);
if (!History.readOnly) { $rev.hide(); }
onUpdate = function () {
$cur.attr('max', states.length);

View File

@@ -14,6 +14,7 @@ define(function () {
var getHeadingText = cfg.getHeadingText || function () { return; };
var updateLocalTitle = function (newTitle) {
exp.title = newTitle;
onLocal();
if (typeof cfg.updateLocalTitle === "function") {
cfg.updateLocalTitle(newTitle);
} else {
@@ -43,11 +44,12 @@ define(function () {
onLocal();
};
exp.updateTitle = function (newTitle) {
// update title: href is optional; if not specified, we use window.location.href
exp.updateTitle = function (newTitle, href) {
if (newTitle === exp.title) { return; }
// Change the title now, and set it back to the old value if there is an error
var oldTitle = exp.title;
Cryptpad.renamePad(newTitle, function (err, data) {
Cryptpad.renamePad(newTitle, href, function (err, data) {
if (err) {
console.log("Couldn't set pad title");
console.error(err);

View File

@@ -68,7 +68,11 @@ define([], function () {
Util.replaceHash = function (hash) {
if (window.history && window.history.replaceState) {
if (!/^#/.test(hash)) { hash = '#' + hash; }
return void window.history.replaceState({}, window.document.title, hash);
void window.history.replaceState({}, window.document.title, hash);
if (typeof(window.onhashchange) === 'function') {
window.onhashchange();
}
return;
}
window.location.hash = hash;
};

View File

@@ -11,11 +11,13 @@ define([
'/common/common-title.js',
'/common/common-metadata.js',
'/common/common-codemirror.js',
'/common/common-file.js',
'/common/clipboard.js',
'/common/pinpad.js',
'/customize/application_config.js'
], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Title, Metadata, CodeMirror, Clipboard, Pinpad, AppConfig) {
], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Title, Metadata,
CodeMirror, Files, Clipboard, Pinpad, AppConfig) {
/* This file exposes functionality which is specific to Cryptpad, but not to
any particular pad type. This includes functions for committing metadata
@@ -114,6 +116,9 @@ define([
// CodeMirror
common.createCodemirror = CodeMirror.create;
// Files
common.createFileManager = function (config) { return Files.create(common, config); };
// History
common.getHistory = function (config) { return History.create(common, config); };
@@ -126,6 +131,11 @@ define([
return store.getProxy().proxy;
}
};
common.getFO = function () {
if (store && store.getProxy()) {
return store.getProxy().fo;
}
};
var getNetwork = common.getNetwork = function () {
if (store) {
if (store.getProxy() && store.getProxy().info) {
@@ -144,7 +154,6 @@ define([
}
var href = '/common/feedback.html?' + action + '=' + (+new Date());
console.log('[feedback] %s', href);
$.ajax({
type: "HEAD",
url: href,
@@ -300,7 +309,7 @@ define([
cb(parsed);
}
if (!pad.title) {
pad.title = common.getDefaultname(parsed);
pad.title = common.getDefaultName(parsed);
}
return parsed.hashData;
};
@@ -520,8 +529,8 @@ define([
cb ("store.forgetPad is not a function");
};
common.setPadTitle = function (name, cb) {
var href = window.location.href;
common.setPadTitle = function (name, padHref, cb) {
var href = padHref || window.location.href;
var parsed = parsePadUrl(href);
if (!parsed.hash) { return; }
href = getRelativeHref(href);
@@ -581,19 +590,6 @@ define([
return pad;
});
if (!contains && href) {
var data = makePad(href, name);
getStore().pushData(data, function (e, id) {
if (e) {
if (e === 'E_OVER_LIMIT') {
common.alert(Messages.pinLimitNotPinned, null, true);
return;
}
else { throw new Error("Cannot push this pad to CryptDrive", e); }
}
getStore().addPad(id, common.initialPath);
});
}
if (updateWeaker.length > 0) {
updateWeaker.forEach(function (obj) {
// If we have a stronger url, and if all the occurences of the weaker were
@@ -601,6 +597,20 @@ define([
getStore().restoreHref(obj.n);
});
}
if (!contains && href) {
var data = makePad(href, name);
getStore().pushData(data, function (e, id) {
if (e) {
if (e === 'E_OVER_LIMIT') {
common.alert(Messages.pinLimitNotPinned, null, true);
}
return void cb(e);
}
getStore().addPad(id, common.initialPath);
cb(err, recent);
});
return;
}
cb(err, recent);
});
};
@@ -621,24 +631,41 @@ define([
/*
* Buttons
*/
common.renamePad = function (title, callback) {
common.renamePad = function (title, href, callback) {
if (title === null) { return; }
if (title.trim() === "") {
var parsed = parsePadUrl(window.location.href);
var parsed = parsePadUrl(href || window.location.href);
title = getDefaultName(parsed);
}
common.setPadTitle(title, function (err) {
common.setPadTitle(title, href, function (err) {
if (err) {
console.log("unable to set pad title");
console.log(err);
console.error(err);
return;
}
callback(null, title);
});
};
common.getUserFilesList = function () {
var store = common.getStore();
var proxy = store.getProxy();
var fo = proxy.fo;
var hashes = [];
var list = fo.getFiles().filter(function (id) {
var href = fo.getFileData(id).href;
var parsed = parsePadUrl(href);
if ((parsed.type === 'file' || parsed.type === 'media')
&& hashes.indexOf(parsed.hash) === -1) {
hashes.push(parsed.hash);
return true;
}
});
return list;
};
var getUserChannelList = common.getUserChannelList = function () {
var store = common.getStore();
var proxy = store.getProxy();
@@ -879,6 +906,21 @@ define([
common.getPinnedUsage(todo);
};
var getAppSuffix = function () {
var parts = window.location.pathname.split('/')
.filter(function (x) { return x; });
if (!parts[0]) { return ''; }
return '_' + parts[0].toUpperCase();
};
var prepareFeedback = common.prepareFeedback = function (key) {
if (typeof(key) !== 'string') { return $.noop; }
return function () {
feedback(key.toUpperCase() + getAppSuffix());
};
};
common.createButton = function (type, rightside, data, callback) {
var button;
var size = "17px";
@@ -887,6 +929,8 @@ define([
button = $('<button>', {
title: Messages.exportButtonTitle,
}).append($('<span>', {'class':'fa fa-download', style: 'font:'+size+' FontAwesome'}));
button.click(prepareFeedback(type));
if (callback) {
button.click(callback);
}
@@ -896,18 +940,40 @@ define([
title: Messages.importButtonTitle,
}).append($('<span>', {'class':'fa fa-upload', style: 'font:'+size+' FontAwesome'}));
if (callback) {
button.click(UI.importContent('text/plain', function (content, file) {
button
.click(prepareFeedback(type))
.click(UI.importContent('text/plain', function (content, file) {
callback(content, file);
}));
}
break;
case 'upload':
button = $('<button>', {
'class': 'btn btn-primary new',
title: Messages.uploadButtonTitle,
}).append($('<span>', {'class':'fa fa-upload'})).append(' '+Messages.uploadButton);
if (!data.FM) { return; }
var $input = $('<input>', {
'type': 'file',
'style': 'display: none;'
}).on('change', function (e) {
var file = e.target.files[0];
var ev = {
target: data.target
};
data.FM.handleFile(file, ev);
if (callback) { callback(); }
});
button.click(function () { $input.click(); });
break;
case 'template':
if (!AppConfig.enableTemplates) { return; }
button = $('<button>', {
title: Messages.saveTemplateButton,
}).append($('<span>', {'class':'fa fa-bookmark', style: 'font:'+size+' FontAwesome'}));
if (data.rt && data.Crypt) {
button.click(function () {
button
.click(function () {
var title = data.getTitle() || document.title;
var todo = function (val) {
if (typeof(val) !== "string") { return; }
@@ -965,7 +1031,9 @@ define([
}
});
if (callback) {
button.click(function() {
button
.click(prepareFeedback(type))
.click(function() {
var href = window.location.href;
var msg = isLoggedIn() ? Messages.forgetPrompt : Messages.fm_removePermanentlyDialog;
common.confirm(msg, function (yes) {
@@ -1021,7 +1089,9 @@ define([
style: 'font:'+size+' FontAwesome'
});
if (data.histConfig) {
button.click(function () {
button
.click(prepareFeedback(type))
.click(function () {
common.getHistory(data.histConfig);
});
}
@@ -1030,7 +1100,8 @@ define([
button = $('<button>', {
'class': "fa fa-question",
style: 'font:'+size+' FontAwesome'
});
})
.click(prepareFeedback(type));
}
if (rightside) {
button.addClass('rightside-button');
@@ -1378,8 +1449,8 @@ define([
initialized = true;
updateLocalVersion();
f(void 0, env);
if (typeof(window.onhashchange) === 'function') { window.onhashchange(); }
}
};
@@ -1422,6 +1493,7 @@ define([
|| parsedOld.channel !== parsedNew.channel
|| parsedOld.mode !== parsedNew.mode
|| parsedOld.key !== parsedNew.key)) {
if (!parsedOld.channel) { oldHref = newHref; return; }
document.location.reload();
return;
}

View File

@@ -1,8 +1,11 @@
define([
'jquery',
'/bower_components/marked/marked.min.js',
'/bower_components/diff-dom/diffDOM.js'
],function ($, Marked) {
'/common/cryptpad-common.js',
'/common/media-tag.js',
'/bower_components/diff-dom/diffDOM.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
],function ($, Marked, Cryptpad, MediaTag) {
var DiffMd = {};
var DiffDOM = window.diffDOM;
@@ -33,6 +36,20 @@ define([
var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : '';
return '<li'+ cls + '>' + text + '</li>\n';
};
renderer.image = function (href, title, text) {
if (href.slice(0,6) === '/file/') {
var parsed = Cryptpad.parsePadUrl(href);
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
var mt = '<media-tag src="/blob/' + hexFileName.slice(0,2) + '/' + hexFileName + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
return mt;
}
var out = '<img src="' + href + '" alt="' + text + '"';
if (title) {
out += ' title="' + title + '"';
}
out += this.options.xhtml ? '/>' : '>';
return out;
};
var forbiddenTags = [
'SCRIPT',
@@ -43,6 +60,10 @@ define([
'AUDIO',
];
var unsafeTag = function (info) {
if (info.node && $(info.node).parents('media-tag').length) {
// Do not remove elements inside a media-tag
return true;
}
if (['addAttribute', 'modifyAttribute'].indexOf(info.diff.action) !== -1) {
if (/^on/.test(info.diff.name)) {
console.log("Rejecting forbidden element attribute with name", info.diff.name);
@@ -61,6 +82,7 @@ define([
}
};
var slice = function (coll) {
return Array.prototype.slice.call(coll);
};
@@ -85,7 +107,7 @@ define([
var DD = new DiffDOM({
preDiffApply: function (info) {
if (unsafeTag(info)) { return true; }
}
},
});
var makeDiff = function (A, B, id) {
@@ -119,9 +141,18 @@ define([
throw new Error(patch);
} else {
DD.apply($content[0], patch);
var $mts = $content.find('media-tag:not(:has(*))');
$mts.each(function (i, el) {
MediaTag(el);
});
}
};
$(window.document).on('decryption', function (e) {
var decrypted = e.originalEvent;
if (decrypted.callback) { decrypted.callback(); }
});
return DiffMd;
});

File diff suppressed because one or more lines are too long

View File

@@ -105,7 +105,7 @@ define([
var oldFo = FO.init(parsed.drive, {
Cryptpad: Cryptpad
});
var todo = function () {
var onMigrated = function () {
oldFo.fixFiles();
var newData = Cryptpad.getStore().getProxy();
var newFo = newData.fo;
@@ -151,8 +151,10 @@ define([
proxy.FS_hashes = [];
}
proxy.FS_hashes.push(localStorage.FS_hash);
if (typeof(cb) === "function") { cb(); }
};
oldFo.migrate(todo);
oldFo.migrate(onMigrated);
return;
}
if (typeof(cb) === "function") { cb(); }
};

View File

@@ -63,6 +63,7 @@ types of messages:
// RPC responses are arrays. this message isn't meant for us.
return;
}
if (/FULL_HISTORY/.test(parsed[0])) { return; }
var response = parsed.slice(2);
@@ -98,7 +99,7 @@ types of messages:
delete ctx.pending[txid];
return;
}
console.error("received message for txid[%s] with no callback", txid);
console.error("received message [%s] for txid[%s] with no callback", msg, txid);
};
var create = function (network, edPrivateKey, edPublicKey, cb) {

View File

@@ -96,6 +96,10 @@ define([
} else {
styleToolbar($container);
}
$container.on('drop dragover', function (e) {
e.preventDefault();
e.stopPropagation();
});
return $toolbar;
};

View File

@@ -520,7 +520,7 @@ define([
// ADD
var add = exp.add = function (id, path) {
if (!Cryptpad.isLoggedIn()) { return; }
if (!Cryptpad.isLoggedIn() && !config.testMode) { return; }
var data = files[FILES_DATA][id];
if (!data || typeof(data) !== "object") { return; }
var newPath = path, parentEl;
@@ -559,7 +559,7 @@ define([
exp.forget = function (href) {
var id = getIdFromHref(href);
if (!id) { return; }
if (!Cryptpad.isLoggedIn()) {
if (!Cryptpad.isLoggedIn() && !config.testMode) {
// delete permanently
exp.removePadAttribute(href);
spliceFileData(id);
@@ -588,7 +588,7 @@ define([
};
var checkDeletedFiles = function () {
// Nothing in OLD_FILES_DATA for workgroups
if (workgroup || !Cryptpad.isLoggedIn()) { return; }
if (workgroup || (!Cryptpad.isLoggedIn() && !config.testMode)) { return; }
var filesList = getFiles([ROOT, 'hrefArray', TRASH]);
var fData = files[FILES_DATA];
@@ -617,7 +617,7 @@ define([
var trashPaths = paths.filter(function(x) { return isPathIn(x, [TRASH]); });
var allFilesPaths = paths.filter(function(x) { return isPathIn(x, [FILES_DATA]); });
if (!Cryptpad.isLoggedIn()) {
if (!Cryptpad.isLoggedIn() && !config.testMode) {
allFilesPaths.forEach(function (path) {
var el = find(path);
if (!el) { return; }
@@ -967,7 +967,7 @@ define([
toClean.push(id);
continue;
}
if (Cryptpad.isLoggedIn() && rootFiles.indexOf(id) === -1) {
if ((Cryptpad.isLoggedIn() || config.testMode) && rootFiles.indexOf(id) === -1) {
debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el);
var newName = Cryptpad.createChannelId();
root[newName] = id;