This commit is contained in:
Kieran 2022-01-25 15:17:38 +00:00
parent c60f346120
commit 509977743b
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
83 changed files with 0 additions and 6616 deletions

View File

@ -1,40 +0,0 @@
Setup
===
* Nginx
* php-fpm
* php-redis
* php-curl
* php-gmp
* Redis
Nginx handler
====
```
location ~* "^\/([0-9a-z]{27})$" {
try_files $uri /src/php/handler.php?h=download&id=$1;
}
```
Void Binary File Format (VBF2)
===
*All numbers are little endian*
| Name | Size | Description |
|---|---|---|
| version | 1 byte unsigned number | Binary file format version |
| magic | 7 bytes | "OID🐱" encoded to UTF8 string |
| uploaded | 4 byte unsigned number | Unix timestamp of when the upload started |
| payload | EOF - 32 bytes | The encrypted payload |
| hash | 32 bytes HMAC-SHA265 | The HMAC of the unencrypted file* |
*\* Using the encryption key as the HMAC key*
VBF Payload Format
====
| Name | Size | Description |
|---|---|---|
| header_length | 2 byte unsigned number | Length of the header section |
| header | {header_length} UTF8 String | The header json |
| file | >EOF | The file |

View File

@ -1,15 +0,0 @@
Service Worker
===
Its reccomended to proxy pass the script so any updates are served automatically to your clients like so (Nginx), You can of course host the script yourself aswell.
```
location /void_auto_loader.js {
proxy_pass https://v3.void.cat/dist/void_auto_loader.js;
}
```
Limitations
====
Currently its not reccomended to embed large resources on any pages as these need to be loaded into ram before decrypting, this service worker is only reccomended for small to medium images that can be downloaded and decrypted quickly.
Keep and eye on this page for an update on this though, in the near future i hope to upgrade to using the new [Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) which would allow streaming of content from the server without the need for buffering in ram.

View File

@ -1,29 +0,0 @@
<html>
<head>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/void_auto_loader.js').then(function (registration) {
console.log(`ServiceWorker registration successful with scope: ${registration.scope}`);
}, function (err) {
console.error(`ServiceWorker registration failed: ${err}`);
});
}
</script>
</head>
<body>
<!--
Path must be to the current host, the service worker will pick this up and redirect the request to the correct server
Keep in mind that the entire file is loaded and decrypted into memory, so it can look like nothing is happening while the file is loading.
Currently its not advised to use this example for anything other than images that can be downloaded fast
Check other examples for videos, this may be added at a later stage
If you already have multiple service workers or this one doesnt work, try to load the service worker under a fake folder name and prefix all the urls with that
for example:
<img src="/void_proxy/Jet0iBptr3AyVu1tUiPTAn5y3VY" />
-->
<img src="/RiUKijQm3I4VotqaeRl81XqnISo:cf7581696fff2349391218f99ffaca50:7b19587e9a5faa2a0439044c8852f0e5" />
</body>
</html>

View File

@ -1,166 +0,0 @@
<!doctype html>
<html>
<head>
<title>void.cat</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="dist/bundle.css" />
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<script>
(adsbygoogle = window.adsbygoogle || []).push({
google_ad_client: "ca-pub-3289062345896209",
enable_page_level_ads: true
});
</script>
<template id="tmpl-upload">
<div class="upload">
<div class="file-info">
<div class="file-info-name">...</div>
<div class="file-info-speed">0 B/s</div>
<div class="file-info-size">0 B</div>
</div>
<div class="status">
<div class="status-state">Starting...</div>
<div class="status-key">Key: 000000000000000000000000000000:00000000000000000000000000</div>
</div>
<div class="upload-progress">
<span>0%</span>
<div></div>
</div>
<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>Id:</b> <span class="view-file-id"></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 class="view-transfer-stats">
<div class="view-download-progress">
<div>0%</div>
<div></div>
</div>
<div class="view-download-label-speed">
0 B/s
</div>
</div>
</div>
</template>
</head>
<body>
<div class="page">
<div class="header" onclick="window.location.href = '/'">
void.cat
</div>
<div id="page-notice">
<b>Please take note that:</b><br>
<ul>
</ul>
</div>
<div id="page-upload">
<div class="page-left" style="width: 100%">
<div id="dropzone">Click me!</div>
<div id="stats"></div>
</div>
<div id="uploads" class="page-right" style="display: none"></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 id="page-stats">
<h3>7 days tx graph</h3>
<canvas id="weektxgraph" width="1024" height="200"></canvas>
</div>
<div id="page-faq">
<div class="faq-section">
<div class="faq-header">
Are there any restrictions on what can be uploaded
</div>
<div class="faq-content">
No.
</div>
</div>
<div class="faq-section">
<div class="faq-header">
How can I embed content in my site
</div>
<div class="faq-content">
I have created a service worker which you can host on your site which will decrypt the links automatically, find
more info <a href="https://github.com/v0l/void.cat/blob/v3-b2b/examples/embed" target="_blank">here on github.</a>
</div>
</div>
<div class="faq-section">
<div class="faq-header">
I have more questions
</div>
<div class="faq-content">
Feel free to join <a href="https://discord.gg/8BkxTGs" target="_blank">Discord</a> and ask there.
</div>
</div>
</div>
<div id="page-donate">
<form method="POST" action="https://btc-pay.noinput.xyz/api/v1/invoices">
<input type="hidden" name="storeId" value="EveZSjtijFkn3UXKjq69AxhHxksjHcsHA23WHFFQWbzH" />
<label for="price">USD: </label><input type="number" name="price" value="10" style="width: 50px"/><br>
<input type="hidden" name="currency" value="USD" />
<input type="image" src="https://btc-pay.noinput.xyz/img/paybutton/pay.png" name="submit" style="width:209px" alt="Pay with BtcPay, Self-Hosted Bitcoin Payment Processor">
</form>
</div>
<div id="footer-stats">
<div><b>Files:</b><span>0</span></div>
<div><b>Total Size:</b><span>0 B</span></div>
<div><b>24Hr Transfer:</b><span>0 B</span></div>
</div>
<div id="footer">
<a href="javascript:(function() { App.ShowStats(); })()">Stats</a> |
<a href="javascript:(function() { App.ShowFAQ(); })()">FAQ</a> |
<a href="javascript:(function() { App.ShowDonate(); })()">Donate</a> |
<a href="http://rv6omygg3ksi3dys.onion/" target="_blank">Tor</a>
<br>
</div>
<ins class="adsbygoogle" style="display:block; margin-left: auto; margin-right: auto;" data-ad-client="ca-pub-3289062345896209"
data-ad-slot="9187315106" data-ad-format="auto" data-full-width-responsive="true"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
</div>
<script type="text/javascript" src="dist/bundle.js"></script>
</body>
</html>

View File

@ -1,35 +0,0 @@
{
"name": "void.cat",
"version": "3.2.0-beta1",
"description": "Free file hosting website",
"repository": {
"type": "git",
"url": "git+https://github.com/v0l/void.cat.git"
},
"scripts": {
"build": "webpack",
"debug": "webpack-cli --entry ./src/js/index.js --mode development --watch --output ./dist/bundle.js",
"debug:worker": "webpack-cli --entry ./src/js/Worker.js --mode development --watch --output ./sw.js"
},
"keywords": [
"upload",
"host",
"files",
"pomf"
],
"author": "v0l",
"license": "MIT",
"bugs": {
"url": "https://github.com/v0l/void.cat/issues"
},
"homepage": "https://github.com/v0l/void.cat#readme",
"dependencies": {
"asmcrypto.js": "^2.3.2",
"css-loader": "^2.1.1",
"mini-css-extract-plugin": "^0.6.0",
"node-sass": "^4.12.0",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"sass-loader": "^7.1.0",
"webpack": "^4.31.0"
}
}

View File

@ -1,9 +0,0 @@
<!doctype html>
<html>
<head>
<title>Admin</title>
</head>
<body>
This is the admin page
</body>
</html>

View File

@ -1,40 +0,0 @@
import { Util, Log, Api } from './modules/Util.js';
import { ViewManager } from './modules/ViewManager.js';
import { FileDownloader } from './modules/FileDownloader.js';
const VoidFetch = function (event) {
let vm = new ViewManager();
let url = new URL(event.request.url);
let path = url.pathname;
let hs = vm.ParseFrag(path.substr(1));
if (hs !== null) {
Log.I(`Worker taking request: ${hs.id}`);
event.respondWith(async function () {
const client = await clients.get(event.clientId);
let fi = await Api.GetFileInfo(hs.id);
if (fi.ok) {
let fd = new FileDownloader(fi.data, hs.key, hs.iv);
fd.onprogress = function (x) {
if (client !== null && client !== undefined) {
client.postMessage({
type: 'progress',
x
});
}
};
let resp = await fd.StreamResponse();
let head = await fd.waitForHeader;
resp.headers.set("Content-Type", head.mime != "" ? head.mime : "application/octet-stream");
resp.headers.set("Content-Disposition", `inline; filename="${head.name}"`);
return resp;
} else {
return Response.error();
}
}());
}
}
self.addEventListener('fetch', VoidFetch);

View File

@ -1,12 +0,0 @@
import * as App from './modules/App.js';
import '../scss/style.scss';
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(function (registration) {
console.log(`ServiceWorker registration successful with scope: ${registration.scope}`);
}, function (err) {
console.error(`ServiceWorker registration failed: ${err}`);
});
}
App.Init();

View File

@ -1,222 +0,0 @@
import { Api, Utils, Log, $ } from './Util.js';
import { ViewManager } from './ViewManager.js';
import { DropzoneManager } from './DropzoneManager.js';
import { FileUpload } from './FileUpload.js';
let ChartJsLoaded = false;
const Elements = {
get Dropzone() { return $('#dropzone') },
get Uploads() { return $('#uploads') },
get PageView() { return $('#page-view') },
get PageUpload() { return $('#page-upload') },
get PageFaq() { return $('#page-faq') },
get PageStats() { return $('#page-stats') },
get PageDonate() { return $('#page-donate') }
};
const Templates = {
get Upload() { return $("template[id='tmpl-upload']") }
};
const Browser = {
get IsEdge() {
return /Edge/.test(navigator.userAgent);
},
get IsChrome() {
return !Browser.IsEdge && /^Mozilla.*Chrome/.test(navigator.userAgent);
},
get IsFirefox() {
return !Browser.IsEdge && /^Mozilla.*Firefox/.test(navigator.userAgent);
}
};
/**
* Uploads the files as selected by the input form
* @param {Element} ctx
* @returns {Promise}
*/
async function UploadFiles(ctx) {
let files = ctx.files;
let proc_files = [];
for (let x = 0; x < files.length; x++) {
let fu = new FileUpload(files[x]);
proc_files[proc_files.length] = fu.ProcessUpload();
}
await Promise.all(proc_files);
}
function ResetView() {
Elements.PageView.style.display = "none";
Elements.PageUpload.style.display = "none";
Elements.PageFaq.style.display = "none";
Elements.PageStats.style.display = "none";
Elements.PageDonate.style.display = "none";
}
async function ShowStats() {
location.hash = "#stats";
ResetView();
if (!ChartJsLoaded) {
await InsertScript("//cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js", () => {
return typeof moment !== "undefined";
});
await InsertScript("//cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js", () => {
return typeof Chart !== "undefined";
});
}
let api_rsp = await Api.GetTxChart();
if (api_rsp.ok) {
let ctx = $('#weektxgraph').getContext('2d');
new Chart(ctx, {
type: 'line',
data: api_rsp.data,
options: {
scales: {
xAxes: [{
type: 'time',
time: {
source: 'data'
}
}],
yAxes: [{
ticks: {
beginAtZero: true,
callback: function(label, index, labels) {
return Utils.FormatBytes(label);
}
}
}]
}
}
});
}
Elements.PageStats.style.display = "block";
}
function ShowFAQ() {
location.hash = "#faq";
ResetView();
Elements.PageFaq.style.display = "block";
}
function ShowDonate() {
location.hash = "#donate";
ResetView();
Elements.PageDonate.style.display = "block";
}
/**
* Sets up the page
*/
async function Init() {
CheckBrowserSupport();
MakePolyfills();
ResetView();
if (location.hash !== "") {
if (location.hash == "#faq") {
ShowFAQ();
} else if (location.hash == "#stats") {
ShowStats();
} else if (location.hash == "#donate") {
ShowDonate();
} else {
Elements.PageView.style.display = "block";
let vm = new ViewManager();
vm.LoadView();
}
window.site_info = await Api.GetSiteInfo();
} else {
window.site_info = await Api.GetSiteInfo();
Elements.PageUpload.style.display = "block";
$('#dropzone').innerHTML = `Click me!<br><small>(${Utils.FormatBytes(window.site_info.data.max_upload_size)} max)</small>`;
new DropzoneManager(Elements.Dropzone);
}
if (window.site_info.ok) {
let elms = document.querySelectorAll("#footer-stats div span");
elms[0].textContent = window.site_info.data.basic_stats.Files;
elms[1].textContent = Utils.FormatBytes(window.site_info.data.basic_stats.Size, 2);
elms[2].textContent = Utils.FormatBytes(window.site_info.data.basic_stats.Transfer_24h, 2);
}
let faq_headers = document.querySelectorAll('#page-faq .faq-header');
for (let x = 0; x < faq_headers.length; x++) {
faq_headers[x].addEventListener('click', function () {
this.nextElementSibling.classList.toggle("show");
}.bind(faq_headers[x]));
}
}
/**
* Adds in polyfills for this browser
*/
function MakePolyfills() {
if (typeof TextEncoder === "undefined" || typeof TextDecoder === "undefined") {
InsertScript("//unpkg.com/text-encoding@0.6.4/lib/encoding-indexes.js");
InsertScript("//unpkg.com/text-encoding@0.6.4/lib/encoding.js");
}
}
/**
* Adds a script tag at the top of the header
* @param {string} src - The script src url
* @param {function} fnWait - Function to use in promise to test if the script is loaded
*/
async function InsertScript(src, fnWait) {
var before = document.head.getElementsByTagName('script')[0];
var newlink = document.createElement('script');
newlink.src = src;
document.head.insertBefore(newlink, before);
if (typeof fnWait === "function") {
await new Promise((resolve, reject) => {
let timer = setInterval(() => {
if (fnWait()) {
clearInterval(timer);
resolve();
}
}, 100);
});
}
}
/**
* Checks browser version
*/
function CheckBrowserSupport() {
if (!Browser.IsFirefox) {
if (Browser.IsChrome) {
AddNoticeItem("Uploads bigger then 100MiB usually crash Chrome when uploading. Please upload with Firefox. Or check <a target=\"_blank\" href=\"https://github.com/v0l/void.cat/releases\">GitHub</a> for tools.");
}
if (Browser.IsEdge) {
let edge_version = /Edge\/([0-9]{1,3}\.[0-9]{1,5})/.exec(navigator.userAgent)[1];
Log.I(`Edge version is: ${edge_version}`);
if (parseFloat(edge_version) < 18.18218) {
AddNoticeItem("Upload progress isn't reported in the version of Edge you are using, see <a target=\"_blank\" href=\"https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12224510/\">here for more info</a>.");
}
}
document.querySelector('#page-notice').style.display = "block";
}
}
/**
* Adds a notice to the UI notice box
* @param {string} txt - Message to add to notice list
*/
function AddNoticeItem(txt) {
let ne = document.createElement('li');
ne.innerHTML = txt;
document.querySelector('#page-notice ul').appendChild(ne);
}
export { Init, Templates };

View File

@ -1,59 +0,0 @@
/**
* Change Log
* 1.0 - https://github.com/v0l/void.cat/commit/b0a49e5bc28d62ebd954fe344c6f604b952e905c
* 1.1 - https://github.com/v0l/void.cat/commit/e3f7f2a59e86f86a27a06d8725f4f6401af7f190
* 1.2 - TBC
*/
/**
* @constant {string} - Stores the current app version
*/
export const AppVersion = require('../../../package.json').version;
/**
* @constant {string} - The hashing algo to use to verify the file
*/
export const HashingAlgo = 'SHA-256';
/**
* @constant {string} - The encryption algoritm to use for file uploads
*/
export const EncryptionAlgo = 'AES-CBC';
/**
* @constant {object} - The 'algo' argument for importing/exporting/generating keys
*/
export const EncryptionKeyDetails = { name: EncryptionAlgo, length: 128 };
/**
* @constant {object} - The 'algo' argument for importing/exporting/generating hmac keys
*/
export const HMACKeyDetails = { name: 'HMAC', hash: HashingAlgo };
/**
* @constant {number} - Size of 1 kiB
*/
export const kiB = Math.pow(1024, 1);
/**
* @constant {number} - Size of 1 MiB
*/
export const MiB = Math.pow(1024, 2);
/**
* @constant {number} - Size of 1 GiB
*/
export const GiB = Math.pow(1024, 3);
/**
* @constant {number} - Size of 1 TiB
*/
export const TiB = Math.pow(1024, 4);
/**
* @constant {number} - Size of 1 PiB
*/
export const PiB = Math.pow(1024, 5);
/**
* @constant {number} - Size of 1 EiB
*/
export const EiB = Math.pow(1024, 6);
/**
* @constant {number} - Size of 1 ZiB
*/
export const ZiB = Math.pow(1024, 7);
/**
* @constant {number} - Size of 1 YiB
*/
export const YiB = Math.pow(1024, 8);

View File

@ -1,34 +0,0 @@
import { FileUpload } from './FileUpload.js';
/**
* @constructor Creates an instance of the DropzoneManager
* @param {HTMLElement} dz - Dropzone element
*/
function DropzoneManager(dz) {
this.dz = dz;
this.SetUI = function () {
document.querySelector('#page-upload div:nth-child(1)').removeAttribute("style");
document.querySelector('#uploads').removeAttribute("style");
};
this.OpenFileSelect = function (ev) {
let i = document.createElement('input');
i.setAttribute('type', 'file');
i.setAttribute('multiple', '');
i.addEventListener('change', function (evt) {
this.SetUI();
let fl = evt.target.files;
let host = window.site_info.ok ? window.site_info.data.upload_host : window.location.host;
for (let z = 0; z < fl.length; z++) {
new FileUpload(fl[z], host).ProcessUpload();
}
}.bind(this));
i.click();
};
this.dz.addEventListener('click', this.OpenFileSelect.bind(this), false);
};
export { DropzoneManager };

View File

@ -1,311 +0,0 @@
import * as Const from './Const.js';
import { VBF } from './VBF.js';
import { XHR, Utils, Log } from './Util.js';
import { HmacSha256, AES_CBC } from 'asmcrypto.js';
/**
* 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
*/
function FileDownloader(fileinfo, key, iv) {
this.fileinfo = fileinfo;
this.key = key;
this.iv = iv;
/**
* Track download stats
*/
this.downloadStats = {
lastRate: 0,
lastLoaded: 0,
lastProgress: 0
};
/**
* Gets the url for downloading
* @returns {string} URL to download from
*/
this.GetLink = function () {
return (this.fileinfo.DownloadHost !== null ? `${self.location.protocol}//${this.fileinfo.DownloadHost}` : '') + `/${this.fileinfo.FileId}`;
};
/**
* Handles progress messages from file download
*/
this.HandleProgress = function (type, progress) {
switch (type) {
case 'progress-download': {
if (typeof this.onprogress === 'function') {
this.onprogress(progress);
}
break;
}
case 'progress-speed': {
if (typeof this.oninfo === 'function') {
this.oninfo(progress);
}
break;
}
case 'decrypt-start': {
if (typeof this.oninfo === 'function') {
this.oninfo('Decrypting..');
}
break;
}
case 'download-complete': {
if (typeof this.oninfo === 'function') {
this.oninfo('Done!');
}
break;
}
case 'rate-limited': {
if (typeof this.onratelimit === 'function') {
this.onratelimit(progress);
}
break;
}
}
};
/**
* Streams the file download response
* @returns {Promise<Response>} The response object to decrypt the download
*/
this.StreamResponse = async function () {
let link = this.GetLink();
let response = await fetch(link, {
mode: 'cors'
});
//hack
var completeHeader;
this.waitForHeader = new Promise((resolve, reject) => {
completeHeader = resolve;
});
let void_download = {
SetFileHeader: function (fh) { completeHeader(fh); }.bind({ completeHeader }),
HandleProgress: this.HandleProgress.bind(this),
downloadStats: this.downloadStats,
isStart: true,
decOffset: 0,
headerLen: 0,
fileHeader: null,
hmacBytes: null,
body: response.body,
fileinfo: this.fileinfo,
aes: new AES_CBC(new Uint8Array(Utils.HexToArray(this.key)), new Uint8Array(Utils.HexToArray(this.iv)), true),
hmac: new HmacSha256(new Uint8Array(Utils.HexToArray(this.key))),
buff: new Uint8Array(),
start(controller) {
this.reader = this.body.getReader();
return this.readOnce(controller);
},
pull(controller) {
return (async function () {
Log.I(`${this.fileinfo.FileId} Starting..`);
while (true) {
await this.readOnce(controller);
}
}.bind(this))();
},
async readOnce(controller) {
let { done, value } = await this.reader.read();
if (done) {
if (this.buff.byteLength > 0) {
//pad the remaining data with PKCS#7
var toDecrypt = null;
let padding = 16 - (this.buff.byteLength % 16);
if (padding !== 0) {
let tmpBuff = new Uint8Array(this.buff.byteLength + padding);
tmpBuff.fill(padding);
tmpBuff.set(this.buff, 0);
this.buff = null;
this.buff = tmpBuff;
}
let decBytes = this.aes.AES_Decrypt_process(this.buff);
this.hmac.process(decBytes);
controller.enqueue(decBytes);
this.buff = null;
}
let last = this.aes.AES_Decrypt_finish();
this.hmac.process(last);
this.hmac.finish();
controller.enqueue(last);
//check hmac
let h1 = Utils.ArrayToHex(this.hmacBytes);
let h2 = Utils.ArrayToHex(this.hmac.result)
if (h1 === h2) {
Log.I(`HMAC verify ok!`);
} else {
Log.E(`HMAC verify failed (${h1} !== ${h2})`);
controller.cancel();
return;
}
Log.I(`${this.fileinfo.FileId} Download complete!`);
controller.close();
return;
}
var sliceStart = 0;
var sliceEnd = value.byteLength;
//!Slice this only once!!
var toDecrypt = value;
if (this.isStart) {
let header = VBF.ParseStart(value.buffer);
if (header !== null) {
Log.I(`${this.fileinfo.FileId} blob header version is ${header.version} uploaded on ${header.uploaded} (Magic: ${Utils.ArrayToHex(header.magic)})`);
sliceStart = VBF.SliceToEncryptedPart(header.version, value);
} else {
throw "Invalid VBF header";
}
} else if (this.fileHeader != null && this.decOffset + toDecrypt.byteLength + this.headerLen + 2 >= this.fileHeader.len) {
sliceEnd -= 32; //hash is on the end (un-encrypted)
this.hmacBytes = toDecrypt.slice(sliceEnd);
}
const GetAdjustedLen = function () {
return sliceEnd - sliceStart;
};
//decrypt
//append last remaining buffer if any
if (this.buff.byteLength > 0) {
let tmpd = new Uint8Array(this.buff.byteLength + GetAdjustedLen());
tmpd.set(this.buff, 0);
tmpd.set(toDecrypt.slice(sliceStart, sliceEnd), this.buff.byteLength);
sliceEnd += this.buff.byteLength;
toDecrypt = tmpd;
this.buff = new Uint8Array();
}
let blkRem = GetAdjustedLen() % 16;
if (blkRem !== 0) {
//save any remaining data into our buffer
this.buff = toDecrypt.slice(sliceEnd - blkRem, sliceEnd);
sliceEnd -= blkRem;
}
let encBytes = toDecrypt.slice(sliceStart, sliceEnd);
let decBytes = this.aes.AES_Decrypt_process(encBytes);
this.decOffset += decBytes.byteLength;
//read header
if (this.isStart) {
this.headerLen = new Uint16Array(decBytes.slice(0, 2))[0];
let header = new TextDecoder('utf-8').decode(decBytes.slice(2, 2 + this.headerLen));
Log.I(`${this.fileinfo.FileId} got header ${header}`);
this.fileHeader = JSON.parse(header);
this.SetFileHeader(this.fileHeader);
decBytes = decBytes.slice(2 + this.headerLen);
}
//Log.I(`${this.fileinfo.FileId} Decrypting ${toDecrypt.byteLength} bytes, got ${decBytes.byteLength} bytes`);
this.hmac.process(decBytes);
controller.enqueue(decBytes);
//report progress
let now = new Date().getTime();
let dxLoaded = decBytes.byteLength - this.downloadStats.lastLoaded;
let dxTime = now - this.downloadStats.lastProgress;
this.downloadStats.lastLoaded = decBytes.byteLength;
this.downloadStats.lastProgress = now;
this.HandleProgress('progress-speed', `${Utils.FormatBytes(dxLoaded / (dxTime / 1000.0), 2)}/s`);
this.HandleProgress('progress-download', this.decOffset / parseFloat(this.fileHeader.len));
this.isStart = false;
}
}
return new Response(new ReadableStream(void_download));
};
/**
* Downloads the file
* @returns {Promise<File>} The loaded and decripted file
*/
this.DownloadFile = async function () {
let link = this.GetLink();
Log.I(`Starting download from: ${link}`);
if (this.fileinfo.IsLegacyUpload) {
return {
isLegacy: true,
name: this.fileinfo.LegacyFilename,
mime: this.fileinfo.LegacyMime,
url: link
};
} else {
let rsp = await XHR('GET', link, undefined, undefined, undefined, function (ev) {
let now = new Date().getTime();
let dxLoaded = ev.loaded - this.downloadStats.lastLoaded;
let dxTime = now - this.downloadStats.lastProgress;
this.downloadStats.lastLoaded = ev.loaded;
this.downloadStats.lastProgress = now;
this.HandleProgress('progress-speed', `${Utils.FormatBytes(dxLoaded / (dxTime / 1000.0), 2)}/s`);
this.HandleProgress('progress-download', ev.loaded / (ev.lengthComputable ? parseFloat(ev.total) : this.fileinfo.Size));
}.bind(this), function (req) {
req.responseType = "arraybuffer";
});
if (rsp.status === 200) {
this.HandleProgress('decrypt-start');
let fd_decrypted = await this.DecryptFile(rsp.response);
this.HandleProgress('download-complete');
return fd_decrypted;
} else if (rsp.status === 429) {
this.HandleProgress('rate-limited');
}
}
return null;
};
/**
* Decrypts the raw VBF file
* @returns {Promise<*>} The decrypted file
*/
this.DecryptFile = async function (blob) {
let header = VBF.Parse(blob);
let hash_text = Utils.ArrayToHex(header.hmac);
Log.I(`${this.fileinfo.FileId} blob header version is ${header.version} and hash is ${hash_text} uploaded on ${header.uploaded} (Magic: ${Utils.ArrayToHex(header.magic)})`);
let key_raw = Utils.HexToArray(this.key);
let iv_raw = Utils.HexToArray(this.iv);
Log.I(`${this.fileinfo.FileId} decrypting with key ${this.key} and iv ${this.iv}`);
let key = await crypto.subtle.importKey("raw", key_raw, Const.EncryptionKeyDetails, false, ['decrypt']);
let keyhmac = await crypto.subtle.importKey("raw", key_raw, Const.HMACKeyDetails, false, ['verify']);
let enc_data = VBF.GetEncryptedPart(header.version, blob);
let decrypted_file = await crypto.subtle.decrypt({ name: Const.EncryptionAlgo, iv: iv_raw }, key, enc_data);
//read the header
let json_header_length = new Uint16Array(decrypted_file.slice(0, 2))[0];
let json_header_text = new TextDecoder('utf-8').decode(decrypted_file.slice(2, json_header_length + 2));
Log.I(`${this.fileinfo.FileId} header is ${json_header_text}`);
//hash the file to verify
let file_data = decrypted_file.slice(2 + json_header_length);
let hmac_verify = await crypto.subtle.verify(Const.HMACKeyDetails, keyhmac, header.hmac, file_data);
if (hmac_verify) {
Log.I(`${this.fileinfo.FileId} HMAC verified!`);
let header_obj = JSON.parse(json_header_text);
return { blob: new Blob([file_data], { type: header_obj.mime }), name: header_obj.name };
} else {
throw "HMAC verify failed";
}
};
};
export { FileDownloader };

