Files
cryptopad/static/index.html
2023-03-15 19:27:25 -04:00

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>