404 lines
11 KiB
HTML
404 lines
11 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>CryptoPad</title>
|
|
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
<link href="lib/bootstrap.min.css" rel="stylesheet" />
|
|
<link href="cryptopad.css" rel="stylesheet" />
|
|
|
|
<script type="text/javascript" src="lib/sjcl.js"></script>
|
|
<script type="text/javascript" src="lib/jquery.min.js"></script>
|
|
<script type="text/javascript" src="lib/bootstrap.min.js"></script>
|
|
|
|
<script type="text/javascript">
|
|
"use strict";
|
|
var key;
|
|
var pads;
|
|
var curSel = null;
|
|
var backup;
|
|
var backupWaiting;
|
|
var curSelName = null;
|
|
var useRemote = true;
|
|
var waitingCount = 0;
|
|
|
|
function getKVSItem(key, func) {
|
|
if (useRemote) {
|
|
showLoader();
|
|
return $.getJSON("storage/" + key, createGetKVSCallback(func));
|
|
} else {
|
|
return func(localStorage.getItem(key));
|
|
}
|
|
}
|
|
|
|
function createGetKVSCallback(func) {
|
|
return function (arg) {
|
|
hideOnCompletion();
|
|
func(arg);
|
|
}
|
|
}
|
|
|
|
function showLoader() {
|
|
if (waitingCount == 0)
|
|
$("#loader").fadeIn();
|
|
waitingCount++;
|
|
}
|
|
|
|
function hideOnCompletion() {
|
|
waitingCount--;
|
|
if (waitingCount == 0)
|
|
$("#loader").fadeOut();
|
|
}
|
|
|
|
function deleteKVSItem(key) {
|
|
if (useRemote) {
|
|
showLoader();
|
|
return $.getJSON("storage/delete/" + key, hideOnCompletion);
|
|
} else {
|
|
localStorage.removeItem(key);
|
|
}
|
|
}
|
|
|
|
function setKVSItem(key, value) {
|
|
if (useRemote) {
|
|
showLoader();
|
|
$.post("storage/" + key, {value: value}, hideOnCompletion);
|
|
} else {
|
|
localStorage.setItem(key, value);
|
|
}
|
|
}
|
|
|
|
|
|
function hashSomething(something) {
|
|
var hmackey = sjcl.hash.sha256.hash(key);
|
|
var hmacr = new sjcl.misc.hmac(hmackey);
|
|
return sjcl.codec.hex.fromBits(hmacr.encrypt(something));
|
|
}
|
|
|
|
function hashTitle(title) {
|
|
return hashSomething("pad:"+title)
|
|
}
|
|
|
|
function supportsStorage() {
|
|
try {
|
|
return 'localStorage' in window && window['localStorage'] !== null;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function navbarHandler(e) {
|
|
loadNote(e.data);
|
|
}
|
|
|
|
function genPadItem(title) {
|
|
var titlehash = hashTitle(title);
|
|
var link = $('<a />').attr("href", "#").click(title, navbarHandler).text(title);
|
|
var item = $('<li/>').attr("id", titlehash).append(link);
|
|
return item;
|
|
}
|
|
|
|
function addPadList(title) {
|
|
$("#padList").append(genPadItem(title));
|
|
}
|
|
|
|
function saveNote() {
|
|
if ($("#title").val() == "") {
|
|
$("#title").val(new Date().toString())
|
|
}
|
|
var title = $("#title").val();
|
|
if (($.inArray(title, pads) != -1) && (title != curSelName)) {
|
|
alert("An item with this title already exists! Cannot save until this is corrected.");
|
|
return;
|
|
}
|
|
var titlehash = hashTitle(title);
|
|
var crypted = sjcl.encrypt(key, $("#data").val(), {iter: 2000, ks: 256});
|
|
setKVSItem(titlehash, crypted);
|
|
if(curSel != null && curSelName != title) {
|
|
pads[$.inArray(curSelName, pads)] = title;
|
|
var newSel = genPadItem(title);
|
|
curSel.replaceWith(newSel);
|
|
curSel = newSel;
|
|
curSel.addClass("active");
|
|
curSelName = title;
|
|
}
|
|
if ($.inArray(title, pads) == -1) {
|
|
pads.push(title);
|
|
addPadList(title);
|
|
setActiveTab(titlehash);
|
|
curSelName = title;
|
|
}
|
|
savePads();
|
|
$("#searchtext").attr("data-source", JSON.stringify(pads));
|
|
}
|
|
|
|
function savePads() {
|
|
var crypted = sjcl.encrypt(key, JSON.stringify(pads), {iter: 2000, ks: 256});
|
|
setKVSItem(hashSomething("pads"), crypted);
|
|
}
|
|
|
|
function setActiveTab(titlehash) {
|
|
if (curSel != null)
|
|
curSel.removeClass("active");
|
|
$("#" + titlehash).addClass("active");
|
|
curSel = $("#" + titlehash);
|
|
}
|
|
|
|
function createNoteCallback(title, titlehash) {
|
|
return function(notedata) {
|
|
if (notedata != null) {
|
|
try {
|
|
$("#data").val(sjcl.decrypt(key, notedata));
|
|
$("#title").val(title);
|
|
$("#searchtext").val("");
|
|
} catch (err) {
|
|
alert("Cryptography error: it's likely you entered the wrong passphrase. If you update, it WILL be overwritten.")
|
|
}
|
|
setActiveTab(titlehash);
|
|
curSelName = title;
|
|
} else {
|
|
alert("Pad does not exist.");
|
|
}
|
|
}
|
|
}
|
|
|
|
function loadNote(title) {
|
|
var titlehash = hashTitle(title);
|
|
getKVSItem(titlehash, createNoteCallback(title, titlehash));
|
|
}
|
|
|
|
function searchNote() {
|
|
var title = $("#searchtext").val();
|
|
loadNote(title);
|
|
return false;
|
|
}
|
|
|
|
function padsLoaded(paddata) {
|
|
if (paddata == null)
|
|
pads = new Array();
|
|
else {
|
|
try {
|
|
pads = JSON.parse(sjcl.decrypt(key, paddata));
|
|
} catch (err) {
|
|
alert("Cryptography error: it's likely you entered the wrong passphrase. If you update, it WILL be overwritten.")
|
|
}
|
|
}
|
|
|
|
for (var pad in pads) {
|
|
if (pads[pad] != null) {
|
|
addPadList(pads[pad]);
|
|
}
|
|
}
|
|
$("#searchtext").attr("data-source", JSON.stringify(pads));
|
|
}
|
|
|
|
function login() {
|
|
key = $("#password").val();
|
|
getKVSItem(hashSomething("pads"),padsLoaded);
|
|
$('#myModal').modal('hide')
|
|
return false; // for form.
|
|
}
|
|
|
|
|
|
function restoreKVS() {
|
|
var data = prompt("Please enter backup blob (any already existing pads will be overwritten!)");
|
|
var d = JSON.parse(data)
|
|
for (var k in d) {
|
|
|
|
setKVSItem(hashTitle(k), sjcl.encrypt(key, d[k], {iter: 2000, ks: 256}));
|
|
if ($.inArray(k, pads) == -1) {
|
|
pads.push(k);
|
|
addPadList(k);
|
|
}
|
|
}
|
|
savePads();
|
|
}
|
|
|
|
|
|
function backupKVS(pad) {
|
|
getKVSItem(hashTitle(pads[pad]), function(data) {
|
|
backup[pads[pad]] = sjcl.decrypt(key, data);
|
|
backupWaiting--;
|
|
doShowBackupDlg();
|
|
})
|
|
}
|
|
|
|
function doShowBackupDlg() {
|
|
if (backupWaiting == 0) {
|
|
$("#backupDlg").modal();
|
|
$("#backupText").val(JSON.stringify(backup))
|
|
}
|
|
}
|
|
|
|
function doBackup() {
|
|
backup = new Object();
|
|
backupWaiting = pads.length;
|
|
for (var pad = 0; pad < pads.length; pad++) {
|
|
if ((pads[pad] == null) || (typeof pads[pad] === 'undefined')) {
|
|
backupWaiting--;
|
|
doShowBackupDlg();
|
|
} else {
|
|
backupKVS(pad);
|
|
}
|
|
}
|
|
}
|
|
|
|
function deleteNote() {
|
|
var index = $.inArray($("#title").val(), pads);
|
|
if ((index != -1) && (curSel != null)) {
|
|
curSel.remove();
|
|
curSel = null;
|
|
deleteKVSItem(hashTitle(pads[index]));
|
|
delete pads[index];
|
|
savePads();
|
|
$("#title").val("");
|
|
$("#data").val("");
|
|
}
|
|
}
|
|
|
|
function newNote() {
|
|
if (curSel != null)
|
|
curSel.removeClass("active");
|
|
$("#title").val("");
|
|
$("#data").val("");
|
|
curSel = null;
|
|
curSelName = null;
|
|
}
|
|
|
|
function init() {
|
|
sjcl.random.startCollectors();
|
|
|
|
$("#data").keydown(function(e) {
|
|
if(e.keyCode === 9) { // from http://stackoverflow.com/questions/6140632/how-to-handle-tab-in-textarea
|
|
var start = this.selectionStart;
|
|
var end = this.selectionEnd;
|
|
|
|
var $this = $(this);
|
|
$this.val($this.val().substring(0, start) + "\t" + $this.val().substring(end));
|
|
this.selectionStart = this.selectionEnd = start + 1;
|
|
return false;
|
|
}
|
|
});
|
|
|
|
if (!supportsStorage()) {
|
|
alert("Your browser does not have Local Storage support. Come back when you've upgraded.");
|
|
}
|
|
|
|
// for web browsers refilling this on refresh.
|
|
$("#title").val("");
|
|
$("#data").val("");
|
|
|
|
$('.tabs').button()
|
|
$("#remoteButton").button('toggle');
|
|
|
|
|
|
$("#remoteButton").click(function() {
|
|
useRemote = true;
|
|
});
|
|
$("#localButton").click(function() {
|
|
useRemote = false;
|
|
});
|
|
|
|
$("#myModal").modal({backdrop: "static", keyboard: false}); // undocumented trick to not hide the dialog
|
|
|
|
var crypt_key=window.location.hash.substring(1);
|
|
|
|
if (crypt_key != "") {
|
|
console.log("poopfuck")
|
|
$("#password").val(crypt_key)
|
|
login()
|
|
}
|
|
|
|
|
|
// set up buttons
|
|
$("#backupCloseBtn").click(function () {
|
|
$("#backupDlg").modal("hide")
|
|
})
|
|
$("#backuper").click(doBackup);
|
|
$("#restorer").click(restoreKVS);
|
|
$("#submitter").click(saveNote);
|
|
$("#deleter").click(deleteNote);
|
|
$("#newer").click(newNote);
|
|
$("#searchform").submit(searchNote);
|
|
$("#loginForm").submit(login);
|
|
$("#loginBtn").click(login);
|
|
// place cursor in password field for quick access.
|
|
$("#password").select();
|
|
}
|
|
|
|
$(document).ready(init);
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<div class="modal hide" id="backupDlg">
|
|
<div class="modal-header">
|
|
<h3>Backup</h3>
|
|
</div>
|
|
<div class="modal-body">
|
|
<textarea rows="10" id="backupText">
|
|
|
|
</textarea>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button id="backupCloseBtn" class="btn btn-primary">Done</button>
|
|
</div>
|
|
</div>
|
|
<div class="modal" id="myModal">
|
|
<div class="modal-header">
|
|
<h3>Login to CryptoPad</h3>
|
|
</div>
|
|
<div class="modal-body">
|
|
Login to:
|
|
<div class="btn-group" data-toggle="buttons-radio">
|
|
<button class="btn" id="localButton">Local</button>
|
|
<button class="btn" id="remoteButton">Server</button>
|
|
</div> <br />
|
|
<form id="loginForm">
|
|
<input name="password" type="password" id="password" placeholder="Password" />
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button id="loginBtn" class="btn btn-primary">Login</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="navbar">
|
|
<div class="navbar-inner">
|
|
<div class="container-fluid">
|
|
<a class="brand" href="#">CryptoPad</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="container-fluid main">
|
|
<div class="main-inner row-fluid">
|
|
<div class="span2">
|
|
<form id="searchform" class="form-search">
|
|
<input autocomplete="off" id="searchtext" type="text" class="input-medium search-query" placeholder="Search" data-provide="typeahead" />
|
|
</form>
|
|
<ul id="padList" class="nav nav-pills nav-stacked">
|
|
</ul>
|
|
<button id="backuper" class="btn btn-inverse">Backup</button>
|
|
<button id="restorer" class="btn btn-inverse">Restore</button>
|
|
</div>
|
|
<div class="span10">
|
|
<div class="well">
|
|
<input type="text" id="title" class="input-medium" placeholder="Title" />
|
|
<div class="textarea-wrapper"><textarea id="data" rows="10"></textarea></div>
|
|
<button id="submitter" class="btn btn-primary">Save</button>
|
|
<button id="newer" class="btn btn-success">New</button>
|
|
<span id="loader" class="middle"><img alt="Working..." class="middle" src="loading.gif"></span>
|
|
<button id="deleter" class="btn btn-danger">Delete</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<footer id="footer">
|
|
Powered by CryptoPad - AGPL - <a href="https://gitorious.org/cryptopad">Source</a><br />
|
|
Secured using <a href="http://crypto.stanford.edu/sjcl/">SJCL</a> with AES-256 in CCM mode and PBKDF2 using SHA256 with 2000 iterations
|
|
</footer>
|
|
</body>
|
|
</html>
|