Merge branch 'donkey' into staging

This commit is contained in:
yflory
2018-06-01 12:07:10 +02:00
27 changed files with 583 additions and 280 deletions

View File

@@ -203,6 +203,10 @@
padding: @alertify_padding-base; padding: @alertify_padding-base;
} }
span.cp-password-container {
margin-bottom: 15px;
}
input[type="checkbox"], input[type="radio"] { input[type="checkbox"], input[type="radio"] {
width: auto; width: auto;
padding: 0; padding: 0;

View File

@@ -5,6 +5,7 @@
input { input {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
margin-bottom: 0 !important; // Override margin from alertify
} }
label, .fa { label, .fa {
margin-left: 10px; margin-left: 10px;

View File

@@ -597,9 +597,9 @@ define(function () {
out.settings_templateSkipHint = "Quand vous créez un nouveau pad, et si vous possédez des modèles pour ce type de pad, une fenêtre peut apparaître pour demander si vous souhaitez importer un modèle. Ici vous pouvez choisir de ne jamais montrer cette fenêtre et donc de ne jamais utiliser de modèle."; out.settings_templateSkipHint = "Quand vous créez un nouveau pad, et si vous possédez des modèles pour ce type de pad, une fenêtre peut apparaître pour demander si vous souhaitez importer un modèle. Ici vous pouvez choisir de ne jamais montrer cette fenêtre et donc de ne jamais utiliser de modèle.";
out.upload_title = "Hébergement de fichiers"; out.upload_title = "Hébergement de fichiers";
out.upload_rename = "Souhaitez-vous renommer <b>{0}</b> avant son stockage en ligne ?<br>" + out.upload_modal_title = "Options d'importation du fichier";
"<em>L'extension du fichier ({1}) sera ajoutée automatiquement. "+ out.upload_modal_filename = "Nom (extension <em>{0}</em> ajoutée automatiquement)";
"Ce nom sera permanent et visible par les autres utilisateurs</em>."; out.upload_modal_owner = "Être propriétaire du fichier";
out.upload_serverError = "Erreur interne: impossible d'importer le fichier pour l'instant."; out.upload_serverError = "Erreur interne: impossible d'importer le fichier pour l'instant.";
out.upload_uploadPending = "Vous avez déjà un fichier en cours d'importation. Souhaitez-vous l'annuler et importer ce nouveau fichier ?"; out.upload_uploadPending = "Vous avez déjà un fichier en cours d'importation. Souhaitez-vous l'annuler et importer ce nouveau fichier ?";
out.upload_success = "Votre fichier ({0}) a été importé avec succès et ajouté à votre CryptDrive."; out.upload_success = "Votre fichier ({0}) a été importé avec succès et ajouté à votre CryptDrive.";

View File

@@ -601,9 +601,9 @@ define(function () {
out.settings_templateSkipHint = "When you create a new empty pad, if you have stored templates for this type of pad, a modal appears to ask if you want to use a template. Here you can choose to never show this modal and so to never use a template."; out.settings_templateSkipHint = "When you create a new empty pad, if you have stored templates for this type of pad, a modal appears to ask if you want to use a template. Here you can choose to never show this modal and so to never use a template.";
out.upload_title = "File upload"; out.upload_title = "File upload";
out.upload_rename = "Do you want to rename <b>{0}</b> before uploading it to the server?<br>" + out.upload_modal_title = "File upload options";
"<em>The file extension ({1}) will be added automatically. "+ out.upload_modal_filename = "File name (extension <em>{0}</em> added automatically)";
"This name will be permanent and visible to other users.</em>"; out.upload_modal_owner = "Owned file";
out.upload_serverError = "Server Error: unable to upload your file at this time."; out.upload_serverError = "Server Error: unable to upload your file at this time.";
out.upload_uploadPending = "You already have an upload in progress. Cancel it and upload your new file?"; out.upload_uploadPending = "You already have an upload in progress. Cancel it and upload your new file?";
out.upload_success = "Your file ({0}) has been successfully uploaded and added to your drive."; out.upload_success = "Your file ({0}) has been successfully uploaded and added to your drive.";

263
rpc.js
View File

@@ -36,6 +36,7 @@ var isValidId = function (chan) {
[32, 48].indexOf(chan.length) > -1; [32, 48].indexOf(chan.length) > -1;
}; };
/*
var uint8ArrayToHex = function (a) { var uint8ArrayToHex = function (a) {
// call slice so Uint8Arrays work as expected // call slice so Uint8Arrays work as expected
return Array.prototype.slice.call(a).map(function (e) { return Array.prototype.slice.call(a).map(function (e) {
@@ -52,14 +53,24 @@ var uint8ArrayToHex = function (a) {
} }
}).join(''); }).join('');
}; };
*/
var testFileId = function (id) {
if (id.length !== 48 || /[^a-f0-9]/.test(id)) {
return false;
}
return true;
};
/*
var createFileId = function () { var createFileId = function () {
var id = uint8ArrayToHex(Nacl.randomBytes(24)); var id = uint8ArrayToHex(Nacl.randomBytes(24));
if (id.length !== 48 || /[^a-f0-9]/.test(id)) { if (!testFileId(id)) {
throw new Error('file ids must consist of 48 hex characters'); throw new Error('file ids must consist of 48 hex characters');
} }
return id; return id;
}; };
*/
var makeToken = function () { var makeToken = function () {
return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)) return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
@@ -811,6 +822,17 @@ var makeFileStream = function (root, id, cb) {
}); });
}; };
var isFile = function (filePath, cb) {
/*:: if (typeof(filePath) !== 'string') { throw new Error('should never happen'); } */
Fs.stat(filePath, function (e, stats) {
if (e) {
if (e.code === 'ENOENT') { return void cb(void 0, false); }
return void cb(e.message);
}
return void cb(void 0, stats.isFile());
});
};
var clearOwnedChannel = function (Env, channelId, unsafeKey, cb) { var clearOwnedChannel = function (Env, channelId, unsafeKey, cb) {
if (typeof(channelId) !== 'string' || channelId.length !== 32) { if (typeof(channelId) !== 'string' || channelId.length !== 32) {
return cb('INVALID_ARGUMENTS'); return cb('INVALID_ARGUMENTS');
@@ -834,11 +856,66 @@ var clearOwnedChannel = function (Env, channelId, unsafeKey, cb) {
}); });
}; };
var removeOwnedBlob = function (Env, blobId, unsafeKey, cb) {
var safeKey = escapeKeyCharacters(unsafeKey);
var safeKeyPrefix = safeKey.slice(0,3);
var blobPrefix = blobId.slice(0,2);
var blobPath = makeFilePath(Env.paths.blob, blobId);
var ownPath = Path.join(Env.paths.blob, safeKeyPrefix, safeKey, blobPrefix, blobId);
nThen(function (w) {
// Check if the blob exists
isFile(blobPath, w(function (e, isFile) {
if (e) {
w.abort();
return void cb(e);
}
if (!isFile) {
WARN('removeOwnedBlob', 'The provided blob ID is not a file!');
w.abort();
return void cb('EINVAL_BLOBID');
}
}));
}).nThen(function (w) {
// Check if you're the owner
isFile(ownPath, w(function (e, isFile) {
if (e) {
w.abort();
return void cb(e);
}
if (!isFile) {
WARN('removeOwnedBlob', 'Incorrect owner');
w.abort();
return void cb('INSUFFICIENT_PERMISSIONS');
}
}));
}).nThen(function (w) {
// Delete the blob
/*:: if (typeof(blobPath) !== 'string') { throw new Error('should never happen'); } */
Fs.unlink(blobPath, w(function (e) {
if (e) {
w.abort();
return void cb(e);
}
}));
}).nThen(function () {
// Delete the proof of ownership
Fs.unlink(ownPath, function (e) {
cb(e);
});
});
};
var removeOwnedChannel = function (Env, channelId, unsafeKey, cb) { var removeOwnedChannel = function (Env, channelId, unsafeKey, cb) {
if (typeof(channelId) !== 'string' || channelId.length !== 32) { if (typeof(channelId) !== 'string' || !isValidId(channelId)) {
return cb('INVALID_ARGUMENTS'); return cb('INVALID_ARGUMENTS');
} }
if (testFileId(channelId)) {
return void removeOwnedBlob(Env, channelId, unsafeKey, cb);
}
if (!(Env.msgStore && Env.msgStore.removeChannel && Env.msgStore.getChannelMetadata)) { if (!(Env.msgStore && Env.msgStore.removeChannel && Env.msgStore.getChannelMetadata)) {
return cb("E_NOT_IMPLEMENTED"); return cb("E_NOT_IMPLEMENTED");
} }
@@ -876,7 +953,7 @@ var upload = function (Env, publicKey, content, cb) {
var session = getSession(Env.Sessions, publicKey); var session = getSession(Env.Sessions, publicKey);
if (typeof(session.currentUploadSize) !== 'number' || if (typeof(session.currentUploadSize) !== 'number' ||
typeof(session.currentUploadSize) !== 'number') { typeof(session.pendingUploadSize) !== 'number') {
// improperly initialized... maybe they didn't check before uploading? // improperly initialized... maybe they didn't check before uploading?
// reject it, just in case // reject it, just in case
return cb('NOT_READY'); return cb('NOT_READY');
@@ -902,12 +979,12 @@ var upload = function (Env, publicKey, content, cb) {
} }
}; };
var upload_cancel = function (Env, publicKey, cb) { var upload_cancel = function (Env, publicKey, fileSize, cb) {
var paths = Env.paths; var paths = Env.paths;
var session = getSession(Env.Sessions, publicKey); var session = getSession(Env.Sessions, publicKey);
delete session.currentUploadSize; session.pendingUploadSize = fileSize;
delete session.pendingUploadSize; session.currentUploadSize = 0;
if (session.blobstage) { session.blobstage.close(); } if (session.blobstage) { session.blobstage.close(); }
var path = makeFilePath(paths.staging, publicKey); var path = makeFilePath(paths.staging, publicKey);
@@ -923,17 +1000,7 @@ var upload_cancel = function (Env, publicKey, cb) {
}); });
}; };
var isFile = function (filePath, cb) { var upload_complete = function (Env, publicKey, id, cb) {
Fs.stat(filePath, function (e, stats) {
if (e) {
if (e.code === 'ENOENT') { return void cb(void 0, false); }
return void cb(e.message);
}
return void cb(void 0, stats.isFile());
});
};
var upload_complete = function (Env, publicKey, cb) {
var paths = Env.paths; var paths = Env.paths;
var session = getSession(Env.Sessions, publicKey); var session = getSession(Env.Sessions, publicKey);
@@ -942,14 +1009,18 @@ var upload_complete = function (Env, publicKey, cb) {
delete session.blobstage; delete session.blobstage;
} }
if (!testFileId(id)) {
WARN('uploadComplete', "id is invalid");
return void cb('EINVAL_ID');
}
var oldPath = makeFilePath(paths.staging, publicKey); var oldPath = makeFilePath(paths.staging, publicKey);
if (!oldPath) { if (!oldPath) {
WARN('safeMkdir', "oldPath is null"); WARN('safeMkdir', "oldPath is null");
return void cb('RENAME_ERR'); return void cb('RENAME_ERR');
} }
var tryRandomLocation = function (cb) { var tryLocation = function (cb) {
var id = createFileId();
var prefix = id.slice(0, 2); var prefix = id.slice(0, 2);
var newPath = makeFilePath(paths.blob, id); var newPath = makeFilePath(paths.blob, id);
if (typeof(newPath) !== 'string') { if (typeof(newPath) !== 'string') {
@@ -968,7 +1039,8 @@ var upload_complete = function (Env, publicKey, cb) {
return void cb(e); return void cb(e);
} }
if (yes) { if (yes) {
return void tryRandomLocation(cb); WARN('isFile', 'FILE EXISTS!');
return void cb('RENAME_ERR');
} }
cb(void 0, newPath, id); cb(void 0, newPath, id);
@@ -976,40 +1048,25 @@ var upload_complete = function (Env, publicKey, cb) {
}); });
}; };
var retries = 3;
var handleMove = function (e, newPath, id) { var handleMove = function (e, newPath, id) {
if (e || !oldPath || !newPath) { if (e || !oldPath || !newPath) {
if (retries--) { return void cb(e || 'PATH_ERR');
setTimeout(function () {
return tryRandomLocation(handleMove);
}, 750);
} else {
cb(e);
}
return;
} }
// lol wut handle ur errors // lol wut handle ur errors
Fs.rename(oldPath, newPath, function (e) { Fs.rename(oldPath, newPath, function (e) {
if (e) { if (e) {
WARN('rename', e); WARN('rename', e);
if (retries--) {
return void setTimeout(function () {
tryRandomLocation(handleMove);
}, 750);
}
return void cb('RENAME_ERR'); return void cb('RENAME_ERR');
} }
cb(void 0, id); cb(void 0, id);
}); });
}; };
tryRandomLocation(handleMove); tryLocation(handleMove);
}; };
/*
var owned_upload_complete = function (Env, safeKey, cb) { var owned_upload_complete = function (Env, safeKey, cb) {
var session = getSession(Env.Sessions, safeKey); var session = getSession(Env.Sessions, safeKey);
@@ -1069,7 +1126,7 @@ var owned_upload_complete = function (Env, safeKey, cb) {
var finalPath; var finalPath;
nThen(function (w) { nThen(function (w) {
// make the requisite directory structure using Mkdirp // make the requisite directory structure using Mkdirp
Mkdirp(plannedPath, w(function (e /*, path */) { Mkdirp(plannedPath, w(function (e) {
if (e) { // does not throw error if the directory already existed if (e) { // does not throw error if the directory already existed
w.abort(); w.abort();
return void cb(e); return void cb(e);
@@ -1088,8 +1145,8 @@ var owned_upload_complete = function (Env, safeKey, cb) {
// move the existing file to its new path // move the existing file to its new path
// flow is dumb and I need to guard against this which will never happen // flow is dumb and I need to guard against this which will never happen
/*:: if (typeof(oldPath) === 'object') { throw new Error('should never happen'); } */ // / *:: if (typeof(oldPath) === 'object') { throw new Error('should never happen'); } * /
Fs.rename(oldPath /* XXX */, finalPath, w(function (e) { Fs.rename(oldPath, finalPath, w(function (e) {
if (e) { if (e) {
w.abort(); w.abort();
return void cb(e.code); return void cb(e.code);
@@ -1102,6 +1159,119 @@ var owned_upload_complete = function (Env, safeKey, cb) {
cb(void 0, blobId); cb(void 0, blobId);
}); });
}; };
*/
var owned_upload_complete = function (Env, safeKey, id, cb) {
var session = getSession(Env.Sessions, safeKey);
// the file has already been uploaded to the staging area
// close the pending writestream
if (session.blobstage && session.blobstage.close) {
session.blobstage.close();
delete session.blobstage;
}
if (!testFileId(id)) {
WARN('ownedUploadComplete', "id is invalid");
return void cb('EINVAL_ID');
}
var oldPath = makeFilePath(Env.paths.staging, safeKey);
if (typeof(oldPath) !== 'string') {
return void cb('EINVAL_CONFIG');
}
// construct relevant paths
var root = Env.paths.blob;
//var safeKey = escapeKeyCharacters(safeKey);
var safeKeyPrefix = safeKey.slice(0, 3);
//var blobId = createFileId();
var blobIdPrefix = id.slice(0, 2);
var ownPath = Path.join(root, safeKeyPrefix, safeKey, blobIdPrefix);
var filePath = Path.join(root, blobIdPrefix);
var tryId = function (path, cb) {
Fs.access(path, Fs.constants.R_OK | Fs.constants.W_OK, function (e) {
if (!e) {
// generate a new id (with the same prefix) and recurse
WARN('ownedUploadComplete', 'id is already used '+ id);
return void cb('EEXISTS');
} else if (e.code === 'ENOENT') {
// no entry, so it's safe for us to proceed
return void cb();
} else {
// it failed in an unexpected way. log it
WARN(e, 'ownedUploadComplete');
return void cb(e.code);
}
});
};
// the user wants to move it into blob and create a empty file with the same id
// in their own space:
// /blob/safeKeyPrefix/safeKey/blobPrefix/blobID
var finalPath;
var finalOwnPath;
nThen(function (w) {
// make the requisite directory structure using Mkdirp
Mkdirp(filePath, w(function (e /*, path */) {
if (e) { // does not throw error if the directory already existed
w.abort();
return void cb(e);
}
}));
Mkdirp(ownPath, w(function (e /*, path */) {
if (e) { // does not throw error if the directory already existed
w.abort();
return void cb(e);
}
}));
}).nThen(function (w) {
// make sure the id does not collide with another
finalPath = Path.join(filePath, id);
finalOwnPath = Path.join(ownPath, id);
tryId(finalPath, w(function (e) {
if (e) {
w.abort();
return void cb(e);
}
}));
}).nThen(function (w) {
// Create the empty file proving ownership
Fs.writeFile(finalOwnPath, '', w(function (e) {
if (e) {
w.abort();
return void cb(e.code);
}
// otherwise it worked...
}));
}).nThen(function (w) {
// move the existing file to its new path
// flow is dumb and I need to guard against this which will never happen
/*:: if (typeof(oldPath) === 'object') { throw new Error('should never happen'); } */
Fs.rename(oldPath /* XXX */, finalPath, w(function (e) {
if (e) {
// Remove the ownership file
// XXX not needed if we have a cleanup script?
Fs.unlink(finalOwnPath, function (e) {
WARN(e, 'Removing ownership file ownedUploadComplete');
});
w.abort();
return void cb(e.code);
}
// otherwise it worked...
}));
}).nThen(function () {
// clean up their session when you're done
// call back with the blob id...
cb(void 0, id);
});
};
var upload_status = function (Env, publicKey, filesize, cb) { var upload_status = function (Env, publicKey, filesize, cb) {
var paths = Env.paths; var paths = Env.paths;
@@ -1504,20 +1674,23 @@ RPC.create = function (
}); });
case 'UPLOAD_COMPLETE': case 'UPLOAD_COMPLETE':
if (!privileged) { return deny(); } if (!privileged) { return deny(); }
return void upload_complete(Env, safeKey, function (e, hash) { return void upload_complete(Env, safeKey, msg[1], function (e, hash) {
WARN(e, hash); WARN(e, hash);
Respond(e, hash); Respond(e, hash);
}); });
case 'OWNED_UPLOAD_COMPLETE': case 'OWNED_UPLOAD_COMPLETE':
if (!privileged) { return deny(); } if (!privileged) { return deny(); }
return void owned_upload_complete(Env, safeKey, function (e, blobId) { return void owned_upload_complete(Env, safeKey, msg[1], function (e, blobId) {
WARN(e, blobId); WARN(e, blobId);
Respond(e, blobId); Respond(e, blobId);
}); });
case 'UPLOAD_CANCEL': case 'UPLOAD_CANCEL':
if (!privileged) { return deny(); } if (!privileged) { return deny(); }
return void upload_cancel(Env, safeKey, function (e) { // msg[1] is fileSize
WARN(e); // if we pass it here, we can start an upload right away without calling
// UPLOAD_STATUS again
return void upload_cancel(Env, safeKey, msg[1], function (e) {
WARN(e, 'UPLOAD_CANCEL');
Respond(e); Respond(e);
}); });
default: default:

View File

@@ -331,14 +331,11 @@ define([
dropArea: $('.CodeMirror'), dropArea: $('.CodeMirror'),
body: $('body'), body: $('body'),
onUploaded: function (ev, data) { onUploaded: function (ev, data) {
//var cursor = editor.getCursor();
//var cleanName = data.name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.url+')';
// PASSWORD_FILES
var parsed = Hash.parsePadUrl(data.url); var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel); var secret = Hash.getSecrets('file', parsed.hash, data.password);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var src = Hash.getBlobPathFromHex(secret.channel);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>'; var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
editor.replaceSelection(mt); editor.replaceSelection(mt);
} }
}; };

View File

@@ -11,6 +11,7 @@ define([
var uint8ArrayToHex = Util.uint8ArrayToHex; var uint8ArrayToHex = Util.uint8ArrayToHex;
var hexToBase64 = Util.hexToBase64; var hexToBase64 = Util.hexToBase64;
var base64ToHex = Util.base64ToHex; var base64ToHex = Util.base64ToHex;
Hash.encodeBase64 = Nacl.util.encodeBase64;
// This implementation must match that on the server // This implementation must match that on the server
// it's used for a checksum // it's used for a checksum
@@ -59,25 +60,13 @@ define([
return '/1/' + hexToBase64(secret.channel) + '/' + return '/1/' + hexToBase64(secret.channel) + '/' +
Crypto.b64RemoveSlashes(data.fileKeyStr) + '/'; Crypto.b64RemoveSlashes(data.fileKeyStr) + '/';
} }
if (version === 2) {
if (!data.fileKeyStr) { return; }
var pass = secret.password ? 'p/' : '';
return '/2/' + secret.type + '/' + Crypto.b64RemoveSlashes(data.fileKeyStr) + '/' + pass;
}
}; };
// V1
/*var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) {
if (typeof keys === 'string') {
return chanKey + keys;
}
if (!keys.editKeyStr) { return; }
return '/1/edit/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.editKeyStr)+'/';
};
var getViewHashFromKeys = Hash.getViewHashFromKeys = function (chanKey, keys) {
if (typeof keys === 'string') {
return;
}
return '/1/view/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.viewKeyStr)+'/';
};
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) {
return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/';
};*/
Hash.getUserHrefFromKeys = function (origin, username, pubkey) { Hash.getUserHrefFromKeys = function (origin, username, pubkey) {
return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-'); return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
}; };
@@ -95,12 +84,22 @@ define([
}; };
Hash.createRandomHash = function (type, password) { Hash.createRandomHash = function (type, password) {
var cryptor = Crypto.createEditCryptor2(void 0, void 0, password); var cryptor;
if (type === 'file') {
cryptor = Crypto.createFileCryptor2(void 0, password);
return getFileHashFromKeys({
password: Boolean(password),
version: 2,
type: type,
keys: cryptor
});
}
cryptor = Crypto.createEditCryptor2(void 0, void 0, password);
return getEditHashFromKeys({ return getEditHashFromKeys({
password: Boolean(password), password: Boolean(password),
version: 2, version: 2,
type: type, type: type,
keys: { editKeyStr: cryptor.editKeyStr } keys: cryptor
}); });
}; };
@@ -113,6 +112,7 @@ Version 1
var parseTypeHash = Hash.parseTypeHash = function (type, hash) { var parseTypeHash = Hash.parseTypeHash = function (type, hash) {
if (!hash) { return; } if (!hash) { return; }
var options;
var parsed = {}; var parsed = {};
var hashArr = fixDuplicateSlashes(hash).split('/'); var hashArr = fixDuplicateSlashes(hash).split('/');
if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) { if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) {
@@ -125,7 +125,6 @@ Version 1
parsed.version = 0; parsed.version = 0;
return parsed; return parsed;
} }
var options;
if (hashArr[1] && hashArr[1] === '1') { // Version 1 if (hashArr[1] && hashArr[1] === '1') { // Version 1
parsed.version = 1; parsed.version = 1;
parsed.mode = hashArr[2]; parsed.mode = hashArr[2];
@@ -175,6 +174,25 @@ Version 1
parsed.key = hashArr[3].replace(/-/g, '/'); parsed.key = hashArr[3].replace(/-/g, '/');
return parsed; return parsed;
} }
if (hashArr[1] && hashArr[1] === '2') { // Version 2
parsed.version = 2;
parsed.app = hashArr[2];
parsed.key = hashArr[3];
options = hashArr.slice(4);
parsed.password = options.indexOf('p') !== -1;
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 4).join('/') + '/';
if (parsed.password) { hash += 'p/'; }
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
return hash;
};
return parsed;
}
return parsed; return parsed;
} }
if (['user'].indexOf(type) !== -1) { if (['user'].indexOf(type) !== -1) {
@@ -309,11 +327,12 @@ Version 1
} }
} }
} else if (parsed.type === "file") { } else if (parsed.type === "file") {
// version 2 hashes are to be used for encrypted blobs secret.channel = base64ToHex(parsed.channel);
secret.channel = parsed.channel; secret.keys = {
secret.keys = { fileKeyStr: parsed.key }; fileKeyStr: parsed.key,
cryptKey: Nacl.util.decodeBase64(parsed.key)
};
} else if (parsed.type === "user") { } else if (parsed.type === "user") {
// version 2 hashes are to be used for encrypted blobs
throw new Error("User hashes can't be opened (yet)"); throw new Error("User hashes can't be opened (yet)");
} }
} else if (parsed.version === 2) { } else if (parsed.version === 2) {
@@ -338,7 +357,12 @@ Version 1
} }
} }
} else if (parsed.type === "file") { } else if (parsed.type === "file") {
throw new Error("File hashes should be version 1"); secret.keys = Crypto.createFileCryptor2(parsed.key, password);
secret.channel = base64ToHex(secret.keys.chanId);
secret.key = secret.keys.fileKeyStr;
if (secret.channel.length !== 48 || secret.key.length !== 24) {
throw new Error("The channel key and/or the encryption key is invalid");
}
} else if (parsed.type === "user") { } else if (parsed.type === "user") {
throw new Error("User hashes can't be opened (yet)"); throw new Error("User hashes can't be opened (yet)");
} }

