343 lines
9.0 KiB
JavaScript
343 lines
9.0 KiB
JavaScript
/*
|
|
* Copyright (C) 2021 Thomas Van Acker
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License as published by the Free Software
|
|
* Foundation, either version 3 of the License, or (at your option) any later
|
|
* version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* This project is hosted on https://git.bitscuit.be/bitscuit/Fawkes
|
|
*
|
|
*/
|
|
|
|
// Imports
|
|
const http = require("http");
|
|
const url = require("url");
|
|
const fs = require("fs");
|
|
const util = require("util");
|
|
const crypto = require("crypto");
|
|
const spawn = require("child_process").spawn;
|
|
const exec = require("child_process").exec;
|
|
|
|
// Vars
|
|
const hostname = "192.168.0.128"; // Enter your local IP address here
|
|
const port = 8001; // Enter a port number here
|
|
|
|
var files = {};
|
|
var fawkes = {};
|
|
|
|
|
|
// Create HTTP server
|
|
const server = http.createServer((req, res) => {
|
|
// Parse url
|
|
const requrl = url.parse(req.url);
|
|
var reqpath = requrl.pathname;
|
|
if(reqpath == "/") reqpath = "/index.html";
|
|
|
|
// Respond to requests
|
|
try{
|
|
// Return requested page
|
|
if(reqpath.startsWith("/dl/")){
|
|
res.statusCode = 200;
|
|
res.write(fs.readFileSync("fawkes/files/"+reqpath.substr(4)));
|
|
res.end();
|
|
}else{
|
|
res.statusCode = 200;
|
|
res.write(fs.readFileSync("html"+reqpath));
|
|
res.end();
|
|
}
|
|
|
|
}catch(error){
|
|
// 404 if page not found
|
|
res.statusCode = 404;
|
|
res.setHeader("Content-Type", "text/plain");
|
|
res.end("404 - Page Not Found");
|
|
}
|
|
});
|
|
|
|
|
|
// Create socket.io server
|
|
const io = require("socket.io")(server); // Can only be done when server is created
|
|
|
|
io.on("connection", (socket) => {
|
|
console.log("New user connected!");
|
|
|
|
// Give user unique ID
|
|
var userId = sha256("USER"+rand(0,9999999)+"TIME"+Date.now()).substr(0, 10);
|
|
console.log("userId = "+userId);
|
|
|
|
// Init directory structure
|
|
fs.mkdirSync("fawkes/files/"+userId);
|
|
|
|
// Init socket listeners
|
|
|
|
// Disconnect
|
|
socket.on("disconnect", () => {
|
|
console.log(userId+": disconnect");
|
|
|
|
// Remove all files from this user
|
|
try{
|
|
fs.rmSync("fawkes/files/"+userId, {recursive: true});
|
|
fs.rmSync("fawkes/files/"+userId+"cloaked", {recursive: true});
|
|
}catch(error){
|
|
console.log(userId+": couldn't delete some files");
|
|
}
|
|
});
|
|
|
|
// File upload
|
|
socket.on("FILE_START", (data) => {
|
|
console.log(userId+": file upload ("+data.name+", "+data.length+" bytes)");
|
|
|
|
// Check length
|
|
if(data.length > 20000000){
|
|
console.log(userId+": file too large (data.length = "+data.length+")");
|
|
socket.emit("FILE_ERROR", {name:data.name, error:"File too large. Max size = 20MB"});
|
|
return;
|
|
}
|
|
|
|
// Check if exists
|
|
if((userId+data.name) in files){
|
|
console.log(userId+": file already exists (data.name = "+data.name+")");
|
|
socket.emit("FILE_ERROR", {name:data.name, error:"File had already been uploaded."});
|
|
return;
|
|
}
|
|
|
|
// Create file
|
|
var name = data.name;
|
|
var path = "fawkes/files/"+userId+"/"+name+".png";
|
|
var file = {
|
|
length: data.length,
|
|
name: data.name,
|
|
path: path,
|
|
numSegments: data.length/100000 + 1,
|
|
nextSegment: 0
|
|
};
|
|
files[userId+data.name] = file;
|
|
|
|
// Ask client to begin upload
|
|
socket.emit("FILE_ACK", {name:file.name, nextSegment:file.nextSegment, numSegments:file.numSegments});
|
|
});
|
|
|
|
// File contents upload
|
|
socket.on("FILE_DATA", (data) => {
|
|
console.log(userId+": file data ("+data.name+", segment "+data.segment+", "+data.data.length+" bytes)");
|
|
|
|
// Check if file exists
|
|
if(!((userId+data.name) in files)){
|
|
console.log(userId+": received segment for unknown file (data.name = "+data.name+")");
|
|
socket.emit("FILE_ERROR", {name:data.name, error:"Received segment for unknown file."});
|
|
return;
|
|
}
|
|
|
|
// Get file
|
|
var file = files[userId+data.name];
|
|
|
|
// Check size
|
|
if(data.data.length > 100000){
|
|
console.log(userId+": segment too large (data.name = "+data.name+", data.data.length = "+data.data.length+")");
|
|
socket.emit("FILE_ERROR", {name:data.name, error:"Segment too large."});
|
|
return;
|
|
}
|
|
|
|
// Check segment number
|
|
if(data.segment != file.nextSegment || data.segment >= file.numSegments){
|
|
console.log(userId+": unexpected segment (data.name = "+data.name+", data.segment = "+data.segment+")");
|
|
socket.emit("FILE_ERROR", {name:data.name, error:"Unexpected segment."});
|
|
return;
|
|
}
|
|
|
|
// Write data
|
|
fs.appendFile(file.path, data.data, 'Binary', function(err){
|
|
if(err){
|
|
// Error
|
|
console.log(userId+": couldn't write to file (error = "+err+")");
|
|
socket.emit("FILE_ERROR", {name:data.name, error:"Internal server error. Couldn't write to file."});
|
|
|
|
}else{
|
|
file.nextSegment++;
|
|
if(file.nextSegment < file.numSegments-1){
|
|
// Ask for new segment
|
|
socket.emit("FILE_ACK", {name:file.name, nextSegment:file.nextSegment, numSegments:file.numSegments});
|
|
|
|
}else{
|
|
// When file fully uploaded
|
|
socket.emit("FILE_DONE", {name:file.name});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Fawkes start
|
|
socket.on("FAWKES_START", () => {
|
|
// Start binary
|
|
var process = spawn("fawkes/protection", ["-d", "fawkes/files/"+userId, "--mode", "low"]);
|
|
|
|
// Update vars
|
|
fawkes[userId] = {
|
|
running: true,
|
|
images: [],
|
|
imageSentDone: []
|
|
};
|
|
|
|
// Add listeners
|
|
process.stdout.setEncoding("utf8");
|
|
process.stdout.on("data", function(data){
|
|
// Received output from process
|
|
data = data.toString();
|
|
console.log("fawkes stdout: "+data);
|
|
|
|
// Parse output
|
|
var lines = data.split("\n");
|
|
for(var l=0; l<lines.length; l++){
|
|
|
|
// For each line
|
|
var splices = lines[l].split(" ");
|
|
if(splices.length == 0) continue;
|
|
|
|
if(splices[0] == "Find"){
|
|
var numFaces = splices[1];
|
|
var imageId = splices[4].substr(0, splices[4].indexOf("."));
|
|
console.log("fawkes found "+numFaces+" faces in image "+imageId);
|
|
|
|
// Update file info
|
|
files[userId+imageId].numFaces = numFaces;
|
|
fawkes[userId].images.push(userId+imageId);
|
|
fawkes[userId].imageSentDone.push(numFaces == 0);
|
|
|
|
// Update state if no faces found
|
|
if(numFaces == 0){
|
|
var d = {name:imageId, face:0, numFaces:0};
|
|
console.log(d);
|
|
socket.emit("FAWKES_STATE", d);
|
|
}
|
|
|
|
}else if(splices[0] == "processing"){
|
|
// Processing next face
|
|
console.log("fawkes processing face "+splices[2]);
|
|
|
|
// Calculate image
|
|
var image;
|
|
var f = splices[2] - 1;
|
|
for(var i=0; i<fawkes[userId].images.length; i++){
|
|
console.log("f = "+f);
|
|
if(f < files[fawkes[userId].images[i]].numFaces){
|
|
image = fawkes[userId].images[i];
|
|
console.log("image = "+image);
|
|
break;
|
|
}else{
|
|
f -= files[fawkes[userId].images[i]].numFaces;
|
|
|
|
// If didn't send it yet, send 'done' message for this image
|
|
if(!fawkes[userId].imageSentDone[i]){
|
|
fawkes[userId].imageSentDone[i] = true;
|
|
|
|
var nf = files[fawkes[userId].images[i]].numFaces;
|
|
var d = {name:fawkes[userId].images[i].substr(userId.length), face:nf+1, numFaces:nf};
|
|
console.log(d);
|
|
socket.emit("FAWKES_STATE", d);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update client states
|
|
var d = {name:image.substr(userId.length), face:f+1, numFaces:files[image].numFaces};
|
|
console.log(d);
|
|
socket.emit("FAWKES_STATE", d);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
process.stderr.setEncoding("utf8");
|
|
process.stderr.on("data", function(data){
|
|
// Received output from process
|
|
console.log("fawkes stderr: "+data);
|
|
});
|
|
process.on("close", function(code){
|
|
// Fawkes exited
|
|
console.log("Fawkes exited with code "+code);
|
|
|
|
// Check if success
|
|
if(code == 0){
|
|
// Create archive
|
|
console.log("creating archive...");
|
|
var archiveName = "fawkes-"+Date.now()+"-"+userId+".tar.gz";
|
|
var child = exec(
|
|
"mkdir fawkes/files/"+userId+"cloaked && "+
|
|
"mv fawkes/files/"+userId+"/*_cloaked* fawkes/files/"+userId+"cloaked/ && "+
|
|
"tar -czvf fawkes/files/"+archiveName+" -C fawkes/files/"+userId+"cloaked ."
|
|
, (error, stdout, stderr) => {
|
|
console.log("archive creation done: "+error+", "+stdout+", "+stderr);
|
|
|
|
// Zip done
|
|
if(error){
|
|
socket.emit("FAWKES_DONE", {success:false, message:"Internal server error. Couldn't create archive."});
|
|
}else{
|
|
socket.emit("FAWKES_DONE", {success:true, downloadUrl:"/dl/"+archiveName});
|
|
}
|
|
});
|
|
|
|
}else{
|
|
// Error
|
|
socket.emit("FAWKES_DONE", {success:false, message:"Internal server error. Fawkes exited with an exception."});
|
|
}
|
|
});
|
|
|
|
// Update file states
|
|
socket.emit("FAWKES_STARTED");
|
|
});
|
|
});
|
|
|
|
|
|
// Start HTTP server
|
|
server.listen(port, hostname, () => {
|
|
console.log("Server running at http://"+hostname+":"+port);
|
|
});
|
|
|
|
|
|
|
|
|
|
// Helper methods
|
|
function rand(min, max){
|
|
return Math.floor(Math.random()*(max-min) + min);
|
|
}
|
|
function sha256(message){
|
|
return crypto.createHash("sha256").update(message).digest("hex");
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|