From ed5cf8b044b491a341b723f91f577383d53114ef Mon Sep 17 00:00:00 2001 From: Jermolene Date: Sun, 19 Jan 2014 20:13:55 +0000 Subject: [PATCH] Refactor importing of encrypted TiddlyWiki files so that it works on Node.js --- core/modules/deserializers.js | 13 ++++-- core/modules/utils/crypto.js | 80 +++++++++++++++++++++++++++++++++++ core/modules/wiki.js | 54 +++-------------------- 3 files changed, 96 insertions(+), 51 deletions(-) create mode 100644 core/modules/utils/crypto.js diff --git a/core/modules/deserializers.js b/core/modules/deserializers.js index 6c6119e25..89584c9ce 100644 --- a/core/modules/deserializers.js +++ b/core/modules/deserializers.js @@ -100,7 +100,7 @@ exports["text/html"] = function(text,fields) { var storeAreaMarkerRegExp = /
/gi, match = storeAreaMarkerRegExp.exec(text); if(match) { - // If so, it's either a classic TiddlyWiki file or a TW5 file + // If so, it's either a classic TiddlyWiki file or an unencrypted TW5 file // First read the normal tiddlers var results = deserializeTiddlyWikiFile(text,storeAreaMarkerRegExp.lastIndex,!!match[1],fields); // Then any system tiddlers @@ -111,8 +111,15 @@ exports["text/html"] = function(text,fields) { } return results } else { - // It's not a TiddlyWiki so we'll return the entire HTML file as a tiddler - return deserializeHtmlFile(text,fields); + // Check whether we've got an encrypted file + var encryptedStoreArea = $tw.utils.extractEncryptedStoreArea(text); + if(encryptedStoreArea) { + // If so, attempt to decrypt it using the current password + return $tw.utils.decryptStoreArea(encryptedStoreArea); + } else { + // It's not a TiddlyWiki so we'll return the entire HTML file as a tiddler + return deserializeHtmlFile(text,fields); + } } }; diff --git a/core/modules/utils/crypto.js b/core/modules/utils/crypto.js new file mode 100644 index 000000000..abeee27ca --- /dev/null +++ b/core/modules/utils/crypto.js @@ -0,0 +1,80 @@ +/*\ +title: $:/core/modules/utils/crypto.js +type: application/javascript +module-type: utils + +Utility functions related to crypto. + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Look for an encrypted store area in the text of a TiddlyWiki file +*/ +exports.extractEncryptedStoreArea = function(text) { + var encryptedStoreAreaStartMarker = "
",
+		encryptedStoreAreaStart = text.indexOf(encryptedStoreAreaStartMarker);
+	if(encryptedStoreAreaStart !== -1) {
+		var encryptedStoreAreaEnd = text.indexOf("
",encryptedStoreAreaStart); + if(encryptedStoreAreaEnd !== -1) { + return $tw.utils.htmlDecode(text.substring(encryptedStoreAreaStart + encryptedStoreAreaStartMarker.length,encryptedStoreAreaEnd-1)); + } + } + return null; +}; + +/* +Attempt to extract the tiddlers from an encrypted store area using the current password +*/ +exports.decryptStoreArea = function(encryptedStoreArea) { + var decryptedText = $tw.crypto.decrypt(encryptedStoreArea); + if(decryptedText) { + var json = JSON.parse(decryptedText), + tiddlers = []; + for(var title in json) { + tiddlers.push(json[title]); + } + return tiddlers; + } else { + return null; + } +}; + +exports.decryptStoreAreaInteractive = function(encryptedStoreArea,callback) { + // Try to decrypt with the current password + var tiddlers = $tw.utils.decryptStoreArea(encryptedStoreArea); + if(tiddlers) { + callback(tiddlers); + } else { + // Prompt for a new password and keep trying + $tw.passwordPrompt.createPrompt({ + serviceName: "Enter a password to decrypt the imported TiddlyWiki", + noUserName: true, + canCancel: true, + submitText: "Decrypt", + callback: function(data) { + // Exit if the user cancelled + if(!data) { + return false; + } + // Attempt to decrypt the tiddlers + $tw.crypto.setPassword(data.password); + var tiddlers = $tw.utils.decryptStoreArea(encryptedStoreArea); + if(tiddlers) { + callback(tiddlers); + // Exit and remove the password prompt + return true; + } else { + // We didn't decrypt everything, so continue to prompt for password + return false; + } + } + }); + } +}; + +})(); diff --git a/core/modules/wiki.js b/core/modules/wiki.js index 866b4dbda..5c21f01d9 100755 --- a/core/modules/wiki.js +++ b/core/modules/wiki.js @@ -1083,56 +1083,14 @@ exports.readFile = function(file,callback) { } } else { // Check whether this is an encrypted TiddlyWiki file - var encryptedStoreAreaStartMarker = "
",
-				encryptedStoreAreaStart = text.indexOf(encryptedStoreAreaStartMarker),
-				encryptedStoreAreaEnd = encryptedStoreAreaStart !== -1 ? text.indexOf("
",encryptedStoreAreaStart) : -1; - if(encryptedStoreAreaStart !== -1 && encryptedStoreAreaEnd !== -1) { - var encryptedJson = $tw.utils.htmlDecode(text.substring(encryptedStoreAreaStart + encryptedStoreAreaStartMarker.length,encryptedStoreAreaEnd-1)), - attemptDecryption = function() { - var decryptedText = $tw.crypto.decrypt(encryptedJson); - if(decryptedText) { - var json = JSON.parse(decryptedText), - tiddlers = []; - for(var title in json) { - tiddlers.push(json[title]); - } - return tiddlers; - } else { - return null; - } - }; - // Try to decrypt with the current password - var tiddlers = attemptDecryption(); - if(tiddlers) { + var encryptedJson = $tw.utils.extractEncryptedStoreArea(text); + if(encryptedJson) { + // If so, attempt to decrypt it with the current password + $tw.utils.decryptStoreAreaInteractive(encryptedJson,function(tiddlers) { callback(tiddlers); - } else { - // Prompt for a new password and keep trying - $tw.passwordPrompt.createPrompt({ - serviceName: "Enter a password to decrypt the imported TiddlyWiki", - noUserName: true, - canCancel: true, - submitText: "Decrypt", - callback: function(data) { - // Exit if the user cancelled - if(!data) { - return false; - } - // Attempt to decrypt the tiddlers - $tw.crypto.setPassword(data.password); - var tiddlers = attemptDecryption(); - if(tiddlers) { - callback(tiddlers); - // Exit and remove the password prompt - return true; - } else { - // We didn't decrypt everything, so continue to prompt for password - return false; - } - } - }); - } + }); } else { - // Try to deserialise any tiddlers in the file + // Otherwise, just try to deserialise any tiddlers in the file callback(self.deserializeTiddlers(type,text,tiddlerFields)); } }