View File

@ -1,505 +0,0 @@
import * as Const from './Const.js';
import { Templates } from './App.js';
import { XHR, Utils, Log, $ } from './Util.js';
import { VBF } from './VBF.js';
import { bytes_to_base64, HmacSha256, AES_CBC } from 'asmcrypto.js';
/**
* File upload handler class
* @class
* @param {File} file - The file handle to upload
* @param {string} host - The hostname to upload to
*/
function FileUpload(file, host) {
this.hasCrypto = typeof window.crypto.subtle === "object";
this.file = file;
this.host = host;
this.domNode = null;
this.key = new Uint8Array(16);
this.iv = new Uint8Array(16);
/**
* Track uplaod stats
*/
this.uploadStats = {
lastRate: 0,
lastLoaded: 0,
lastProgress: 0
};
/**
* Get the encryption key as hex
* @returns {Promise<string>} The encryption get in hex
*/
this.HexKey = async () => {
return Utils.ArrayToHex(await crypto.subtle.exportKey('raw', this.key));
};
/**
* Get the IV as hex
* @returns {string} The IV for envryption has hex
*/
this.HexIV = () => {
return Utils.ArrayToHex(this.iv);
};
/**
* Returns the formatted key and iv as hex
* @returns {Promise<string>} The key:iv as hex
*/
this.TextKey = async () => {
return `${await this.HexKey()}:${this.HexIV()}`;
};
/**
* Retruns the formatted hash fragment for this upload
* @param {string} id - The id returned from upload result
* @returns {Promise<string>} The id:key:iv concatenated and converted to base64
*/
this.FormatUrl = async (id) => {
let id_hex = new Uint8Array(Utils.HexToArray(id));
let key = new Uint8Array(await crypto.subtle.exportKey('raw', this.key));
let iv = new Uint8Array(this.iv);
let ret = new Uint8Array(id_hex.byteLength + key.byteLength + iv.byteLength);
ret.set(id_hex, 0);
ret.set(key, id_hex.byteLength);
ret.set(iv, id_hex.byteLength + key.byteLength);
return bytes_to_base64(ret);
};
/**
* Retruns the formatted hash fragment for this upload
* @param {string} id - The id returned from upload result
* @returns {Promise<string>} The id:key:iv concatenated and converted to base64
*/
this.FormatRawUrl = async (id) => {
let id_hex = new Uint8Array(Utils.HexToArray(id));
let ret = new Uint8Array(id_hex.byteLength + this.key.byteLength + this.iv.byteLength);
ret.set(id_hex, 0);
ret.set(this.key, id_hex.byteLength);
ret.set(this.iv, id_hex.byteLength + this.key.byteLength);
return bytes_to_base64(ret);
};
/**
* Loads the file and SHA256 hashes it
* @return {Promise<ArrayBuffer>}
*/
this.HashFile = async () => {
return new Promise(function (resolve, reject) {
var fr = new FileReader();
fr.onloadstart = function (ev) {
this.HandleProgress('state-load-start');
}.bind(this);
fr.onloadend = function (ev) {
this.HandleProgress('state-load-end');
}.bind(this);
fr.onload = function (ev) {
this.HandleProgress('state-hash-start');
crypto.subtle.sign(Const.HMACKeyDetails, this.hmackey, ev.target.result).then(function (hash) {
this.HandleProgress('state-hash-end');
resolve({
hash: hash,
data: ev.target.result
});
}.bind(this));
}.bind(this);
fr.onprogress = function (ev) {
this.HandleProgress('progress', ev.loaded / parseFloat(ev.total));
}.bind(this);
fr.onerror = function (ev) {
this.HandleError({
type: 'FileReaderError',
error: ev.target.error
})
}.bind(this);
fr.readAsArrayBuffer(this.file);
}.bind(this));
};
/**
* Sets the width of the progress bar for this upload
* @param {number} value - The value of the progress
*/
this.SetProgressBar = function (value) {
this.domNode.progress.textContent = `${(100 * value).toFixed(1)}%`;
this.domNode.progressBar.style.width = `${(100 * value)}%`;
};
/**
* Sets the status label for this upload
* @param {string} value - The status label
*/
this.SetStatus = function (value) {
this.domNode.state.textContent = `Status: ${value}`;
};
/**
* Sets the speed value on the UI
*/
this.SetSpeed = function (value) {
this.domNode.filespeed.textContent = value;
};
/**
* Handles progress messages from the upload process and updates the UI
* @param {string} type - The progress event type
* @param {number} progress - The percentage of this progress type
*/
this.HandleProgress = function (type, progress) {
switch (type) {
case 'state-load-start': {
this.SetStatus('Loading file..');
this.SetProgressBar(0);
break;
}
case 'state-load-end': {
this.SetProgressBar(1);
break;
}
case 'state-hash-start': {
this.SetStatus('Hashing..');
this.SetProgressBar(0);
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': {
this.SetStatus('Uploading..');
this.SetProgressBar(0);
break;
}
case 'state-upload-end': {
this.SetProgressBar(1);
this.SetSpeed("Done");
break;
}
case 'progress': {
this.SetProgressBar(progress < 0.01 ? 0.01 : progress);
break;
}
}
};
/**
* Handles upload errors to display on the UI
*/
this.HandleError = function (err) {
Log.E(err.error);
switch (err.type) {
case 'FileReaderError': {
this.SetProgressBar('1px');
break;
}
}
};
/**
* Creates a template for the upload to show progress
*/
this.CreateNode = function () {
let nelm = document.importNode(Templates.Upload.content, true);
nelm.filename = nelm.querySelector('.file-info .file-info-name');
nelm.filesize = nelm.querySelector('.file-info .file-info-size');
nelm.filespeed = nelm.querySelector('.file-info .file-info-speed');
nelm.progress = nelm.querySelector('.upload-progress span');
nelm.progressBar = nelm.querySelector('.upload-progress div');
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 = Utils.FormatBytes(this.file.size, 2);
this.domNode = nelm;
$('#uploads').appendChild(nelm);
};
/**
* Generates a new key to use for encrypting the file
* @returns {Uint8Array} The new key
*/
this.GenerateRawKey = function () {
crypto.getRandomValues(this.key);
crypto.getRandomValues(this.iv);
this.domNode.key.textContent = `Key: ${Utils.ArrayToHex(this.key)}`;
return this.key;
};
/**
* Generates a new key to use for encrypting the file
* @returns {Promise<CryptoKey>} The new key
*/
this.GenerateKey = async function () {
this.key = await crypto.subtle.generateKey(Const.EncryptionKeyDetails, true, ['encrypt', 'decrypt']);
this.hmackey = await crypto.subtle.importKey("raw", await crypto.subtle.exportKey('raw', this.key), Const.HMACKeyDetails, false, ["sign"]);
crypto.getRandomValues(this.iv);
this.domNode.key.textContent = `Key: ${await this.TextKey()}`;
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: Const.EncryptionAlgo,
iv: this.iv
}, this.key, fileData);
this.HandleProgress('state-encrypt-end');
return encryptedData;
};
/**
* Uploads Blob data to site
* @param {ReadableStream} fileData - The encrypted file data to upload
* @returns {Promise<object>} The json result
*/
this.UploadDataStream = async function (fileData) {
this.uploadStats.lastProgress = new Date().getTime();
this.HandleProgress('state-upload-start');
let request = new Request(`${window.location.protocol}//${this.host}/upload`, {
method: "POST",
body: fileData,
headers: { "Content-Type": "application/octet-stream" }
})
let response = await fetch(request);
return await response.json();
};
/**
* 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", `${window.location.protocol}//${this.host}/upload`, fileData, { "Content-Type": "application/octet-stream" }, 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.SetSpeed(`${Utils.FormatBytes(dxLoaded / (dxTime / 1000.0), 2)}/s`);
this.HandleProgress('progress', ev.loaded / parseFloat(ev.total));
}.bind(this));
this.HandleProgress('state-upload-end');
return JSON.parse(uploadResult.response);
};
/**
* Creates a header object to be prepended to the file for encrypting
* @returns {any}
*/
this.CreateHeader = function () {
return {
name: this.file.name,
mime: this.file.type,
len: this.file.size
};
};
/**
* Processes the file upload
* @return {Promise}
*/
this.ProcessUpload = async function () {
Log.I(`Starting upload for ${this.file.name}`);
this.CreateNode();
await this.GenerateKey();
let header = JSON.stringify(this.CreateHeader());
let hash_data = await this.HashFile();
let h256 = Utils.ArrayToHex(hash_data.hash);
Log.I(`${this.file.name} hash is: ${h256}`);
//create blob for encryption
let header_data = new TextEncoder('utf-8').encode(header);
Log.I(`Using header: ${header} (length=${header_data.byteLength})`);
let encryption_payload = new Uint8Array(2 + header_data.byteLength + hash_data.data.byteLength);
let header_length_data = new Uint16Array(1);
header_length_data[0] = header_data.byteLength; //header length
encryption_payload.set(header_length_data, 0);
encryption_payload.set(new Uint8Array(header_data), 2); //the file info header
encryption_payload.set(new Uint8Array(hash_data.data), 2 + header_data.byteLength);
//encrypt with the key
Log.I(`Encrypting ${this.file.name} with key ${await this.HexKey()} and IV ${this.HexIV()}`)
let encryptedData = await this.EncryptFile(encryption_payload);
Log.I(`Uploading file ${this.file.name}`);
let upload_payload = VBF.Create(hash_data.hash, encryptedData);
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.links.style.display = "";
let nl = document.createElement("a");
nl.target = "_blank";
nl.href = `${window.location.protocol}//${window.location.host}/#${await this.FormatUrl(uploadResult.id)}`;
nl.textContent = this.file.name;
this.domNode.links.appendChild(nl);
} else {
this.domNode.errors.style.display = "";
this.domNode.errors.textContent = uploadResult.msg;
}
};
/**
* Stream the file upload
* @return {Promise}
*/
this.StreamUpload = async function () {
Log.I(`Starting upload for ${this.file.name}`);
this.CreateNode();
this.GenerateRawKey();
let header = JSON.stringify(this.CreateHeader());
let vbf_stream = {
self: this,
header,
start(controller) {
this.self.HandleProgress('state-load-start');
this.offset = 0;
this.chunkSize = 32 * 1024;
this.aes = new AES_CBC(this.self.key, this.self.iv, true);
this.hmac = new HmacSha256(this.self.key);
//encode the header to bytes for encryption
this.header_data = new TextEncoder('utf-8').encode(this.header);
Log.I(`Using header: ${this.header} (length=${this.header_data.byteLength})`);
},
pull(controller) {
return (async function () {
debugger;
while (true) {
let read_now = this.chunkSize;
if (this.offset === 0) {
controller.enqueue(VBF.CreateV2Start());
read_now -= this.header_data.byteLength;
}
let file_to_read = this.self.file.slice(this.offset, this.offset + read_now);
let data = await new Promise(function (resolve, reject) {
let fr = new FileReader();
fr.onload = function (ev) {
resolve(ev.target.result);
}
fr.onerror = function (ev) { reject(); }
fr.readAsArrayBuffer(file_to_read);
});
//check is eof
if (this.offset + data.byteLength === this.self.file.size) {
//done, send last encrypted part and hmac
this.hmac.process(data);
controller.enqueue(this.aes.AES_Encrypt_process(data));
controller.enqueue(this.aes.AES_Encrypt_finish());
this.hmac.finish();
controller.enqueue(this.hmac.result);
controller.close();
return;
}
var buf = null;
if (this.offset === 0) {
buf = new Uint8Array(2 + this.header_data.byteLength + data.byteLength);
let hlen = new Uint16Array(1);
hlen[0] = this.header_data.byteLength;
buf.set(hlen, 0)
buf.set(this.header_data, hlen.byteLength);
buf.set(data, hlen.byteLength + this.header_data.byteLength);
} else {
buf = new Uint8Array(data);
}
//hash the buffer
this.hmac.process(buf);
//encrypt the buffer
controller.enqueue(this.aes.AES_Encrypt_process(buf));
//add offset bytes to fileread
this.offset += data.byteLength;
}
}.bind(this))();
}
};
let file_stream = new ReadableStream(vbf_stream);
Log.I(`Uploading file ${this.file.name}`);
let uploadResult = await this.UploadData(file_stream);
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.links.style.display = "";
let nl = document.createElement("a");
nl.target = "_blank";
nl.href = `${window.location.protocol}//${window.location.host}/#${await this.FormatRawUrl(uploadResult.id)}`;
nl.textContent = this.file.name;
this.domNode.links.appendChild(nl);
} else {
this.domNode.errors.style.display = "";
this.domNode.errors.textContent = uploadResult.msg;
}
};
};
export { FileUpload };

View File

@ -1,191 +0,0 @@
import * as Const from './Const.js';
/**
* @constant {function} - Helper function for document.querySelector
* @param {string} selector - The selector to use in the query
* @returns {HTMLElement} The first selected element
*/
export const $ = (selector) => document.querySelector(selector);
export const Log = {
I: (msg) => console.log(`[App_v ${Const.AppVersion}][I]: ${msg}`),
W: (msg) => console.warn(`[App_v ${Const.AppVersion}][W]: ${msg}`),
E: (msg) => console.error(`[App_v ${Const.AppVersion}][E]: ${msg}`)
};
/**
* Make a HTTP request with promise
* @param {string} method - HTTP method for this request
* @param {string} url - Request URL
* @param {[object]} data - Request payload (method must be post)
* @returns {Promise<XMLHttpRequest>} The completed request
*/
export async function JsonXHR(method, url, data) {
if ('fetch' in self) {
let resp = await fetch(url, {
method: method,
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json"
}
});
return {
response: await resp.text()
};
} else {
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
* @param {[function]} uploadprogress - Progress function from data uploads
* @param {[function]} downloadprogress - Progress function for data downloads
* @param {[function]} editrequest - Function that can edit the request before its sent
* @returns {Promise<XMLHttpRequest>} The completed request
*/
export function XHR(method, url, data, headers, uploadprogress, downloadprogress, editrequest) {
return new Promise(function (resolve, reject) {
let x = new XMLHttpRequest();
x.onreadystatechange = function (ev) {
if (ev.target.readyState === 4) {
resolve(ev.target);
}
};
if (typeof uploadprogress === "function") {
x.upload.onprogress = uploadprogress;
}
if (typeof downloadprogress === "function") {
x.onprogress = downloadprogress;
}
x.onerror = function (ev) {
reject(ev);
};
x.open(method, url, true);
if (typeof editrequest === "function") {
editrequest(x);
}
//set headers if they are passed
if (typeof headers === "object") {
for (let h in headers) {
x.setRequestHeader(h, headers[h]);
}
}
if (method === "POST" && typeof data !== "undefined") {
x.send(data);
} else {
x.send();
}
})
};
/**
* Calls api handler
*/
export const Api = {
DoRequest: async function (req) {
return JSON.parse((await JsonXHR('POST', '/api', req)).response);
},
GetTxChart: async function (id) {
return await Api.DoRequest({
cmd: '7_day_tx_graph'
});
},
GetSiteInfo: async function (id) {
return await Api.DoRequest({
cmd: 'site_info'
});
},
GetFileInfo: async function (id) {
return await Api.DoRequest({
cmd: 'file_info',
id: id
});
},
CaptchaInfo: async function() {
return await Api.DoRequest({
cmd: 'captcha_info'
});
},
VerifyCaptchaRateLimit: async function(id, token) {
return await Api.DoRequest({
cmd: 'verify_captcha_rate_limit',
id: id,
token: token
});
}
};
/**
* Generic util functions
*/
export const Utils = {
/**
* Formats an ArrayBuffer to hex
* @param {ArrayBuffer} buffer - Input data to convert to hex
* @returns {string} The encoded data as a hex string
*/
ArrayToHex: (buffer) => Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''),
/**
* Converts hex to ArrayBuffer
* @param {string} hex - The hex to parse into ArrayBuffer
* @returns {ArrayBuffer} The parsed hex data
*/
HexToArray: (hex) => {
let ret = new Uint8Array(hex.length / 2)
for (let i = 0; i < hex.length; i += 2) {
ret[i / 2] = parseInt(hex.substring(i, i + 2), 16)
}
return ret.buffer
},
/**
* Formats bytes into binary notation
* @param {number} b - The value in bytes
* @param {number} [f=2] - The number of decimal places to use
* @returns {string} Bytes formatted in binary notation
*/
FormatBytes: (b, f) => {
f = typeof f === 'number' ? 2 : f;
if (b >= Const.YiB)
return (b / Const.YiB).toFixed(f) + ' YiB';
if (b >= Const.ZiB)
return (b / Const.ZiB).toFixed(f) + ' ZiB';
if (b >= Const.EiB)
return (b / Const.EiB).toFixed(f) + ' EiB';
if (b >= Const.PiB)
return (b / Const.PiB).toFixed(f) + ' PiB';
if (b >= Const.TiB)
return (b / Const.TiB).toFixed(f) + ' TiB';
if (b >= Const.GiB)
return (b / Const.GiB).toFixed(f) + ' GiB';
if (b >= Const.MiB)
return (b / Const.MiB).toFixed(f) + ' MiB';
if (b >= Const.kiB)
return (b / Const.kiB).toFixed(f) + ' KiB';
return b.toFixed(f) + ' B'
},
/**
* Gets the text length of x bytes as base64 (including padding)
* @param {number} x - N bytes
*/
Base64Len: (x) => Math.ceil(x / 3) * 4
};

View File

@ -1,180 +0,0 @@
/**
* @constant {object} - Creates and decodes VBF file format
*/
const VBF = {
Version: 2,
/**
* Creates a VBF file with the specified encryptedData using the current version
* @param {BufferSource} hash
* @param {BufferSource} encryptedData
* @param {number} version
* @returns {Uint8Array} VBF formatted file
*/
Create: function (hash, encryptedData, version) {
version = typeof version === "number" ? version : VBF.Version;
switch (version) {
case 1:
return VBF.CreateV1(hash, encryptedData);
case 2:
return VBF.CreateV2(hash, encryptedData);
}
},
/**
* Creates a VBF1 file with the specified encryptedData
* @param {BufferSource} hash
* @param {BufferSource} encryptedData
* @param {number} version
* @returns {Uint8Array} VBF formatted file
*/
CreateV1: function (hash, encryptedData) {
let upload_payload = new Uint8Array(37 + encryptedData.byteLength);
let created = new ArrayBuffer(4);
new DataView(created).setUint32(0, parseInt(new Date().getTime() / 1000), true);
upload_payload[0] = 1; //blob version
upload_payload.set(new Uint8Array(hash), 1);
upload_payload.set(new Uint8Array(created), hash.byteLength + 1);
upload_payload.set(new Uint8Array(encryptedData), 37);
return upload_payload;
},
/**
* Creates a VBF2 file with the specified encryptedData
* @param {BufferSource} hash
* @param {BufferSource} encryptedData
* @param {number} version
* @returns {Uint8Array} VBF formatted file
*/
CreateV2: function (hash, encryptedData) {
let header_len = 12;
let upload_payload = new Uint8Array(header_len + encryptedData.byteLength + hash.byteLength);
let created = new ArrayBuffer(4);
new DataView(created).setUint32(0, parseInt(new Date().getTime() / 1000), true);
upload_payload.set(new Uint8Array([0x02, 0x4f, 0x49, 0x44, 0xf0, 0x9f, 0x90, 0xb1]), 0);
upload_payload.set(new Uint8Array(created), 8);
upload_payload.set(new Uint8Array(encryptedData), header_len);
upload_payload.set(new Uint8Array(hash), header_len + encryptedData.byteLength);
return upload_payload;
},
/**
* Creates the header part of VBF_V2
* @param {BufferSource} hash
* @param {BufferSource} encryptedData
* @param {number} version
* @returns {Uint8Array} VBF2 header
*/
CreateV2Start: function () {
let header_len = 12;
let start = new Uint8Array(header_len);
let created = new ArrayBuffer(4);
new DataView(created).setUint32(0, parseInt(new Date().getTime() / 1000), true);
start.set(new Uint8Array([0x02, 0x4f, 0x49, 0x44, 0xf0, 0x9f, 0x90, 0xb1]), 0);
start.set(new Uint8Array(created), 8);
return start;
},
/**
* Returns the encrypted part of the VBF blob
* @param {number} version
* @param {ArrayBuffer} blob
*/
GetEncryptedPart: function (version, blob) {
switch (version) {
case 1:
return blob.slice(37);
case 2:
return blob.slice(12, blob.byteLength - 32);
}
},
/**
* Returns a position to slice from for the start of encrypted data
* @param {number} version
* @param {ArrayBuffer} blob
* @returns {number} The position to slice from
*/
SliceToEncryptedPart: function (version, blob) {
switch (version) {
case 1:
return 37;
case 2:
return 12;
}
},
/**
* Parses the header of the raw file
* @param {ArrayBuffer} data - Raw data from the server
* @returns {*} The header
*/
Parse: function (data) {
let version = new Uint8Array(data)[0];
if (version === 1) {
let hmac = data.slice(1, 33);
let uploaded = new DataView(data.slice(33, 37)).getUint32(0, true);
return {
version,
hmac,
uploaded,
magic: null
};
} else if (version === 2) {
let magic = data.slice(1, 8);
let hmac = data.slice(data.byteLength - 32);
let uploaded = new DataView(data.slice(8, 12)).getUint32(0, true);
return {
version,
hmac,
uploaded,
magic
};
}
},
/**
* Parses a buffer as the start of a VBF file
* @param {ArrayBuffer} data - The start of a VBF file
* @returns {*} VBF info object
*/
ParseStart: function (data) {
let version = new Uint8Array(data)[0];
if (version === 1 && data.byteLength >= 37) {
let hmac = data.slice(1, 33);
let uploaded = new DataView(data.slice(33, 37)).getUint32(0, true);
return {
version,
hmac,
uploaded,
magic: null
};
} else if (version === 2 && data.byteLength >= 12) {
let magic = data.slice(1, 8);
let uploaded = new DataView(data.slice(8, 12)).getUint32(0, true);
return {
version,
hmac: null,
uploaded,
magic
};
}
return null;
}
};
export { VBF };

