upload
#import jquery
#import getscript
#import nestlinks
function pbkdf2salt() {
return '9fd89c8cee52923790bcbaa585bb5fa817dcdd5ae01725b45c226f23724051c5';
}
function pbkdf2iterations() {
return 1000;
}
function keyfrompassword(pass,cb) {
//We are going to later encrypt the file with AES-GCM
//This requires a significant sized key.
//To get that key we derive one from a password
//We could just pad the bytes or repeat it but that would be easy to dictionary attack
//We are going to use the password plus a salt and pbkdf2 it a few times
var te = new TextEncoder();
crypto.subtle.importKey("raw",te.encode(pass),{name:'PBKDF2'},false,['deriveKey']).then(bk=>{
crypto.subtle.deriveKey({
name:'PBKDF2',
salt:te.encode('upload'),
iterations:pbkdf2iterations(),
hash:{name:'SHA-512'}
},bk,{name:'AES-GCM',length:128},true,['encrypt','decrypt']).then(cb);
});
}
function encryptwithpass(pass,source,cb) {
var iv = crypto.getRandomValues(new Uint8Array(12));
window.iv = iv;
console.log('iv',iv);
keyfrompassword(pass,key=>{
crypto.subtle.encrypt({name:'AES-GCM',iv},key,source).then(r=>{
console.log('data',r);
cb(joinIvAndData(iv,r));
});
});
}
function decryptwithpass(pass,source,cb) {
var [iv,data] = separateIvFromData(source);
console.log('iv',iv);
console.log('data',data);
keyfrompassword(pass,key=>{
crypto.subtle.decrypt({name:'AES-GCM',iv},key,data).then(cb);
});
}
function joinIvAndData(iv, data) {
var buf = new Uint8Array(iv.length + data.byteLength);
data = new Uint8Array(data);
Array.prototype.forEach.call(iv, function (byte, i) {
buf[i] = byte;
});
Array.prototype.forEach.call(data, function (byte, i) {
buf[iv.length + i] = byte;
});
return buf;
}
function separateIvFromData(buf) {
console.log('buf length',buf.length);
console.log('buf',buf);
var iv = new Uint8Array(12);
var data = new Uint8Array(buf.length - iv.length);
Array.prototype.forEach.call(buf, function (byte, i) {
if (i < iv.length) {
iv[i] = byte;
} else {
data[i - iv.length] = byte;
}
});
return [iv,data];
}
function uploadEncrypted(pass,e,cb) {
var fd = new FormData($('form')[0])
var fr = new FileReader();
fr.onloadend=(e)=>{
encryptwithpass(pass,e.target.result,encdata=>{
fd.delete('file');
fd.append('file',new Blob([encdata],{type:file.type}),'enc_'+file.name);
console.log(fd);
cb(e,fd);
});
}
fr.readAsArrayBuffer(fd.get('file'));
}
function randomString(n) {
var text = '';
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for(var i=0;i<n;++i) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
function uploadUrl(e) {
var url = $('#url').val();
var name = $('#location').val();
if(!name) {
name = randomString(10);
var maybeappend = url.split('.').pop();
if(maybeappend.length<=4) {
name+='.'+maybeappend;
}
}
var keytype = $('#vis').val();
var uploadurl = '/'+keytype+'/fseturl/upload/'+name;
var downloadurl = uploadurl.replace('fseturl','fget');
if(keytype!='skey') {
downloadurl=downloadurl.replace('/fget/','/fget/'+window.user+'/');
}
var expandedurl = location.protocol+'//'+location.host+downloadurl;
$.post({
xhr:()=>{
var xhr=$.ajaxSettings.xhr();
$('progress').attr('value',0).css('display','inline');
xhr.upload.addEventListener('progress',e=>{
$('progress').attr('value',e.loaded/e.total);
});
return xhr;
},
url:uploadurl,
data:url,
},()=>{
$('progress').css('display','none').attr('value',0);
addUploadToHistory({name,url:expandedurl,time:Date.now()});
}).fail(()=>{
$('p').prepend('upload failed');
});
}
function uploadClick(e,fd) {
console.log('uploadclick',fd);
e.preventDefault();
if($('#url').val()) {
return uploadUrl(e);
}
var pass = $('#pass').val();
if(pass && !fd) {
return uploadEncrypted(pass,e,uploadClick);
}
fd = fd || new FormData($('form')[0]);
var keytype = $('#vis').val();
var name = $('#location').val();
if(!name) {
name = randomString(10)+'.'+fd.get('file').name.split('.').pop();
}
var uploadurl='/'+keytype+'/fset/upload/'+name;
var downloadurl=uploadurl.replace('fset','fget')
if(keytype!='skey') {
downloadurl=downloadurl.replace('/fget/','/fget/'+window.user+'/');
}
if(pass) {
downloadurl='/app/encdownload'+downloadurl.replace('/fget/','/')+'#'+pass;
}
var expandedurl = location.protocol+'//'+location.host+downloadurl;
$.post({
xhr:()=>{
var xhr=$.ajaxSettings.xhr();
$('progress').attr('value',0).css('display','inline');
xhr.upload.addEventListener('progress',e=>{
$('progress').attr('value',e.loaded/e.total);
});
return xhr;
},
url:uploadurl,
data:fd,
processData:false,
cache: false,
contentType: false,
},()=>{
//$('p').prepend('<br>').prepend($('<a>').attr('href',downloadurl).text(expandedurl))
$('progress').css('display','none').attr('value',0);
addUploadToHistory({name,url:expandedurl,time:Date.now()});
}).fail(()=>{
$('p').prepend('upload failed');
});
}
function getSCSS() {
return `
body {
font-family:sans;
text-align:center;
}
a {
color:black;
text-decoration:none;
}
a:hover {
text-decoration:underline;
}
#uploadform {
text-align:center;
}
form {
background: #CCE8ED;
text-align: center;
padding: 24px;
border: 2px solid #396B84;
border-radius: 4px;
display:inline-block;
}
form > * {
width:224px;
box-sizing:border-box;
}
#history > * {
margin:auto;
margin-top: 5px;
margin-bottom: 7px;
border: 1px solid #396B84;
border-radius: 4px;
padding-top: 2px;
padding-bottom: 2px;
background-color: #CCE8ED;
display:block;
width:80vw;
}
td {
padding-right:16px;
}
progress {
display:none;
}
`;
}
function getSassLib(cb) {
getscript('https://cdnjs.cloudflare.com/ajax/libs/sass.js/0.10.13/sass.sync.min.js',()=>{
cb(Sass);
});
}
function getCSS(cb) {
getSassSync();
Sass.compile(getSCSS(),obj=>{
cb(obj.text);
});
}
function getHTML() {
return `
<div id="uploadform">
<form method="POST" enctype="multipart/form-data">
<input id="location" placeholder="Name (optional)">
<br>
<input type="file" name="file" id="file">
<br>
<input name="url" id="url" placeholder="Url (optional)">
<br>
<select id='vis'>
<option value='skey'>Private</option>
<option value='pkey'>Logged in users</option>
<option value='ppkey' selected>Public</option>
</select>
<br>
<input id="pass" placeholder="Pass (optional)">
<br>
<button>Upload</button>
<br>
<progress max="1" value="0"></progress>
</form>
<p></p>
</div>
<div id="history"></div>
<h3>Enjoy unlimited upload!</h3>
<p>*<a href="/contentpolicy" style="color:blue">Content policy</a></p>
<p>Accounts with offending content will have all content removed.</p>
`;
}
function getUploadHistory(cb) {
$.post('/skey/get/upload/uploads',uploads=>{
window.uploads = JSON.parse(uploads||'[]');
cb(uploads);
});
}
function saveUploadHistory(history,cb) {
$.post('/skey/set/upload/uploads',JSON.stringify(history),cb||function(){});
}
function addUploadToHistoryUI(item) {
console.log('item',item);
var tr = $('<a>').attr('href',item.url).attr('target','_blank');
//var name = $('<td>').text(item.name);
var link = $('<td>').text(item.url);
var time = $('<td>').text((new Date(item.time)).toLocaleString());
tr.append(link,time);
$('#history').first().prepend(tr);
}
function addUploadToHistory(item) {
window.uploads = window.uploads||[];
window.uploads.unshift(item);
addUploadToHistoryUI(item);
saveUploadHistory(window.uploads);
}
function initHistoryUI() {
getUploadHistory(()=>{
for(var c=window.uploads.length-1;c>=0;--c) {
addUploadToHistoryUI(window.uploads[c]);
}
});
}
function startApp() {
add_style(getSCSS());
add_style(nestcss());
getjquery(()=>{
$('body').html(getHTML());
$('body').css('display','flow').css('flex-flow','row');
$('form').submit(uploadClick);
initHistoryUI();
$('body').append(nestlinkselem());
});
}
console.log('Fuck off');
startApp();
upload