mirror of
https://git.v0l.io/Kieran/void.cat.git
synced 2025-03-28 18:21:44 +01:00
checkpoint - downloads
This commit is contained in:
parent
f55397e728
commit
f1c5ef33d9
43
README.md
43
README.md
@ -1,23 +1,34 @@
|
||||
* Nginx >= 1.13.0
|
||||
* php7.0-fpm & php7.0-dev
|
||||
* Redis >= 4.0.0
|
||||
* phpredis >= (develop branch)
|
||||
|
||||
```
|
||||
cat src/db.sql | mysql -D YOUR_DB -p
|
||||
```
|
||||
Setup
|
||||
===
|
||||
|
||||
* Nginx
|
||||
* php-fpm
|
||||
* php-redis
|
||||
* php-curl
|
||||
* Redis
|
||||
|
||||
Nginx handler
|
||||
====
|
||||
```
|
||||
location ~ "^\/[0-9a-z\.]{36,40}$" {
|
||||
try_files $uri /src/php/download.php;
|
||||
location ~ "^\/([0-9a-z\.]{36,40})$" {
|
||||
try_files $uri /src/php/handler.php?h=download&hash=$1;
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
fastcgi_read_timeout 1200;
|
||||
```
|
||||
Void Binary File Format (VBF)
|
||||
===
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| version | uint8_t | Binary file format version |
|
||||
| hash | 32 byte hash | The hash of the unencrypted file |
|
||||
| payload | >EOF | The encrypted payload |
|
||||
|
||||
For lightning tips i recommend using `socat` to connect to the rpc file.
|
||||
```
|
||||
socat TCP-LISTEN:9737,bind=127.0.0.1,reuseaddr,fork,range=127.0.0.0/8 UNIX-CLIENT:/root/.lightning/lightning-rpc 1> /var/log/socat-lightning-log 2>&1 &
|
||||
```
|
||||
---
|
||||
VBF Payload Format
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| header_length | uint16_t | Length of the header section |
|
||||
| header | string | The header json |
|
||||
| file | >EOF | The file |
|
||||
|
59
index.html
59
index.html
@ -5,7 +5,7 @@
|
||||
<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"/>
|
||||
<link rel="stylesheet" href="dist/style.css" />
|
||||
<template id="tmpl-upload">
|
||||
<div class="upload">
|
||||
<div class="file-info">
|
||||
@ -21,8 +21,35 @@
|
||||
<span>0%</span>
|
||||
<div></div>
|
||||
</div>
|
||||
<div class="links" style="display: none">
|
||||
<div class="links" style="display: none"></div>
|
||||
<div class="errors" style="display: none"></div>
|
||||
</div>
|
||||
</template>
|
||||
<template id="tmpl-view-image">
|
||||
|
||||
</template>
|
||||
<template id="tmpl-view-video">
|
||||
|
||||
</template>
|
||||
<template id="tmpl-view-audio">
|
||||
|
||||
</template>
|
||||
<template id="tmpl-view-default">
|
||||
<div class="view-default">
|
||||
<div>
|
||||
<b>Public Hash:</b> <span class="view-public-hash"></span>
|
||||
</div>
|
||||
<div>
|
||||
<b>Hash:</b> <span class="view-hash"></span>
|
||||
</div>
|
||||
<div>
|
||||
<b>Key:</b> <span class="view-key"></span>
|
||||
</div>
|
||||
<div>
|
||||
<b>IV:</b> <span class="view-iv"></span>
|
||||
</div>
|
||||
<div class="btn-download">
|
||||
Download
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -30,11 +57,29 @@
|
||||
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="page-left">
|
||||
<div id="dropzone">Click me!</div>
|
||||
<div id="stats"></div>
|
||||
</div>
|
||||
<div id="uploads" class="page-right"></div>
|
||||
<div id="page-upload">
|
||||
<div class="page-left">
|
||||
<div id="dropzone">Click me!</div>
|
||||
<div id="stats"></div>
|
||||
</div>
|
||||
<div id="uploads" class="page-right"></div>
|
||||
</div>
|
||||
<div id="page-view">
|
||||
<div class="file-info">
|
||||
<div>
|
||||
<b>Size:</b> <span class="file-info-size"></span>
|
||||
</div>
|
||||
<div>
|
||||
<b>Views:</b> <span class="file-info-views"></span>
|
||||
</div>
|
||||
<div>
|
||||
<b>Last Download:</b> <span class="file-info-last-download"></span>
|
||||
</div>
|
||||
<div>
|
||||
<b>Uploaded:</b> <span class="file-info-uploaded"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="src/js/script.js" async></script>
|
||||
</body>
|
||||
|
@ -18,6 +18,12 @@ html, body {
|
||||
background-color: rgb(14, 14, 14);
|
||||
}
|
||||
|
||||
@media (max-device-width: $page-width){
|
||||
.page {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
.page {
|
||||
width: $page-width;
|
||||
margin-left: auto;
|
||||
@ -31,6 +37,10 @@ html, body {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#page-view {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.page-left {
|
||||
width: 50%;
|
||||
float:left;
|
||||
@ -47,6 +57,10 @@ html, body {
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
#page-upload {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.upload {
|
||||
overflow: hidden;
|
||||
border: $upload-border;
|
||||
@ -108,15 +122,70 @@ html, body {
|
||||
background-color: rgb(10, 161, 10);
|
||||
}
|
||||
|
||||
.links {
|
||||
.upload .links {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.links a {
|
||||
.upload .links a {
|
||||
margin: 10px;
|
||||
line-height: 40px;
|
||||
border: 1px solid;
|
||||
color: white;
|
||||
text-align: center;
|
||||
background-color: #569a59;
|
||||
}
|
||||
|
||||
.upload .errors {
|
||||
margin: 10px;
|
||||
line-height: 40px;
|
||||
border: 1px solid;
|
||||
color: white;
|
||||
text-align: center;
|
||||
background-color: #790e00;
|
||||
}
|
||||
|
||||
#page-view .file-info {
|
||||
display: grid;
|
||||
grid-template-columns: 25% 25% 25% 25%;
|
||||
}
|
||||
|
||||
.btn-download {
|
||||
line-height: 40px;
|
||||
margin: 10px;
|
||||
border: 1px solid rgb(70, 70, 70);
|
||||
color: white;
|
||||
text-align: center;
|
||||
background-color: #569a59;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn-download:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.view-default {
|
||||
padding: 30px 10px;
|
||||
background-color: rgb(180, 180, 180);
|
||||
display: grid;
|
||||
grid-template-columns: 60% 40%;
|
||||
}
|
||||
|
||||
.view-default div:nth-child(1), div:nth-child(2), div:nth-child(3), div:nth-child(4) {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.view-default span {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.view-default div:nth-child(4) {
|
||||
grid-row-start: 4;
|
||||
}
|
||||
|
||||
.view-default .btn-download {
|
||||
line-height: 100px;
|
||||
grid-column-start: 2;
|
||||
grid-row-start: 1;
|
||||
grid-row-end: 5;
|
||||
}
|
114
src/js/script.js
114
src/js/script.js
@ -56,7 +56,9 @@ const App = {
|
||||
|
||||
Elements: {
|
||||
get Dropzone() { return $('#dropzone') },
|
||||
get Uploads() { return $('#uploads') }
|
||||
get Uploads() { return $('#uploads') },
|
||||
get PageView() { return $('#page-view') },
|
||||
get PageUpload() { return $('#page-upload') }
|
||||
},
|
||||
|
||||
Templates: {
|
||||
@ -120,7 +122,15 @@ const App = {
|
||||
* Sets up the page
|
||||
*/
|
||||
Init: function () {
|
||||
new DropzoneManager(App.Elements.Dropzone)
|
||||
if(location.hash !== "") {
|
||||
App.Elements.PageUpload.style.display = "none";
|
||||
App.Elements.PageView.style.display = "block";
|
||||
new ViewManager();
|
||||
} else {
|
||||
App.Elements.PageUpload.style.display = "block";
|
||||
App.Elements.PageView.style.display = "none";
|
||||
new DropzoneManager(App.Elements.Dropzone);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -165,8 +175,8 @@ const XHR = function (method, url, data, headers, progress) {
|
||||
|
||||
//set headers if they are passed
|
||||
if (typeof headers === "object") {
|
||||
for (let x in headers) {
|
||||
x.setRequestHeader(x, headers[x]);
|
||||
for (let h in headers) {
|
||||
x.setRequestHeader(h, headers[h]);
|
||||
}
|
||||
}
|
||||
if (method === "POST" && typeof data !== "undefined") {
|
||||
@ -177,6 +187,19 @@ const XHR = function (method, url, data, headers, progress) {
|
||||
})
|
||||
};
|
||||
|
||||
const Api = {
|
||||
DoRequest: async function(req) {
|
||||
return JSON.parse((await JsonXHR('POST', '/api', req)).response);
|
||||
},
|
||||
|
||||
GetFileInfo: async function(hash) {
|
||||
return await Api.DoRequest({
|
||||
cmd: 'file_info',
|
||||
hash: hash
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @constructor Creates an instance of the DropzoneManager
|
||||
* @param {HTMLElement} dz - Dropzone element
|
||||
@ -200,6 +223,78 @@ const DropzoneManager = function (dz) {
|
||||
this.dz.addEventListener('click', this.OpenFileSelect.bind(this), false);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const ViewManager = function () {
|
||||
this.hash = null;
|
||||
this.key = null;
|
||||
this.iv = null;
|
||||
|
||||
this.ParseUrlHash = function() {
|
||||
let hs = window.location.hash.substr(1).split(':');
|
||||
this.hash = hs[0];
|
||||
this.key = hs[1];
|
||||
this.iv = hs[2];
|
||||
};
|
||||
|
||||
this.LoadView = async function() {
|
||||
this.ParseUrlHash();
|
||||
|
||||
let fi = await Api.GetFileInfo(this.hash);
|
||||
|
||||
if(fi.ok === true){
|
||||
$('#page-view .file-info-size').textContent = App.Utils.FormatBytes(fi.data.Size);
|
||||
$('#page-view .file-info-views').textContent = fi.data.Views.toLocaleString();
|
||||
$('#page-view .file-info-last-download').textContent = new Date(fi.data.LastView * 1000).toLocaleString();
|
||||
$('#page-view .file-info-uploaded').textContent = new Date(fi.data.Uploaded * 1000).toLocaleString();
|
||||
|
||||
await this.ShowPreview(fi.data);
|
||||
}
|
||||
};
|
||||
|
||||
this.ShowPreview = async function(fileinfo) {
|
||||
let nelm = document.importNode($("template[id='tmpl-view-default']").content, true);
|
||||
nelm.querySelector('.view-public-hash').textContent = fileinfo.PublicHash;
|
||||
nelm.querySelector('.view-hash').textContent = fileinfo.Hash;
|
||||
nelm.querySelector('.view-key').textContent = this.key;
|
||||
nelm.querySelector('.view-iv').textContent = this.iv;
|
||||
|
||||
$('#page-view').appendChild(nelm);
|
||||
|
||||
};
|
||||
|
||||
this.LoadView();
|
||||
};
|
||||
|
||||
/**
|
||||
* File download and decryption class
|
||||
* @class
|
||||
* @param {object} fileinfo - The file info from the api response
|
||||
* @param {string} key - The key to use for decryption
|
||||
* @param {string} iv - The IV to use for decryption
|
||||
*/
|
||||
const FileDownloader = function(fileinfo, key, iv) {
|
||||
this.fileinfo = fileinfo;
|
||||
|
||||
/**
|
||||
* Track download stats
|
||||
*/
|
||||
this.downloadStats = {
|
||||
lastRate: 0,
|
||||
lastLoaded: 0,
|
||||
lastProgress: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* Downloads the file
|
||||
* @returns {Promise<File>} The loaded and decripted file
|
||||
*/
|
||||
this.DownloadFile = async function() {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* File upload handler class
|
||||
* @class
|
||||
@ -398,6 +493,7 @@ const FileUpload = function (file) {
|
||||
nelm.state = nelm.querySelector('.status .status-state');
|
||||
nelm.key = nelm.querySelector('.status .status-key');
|
||||
nelm.links = nelm.querySelector('.links');
|
||||
nelm.errors = nelm.querySelector('.errors');
|
||||
|
||||
nelm.filename.textContent = this.file.name;
|
||||
nelm.filesize.textContent = App.Utils.FormatBytes(this.file.size, 2);
|
||||
@ -512,9 +608,10 @@ const FileUpload = function (file) {
|
||||
let uploadResult = await this.UploadData(upload_payload);
|
||||
|
||||
Log.I(`Got response for file ${this.file.name}: ${JSON.stringify(uploadResult)}`);
|
||||
this.domNode.state.parentNode.style.display = "none";
|
||||
this.domNode.progress.parentNode.style.display = "none";
|
||||
|
||||
if (uploadResult.status === 200) {
|
||||
this.domNode.state.parentNode.style.display = "none";
|
||||
this.domNode.progress.parentNode.style.display = "none";
|
||||
this.domNode.links.style.display = "";
|
||||
|
||||
let nl = document.createElement("a");
|
||||
@ -522,7 +619,10 @@ const FileUpload = function (file) {
|
||||
nl.href = `${window.location.protocol}//${window.location.host}/#${uploadResult.pub_hash}:${await this.TextKey()}`;
|
||||
nl.textContent = this.file.name;
|
||||
this.domNode.links.appendChild(nl);
|
||||
} else {
|
||||
this.domNode.errors.style.display = "";
|
||||
this.domNode.errors.textContent = uploadResult.msg;
|
||||
}
|
||||
};
|
||||
};
|
||||
App.Init();
|
||||
setTimeout(App.Init);
|
||||
|
@ -3,6 +3,7 @@
|
||||
public $ok = false;
|
||||
public $msg;
|
||||
public $data;
|
||||
public $cmd;
|
||||
}
|
||||
|
||||
class Api implements RequestHandler {
|
||||
@ -14,14 +15,22 @@
|
||||
$cmd = json_decode(file_get_contents("php://input"));
|
||||
|
||||
$rsp = new ApiResponse();
|
||||
$rsp->cmd = $cmd;
|
||||
|
||||
$fs = new FileStore();
|
||||
|
||||
switch($cmd->cmd){
|
||||
case "file_info":{
|
||||
$rsp->ok = true;
|
||||
$rsp->data = $fs->GetPublicFileInfo($cmd->hash);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($rsp);
|
||||
}
|
||||
|
||||
public function GetStats() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
@ -1,19 +1,13 @@
|
||||
<?php
|
||||
class Download implements RequestHandler {
|
||||
|
||||
function __construct(){
|
||||
$this->Abuse = new Abuse();
|
||||
$this->Tracking = new Tracking();
|
||||
$this->FileStore = new FileStore();
|
||||
}
|
||||
|
||||
public function HandleRequest() : void {
|
||||
$fs = new FileStore();
|
||||
if(isset($_REQUEST["hash"])){
|
||||
$hash = $_REQUEST["hash"];
|
||||
|
||||
$file_info = $this->FileStore->GetPublicFileInfo($hash);
|
||||
$file_info = $fs->GetPublicFileInfo($hash);
|
||||
if($file_info != NULL){
|
||||
var_dump($file_info);
|
||||
$this->StartDownload($file_info);
|
||||
} else {
|
||||
http_response_code(404);
|
||||
exit();
|
||||
@ -24,8 +18,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
function StartDownload(){
|
||||
|
||||
function StartDownload($file_info){
|
||||
$abuse = new Abuse();
|
||||
$tracking = new Tracking();
|
||||
|
||||
//pass to nginx to handle download
|
||||
$this->InternalNginxRedirect($file_info->Path, 604800);
|
||||
}
|
||||
|
||||
function InternalNginxRedirect($location, $expire){
|
||||
//var_dump($location);
|
||||
header("X-Accel-Redirect: /" . $location);
|
||||
//header("Cache-Control: public, max-age=$expire");
|
||||
}
|
||||
}
|
||||
?>
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
class FileInfo {
|
||||
public $PublicHash;
|
||||
public $Hash;
|
||||
public $Path;
|
||||
public $Views;
|
||||
public $Uploaded;
|
||||
|
@ -1,17 +1,33 @@
|
||||
<?php
|
||||
class FileStore {
|
||||
public function GetPublicFileInfo($public_hash) : FileInfo {
|
||||
public function SetPublicFileInfo($info) : void {
|
||||
$redis = StaticRedis::$Instance;
|
||||
$file_key = REDIS_PREFIX . $info->PublicHash;
|
||||
|
||||
$redis->hMSet($file_key, array(
|
||||
'path' => $info->Path,
|
||||
'views' => $info->Views,
|
||||
'uploaded' => $info->Uploaded,
|
||||
'lastview' => $info->LastView,
|
||||
'size' => $info->Size,
|
||||
'hash' => $info->Hash
|
||||
));
|
||||
}
|
||||
|
||||
public function GetPublicFileInfo($public_hash) : ?FileInfo {
|
||||
$redis = StaticRedis::$Instance;
|
||||
$file_key = REDIS_PREFIX . $public_hash;
|
||||
|
||||
$public_file_info = $redis->hMGet($file_key, array('path', 'views', 'uploaded', 'lastview', 'size'));
|
||||
$public_file_info = $redis->hMGet($file_key, array('path', 'hash', 'views', 'uploaded', 'lastview', 'size'));
|
||||
if($public_file_info['path'] != False){
|
||||
$file = new FileInfo();
|
||||
$file->PublicHash = $public_hash;
|
||||
$file->Hash = $public_file_info['hash'];
|
||||
$file->Path = $public_file_info['path'];
|
||||
$file->Views = $public_file_info['views'];
|
||||
$file->Uploaded = $public_file_info['uploaded'];
|
||||
$file->LastView = $public_file_info['lastview'];
|
||||
$file->Size = $public_file_info['size'];
|
||||
$file->Views = intval($public_file_info['views']);
|
||||
$file->Uploaded = intval($public_file_info['uploaded']);
|
||||
$file->LastView = intval($public_file_info['lastview']);
|
||||
$file->Size = intval($public_file_info['size']);
|
||||
|
||||
return $file;
|
||||
}
|
||||
@ -19,7 +35,7 @@
|
||||
return NULL;
|
||||
}
|
||||
|
||||
public function FileExists($public_hash) {
|
||||
public function FileExists($public_hash) : Boolean {
|
||||
$redis = StaticRedis::$Instance;
|
||||
$file_key = REDIS_PREFIX . $public_hash;
|
||||
return $redis->hExists($file_key, 'path');
|
||||
|
@ -18,6 +18,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
//var_dump($_REQUEST);
|
||||
http_response_code(400);
|
||||
exit();
|
||||
} else {
|
||||
|
@ -6,23 +6,24 @@
|
||||
}
|
||||
|
||||
class Upload implements RequestHandler {
|
||||
public static $UploadFolderDefault = "out";
|
||||
|
||||
private $isMultipart = False;
|
||||
private $MaxUploadSize = 104857600; //100MiB is the default upload size
|
||||
private $UploadPath = NULL;
|
||||
private $UploadFolder = NULL;
|
||||
private $PublicHashAlgo = "ripemd160";
|
||||
|
||||
public function __construct(){
|
||||
$cfg = Config::MGetConfig(array('max_size', 'upload_path', 'public_hash_algo'));
|
||||
$cfg = Config::MGetConfig(array('max_size', 'upload_folder', 'public_hash_algo'));
|
||||
|
||||
if($cfg["max_size"] != False){
|
||||
$this->MaxUploadSize = $cfg["max_size"];
|
||||
}
|
||||
|
||||
if($cfg["upload_path"] != False){
|
||||
$this->UploadPath = $cfg["upload_path"];
|
||||
if($cfg["upload_folder"] != False){
|
||||
$this->UploadFolder = $cfg["upload_folder"];
|
||||
} else {
|
||||
$this->UploadPath = $_SERVER["DOCUMENT_ROOT"] . "/out";
|
||||
$this->UploadFolder = self::$UploadFolderDefault;
|
||||
}
|
||||
|
||||
if($cfg["public_hash_algo"] != False){
|
||||
@ -37,8 +38,8 @@
|
||||
ini_set('enable_post_data_reading', 0);
|
||||
|
||||
//check upload dir exists
|
||||
if(!file_exists($this->UploadPath)){
|
||||
mkdir($this->UploadPath);
|
||||
if(!file_exists("$_SERVER[DOCUMENT_ROOT]/$this->UploadFolder")){
|
||||
mkdir("$_SERVER[DOCUMENT_ROOT]/$this->UploadFolder");
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,6 +58,9 @@
|
||||
//generate public hash
|
||||
$pub_hash = hash($this->PublicHashAlgo, $bf->Hash);
|
||||
|
||||
//save upload
|
||||
$this->SaveUpload($input, $bf->Hash, $pub_hash);
|
||||
|
||||
//sync to other servers
|
||||
$this->SyncFileUpload($input);
|
||||
|
||||
@ -74,5 +78,24 @@
|
||||
function SyncFileUpload() {
|
||||
|
||||
}
|
||||
|
||||
function SaveUpload($input, $hash, $pub_hash) {
|
||||
$fs = new FileStore();
|
||||
$file_path = "$this->UploadFolder/$pub_hash";
|
||||
|
||||
$fi = new FileInfo();
|
||||
$fi->PublicHash = $pub_hash;
|
||||
$fi->Hash = $hash;
|
||||
$fi->Path = $file_path;
|
||||
$fi->Uploaded = time();
|
||||
$fi->LastView = time();
|
||||
$fi->Views = 0;
|
||||
|
||||
$fout = fopen("$_SERVER[DOCUMENT_ROOT]/$file_path", 'wb+');
|
||||
$fi->Size = stream_copy_to_stream($input, $fout);
|
||||
fclose($fout);
|
||||
|
||||
$fs->SetPublicFileInfo($fi);
|
||||
}
|
||||
}
|
||||
?>
|
Loading…
x
Reference in New Issue
Block a user