View File

@ -1,175 +0,0 @@
import { Api, Utils, Log, $ } from './Util.js';
import { FileDownloader } from './FileDownloader.js';
import { base64_to_bytes } from 'asmcrypto.js';
/**
* @constructor Creates an instance of the ViewManager
*/
function ViewManager() {
this.id = null;
this.key = null;
this.iv = null;
/**
* Parse URL hash fragment and return its components
* @param {string} frag - The Url hash fragment excluding the #
* @returns {*} - The details decoded from the hash
*/
this.ParseFrag = function (frag) {
if (frag.indexOf(':') !== -1) {
let hs = frag.split(':');
return {
id: hs[0],
key: hs[1],
iv: hs[2]
}
} else if (frag.length === Utils.Base64Len(52)) { //base64 encoded id:key:iv
let hs = base64_to_bytes(frag);
return {
id: Utils.ArrayToHex(hs.slice(0, 20)),
key: Utils.ArrayToHex(hs.slice(20, 36)),
iv: Utils.ArrayToHex(hs.slice(36))
};
}
return null;
};
/**
* Loads the view for downloading files
*/
this.LoadView = async function () {
let uh = this.ParseFrag(window.location.hash.substr(1));
if (uh !== null) {
this.id = uh.id;
this.key = uh.key;
this.iv = uh.iv;
}
let fi = await Api.GetFileInfo(this.id);
if (fi.ok === true) {
$('#page-view .file-info-size').textContent = 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);
}
};
/**
* Starts the browser download action
* @param {string} url - The url to download
* @param {string?} name - The filename to donwload
*/
this.DownloadLink = function(url, name) {
var dl_link = document.createElement('a');
dl_link.href = url;
if (name !== undefined) {
dl_link.download = name;
}
dl_link.style.display = "none";
let lnk = document.body.appendChild(dl_link);
lnk.click();
document.body.removeChild(lnk);
};
this.ShowPreview = async function (fileinfo) {
let cap_info = await Api.CaptchaInfo();
let nelm = document.importNode($("template[id='tmpl-view-default']").content, true);
nelm.querySelector('.view-file-id').textContent = fileinfo.FileId;
if (fileinfo.IsLegacyUpload) {
let keyrow = nelm.querySelector('.view-key');
keyrow.textContent = fileinfo.LegacyFilename;
keyrow.previousElementSibling.textContent = "Filename:";
nelm.querySelector('.view-iv').parentNode.style.display = "none";
nelm.querySelector('.view-transfer-stats').style.display = "none";
} else {
nelm.querySelector('.view-key').textContent = this.key;
nelm.querySelector('.view-iv').textContent = this.iv;
}
nelm.querySelector('.btn-download').addEventListener('click', async function () {
//detect if the service worker is installed
if ('serviceWorker' in navigator) {
let swreg = await navigator.serviceWorker.getRegistration();
if (swreg !== null) {
Log.I(`Service worker detected, using it for download..`);
let elm_bar_label = document.querySelector('.view-download-progress div:nth-child(1)');
let elm_bar = document.querySelector('.view-download-progress div:nth-child(2)');
navigator.serviceWorker.addEventListener('message', event => {
let msg = event.data;
switch(msg.type) {
case "progress": {
elm_bar.style.width = `${100 * msg.value}%`;
elm_bar_label.textContent = `${(100 * msg.value).toFixed(0)}%`;
break;
}
case "info": {
document.querySelector('.view-download-label-speed').textContent = msg.value;
break;
}
}
});
let link = (this.fileinfo.DownloadHost !== null ? `${window.location.protocol}//${this.fileinfo.DownloadHost}` : '') + `/${window.location.hash.substr(1)}`;
this.self.DownloadLink(link);
return;
}
}
let fd = new FileDownloader(this.fileinfo, this.self.key, this.self.iv);
fd.onprogress = function (x) {
this.elm_bar.style.width = `${100 * x}%`;
this.elm_bar_label.textContent = `${(100 * x).toFixed(0)}%`;
}.bind({
elm_bar_label: document.querySelector('.view-download-progress div:nth-child(1)'),
elm_bar: document.querySelector('.view-download-progress div:nth-child(2)')
});
fd.oninfo = function (v) {
this.elm.textContent = v;
}.bind({
elm: document.querySelector('.view-download-label-speed')
});
fd.onratelimit = function () {
if (this.cap_info.ok) {
window.grecaptcha.execute(this.cap_info.data.site_key, { action: 'download_rate_limit' }).then(async function (token) {
let api_rsp = await Api.VerifyCaptchaRateLimit(this.id, token);
if (api_rsp.ok) {
document.querySelector('.btn-download').click(); //simulate button press to start download again
} else {
alert('Captcha check failed, are you a robot?');
}
}.bind({ id: this.id }));
} else {
Log.E('No recaptcha config set');
}
}.bind({
id: this.fileinfo.FileId,
cap_info: this.cap_info
});
fd.DownloadFile().then(function (file) {
if (file !== null) {
var objurl = file.isLegacy !== undefined ? file.url : URL.createObjectURL(file.blob);
DownloadLink(objurl, file.isLegacy === undefined ? file.name : undefined);
}
}).catch(function (err) {
alert(err);
});
}.bind({
self: this,
fileinfo,
cap_info
}));
$('#page-view').appendChild(nelm);
if (cap_info.ok) {
let st = document.createElement('script');
st.src = "https://www.google.com/recaptcha/api.js?render=" + cap_info.data.site_key;
st.async = true;
document.body.appendChild(st);
}
};
};
export { ViewManager };

View File

@ -1,31 +0,0 @@
const VoidFetch = function (event) {
let re = /\/([a-z0-9]{26,27}):([a-z0-9]{32}):([a-z0-9]{32})$/i;
if (re.test(event.request.url)) {
let rx = re.exec(event.request.url);
let id = rx[1];
let key = rx[2];
let iv = rx[3];
Log.I(`AutoDownloader taking request: ${id}`);
event.respondWith(async function () {
const client = await clients.get(event.clientId);
if (!client) return;
let fd = new FileDownloader({
FileId: id
}, key, iv);
let rsp = await fetch(`https://v3.void.cat/${id}`, {
mode: 'cors'
});
let blob = await rsp.arrayBuffer();
if (blob.byteLength > 0) {
let dec_file = await fd.DecryptFile(blob);
return new Response(dec_file.blob);
} else {
throw "Invalid data recieved from server";
}
}());
}
}
self.addEventListener('fetch', VoidFetch);

View File

@ -1,46 +0,0 @@
<?php
class Abuse {
public function CheckDownload($id) {
$redis = StaticRedis::WriteOp();
$key = REDIS_PREFIX . "uvc:" . USER_IP;
$views = $redis->hGet($key, $id);
if($views !== False) {
if($views >= Config::$Instance->download_captcha_check * 2) {
} else if($views >= Config::$Instance->download_captcha_check) {
http_response_code(429); // Too many requests, tell the client to do captcha check
exit();
}
}
$redis->hIncrBy($key, $id, 1);
}
public function VerifyCaptcha($token) : ?object {
if(isset(Config::$Instance->recaptcha_secret)) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://www.google.com/recaptcha/api/siteverify');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, array(
"secret" => Config::$Instance->recaptcha_secret,
"response" => $token,
"remoteip" => USER_IP
));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$crsp = json_decode(curl_exec($ch));
curl_close ($ch);
return $crsp;
}
return null;
}
public function ResetRateLimits($id) {
$redis = StaticRedis::WriteOp();
$key = REDIS_PREFIX . "uvc:" . USER_IP;
$redis->hSet($key, $id, 0);
}
}
?>

View File

@ -1,9 +0,0 @@
<?php
class Admin implements RequestHandler {
public function HandleRequest() : void {
include(dirname(__FILE__) . "/../admin/index.html");
}
}
?>

View File

@ -1,88 +0,0 @@
<?php
class ApiResponse {
public $ok = false;
public $msg;
public $data;
public $cmd;
}
class Api implements RequestHandler {
public function __construct(){
Config::LoadConfig(array('max_upload_size', 'upload_folder', 'recaptcha_site_key', 'recaptcha_secret'));
ini_set('enable_post_data_reading', 0);
}
public function HandleRequest() : void {
$cmd = json_decode(file_get_contents("php://input"));
$rsp = new ApiResponse();
$rsp->cmd = $cmd;
$fs = new FileStore(Config::$Instance->upload_folder);
switch($cmd->cmd){
case "site_info": {
$rsp->ok = true;
$rsp->data = array(
"max_upload_size" => Config::$Instance->max_upload_size,
"basic_stats" => Stats::Get(),
"upload_host" => Upload::GetUploadHost(),
"geoip_info" => geoip_database_info(),
"host" => gethostname()
);
break;
}
case "file_info": {
$rsp->ok = true;
$rsp->data = $fs->GetFileInfo($cmd->id);
//$rsp->data->DownloadHost = Upload::GetUploadHost(); //bypass CF proxy for downloads (slow..)
break;
}
case 'captcha_info': {
if(isset(Config::$Instance->recaptcha_site_key) && Config::$Instance->recaptcha_site_key !== False && isset(Config::$Instance->recaptcha_secret) && Config::$Instance->recaptcha_secret !== False) {
$rsp->ok = true;
$rsp->data = array(
"site_key" => Config::$Instance->recaptcha_site_key
);
}
break;
}
case "verify_captcha_rate_limit": {
$abuse = new Abuse();
$rsp->data = $abuse->VerifyCaptcha($cmd->token);
if($rsp->data !== null && $rsp->data->success) {
$abuse->ResetRateLimits($cmd->id);
$rsp->ok = true;
}
break;
}
case "7_day_tx_graph": {
$stats = Stats::GetTxStats(24 * 7);
$data = array();
foreach($stats as $time => $bytes){
$data[] = array(
"t" => date_timestamp_get(date_create_from_format("YmdH", $time)) * 1000,
"y" => $bytes
);
}
$rsp->data = array(
"datasets" => array(
array(
"label" => "bytes",
"data" => $data
)
)
);
$rsp->ok = true;
break;
}
}
header('Content-Type: application/json');
echo json_encode($rsp);
}
}
?>

View File

@ -1,38 +0,0 @@
<?php
class Auth {
//https://stackoverflow.com/questions/40582161/how-to-properly-use-bearer-tokens
public function GetAuthorizationHeader() : ?string {
$headers = null;
if (isset($_SERVER['Authorization'])) {
$headers = trim($_SERVER["Authorization"]);
}
else if (isset($_SERVER['HTTP_AUTHORIZATION'])) { //Nginx or fast CGI
$headers = trim($_SERVER["HTTP_AUTHORIZATION"]);
} elseif (function_exists('apache_request_headers')) {
$requestHeaders = apache_request_headers();
// Server-side fix for bug in old Android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization)
$requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders));
if (isset($requestHeaders['Authorization'])) {
$headers = trim($requestHeaders['Authorization']);
}
}
return $headers;
}
function GetBearerToken() : ?string {
$headers = $this->GetAuthorizationHeader();
if (!empty($headers)) {
if (preg_match('/Bearer\s(\S+)/', $headers, $matches)) {
return $matches[1];
}
}
return null;
}
function CheckApiToken($token) {
return StaticRedis::ReadOp()->sIsMember(REDIS_PREFIX . "api-keys", $token);
}
}
?>

View File

@ -1,41 +0,0 @@
<?php
class BlobFile {
public $Version;
public $Hash;
public $Uploaded;
public static function LoadHeader($path) : ?BlobFile {
$input = fopen($path, "rb");
if ($input !== false) {
$version = ord(fread($input, 1));
//error_log($version);
$bf = new BlobFile();
if($version == 1) {
$header = fread($input, 36); //+32 byte hash (64 hex digits) + 4 byte timestamp
fclose($input);
$header_data = unpack("H64hash256/Vuploaded", $header);
$bf->Version = 1;
$bf->Hash = $header_data["hash256"];
$bf->Uploaded = $header_data["uploaded"];
return $bf;
} elseif($version == 2) {
$header = fread($input, 11); //+7 magic bytes + 4 byte timestamp
$header_data = unpack("H14magic/Vuploaded", $header);
fclose($input);
//error_log("Magic is: " . $header_data["magic"]);
if($header_data["magic"] == "4f4944f09f90b1") { //OID🐱 as hex (UTF-8)
$bf->Version = 2;
$bf->Uploaded = $header_data["uploaded"];
return $bf;
}
} else {
fclose($input);
}
}
return null;
}
}
?>

View File

@ -1,33 +0,0 @@
<?php
class Config {
public static $Instance;
public static function GetConfig($config_name) {
$redis = StaticRedis::ReadOp();
return $redis->hGet(REDIS_PREFIX . 'config', $config_name);
}
public static function MGetConfig($config_name) {
$redis = StaticRedis::ReadOp();
return (object)$redis->hMGet(REDIS_PREFIX . 'config', $config_name);
}
public static function LoadConfig($config_name){
self::$Instance = self::MGetConfig($config_name);
//set defaults
if(!isset(self::$Instance->upload_folder) || self::$Instance->upload_folder == False) {
self::$Instance->upload_folder = "out";
}
if(!isset(self::$Instance->public_hash_algo) || self::$Instance->public_hash_algo == False) {
self::$Instance->public_hash_algo = "ripemd160";
}
if(!isset(self::$Instance->max_upload_size) || self::$Instance->max_upload_size == False) {
self::$Instance->max_upload_size = 104857600; //100MiB is the default upload size
}
if(!isset(self::$Instance->download_captcha_check) || self::$Instance->download_captcha_check == False) {
self::$Instance->download_captcha_check = 10;
}
}
}
?>

View File

@ -1,62 +0,0 @@
<?php
include_once("init.php");
if(StaticRedis::Connect()) {
echo "Connected to redis..\n";
Config::LoadConfig(array("upload_folder", "discord_webhook_pub"));
$fs = new FileStore(Config::$Instance->upload_folder, $_SERVER["cron_root"]);
//delete expired files
$pmsg = "`" . gethostname() . ":\n";
$redis = StaticRedis::ReadOp();
$deleted = false;
foreach($fs->ListFiles() as $file) {
$id = basename($file);
$file_key = REDIS_PREFIX . $id;
$lv = $redis->hGet($file_key, "lastview");
$expire = time() - (30 * 24 * 60 * 60);
//use the file upload timestamp if there is no view data recorded
//if the file upload time is greater than the current timestamp, mark as old (!!abuse!!)
//this will also force legacy file uploads with no views to be deleted (header will always fail to load)
if($lv === false) {
$file_header = BlobFile::LoadHeader($file);
if($file_header !== null && $file_header->Uploaded <= time()){
$lv = $file_header->Uploaded;
} else {
//cant read file header or upload timestamp is invalid, mark as old
$lv = $file_header !== null ? $file_header->Uploaded : 0;
}
}
if($lv !== false && (intval($lv) < $expire) || intval($lv) > time()) {
$nmsg = "Deleting expired file: " . $id . " (lastview=" . date("Y-m-d h:i:s", intval($lv)) . ")\n";
if(strlen($pmsg) + strlen($nmsg) >= 2000){
//send to discord public hook
$pmsg = $pmsg . "`";
Discord::SendPublic(array(
"content" => $pmsg
));
$pmsg = "`";
}
$pmsg = $pmsg . $nmsg;
unlink($file);
$deleted = true;
}
}
//send last message if any
if(strlen($pmsg) > 0 && $deleted) {
$pmsg = $pmsg . "`";
Discord::SendPublic(array(
"content" => $pmsg
));
}
if(StaticRedis::$IsConnectedToSlave == False) {
echo "Runing master node tasks..\n";
Stats::Collect($fs);
}
}
?>

View File

@ -1,26 +0,0 @@
<?php
class Discord {
public static function SendPublic($msg) : void {
self::CallWebhook(Config::$Instance->discord_webhook_pub, $msg);
}
private static function CallWebhook($url, $data) : void {
self::CurlPost($url, json_encode($data));
}
private static function CurlPost($url, $data) : ?string {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
}
?>

View File

@ -1,56 +0,0 @@
<?php
class Download implements RequestHandler {
private $Config;
private $Fs;
public function __construct() {
Config::LoadConfig(array("upload_folder", "download_captcha_check"));
}
public function HandleRequest() : void {
$ref = isset($_SERVER["HTTP_REFERER"]) ? $_SERVER["HTTP_REFERER"] : null;
$id = isset($_REQUEST["id"]) ? $_REQUEST["id"] : null;
if($ref === null && $id !== null) {
header("location: /");
} else if($id !== null) {
$this->Fs = new FileStore(Config::$Instance->upload_folder);
if($this->Fs->FileExists($id)) {
$this->StartDownload($id, $this->Fs->GetFileInfo($id));
} else {
http_response_code(404);
}
} else {
http_response_code(404);
}
}
function StartDownload($id, $info) : void {
$abuse = new Abuse();
$tracking = new Tracking();
header("Cache-Control: private");
if(isset($_SERVER["HTTP_ORIGIN"])) {
header("Access-Control-Allow-Origin: " . $_SERVER["HTTP_ORIGIN"]);
header("Access-Control-Allow-Method: GET");
}
$abuse->CheckDownload($id);
$tracking->TrackDownload($this->Fs, $id);
if($_SERVER["REQUEST_METHOD"] === "GET") {
$this->InternalNginxRedirect($this->Fs->GetRelativeFilePath($id), 604800, $info);
}
}
function InternalNginxRedirect($location, $expire, $info) : void {
header("X-Accel-Redirect: /" . $location);
if($info->IsLegacyUpload) {
header("Content-Type: $info->LegacyMime");
header("Content-Disposition: inline; filename=\"$info->LegacyFilename\"");
} else {
header("Content-Type: application/octet-stream");
}
}
}
?>

View File

@ -1,15 +0,0 @@
<?php
class FileInfo {
public $FileId;
public $Views;
public $Hash;
public $LastView;
public $Uploaded;
public $Size;
public $DownloadHost;
public $IsLegacyUpload;
public $LegacyFilename;
public $LegacyMime;
}
?>

View File

@ -1,144 +0,0 @@
<?php
class FileStore {
private $UploadFolder;
private $DocumentRoot;
public function __construct($dir, $root = null) {
$this->UploadFolder = $dir;
$this->DocumentRoot = $root === null ? $_SERVER["DOCUMENT_ROOT"] : $root;
}
public function SetFileStats($info) : void {
$redis = StaticRedis::WriteOp();
$file_key = REDIS_PREFIX . $info->FileId;
$redis->hMSet($file_key, array(
'views' => $info->Views,
'lastview' => $info->LastView
));
}
public function GetFileStats($id) : object {
$redis = StaticRedis::ReadOp();
$file_key = REDIS_PREFIX . $id;
$public_file_info = $redis->hMGet($file_key, array('views', 'lastview', 'islegacy', 'filename', 'mime'));
return (object)array(
"views" => ($public_file_info["views"] !== False ? $public_file_info["views"] : 0),
"lastview" => ($public_file_info["lastview"] !== False ? $public_file_info["lastview"] : 0),
"islegacy" => ($public_file_info["islegacy"] !== False ? $public_file_info["islegacy"] === "1" : false),
"filename" => ($public_file_info["filename"] !== False ? $public_file_info["filename"] : ""),
"mime" => ($public_file_info["mime"] !== False ? $public_file_info["mime"] : ""),
);
}
public function SetAsLegacyFile($info) : void {
$redis = StaticRedis::WriteOp();
$file_key = REDIS_PREFIX . $info->FileId;
$redis->hMSet($file_key, array(
'islegacy' => true,
'filename' => $info->LegacyFilename,
'mime' => $info->LegacyMime
));
}
public function GetUploadDirAbsolute() : string {
return "$this->DocumentRoot/$this->UploadFolder";
}
/**
* $id should be the ripemd160(hmac-sha256(file, key)) as hex
*/
public function GetRelativeFilePath($id) : string {
return $this->UploadFolder . "/" . $id;
}
/**
* $id should be the ripemd160(hmac-sha256(file, key)) as hex
*/
public function GetAbsoluteFilePath($id) : string {
return $this->GetUploadDirAbsolute() . "/" . $id;
}
public function GetFileInfo($id) : ?FileInfo {
$file_path = $this->GetAbsoluteFilePath($id);
if($this->FileExists($id)) {
$stats = $this->GetFileStats($id);
$file_stat = stat($file_path);
$file = new FileInfo();
$file->FileId = $id;
$file->Views = intval($stats->views);
$file->LastView = intval($stats->lastview);
$file->Size = $file_stat["size"];
$file->Uploaded = $file_stat["ctime"];
$file->IsLegacyUpload = $stats->islegacy;
$file->LegacyFilename = $stats->filename;
$file->LegacyMime = $stats->mime;
return $file;
}
return NULL;
}
public function FileExists($id) : bool {
$file_path = $this->GetAbsoluteFilePath($id);
return file_exists($file_path);
}
public function StoreFile($file, $id) : bool {
$file_path = $this->GetAbsoluteFilePath($id);
if(!file_exists($file_path)) {
$fout = fopen($file_path, 'wb+');
stream_copy_to_stream($file, $fout);
fclose($fout);
return true;
}
return false;
}
public function StoreV1File($bf, $file) : ?string {
$id = hash(Config::$Instance->public_hash_algo, $bf->Hash);
$input = fopen($file, "rb");
$res = $this->StoreFile($input, $id);
fclose($input);
return $res ? $id : null;
}
public function StoreV2File($bf, $file) : ?string {
//we need to seek to the end before finding the id, do that first
$input = fopen($file, "rb");
$temp_name = tempnam($this->GetUploadDirAbsolute(), "VTMP_");
$input_temp = fopen($temp_name, "wb+");
stream_copy_to_stream($input, $input_temp);
fclose($input);
fseek($input_temp, -32, SEEK_END);
$hash = unpack("H64hash256", fread($input_temp, 32));
fclose($input_temp);
$id = hash(Config::$Instance->public_hash_algo, $hash["hash256"]);
$file_path = $this->GetAbsoluteFilePath($id);
if(!file_exists($file_path)){
rename($temp_name, $file_path);
return $id;
}
return null;
}
/**
* $id should be formatted base62 filename
*/
public function GetFileSize($id) : int {
return filesize($this->GetAbsoluteFilePath($id));
}
public function ListFiles() : array {
return glob($this->GetUploadDirAbsolute() . "/*");
}
}
?>

View File

@ -1,25 +0,0 @@
<?php
include_once("init.php");
//Startup
if(StaticRedis::Connect() == True) {
Tracking::SendMatomoEvent();
if(isset($_REQUEST["h"])) {
$handler_name = $_REQUEST["h"];
if(file_exists($handler_name . '.php')){
$handler = new $handler_name();
if($handler instanceof RequestHandler){
$handler->HandleRequest();
exit();
}
}
}
//var_dump($_REQUEST);
http_response_code(400);
exit();
} else {
http_response_code(500);
exit();
}
?>

View File

@ -1,7 +0,0 @@
<?php
class Info implements RequestHandler {
public function HandleRequest() : void {
phpinfo();
}
}
?>

View File

@ -1,14 +0,0 @@
<?php
define('REDIS_CONFIG', 'redis-host');
define('REDIS_DB', 0);
define('REDIS_PREFIX', 'vc:');
define('USER_IP', isset($_SERVER['HTTP_CF_CONNECTING_IP']) ? $_SERVER['HTTP_CF_CONNECTING_IP'] : (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : "127.0.0.1"));
if(!isset($_COOKIE["VC:UID"])) {
setcookie("VC:UID", uniqid());
}
spl_autoload_register(function ($class_name) {
include dirname(__FILE__) . '/' . strtolower($class_name) . '.php';
});
?>

View File

@ -1,5 +0,0 @@
<?php
interface RequestHandler {
public function HandleRequest() : void;
}
?>

View File

@ -1,36 +0,0 @@
<?php
class StaticRedis {
public static $Instance = null;
public static $MasterInstance = null;
public static $IsConnectedToSlave = false;
public static function ReadOp() : object {
return self::$Instance;
}
public static function WriteOp() : object {
if(self::$MasterInstance != null){
return self::$MasterInstance;
} else {
return self::$Instance;
}
}
public static function Connect() : bool {
self::$Instance = new Redis();
$con = self::$Instance->pconnect(REDIS_CONFIG);
if($con){
self::$Instance->select(REDIS_DB);
$rep = self::$Instance->info();
if($rep["role"] == "slave"){
self::$IsConnectedToSlave = true;
self::$MasterInstance = new Redis();
$mcon = self::$MasterInstance->pconnect($rep["master_host"], $rep["master_port"]);
return $con && $mcon;
}
}
return $con;
}
}
?>

View File

@ -1,79 +0,0 @@
<?php
class Stats {
public $Files;
public $Size;
public $Transfer_24h;
private static $AllTransferStatsKey = REDIS_PREFIX . "stats-transfer-all:";
private static $GeneralStatsKey = REDIS_PREFIX . "stats-general";
public static function GetTxStats($nHours) : array {
$redis = StaticRedis::ReadOp();
$ret = array();
$now = time();
for($x = 0; $x < $nHours; $x += 1) {
$stat_key = date("YmdH", $now - (60 * 60 * $x));
$val = $redis->get(self::$AllTransferStatsKey . $stat_key);
if($val != False){
$ret[$stat_key] = intval($val);
} else {
$ret[$stat_key] = 0;
}
}
return $ret;
}
public static function Get() : Stats {
$redis = StaticRedis::ReadOp();
//calculate 24hr transfer stats
$tx_24h = 0;
foreach(self::GetTxStats(24) as $time => $bytes) {
$tx_24h += $bytes;
}
//get general stats
$general = (object)$redis->hMGet(self::$GeneralStatsKey, array("files", "size"));
$ret = new Stats();
$ret->Transfer_24h = $tx_24h;
$ret->Files = intval($general->files !== False ? $general->files : 0);
$ret->Size = intval($general->size !== False ? $general->size : 0);
return $ret;
}
public static function TrackTransfer($id, $size) : void {
self::AddAllTransfer($size);
}
public static function AddAllTransfer($size) : void {
$redis = StaticRedis::WriteOp();
$stat_member = date("YmdH");
$redis->incrBy(self::$AllTransferStatsKey . $stat_member, $size);
$redis->setTimeout(self::$AllTransferStatsKey . $stat_member, 2592000); //store 30 days only
}
public static function Collect($fs) : void {
$redis = StaticRedis::WriteOp();
$files = $fs->ListFiles();
$total_size = 0;
foreach($files as $file) {
$total_size += filesize($file);
}
$redis->hMSet(self::$GeneralStatsKey, array(
"files" => count($files),
"size" => $total_size
));
//tick from cron job to create keys for every hour
//if no downloads are happening we will be missing keys
//this will prevent inaccurate reporting
self::AddAllTransfer(0);
}
}
?>

View File

@ -1,59 +0,0 @@
<?php
class Sync implements RequestHandler {
public function __construct(){
Config::LoadConfig(array('max_upload_size', 'upload_folder'));
set_time_limit(1200);
ini_set('post_max_size', Config::$Instance->max_upload_size);
ini_set('upload_max_filesize', Config::$Instance->max_upload_size);
ini_set('memory_limit', Config::$Instance->max_upload_size);
ini_set('enable_post_data_reading', 0);
}
public function HandleRequest() : void {
if(isset($_SERVER["HTTP_X_FILE_ID"])) {
$id = $_SERVER["HTTP_X_FILE_ID"];
$fs = new FileStore(Config::$Instance->upload_folder);
if(!$fs->FileExists($id)) {
//resolve the hostnames to ips
$redis = StaticRedis::ReadOp();
$sync_hosts = $redis->sMembers(REDIS_PREFIX . 'sync-hosts');
$sync_hosts_ips = array();
foreach($sync_hosts as $host) {
$sync_hosts_ips[] = gethostbyname($host);
}
//check the ip of the host submitting the file for sync
if(in_array(USER_IP, $sync_hosts_ips)) {
$fs->StoreFile(fopen("php://input", "rb"), $id);
http_response_code(201);
} else {
http_response_code(401);
}
} else {
http_response_code(200);
}
} else {
http_response_code(400);
}
}
public static function SyncFile($id, $filename, $host) : int {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://$host/sync");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, file_get_contents($filename));
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"Content-Type: application/octet-stream",
"X-File-Id: " . $id
));
curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close ($ch);
return intval($status);
}
}
?>

