2018-07-01 17:49:54 +02:00
|
|
|
<style>
|
2018-07-01 19:26:53 +02:00
|
|
|
body{
|
|
|
|
display: grid;
|
|
|
|
grid-template: "menu menu menu" 2em
|
|
|
|
"main main main" auto
|
|
|
|
"left middle right" 2em
|
|
|
|
/ 6em auto 6em;
|
|
|
|
height: 100%;
|
|
|
|
grid-gap: 1em;
|
|
|
|
margin: 0;
|
|
|
|
background: lightgrey;
|
|
|
|
box-sizing: border-box;
|
|
|
|
padding: 1em;
|
|
|
|
}
|
|
|
|
|
|
|
|
menu { grid-area: menu }
|
|
|
|
input[type=file] { display: none; }
|
|
|
|
|
|
|
|
ui-data {
|
|
|
|
grid-area: main;
|
|
|
|
margin-top: -1.05em;
|
|
|
|
height: calc(100% + 1.05em);
|
|
|
|
}
|
|
|
|
|
|
|
|
button[encrypt] { grid-area: left }
|
|
|
|
pass { grid-area: middle }
|
|
|
|
button[decrypt] { grid-area: right }
|
|
|
|
|
|
|
|
button[encrypt],
|
|
|
|
button[decrypt] {
|
|
|
|
font-weight: bolder;
|
|
|
|
text-transform: uppercase;
|
|
|
|
}
|
|
|
|
menu {
|
|
|
|
display: flex;
|
|
|
|
height: 2em;
|
|
|
|
margin: 0;
|
|
|
|
padding: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
menu > button {
|
|
|
|
font-size: 1em;
|
|
|
|
height: 2em;
|
|
|
|
width: 2em;
|
|
|
|
margin-left: -1px;
|
|
|
|
}
|
|
|
|
|
|
|
|
input[filename] { flex: 1; padding: 0 .5em; }
|
|
|
|
|
|
|
|
pass {
|
|
|
|
position: relative;
|
|
|
|
height: 2em;
|
|
|
|
border: 1px solid darkgrey;
|
|
|
|
overflow: hidden;
|
|
|
|
}
|
|
|
|
pass input { filter: blur(5px); width: 100%; border: none;
|
|
|
|
height: 100%;
|
|
|
|
padding: 0 .5em; }
|
|
|
|
pass i { position: absolute; top: .5em; right: .5em;}
|
|
|
|
pass input.clear { filter: blur(0) }
|
|
|
|
|
|
|
|
ui-toast {
|
|
|
|
position: fixed;
|
|
|
|
text-align: center;
|
|
|
|
background: lightblue;
|
|
|
|
top: 50%;
|
|
|
|
left: 50%;
|
|
|
|
width: 20em;
|
|
|
|
margin: -1em -10em;
|
|
|
|
height: 2em;
|
|
|
|
}
|
2018-07-01 17:49:54 +02:00
|
|
|
</style>
|
2018-07-01 19:26:53 +02:00
|
|
|
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/solid.css" integrity="sha384-TbilV5Lbhlwdyc4RuIV/JhD8NR+BfMrvz4BL5QFa2we1hQu6wvREr3v6XSRfCTRp" crossorigin="anonymous">
|
|
|
|
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/regular.css" integrity="sha384-avJt9MoJH2rB4PKRsJRHZv7yiFZn8LrnXuzvmZoD3fh1aL6aM6s0BBcnCvBe6XSD" crossorigin="anonymous">
|
|
|
|
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/fontawesome.css" integrity="sha384-ozJwkrqb90Oa3ZNb+yKFW2lToAWYdTiF1vt8JiH5ptTGHTGcN7qdoR1F95e0kYyG" crossorigin="anonymous">
|
|
|
|
|
|
|
|
|
|
|
|
<menu>
|
|
|
|
<button disabled select title="Select text on the page"
|
|
|
|
onclick="">
|
|
|
|
<i class="fas fa-align-center"></i>
|
|
|
|
</button>
|
|
|
|
<button disabled point title="Point the element in the page"
|
|
|
|
onclick="">
|
|
|
|
<i class="fas fa-mouse-pointer"></i>
|
|
|
|
</button>
|
|
|
|
<button page title="Load the whole page content"
|
|
|
|
onclick="$text.value = opener.document.body.textContent">
|
|
|
|
<i class="far fa-file"></i>
|
|
|
|
</button>
|
|
|
|
<button open title="Load a file from disk"
|
|
|
|
onclick="$`[type=file]`.click()">
|
|
|
|
<i class="far fa-folder-open"></i>
|
|
|
|
<input type="file" onchange="read(this.files[0]).then( s=> $text.value = s ); $filename.value = this.files[0].name"/>
|
|
|
|
</button>
|
|
|
|
<input filename type="text"/>
|
|
|
|
<button save title="Save to disk"
|
|
|
|
onclick="save($text.value,'cryptor.txt').catch(bubbleOn(this))">
|
|
|
|
<i class="far fa-save"></i>
|
|
|
|
</button>
|
|
|
|
</menu>
|
|
|
|
<ui-data></ui-data>
|
|
|
|
|
|
|
|
<button encrypt onclick="encrypt2($text.value).then(res=>$text.value=res,bubbleOn(this))">encrypt</button>
|
|
|
|
<pass>
|
|
|
|
<input type="text" placeholder="Enter a password"/>
|
|
|
|
<i class="far fa-eye-slash" onmouseover="$pass.classList.add('clear');this.classList.remove('fa-eye-slash');this.classList.add('fa-eye')"
|
|
|
|
onmouseout="$pass.classList.remove('clear');this.classList.add('fa-eye-slash');this.classList.remove('fa-eye')"></i>
|
|
|
|
</pass>
|
|
|
|
<button decrypt onclick="decrypt($text.value).catch(bubbleOn(this))">decrypt</button>
|
|
|
|
|
|
|
|
<template ui-data>
|
|
|
|
<style>
|
|
|
|
textarea {
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
resize: none;
|
|
|
|
padding-bottom: 2em;
|
|
|
|
}
|
|
|
|
button {
|
|
|
|
position: relative;
|
|
|
|
top: -1.6em;
|
|
|
|
margin-right: -1px;
|
|
|
|
}
|
|
|
|
|
|
|
|
:host-context([bin]) [bin] { color: rebeccapurple }
|
|
|
|
:host-context([uint]) [uint] { color: rebeccapurple }
|
|
|
|
:host-context([utf8]) [utf8] { color: rebeccapurple }
|
|
|
|
:host-context([base64]) [base64] { color: rebeccapurple }
|
|
|
|
:host-context([frame]) [frame] { color: rebeccapurple }
|
|
|
|
</style>
|
|
|
|
<textarea></textarea>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script ui>
|
|
|
|
const _s = (ss, ...pp)=> ss.map((s,i)=> s + (pp[i]||'')).join('')
|
|
|
|
, _a = iterable=> iterable.length > 1 ? iterable : iterable[0]
|
|
|
|
, Q = (ss, ...pp)=> Array.from( document.querySelectorAll( _s(ss, ...pp)) )
|
|
|
|
, T = document.createElement('template')
|
|
|
|
, DOM = (ss, ...pp)=> ( T.innerHTML = _s(ss, ...pp), document.adoptNode(T.content).childNodes )
|
|
|
|
, $ = (ss, ...pp)=> _a( ~_s(ss, ...pp).indexOf('<') ? DOM(ss, ...pp) : Q(ss, ...pp) )
|
|
|
|
|
|
|
|
, notify = (html,duration)=> $`body`.add( $`<ui-toast ${duration?`duration="${duration}"`:''}>${html}</ui-toast>` )
|
|
|
|
, bubbleOn = el=> e=> el.dispatchEvent( new ErrorEvent('Error',{bubbles:true, error:e}) )
|
|
|
|
, parseAlgo = str=> ({ name: str.split(' ')[0], iv: Uint8Array.from(str.split(' ').slice(1)) })
|
|
|
|
, pulse = color=> (document.body.style.background = color) && setTimeout(o=> document.body.style.background = 'lightgrey', 1000)
|
|
|
|
|
|
|
|
Node.prototype.add = function( ...els )
|
|
|
|
{
|
|
|
|
this.append( ...els )
|
|
|
|
return _a( els )
|
|
|
|
}
|
|
|
|
Node.prototype.$ = function(ss, ...pp)
|
|
|
|
{
|
|
|
|
return _a(
|
|
|
|
~_s(ss, ...pp).indexOf('<')
|
|
|
|
? this.add(...DOM(ss, ...pp))
|
|
|
|
: Array.from( this.querySelectorAll( _s(ss, ...pp)) )
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
class UI extends HTMLElement {
|
|
|
|
static get is(){ return 'ui' + this.name.replace(/[A-Z]/g, L=> '-'+L.toLocaleLowerCase()) }
|
|
|
|
constructor()
|
|
|
|
{
|
|
|
|
super()
|
|
|
|
let tpl = $`template[${this.constructor.is}]`
|
|
|
|
tpl && this.attachShadow({mode: 'open'})
|
|
|
|
.appendChild( tpl.content.cloneNode(true) )
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Toast extends UI {
|
|
|
|
static get duration(){ return this._duration || 5000000 }
|
|
|
|
static set duration( v ){ this._duration = v }
|
|
|
|
connectedCallback()
|
|
|
|
{
|
|
|
|
setTimeout( toast=> toast.remove(), this.getAttribute('duration') || Toast.duration, this )
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class Data extends UI {
|
|
|
|
static get observedAttributes(){ return ['bin','uint','utf8','base64','frame'] }
|
|
|
|
connectedCallback()
|
|
|
|
{
|
|
|
|
// this.observer = new MutationObserver(
|
|
|
|
// changes=> console.log(changes.filter(m=>~['bin','255','utf8','base64','object'].indexOf(m.attributeName)) )
|
|
|
|
// )
|
|
|
|
// .observe( this, { attributes: true } )
|
|
|
|
this.shadowRoot.append( ...Data.observedAttributes.map( type=> $`<button ${type}>${type}</button>` ) )
|
|
|
|
this.shadowRoot.$`button`.map( button=> button.onclick = e=> {
|
|
|
|
[...this.attributes].map( o=> o.ownerElement.removeAttributeNode(o) )
|
|
|
|
this.setAttribute( e.target.innerText, '' )
|
|
|
|
})
|
|
|
|
}
|
|
|
|
get value(){ return this.shadowRoot.$`textarea`.value }
|
|
|
|
set value( v ){ return this.shadowRoot.$`textarea`.value = v }
|
|
|
|
attributeChangedCallback(name, oldValue, newValue)
|
|
|
|
{
|
|
|
|
console.log( arguments )
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
;[ Toast, Data ]
|
|
|
|
.map( klass=> customElements.define(klass.is, klass) )
|
|
|
|
|
|
|
|
document.body.addEventListener('Error', e=> pulse('red') && (notify(e.error.message).style.color = 'red') )
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<script app>
|
|
|
|
const algo = { name: "AES-GCM", iv: Uint8Array.from([120,1,248,135,62,71,87,156,92,67,155,37]) }
|
|
|
|
, algos = [ "AES-GCM 120 1 248 135 62 71 87 156 92 67 155 37" ]
|
|
|
|
//, algo = parseAlgo(algos[0])
|
|
|
|
, $pass = $`pass input`
|
|
|
|
, $text = $`ui-data`
|
|
|
|
, $filename = $`[filename]`
|
|
|
|
|
|
|
|
let key
|
|
|
|
$pass.onchange = async e=>
|
|
|
|
key = await crypto.subtle.importKey(
|
|
|
|
'raw'
|
|
|
|
, await crypto.subtle.digest( 'SHA-256', new TextEncoder().encode($pass.value) )
|
|
|
|
, algo, false, ['encrypt','decrypt']
|
|
|
|
)
|
2018-07-01 17:49:54 +02:00
|
|
|
|
|
|
|
const encrypt = ()=> {
|
2018-07-01 19:26:53 +02:00
|
|
|
return crypto.subtle.digest( 'SHA-256', new TextEncoder().encode($pass.value) )
|
2018-07-01 17:49:54 +02:00
|
|
|
.then( pwHash=> crypto.subtle.importKey('raw', pwHash, algo, false, ['encrypt']) )
|
|
|
|
.then( key=> crypto.subtle.encrypt(algo, key, new TextEncoder().encode($text.value)) )
|
2018-07-01 19:26:53 +02:00
|
|
|
//.then( ctBuffer=> $text.value = new Uint8Array(ctBuffer).toString().replace(/,/g,'O') )
|
|
|
|
.then( ctBuffer=> $text.value = uint2String( new Uint8Array(ctBuffer) ) )
|
|
|
|
//.then( ctBuffer=> $text.value = new TextDecoder().decode(new Uint8Array(ctBuffer)) )
|
|
|
|
|
|
|
|
//.catch( e=> pulse('red') )
|
2018-07-01 17:49:54 +02:00
|
|
|
}
|
2018-07-01 19:26:53 +02:00
|
|
|
const encrypt2 = str=>
|
|
|
|
crypto.subtle.encrypt( algo, key, new TextEncoder().encode(str) )
|
|
|
|
//.then( ctBuffer=> $text.value = new Uint8Array(ctBuffer).toString().replace(/,/g,'O') )
|
|
|
|
.then( ctBuffer=> uint2String(new Uint8Array(ctBuffer)) )
|
|
|
|
//.then( ctBuffer=> $text.value = new TextDecoder().decode(new Uint8Array(ctBuffer)) )
|
|
|
|
|
|
|
|
//.catch( e=> pulse('red') )
|
|
|
|
|
2018-07-01 17:49:54 +02:00
|
|
|
|
|
|
|
const decrypt = ()=> {
|
2018-07-01 19:26:53 +02:00
|
|
|
return crypto.subtle.digest( 'SHA-256', new TextEncoder().encode($pass.value) )
|
2018-07-01 17:49:54 +02:00
|
|
|
.then( pwHash=> crypto.subtle.importKey('raw', pwHash, algo, false, ['decrypt']) )
|
2018-07-01 19:26:53 +02:00
|
|
|
//.then( key=> crypto.subtle.decrypt(algo, key, Uint8Array.from($text.value.split('O').map(Number)).buffer ) )
|
|
|
|
.then( key=> crypto.subtle.decrypt(algo, key, string2Uint($text.value).buffer ) )
|
|
|
|
//.then( key=> crypto.subtle.decrypt(algo, key, new TextEncoder().encode($text.value).buffer ) )
|
2018-07-01 17:49:54 +02:00
|
|
|
.then( ptBuffer=> $text.value = new TextDecoder().decode(ptBuffer) )
|
2018-07-01 19:26:53 +02:00
|
|
|
|
|
|
|
//.catch( e=> pulse('red') )
|
2018-07-01 17:49:54 +02:00
|
|
|
}
|
2018-07-01 19:26:53 +02:00
|
|
|
|
|
|
|
var lastSaved
|
|
|
|
const save = ( content, filename )=> {
|
|
|
|
let link = document.createElement('a')
|
|
|
|
link.setAttribute( 'download', filename )
|
|
|
|
// If we are replacing a previously generated file we need to
|
|
|
|
// manually revoke the object URL to avoid memory leaks.
|
|
|
|
lastSaved && URL.revokeObjectURL( lastSaved )
|
|
|
|
link.href = lastSaved = URL.createObjectURL( new Blob([content], {type: 'text/plain'}) )
|
|
|
|
//link.href = makeTextFile( content )
|
|
|
|
document.body.appendChild( link )
|
|
|
|
|
|
|
|
// wait for the link to be added to the document
|
|
|
|
window.requestAnimationFrame( e=> {
|
|
|
|
link.dispatchEvent( new MouseEvent('click') )
|
|
|
|
document.body.removeChild( link )
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
const read = file=> new Promise( (ok,ko)=> {
|
|
|
|
let r = new FileReader()
|
|
|
|
r.onload = e=> ok( r.result )
|
|
|
|
r.onerror = e=> ko()
|
|
|
|
r.readAsText( file )
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
var textFile
|
|
|
|
const makeTextFile = text=> {
|
|
|
|
|
|
|
|
textFile && URL.revokeObjectURL(textFile)
|
|
|
|
textFile = URL.createObjectURL( new Blob([text], {type: 'text/plain'}) )
|
|
|
|
|
|
|
|
// returns a URL you can use as a href
|
|
|
|
return textFile
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const save2 = (file, content, type)=> window.open( "data:application/octet-stream," + encodeURIComponent(content), file )
|
|
|
|
|
|
|
|
const uint2String = uintArray=> String.fromCharCode.apply( null, uintArray )
|
|
|
|
|
|
|
|
const string2Uint = string=> new Uint8Array(
|
|
|
|
string.split('')
|
|
|
|
.map( l=>l.charCodeAt(0) )
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
/*const uintToString = (uintArray)=> {
|
|
|
|
var encodedString = String.fromCharCode.apply(null, uintArray),
|
|
|
|
decodedString = decodeURIComponent(escape(encodedString));
|
|
|
|
return decodedString;
|
|
|
|
}
|
|
|
|
function stringToUint(string) {
|
|
|
|
var string = btoa(unescape(encodeURIComponent(string))),
|
|
|
|
charList = string.split(''),
|
|
|
|
uintArray = [];
|
|
|
|
for (var i = 0; i < charList.length; i++) {
|
|
|
|
uintArray.push(charList[i].charCodeAt(0));
|
|
|
|
}
|
|
|
|
return new Uint8Array(uintArray);
|
|
|
|
}
|
|
|
|
|
|
|
|
*/
|
2018-07-01 17:49:54 +02:00
|
|
|
</script>
|