first commit
This commit is contained in:
commit
e1cbaed324
7 changed files with 327 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*~
|
100
client/crypto.js
Normal file
100
client/crypto.js
Normal file
|
@ -0,0 +1,100 @@
|
|||
var CryptoUtils = {
|
||||
cypher: "AES-GCM",
|
||||
key: null,
|
||||
iv: null,
|
||||
urlSafeKey: null,
|
||||
urlSafeIV: null,
|
||||
|
||||
initialize: function(key = null, iv = null) {
|
||||
if(key === null || iv === null) {
|
||||
this.generateIV();
|
||||
return this.generateKey();
|
||||
}
|
||||
else {
|
||||
this.iv = this.base64ToArrayBuffer(iv)
|
||||
this.urlSafeIV = iv;
|
||||
this.urlSafeKey = key;
|
||||
return this.importKey(this.base64ToArrayBuffer(key));
|
||||
}
|
||||
},
|
||||
|
||||
generateKey: function() {
|
||||
var self = this;
|
||||
return window.crypto.subtle.generateKey(
|
||||
{
|
||||
name: this.cypher,
|
||||
length: 256,
|
||||
},
|
||||
true,
|
||||
["encrypt", "decrypt"]
|
||||
).then(function(k){
|
||||
self.key = k;
|
||||
return window.crypto.subtle.exportKey("raw", k)
|
||||
}).then(function(keydata) {
|
||||
console.log(keydata);
|
||||
self.urlSafeKey = self.arrayBufferToBase64(keydata);
|
||||
}).catch(function(err){
|
||||
console.error(err)
|
||||
});
|
||||
},
|
||||
|
||||
generateIV: function() {
|
||||
this.iv = window.crypto.getRandomValues(new Uint8Array(12));
|
||||
this.urlSafeIV = this.arrayBufferToBase64(this.iv);
|
||||
},
|
||||
|
||||
encrypt: function(data) {
|
||||
return window.crypto.subtle.encrypt(
|
||||
{
|
||||
name: this.cypher,
|
||||
iv: this.iv,
|
||||
},
|
||||
this.key,
|
||||
data
|
||||
);
|
||||
},
|
||||
|
||||
decrypt: function(data) {
|
||||
return window.crypto.subtle.decrypt(
|
||||
{
|
||||
name: this.cypher,
|
||||
iv: this.iv,
|
||||
},
|
||||
this.key,
|
||||
data
|
||||
);
|
||||
},
|
||||
|
||||
importKey: function(key) {
|
||||
var self = this;
|
||||
return window.crypto.subtle.importKey(
|
||||
"raw",
|
||||
key,
|
||||
{
|
||||
name: self.cypher
|
||||
},
|
||||
true,
|
||||
["encrypt", "decrypt"]
|
||||
).then(function(k){
|
||||
self.key = k;
|
||||
});
|
||||
},
|
||||
|
||||
importIV: function(iv) {
|
||||
|
||||
},
|
||||
|
||||
arrayBufferToBase64: function(a) {
|
||||
return btoa(String.fromCharCode(...new Uint8Array(a)))
|
||||
},
|
||||
|
||||
base64ToArrayBuffer: function(b) {
|
||||
var str = atob(b);
|
||||
var buf = new ArrayBuffer(str.length);
|
||||
var bufView = new Uint8Array(buf);
|
||||
for (var i=0, strLen=str.length; i<strLen; i++) {
|
||||
bufView[i] = str.charCodeAt(i);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
}
|
26
client/index.html
Normal file
26
client/index.html
Normal file
|
@ -0,0 +1,26 @@
|
|||
<html>
|
||||
<head>
|
||||
<script src="vendor/zepto.min.js"></script>
|
||||
<script src="crypto.js"></script>
|
||||
<script src="main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="create-room">
|
||||
<input type="button" value="create room">
|
||||
</div>
|
||||
<div id="room">
|
||||
<div id="link">
|
||||
</div>
|
||||
<div id="upload">
|
||||
<form>
|
||||
<input id="file-selector" type="file" name="file"/>
|
||||
<input id="woot" type="button" name="upload" value="upload"/>
|
||||
</form>
|
||||
</div>
|
||||
<div id="file-list">
|
||||
<ul>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
145
client/main.js
Normal file
145
client/main.js
Normal file
|
@ -0,0 +1,145 @@
|
|||
var Uploader = {
|
||||
|
||||
roomId: null,
|
||||
|
||||
createRoom: function() {
|
||||
var self = this;
|
||||
$.post('/room', {}, function(response) {
|
||||
self.roomId = $.parseJSON(response).id
|
||||
})
|
||||
},
|
||||
|
||||
getRoom: function(callback) {
|
||||
$.getJSON('/room/'+this.roomId, callback)
|
||||
},
|
||||
|
||||
getFile: function(fileName, callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/room/'+this.roomId+'/'+fileName, true);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
xhr.onload = callback;
|
||||
xhr.send();
|
||||
},
|
||||
|
||||
deleteRoom: function() {
|
||||
$.ajax({
|
||||
type: 'DELETE',
|
||||
url: '/room/'+this.roomId
|
||||
})
|
||||
},
|
||||
|
||||
uploadFile: function(name, data){
|
||||
var formData = new FormData();
|
||||
formData.append("file", data, name);
|
||||
$.ajax({
|
||||
url: '/room/'+this.roomId,
|
||||
data: formData,
|
||||
cache: false,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
xhr: function () {
|
||||
var xhr = new XMLHttpRequest()
|
||||
//xhr.upload.addEventListener('progress', progress, false)
|
||||
return xhr
|
||||
},
|
||||
type: 'POST'
|
||||
});
|
||||
},
|
||||
|
||||
deleteFile: function() {
|
||||
console.log('NOPE');
|
||||
},
|
||||
}
|
||||
|
||||
var CryptoUploader = {
|
||||
|
||||
createRoom: function() {
|
||||
Uploader.createRoom();
|
||||
CryptoUtils.initialize().then(function(){
|
||||
window.location = window.location.href+"#"+Uploader.roomId+","+CryptoUtils.urlSafeKey+","+CryptoUtils.urlSafeIV;
|
||||
window.location.reload();
|
||||
})
|
||||
},
|
||||
|
||||
uploadFile: function() {
|
||||
var fileReader = new FileReader();
|
||||
var data;
|
||||
var file = document.getElementById("file-selector").files[0];
|
||||
var self = this;
|
||||
fileReader.onload = function(e) {
|
||||
this.data = fileReader.result;
|
||||
CryptoUtils.encrypt(this.data)
|
||||
.then(function(encrypted){
|
||||
Uploader.uploadFile(file.name, new Blob([encrypted], { type: 'application/octet-binary' }));
|
||||
self.showRoomContent();
|
||||
}).catch(function(err){
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
},
|
||||
|
||||
downloadFile: function(fileName) {
|
||||
Uploader.getFile(fileName, function(e){
|
||||
if(this.status == 200) {
|
||||
CryptoUtils.decrypt(this.response).
|
||||
then(function(decrypted){
|
||||
var a = document.createElement("a");
|
||||
document.body.appendChild(a);
|
||||
a.style = "display: none";
|
||||
blob = new Blob([decrypted], {type: "application/octet-binary"}),
|
||||
url = window.URL.createObjectURL(blob);
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
}).catch(function(err){
|
||||
console.error(err);
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
showRoomContent: function() {
|
||||
$("#file-list > ul").empty();
|
||||
Uploader.getRoom(function(data){
|
||||
for(let f of data.entries()) {
|
||||
//var a = document.createElement("a");
|
||||
$("<li><a>"+f[1]+"</a></li>")
|
||||
.appendTo("#file-list > ul")
|
||||
.on('click', function(e){CryptoUploader.downloadFile(e.target.text)});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
$(function(){
|
||||
if(window.location.hash) {
|
||||
var params = window.location.hash.substr(1).split(',');
|
||||
var roomId = params.shift();
|
||||
var key = params.shift();
|
||||
var iv = params.shift();
|
||||
var file = params.shift();
|
||||
$("#create-room").hide();
|
||||
$("#room").show();
|
||||
Uploader.roomId = roomId;
|
||||
CryptoUtils.initialize(key, iv).then(function(){
|
||||
$("#woot").on('click', function(){
|
||||
CryptoUploader.uploadFile();
|
||||
});
|
||||
CryptoUploader.showRoomContent();
|
||||
if(file !== undefined) {
|
||||
CryptoUploader.downloadFile(file);
|
||||
}
|
||||
}).catch(function(err){
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
else {
|
||||
$("#room").hide();
|
||||
$("#create-room").show();
|
||||
$("#create-room > input").on('click', function(e){
|
||||
CryptoUploader.createRoom();
|
||||
});
|
||||
}
|
||||
})
|
2
client/vendor/zepto.min.js
vendored
Normal file
2
client/vendor/zepto.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
server/public
Symbolic link
1
server/public
Symbolic link
|
@ -0,0 +1 @@
|
|||
/home/marco/projects/bohbohboh/client/
|
52
server/server.rb
Normal file
52
server/server.rb
Normal file
|
@ -0,0 +1,52 @@
|
|||
require 'sinatra'
|
||||
require 'securerandom'
|
||||
require 'json'
|
||||
|
||||
# create a room
|
||||
|
||||
post '/room' do
|
||||
id = SecureRandom.hex(4)
|
||||
Dir.mkdir(id)
|
||||
content_type 'text/json'
|
||||
{id: id}.to_json
|
||||
end
|
||||
|
||||
# get room content
|
||||
|
||||
get '/room/:id' do |id|
|
||||
if Dir.exist?('./'+id)
|
||||
content_type 'text/json'
|
||||
Dir.glob('./'+id+'/*').map { |f| File.basename(f)}.to_json
|
||||
else
|
||||
404
|
||||
end
|
||||
end
|
||||
|
||||
# upload a file
|
||||
|
||||
post '/room/:id' do |id|
|
||||
unless params[:file] &&
|
||||
(tmpfile = params[:file][:tempfile]) &&
|
||||
(name = params[:file][:filename])
|
||||
403
|
||||
return
|
||||
end
|
||||
FileUtils.cp(tmpfile, "./"+id+"/"+name)
|
||||
200
|
||||
end
|
||||
|
||||
# remove a room
|
||||
|
||||
delete '/room/:id' do |id|
|
||||
end
|
||||
|
||||
# get a file
|
||||
|
||||
get '/room/:id/:filename' do |id, filename|
|
||||
send_file File.join('./'+id, filename)
|
||||
end
|
||||
|
||||
# remove a file
|
||||
|
||||
delete '/room/:id/:filename' do |id, filename|
|
||||
end
|
Loading…
Reference in a new issue