View File

@ -1,18 +0,0 @@
<?php
class SyncThread extends Thread {
private $Destination;
private $FilePath;
private $Id;
public function __constrct($id, $filepath, $host){
$this->Id = $id;
$this->FilePath = $filepath;
$this->Destination = $host;
}
public function run() {
Sync::SyncFile($this->Id, $this->FilePath, $this->Destination);
}
}
?>

View File

@ -1,64 +0,0 @@
<?php
class Tracking {
public function TrackDownload($fs, $id) : void {
$file_size = $fs->GetFileSize($id);
if(!$this->IsRangeRequest()) {
$this->TrackView($id);
Stats::TrackTransfer($id, $file_size);
} else {
$range = $this->GetRequestRange($file_size);
Stats::TrackTransfer($id, $range->end - $range->start);
}
}
public function TrackView($id) : void {
$redis = StaticRedis::WriteOp();
$file_key = REDIS_PREFIX . $id;
$redis->hIncrBy($file_key, 'views', 1);
$redis->hSet($file_key, 'lastview', time());
}
function GetRequestRange($len) : ?object {
if(isset($_SERVER['HTTP_RANGE'])) {
$rby = explode('=', $_SERVER['HTTP_RANGE']);
$rbv = explode('-', $rby[1]);
return (object)array(
"start" => intval($rbv[0]),
"end" => intval($rbv[1] == "" ? $len : $rbv[1])
);
}
return null;
}
function IsRangeRequest() : bool {
$range = $this->GetRequestRange(0);
if($range !== null){
if($range->start != 0){
return true;
}
}
return false;
}
public static function SendMatomoEvent() : void {
$msg = "?" . http_build_query(array(
"idsite" => 3,
"rec" => 1,
"apiv" => 1,
"_id" => isset($_COOKIE["VC:UID"]) ? $_COOKIE["VC:UID"] : uniqid(),
"url" => (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]",
"cip" => USER_IP,
"ua" => isset($_SERVER["HTTP_USER_AGENT"]) ? $_SERVER["HTTP_USER_AGENT"] : "",
"urlref" => isset($_SERVER["HTTP_REFERER"]) ? $_SERVER["HTTP_REFERER"] : "",
"h" => date("H"),
"m" => date("i"),
"s" => date("s")
));
//this should be sent to the slave node if we are connected on a slave
StaticRedis::ReadOp()->publish(StaticRedis::$IsConnectedToSlave ? 'v3-matomo' : 'v3-matomo-master', $msg);
}
}
?>

View File

@ -1,158 +0,0 @@
<?php
class UploadResponse {
public $status = 0;
public $msg;
public $id;
public $sync;
}
class Upload implements RequestHandler {
private $IsMultipart = False;
public function __construct() {
Config::LoadConfig(array('max_upload_size', 'upload_folder', 'public_hash_algo'));
//set php params
set_time_limit(1200);
ini_set('post_max_size', Config::$Instance->max_upload_size);
ini_set('upload_max_filesize', Config::$Instance->max_upload_size);
ini_set('memory_limit', Config::$Instance->max_upload_size);
ini_set('enable_post_data_reading', 0);
//check upload dir exists
if(!file_exists("$_SERVER[DOCUMENT_ROOT]/" . Config::$Instance->upload_folder)){
mkdir("$_SERVER[DOCUMENT_ROOT]/" . Config::$Instance->upload_folder);
}
}
public function HandleRequest() : void {
if(isset($_SERVER["HTTP_ORIGIN"])) {
header("Access-Control-Allow-Origin: " . $_SERVER["HTTP_ORIGIN"]);
header("Access-Control-Allow-Method: POST,OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
}
$rsp = new UploadResponse();
$file_size = $_SERVER["CONTENT_LENGTH"];
if($file_size > Config::$Instance->max_upload_size){
$rsp->status = 1;
$rsp->msg = "File is too large";
} else {
$auth = new Auth();
$tracking = new Tracking();
$token = $auth->GetBearerToken();
if($token !== null) {
if($auth->CheckApiToken($token)) {
$id = $this->SaveLegacyUpload();
//sync to other servers
if($id !== null) {
$rsp->sync = $this->SyncFileUpload($id);
$rsp->status = 200;
$rsp->id = $id;
//finally set the last view to now
$tracking->TrackView($id);
} else {
$rsp->status = 3;
$rsp->msg = "Legacy upload error";
}
} else {
http_response_code(403);
exit();
}
} else {
$read_from = "php://input";
$bf = BlobFile::LoadHeader($read_from);
if($bf != null){
//save upload
$id = $this->SaveUpload($bf, $read_from);
//sync to other servers
if($id == null) {
$rsp->status = 4;
$rsp->msg = "Invalid VBF or file already exists";
} else {
$rsp->sync = $this->SyncFileUpload($id);
$rsp->status = 200;
$rsp->id = $id;
//finally set the last view to now
$tracking->TrackView($id);
}
} else {
$rsp->status = 2;
$rsp->msg = "Invalid file header";
}
}
}
header('Content-Type: application/json');
echo json_encode($rsp);
}
function SyncFileUpload($id) : array {
$redis = StaticRedis::ReadOp();
$sync_hosts = $redis->sMembers(REDIS_PREFIX . 'sync-hosts');
if($sync_hosts !== False) {
$fs = new FileStore(Config::$Instance->upload_folder);
$status_codes = [];
foreach($sync_hosts as $host) {
$status_codes[] = Sync::SyncFile($id, $fs->GetAbsoluteFilePath($id), $host);
}
return $status_codes;
}
return array();
}
function SaveUpload($bf, $rf) : ?string {
$fs = new FileStore(Config::$Instance->upload_folder);
switch($bf->Version) {
case 1:
return $fs->StoreV1File($bf, $rf);
case 2:
return $fs->StoreV2File($bf, $rf);
}
return null;
}
function SaveLegacyUpload() : ?string {
if(isset($_SERVER["HTTP_X_LEGACY_FILENAME"])){
$hash = hash_file("sha256", "php://input");
$id = gmp_strval(gmp_init("0x" . hash(Config::$Instance->public_hash_algo, $hash)), 62);
$fs = new FileStore(Config::$Instance->upload_folder);
$fs->StoreFile(fopen("php://input", "rb"), $id);
$info = new FileInfo();
$info->FileId = $id;
$info->LegacyFilename = $_SERVER["HTTP_X_LEGACY_FILENAME"];
$info->LegacyMime = isset($_SERVER["CONTENT_TYPE"]) ? $_SERVER["CONTENT_TYPE"] : "application/octet-stream";
$fs->SetAsLegacyFile($info);
return $id;
}
return null;
}
public static function GetUploadHost() : string {
$cont = geoip_continent_code_by_name(USER_IP);
if($cont === False){
$cont = "EU";
}
$redis = StaticRedis::ReadOp();
$map = $redis->hGetAll(REDIS_PREFIX . "upload-region-mapping");
if($map !== False && isset($map[$cont])) {
return $map[$cont];
} else {
return $_SERVER["HTTP_HOST"];
}
}
}
?>

View File

@ -1,341 +0,0 @@
$page-width: 1024px;
$page-padding: 10px;
$page-margin-top: 20px;
$dropzone-border-width: 2px;
$upload-progress-padding: 2px;
$upload-progress-height: 20px;
$upload-padding: 10px;
$upload-border: 2px solid rgb(168, 168, 168);
html, body {
margin: 0;
padding: 0;
font-family: Arial;
font-size: 12px;
background-color: #444;
}
a { text-decoration: none; color: rgb(0, 9, 94); font-weight: bold; }
a:visited { text-decoration: none; }
a:hover { text-decoration: underline; }
.page {
width: $page-width;
margin-left: auto;
margin-right: auto;
margin-top: $page-margin-top;
overflow: hidden;
border-radius: 10px;
background-color: rgb(233, 252, 255);
box-shadow: 0px 0px 15px 5px #000;
user-select: none;
}
#page-view {
display:none;
padding: 10px 10px 0px 10px;
overflow: hidden;
}
.page-left {
width: 50%;
float:left;
}
.page-right {
width: 50%;
float:left;
}
#dropzone {
border: $dropzone-border-width dashed #333;
line-height: 100px;
text-align: center;
font-size: 50px;
}
#dropzone small {
font-size: 20px;
}
#page-upload {
display: none;
padding: 10px 10px 0px 10px;
overflow: hidden;
}
.upload {
overflow: hidden;
border: $upload-border;
margin: $upload-padding;
margin-top: 0;
}
.upload .upload-progress {
border: 0.5px solid;
overflow: hidden;
text-align: center;
margin: 10px;
}
.upload .status {
display: grid;
grid-template-columns: 40% 60%;
margin: 10px;
}
.upload .status div {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.upload .file-info {
display: grid;
grid-template-columns: 60% 20% 20%;
background-color: rgb(96, 136, 146);
color: #eee;
}
.upload .file-info div {
padding: 10px;
}
.upload .file-info .file-info-name {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.upload .file-info .file-info-size {
background-color: rgb(0, 113, 133);
text-align: center;
}
.upload .file-info .file-info-speed {
background-color: rgb(0, 84, 99);
text-align: center;
}
.upload .upload-progress div {
margin-top: -$upload-progress-height;
height: $upload-progress-height;
line-height: $upload-progress-height;
font-size: $upload-progress-height * 0.8;
background-color: rgb(10, 161, 10);
}
.upload .links {
display: grid;
}
.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;
}
#footer {
text-align: center;
text-transform: uppercase;
font-size: 14px;
}
#footer-stats {
display: grid;
grid-template-columns: 33.3% 33.3% 33.3%;
text-align: center;
border: 1px solid #aaa;
margin: 0px 10px 10px 10px;
padding: 5px;
}
#page-notice {
display: none;
margin: 10px;
padding: 10px;
border: 3px solid black;
background-color: #a90000;
color: #ffe;
}
#page-stats {
display: none;
margin: 10px;
}
#page-faq {
display: none;
}
#page-donate {
display: none;
text-align: center;
}
#page-faq .faq-section {
margin: 10px;
}
#page-faq .faq-section .faq-header {
font-weight: bold;
margin-top: 10px;
padding: 10px;
overflow: hidden;
border: 1px solid #aaa;
background-color: #eee;
}
#page-faq .faq-section .faq-content {
display: none;
padding: 10px;
overflow: hidden;
border: 1px solid #eee;
background-color: #fff;
}
.show {
display: block !important;
}
#page-view .file-info {
display: grid;
grid-template-columns: 25% 25% 25% 25%;
border: 1px solid #aaa;
margin: 10px 0px 0px 0px;
padding: 5px;
}
.header {
text-align: center;
color: #555;
font-size: 50px;
padding: 10px;
border-bottom: 1px solid #aaa;
background-color: #e4e4e4;
border-radius: 5px 5px 0 0;
}
.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: 20px 10px;
background-color: rgb(180, 180, 180);
display: grid;
grid-template-columns: 60% 40%;
}
.view-default div {
line-height: 30px;
}
.view-default span {
float: right;
}
.view-default .view-transfer-stats {
display: grid;
grid-template-columns: 80% 20%;
}
.view-default .view-transfer-stats div {
height: 20px;
}
.view-default .view-transfer-stats div:nth-child(1) {
color: rgb(214, 214, 214);
}
.view-default .view-transfer-stats div:nth-child(2) {
color: rgb(214, 214, 214);
text-align: center;
line-height: 20px;
background-color: #007185;
}
.view-default .btn-download {
line-height: 100px;
grid-column-start: 2;
grid-row-start: 1;
grid-row-end: 5;
}
.view-default .view-download-progress div:nth-child(1) {
background-color: #333;
text-align: center;
line-height: 20px;
}
.view-default .view-download-progress div:nth-child(2) {
background-color: rgb(0, 146, 0);
height: 20px;
width: 0px;
margin-top: -20px;
}
@media (max-device-width: $page-width){
.page {
width: auto !important;
margin-top: 0;
border-radius: 0;
}
.header {
border-radius: 0;
}
.page-left {
width: initial !important;
float: initial !important;
}
.page-right {
width: initial !important;
float: initial !important;
margin-top: 10px;
}
.view-default {
grid-template-columns: 100%;
}
.view-default .btn-download {
line-height: 50px;
grid-column-start: 1;
grid-row-start: 5;
grid-row-end: initial;
}
#page-view .file-info {
grid-template-columns: none;
}
#page-view .file-info span {
float: right;
}
}

View File

@ -1,14 +0,0 @@
Building on Windows
===
1. Setup VCPKG & VS2017
2. Install deps `vcpkg install cryptopp cxxopts curl nlohmann-json`
3. Build in VS2017
Building on Linux
===
1. Install deps `apt install libcrypto++-dev libcurl4-openssl-dev nlohmann-json-dev`
2. Get cxxopts header `wget -O /usr/include/cxxopts.hpp https://raw.githubusercontent.com/jarro2783/cxxopts/master/include/cxxopts.hpp`
3. Build `g++ -O3 -o void_upload main.cpp -I/usr/include -L/usr/local/lib -lcurl -lcryptopp`

View File

@ -1,31 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.438
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "void_upload", "void_upload\void_upload.vcxproj", "{1B176CFF-D513-40AA-9AEE-4E3236A48F47}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1B176CFF-D513-40AA-9AEE-4E3236A48F47}.Debug|x64.ActiveCfg = Debug|x64
{1B176CFF-D513-40AA-9AEE-4E3236A48F47}.Debug|x64.Build.0 = Debug|x64
{1B176CFF-D513-40AA-9AEE-4E3236A48F47}.Debug|x86.ActiveCfg = Debug|Win32
{1B176CFF-D513-40AA-9AEE-4E3236A48F47}.Debug|x86.Build.0 = Debug|Win32
{1B176CFF-D513-40AA-9AEE-4E3236A48F47}.Release|x64.ActiveCfg = Release|x64
{1B176CFF-D513-40AA-9AEE-4E3236A48F47}.Release|x64.Build.0 = Release|x64
{1B176CFF-D513-40AA-9AEE-4E3236A48F47}.Release|x86.ActiveCfg = Release|Win32
{1B176CFF-D513-40AA-9AEE-4E3236A48F47}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E0BC8108-D8BD-4931-90BA-B80CC3D7797E}
EndGlobalSection
EndGlobal

View File

@ -1,184 +0,0 @@
#pragma warning(disable:4996)
#include "Upload.h"
#include "Util.h"
#include <curl/curl.h>
#include <nlohmann/json.hpp>
static int init_upload_state(upload_state* st) {
st->ctx = (VBF_CTX*)malloc(sizeof(VBF_CTX));
st->ctx->mode = VBFMODE::ENCRYPT;
st->bi = (vbf_buf*)malloc(sizeof(vbf_buf));
st->bo = (vbf_buf*)malloc(sizeof(vbf_buf));
st->bi->len = ENC_ALGO::BLOCKSIZE * 1024;
st->bi->buf = (unsigned char*)malloc(st->bi->len);
vbf_init(st->ctx);
return 1;
}
static int free_upload_state(upload_state* st) {
free(st->bi->buf);
free(st->bi);
free(st->bo);
free(st);
return 1;
}
static int curl_upload_write(void *ptr, size_t size, size_t nmemb, void *stream) {
upload_state* state = (upload_state*)stream;
((char*)ptr)[1 + (size * nmemb)] = 0; //null terminate the json
fprintf(stdout, "Got response for file %s: %s\n", state->filename, (char*)ptr);
nlohmann::json json_parsed = nlohmann::json::parse(nlohmann::detail::input_adapter((char*)ptr, size * nmemb));
if (json_parsed["status"].get<int>() == 200) {
unsigned char* kh = to_hex(state->ctx->key, ENC_ALGO::DEFAULT_KEYLENGTH);
unsigned char* ih = to_hex(state->ctx->iv, ENC_ALGO::BLOCKSIZE);
fprintf(stdout, "https://%s/#%s:%s:%s\n", state->upload_host, json_parsed["id"].get<std::string>().c_str(), kh, ih);
free(kh);
free(ih);
}
else {
fprintf(stderr, "Upload failed: %s\n", json_parsed["msg"].get<std::string>().c_str());
}
return (size * nmemb);
}
static int curl_upload_read(void *ptr, size_t size, size_t nmemb, void *stream) {
upload_state* state = (upload_state*)stream;
unsigned int offset = 0;
int target_size = min(size * nmemb, state->bi->len);
//clamp the buffer len to whichever is smaller
state->bi->len = target_size - (target_size % ENC_ALGO::BLOCKSIZE);
//set our output buffer to the curl buffer
state->bo->len = state->bi->len;
state->bo->buf = (unsigned char*)ptr;
bool start = ftell(state->file) == 0;
if (feof(state->file)) {
return 0;
}
else {
if (start) {
fseek(state->file, 0, SEEK_END);
long flen = ftell(state->file);
rewind(state->file);
VBFPayloadHeader h;
h.len = flen;
h.mime = DEFAULT_MIME;
h.filename = state->filename;
state->bo->len = state->bi->len -= ENC_ALGO::BLOCKSIZE - sizeof(VBFHeader); //reduce by 1 block to allow space for header
vbf_start_buffer(state->ctx, &h, state->bi, offset);
fprintf(stdout, "Using header: %s\n", state->bi->buf + sizeof(VBFHeader) + sizeof(uint16_t));
unsigned char* kh = to_hex(state->ctx->key, 16);
unsigned char* ih = to_hex(state->ctx->iv, 16);
fprintf(stdout, "Encrypting %s with key %s and iv %s\n", state->filename, kh, ih);
free(kh);
free(ih);
}
int nread = fread(state->bi->buf + offset, 1, state->bi->len - offset, state->file);
if (nread != state->bi->len - offset) {
if (start) {
offset -= sizeof(VBFHeader);
state->bi->buf += sizeof(VBFHeader);
state->bo->buf += sizeof(VBFHeader);
}
state->bi->len = offset + nread;
vbf_encrypt_na_end(state->ctx, state->bi, offset, state->bo);
if (start) {
offset += sizeof(VBFHeader);
state->bi->buf -= sizeof(VBFHeader);
state->bo->buf -= sizeof(VBFHeader);
state->bo->len += sizeof(VBFHeader);
memcpy(state->bo->buf, state->bi->buf, sizeof(VBFHeader));
}
unsigned char* hh = to_hex(state->bo->buf + (state->bo->len - HMAC_DGST::DIGESTSIZE), HMAC_DGST::DIGESTSIZE);
fprintf(stdout, "HMAC is: %s\n", hh);
free(hh);
}
else {
if (start) {
vbf_encrypt_na_start(state->ctx, state->bi, offset, state->bo);
}
else {
vbf_encrypt_na(state->ctx, state->bi, offset, state->bo);
}
}
int rlen = state->bo->len;
if (start) {
state->bo->len = state->bi->len += ENC_ALGO::BLOCKSIZE - sizeof(VBFHeader);
}
return rlen;
}
return 0;
}
int uploadFile(const char* file, const char* hostname, bool verbose) {
upload_state* ustate = (upload_state*)malloc(sizeof(upload_state));
memset(ustate, 0, sizeof(upload_state));
if (!init_upload_state(ustate)) {
return 1;
}
ustate->file = fopen(file, "rb");
if (!ustate->file) {
print_sys_error();
return 1;
}
#ifdef WIN32
const char* fname = strrchr(file, '\\') + 1;
#else
const char* fname = strrchr(file, '//') + 1;
#endif
ustate->filename = fname;
ustate->upload_host = hostname;
CURL *curl;
CURLcode res;
fprintf(stdout, "Starting upload for %s\n", ustate->filename);
char url[512];
sprintf(url, "http://%s/upload", ustate->upload_host);
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, curl_upload_read);
curl_easy_setopt(curl, CURLOPT_READDATA, ustate);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_upload_write);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, ustate);
if (verbose) {
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
}
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
}
curl_easy_cleanup(curl);
}
free_upload_state(ustate);
}

