forked from Kieran/void.cat
reset
This commit is contained in:
parent
c60f346120
commit
509977743b
40
README.md
40
README.md
@ -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 |
|
|
@ -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.
|
|
@ -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>
|
|
166
index.html
166
index.html
@ -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>
|
|
35
package.json
35
package.json
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Admin</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
This is the admin page
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -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);
|
|
@ -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();
|
|
@ -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 };
|
|
@ -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);
|
|
@ -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 };
|
|
@ -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 };
|
|
@ -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 };
|
|
@ -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
|
|
||||||
};
|
|
@ -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 };
|
|
@ -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 };
|
|
@ -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);
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
@ -1,9 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Admin implements RequestHandler {
|
|
||||||
public function HandleRequest() : void {
|
|
||||||
include(dirname(__FILE__) . "/../admin/index.html");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
@ -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;
|
|
||||||
}
|
|
||||||
?>
|
|
@ -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() . "/*");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
@ -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();
|
|
||||||
}
|
|
||||||
?>
|
|
@ -1,7 +0,0 @@
|
|||||||
<?php
|
|
||||||
class Info implements RequestHandler {
|
|
||||||
public function HandleRequest() : void {
|
|
||||||
phpinfo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
@ -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';
|
|
||||||
});
|
|
||||||
?>
|
|
@ -1,5 +0,0 @@
|
|||||||
<?php
|
|
||||||
interface RequestHandler {
|
|
||||||
public function HandleRequest() : void;
|
|
||||||
}
|
|
||||||
?>
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
@ -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"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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`
|
|
@ -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
|
|
@ -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);
|
|
||||||
}
|
|
@ -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);
|
|
@ -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();
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
@ -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
|
|
||||||
}
|
|
@ -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>
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
@ -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
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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\""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
@ -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>
|
|
250
tools/void_util/void_util_form/MainForm.Designer.cs
generated
250
tools/void_util/void_util_form/MainForm.Designer.cs
generated
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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")]
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 |
@ -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>
|
|
@ -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
|
|
@ -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
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
|
@ -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')
|
|
||||||
})
|
|
||||||
]
|
|
||||||
};
|
|
Loading…
Reference in New Issue
Block a user