View File

@@ -425,7 +425,8 @@ define([
cb = cb || function () {}; cb = cb || function () {};
opt = opt || {}; opt = opt || {};
var input = dialog.textInput(); var inputBlock = opt.password ? UI.passwordInput() : dialog.textInput();
var input = opt.password ? $(inputBlock).find('input')[0] : inputBlock;
input.value = typeof(def) === 'string'? def: ''; input.value = typeof(def) === 'string'? def: '';
var message; var message;
@@ -441,7 +442,7 @@ define([
var cancel = dialog.cancelButton(opt.cancel); var cancel = dialog.cancelButton(opt.cancel);
var frame = dialog.frame([ var frame = dialog.frame([
message, message,
input, inputBlock,
dialog.nav([ cancel, ok, ]), dialog.nav([ cancel, ok, ]),
]); ]);

View File

@@ -250,17 +250,15 @@ define([
var k = getKey(parsed.type, channel); var k = getKey(parsed.type, channel);
common.setThumbnail(k, b64, cb); common.setThumbnail(k, b64, cb);
}; };
Thumb.displayThumbnail = function (common, href, channel, $container, cb) { Thumb.displayThumbnail = function (common, href, channel, password, $container, cb) {
cb = cb || function () {}; cb = cb || function () {};
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);
var k = getKey(parsed.type, channel); var k = getKey(parsed.type, channel);
var whenNewThumb = function () { var whenNewThumb = function () {
// PASSWORD_FILES var secret = Hash.getSecrets('file', parsed.hash, password);
var secret = Hash.getSecrets('file', parsed.hash); var hexFileName = channel;
var hexFileName = Util.base64ToHex(secret.channel);
var src = Hash.getBlobPathFromHex(hexFileName); var src = Hash.getBlobPathFromHex(hexFileName);
var cryptKey = secret.keys && secret.keys.fileKeyStr; var key = secret.keys && secret.keys.cryptKey;
var key = Nacl.util.decodeBase64(cryptKey);
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) { FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (e) { if (e) {
if (e === 'XHR_ERROR') { return; } if (e === 'XHR_ERROR') { return; }

View File

@@ -162,7 +162,6 @@ define([
$pwInput.val(data.password).click(function () { $pwInput.val(data.password).click(function () {
$pwInput[0].select(); $pwInput[0].select();
}); });
$(password).find('.cp-checkmark').css('margin-bottom', '15px');
$d.append(password); $d.append(password);
} }
@@ -960,7 +959,7 @@ define([
}; };
}; };
var setHTML = function (e, html) { var setHTML = UIElements.setHTML = function (e, html) {
e.innerHTML = html; e.innerHTML = html;
return e; return e;
}; };
@@ -1172,8 +1171,8 @@ define([
// No password for avatars // No password for avatars
var secret = Hash.getSecrets('file', parsed.hash); var secret = Hash.getSecrets('file', parsed.hash);
if (secret.keys && secret.channel) { if (secret.keys && secret.channel) {
var cryptKey = secret.keys && secret.keys.fileKeyStr; var hexFileName = secret.channel;
var hexFileName = Util.base64ToHex(secret.channel); var cryptKey = Hash.encodeBase64(secret.keys && secret.keys.cryptKey);
var src = Hash.getBlobPathFromHex(hexFileName); var src = Hash.getBlobPathFromHex(hexFileName);
Common.getFileSize(hexFileName, function (e, data) { Common.getFileSize(hexFileName, function (e, data) {
if (e || !data) { if (e || !data) {

View File

@@ -204,8 +204,8 @@ define([
}); });
}; };
common.uploadComplete = function (cb) { common.uploadComplete = function (id, owned, cb) {
postMessage("UPLOAD_COMPLETE", null, function (obj) { postMessage("UPLOAD_COMPLETE", {id: id, owned: owned}, function (obj) {
if (obj && obj.error) { return void cb(obj.error); } if (obj && obj.error) { return void cb(obj.error); }
cb(null, obj); cb(null, obj);
}); });
@@ -218,8 +218,8 @@ define([
}); });
}; };
common.uploadCancel = function (cb) { common.uploadCancel = function (size, cb) {
postMessage("UPLOAD_CANCEL", null, function (obj) { postMessage("UPLOAD_CANCEL", {size: size}, function (obj) {
if (obj && obj.error) { return void cb(obj.error); } if (obj && obj.error) { return void cb(obj.error); }
cb(null, obj); cb(null, obj);
}); });
@@ -578,7 +578,6 @@ define([
} }
var parsed = Hash.parsePadUrl(window.location.href); var parsed = Hash.parsePadUrl(window.location.href);
if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); } if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); }
if (parsed.type === 'file' && typeof(parsed.channel) === 'string') { secret.channel = Util.base64ToHex(secret.channel); }
hashes = Hash.getHashes(secret); hashes = Hash.getHashes(secret);
if (secret.version === 0) { if (secret.version === 0) {

View File

@@ -41,11 +41,15 @@ define([
}; };
renderer.image = function (href, title, text) { renderer.image = function (href, title, text) {
if (href.slice(0,6) === '/file/') { if (href.slice(0,6) === '/file/') {
// PASSWORD_FILES // DEPRECATED
// Mediatag using markdown syntax should not be used anymore so they don't support
// password-protected files
console.log('DEPRECATED: mediatag using markdown syntax!');
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);
var hexFileName = Util.base64ToHex(parsed.hashData.channel); var secret = Hash.getSecrets('file', parsed.hash);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var src = Hash.getBlobPathFromHex(secret.channel);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '">'; var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
if (mediaMap[src]) { if (mediaMap[src]) {
mt += mediaMap[src]; mt += mediaMap[src];
} }

View File

@@ -115,13 +115,8 @@ define([
parsed = Hash.parsePadUrl(el.href); parsed = Hash.parsePadUrl(el.href);
if (!el.href) { return; } if (!el.href) { return; }
if (!el.channel) { if (!el.channel) {
if (parsed.hashData && parsed.hashData.type === "file") { var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
// PASSWORD_FILES el.channel = secret.channel;
el.channel = Util.base64ToHex(parsed.hashData.channel);
} else {
var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.channel = secret.channel;
}
progress(6, Math.round(100*i/padsLength)); progress(6, Math.round(100*i/padsLength));
console.log('Adding missing channel in filesData ', el.channel); console.log('Adding missing channel in filesData ', el.channel);
} }

View File

@@ -92,7 +92,7 @@ define([
var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit, null) : null; var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit, null) : null;
if (profileChan) { list.push(profileChan); } if (profileChan) { list.push(profileChan); }
var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar, null) : null; var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar, null) : null;
if (avatarChan) { list.push(Util.base64ToHex(avatarChan)); } if (avatarChan) { list.push(avatarChan); }
} }
if (store.proxy.friends) { if (store.proxy.friends) {
@@ -232,7 +232,16 @@ define([
Store.uploadComplete = function (data, cb) { Store.uploadComplete = function (data, cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
store.rpc.uploadComplete(function (err, res) { if (data.owned) {
// Owned file
store.rpc.ownedUploadComplete(data.id, function (err, res) {
if (err) { return void cb({error:err}); }
cb(res);
});
return;
}
// Normal upload
store.rpc.uploadComplete(data.id, function (err, res) {
if (err) { return void cb({error:err}); } if (err) { return void cb({error:err}); }
cb(res); cb(res);
}); });
@@ -248,7 +257,7 @@ define([
Store.uploadCancel = function (data, cb) { Store.uploadCancel = function (data, cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
store.rpc.uploadCancel(function (err, res) { store.rpc.uploadCancel(data.size, function (err, res) {
if (err) { return void cb({error:err}); } if (err) { return void cb({error:err}); }
cb(res); cb(res);
}); });
@@ -678,6 +687,7 @@ define([
if (Store.channel && Store.channel.wc && channel === Store.channel.wc.id) { if (Store.channel && Store.channel.wc && channel === Store.channel.wc.id) {
owners = Store.channel.data.owners || undefined; owners = Store.channel.data.owners || undefined;
} }
var expire; var expire;
if (Store.channel && Store.channel.wc && channel === Store.channel.wc.id) { if (Store.channel && Store.channel.wc && channel === Store.channel.wc.id) {
expire = +Store.channel.data.expire || undefined; expire = +Store.channel.data.expire || undefined;
@@ -726,7 +736,11 @@ define([
contains = true; contains = true;
pad.atime = +new Date(); pad.atime = +new Date();
pad.title = title; pad.title = title;
pad.owners = owners; if (owners || h.type !== "file") {
// OWNED_FILES
// Never remove owner for files
pad.owners = owners;
}
pad.expire = expire; pad.expire = expire;
// If the href is different, it means we have a stronger one // If the href is different, it means we have a stronger one

View File

@@ -1,8 +1,9 @@
define([ define([
'/file/file-crypto.js', '/file/file-crypto.js',
'/common/common-hash.js', '/common/common-hash.js',
'/bower_components/nthen/index.js',
'/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
], function (FileCrypto, Hash) { ], function (FileCrypto, Hash, nThen) {
var Nacl = window.nacl; var Nacl = window.nacl;
var module = {}; var module = {};
@@ -10,97 +11,122 @@ define([
var u8 = file.blob; // This is not a blob but a uint8array var u8 = file.blob; // This is not a blob but a uint8array
var metadata = file.metadata; var metadata = file.metadata;
var owned = file.isOwned;
// XXX
owned = true;
// if it exists, path contains the new pad location in the drive // if it exists, path contains the new pad location in the drive
var path = file.path; var path = file.path;
var key = Nacl.randomBytes(32); var password = file.password;
var next = FileCrypto.encrypt(u8, metadata, key); var hash, secret, key, id, href;
var estimate = FileCrypto.computeEncryptedSize(u8.length, metadata); var getNewHash = function () {
hash = Hash.createRandomHash('file', password);
secret = Hash.getSecrets('file', hash, password);
key = secret.keys.cryptKey;
id = secret.channel;
href = '/file/#' + hash;
};
var sendChunk = function (box, cb) { var getValidHash = function (cb) {
var enc = Nacl.util.encodeBase64(box); getNewHash();
common.uploadChunk(enc, function (e, msg) { common.getFileSize(href, password, function (err, size) {
cb(e, msg); if (err || typeof(size) !== "number") { throw new Error(err || "Invalid size!"); }
if (size === 0) { return void cb(); }
getValidHash();
}); });
}; };
var actual = 0; var edPublic;
var again = function (err, box) { nThen(function (waitFor) {
if (err) { throw new Error(err); } // Generate a hash and check if the resulting id is valid (not already used)
if (box) { getValidHash(waitFor());
actual += box.length; }).nThen(function (waitFor) {
var progressValue = (actual / estimate * 100); if (!owned) { return; }
updateProgress(progressValue); common.getMetadata(waitFor(function (err, m) {
edPublic = m.priv.edPublic;
metadata.owners = [edPublic];
}));
}).nThen(function () {
var next = FileCrypto.encrypt(u8, metadata, key);
return void sendChunk(box, function (e) { var estimate = FileCrypto.computeEncryptedSize(u8.length, metadata);
if (e) { return console.error(e); }
next(again); var sendChunk = function (box, cb) {
var enc = Nacl.util.encodeBase64(box);
common.uploadChunk(enc, function (e, msg) {
cb(e, msg);
}); });
} };
if (actual !== estimate) { var actual = 0;
console.error('Estimated size does not match actual size'); var again = function (err, box) {
} if (err) { throw new Error(err); }
if (box) {
actual += box.length;
var progressValue = (actual / estimate * 100);
updateProgress(progressValue);
// if not box then done return void sendChunk(box, function (e) {
common.uploadComplete(function (e, id) { if (e) { return console.error(e); }
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 secret = {
version: 1,
channel: id,
keys: {
fileKeyStr: b64Key
}
};
var hash = Hash.getFileHashFromKeys(secret);
var href = '/file/#' + hash;
var title = metadata.name;
if (noStore) { return void onComplete(href); }
// PASSWORD_FILES
var data = {
title: title || "",
href: href,
path: path,
channel: id
};
common.setPadTitle(data, function (err) {
if (err) { return void console.error(err); }
onComplete(href);
common.setPadAttribute('fileType', metadata.type, null, href);
});
});
};
common.uploadStatus(estimate, function (e, pending) {
if (e) {
console.error(e);
onError(e);
return;
}
if (pending) {
return void onPending(function () {
// if the user wants to cancel the pending upload to execute that one
common.uploadCancel(function (e, res) {
if (e) {
return void console.error(e);
}
console.log(res);
next(again); next(again);
}); });
}
if (actual !== estimate) {
console.error('Estimated size does not match actual size');
}
// if not box then done
common.uploadComplete(id, owned, function (e) {
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 title = metadata.name;
if (noStore) { return void onComplete(href); }
var data = {
title: title || "",
href: href,
path: path,
password: password,
channel: id
};
common.setPadTitle(data, function (err) {
if (err) { return void console.error(err); }
onComplete(href);
common.setPadAttribute('fileType', metadata.type, null, href);
common.setPadAttribute('owners', metadata.owners, null, href);
});
}); });
} };
next(again);
common.uploadStatus(estimate, function (e, pending) {
if (e) {
console.error(e);
onError(e);
return;
}
if (pending) {
return void onPending(function () {
// if the user wants to cancel the pending upload to execute that one
common.uploadCancel(estimate, function (e) {
if (e) {
return void console.error(e);
}
next(again);
});
});
}
next(again);
});
}); });
}; };
return module; return module;
}); });

View File

@@ -589,14 +589,9 @@ define([
// Fix channel // Fix channel
if (!el.channel) { if (!el.channel) {
try { try {
if (parsed.hashData && parsed.hashData.type === "file") {
// PASSWORD_FILES
el.channel = Util.base64ToHex(parsed.hashData.channel);
} else {
var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.channel = secret.channel; el.channel = secret.channel;
} console.log('Adding missing channel in filesData ', el.channel);
console.log('Adding missing channel in filesData ', el.channel);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }

View File

@@ -151,7 +151,7 @@ define([
}; };
exp.removeOwnedChannel = function (channel, cb) { exp.removeOwnedChannel = function (channel, cb) {
if (typeof(channel) !== 'string' || channel.length !== 32) { if (typeof(channel) !== 'string' || [32,48].indexOf(channel.length) === -1) {
// can't use this on files because files can't be owned... // can't use this on files because files can't be owned...
return void cb('INVALID_ARGUMENTS'); return void cb('INVALID_ARGUMENTS');
} }
@@ -176,8 +176,19 @@ define([
}); });
}; };
exp.uploadComplete = function (cb) { exp.uploadComplete = function (id, cb) {
rpc.send('UPLOAD_COMPLETE', null, function (e, res) { rpc.send('UPLOAD_COMPLETE', id, function (e, res) {
if (e) { return void cb(e); }
var id = res[0];
if (typeof(id) !== 'string') {
return void cb('INVALID_ID');
}
cb(void 0, id);
});
};
exp.ownedUploadComplete = function (id, cb) {
rpc.send('OWNED_UPLOAD_COMPLETE', id, function (e, res) {
if (e) { return void cb(e); } if (e) { return void cb(e); }
var id = res[0]; var id = res[0];
if (typeof(id) !== 'string') { if (typeof(id) !== 'string') {
@@ -203,8 +214,8 @@ define([
}); });
}; };
exp.uploadCancel = function (cb) { exp.uploadCancel = function (size, cb) {
rpc.send('UPLOAD_CANCEL', void 0, function (e) { rpc.send('UPLOAD_CANCEL', size, function (e) {
if (e) { return void cb(e); } if (e) { return void cb(e); }
cb(); cb();
}); });

View File

@@ -329,15 +329,11 @@ define([
dropArea: $('.CodeMirror'), dropArea: $('.CodeMirror'),
body: $('body'), body: $('body'),
onUploaded: function (ev, data) { onUploaded: function (ev, data) {
//var cursor = editor.getCursor();
//var cleanName = data.name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.url+')';
// PASSWORD_FILES
var parsed = Hash.parsePadUrl(data.url); var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel); var secret = Hash.getSecrets('file', parsed.hash, data.password);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var src = Hash.getBlobPathFromHex(secret.channel);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + var key = Hash.encodeBase64(secret.keys.cryptKey);
parsed.hashData.key + '"></media-tag>'; var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
editor.replaceSelection(mt); editor.replaceSelection(mt);
} }
}; };

View File

@@ -3,11 +3,13 @@ define([
'/file/file-crypto.js', '/file/file-crypto.js',
'/common/common-thumbnail.js', '/common/common-thumbnail.js',
'/common/common-interface.js', '/common/common-interface.js',
'/common/common-ui-elements.js',
'/common/common-util.js', '/common/common-util.js',
'/common/hyperscript.js',
'/customize/messages.js', '/customize/messages.js',
'/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
], function ($, FileCrypto, Thumb, UI, Util, Messages) { ], function ($, FileCrypto, Thumb, UI, UIElements, Util, h, Messages) {
var Nacl = window.nacl; var Nacl = window.nacl;
var module = {}; var module = {};
@@ -26,6 +28,7 @@ define([
module.create = function (common, config) { module.create = function (common, config) {
var File = {}; var File = {};
var origin = common.getMetadataMgr().getPrivateData().origin;
var queue = File.queue = { var queue = File.queue = {
queue: [], queue: [],
@@ -53,6 +56,7 @@ define([
data.name = file.metadata.name; data.name = file.metadata.name;
data.url = href; data.url = href;
data.password = file.password;
if (file.metadata.type.slice(0,6) === 'image/') { if (file.metadata.type.slice(0,6) === 'image/') {
data.mediatag = true; data.mediatag = true;
} }
@@ -212,29 +216,61 @@ define([
queue.next(); queue.next();
}; };
// Don't show the rename prompt if we don't want to store the file in the drive (avatar) // Get the upload options
var showNamePrompt = !config.noStore; var fileUploadModal = function (file, cb) {
var promptName = function (file, cb) {
var extIdx = file.name.lastIndexOf('.'); var extIdx = file.name.lastIndexOf('.');
var name = extIdx !== -1 ? file.name.slice(0,extIdx) : file.name; var name = extIdx !== -1 ? file.name.slice(0,extIdx) : file.name;
var ext = extIdx !== -1 ? file.name.slice(extIdx) : ""; var ext = extIdx !== -1 ? file.name.slice(extIdx) : "";
var msg = Messages._getKey('upload_rename', [
Util.fixHTML(file.name), var createHelper = function (href, text) {
Util.fixHTML(ext) var q = h('a.fa.fa-question-circle', {
style: 'text-decoration: none !important;',
title: text,
href: origin + href,
target: "_blank",
'data-tippy-placement': "right"
});
return q;
};
// Ask for name, password and owner
var content = h('div', [
h('h4', Messages.upload_modal_title),
UIElements.setHTML(h('label', {for: 'cp-upload-name'}),
Messages._getKey('upload_modal_filename', [ext])),
h('input#cp-upload-name', {type: 'text', placeholder: name}),
h('label', {for: 'cp-upload-password'}, Messages.creation_passwordValue),
UI.passwordInput({id: 'cp-upload-password'}),
h('span', {
style: 'display:flex;align-items:center;justify-content:space-between'
}, [
UI.createCheckbox('cp-upload-owned', Messages.upload_modal_owner, true),
createHelper('/faq.html#keywords-owned', Messages.creation_owned1)
]),
]); ]);
UI.prompt(msg, name, function (newName) {
if (newName === null) { UI.confirm(content, function (yes) {
showNamePrompt = false; if (!yes) { return void cb(); }
return void cb (file.name);
} // Get the values
if (!newName || !newName.trim()) { return void cb (file.name); } var newName = $(content).find('#cp-upload-name').val();
var password = $(content).find('#cp-upload-password').val() || undefined;
var owned = $(content).find('#cp-upload-owned').is(':checked');
// Add extension to the name if needed
if (!newName || !newName.trim()) { newName = file.name; }
var newExtIdx = newName.lastIndexOf('.'); var newExtIdx = newName.lastIndexOf('.');
var newExt = newExtIdx !== -1 ? newName.slice(newExtIdx) : ""; var newExt = newExtIdx !== -1 ? newName.slice(newExtIdx) : "";
if (newExt !== ext) { newName += ext; } if (newExt !== ext) { newName += ext; }
cb(newName);
}, {cancel: Messages.doNotAskAgain}, true); cb({
name: newName,
password: password,
owned: owned
});
});
}; };
var handleFileState = { var handleFileState = {
queue: [], queue: [],
inProgress: false inProgress: false
@@ -246,17 +282,23 @@ define([
var thumb; var thumb;
var file_arraybuffer; var file_arraybuffer;
var name = file.name; var name = file.name;
var finish = function () { var password;
var metadata = { var owned = true;
name: name, var finish = function (abort) {
type: file.type, if (!abort) {
}; var metadata = {
if (thumb) { metadata.thumbnail = thumb; } name: name,
queue.push({ type: file.type,
blob: file_arraybuffer, };
metadata: metadata, if (thumb) { metadata.thumbnail = thumb; }
dropEvent: e queue.push({
}); blob: file_arraybuffer,
metadata: metadata,
password: password,
owned: owned,
dropEvent: e
});
}
handleFileState.inProgress = false; handleFileState.inProgress = false;
if (handleFileState.queue.length) { if (handleFileState.queue.length) {
var next = handleFileState.queue.shift(); var next = handleFileState.queue.shift();
@@ -264,9 +306,16 @@ define([
} }
}; };
var getName = function () { var getName = function () {
if (!showNamePrompt) { return void finish(); } // If "noStore", it means we don't want to store this file in our drive (avatar)
promptName(file, function (newName) { // In this case, we don't want a password or a filename, and we own the file
name = newName; if (config.noStore) { return void finish(); }
// Otherwise, ask for password, name and ownership
fileUploadModal(file, function (obj) {
if (!obj) { return void finish(true); }
name = obj.name;
password = obj.password;
owned = obj.owned;
finish(); finish();
}); });
}; };

View File

@@ -150,12 +150,12 @@ define([
todo(); todo();
} else { } else {
// Ask for the password and check if the pad exists // Ask for the password and check if the pad exists
// If the pad doesn't exist, it means the password is oncorrect // If the pad doesn't exist, it means the password isn't correct
// or the pad has been deleted // or the pad has been deleted
var correctPassword = waitFor(); var correctPassword = waitFor();
sframeChan.on('Q_PAD_PASSWORD_VALUE', function (data, cb) { sframeChan.on('Q_PAD_PASSWORD_VALUE', function (data, cb) {
password = data; password = data;
Cryptpad.isNewChannel(window.location.href, password, function (e, isNew) { var next = function (e, isNew) {
if (Boolean(isNew)) { if (Boolean(isNew)) {
// Ask again in the inner iframe // Ask again in the inner iframe
// We should receive a new Q_PAD_PASSWORD_VALUE // We should receive a new Q_PAD_PASSWORD_VALUE
@@ -165,7 +165,17 @@ define([
correctPassword(); correctPassword();
cb(true); cb(true);
} }
}); };
if (parsed.type === "file") {
// `isNewChannel` doesn't work for files (not a channel)
// `getFileSize` is not adapted to channels because of metadata
Cryptpad.getFileSize(window.location.href, password, function (e, size) {
next(e, size === 0);
});
return;
}
// Not a file, so we can use `isNewChannel`
Cryptpad.isNewChannel(window.location.href, password, next);
}); });
sframeChan.event("EV_PAD_PASSWORD"); sframeChan.event("EV_PAD_PASSWORD");
} }

View File

@@ -113,16 +113,16 @@ define([
return '<script src="' + origin + '/common/media-tag-nacl.min.js"></script>'; return '<script src="' + origin + '/common/media-tag-nacl.min.js"></script>';
}; };
funcs.getMediatagFromHref = function (href) { funcs.getMediatagFromHref = function (href) {
// PASSWORD_FILES // XXX: Should only be used with the current href
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets('file', parsed.hash);
var data = ctx.metadataMgr.getPrivateData(); var data = ctx.metadataMgr.getPrivateData();
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets('file', parsed.hash, data.password);
if (secret.keys && secret.channel) { if (secret.keys && secret.channel) {
var cryptKey = secret.keys && secret.keys.fileKeyStr; var key = Hash.encodeBase64(secret.keys && secret.keys.cryptKey);
var hexFileName = Util.base64ToHex(secret.channel); var hexFileName = secret.channel;
var origin = data.fileHost || data.origin; var origin = data.fileHost || data.origin;
var src = origin + Hash.getBlobPathFromHex(hexFileName); var src = origin + Hash.getBlobPathFromHex(hexFileName);
return '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + cryptKey + '">' + return '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '">' +
'</media-tag>'; '</media-tag>';
} }
return; return;

View File

@@ -590,7 +590,6 @@ define([
} }
}, cb); }, cb);
} }
console.log(path, newName);
if (path.length <= 1) { if (path.length <= 1) {
logError('Renaming `root` is forbidden'); logError('Renaming `root` is forbidden');
return; return;

View File

@@ -1305,7 +1305,7 @@ define([
$span.attr('title', name); $span.attr('title', name);
var type = Messages.type[hrefData.type] || hrefData.type; var type = Messages.type[hrefData.type] || hrefData.type;
common.displayThumbnail(data.href, data.channel, $span, function ($thumb) { common.displayThumbnail(data.href, data.channel, data.password, $span, function ($thumb) {
// Called only if the thumbnail exists // Called only if the thumbnail exists
// Remove the .hide() added by displayThumnail() because it hides the icon in // Remove the .hide() added by displayThumnail() because it hides the icon in
// list mode too // list mode too

View File

@@ -54,17 +54,14 @@ define([
var uploadMode = false; var uploadMode = false;
var secret; var secret;
var hexFileName;
var metadataMgr = common.getMetadataMgr(); var metadataMgr = common.getMetadataMgr();
var priv = metadataMgr.getPrivateData(); var priv = metadataMgr.getPrivateData();
if (!priv.filehash) { if (!priv.filehash) {
uploadMode = true; uploadMode = true;
} else { } else {
// PASSWORD_FILES secret = Hash.getSecrets('file', priv.filehash, priv.password);
secret = Hash.getSecrets('file', priv.filehash);
if (!secret.keys) { throw new Error("You need a hash"); } if (!secret.keys) { throw new Error("You need a hash"); }
hexFileName = Util.base64ToHex(secret.channel);
} }
var Title = common.createTitle({}); var Title = common.createTitle({});
@@ -87,9 +84,10 @@ define([
toolbar.$rightside.html(''); toolbar.$rightside.html('');
if (!uploadMode) { if (!uploadMode) {
var hexFileName = secret.channel;
var src = Hash.getBlobPathFromHex(hexFileName); var src = Hash.getBlobPathFromHex(hexFileName);
var cryptKey = secret.keys && secret.keys.fileKeyStr; var key = secret.keys && secret.keys.cryptKey;
var key = Nacl.util.decodeBase64(cryptKey); var cryptKey = Nacl.util.encodeBase64(key);
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) { FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (e) { if (e) {
@@ -98,17 +96,27 @@ define([
} }
return void console.error(e); return void console.error(e);
} }
// Add pad attributes when the file is saved in the drive
Title.onTitleChange(function () {
var owners = metadata.owners;
if (owners) {
common.setPadAttribute('owners', owners);
}
common.setPadAttribute('fileType', metadata.type);
});
// Save to the drive or update the acces time
var title = document.title = metadata.name; var title = document.title = metadata.name;
Title.updateTitle(title || Title.defaultTitle); Title.updateTitle(title || Title.defaultTitle);
toolbar.addElement(['pageTitle'], {pageTitle: title}); toolbar.addElement(['pageTitle'], {pageTitle: title});
toolbar.$rightside.append(common.createButton('forget', true)); toolbar.$rightside.append(common.createButton('forget', true));
toolbar.$rightside.append(common.createButton('properties', true));
if (common.isLoggedIn()) { if (common.isLoggedIn()) {
toolbar.$rightside.append(common.createButton('hashtag', true)); toolbar.$rightside.append(common.createButton('hashtag', true));
} }
common.setPadAttribute('fileType', metadata.type);
var displayFile = function (ev, sizeMb, CB) { var displayFile = function (ev, sizeMb, CB) {
var called_back; var called_back;
var cb = function (e) { var cb = function (e) {
@@ -118,9 +126,7 @@ define([
}; };
var $mt = $dlview.find('media-tag'); var $mt = $dlview.find('media-tag');
var cryptKey = secret.keys && secret.keys.fileKeyStr; $mt.attr('src', src);
var hexFileName = Util.base64ToHex(secret.channel);
$mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName);
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey); $mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
var rightsideDisplayed = false; var rightsideDisplayed = false;
@@ -263,7 +269,7 @@ define([
dropArea: $form, dropArea: $form,
hoverArea: $label, hoverArea: $label,
body: $body, body: $body,
keepTable: true // Don't fadeOut the tbale with the uploaded files keepTable: true // Don't fadeOut the table with the uploaded files
}; };
var FM = common.createFileManager(fmConfig); var FM = common.createFileManager(fmConfig);

View File

@@ -40,14 +40,14 @@ define([
var parsed = Hash.parsePadUrl(data.url); var parsed = Hash.parsePadUrl(data.url);
hideFileDialog(); hideFileDialog();
if (parsed.type === 'file') { if (parsed.type === 'file') {
// PASSWORD_FILES var secret = Hash.getSecrets('file', parsed.hash, data.password);
var hexFileName = Util.base64ToHex(parsed.hashData.channel); var src = Hash.getBlobPathFromHex(secret.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var key = Hash.encodeBase64(secret.keys.cryptKey);
sframeChan.event("EV_FILE_PICKED", { sframeChan.event("EV_FILE_PICKED", {
type: parsed.type, type: parsed.type,
src: src, src: src,
name: data.name, name: data.name,
key: parsed.hashData.key key: key
}); });
return; return;
} }
@@ -69,8 +69,8 @@ define([
APP.FM = common.createFileManager(fmConfig); APP.FM = common.createFileManager(fmConfig);
// Create file picker // Create file picker
var onSelect = function (url, name) { var onSelect = function (url, name, password) {
onFilePicked({url: url, name: name}); onFilePicked({url: url, name: name, password: password});
}; };
var data = { var data = {
FM: APP.FM FM: APP.FM
@@ -135,11 +135,13 @@ define([
$('<span>', {'class': 'cp-filepicker-content-element-name'}).text(name) $('<span>', {'class': 'cp-filepicker-content-element-name'}).text(name)
.appendTo($span); .appendTo($span);
$span.click(function () { $span.click(function () {
if (typeof onSelect === "function") { onSelect(data.href, name); } if (typeof onSelect === "function") {
onSelect(data.href, name, data.password);
}
}); });
// Add thumbnail if it exists // Add thumbnail if it exists
common.displayThumbnail(data.href, data.channel, $span); common.displayThumbnail(data.href, data.channel, data.password, $span);
}); });
$input.focus(); $input.focus();
}; };

View File

@@ -552,11 +552,11 @@ define([
ckeditor: editor, ckeditor: editor,
body: $('body'), body: $('body'),
onUploaded: function (ev, data) { onUploaded: function (ev, data) {
// PASSWORD_FILES
var parsed = Hash.parsePadUrl(data.url); var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel); var secret = Hash.getSecrets('file', parsed.hash, data.password);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var src = Hash.getBlobPathFromHex(secret.channel);
var mt = '<media-tag contenteditable="false" src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '" tabindex="1"></media-tag>'; var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag contenteditable="false" src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
// MEDIATAG // MEDIATAG
var element = window.CKEDITOR.dom.element.createFromHtml(mt); var element = window.CKEDITOR.dom.element.createFromHtml(mt);
editor.insertElement(element); editor.insertElement(element);

View File

@@ -500,11 +500,11 @@ define([
dropArea: $('.CodeMirror'), dropArea: $('.CodeMirror'),
body: $('body'), body: $('body'),
onUploaded: function (ev, data) { onUploaded: function (ev, data) {
// PASSWORD_FILES
var parsed = Hash.parsePadUrl(data.url); var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel); var secret = Hash.getSecrets('file', parsed.hash, data.password);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var src = Hash.getBlobPathFromHex(secret.channel);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>'; var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
editor.replaceSelection(mt); editor.replaceSelection(mt);
} }
}; };