View File

@ -1,14 +0,0 @@
#pragma once
#include "VBF.h"
typedef struct {
const char* filename;
const char* upload_host;
FILE* file;
VBF_CTX* ctx;
vbf_buf* bi;
vbf_buf* bo;
} upload_state;
int uploadFile(const char* file, const char* hostname, bool verbose);

View File

@ -1,5 +0,0 @@
#pragma once
short findn(unsigned long num);
unsigned char* to_hex(unsigned char* buf, size_t len);
void print_sys_error();

View File

@ -1,236 +0,0 @@
#pragma warning(disable:4996)
#include "VBF.h"
#include "Util.h"
#include <ctime>
#include <cryptopp/osrng.h>
int vbf_init(VBF_CTX* ctx) {
ctx->hmac_ctx = new HMAC_DGST();
CryptoPP::AutoSeededRandomPool prng;
prng.GenerateBlock(ctx->key, ENC_ALGO::DEFAULT_KEYLENGTH);
prng.GenerateBlock(ctx->iv, ENC_ALGO::BLOCKSIZE);
ctx->hmac_ctx->SetKey(ctx->key, ENC_ALGO::DEFAULT_KEYLENGTH);
if(ctx->mode == VBFMODE::ENCRYPT) {
ctx->aes_enc_ctx = new CryptoPP::CBC_Mode<ENC_ALGO>::Encryption();
ctx->aes_enc_ctx->SetKeyWithIV(ctx->key, ENC_ALGO::DEFAULT_KEYLENGTH, ctx->iv);
} else {
ctx->aes_dec_ctx = new CryptoPP::CBC_Mode<ENC_ALGO>::Decryption();
ctx->aes_dec_ctx->SetKeyWithIV(ctx->key, ENC_ALGO::DEFAULT_KEYLENGTH, ctx->iv);
}
#ifdef TEST_KEYS
vbf_set_key(ctx, (unsigned char*)"\x4c\x2f\x44\xac\xda\xbd\x0f\xf3\x6a\x74\xa4\xbb\xa0\x2a\x8e\x7d", (unsigned char*)"\xa3\x2b\xc6\x6c\xa2\x63\x70\xf0\x32\xed\xdb\x99\x84\xcf\xc2\x61");
#endif
return 1;
}
VBFHeader vbf_make_header() {
VBFHeader vheader;
memcpy(vheader.magic, VOID_MAGIC, MAGIC_LEN);
#ifdef TEST_KEYS
vheader.uploaded = 1553867425;
#else
vheader.uploaded = (uint32_t)std::time(0);
#endif
return vheader;
}
int vbf_set_key(VBF_CTX* ctx, unsigned char* key, unsigned char* iv) {
memcpy(ctx->key, key, ENC_ALGO::DEFAULT_KEYLENGTH);
memcpy(ctx->iv, iv, ENC_ALGO::BLOCKSIZE);
ctx->hmac_ctx->SetKey(ctx->key, ENC_ALGO::DEFAULT_KEYLENGTH);
if (ctx->mode == VBFMODE::ENCRYPT) {
ctx->aes_enc_ctx->SetKeyWithIV(ctx->key, ENC_ALGO::DEFAULT_KEYLENGTH, ctx->iv);
}
else {
ctx->aes_dec_ctx->SetKeyWithIV(ctx->key, ENC_ALGO::DEFAULT_KEYLENGTH, ctx->iv);
}
return 1;
}
int vbf_encrypt_file(VBF_CTX* ctx, const char* filename, FILE* in, FILE* out) {
int bsize = ENC_ALGO::BLOCKSIZE * 1024;
unsigned char* kh = to_hex(ctx->key, 16);
unsigned char* ih = to_hex(ctx->iv, 16);
fprintf(stdout, "Encrypting %s with key %s and iv %s\n", filename, kh, ih);
free(kh);
free(ih);
unsigned int offset;
vbf_buf i;
i.len = bsize;
i.buf = (unsigned char*)malloc(i.len);
vbf_buf o;
o.len = bsize;
o.buf = (unsigned char*)malloc(o.len);
bool start = true;
for (;;) {
offset = 0;
if (feof(in)) {
break;
}
else {
if (start) {
fseek(in, 0, SEEK_END);
long flen = ftell(in);
rewind(in);
VBFPayloadHeader h;
h.len = flen;
h.mime = "text/plain";
h.filename = filename;
o.len = i.len -= ENC_ALGO::BLOCKSIZE - sizeof(VBFHeader); //reduce by 1 block to allow space for header
vbf_start_buffer(ctx, &h, &i, offset);
fprintf(stdout, "Using header: %s\n", i.buf + sizeof(VBFHeader) + sizeof(uint16_t));
}
int nread = fread(i.buf + offset, 1, i.len - offset, in);
if (nread != i.len - offset) {
if (start) {
offset -= sizeof(VBFHeader);
i.buf += sizeof(VBFHeader);
o.buf += sizeof(VBFHeader);
}
i.len = offset + nread;
vbf_encrypt_na_end(ctx, &i, offset, &o);
if (start) {
offset += sizeof(VBFHeader);
i.buf -= sizeof(VBFHeader);
o.buf -= sizeof(VBFHeader);
o.len += sizeof(VBFHeader);
memcpy(o.buf, i.buf, sizeof(VBFHeader));
}
unsigned char* hh = to_hex(o.buf + (o.len - HMAC_DGST::DIGESTSIZE), HMAC_DGST::DIGESTSIZE);
fprintf(stdout, "HMAC is: %s\n", hh);
free(hh);
}
else {
if (start) {
vbf_encrypt_na_start(ctx, &i, offset, &o);
}
else {
vbf_encrypt_na(ctx, &i, offset, &o);
}
}
if (!fwrite(o.buf, 1, o.len, out)) {
return 0; //write problem
}
else if (start) {
o.len = i.len += ENC_ALGO::BLOCKSIZE - sizeof(VBFHeader);
}
}
start = false;
}
free(i.buf);
free(o.buf);
return 1;
}
int vbf_decrypt_file(VBF_CTX* ctx, FILE* in, FILE* out) {
return 0;
}
int vbf_start_buffer(VBF_CTX* ctx, VBFPayloadHeader* header, vbf_buf* outBuf, unsigned int& offset) {
if (ctx->mode != VBFMODE::ENCRYPT) {
return 0;
}
if ((outBuf->len - sizeof(VBFHeader)) % ENC_ALGO::BLOCKSIZE != 0) {
return 0;
}
//28 chars being the json wrapping around these values
uint16_t json_len = 28 + strlen(header->filename) + strlen(header->mime) + findn(header->len);
//buffer is too small
if (outBuf->len < sizeof(VBFHeader) + 2 + json_len) {
return 0;
}
//put vbf header
VBFHeader vheader = vbf_make_header();
//copy header to output
memcpy(outBuf->buf, &vheader, sizeof(VBFHeader));
memcpy(outBuf->buf + sizeof(VBFHeader), &json_len, sizeof(uint16_t));
//by using sizeof(VBFHeader) + sizeof(uint16_t) you can find the start of the json string and print it
sprintf((char*)outBuf->buf + sizeof(VBFHeader) + sizeof(uint16_t), "{\"name\":\"%s\",\"mime\":\"%s\",\"len\":%ld}", header->filename, header->mime, header->len);
offset = sizeof(VBFHeader) + 2 + json_len;
return 1;
}
int vbf_encrypt_start(VBF_CTX* ctx, vbf_buf* in, size_t offset, vbf_buf* out) {
out->len = in->len;
out->buf = (unsigned char*)malloc(out->len);
return vbf_encrypt_na_start(ctx, in, offset, out);
}
int vbf_encrypt_na_start(VBF_CTX* ctx, vbf_buf* in, size_t offset, vbf_buf* out) {
//copy the header to the output
memcpy(out->buf, in->buf, sizeof(VBFHeader));
ctx->aes_enc_ctx->ProcessData(out->buf + sizeof(VBFHeader), in->buf + sizeof(VBFHeader), in->len - sizeof(VBFHeader));
ctx->hmac_ctx->Update(in->buf + offset, in->len - offset);
return 1;
}
int vbf_encrypt(VBF_CTX* ctx, vbf_buf* in, size_t offset, vbf_buf* out) {
out->len = in->len;
out->buf = (unsigned char*)malloc(out->len);
return vbf_encrypt_na(ctx, in, offset, out);
}
int vbf_encrypt_na(VBF_CTX* ctx, vbf_buf* in, size_t offset, vbf_buf* out) {
ctx->aes_enc_ctx->ProcessData(out->buf, in->buf, in->len);
ctx->hmac_ctx->Update(in->buf + offset, in->len - offset);
return 1;
}
int vbf_encrypt_end(VBF_CTX* ctx, vbf_buf* in, size_t offset, vbf_buf* out) {
int padding = (ENC_ALGO::BLOCKSIZE - (in->len % ENC_ALGO::BLOCKSIZE));
out->len = in->len + padding + HMAC_DGST::DIGESTSIZE;
out->buf = (unsigned char*)malloc(out->len);
return vbf_encrypt_end(ctx, in, offset, out);
}
int vbf_encrypt_na_end(VBF_CTX* ctx, vbf_buf* in, size_t offset, vbf_buf* out) {
//Add PKCS#7 Padding
int padding = 0;
if (in->len % ENC_ALGO::BLOCKSIZE != 0) {
padding = (ENC_ALGO::BLOCKSIZE - (in->len % ENC_ALGO::BLOCKSIZE));
memset(in->buf + in->len, padding, padding);
}
ctx->aes_enc_ctx->ProcessData(out->buf, in->buf, in->len + padding);
ctx->hmac_ctx->Update(in->buf + offset, in->len - offset);
ctx->hmac_ctx->Final(ctx->hmac);
//copy hmac to output
memcpy(out->buf + in->len + padding, ctx->hmac, HMAC_DGST::DIGESTSIZE);
out->len = in->len + padding + HMAC_DGST::DIGESTSIZE;
return 1;
}

View File

@ -1,73 +0,0 @@
#pragma once
#include <cryptopp/aes.h>
#include <cryptopp/ccm.h>
#include <cryptopp/hmac.h>
#include <cryptopp/sha.h>
#define VOID_MAGIC "\x02OID\xf0\x9f\x90\xb1"
#define MAGIC_LEN 8
#define HMAC_DGST CryptoPP::HMAC<CryptoPP::SHA256>
#define ENC_ALGO CryptoPP::AES
typedef enum {
ENCRYPT,
DECRYPT
} VBFMODE;
typedef struct {
char magic[8];
uint32_t uploaded;
} VBFHeader;
typedef struct {
const char* filename;
const char* mime;
long len;
} VBFPayloadHeader;
typedef struct {
VBFMODE mode;
//HMAC-SHA256
HMAC_DGST* hmac_ctx;
unsigned char hmac[HMAC_DGST::DIGESTSIZE];
//AES128-CBC
CryptoPP::CBC_Mode<ENC_ALGO>::Encryption* aes_enc_ctx;
CryptoPP::CBC_Mode<ENC_ALGO>::Decryption* aes_dec_ctx;
unsigned char key[ENC_ALGO::DEFAULT_KEYLENGTH];
unsigned char iv[ENC_ALGO::BLOCKSIZE];
} VBF_CTX;
//Hints to use vbf_start_buffer for encryption/decryption
typedef struct {
unsigned char* buf;
size_t len;
} vbf_buf;
//CTX must be already allocated with the mode set to the mode you wish to use for this context
int vbf_init(VBF_CTX* ctx);
VBFHeader vbf_make_header();
int vbf_set_key(VBF_CTX* ctx, unsigned char* key, unsigned char* iv);
int vbf_encrypt_file(VBF_CTX* ctx, const char* filename, FILE* in, FILE* out);
int vbf_decrypt_file(VBF_CTX* ctx, FILE* in, FILE* out);
//This is only for encryption
//Puts the headers at the start of the buffer returning the offset of the payload data
//In VBF2 encryption starts after sizeof(VBFHeader) bytes
int vbf_start_buffer(VBF_CTX* ctx, VBFPayloadHeader* header, vbf_buf* outBuf, unsigned int& offset);
//encrypts the payload part of the in buffer create with vbf_start_buffer
//allocates the buffer on out
int vbf_encrypt_start(VBF_CTX* ctx, vbf_buf* in, size_t offset, vbf_buf* out);
int vbf_encrypt_na_start(VBF_CTX* ctx, vbf_buf* in, size_t offset, vbf_buf* out);
//encrypts the entire input and creates the output buffer
int vbf_encrypt(VBF_CTX* ctx, vbf_buf* in, size_t offset, vbf_buf* out);
int vbf_encrypt_na(VBF_CTX* ctx, vbf_buf* in, size_t offset, vbf_buf* out);
//in buf must have enough space to add padding, len must be the end of the data
//offset is only needed if your buffer contains the entire payload
int vbf_encrypt_end(VBF_CTX* ctx, vbf_buf* in, size_t offset, vbf_buf* out);
int vbf_encrypt_na_end(VBF_CTX* ctx, vbf_buf* in, size_t offset, vbf_buf* out);

View File

@ -1,64 +0,0 @@
#include "Util.h"
#include <memory>
short findn(unsigned long num)
{
if (num < 10UL)
return 1;
if (num < 100UL)
return 2;
if (num < 1000UL)
return 3;
if (num < 10000UL)
return 4;
if (num < 100000UL)
return 5;
if (num < 1000000UL)
return 6;
if (num < 10000000UL)
return 7;
if (num < 100000000UL)
return 8;
if (num < 1000000000UL)
return 9;
if (num < 10000000000UL)
return 10;
if (num < 100000000000UL)
return 11;
if (num < 1000000000000UL)
return 12;
if (num < 10000000000000UL)
return 13;
if (num < 100000000000000UL)
return 14;
if (num < 1000000000000000UL)
return 15;
if (num < 10000000000000000UL)
return 16;
if (num < 100000000000000000UL)
return 17;
if (num < 1000000000000000000UL)
return 18;
if (num < 10000000000000000000UL)
return 19;
}
const char ht[] = "0123456789abcdef";
unsigned char* to_hex(unsigned char* buf, size_t len) {
unsigned char* ret = (unsigned char*)malloc((len * 2) + 1);
memset(ret, 0, (len * 2) + 1);
for (unsigned int x = 0; x < len; x++) {
ret[x * 2] = ht[buf[x] >> 4];
ret[(x * 2) + 1] = ht[buf[x] & 0x0F];
}
return ret;
}
void print_sys_error() {
#ifdef WIN32
LPSTR msg = 0;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_FROM_SYSTEM, (LPCVOID)&errno, NULL, NULL, msg, 1024, NULL);
fprintf(stderr, msg);
#endif
}

View File

@ -1,136 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{1B176CFF-D513-40AA-9AEE-4E3236A48F47}</ProjectGuid>
<RootNamespace>voidupload</RootNamespace>
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<UndefinePreprocessorDefinitions>
</UndefinePreprocessorDefinitions>
<PreprocessorDefinitions>TEST_KEYS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<UndefinePreprocessorDefinitions>
</UndefinePreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="void_util.cpp" />
<ClCompile Include="Upload.cxx" />
<ClCompile Include="util.cxx" />
<ClCompile Include="VBF.cxx" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Upload.h" />
<ClInclude Include="Util.h" />
<ClInclude Include="VBF.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -1,85 +0,0 @@
#pragma warning(disable:4996)
#include "Util.h"
#include "Upload.h"
#include <argtable2.h>
#define CLI_VERSION "void_util v0.1"
#define DEFAULT_MIME "application/octet-stream"
#define DEFAULT_HOST "v3.void.cat"
struct arg_lit *verb, *help;
struct arg_file *upload, *pack;
struct arg_str *download, *host;
struct arg_end *end;
void *argtable[] = {
help = arg_lit0("h", "help", "Displays this help message."),
verb = arg_lit0("v", "verbose", "Verbose CURL output"),
upload = arg_file0("u", "upload", "<file>", "Uploads a file"),
download = arg_str0("d", "download", "<url>", "Downloads a file"),
pack = arg_file0("p", "pack", "<file>", "Packs a file into VBF format \"<file>.vbf\""),
host = arg_str0(NULL, "host", "<hostname>", "Sets custom server hostname for uploading"),
end = arg_end(20),
};
int main(int argc, char* argv[]) {
int nerrors = arg_parse(argc, argv, argtable);
bool missingMode = upload->count == 0 && download->count == 0 && pack->count == 0;
if (help->count > 0 || nerrors > 0 || missingMode)
{
if (nerrors > 0) {
arg_print_errors(stdout, end, CLI_VERSION);
}
fprintf(stdout, "Usage: %s", CLI_VERSION);
arg_print_syntax(stdout, argtable, "\n");
arg_print_glossary(stdout, argtable, " %-25s %s\n");
goto exit;
}
if (upload->count > 0) {
const char* fn = upload->filename[0];
const char* hn = host->count > 0 ? host->sval[0] : "v3.void.cat";
bool verbose = verb->count > 0;
uploadFile(fn, hn, verbose);
goto exit;
}
if (pack->count > 0) {
const char* fn = pack->filename[0];
char* fo = (char*)malloc(strlen(fn) + 5);
sprintf(fo, "%s.vbf", fn);
VBF_CTX* ctx = (VBF_CTX*)malloc(sizeof(VBF_CTX));
ctx->mode = VBFMODE::ENCRYPT;
vbf_init(ctx);
FILE *ffi, *ffo;
ffi = fopen(fn, "rb");
ffo = fopen(fo, "wb+");
if (!ffi || !ffo) {
fprintf(stderr, "IO error\n");
}
else {
#ifdef _MSC_VER
const char* fname = strrchr(fn, '\\') + 1;
#else
const char* fname = strrchr(fn, '//') + 1;
#endif
vbf_encrypt_file(ctx, fname, ffi, ffo);
fprintf(stdout, "Done!");
fflush(ffo);
fclose(ffi);
fclose(ffo);
}
free(fo);
free(ctx);
}
exit:
arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0]));
return 0;
}

View File

@ -1,212 +0,0 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace void_lib
{
public class ChunkStream : Stream, IDisposable
{
private static byte[] CRLF = new byte[] { 13, 10 };
private bool LeaveOpen { get; set; } = false;
private byte[] InternalBuffer { get; set; }
private Stream BaseStream { get; set; }
private int ReadingChunkSize { get; set; } = -1;
private int ReadOffset { get; set; } = 0;
private int LoadOffset { get; set; }
private int Loaded { get; set; }
public override bool CanRead => BaseStream.CanRead;
public override bool CanSeek => false;
public override bool CanWrite => BaseStream.CanWrite;
public override long Length => Loaded - ReadOffset;
public override long Position { get => 0; set { ; } }
public override void Flush()
{
BaseStream.Flush();
}
/// <summary>
/// Adds data to the start of the read buffer
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="count"></param>
public void PreLoadBuffer(byte[] buffer, int offset, int count)
{
if(count > InternalBuffer.Length)
{
throw new Exception("Cant preload data larger than our buffer");
}
Array.Copy(buffer, offset, InternalBuffer, 0, count);
Loaded += count;
LoadOffset += count;
}
private async Task<bool> BufferSomeAsync(CancellationToken cancellationToken)
{
if(Loaded == InternalBuffer.Length)
{
return true;
}
var rlen = await BaseStream.ReadAsync(InternalBuffer, LoadOffset, InternalBuffer.Length - Loaded, cancellationToken);
if(rlen != 0)
{
Loaded += rlen;
LoadOffset += rlen;
return true;
}
else
{
return false;
}
}
private bool BufferSome()
{
if (Loaded == InternalBuffer.Length)
{
return true;
}
var rlen = BaseStream.Read(InternalBuffer, LoadOffset, InternalBuffer.Length - Loaded);
if (rlen != 0)
{
Loaded += rlen;
return true;
}
else
{
return false;
}
}
private int ParseChunks(byte[] data, int offset, int count)
{
//prepare internal buffer for copying
if (ReadingChunkSize == -1)
{
var clen_end = InternalBuffer.IndexOf(CRLF, ReadOffset, Loaded - ReadOffset);
var hex_len = Encoding.ASCII.GetString(InternalBuffer, ReadOffset, clen_end - ReadOffset + 2);
ReadingChunkSize = Convert.ToInt32(hex_len.Trim(), 16);
ReadOffset += 2 + clen_end - ReadOffset;
}
var sending_data = Math.Min(count, ReadingChunkSize <= Loaded - ReadOffset ? ReadingChunkSize : Loaded);
Array.Copy(InternalBuffer, ReadOffset, data, offset, sending_data);
ReadOffset += sending_data;
//did we send all of the chunk this time, if so expect CRLF and reset chunk read size
if (sending_data == ReadingChunkSize)
{
ReadingChunkSize = -1;
ReadOffset += 2;
}
//if we moved all our buffer then reset read to start of buffer
if(ReadOffset == Loaded)
{
LoadOffset = 0;
ReadOffset = 0;
Loaded = 0;
}
//do we still have some data left on this chunk
//adjust the chunk size so we will copy the rest next time
if(sending_data < ReadingChunkSize)
{
ReadingChunkSize -= sending_data;
}
return sending_data;
}
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if(await BufferSomeAsync(cancellationToken) || Length > 0)
{
return ParseChunks(buffer, offset, count);
}
else
{
return 0;
}
}
public override int Read(byte[] buffer, int offset, int count)
{
if (BufferSome() || Length > 0)
{
return ParseChunks(buffer, offset, count);
}
else
{
return 0;
}
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var chunk_len = Encoding.ASCII.GetBytes($"{count.ToString("X")}\r\n");
await BaseStream.WriteAsync(chunk_len, 0, chunk_len.Length, cancellationToken);
if (count > 0)
{
await BaseStream.WriteAsync(buffer, offset, count, cancellationToken);
}
await BaseStream.WriteAsync(CRLF, 0, CRLF.Length, cancellationToken);
}
public override void Write(byte[] buffer, int offset, int count)
{
var chunk_len = Encoding.ASCII.GetBytes($"{count.ToString("X")}\r\n");
BaseStream.Write(chunk_len, 0, chunk_len.Length);
if (count > 0)
{
BaseStream.Write(buffer, offset, count);
}
BaseStream.Write(CRLF, 0, CRLF.Length);
}
protected override void Dispose(bool disposing)
{
if (!LeaveOpen)
{
BaseStream.Dispose();
}
base.Dispose(disposing);
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return BaseStream.FlushAsync(cancellationToken);
}
public ChunkStream(Stream stream, int bufferSize = 16 * 1024, bool leaveOpen = false)
{
BaseStream = stream;
InternalBuffer = new byte[bufferSize];
LeaveOpen = leaveOpen;
}
}
}

