forked from Kieran/void.cat
add file encryption and upload logic
This commit is contained in:
parent
00a2a62c89
commit
958bd7d300
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +1,6 @@
|
||||
.vs/
|
||||
bin/
|
||||
obj/
|
||||
*.csproj.user
|
||||
*.csproj.user
|
||||
*.min.*
|
||||
dist/
|
22
index.html
22
index.html
@ -3,12 +3,21 @@
|
||||
|
||||
<head>
|
||||
<title>void.cat</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="dist/style.css"/>
|
||||
<template id="tmpl-upload">
|
||||
<div class="upload">
|
||||
<div class="file-info"></div>
|
||||
<div class="status"></div>
|
||||
<div class="file-info">
|
||||
<div class="file-info-name"></div>
|
||||
<div class="file-info-speed"></div>
|
||||
<div class="file-info-size"></div>
|
||||
</div>
|
||||
<div class="status">
|
||||
<div class="status-state"></div>
|
||||
<div class="status-speed"></div>
|
||||
<div class="status-key"></div>
|
||||
</div>
|
||||
<div class="upload-progress">
|
||||
<span></span>
|
||||
<div></div>
|
||||
@ -19,11 +28,12 @@
|
||||
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="float-left">
|
||||
<div id="dropzone" class="left-module">Click me!</div>
|
||||
<div id="stats" class="left-module">asdf</div>
|
||||
<div class="page-left">
|
||||
<div id="dropzone">Click me!</div>
|
||||
<div id="stats">asdf</div>
|
||||
</div>
|
||||
<div id="uploads" class="page-right">
|
||||
</div>
|
||||
<div id="uploads" class="float-left"></div>
|
||||
</div>
|
||||
<script type="text/javascript" src="src/js/script.js" async></script>
|
||||
<script type="text/javascript" src="src/js/ripemd160.js" async></script>
|
||||
|
@ -21,7 +21,6 @@ html, body {
|
||||
|
||||
.page {
|
||||
width: $page-width;
|
||||
min-height: $page-height;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: $page-margin-top;
|
||||
@ -31,15 +30,19 @@ html, body {
|
||||
background-color: rgb(233, 252, 255);
|
||||
box-shadow: 0px 0px 15px 5px #000;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.float-left {
|
||||
float: left;
|
||||
.page-left{
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.left-module {
|
||||
height: $dropzone-height;
|
||||
width: $dropzone-width;
|
||||
.page-right{
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#dropzone {
|
||||
@ -49,16 +52,13 @@ html, body {
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
#uploads {
|
||||
float: left;
|
||||
width: ($page-width / 2);
|
||||
}
|
||||
|
||||
.upload {
|
||||
overflow: hidden;
|
||||
border: $upload-border;
|
||||
margin: $upload-padding;
|
||||
margin-top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.upload .upload-progress {
|
||||
@ -69,14 +69,49 @@ html, body {
|
||||
}
|
||||
|
||||
.upload .status {
|
||||
padding: 5px 5px 5px 10px;
|
||||
display: flex;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.upload .status-state {
|
||||
order: 1;
|
||||
flex-grow: 3;
|
||||
}
|
||||
|
||||
.upload .status-speed {
|
||||
order: 2;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.upload .status-key {
|
||||
order: 3;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.upload .file-info {
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
border-bottom: $upload-border;
|
||||
background-color: #aaa;
|
||||
display: flex;
|
||||
background-color: rgb(96, 136, 146);
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.upload .file-info div {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.upload .file-info .file-info-name {
|
||||
order: 1;
|
||||
flex-grow: 4;
|
||||
}
|
||||
|
||||
.upload .file-info .file-info-speed {
|
||||
order: 2;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.upload .file-info .file-info-size {
|
||||
order: 2;
|
||||
flex-grow: 1;
|
||||
background-color: rgb(0, 84, 99);
|
||||
}
|
||||
|
||||
.upload .upload-progress div {
|
||||
|
183
src/js/script.js
183
src/js/script.js
@ -1,3 +1,8 @@
|
||||
|
||||
/**
|
||||
* @constant {string} - The encryption algoritm to use for file uploads
|
||||
*/
|
||||
const EncryptionAlgo = 'AES-GCM';
|
||||
/**
|
||||
* @constant {number} - Size of 1 kiB
|
||||
*/
|
||||
@ -126,7 +131,21 @@ const App = {
|
||||
* @param {[object]} data - Request payload (method must be post)
|
||||
* @returns {Promise<XMLHttpRequest>} The completed request
|
||||
*/
|
||||
const XHR = function(method, url, data){
|
||||
const JsonXHR = async function (method, url, data) {
|
||||
return await XHR(method, url, JSON.stringify(data), {
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a HTTP request with promise
|
||||
* @param {string} method - HTTP method for this request
|
||||
* @param {string} url - Request URL
|
||||
* @param {[*]} data - Request payload (method must be post)
|
||||
* @param {[*]} headers - Headers to add to the request
|
||||
* @returns {Promise<XMLHttpRequest>} The completed request
|
||||
*/
|
||||
const XHR = function (method, url, data, headers, progress) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
let x = new XMLHttpRequest();
|
||||
x.onreadystatechange = function (ev) {
|
||||
@ -134,13 +153,24 @@ const XHR = function(method, url, data){
|
||||
resolve(ev.target);
|
||||
}
|
||||
};
|
||||
x.upload.onprogress = function(ev){
|
||||
if(typeof progress === "function"){
|
||||
progress(ev);
|
||||
}
|
||||
};
|
||||
x.onerror = function (ev) {
|
||||
reject(ev);
|
||||
};
|
||||
x.open(method, url, true);
|
||||
if (method === "POST" && typeof data === "object" && data !== null) {
|
||||
x.setRequestHeader('Content-Type', 'application/json');
|
||||
x.send(JSON.stringify(data));
|
||||
|
||||
//set headers if they are passed
|
||||
if(typeof headers === "object"){
|
||||
for(let x in headers){
|
||||
x.setRequestHeader(x, headers[x]);
|
||||
}
|
||||
}
|
||||
if (method === "POST" && typeof data !== "undefined") {
|
||||
x.send(data);
|
||||
} else {
|
||||
x.send();
|
||||
}
|
||||
@ -154,7 +184,7 @@ const XHR = function(method, url, data){
|
||||
const DropzoneManager = function (dz) {
|
||||
this.dz = dz;
|
||||
|
||||
this.OpenFileSelect = function(ev){
|
||||
this.OpenFileSelect = function (ev) {
|
||||
let i = document.createElement('input');
|
||||
i.setAttribute('type', 'file');
|
||||
i.setAttribute('multiple', '');
|
||||
@ -166,7 +196,7 @@ const DropzoneManager = function (dz) {
|
||||
}.bind(this));
|
||||
i.click();
|
||||
};
|
||||
|
||||
|
||||
this.dz.addEventListener('click', this.OpenFileSelect.bind(this), false);
|
||||
};
|
||||
|
||||
@ -179,6 +209,28 @@ const FileUpload = function (file) {
|
||||
this.hasCrypto = typeof window.crypto.subtle === "object";
|
||||
this.file = file;
|
||||
this.domNode = null;
|
||||
this.key = null;
|
||||
this.iv = new Uint32Array(16);
|
||||
|
||||
this.uploadStats = {
|
||||
lastRate: 0,
|
||||
lastLoaded: 0,
|
||||
lastProgress: 0
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
this.HexKey = async () => {
|
||||
return App.Utils.ArrayToHex(await crypto.subtle.exportKey('raw', this.key));
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
this.HexIV = () => {
|
||||
return App.Utils.ArrayToHex(this.iv);
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads the file and SHA256 hashes it
|
||||
@ -200,13 +252,15 @@ const FileUpload = function (file) {
|
||||
this.HandleProgress('state-hash-start');
|
||||
crypto.subtle.digest("SHA-256", ev.target.result).then(function (hash) {
|
||||
this.HandleProgress('state-hash-end');
|
||||
this.HandleProgress('progress-hash-file', 1); //no progress from crypto.subtle.digest so we cant show any progress
|
||||
resolve(hash);
|
||||
resolve({
|
||||
h256: hash,
|
||||
data: ev.target.result
|
||||
});
|
||||
}.bind(this));
|
||||
}.bind(this);
|
||||
|
||||
fr.onprogress = function (ev) {
|
||||
this.HandleProgress('progress-load-file', ev.loaded / parseFloat(ev.total));
|
||||
this.HandleProgress('progress', ev.loaded / parseFloat(ev.total));
|
||||
}.bind(this);
|
||||
|
||||
fr.onerror = function (ev) {
|
||||
@ -233,8 +287,8 @@ const FileUpload = function (file) {
|
||||
* Sets the status label for this upload
|
||||
* @param {string} value - The status label
|
||||
*/
|
||||
this.SetStatus = function (value){
|
||||
this.domNode.status.textContent = `Status: ${value}`;
|
||||
this.SetStatus = function (value) {
|
||||
this.domNode.state.textContent = `Status: ${value}`;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -250,7 +304,7 @@ const FileUpload = function (file) {
|
||||
break;
|
||||
}
|
||||
case 'state-load-end': {
|
||||
|
||||
this.SetProgressBar(1);
|
||||
break;
|
||||
}
|
||||
case 'state-hash-start': {
|
||||
@ -259,7 +313,25 @@ const FileUpload = function (file) {
|
||||
break;
|
||||
}
|
||||
case 'state-hash-end': {
|
||||
|
||||
this.SetProgressBar(1);
|
||||
break;
|
||||
}
|
||||
case 'state-pre-check-start': {
|
||||
this.SetStatus('Checking file info..');
|
||||
this.SetProgressBar(0);
|
||||
break;
|
||||
}
|
||||
case 'state-pre-check-end': {
|
||||
this.SetProgressBar(1);
|
||||
break;
|
||||
}
|
||||
case 'state-encrypt-start': {
|
||||
this.SetStatus('Encrypting..');
|
||||
this.SetProgressBar(0);
|
||||
break;
|
||||
}
|
||||
case 'state-encrypt-end': {
|
||||
this.SetProgressBar(1);
|
||||
break;
|
||||
}
|
||||
case 'state-upload-start': {
|
||||
@ -268,14 +340,10 @@ const FileUpload = function (file) {
|
||||
break;
|
||||
}
|
||||
case 'state-upload-end': {
|
||||
|
||||
this.SetProgressBar(1);
|
||||
break;
|
||||
}
|
||||
case 'progress-load-file': {
|
||||
this.SetProgressBar(progress < 0.01 ? 0.01 : progress);
|
||||
break;
|
||||
}
|
||||
case 'progress-hash-file': {
|
||||
case 'progress': {
|
||||
this.SetProgressBar(progress < 0.01 ? 0.01 : progress);
|
||||
break;
|
||||
}
|
||||
@ -300,16 +368,72 @@ const FileUpload = function (file) {
|
||||
*/
|
||||
this.CreateNode = function () {
|
||||
let nelm = document.importNode(App.Templates.Upload.content, true);
|
||||
nelm.fileInfo = nelm.querySelector('.file-info');
|
||||
nelm.filename = nelm.querySelector('.file-info .file-info-name');
|
||||
nelm.filesize = nelm.querySelector('.file-info .file-info-size');
|
||||
nelm.progress = nelm.querySelector('.upload-progress span');
|
||||
nelm.progressBar = nelm.querySelector('.upload-progress div');
|
||||
nelm.status = nelm.querySelector('.status');
|
||||
nelm.state = nelm.querySelector('.status .status-state');
|
||||
nelm.speed = nelm.querySelector('.status .status-speed');
|
||||
nelm.key = nelm.querySelector('.status .status-key');
|
||||
|
||||
nelm.fileInfo.textContent = this.file.name;
|
||||
nelm.filename.textContent = this.file.name;
|
||||
nelm.filesize.textContent = App.Utils.FormatBytes(this.file.size, 2);
|
||||
this.domNode = nelm;
|
||||
$('#uploads').appendChild(nelm);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a new key to use for encrypting the file
|
||||
* @returns {Promise<CryptoKey>} The new key
|
||||
*/
|
||||
this.GerneteKey = async function () {
|
||||
this.key = await crypto.subtle.generateKey({ name: EncryptionAlgo, length: 128 }, true, ['encrypt', 'decrypt']);
|
||||
crypto.getRandomValues(this.iv);
|
||||
|
||||
this.domNode.key.textContent = "Key: " + await this.HexKey() + ":" + this.HexIV();
|
||||
|
||||
return this.key;
|
||||
};
|
||||
|
||||
/**
|
||||
* Encrypts the file using the key and iv
|
||||
* @param {BufferSource} fileData - The data to encrypt
|
||||
* @returns {Promise<ArrayBuffer>} - The Encrypted data
|
||||
*/
|
||||
this.EncryptFile = async function (fileData) {
|
||||
this.HandleProgress('state-encrypt-start');
|
||||
let encryptedData = await crypto.subtle.encrypt({
|
||||
name: EncryptionAlgo,
|
||||
iv: this.iv
|
||||
}, this.key, fileData);
|
||||
this.HandleProgress('state-encrypt-end');
|
||||
return encryptedData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Uploads Blob data to site
|
||||
* @param {Blob|BufferSource} fileData - The encrypted file data to upload
|
||||
* @returns {Promise<object>} The json result
|
||||
*/
|
||||
this.UploadData = async function (fileData){
|
||||
this.uploadStats.lastProgress = new Date().getTime();
|
||||
this.HandleProgress('state-upload-start');
|
||||
let uploadResult = await XHR("POST", "https://upload.void.cat/src/php/upload.php?filename=" + this.file.name, fileData, undefined, function(ev){
|
||||
let now = new Date().getTime();
|
||||
let dxLoaded = ev.loaded - this.uploadStats.lastLoaded;
|
||||
let dxTime = now - this.uploadStats.lastProgress;
|
||||
|
||||
this.uploadStats.lastLoaded = ev.loaded;
|
||||
this.uploadStats.lastProgress = now;
|
||||
|
||||
this.domNode.speed.textContent = App.Utils.FormatBytes(dxLoaded / (dxTime / 1000.0), 2) + "/s";
|
||||
this.HandleProgress('progress', ev.loaded / parseFloat(ev.total));
|
||||
}.bind(this));
|
||||
|
||||
this.HandleProgress('state-upload-end');
|
||||
return uploadResult;
|
||||
};
|
||||
|
||||
/**
|
||||
* Processes the file upload
|
||||
* @return {Promise}
|
||||
@ -318,9 +442,22 @@ const FileUpload = function (file) {
|
||||
Log.I(`Starting upload for ${this.file.name}`);
|
||||
this.CreateNode();
|
||||
|
||||
let h256 = App.Utils.ArrayToHex(await this.HashFile());
|
||||
await this.GerneteKey();
|
||||
let hash_data = await this.HashFile();
|
||||
let h256 = App.Utils.ArrayToHex(hash_data.h256);
|
||||
let h160 = CryptoJS.RIPEMD160(h256);
|
||||
Log.I(`${this.file.name} hash is: ${h256} (${h160})`);
|
||||
|
||||
//check file params are ok
|
||||
//TODO: call to api to check file info
|
||||
|
||||
//encrypt with the key
|
||||
Log.I(`Encrypting ${this.file.name} with key ${await this.HexKey()} and IV ${this.HexIV()}`)
|
||||
let encryptedData = await this.EncryptFile(hash_data.data);
|
||||
|
||||
//upload the encrypted file data
|
||||
Log.I(`Uploading file ${this.file.name}`);
|
||||
let uploadResult = await this.UploadData(encryptedData);
|
||||
};
|
||||
};
|
||||
App.Init();
|
||||
|
Loading…
Reference in New Issue
Block a user