diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less index 24c63e570..ebc0b1383 100644 --- a/customize.dist/src/less2/include/alertify.less +++ b/customize.dist/src/less2/include/alertify.less @@ -203,6 +203,10 @@ padding: @alertify_padding-base; } + span.cp-password-container { + margin-bottom: 15px; + } + input[type="checkbox"], input[type="radio"] { width: auto; padding: 0; diff --git a/customize.dist/src/less2/include/password-input.less b/customize.dist/src/less2/include/password-input.less index 8836476fd..015f364aa 100644 --- a/customize.dist/src/less2/include/password-input.less +++ b/customize.dist/src/less2/include/password-input.less @@ -5,6 +5,7 @@ input { flex: 1; min-width: 0; + margin-bottom: 0 !important; // Override margin from alertify } label, .fa { margin-left: 10px; diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index b11bc2e17..e053d8fab 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -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.upload_title = "Hébergement de fichiers"; - out.upload_rename = "Souhaitez-vous renommer {0} avant son stockage en ligne ?
" + - "L'extension du fichier ({1}) sera ajoutée automatiquement. "+ - "Ce nom sera permanent et visible par les autres utilisateurs."; + out.upload_modal_title = "Options d'importation du fichier"; + out.upload_modal_filename = "Nom (extension {0} ajoutée automatiquement)"; + out.upload_modal_owner = "Être propriétaire du fichier"; 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_success = "Votre fichier ({0}) a été importé avec succès et ajouté à votre CryptDrive."; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index b61ae3d23..8251147dd 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -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.upload_title = "File upload"; - out.upload_rename = "Do you want to rename {0} before uploading it to the server?
" + - "The file extension ({1}) will be added automatically. "+ - "This name will be permanent and visible to other users."; + out.upload_modal_title = "File upload options"; + out.upload_modal_filename = "File name (extension {0} added automatically)"; + out.upload_modal_owner = "Owned file"; 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_success = "Your file ({0}) has been successfully uploaded and added to your drive."; diff --git a/rpc.js b/rpc.js index 88228fdac..c21bee11f 100644 --- a/rpc.js +++ b/rpc.js @@ -36,6 +36,7 @@ var isValidId = function (chan) { [32, 48].indexOf(chan.length) > -1; }; +/* var uint8ArrayToHex = function (a) { // call slice so Uint8Arrays work as expected return Array.prototype.slice.call(a).map(function (e) { @@ -52,14 +53,24 @@ var uint8ArrayToHex = function (a) { } }).join(''); }; +*/ +var testFileId = function (id) { + if (id.length !== 48 || /[^a-f0-9]/.test(id)) { + return false; + } + return true; +}; + +/* var createFileId = function () { 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'); } return id; }; +*/ var makeToken = function () { 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) { if (typeof(channelId) !== 'string' || channelId.length !== 32) { 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) { - if (typeof(channelId) !== 'string' || channelId.length !== 32) { + if (typeof(channelId) !== 'string' || !isValidId(channelId)) { return cb('INVALID_ARGUMENTS'); } + if (testFileId(channelId)) { + return void removeOwnedBlob(Env, channelId, unsafeKey, cb); + } + if (!(Env.msgStore && Env.msgStore.removeChannel && Env.msgStore.getChannelMetadata)) { return cb("E_NOT_IMPLEMENTED"); } @@ -876,7 +953,7 @@ var upload = function (Env, publicKey, content, cb) { var session = getSession(Env.Sessions, publicKey); if (typeof(session.currentUploadSize) !== 'number' || - typeof(session.currentUploadSize) !== 'number') { + typeof(session.pendingUploadSize) !== 'number') { // improperly initialized... maybe they didn't check before uploading? // reject it, just in case 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 session = getSession(Env.Sessions, publicKey); - delete session.currentUploadSize; - delete session.pendingUploadSize; + session.pendingUploadSize = fileSize; + session.currentUploadSize = 0; if (session.blobstage) { session.blobstage.close(); } var path = makeFilePath(paths.staging, publicKey); @@ -923,17 +1000,7 @@ var upload_cancel = function (Env, publicKey, cb) { }); }; -var isFile = function (filePath, 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 upload_complete = function (Env, publicKey, id, cb) { var paths = Env.paths; var session = getSession(Env.Sessions, publicKey); @@ -942,14 +1009,18 @@ var upload_complete = function (Env, publicKey, cb) { delete session.blobstage; } + if (!testFileId(id)) { + WARN('uploadComplete', "id is invalid"); + return void cb('EINVAL_ID'); + } + var oldPath = makeFilePath(paths.staging, publicKey); if (!oldPath) { WARN('safeMkdir', "oldPath is null"); return void cb('RENAME_ERR'); } - var tryRandomLocation = function (cb) { - var id = createFileId(); + var tryLocation = function (cb) { var prefix = id.slice(0, 2); var newPath = makeFilePath(paths.blob, id); if (typeof(newPath) !== 'string') { @@ -968,7 +1039,8 @@ var upload_complete = function (Env, publicKey, cb) { return void cb(e); } if (yes) { - return void tryRandomLocation(cb); + WARN('isFile', 'FILE EXISTS!'); + return void cb('RENAME_ERR'); } 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) { if (e || !oldPath || !newPath) { - if (retries--) { - setTimeout(function () { - return tryRandomLocation(handleMove); - }, 750); - } else { - cb(e); - } - return; + return void cb(e || 'PATH_ERR'); } // lol wut handle ur errors Fs.rename(oldPath, newPath, function (e) { if (e) { WARN('rename', e); - - if (retries--) { - return void setTimeout(function () { - tryRandomLocation(handleMove); - }, 750); - } - return void cb('RENAME_ERR'); } cb(void 0, id); }); }; - tryRandomLocation(handleMove); + tryLocation(handleMove); }; +/* var owned_upload_complete = function (Env, safeKey, cb) { var session = getSession(Env.Sessions, safeKey); @@ -1069,7 +1126,7 @@ var owned_upload_complete = function (Env, safeKey, cb) { var finalPath; nThen(function (w) { // 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 w.abort(); return void cb(e); @@ -1088,8 +1145,8 @@ var owned_upload_complete = function (Env, safeKey, cb) { // 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 (typeof(oldPath) === 'object') { throw new Error('should never happen'); } * / + Fs.rename(oldPath, finalPath, w(function (e) { if (e) { w.abort(); return void cb(e.code); @@ -1102,6 +1159,119 @@ var owned_upload_complete = function (Env, safeKey, cb) { 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 paths = Env.paths; @@ -1504,20 +1674,23 @@ RPC.create = function ( }); case 'UPLOAD_COMPLETE': 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); Respond(e, hash); }); case 'OWNED_UPLOAD_COMPLETE': 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); Respond(e, blobId); }); case 'UPLOAD_CANCEL': if (!privileged) { return deny(); } - return void upload_cancel(Env, safeKey, function (e) { - WARN(e); + // msg[1] is fileSize + // 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); }); default: diff --git a/www/code/inner.js b/www/code/inner.js index 0a6b11abc..afb4cdcd0 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -331,14 +331,11 @@ define([ dropArea: $('.CodeMirror'), body: $('body'), 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 hexFileName = Util.base64ToHex(parsed.hashData.channel); - var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; - var mt = ''; + var secret = Hash.getSecrets('file', parsed.hash, data.password); + var src = Hash.getBlobPathFromHex(secret.channel); + var key = Hash.encodeBase64(secret.keys.cryptKey); + var mt = ''; editor.replaceSelection(mt); } }; diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 3aaa7719b..4b0c2c607 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -11,6 +11,7 @@ define([ var uint8ArrayToHex = Util.uint8ArrayToHex; var hexToBase64 = Util.hexToBase64; var base64ToHex = Util.base64ToHex; + Hash.encodeBase64 = Nacl.util.encodeBase64; // This implementation must match that on the server // it's used for a checksum @@ -59,25 +60,13 @@ define([ return '/1/' + hexToBase64(secret.channel) + '/' + 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) { return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-'); }; @@ -95,12 +84,22 @@ define([ }; 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({ password: Boolean(password), version: 2, type: type, - keys: { editKeyStr: cryptor.editKeyStr } + keys: cryptor }); }; @@ -113,6 +112,7 @@ Version 1 var parseTypeHash = Hash.parseTypeHash = function (type, hash) { if (!hash) { return; } + var options; var parsed = {}; var hashArr = fixDuplicateSlashes(hash).split('/'); if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) { @@ -125,7 +125,6 @@ Version 1 parsed.version = 0; return parsed; } - var options; if (hashArr[1] && hashArr[1] === '1') { // Version 1 parsed.version = 1; parsed.mode = hashArr[2]; @@ -175,6 +174,25 @@ Version 1 parsed.key = hashArr[3].replace(/-/g, '/'); 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; } if (['user'].indexOf(type) !== -1) { @@ -309,11 +327,12 @@ Version 1 } } } else if (parsed.type === "file") { - // version 2 hashes are to be used for encrypted blobs - secret.channel = parsed.channel; - secret.keys = { fileKeyStr: parsed.key }; + secret.channel = base64ToHex(parsed.channel); + secret.keys = { + fileKeyStr: parsed.key, + cryptKey: Nacl.util.decodeBase64(parsed.key) + }; } 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)"); } } else if (parsed.version === 2) { @@ -338,7 +357,12 @@ Version 1 } } } 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") { throw new Error("User hashes can't be opened (yet)"); } diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 0592d859a..e65742d0f 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -425,7 +425,8 @@ define([ cb = cb || function () {}; 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: ''; var message; @@ -441,7 +442,7 @@ define([ var cancel = dialog.cancelButton(opt.cancel); var frame = dialog.frame([ message, - input, + inputBlock, dialog.nav([ cancel, ok, ]), ]); diff --git a/www/common/common-thumbnail.js b/www/common/common-thumbnail.js index ab6d3e6d4..bc8145d46 100644 --- a/www/common/common-thumbnail.js +++ b/www/common/common-thumbnail.js @@ -250,17 +250,15 @@ define([ var k = getKey(parsed.type, channel); 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 () {}; var parsed = Hash.parsePadUrl(href); var k = getKey(parsed.type, channel); var whenNewThumb = function () { - // PASSWORD_FILES - var secret = Hash.getSecrets('file', parsed.hash); - var hexFileName = Util.base64ToHex(secret.channel); + var secret = Hash.getSecrets('file', parsed.hash, password); + var hexFileName = channel; var src = Hash.getBlobPathFromHex(hexFileName); - var cryptKey = secret.keys && secret.keys.fileKeyStr; - var key = Nacl.util.decodeBase64(cryptKey); + var key = secret.keys && secret.keys.cryptKey; FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) { if (e) { if (e === 'XHR_ERROR') { return; } diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 18f38aae0..3c1c9c7ab 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -162,7 +162,6 @@ define([ $pwInput.val(data.password).click(function () { $pwInput[0].select(); }); - $(password).find('.cp-checkmark').css('margin-bottom', '15px'); $d.append(password); } @@ -960,7 +959,7 @@ define([ }; }; - var setHTML = function (e, html) { + var setHTML = UIElements.setHTML = function (e, html) { e.innerHTML = html; return e; }; @@ -1172,8 +1171,8 @@ define([ // No password for avatars var secret = Hash.getSecrets('file', parsed.hash); if (secret.keys && secret.channel) { - var cryptKey = secret.keys && secret.keys.fileKeyStr; - var hexFileName = Util.base64ToHex(secret.channel); + var hexFileName = secret.channel; + var cryptKey = Hash.encodeBase64(secret.keys && secret.keys.cryptKey); var src = Hash.getBlobPathFromHex(hexFileName); Common.getFileSize(hexFileName, function (e, data) { if (e || !data) { diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 5faffc5c9..f91f050e8 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -204,8 +204,8 @@ define([ }); }; - common.uploadComplete = function (cb) { - postMessage("UPLOAD_COMPLETE", null, function (obj) { + common.uploadComplete = function (id, owned, cb) { + postMessage("UPLOAD_COMPLETE", {id: id, owned: owned}, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(null, obj); }); @@ -218,8 +218,8 @@ define([ }); }; - common.uploadCancel = function (cb) { - postMessage("UPLOAD_CANCEL", null, function (obj) { + common.uploadCancel = function (size, cb) { + postMessage("UPLOAD_CANCEL", {size: size}, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(null, obj); }); @@ -578,7 +578,6 @@ define([ } var parsed = Hash.parsePadUrl(window.location.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); if (secret.version === 0) { diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index 80c9c4b79..fca3023b4 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -41,11 +41,15 @@ define([ }; renderer.image = function (href, title, text) { 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 hexFileName = Util.base64ToHex(parsed.hashData.channel); - var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; - var mt = ''; + var secret = Hash.getSecrets('file', parsed.hash); + var src = Hash.getBlobPathFromHex(secret.channel); + var key = Hash.encodeBase64(secret.keys.cryptKey); + var mt = ''; if (mediaMap[src]) { mt += mediaMap[src]; } diff --git a/www/common/migrate-user-object.js b/www/common/migrate-user-object.js index 1d6dd1210..fb69fb20b 100644 --- a/www/common/migrate-user-object.js +++ b/www/common/migrate-user-object.js @@ -115,13 +115,8 @@ define([ parsed = Hash.parsePadUrl(el.href); if (!el.href) { return; } if (!el.channel) { - 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); - el.channel = secret.channel; - } + var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); + el.channel = secret.channel; progress(6, Math.round(100*i/padsLength)); console.log('Adding missing channel in filesData ', el.channel); } diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index e02a527ae..680712f17 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -92,7 +92,7 @@ define([ var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit, null) : null; if (profileChan) { list.push(profileChan); } 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) { @@ -232,7 +232,16 @@ define([ Store.uploadComplete = function (data, cb) { 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}); } cb(res); }); @@ -248,7 +257,7 @@ define([ Store.uploadCancel = function (data, cb) { 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}); } cb(res); }); @@ -678,6 +687,7 @@ define([ if (Store.channel && Store.channel.wc && channel === Store.channel.wc.id) { owners = Store.channel.data.owners || undefined; } + var expire; if (Store.channel && Store.channel.wc && channel === Store.channel.wc.id) { expire = +Store.channel.data.expire || undefined; @@ -726,7 +736,11 @@ define([ contains = true; pad.atime = +new Date(); pad.title = title; - pad.owners = owners; + if (owners || h.type !== "file") { + // OWNED_FILES + // Never remove owner for files + pad.owners = owners; + } pad.expire = expire; // If the href is different, it means we have a stronger one diff --git a/www/common/outer/upload.js b/www/common/outer/upload.js index 7f3874511..65f674517 100644 --- a/www/common/outer/upload.js +++ b/www/common/outer/upload.js @@ -1,8 +1,9 @@ define([ '/file/file-crypto.js', '/common/common-hash.js', + '/bower_components/nthen/index.js', '/bower_components/tweetnacl/nacl-fast.min.js', -], function (FileCrypto, Hash) { +], function (FileCrypto, Hash, nThen) { var Nacl = window.nacl; var module = {}; @@ -10,97 +11,122 @@ define([ var u8 = file.blob; // This is not a blob but a uint8array var metadata = file.metadata; + var owned = file.isOwned; +// XXX +owned = true; + // if it exists, path contains the new pad location in the drive var path = file.path; - var key = Nacl.randomBytes(32); - var next = FileCrypto.encrypt(u8, metadata, key); + var password = file.password; + 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 enc = Nacl.util.encodeBase64(box); - common.uploadChunk(enc, function (e, msg) { - cb(e, msg); + var getValidHash = function (cb) { + getNewHash(); + common.getFileSize(href, password, function (err, size) { + if (err || typeof(size) !== "number") { throw new Error(err || "Invalid size!"); } + if (size === 0) { return void cb(); } + getValidHash(); }); }; - 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); + var edPublic; + nThen(function (waitFor) { + // Generate a hash and check if the resulting id is valid (not already used) + getValidHash(waitFor()); + }).nThen(function (waitFor) { + if (!owned) { return; } + 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) { - if (e) { return console.error(e); } - next(again); + var estimate = FileCrypto.computeEncryptedSize(u8.length, metadata); + + var sendChunk = function (box, cb) { + var enc = Nacl.util.encodeBase64(box); + common.uploadChunk(enc, function (e, msg) { + cb(e, msg); }); - } + }; - if (actual !== estimate) { - console.error('Estimated size does not match actual size'); - } + 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); - // 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 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); + 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(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; }); diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index b24092673..8b2270797 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -589,14 +589,9 @@ define([ // Fix channel if (!el.channel) { 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); el.channel = secret.channel; - } - console.log('Adding missing channel in filesData ', el.channel); + console.log('Adding missing channel in filesData ', el.channel); } catch (e) { console.error(e); } diff --git a/www/common/pinpad.js b/www/common/pinpad.js index b200f5493..957f5d1d6 100644 --- a/www/common/pinpad.js +++ b/www/common/pinpad.js @@ -151,7 +151,7 @@ define([ }; 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... return void cb('INVALID_ARGUMENTS'); } @@ -176,8 +176,19 @@ define([ }); }; - exp.uploadComplete = function (cb) { - rpc.send('UPLOAD_COMPLETE', null, function (e, res) { + exp.uploadComplete = function (id, cb) { + 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); } var id = res[0]; if (typeof(id) !== 'string') { @@ -203,8 +214,8 @@ define([ }); }; - exp.uploadCancel = function (cb) { - rpc.send('UPLOAD_CANCEL', void 0, function (e) { + exp.uploadCancel = function (size, cb) { + rpc.send('UPLOAD_CANCEL', size, function (e) { if (e) { return void cb(e); } cb(); }); diff --git a/www/common/sframe-common-codemirror.js b/www/common/sframe-common-codemirror.js index 19b4c7ae0..0c1a6a7a5 100644 --- a/www/common/sframe-common-codemirror.js +++ b/www/common/sframe-common-codemirror.js @@ -329,15 +329,11 @@ define([ dropArea: $('.CodeMirror'), body: $('body'), 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 hexFileName = Util.base64ToHex(parsed.hashData.channel); - var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; - var mt = ''; + var secret = Hash.getSecrets('file', parsed.hash, data.password); + var src = Hash.getBlobPathFromHex(secret.channel); + var key = Hash.encodeBase64(secret.keys.cryptKey); + var mt = ''; editor.replaceSelection(mt); } }; diff --git a/www/common/sframe-common-file.js b/www/common/sframe-common-file.js index 2c212701e..83127d886 100644 --- a/www/common/sframe-common-file.js +++ b/www/common/sframe-common-file.js @@ -3,11 +3,13 @@ define([ '/file/file-crypto.js', '/common/common-thumbnail.js', '/common/common-interface.js', + '/common/common-ui-elements.js', '/common/common-util.js', + '/common/hyperscript.js', '/customize/messages.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 module = {}; @@ -26,6 +28,7 @@ define([ module.create = function (common, config) { var File = {}; + var origin = common.getMetadataMgr().getPrivateData().origin; var queue = File.queue = { queue: [], @@ -53,6 +56,7 @@ define([ data.name = file.metadata.name; data.url = href; + data.password = file.password; if (file.metadata.type.slice(0,6) === 'image/') { data.mediatag = true; } @@ -212,29 +216,61 @@ define([ queue.next(); }; - // Don't show the rename prompt if we don't want to store the file in the drive (avatar) - var showNamePrompt = !config.noStore; - - var promptName = function (file, cb) { + // Get the upload options + var fileUploadModal = function (file, cb) { var extIdx = file.name.lastIndexOf('.'); var name = extIdx !== -1 ? file.name.slice(0,extIdx) : file.name; var ext = extIdx !== -1 ? file.name.slice(extIdx) : ""; - var msg = Messages._getKey('upload_rename', [ - Util.fixHTML(file.name), - Util.fixHTML(ext) + + var createHelper = function (href, text) { + 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) { - showNamePrompt = false; - return void cb (file.name); - } - if (!newName || !newName.trim()) { return void cb (file.name); } + + UI.confirm(content, function (yes) { + if (!yes) { return void cb(); } + + // Get the values + 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 newExt = newExtIdx !== -1 ? newName.slice(newExtIdx) : ""; if (newExt !== ext) { newName += ext; } - cb(newName); - }, {cancel: Messages.doNotAskAgain}, true); + + cb({ + name: newName, + password: password, + owned: owned + }); + }); }; + var handleFileState = { queue: [], inProgress: false @@ -246,17 +282,23 @@ define([ var thumb; var file_arraybuffer; var name = file.name; - var finish = function () { - var metadata = { - name: name, - type: file.type, - }; - if (thumb) { metadata.thumbnail = thumb; } - queue.push({ - blob: file_arraybuffer, - metadata: metadata, - dropEvent: e - }); + var password; + var owned = true; + var finish = function (abort) { + if (!abort) { + var metadata = { + name: name, + type: file.type, + }; + if (thumb) { metadata.thumbnail = thumb; } + queue.push({ + blob: file_arraybuffer, + metadata: metadata, + password: password, + owned: owned, + dropEvent: e + }); + } handleFileState.inProgress = false; if (handleFileState.queue.length) { var next = handleFileState.queue.shift(); @@ -264,9 +306,16 @@ define([ } }; var getName = function () { - if (!showNamePrompt) { return void finish(); } - promptName(file, function (newName) { - name = newName; + // If "noStore", it means we don't want to store this file in our drive (avatar) + // In this case, we don't want a password or a filename, and we own the file + 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(); }); }; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index c06c1e376..0346bde2c 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -150,12 +150,12 @@ define([ todo(); } else { // 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 var correctPassword = waitFor(); sframeChan.on('Q_PAD_PASSWORD_VALUE', function (data, cb) { password = data; - Cryptpad.isNewChannel(window.location.href, password, function (e, isNew) { + var next = function (e, isNew) { if (Boolean(isNew)) { // Ask again in the inner iframe // We should receive a new Q_PAD_PASSWORD_VALUE @@ -165,7 +165,17 @@ define([ correctPassword(); 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"); } diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 4fc5a6ac6..572fe5f4a 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -113,16 +113,16 @@ define([ return ''; }; funcs.getMediatagFromHref = function (href) { - // PASSWORD_FILES - var parsed = Hash.parsePadUrl(href); - var secret = Hash.getSecrets('file', parsed.hash); + // XXX: Should only be used with the current href var data = ctx.metadataMgr.getPrivateData(); + var parsed = Hash.parsePadUrl(href); + var secret = Hash.getSecrets('file', parsed.hash, data.password); if (secret.keys && secret.channel) { - var cryptKey = secret.keys && secret.keys.fileKeyStr; - var hexFileName = Util.base64ToHex(secret.channel); + var key = Hash.encodeBase64(secret.keys && secret.keys.cryptKey); + var hexFileName = secret.channel; var origin = data.fileHost || data.origin; var src = origin + Hash.getBlobPathFromHex(hexFileName); - return '' + + return '' + ''; } return; diff --git a/www/common/userObject.js b/www/common/userObject.js index b05798946..fb39eb484 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -590,7 +590,6 @@ define([ } }, cb); } - console.log(path, newName); if (path.length <= 1) { logError('Renaming `root` is forbidden'); return; diff --git a/www/drive/inner.js b/www/drive/inner.js index b668429cd..d072992d6 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -1305,7 +1305,7 @@ define([ $span.attr('title', name); 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 // Remove the .hide() added by displayThumnail() because it hides the icon in // list mode too diff --git a/www/file/inner.js b/www/file/inner.js index 3d752d95e..78d162f5e 100644 --- a/www/file/inner.js +++ b/www/file/inner.js @@ -54,17 +54,14 @@ define([ var uploadMode = false; var secret; - var hexFileName; var metadataMgr = common.getMetadataMgr(); var priv = metadataMgr.getPrivateData(); if (!priv.filehash) { uploadMode = true; } else { - // PASSWORD_FILES - secret = Hash.getSecrets('file', priv.filehash); + secret = Hash.getSecrets('file', priv.filehash, priv.password); if (!secret.keys) { throw new Error("You need a hash"); } - hexFileName = Util.base64ToHex(secret.channel); } var Title = common.createTitle({}); @@ -87,9 +84,10 @@ define([ toolbar.$rightside.html(''); if (!uploadMode) { + var hexFileName = secret.channel; var src = Hash.getBlobPathFromHex(hexFileName); - var cryptKey = secret.keys && secret.keys.fileKeyStr; - var key = Nacl.util.decodeBase64(cryptKey); + var key = secret.keys && secret.keys.cryptKey; + var cryptKey = Nacl.util.encodeBase64(key); FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) { if (e) { @@ -98,17 +96,27 @@ define([ } 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; Title.updateTitle(title || Title.defaultTitle); + toolbar.addElement(['pageTitle'], {pageTitle: title}); toolbar.$rightside.append(common.createButton('forget', true)); + toolbar.$rightside.append(common.createButton('properties', true)); if (common.isLoggedIn()) { toolbar.$rightside.append(common.createButton('hashtag', true)); } - - common.setPadAttribute('fileType', metadata.type); - var displayFile = function (ev, sizeMb, CB) { var called_back; var cb = function (e) { @@ -118,9 +126,7 @@ define([ }; var $mt = $dlview.find('media-tag'); - var cryptKey = secret.keys && secret.keys.fileKeyStr; - var hexFileName = Util.base64ToHex(secret.channel); - $mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName); + $mt.attr('src', src); $mt.attr('data-crypto-key', 'cryptpad:'+cryptKey); var rightsideDisplayed = false; @@ -263,7 +269,7 @@ define([ dropArea: $form, hoverArea: $label, 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); diff --git a/www/filepicker/inner.js b/www/filepicker/inner.js index 9dd05076a..c30ada772 100644 --- a/www/filepicker/inner.js +++ b/www/filepicker/inner.js @@ -40,14 +40,14 @@ define([ var parsed = Hash.parsePadUrl(data.url); hideFileDialog(); if (parsed.type === 'file') { - // PASSWORD_FILES - var hexFileName = Util.base64ToHex(parsed.hashData.channel); - var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; + var secret = Hash.getSecrets('file', parsed.hash, data.password); + var src = Hash.getBlobPathFromHex(secret.channel); + var key = Hash.encodeBase64(secret.keys.cryptKey); sframeChan.event("EV_FILE_PICKED", { type: parsed.type, src: src, name: data.name, - key: parsed.hashData.key + key: key }); return; } @@ -69,8 +69,8 @@ define([ APP.FM = common.createFileManager(fmConfig); // Create file picker - var onSelect = function (url, name) { - onFilePicked({url: url, name: name}); + var onSelect = function (url, name, password) { + onFilePicked({url: url, name: name, password: password}); }; var data = { FM: APP.FM @@ -135,11 +135,13 @@ define([ $('', {'class': 'cp-filepicker-content-element-name'}).text(name) .appendTo($span); $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 - common.displayThumbnail(data.href, data.channel, $span); + common.displayThumbnail(data.href, data.channel, data.password, $span); }); $input.focus(); }; diff --git a/www/pad/inner.js b/www/pad/inner.js index 5b150c419..b8199bc61 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -552,11 +552,11 @@ define([ ckeditor: editor, body: $('body'), onUploaded: function (ev, data) { - // PASSWORD_FILES var parsed = Hash.parsePadUrl(data.url); - var hexFileName = Util.base64ToHex(parsed.hashData.channel); - var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; - var mt = ''; + var secret = Hash.getSecrets('file', parsed.hash, data.password); + var src = Hash.getBlobPathFromHex(secret.channel); + var key = Hash.encodeBase64(secret.keys.cryptKey); + var mt = ''; // MEDIATAG var element = window.CKEDITOR.dom.element.createFromHtml(mt); editor.insertElement(element); diff --git a/www/slide/inner.js b/www/slide/inner.js index 18b7da4e3..497b0b8ca 100644 --- a/www/slide/inner.js +++ b/www/slide/inner.js @@ -500,11 +500,11 @@ define([ dropArea: $('.CodeMirror'), body: $('body'), onUploaded: function (ev, data) { - // PASSWORD_FILES var parsed = Hash.parsePadUrl(data.url); - var hexFileName = Util.base64ToHex(parsed.hashData.channel); - var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; - var mt = ''; + var secret = Hash.getSecrets('file', parsed.hash, data.password); + var src = Hash.getBlobPathFromHex(secret.channel); + var key = Hash.encodeBase64(secret.keys.cryptKey); + var mt = ''; editor.replaceSelection(mt); } };