View File

@ -1,160 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace void_lib
{
public class DownloadResult
{
public string Filepath { get; set; }
public FileHeader Header { get; set; }
}
public class Download : Progress<VoidProgress>
{
public Guid Id { get; private set; } = Guid.NewGuid();
public FileHeader Header { get; private set; }
private string UserAgent { get; set; }
private Stopwatch Timer { get; set; }
public decimal CalculatedSpeed { get; private set; }
public Download(string ua = "VoidUtil/1.0")
{
UserAgent = ua;
}
/// <summary>
/// Downloads a file and returns the temp file name and the FileHeader associated with the file
/// </summary>
/// <param name="url">Full url including key and iv</param>
/// <returns></returns>
public async Task<DownloadResult> DownloadFileAsync(string url)
{
Timer = Stopwatch.StartNew();
var url_base = new Uri(url);
var hash_frag = url_base.Fragment.Substring(1).Split(':');
var key = hash_frag[1].FromHex();
var iv = hash_frag[2].FromHex();
base.OnReport(VoidProgress.Create(Id, label: $"Starting..."));
base.OnReport(VoidProgress.Create(Id, log: $"Starting download for: {hash_frag[0]}"));
var req = (HttpWebRequest)WebRequest.Create($"{url_base.Scheme}://{url_base.Host}/{hash_frag[0]}");
req.UserAgent = UserAgent;
var rsp = await req.GetResponseAsync();
var file_length = rsp.ContentLength;
using (var rsp_stream = rsp.GetResponseStream())
{
var version = rsp_stream.ReadByte();
var hmac_data = new byte[32];
var ts = new byte[4];
await rsp_stream.ReadAsync(hmac_data, 0, hmac_data.Length);
await rsp_stream.ReadAsync(ts, 0, ts.Length);
base.OnReport(VoidProgress.Create(Id, log: $"Blob version is {version}, HMAC is {hmac_data.ToHex()}"));
var tmp_name = Path.GetTempFileName();
using (var tmp_file = new FileStream(tmp_name, FileMode.Open, FileAccess.ReadWrite))
{
using (var aes = new AesManaged())
{
aes.Padding = PaddingMode.PKCS7;
aes.Mode = CipherMode.CBC;
using (var ds = aes.CreateDecryptor(key, iv))
{
var buf = new byte[ds.InputBlockSize * 1024];
var out_buf = new byte[ds.OutputBlockSize * 1024];
bool first_block = true;
int read_offset = 0;
long t_len = 0;
while (true)
{
var rlen = await rsp_stream.ReadAsync(buf, read_offset, buf.Length - read_offset);
//end do final block
if (rlen == 0)
{
var last_buf = ds.TransformFinalBlock(buf, 0, read_offset);
await tmp_file.WriteAsync(last_buf, 0, last_buf.Length);
break;
}
else
{
if ((read_offset + rlen) % ds.InputBlockSize != 0)
{
read_offset += rlen;
continue;
}
else
{
rlen += read_offset;
read_offset = 0;
}
}
var clen = ds.TransformBlock(buf, 0, rlen, out_buf, 0);
if (first_block)
{
first_block = false;
var hlen = BitConverter.ToUInt16(out_buf, 0);
var header = Encoding.UTF8.GetString(out_buf, 2, hlen);
base.OnReport(VoidProgress.Create(Id, log: $"Header is: {header}"));
Header = JsonConvert.DeserializeObject<FileHeader>(header);
var file_start = 2 + hlen;
await tmp_file.WriteAsync(out_buf, file_start, clen - file_start);
}
else
{
await tmp_file.WriteAsync(out_buf, 0, clen);
}
t_len += rlen;
base.OnReport(VoidProgress.Create(Id, percentage: t_len / (decimal)file_length, size: file_length));
CalculatedSpeed = (decimal)(t_len / Timer.Elapsed.TotalSeconds);
}
}
}
base.OnReport(VoidProgress.Create(Id, percentage: 1));
tmp_file.Seek(0, SeekOrigin.Begin);
using (var hmac = HMAC.Create("HMACSHA256"))
{
hmac.Key = key;
var hmac_test = hmac.ComputeHash(tmp_file);
if (hmac_test.ToHex() == hmac_data.ToHex())
{
base.OnReport(VoidProgress.Create(Id, log: "HMAC verified!"));
}
else
{
throw new Exception($"HMAC verify failed.. {hmac_test.ToHex()} != {hmac_data.ToHex()}");
}
}
}
//file is downloaded to temp path, move it now
return new DownloadResult()
{
Filepath = tmp_name,
Header = Header
};
}
}
}
}

View File

@ -1,71 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace void_lib
{
public static class Ext
{
public static byte[] FromHex(this string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
public static string ToHex(this byte[] data)
{
return BitConverter.ToString(data).Replace("-", string.Empty).ToLower();
}
public static async Task CopyToAsync(this Stream in_stream, Stream out_stream, Action<long> progress)
{
long total = 0;
var buff = new byte[1024];
int rlen = 0;
while((rlen = await in_stream.ReadAsync(buff, 0, buff.Length)) != 0)
{
await out_stream.WriteAsync(buff, 0, rlen);
total += rlen;
progress(total);
}
}
public static int IndexOf(this byte[] data, byte[] seq, int offset = 0, int length = -1)
{
if(length == -1)
{
length = data.Length;
}
if(offset + length > data.Length)
{
throw new IndexOutOfRangeException();
}
for(var x = offset; x < offset + length; x++)
{
bool checkpos = true;
for (var y = 0;y < seq.Length; y++)
{
if(data[x+y] != seq[y])
{
checkpos = false;
break;
}
}
if (checkpos)
{
return x;
}
}
return -1;
}
}
}

View File

@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace void_lib
{
public class FileHeader
{
public string name { get; set; }
public string mime { get; set; }
public ulong len { get; set; }
}
}

View File

@ -1,57 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace void_lib
{
public abstract class VoidProgress
{
public Guid Id { get; set; }
public long Size { get; set; }
public static VoidProgress Create(Guid id, string label = null, string log = null, decimal? percentage = null, long? size = null)
{
if (label != null)
{
return new LabelVoidProgress()
{
Id = id,
Label = label
};
}
else if (log != null)
{
return new LogVoidProgress()
{
Id = id,
Log = log
};
}
else if (percentage != null)
{
return new PercentageVoidProgress()
{
Id = id,
Percentage = percentage.Value,
Size = size ?? 0
};
}
return null;
}
}
public class LabelVoidProgress : VoidProgress
{
public string Label { get; set; }
}
public class LogVoidProgress : VoidProgress
{
public string Log { get; set; }
}
public class PercentageVoidProgress : VoidProgress
{
public decimal Percentage { get; set; }
}
}

View File

@ -1,221 +0,0 @@
using Newtonsoft.Json;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace void_lib
{
public class UploadResponse
{
public int status { get; set; }
public string msg { get; set; }
public string id { get; set; }
public int[] sync { get; set; }
}
public class Upload : Progress<VoidProgress>
{
private Guid Id { get; set; } = Guid.NewGuid();
private string UserAgent { get; set; }
private string BaseHostname { get; set; }
public Upload(string host = "v3.void.cat", string ua = "VoidLib/1.0")
{
BaseHostname = host;
UserAgent = ua;
}
public Task<UploadResponse> UploadFileAsync(string file, byte[] key, byte[] iv)
{
var fi = new FileInfo(file);
return UploadFileAsync(fi.OpenRead(), fi.Name, key, iv);
}
public async Task<UploadResponse> UploadFileAsync(Stream in_stream, string filename, byte[] key, byte[] iv)
{
base.OnReport(VoidProgress.Create(Id, label: "Starting.."));
var site_info = await new VoidApi(BaseHostname).GetUploadHostAsync();
base.OnReport(VoidProgress.Create(Id, log: $"Starting upload for: {filename} => {site_info.upload_host}\nUsing key: {key.ToHex()} and IV: {iv.ToHex()}"));
var file_length = in_stream.Length;
var header = JsonConvert.SerializeObject(new FileHeader()
{
name = filename,
mime = "", // idk what to do with this haha, its not really important anyway since we dont preview in browser
len = (ulong)file_length
});
base.OnReport(VoidProgress.Create(Id, log: $"Using header: {header}"));
//unforutnatly we need to use a raw socket here because HttpWebRequest just bufferes forever
//sad.. no good for large uploads
var hosts = await Dns.GetHostAddressesAsync(site_info.upload_host);
if (hosts.Length > 0)
{
var sock = new Socket(SocketType.Stream, ProtocolType.Tcp);
var tcs = new TaskCompletionSource<bool>();
var sae = new SocketAsyncEventArgs()
{
RemoteEndPoint = new IPEndPoint(hosts[0], 443)
};
sae.Completed += (s, e) =>
{
tcs.SetResult(true);
};
if (sock.ConnectAsync(sae))
{
await tcs.Task;
}
using (var ssl_stream = new SslStream(new NetworkStream(sock)))
{
await ssl_stream.AuthenticateAsClientAsync(site_info.upload_host);
var http_header = $"POST /upload HTTP/1.1\r\nHost: {site_info.upload_host}\r\nConnection: close\r\nContent-Type: application/octet-stream\r\nTransfer-Encoding: chunked\r\nUser-Agent: {UserAgent}\r\nTrailer: \r\nAccept-Encoding: 0\r\n\r\n";
var http_header_bytes = Encoding.UTF8.GetBytes(http_header);
await ssl_stream.WriteAsync(http_header_bytes, 0, http_header_bytes.Length);
await ssl_stream.FlushAsync();
using (var cs = new ChunkStream(ssl_stream, 16384, true))
{
//send the file data
byte[] hash;
//create hmac
base.OnReport(VoidProgress.Create(Id, label: "Hashing..."));
using (var hmac = HMAC.Create("HMACSHA256"))
{
hmac.Key = key;
hash = hmac.ComputeHash(in_stream);
}
base.OnReport(VoidProgress.Create(Id, log: $"HMAC is: {hash.ToHex()}"));
in_stream.Seek(0, SeekOrigin.Begin);
//write header to request stream
var vbf_buf = new byte[37];
vbf_buf[0] = 1;
Array.Copy(hash, 0, vbf_buf, 1, hash.Length);
var ts_buf = BitConverter.GetBytes((UInt32)DateTimeOffset.Now.ToUnixTimeSeconds());
Array.Copy(ts_buf, 0, vbf_buf, 33, ts_buf.Length);
await cs.WriteAsync(vbf_buf, 0, vbf_buf.Length);
base.OnReport(VoidProgress.Create(Id, label: "Uploading..."));
using (var aes = new AesManaged())
{
aes.Padding = PaddingMode.PKCS7;
aes.Mode = CipherMode.CBC;
using (var ds = aes.CreateEncryptor(key, iv))
{
var buf = new byte[ds.InputBlockSize * 64];
var out_buf = new byte[ds.OutputBlockSize * 64];
var header_bytes = Encoding.UTF8.GetBytes(header);
var hlb = BitConverter.GetBytes((UInt16)header_bytes.Length);
Array.Copy(hlb, buf, hlb.Length);
Array.Copy(header_bytes, 0, buf, 2, header_bytes.Length);
var init_offset = hlb.Length + header_bytes.Length;
long frlen = 0;
long tlen = 0;
while ((frlen = await in_stream.ReadAsync(buf, init_offset, buf.Length - init_offset)) > 0)
{
var actual_rlen = (int)(init_offset + frlen);
if (actual_rlen % ds.InputBlockSize != 0)
{
var last_block = ds.TransformFinalBlock(buf, 0, actual_rlen);
await cs.WriteAsync(last_block, 0, last_block.Length);
}
else
{
var clen = ds.TransformBlock(buf, 0, actual_rlen, out_buf, 0);
await cs.WriteAsync(out_buf, 0, clen);
}
//offset should always be 0 from after the first block
if (init_offset != 0)
{
init_offset = 0;
}
tlen += frlen;
base.OnReport(VoidProgress.Create(Id, percentage: tlen / (decimal)file_length, size: file_length));
}
}
}
//write end chunk
await cs.WriteAsync(new byte[0], 0, 0);
await cs.FlushAsync();
}
//fuck my life why am i doing this to mysefl..
var crlf = new byte[] { 13, 10, 13, 10 };
var sb_headers = new StringBuilder();
var rlen = 0;
var header_buff = new byte[256];
var header_end = 0;
while ((rlen = await ssl_stream.ReadAsync(header_buff, 0, header_buff.Length)) != 0)
{
if ((header_end = header_buff.IndexOf(crlf)) != -1)
{
sb_headers.Append(Encoding.UTF8.GetString(header_buff, 0, header_end + 4));
break;
}
else
{
sb_headers.Append(Encoding.UTF8.GetString(header_buff, 0, rlen));
}
}
var header_dict = sb_headers.ToString().Split('\n').Select(a =>
{
var i = a.IndexOf(":");
return i == -1 ? null : new string[] { a.Substring(0, i), a.Substring(i + 2) };
}).Where(a => a != null).ToDictionary(a => a[0].Trim(), b => b.Length > 1 ? b[1].Trim() : null);
if (header_dict.ContainsKey("Content-Length"))
{
using (var msb = new MemoryStream())
{
msb.Write(header_buff, header_end + 4, rlen - header_end - 4);
await ssl_stream.CopyToAsync(msb);
return JsonConvert.DeserializeObject<UploadResponse>(Encoding.UTF8.GetString(msb.ToArray()));
}
}
else
{
if (header_dict.ContainsKey("Transfer-Encoding") && header_dict["Transfer-Encoding"] == "chunked")
{
using (var msb = new MemoryStream())
{
using (var cr = new ChunkStream(ssl_stream))
{
cr.PreLoadBuffer(header_buff, header_end + 4, rlen - header_end - 4);
await cr.CopyToAsync(msb);
}
return JsonConvert.DeserializeObject<UploadResponse>(Encoding.UTF8.GetString(msb.ToArray()));
}
}
}
}
}
return null;
}
}
}

View File

@ -1,73 +0,0 @@
using Newtonsoft.Json;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace void_lib
{
public class BasicStats
{
public int Files { get; set; }
public long Size { get; set; }
public long Transfer_24h { get; set; }
}
public class SiteInfo
{
public long max_upload_size { get; set; }
public BasicStats basic_stats { get; set; }
public string upload_host { get; set; }
public string geoip_info { get; set; }
}
public class Cmd
{
public string cmd { get; set; }
}
public class ApiResponse<T>
{
public bool ok { get; set; }
public object msg { get; set; }
public T data { get; set; }
public Cmd cmd { get; set; }
}
public class VoidApi
{
public static string BaseHostname { get; set; }
public static string UserAgent { get; set; }
public VoidApi(string hostname, string ua = "VoidApi/1.0")
{
BaseHostname = hostname;
UserAgent = ua;
}
public async Task<string> CallApiAsync(string cmd)
{
var req = (HttpWebRequest)WebRequest.Create($"https://{BaseHostname}/api");
req.Method = "POST";
req.ContentType = "application/json";
req.UserAgent = UserAgent;
var cmd_data = Encoding.UTF8.GetBytes(cmd);
await (await req.GetRequestStreamAsync()).WriteAsync(cmd_data, 0, cmd_data.Length);
var rsp = await req.GetResponseAsync();
using (var sr = new StreamReader(rsp.GetResponseStream()))
{
return await sr.ReadToEndAsync();
}
}
public async Task<SiteInfo> GetUploadHostAsync()
{
return JsonConvert.DeserializeObject<ApiResponse<SiteInfo>>(await CallApiAsync(JsonConvert.SerializeObject(new Cmd()
{
cmd = "site_info"
}))).data;
}
}
}

View File

@ -1,12 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.1;net472</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
</ItemGroup>
</Project>

View File

@ -1,37 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27703.2042
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "void_util", "void_util\void_util.csproj", "{6AE35BCD-015B-4316-8783-7AF6D75DE7E9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "void_util_form", "void_util_form\void_util_form.csproj", "{35F4300A-A50E-4D66-96D4-F9D380B21728}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "void_lib", "void_lib\void_lib.csproj", "{AF65F8E1-D7A7-4EE1-B6C2-31FC6047B8B0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6AE35BCD-015B-4316-8783-7AF6D75DE7E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6AE35BCD-015B-4316-8783-7AF6D75DE7E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6AE35BCD-015B-4316-8783-7AF6D75DE7E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6AE35BCD-015B-4316-8783-7AF6D75DE7E9}.Release|Any CPU.Build.0 = Release|Any CPU
{35F4300A-A50E-4D66-96D4-F9D380B21728}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{35F4300A-A50E-4D66-96D4-F9D380B21728}.Debug|Any CPU.Build.0 = Debug|Any CPU
{35F4300A-A50E-4D66-96D4-F9D380B21728}.Release|Any CPU.ActiveCfg = Release|Any CPU
{35F4300A-A50E-4D66-96D4-F9D380B21728}.Release|Any CPU.Build.0 = Release|Any CPU
{AF65F8E1-D7A7-4EE1-B6C2-31FC6047B8B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF65F8E1-D7A7-4EE1-B6C2-31FC6047B8B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF65F8E1-D7A7-4EE1-B6C2-31FC6047B8B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF65F8E1-D7A7-4EE1-B6C2-31FC6047B8B0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {90C997D3-2657-426B-A247-555831779BD3}
EndGlobalSection
EndGlobal

View File

@ -1,138 +0,0 @@
using System;
using System.IO;
using System.Security.Cryptography;
using System.Threading.Tasks;
using void_lib;
namespace void_util
{
class Program
{
public static string BaseHostname => "v3.void.cat";
public static string UserAgent => "VoidUtil/1.1";
static void PrintHelp()
{
Console.WriteLine($@"
Usage: void_util [MODE] [FILE|URL]
Modes:
upload Upload a file to {BaseHostname}
download Downloads a file and decrypts it
pack Packs a file into VBF format (can be useful for uploading with curl or another program)
");
}
static Task Main(string[] args)
{
if (args.Length > 1)
{
switch (args[0])
{
case "upload":
{
using (var rng = new RNGCryptoServiceProvider())
{
var key = new byte[16];
var iv = new byte[16];
rng.GetBytes(key);
rng.GetBytes(iv);
return UploadFileAsync(args[1], key, iv);
}
}
case "download":
{
return DownloadFileAsync(args[1]);
}
case "pack":
{
Console.WriteLine("Mode not implemented yet, please check github for updates");
break;
}
default:
{
Console.WriteLine($"Unknown mode: {args[0]}");
PrintHelp();
break;
}
}
return Task.CompletedTask;
}
else
{
PrintHelp();
return Task.CompletedTask;
}
}
private static void VoidProgress(object sender, VoidProgress vp)
{
switch (vp)
{
case LogVoidProgress l:
{
Console.WriteLine(l.Log);
break;
}
case LabelVoidProgress l:
{
Console.WriteLine(l.Label);
break;
}
case PercentageVoidProgress p:
{
Console.Write($"\r{(100 * p.Percentage).ToString("000.00")}%");
break;
}
}
}
private static async Task DownloadFileAsync(string url)
{
var dl = new Download(ua: UserAgent);
dl.ProgressChanged += VoidProgress;
var res = await dl.DownloadFileAsync(url);
if(res != null)
{
var out_file = Path.Combine(Directory.GetCurrentDirectory(), res.Header.name);
Console.WriteLine($"\nMoving file to {out_file}");
File.Move(res.Filepath, out_file);
}
}
private static async Task UploadFileAsync(string filename, byte[] key, byte[] iv)
{
var up = new Upload(host: BaseHostname, ua: UserAgent);
up.ProgressChanged += VoidProgress;
var rsp = await up.UploadFileAsync(filename, key, iv);
if (rsp != null)
{
if (rsp.status == 200)
{
Console.WriteLine($"\nUpload complete!\nUrl: https://{BaseHostname}/#{rsp.id}:{key.ToHex()}:{iv.ToHex()}");
}
else
{
Console.WriteLine($"\nUpload error: {rsp.msg}");
}
}
}
}
internal class FileData
{
public FileHeader Header { get; set; }
public string Hmac { get; set; }
public byte Version { get; set; }
public DateTime Uploaded { get; set; }
public byte[] EncryptedPayload { get; set; }
}
}

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<TargetFramework>netcoreapp2.1</TargetFramework>
<PublishDir>bin\publish</PublishDir>
<SelfContained>true</SelfContained>
<_IsPortable>false</_IsPortable>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
</PropertyGroup>
</Project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
</Project>

View File

@ -1,8 +0,0 @@
{
"profiles": {
"void_download": {
"commandName": "Project",
"commandLineArgs": "upload \"D:\\en_windows_10_consumer_edition_version_1803_updated_sep_2018_x64_dvd_69339216.iso\""
}
}
}

View File

@ -1,13 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.1;net472</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\void_lib\void_lib.csproj" />
</ItemGroup>
</Project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>

View File

@ -1,250 +0,0 @@
namespace void_util_form
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.button1 = new System.Windows.Forms.Button();
this.listView1 = new System.Windows.Forms.ListView();
this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.columnHeader2 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.columnHeader3 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.textBox1 = new System.Windows.Forms.TextBox();
this.label1 = new System.Windows.Forms.Label();
this.button2 = new System.Windows.Forms.Button();
this.listView2 = new System.Windows.Forms.ListView();
this.columnHeader4 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.columnHeader5 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.columnHeader6 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.columnHeader7 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components);
this.saveToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.stopToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.removeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.groupBox1.SuspendLayout();
this.groupBox2.SuspendLayout();
this.contextMenuStrip1.SuspendLayout();
this.SuspendLayout();
//
// groupBox1
//
this.groupBox1.Controls.Add(this.button1);
this.groupBox1.Controls.Add(this.listView1);
this.groupBox1.Location = new System.Drawing.Point(12, 12);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(884, 246);
this.groupBox1.TabIndex = 0;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "Upload";
//
// button1
//
this.button1.Location = new System.Drawing.Point(795, 19);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(83, 23);
this.button1.TabIndex = 1;
this.button1.Text = "Upload Files";
this.button1.UseVisualStyleBackColor = true;
//
// listView1
//
this.listView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
this.columnHeader1,
this.columnHeader2,
this.columnHeader3});
this.listView1.Location = new System.Drawing.Point(9, 19);
this.listView1.Name = "listView1";
this.listView1.Size = new System.Drawing.Size(780, 221);
this.listView1.TabIndex = 0;
this.listView1.UseCompatibleStateImageBehavior = false;
this.listView1.View = System.Windows.Forms.View.Details;
//
// columnHeader1
//
this.columnHeader1.Text = "ID";
this.columnHeader1.Width = 146;
//
// columnHeader2
//
this.columnHeader2.Text = "Name";
this.columnHeader2.Width = 187;
//
// columnHeader3
//
this.columnHeader3.Text = "Progress";
//
// groupBox2
//
this.groupBox2.Controls.Add(this.textBox1);
this.groupBox2.Controls.Add(this.label1);
this.groupBox2.Controls.Add(this.button2);
this.groupBox2.Controls.Add(this.listView2);
this.groupBox2.Location = new System.Drawing.Point(12, 264);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(884, 251);
this.groupBox2.TabIndex = 1;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "Download";
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(32, 13);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(757, 20);
this.textBox1.TabIndex = 5;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(6, 16);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(20, 13);
this.label1.TabIndex = 4;
this.label1.Text = "Url";
//
// button2
//
this.button2.Location = new System.Drawing.Point(795, 11);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(83, 23);
this.button2.TabIndex = 3;
this.button2.Text = "Download";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// listView2
//
this.listView2.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
this.columnHeader4,
this.columnHeader5,
this.columnHeader6,
this.columnHeader7});
this.listView2.ContextMenuStrip = this.contextMenuStrip1;
this.listView2.FullRowSelect = true;
this.listView2.Location = new System.Drawing.Point(6, 39);
this.listView2.MultiSelect = false;
this.listView2.Name = "listView2";
this.listView2.Size = new System.Drawing.Size(872, 201);
this.listView2.TabIndex = 2;
this.listView2.UseCompatibleStateImageBehavior = false;
this.listView2.View = System.Windows.Forms.View.Details;
//
// columnHeader4
//
this.columnHeader4.Text = "ID";
this.columnHeader4.Width = 146;
//
// columnHeader5
//
this.columnHeader5.Text = "Name";
this.columnHeader5.Width = 187;
//
// columnHeader6
//
this.columnHeader6.Text = "Progress";
this.columnHeader6.Width = 63;
//
// columnHeader7
//
this.columnHeader7.Text = "Speed";
this.columnHeader7.Width = 91;
//
// contextMenuStrip1
//
this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.saveToolStripMenuItem,
this.stopToolStripMenuItem,
this.removeToolStripMenuItem});
this.contextMenuStrip1.Name = "contextMenuStrip1";
this.contextMenuStrip1.Size = new System.Drawing.Size(118, 70);
this.contextMenuStrip1.Opening += new System.ComponentModel.CancelEventHandler(this.contextMenuStrip1_Opening);
//
// saveToolStripMenuItem
//
this.saveToolStripMenuItem.Name = "saveToolStripMenuItem";
this.saveToolStripMenuItem.Size = new System.Drawing.Size(117, 22);
this.saveToolStripMenuItem.Text = "Save";
//
// stopToolStripMenuItem
//
this.stopToolStripMenuItem.Name = "stopToolStripMenuItem";
this.stopToolStripMenuItem.Size = new System.Drawing.Size(117, 22);
this.stopToolStripMenuItem.Text = "Stop";
//
// removeToolStripMenuItem
//
this.removeToolStripMenuItem.Name = "removeToolStripMenuItem";
this.removeToolStripMenuItem.Size = new System.Drawing.Size(117, 22);
this.removeToolStripMenuItem.Text = "Remove";
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(908, 527);
this.Controls.Add(this.groupBox2);
this.Controls.Add(this.groupBox1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "MainForm";
this.Text = "VoidApp";
this.groupBox1.ResumeLayout(false);
this.groupBox2.ResumeLayout(false);
this.groupBox2.PerformLayout();
this.contextMenuStrip1.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.ListView listView1;
private System.Windows.Forms.ColumnHeader columnHeader1;
private System.Windows.Forms.ColumnHeader columnHeader2;
private System.Windows.Forms.ColumnHeader columnHeader3;
private System.Windows.Forms.GroupBox groupBox2;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.ListView listView2;
private System.Windows.Forms.ColumnHeader columnHeader4;
private System.Windows.Forms.ColumnHeader columnHeader5;
private System.Windows.Forms.ColumnHeader columnHeader6;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.ColumnHeader columnHeader7;
private System.Windows.Forms.ContextMenuStrip contextMenuStrip1;
private System.Windows.Forms.ToolStripMenuItem saveToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem stopToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem removeToolStripMenuItem;
}
}

View File

@ -1,126 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using void_lib;
namespace void_util_form
{
public partial class MainForm : Form
{
private static ObservableCollection<DownloadView> Downloads { get; set; } = new ObservableCollection<DownloadView>();
public MainForm()
{
InitializeComponent();
Downloads.CollectionChanged += (s, e) =>
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
{
foreach (var item in e.NewItems)
{
var i = (DownloadView)item;
listView2.Items.Add(new ListViewItem(new string[] { i.Id.ToString(), "??", "0.00%", "0 B/s" })
{
Name = i.Id.ToString()
});
}
break;
}
}
};
}
private string FormatBytes(decimal x)
{
if(x >= 1073741824)
{
return $"{(x / 1073741824m).ToString("0.00")} GiB";
} else if(x >= 1048576)
{
return $"{(x / 1048576m).ToString("0.00")} MiB";
}
else if(x >= 1024)
{
return $"{(x / 1024m).ToString("0.00")} KiB";
}else
{
return $"{(x).ToString("0.00")} B";
}
}
private void button2_Click(object sender, EventArgs e)
{
Downloads.Add(new DownloadView(textBox1.Text, (s, ev) =>
{
switch (ev)
{
case PercentageVoidProgress p:
{
listView2.BeginInvoke(new Action(() =>
{
var i = listView2.Items.Find(ev.Id.ToString(), false);
var dl = ((Download)s);
foreach (var ii in i)
{
ii.SubItems[1].Text = dl.Header?.name ?? "??";
ii.SubItems[2].Text = $"{(100 * p.Percentage).ToString("0.00")}%";
ii.SubItems[3].Text = $"{FormatBytes(dl.CalculatedSpeed)}/s";
}
}));
break;
}
}
}));
}
private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
{
var items = listView2.SelectedItems;
if(items.Count > 0)
{
} else
{
e.Cancel = true;
}
}
}
internal class DownloadView
{
private Download Me { get; set; }
private Task DownloadResult { get; set; }
private StringBuilder Log { get; set; } = new StringBuilder();
public DownloadView(string url, EventHandler<VoidProgress> progress)
{
Me = new Download();
Me.ProgressChanged += progress;
Me.ProgressChanged += (s, e) =>
{
switch (e)
{
case LogVoidProgress l:
{
Log.AppendLine(l.Log);
break;
}
}
};
DownloadResult = Me.DownloadFileAsync(url);
}
public Guid Id => Me.Id;
}
}

View File

@ -1,344 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="contextMenuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAEAQEAAAAEAGABgMgAAFgAAACgAAABAAAAAgAAAAAEAGAAAAAAAADAAAAAAAAAAAAAAAAAAAAAA
AAB6lo9YYJNnbbJUXppMZqNNYahRTHo/LFlLTHhZZJxfdcdTbstggc+Cj820yPDd7tyyyq+aobSEgadz
icB1icJsZoOCj818ksKQm6uKfZSZr6OMobCOrqGHt62NxMGVvr+gt7qpq86zttejstWksMmirtZ+htpe
eN5zlt+Rss2lrc6XsshzltRbfNJhgbtdfLZlg71ohc5adcVTX71jj9BzorV4mJd2i8ZVZtRWZsRsh81E
ZcQ2asNMldBgjKJSd5ppopJnfodTY7JVaLIsPH4rbalMkch5hsRhXoxJUJJdctRffdZ4jcuWt+XM8+O2
1bKKsZl9pamZpMpvfLlvfLuJkLN0hMGFjreIpLyTsMeBpsZ7osWOra2NsaqJs6iHtLefqcCZoc2ZoNKq
t9Czus+nvMaot9+ElN1nf+N2lNqjwsydyNSOpdR1e+lkeNxrg9Fod9FgctdccMpleNN3k8pwg512nZuN
rbdpedJEXs5cd8pXe9NJeMVAX4lLWomIl9JjuKBjjn5cb6hUXrpla5pheW8yYl9ajrBvgLVYbMlacNF8
ktSPpty+6+m42bSPs4d3g4dfaWlspLKTpMtlcLFrer19grOKn8ppks5Xgc1li9Jif71ui8pumsNmpLtv
m7uDk82GkseSmsaUqMx+m8iXs8miwdB9isyXqeNrgOGSo8uxzNCRxt+DoNVxe+loe+Bue9hjbdpQZcpe
b8h+gbOWmbKSqb5opbloh7RWb81KatJCX7U8RX9xa5ilr8WFmqtsv7Nak3pdhJ1meMVyWXVDMy5aWlNP
ZIxdfMVhectzeMSaqOG/2vLK6syUuIpupZFtgZRnYmtUdnJzp8B9j7l2hKxufLJRarBRba1XWpBjeLVt
cqNnd7dEZshRgcljhqp2e7RueL+Nl755mMVYcaWDteB0o75xeayVm8+JnuV9i9OswL+h3Nx/tdyAj9xv
geRcdt9id8p9iM6YmsqaoL10kbFgg8ZmirtRib9VfcZJV7NWVJmTkbiQnLthh6hXcJh9xcRPoHFHlpFS
isF4dpxnTEphdI87WKFWcLFSabuOmtjBxfDd+eWfw6JabGgzREVQnItYjY5gg21TfoNPibVgeKFabqxX
W5FRWYlpdJlqaadwfKtdcrg/Ysdkg8Rfd6R9j8FudrZ/kLlnkLJjfLVnmstIaZqEgK2epcxxhsBph9ef
q8Cl3LqZzeSAjOl4i+d6l9WKp9OMpNB2lblXi7dfgMFbi7lQfsBTdsBeZa6JeqaopL9lf6UuXaJJhMJW
e555ta9luJlQjXo4gaRiu9lYa6I5U5kqP4lcbrdvhc21su7o7PLJ4dBscmcpMzozHClMRTJPgHNeg357
g5BKh6JIe65qgb9kcqZxb5x+j8dkcKtofbdTb71KaLNsg7dCYaZScrtofrxffLl1kLhtksRGXZRtZYyt
sMR6lrs6UJladciIpteevLCx5sqDudxyk+R9neBsiOhviNJiisdfnLxcjc5dgLtjcryRj76orrqKma9T
daA3bbskUq8uhcxPkrlmdGuG2MdfjpBJYYdAn71q0eVehbU+XWdCbLCaoOjp4/Xf4d+xsrZ/dIJHGitS
NECJVWiDiXdmnpNxrbdknLpWgrlleMp8j89+kshLa8U3X9RFXr5PZq9ngbVMcMAoUcUyY9dFdtk9aL1s
hsVQV5V1apSbmax0l783W5Q8R4hbarFvi9SUrcGixrSs181xqNtclvGAk955kNtjft9dhb9jg7eSlcC1
tMuhtrdkmqNJg7FVg8lCaq05c7sxXMtAb7V8fYWYv62Dx8tjY448SYpgk7pUeqM9S39JZK/I1vTx5+TE
vcqaho6OmIxwdXlrZmWbp7SWzNBtusFBtr5XwMFun8FRasFVcsUvV8c7bNNKjOI9fuxWaMBphLguVLUp
SKguTLUuWMoyXbtKW558d5qHjqdgh7UkTpcfR6FPX6Rkb6JZcbhzm8yXt6qq172ZusRzl+xtg+Red+Fm
gdCPk8GqqMamtNJ4orxhkrpYi850ltlYgsBAZcE3Vb07VbRjdbxXXnp9mImf5sOGpMRWSY1FSpxth8Nb
c7uOq+H08PDZ2NyilaF4XnR5X2OKpZBuoqZObZZJiZ9ozss4oIo2h3Jssb5LebI7a9NTdORtgbBYgoxA
mb6EuvJ0gcNEbMI0VsYuPZo6SbVkba54fqFVdpxIfbgcUacnQocgSbk5TqlNUHdudqlmc6dnkaGFybCZ
yK1/mNSCl+qIntmUr9CEoc53ltNoj8xli8tyk994kNNdgtlefL88ZcBafNJ9lMJiebhPTm1WW2WTyJ+i
3tGCmadJQHw8Q4l1isvM1er28OvMysyRjZlFMEF4Y2l+oZ88eZAgUHpjmcBzuttIZoAqRTBQjqhpkrRj
o+hJYMMuIEs2OT05SXOAsNdGV6JGYa8+ab9VZ7mQo9lxjspJb6dQnNUZT6oZMIAnOYg9TrdgbLdiYYY+
N2BKM3Npg6KGu7mZvq9+nap6nNZ8jc5vgMtzh9ZvhNVrjNdskdtsmtiFoMpXbc5ih+R3mdh7kbhTZ5xL
YK9KV4ZAOlJ2inqp48+NzcKGlJ9KQGthYY/k5Ort3+PCt7mAcYJlWmJva3xqpbwmYbA+cKhYhLhil9FB
f8c6bqJkmrxlhrGDw9xPlewpOZVTOn5+e6Bmic85TJ87UalNX5xMV5M4QIs3Y7g0fd9Ljc45e9NEc8t7
it2aqdZXfsxDWKl7eKqgksGPqcNRebVRdKWBj5dsirF0iMxsfdVykM5niNd1k9t+ls5gieJsjdBkl9x5
lttob6VMTIdNXqQ9Q3ZGaZ49O0xKTGGQq5u17d+Uwb+RkqSblLLp3eLu3d+8u8BtY4RVVH19kbtvodEz
UpVBX8FER4lDPWxcnctSpMtfeqJUba1ymceKy+lzsPSYotils+JXasJdctdab8pNYapHW6Y5PYUnO5gW
P6A6Zp6DncWaqbeTpbtnfapQeZg3i9pXhdtwh7Q1U6AwSplPgNhic7xxe51eirhdd9p2jNJskdRzlN+I
ntR5luRtk91gh8paYqJgYZVldaqCiLiLi6hIZa9JVGk4L0RWUmeWqp3N7OG5ydzQxN/p19fz9OqJlK05
N1tzfKeMrt9chsE1R2xwo91ER3ZBNYJhhMxmlrpLQlokKUZFdbdeibpNd7Nzn99whcyHl+J5leFvhL1C
YatAaNxggehfdcp9k9GPrdhqcG4pIRMdOz4eSWMsKzNdpbE1meY5bbU3V6ZlfdNUbdlJTqVXZJdmkKpF
bMNgddFthcpofNB0gcl0gLh6fLaUjLyxqcCqurebqaZ9jaNPZqNHX6VQXIlKP01AL0FFRFpkaHrAyNPd
1dT28enO6O1WcosVEihhYY+Zwe9mk9xtk9J/tdlorulylOqFsuyLuOBQTHhSUnA+hcEfXH09T4k6T5pk
fL5mgMxNZaMnS5AaQKo2TbZxh8mKm9V1mMNHisBqkJhkgYFPaXdnfYRwiItFhagqhNA4etFklcFPecZe
XrRmZKJmfK90k6x4jrR8jMaemcu1qMq3scm/vc+5wM2hr71/mKtTaow2Too7YbVVe811lsg7SIF9gY8/
Kj4mEiE/LElmXHzh3OD2+fmQqcAkNC4xQWV2eryrxu1jjOZDV8Rga7RlkKxAZKZbiNBgjLZOjLdDa7U6
fKxUjJZZe8BLYbtoftlogstYer41XaMnPIJETaxEQn4sNXkuO188Zog/f5RroZ+avcZ4nK1PgqZZg7pZ
hatglMB7ocWPp72WrryissWot8Owx86twMeit8Wgrr2Rrbl/rLVtjq5Xe6hOa6tBaZ9MfLpYiMdjf7pg
cKWMy+NZi7dddZJ8cIRnSWYsGShAL0fi2uLc7fVIX3YjMEVFUGlwdKCqwOZ+n+1Yd95BUr5ZVIhgapxk
i9I8YKonSHgyX51WmKtFhq9KWpY5PGVJW6lNZchSc7Feh8Bqi7h6qetVYsooJmotK1hRXn10lLJne7Nm
hKJ/pLWDtMORscKascF/paZon6dhkrBdg6xZibVVirtKdKU1gpU1i5M6mapRlrZGkLw/f8NFcbRGZLlG
WrhPWKdFP39GQHtFOmWBuuB+wcZTn5NGeoVgWYF1VYaPgKD19faersk2Nlc4WYEjQY9ITIqassGev/hs
hulxid2CmNmSo95re9ZFVslHcblUfsBIhrRgk6IlIDkfDRIeIUZDY8ssPoEsNXZBWIZZa5WFn9aSmMGn
s86Jq84uZbsOMpwfRpQoWHciZV4wU1g0PFIxdIIonYQurrQphdFChNVXg8Jfjb1xwMhpx81ZssZFp89e
mbxNoL9PgcxgcMBtYbR7YaSSdK6Tg6WJgp54stdrttV6zK+WyrCdvb2Ym7TIxc/l7PFYZIw1VIwhSZoZ
MpAjMI1vhqGo2uh+n/RigeJmg+FUcN5ATbZfYLlJYqgmU7pJctCEo8dba4c8NYAyOINFZ9I6V60vNnw+
QGlqe51xiKtad7BZccBFa8xAfdwnfOgkfupdt9xRlZZRb5RANV02WIdhxctOoaRGjdRBg9RVic9ki8yB
msp7orpskrxsj7pxi69qe59ieZFlcolpcIxxZIdzZHtkV2FiT1SOws5XwNJgn7hSXmZGXDo+dkyfyL2Y
qcs9Wp8tValRY6pHUXgeIl81RpCIrMOPve1/m+hzkthIYdRRV7hqeLxSbJpNdZkpgcdGjddkqM5moN5l
he5ZeuVWeNRfftVHU6BATpQuS5ogPpQbMIo+TqYqL1wZQqkrhfNVjrRWdWpSm7hAe8VGfLVQbpRokrFy
kL1zjr95nMVwm8uhsdGjpLeWnrGYnbaOjKePfpqQg6CKi5GEiY2VjpaZnZWOsJKssauaw8tt3MdSssdM
Rm1BKCxEUT50sbZPer04WrZUZK1NUnApJTciGx0XGkhBWYxyqcl5s/N5nOqRq+SLm+JqbZk3KTkrGyBC
lqonfMI4XplCZKlujtyHpPl6leqCquRyqvpljuxWkeQ7dNhCXr0uMoQvKWQ7U71pkNl9jJ50kYlxrJ5V
utGFvdaTkqeTmbqUk8d9grKJlbOOn7CWusmGmMqQnbWUmqSTkZqTfZmNbI9+dF9pjmhgjm1jcmViclVm
Y06i2NSE5tJhwc1eobBcg45/wMVYotVVgNVCWrZhaKsXEx8aDRwdFiEeHkAdJEw2U4JWkMtej/Bwpe6D
uthZfKxdL3FMPlo8d7Y4WpUvL0MgH1FEWrFrjd02TpQvQ15bkKNfs9VOqtoikss3hNRVhuVTbuRDXL9s
d7FcUWxLaGpIrqBVu4tqqY1Yj5VQZLBjZs53dsSKjL+Ms7KPzdNfiOGBlteen6SUqZ2FopWNenpxUk9s
lFlYkU9SXUxtXGV/VmKo1MWr6eFyxdlo18Jx5shcuN5fmOVOa7osOHNZdLhZa6U1OGdJVKBeZrdSUnUn
IkYmUIpBestFet1lruNssbxZeb1vdMpkaMlOP582IjVAOWJiecwvUKwOFTYvFiRCRkhJkpxmoZxeyMNI
s883pddfnu18ktKJhKCaqsd1sNZ2zs5eo3lSg2NWk5NPc8BLYdxcatpiatJ2nad70tA8iNdjidyZm6ds
fmdVl2xnaG5KODdEYDs8XkJCSFJWPFhoOEqKw6Kzysaj4eZe5N5f1uJcreNln+czRo4nIkcxRVY4dq4z
YMpvi+iCkfF+htRpZn8nNlQcUpw8WMxJgN1XrNtsns9jeLZVPa9GMKRfU6NyfclyjOQ7XrggNWxAMlBe
XGdxjYqDqIqLq4+cw62x5uCw7Oye2faCseRlkM5bksR0o8RkiqlNY4dNaY9ecp5ofb9zh8V6pseTvs6o
2N18q9aRpOS1vNGytLS1wrW2wryturOjxradtbKipLCliamljJmHy6GUtamru8OY2+iK3ep+s+RvpuRC
YMQxNH8rLmgxbohdf8dbYoJxfLB3h+OFgcRpWoAmO2gnTaY9V9JbjORnptpffM1aWr9pdr5uh+Bhf+h1
md5WgM43erg3aKM6OZw3Kow4N2UvPj8mQx49ZEZXjHmGxMJywc9Qjq1XZYNjZI1MZaw6QG4sRXsqMGIx
Po9AQ4M7dJRVsqx4yL9nhLBka6Fecp1aa4FfY3ZiYmlaU2M+RUk3Nzw6KzIvGCAtGhiSxaWMwKqRwKKW
qLalyeqtv+6Ovehjk+46WdE5S6M2SWhBTW0sLjtVdodrgdVpYMqAdLRcUX4nPIM0YcBFct1nlNljis9v
itBiid8sXL8hP6lWhdR5rt1eqcJoraRpg8xQScNGQLg+RpgzSncnSlYgQlUhMiEzcEdVj6KbnrORiISF
maB+kqp7iqNsg61tga1te7FZi6FQwqhg2b5boqRicZlTcpNBemsza3U+TW9QUWxeVmlfW2Byam2Nf4iX
h5SswLmoxbagwbKGqZl8uLvA2fWmxeqXveNhju9Xd+JHXJ9WYXx0kctcbsBKVaxGQ5NwZceCcqVAPXQv
TLFBbd9iiM5skMtpe+I0QLctM3QlIm8tUrFtrt1HcJFENkYzMy0/SoY/Q6s0SrEoRJ8tW6ogVJoSOHEU
MEopMzdaaXqGYYB2Zmdsk4thoadzoMGPoNKapticttedy9in096eytWjuM2mrMCZr7SEpq+FnLWCl62D
jaZxf7Nkb6ZLUI48L3TJytK3tsiXsZ+Fp5FunZ+uyd2y0fSmzuSQu+llmfBIYuJLYb9fc9FdZ748O35A
PntUTrF0cL12fKAwOYkrSMNNd9NwoMlxjNlufNxUYMguLrI/WLNYjck5SH0hEScWGBYoKW1MWL0aP70+
VsE/XdoqTME0V8VLX8BmdYNhYW5rRHhrX3t2tcRdq811t9eEotZ9is57h7Z2ha5vg6hpe55kaZlbVZZO
SZU9QYMxN3QpMHUeLYUfL4YgNI0mNJg4QJ/FuMuunbOMp6x3vb5er6hin6yzxO6h3O2Oud6BuOFvmvJY
dvBffuJmetNlbrNFQn8uKXBiZrNcj6hVc4koPpsxW9BkjNV6m89kd9pKXNZfdM1cbsg0U7Q6WKEcM2Id
JGBGWqomVs0rQ8FkeuovUMYtOphFTaiLlMuitsl+eZ1jUn9Wbpcqab0xXNZRieRhmdZzlcyTpc+eqsek
rsuUqbqAlqlxdaNiWaBJS5A1Q68oQMcrQb00R7IzSK8xQqAzN5nKvcyypMGGssVZqZc6nHoxg3pmkbaX
5vGIz+JxqtZtmclaguxQd+9ggOg7WMY4RpNRVJRWZ6VLXoVIX2owXoojP7BMbtN5ksx/kt97leVhec9U
a9FOYsU5WKg9UqFMZ7goZtMcPbZYZ9NBXtFFVa1GTKCJidmkoriPk7yJpNhzgLtObsgmWc1Ga9taeN5b
fNVPadJWas1rgsp6j8Nvk817mteKo8OVp76aqMOCo9VugtJkbbRVXaNIS51JRpNCOoHPx8+1sMiItbRg
rJdIhntZeoNkdKKMwdCF6upqx+NppMxggdBFa9ZIcuQ7S7lIWMVQYLROYJo1IkIlNEo0cHQoQ683UMZs
f7+Gld1tg9xWb8xTXp9KW8hSbMlIcMgqWcoVObA0QrNMY9RLY8dVYKiJgtO3p8egnr6Fk9VyjdlJcNIl
PrpAXNVYc+B5juRqhdtujdxLaNdjesxPa71Vd+Nqi+5FZ9JCYbdul9RngrdndZd0hpp/hZJ/iZp3j6+B
gq/WzdO/uc+Y0tpnl6Zcfp9ebLFISXNZU2aZ4NZs0ONmvdthlNZXbtg9Y+JLa91NWcQ3SbUePaIsQ4FQ
a5UyVmgwRpMvOcxUar5xg7Z3kNJQbsxPVqpVZMhGXcsvV8MgRq0uQqtCVM84UcNlb76PgLS+oMa4xt97
ktZxgc1qjOU+Z+dDZNtkfuh6jOdggNE6XZM9QVhIb7ZMb8k5W8A3UK8+TpY4VctLZtpvkONYY5dNPUMi
NzINNWYwK2NDPl05TpTa0dDJ0el+xtYvdqkaSbIrL5JJME9BHjRrZXSg5OdhsdRkq85fmuNGa+AuTORO
beNccsc4QrFSY7pKeY4tdosoLmsfLJ43R9NNbcRfibdfgcRBY8NIZM0zVLwyUsAyUbxNXtBUZtVrdcKC
Z6WthL+mvMh3odh6kdteftU+cOkdRL9deNOAl+9Zds04X61BTl5ZX4hESJ5AbMgyUaklOYlEV6Y6Spg3
QqI9X8I2UpcfGVYmTFwiXIpFY75pV7RYYKvf2dfL3eyGrOU3WYYtTnwfKmxNQItFKEIuDSGTpquB0OxU
psddnL1Xn95DZuk0TN1IcOJdgc9od8BRfKQxQF4iGS8bNFUqTK03Tsc/WLVVcMJOZ7s6WbAxTbQtR7lS
ZNldZc1ueNqCfbupiL6tpsxvr9RRgM5WeddIdeomVco4b6pZa7lXacA/bcU/WalBVoBTT25iZplGZtND
YbgvSJovVaw3S6o6RL86Va5Ler4kOJ8uJXhEKlZHWKhcZdViY7Lb1eDQ5Ol1s9lKQXlSQz4zMkYpJmo0
HTlFNUV+h4ys4uxfs9dTmcdcn7xTnelAZts0TdlDc95cfdFfd61HUXlORGQkWWovV6EvOqg1QaUtPqs5
Ta9GV6k6Xr83TsRnc89ja9aYmOScls6Zjb2RtNpRgdFPddhAXtctVNUqZ6xYfqhBNHFBdMdBda06eKAu
Wp1EVYZMYKxFXccyVL8rQ5gpRoU2Zr9VccBhe9BvkeFpneBbdsd0ba9thc1faeNpasDT2+DZ6OOFzdgz
an9FLERcKEtlWn6Kn7aMpbJpfJenys97xNpLoctMkMFmqNBMnfBAZOMqRs8+atNUf8FddZ5AY4c6ZJEt
RpkiKqYdOZAeM3EmLXRGWbdAZq5QctNhcdB9jN6Tm9GYntNoc7lVc9VPc9dEY9I9X9snRalLjK1NYZ5k
bbhIgM9FS4pJW4c2cLI+acVbfdhQaMsyS7IoRKM6T6I6XqZOasNXcd5RbdVqid9nkeBgj+hUdOFbb+Jl
asfN3+jX69qe595MmpRDcoN8h6OasLx7laZWb6xGVpF0eIqc1eNVnsJLkr1UichsuuRHivFEV+Y2UdA4
YcxSdrtaZJxTV4k7SYw0Q5VHUW4qMU0kIWtIUbVFY6xkedpmg9Zzjr1pfbRaa8VPatI7XM1AW800WM0s
R7Vee65Sf7pFU5w2WLZYicVDbbY8aJs6UnxCSIdKaNIzSrpHXcFJYchNbctTa8FacddaZMVDV740UK5C
YdJGbdNbethRadpdbMm75u3Z4+G269tp2cM4rJ41kpZLeJxQbLhifNo/RY9CNGSmzdVQqclUnrpRgbpp
mtJZuudBbetKWdczStIwXcpTeMRZaZZSXoxLV44qKVYZHBUwKHRHUbpHVqpYatNabsVXcblDcMBHW8hG
XM4yQ7I0T8hDW8tggLM5hJ9JbbdfdbtBXLhJU31si9RRbLhDXqFIYadYctlFWLBVZb51iutwkOhsi+Bt
hd1adctYcdNhf9Zcet5LaMdMWapXb89eddWw8ujL4O/g2+Ke6dde5sJVwKlTlLdEacZAUqwtK1MWBhZ/
iYpqxcdCqMZQlL1lg7xrudJPpPFFY+1KWtIvUcxIctlUc7pYaYo3VX0bOHdINnhKSJ8nPJYlN50+T709
VbY8WL06VLRAV75FXcI1TMlMa9NkertQfrRAdK1EW508T6Zab8xHTJZIVapNasVPZK88S5I3ULVRT7Fp
etVYduFSa+NegNZigdRjfddmhOBWcLZnfsFpjtlzjOJvgeBjd9jE6d2k7fPK3/DN6uDB+MiWyqFTdYo4
T4RARXyEcHpYPUJdUVCW18w5oKs9rNRWfMdzpLxxwN1Cku86ZOhFW9I4XLNNa8BNZLk+Vo5HUXRIWqkm
OJASHYAcJZcvOKw4R780UcAySLgzRbwxT7tHYsxOYrBIXKU8XqdQbrFOYqtBXK1UYKdbYrRLXr1Uab1Q
X587TbFUZLtwh+c8YssrRa8tRbJAX89fgs1rjN5Qa9JKXrxFZrw/ZdFJZ91LZNNLftDE0dy48e2p6fXk
8fDm59eeor9cWW4sKDEzLT61pqHs2dppT12u3dBMpJs2n7lEf95qmcN4tMBrweEvjOwzYuhDZcpYdrBd
XsZCUcVIXK9AUakwNqIrMKFJSLBeacBmfsBigsxMadRBT8szScM+Ubg2Vqk0Was9UpQ1WK03Uq5KXLFB
VqZKV6BOXKw/TKlIWr5EUrpabMo0Wsw2YrRVjrJIgLotQb5Ob85oi9deguJIadRKbNhBYsFKiMtWh+BN
euCyw9u88eCs8fDG5umeuM99ibVELkUnGCQnGiV9a2X28e/OvcjQ7utqsbNDj6Y9dtJJg9FhqrtmwMBf
vt4xn/IsceJbj8Bxd7RhYM5HUdZYYMdEUaxca65mbp1scKeKmceXstmZuN+Cp+prleRFdc47dME9WqYz
WqoyVqM9UJc5S5lCS50/TaZBUqRDWbBWX65cZsdHYc1Id9lpi6E6OmNOTldLYKw4VdZagtJniOQ/YORa
hetQWqNZUqBleLFXc96zxOCk6N+s996G3exfiKRbb2ZocIRlQmtZPkBuV1vt4+CQiqCYtbh5xtdYjKFJ
a8ZFWclTk9tgnMRmjs1Mmtw7r/NiuOF9oMGBiMxydOhCS8haYbBjaYIjJy4XFCE+O2Z+g8eEmMOVssOF
tr19vc9tq8hTkLxMcrQ2arY4W68/T5Y9SZdARY9FSpFFWKs4Q5tFULc5Wc5dh8dIUJNRWI46PlM+SpRI
W+RvjuR4mt1Kc949Z+Fum+mCkdVohdtSY8itzuWX5eOe9MuC4eJbjr5jWGlfWD5IOzUqGxtLMjaqnp5K
PkRrdn+B4upXl6pYe7hRXblXc6xGd8Fles5LY8otZ9JUtu+O29+Tss+Ik9JjdOVpc8tUVGkhGSc0LzI+
MlhrcbZaZZNDXmMtTkE6X0pYeGZgmIp9ureIpbs/hLc0YKA5T5o1Ons3M3hJUJJLWrwuRbZPbNlFWok0
WXsdTp1FV7lpc+V2j99cd7xdhr1wpdtNfeFBYOFMdd9LaMxRZsKl0uKS3OaR7MaY5M5kq7VBe6Q7THIk
GzUiFh4yHyY1KTwlIC9xobKI3+Zdnbp0jrtRXLFGUKdfjdNMb7o+Wsc6UcIycNVvzu+i69+SwtGRn9xl
eN5NV4RPQExKOlw2OHJKSogoMlobHSUpIyklOxoiUDs7Zj9VmVuQtK+q3914xtM6iLYmUqAyNHo4OWZP
UqNJW8I+XMg+UoxARpA0UaQ3U81cdNEeMHMTIlIcRlc2c31ln8ltjuVKZddQYLlNXqeh3OeS2OmN3tGX
xrKMzMRYrKRFlZdVha9ZbJJnZYJjX4BdiZ5+3tF91dpdnMuAlbpjdLJoesRSaao3V7E8Yss7VL0wU8dO
juqf1e2s9N2P0s51iN1QWcg+Rn0hIlEkIE8nLGUPESwgFBYwLDo6Vm5Yd7NviJRKZmoxOjpNWUijybWb
9uRatL1AZ60wQYg2N3BFVKc9Vso5Wr5TW6NBTKlbdN06SIMdGURIYIdAcXY3PlckP0lgjsx9l+djcLhl
Y62Y3eqj5ezJ3eS9x7qG07OC5M9fzrBVwpRlwLWHtsuIv8ZqwcB60MJ9vdxnpttngr5tir5xe5peYqVj
dsY6V7IiQrkqW8MufN1ur+qq6eCn7daPydlmdt1VXrcwK0gkH08UHUkXERU3JihNOnV1ctVuYaQ7OlIf
IygdJg0QHxIqKh6RrYzF+e55ws5XeMJLVY4zOn06WK4yTs5DVahEWbBbfMI5SXxST45tXKI2GCpWVn9G
RFg5RZxohNl8kt5obLOY2+St2tPAyd/Hx8qy1KZ62qxf5L1e0qdmva9knqVQlZVGe5x9vdl7s95sp+Fy
kchNYb9+ntJ5faZTVpBOX5w9VMAyUbs1Z9RUkd+WyuKj3NWY5dF4s99KXrorJUUkJVYVFS8pIR1HMz1v
aK9uZ7A+Lk4XCx4mJkNBLEI1MBgaExsrJRqUsZHL9u2GwehfiL9LT50yQ44vVLwsUdRBVKdYep5+o756
ntZVSLtgNXB0gKVHR2chHllKV7hgbsBhYp+w2ti75r6z1M6xxNOX0aZl0ok9xX4wo5IpeZIkaHwse6w8
hcCJwt10reJXk+SIsNpvfshJZL90lL6DhadncqgjK24iNKAqYM82iexws/GTydic286SztZTftUzOI8g
H1UeGyowIx9TPkluZ6RYTHksGR8vDg43JCMvMFxTMDs/KB8lFiIcMhqFsIzK9fZ0zeBmnLtDWrQtRZIv
XdZRa9hRUodyiqqn0txblKM1Wno9NVQxHjdNSollbspfar1NU6G32r2R8cyU3uaVzumDydJ936hdzKFA
rKoqpsAsmbowfLBLcp6WzudinOBYguN+reCBo9NTacBgk8l9hbNqapdFR34oMHwwT7E+feNFl+1uxOCW
ys2az8xtmtg6RKg2MVEhHjIqJh1RREBkXIqTgZR+WFVgJzVGJCQ6ME9DMV9dODpSJyQeEhoZLSKezLWw
9eh3zdBop9VBXrU2VLUwVsdYbrFdU3pxgJahxMaKrahxZWtmcJBfes1FWrhVYLlOT6Cs6s+h6ufB2trG
396a1OGQ1cB3wLlTrK0viJU6hbRCdsFehM+q1fVbiuFEa9J7p+CGqs5Cd9tvm85/iMBzZ55jYZdJTpEf
Lm9AasVEguZmqOyHxdCRzcV/vM1KZcs+PWAiJiYoJiBLRTNoV3/IvNHw3tSwkYFcQ0Y4Hy0wJEVUQFZY
NDM6JhkVFhNHi3ao8tSh6NJ10N1fo+ZXaLNCW8I0Vctbb7Zjap5idrN2sMiKtdpli9YwRp40R6FGVKZG
RZem7uK008u3qaa+scHM2N2O0clmusRgnrhCf5pYn9VLeMh+odahwfJffM9gfdZxmeCQrMtWhOA/ettX
i81+msaKhqRlT4xQWoxHZ6Q0Yb9Gi9pru+SVusWIv8BNlNUgM4UnMCglIShGOiVaTmOypMXz7PR5b38j
Gyc5FxImEypeSmRdQ0dMLC42NyJEeW5f3byE5cWR3NNhtN5Ok9ZZdcFOb+VCYs5Oc8pigMtvldc6Vq4l
PJUiLH47TKpCVrJETJi819jMxrzhzMrbys6+wcae29Vqvs9krMJEnbRpq9pGcL6Dpdqiu++BjcV1j8CA
q+WVt9Bthc9LdN83d9ZQkdlKZqJpWo5YVno/T3xYdsA4Y8lKq+Fzz9OWtcJ8jcwvU7cVHTscMiQ8OiJM
Nj53ZIG3prRjVX8xHTc2ITxYPVyKaXFjR0RJMzhGRzFZnpBpzblOl6yFsOCEvdBrwOFYjsNegsJdeeBL
XtpaZ8tUYa5leMtWasRJW7A9SaQ+Vao6TZTS0MvdzM3Ukrnbpre5vrml5tJ70tpYssxdm71tqNZFdrx9
q9qxueNZaJ4zRHhuqtGcy916oMhnc8pPcdJEettHe+RATqxCRF9ZZYtpgLVEY71AhNlqweOQycyPkMBr
ec4cJ4EbHygqQihOQC5CIjlWQklrYGdpU39eQmiLY3B0UExbPkdeQUdHWlKAwL5ii58pP3BjiNdcg9V9
tMh/yNRppNROb9Niet15gNKWl99ATa0sNX8zK0IwPWkuQ5UvWprj2NrSwNTdw97pz9/OwMGw5bl66Nxw
v9l/r8Zoq9hJesaFr82irNY2PmUrGThReqih3uV2r8tumMhles5ki9pYfd4zRtEyNHRibIdteJ9nf8NN
dM9Tm+R+wdOSsMGBi8hAdcggI3AdIyU1QSxWMypFKypKNSdYRDFjUT5gSj1YQ0dXRFA5LkFPU3hvqMAt
RX0cFUJHUYhdh99Ceq9tmbh0nMxskMmjueJzfcleZ8p5iNpUYpFAOGtCMkM6Q48xZKHW5tzc2Mvky8nX
xcfEzs252a2Y9ct4x+OBq+ButdhBftF5pMWHpMNESoFRRoNNXJ2k2uSIwcZanstejtNbf9Rbf90+XNtD
Qa1laItyd5lYZpNTbbhVh9NlndKWs8uRjMd0l81BXMcmI3EYHzMpKixFNy1FPS09PSs0QS46RDA7NzIr
JDMjH0JAWIwuXJYbHCkgGiInI0tAXbRRhMs9TIBHSmpMZn9Sa4Nab4I6UJxJX5szMTgkQkczNHA6Q5s9
aKrb7NLYzbHbqdDekZvC1M+00q+t79KV0eCCvOJzwuBPd9xjj8qCqbZibJxQR3JBQo+Ruuyh1tdrlK5a
o9Nemehoe75YeuVLY9hBP3FORmxYYn9kea5cdMJNbsWJqNWHjr+Bkbpxoc87XsopKYYgJEgiIj4mIyop
IyAqJR8jJiUgJCweGy0rLl0iS5MdLUcVGR0fGx0gHzoPKn83achMYKAyKUUoPEApNj8rRDYrWWosVmMo
KD4uLEY/QYVBTaVIabbd59/l38Lf7OHs7/nLwdvYvLfE5dynzt6at9F33N9TmeRLdNlplLFtjJA0Lj1i
WZSEru2R0OB6rK5kpcRTpuZYi9Jidso1Xdo7UalcX4CQkrN+ibtDUZI1S5qGo99nfsSFl8GNs8RcqMtU
iOI1RrA3PYg3Pn07PXY8QWk2OXI3PH0yP4QwQJUtSqFLW4NNZH48T4MjNncuPY84V7Y9XMxTW6xMSX9B
Rlk4QGQ3R4JKYHdLd5pZZ6xRXcFHWbRMdL7g6ejmsrrhv7TPss7aoMrmxL/Q5+Ws1+CZwc2C3NBo0+ZF
aOFRc81PaaFuiLZ6iMdNc86EquNnk6ZZk8x2ueJTsdhoidJPZdI5V9RugLGElqNih4gmJzg8P3aWreVx
jNB/fqB4j59uu6x0sK5upt9agdtMZMI9UsY+R8c9UMc/Vc9RWsxLX8pPastYctdgdeRqfd9yh9uFotd3
itNUadlcd+NlgdNngdJvhtVykNBpmN9kjudJZcdJWbZbasVbecTe3uHw4Ort2tbtzczltMfk2dLT6uql
4eqlwdGLx8mM3uRRp+1FYNhDX7xKWKgyR5wrTqxchdBUj8WBtOqAoKxnm7VjseBjiNJHW9FQeNp6mKY/
YE4rOURCQ2SvudOQmsyBfMdtdKyauLGHqJZ6qaJnq6iSu+BorN5oqNuLrt+Dv+SWvOigwuCZw+KDwuh9
wut+w+p9t+puqt+HrOBofNxcdOhqj+1wkeVbeNVOZshHaM5HbclEW7BHV6JgcsR4itPi0d7fzNfatdbk
vtbgwcbp8um54++e0uSmw9ePusKT09B+1PA3kug6WtdMbMVLVpcfIm82YrxwpOKZudYySHg8W4N3xc5y
u+VSedxUcdtfg8tlfZY7XU5MS0q9xtKRl8ZpasR2ca5UQVGvubyfzK+Ht5pnmKGAtr6JoLqPpLaKr7qW
tMyUt8uSuM+IrteGuNBrr8Nbmc5KkdZwmN91kN1HWslPceFRedlCZbpHYMRDRZMwPogxPG01K0hbaLt0
jtLYv9nh19vUnrbis8Pq8unD5fCx1NzB1NmpqNSXpMWFuLuW3+FtyfNIgOY0Xdg4XL0wP5Y2WL2Fsuxs
kMY3OIArD0Nhia+M0tVgnuVghOd+k99ydY08L0FQTlnCy9aZnbxbXKJ4c75dTGh/dnynxs+gt8SFw8Ju
obGMo8Z+mNJ5gtRidLFpgrpoi85ZYqJzcJhwfrFfc7lzaZZTfKhsltpyguFDYdFEZ89ffsdKXa9qgNds
ccd/kdGUl8+Ejc2Gl7/JtsnTxdzr3d7p7evM7O++3dXJ3tyyt9+fos2FpNZ9tcSF0bua6u10xO9WieU5
YeE1VL5NbMh1pO1ukck/bbdhZ6+Wtd15sdF2wdlZmu5jfNZBSIo1L1JgV2uorLq8x91oaqVubbdYV4Nf
X496gLlFQFB+lpuRssaRqMd4ic19itNwe8hvgLVib6mTk728rNG2o82npMidtsyixMV2qL57nt93jOlY
c9NScsxOYrhXaLxyfcmfteOCuOyDtNpwnsrOztHRtdDfzOnc7O/X4tXK5tmwxu6oq+CincqUpdp6rcRc
iqR7vcWX6uiIzfFjj+lIa+VMa81jkuNYe8dRe7pdkdtZerxrqNh7xsmMxN9CaMs3SbVCRY5kW26DjYy6
zc6Slbt2ebtfX482OHFZX6RXVYVmX4pdXm95f5JgXHx0eq6AisqiqtOTlbaQj7uancV7e6aGssZZo5Jc
kIKKsbmBwL2GreGBme9IVKpvfbhnbrlhasB7f7hyibl7nM58vNLHxMzaxNrk0efq3uLY6OGo0vGcsOab
nteYnc2Dp9haesJtd6pqeaqazNun3eOW1e1lnO9HcOVYhdxJfNE4ULA9UJk8Wq92i7qv0dB/ws9fg8th
d9JKYKJBRF9XbGqWtaatw8OPk85jYIsxKUc+QY5ET6VqcL1sbJ+Bf5R4b4a1s8dlXHKPlaRgZoOAn7aE
p7lnoKBHoJV0sJ98rqR2jqV9oJZvnqWTrtKYnNFZV5pvd79larV+h75bdbE9OYpGOW4AAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA
</value>
</data>
</root>

View File

@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace void_util_form
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}

View File

@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("void_util_form")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("void_util_form")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("35f4300a-a50e-4d66-96d4-f9d380b21728")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -1,71 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace void_util_form.Properties
{
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("void_util_form.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
}
}

View File

@ -1,117 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -1,30 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace void_util_form.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
}

View File

@ -1,7 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,95 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{35F4300A-A50E-4D66-96D4-F9D380B21728}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>void_util_form</RootNamespace>
<AssemblyName>void_util_form</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>favicon.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="MainForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="MainForm.Designer.cs">
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\void_lib\void_lib.csproj">
<Project>{af65f8e1-d7a7-4ee1-b6c2-31fc6047b8b0}</Project>
<Name>void_lib</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="favicon.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -1,10 +0,0 @@
[Unit]
Description=GA page view batching
After=network.target
[Service]
ExecStart=/usr/bin/dotnet /usr/local/ga-page-view/ga-page-view.dll
Restart=on-failure
[Install]
WantedBy=multi-user.target

View File

@ -1,22 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26430.15
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ga-page-view", "ga-page-view\ga-page-view.csproj", "{AF43FA92-7812-40A4-839C-F9D9BEE3894F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AF43FA92-7812-40A4-839C-F9D9BEE3894F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF43FA92-7812-40A4-839C-F9D9BEE3894F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF43FA92-7812-40A4-839C-F9D9BEE3894F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF43FA92-7812-40A4-839C-F9D9BEE3894F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -1,98 +0,0 @@
using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
namespace ga_page_view
{
class Program
{
static ConnectionMultiplexer c { get; set; }
static BatchBlock<string> _queue = new BatchBlock<string>(20);
static string Token { get; set; }
static string Channel { get; set; }
static Task Main(string[] args)
{
if (args.Length != 2)
{
Console.WriteLine("Required args: channel token_auth");
return Task.CompletedTask;
}
Channel = args[0];
Token = args[1];
Console.WriteLine($"Token is: {Token}\nChannel is: {Channel}");
return startSvc();
}
private static async Task startSvc()
{
c = await ConnectionMultiplexer.ConnectAsync("localhost");
await c.GetSubscriber().SubscribeAsync(Channel, queueMsg);
Console.WriteLine("Connected to redis");
_queue.LinkTo(new ActionBlock<string[]>(async (r) =>
{
Console.WriteLine("Sending stats");
await SendData(r);
}));
await _queue.Completion;
}
private static void queueMsg(RedisChannel a, RedisValue b)
{
try
{
Console.Write("."); //tick
_queue.Post(b.ToString());
}
catch (Exception ex)
{
Console.WriteLine($"Queue msg failed.. {ex.ToString()}");
}
}
private static async Task SendData(string[] payload)
{
try
{
var req = (HttpWebRequest)WebRequest.Create("https://matomo.trash.lol/piwik.php");
req.Method = "POST";
using (StreamWriter sw = new StreamWriter(await req.GetRequestStreamAsync()))
{
await sw.WriteAsync(JsonConvert.SerializeObject(new BulkStats
{
requests = payload,
token_auth = Token
}));
}
using (var rsp = (HttpWebResponse)await req.GetResponseAsync())
{
using (StreamReader sr = new StreamReader(rsp.GetResponseStream()))
{
var rsp_json = await sr.ReadToEndAsync();
Console.WriteLine($"Got reponse from analytics: {rsp_json}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error sending stats {ex.ToString()}");
}
}
}
internal class BulkStats
{
public string[] requests { get; set; }
public string token_auth { get; set; }
}
}

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<TargetFramework>netcoreapp2.1</TargetFramework>
<PublishDir>bin\publish\</PublishDir>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<_IsPortable>false</_IsPortable>
</PropertyGroup>
</Project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
</Project>

View File

@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="StackExchange.Redis" Version="2.0.510" />
</ItemGroup>
</Project>

View File

@ -1,12 +0,0 @@
#!/bin/bash
dotnet publish -c Release
mkdir /usr/local/ga-page-view
cp ./ga-page-view/bin/Release/netcoreapp2.0/publish/* /usr/local/ga-page-view
cp ./ga-page-view.service /lib/systemd/system/
systemctl daemon-reload
systemctl enable ga-page-view
systemctl start ga-page-view

View File

@ -1,35 +0,0 @@
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
entry: {
bundle: './src/js/index.js',
sw: './src/js/Worker.js'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"]
}
]
},
mode: 'production',
optimization: {
usedExports: true,
minimizer: [new TerserJSPlugin(), new OptimizeCSSAssetsPlugin()]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
path: path.resolve(__dirname, 'dist')
})
]
};