set lastview on upload

This commit is contained in:
Kieran 2019-05-01 02:17:37 +08:00
parent 6a5395f244
commit 196bec4412
21 changed files with 993 additions and 983 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,56 +1,56 @@
<?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: /#$id");
} 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");
}
}
}
<?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: /#$id");
} else if($id !== null) {
$this->Fs = new FileStore(Config::$Instance->upload_folder);
if($this->Fs->FileExists($id)) {
$this->StartDownload($id, $this->Fs->GetFileInfo($id));
} else {
http_response_code(404);
}
} else {
http_response_code(404);
}
}
function StartDownload($id, $info) : void {
$abuse = new Abuse();
$tracking = new Tracking();
header("Cache-Control: private");
if(isset($_SERVER["HTTP_ORIGIN"])) {
header("Access-Control-Allow-Origin: " . $_SERVER["HTTP_ORIGIN"]);
header("Access-Control-Allow-Method: GET");
}
$abuse->CheckDownload($id);
$tracking->TrackDownload($this->Fs, $id);
if($_SERVER["REQUEST_METHOD"] === "GET") {
$this->InternalNginxRedirect($this->Fs->GetRelativeFilePath($id), 604800, $info);
}
}
function InternalNginxRedirect($location, $expire, $info) : void {
header("X-Accel-Redirect: /" . $location);
if($info->IsLegacyUpload) {
header("Content-Type: $info->LegacyMime");
header("Content-Disposition: inline; filename=\"$info->LegacyFilename\"");
} else {
header("Content-Type: application/octet-stream");
}
}
}
?>

View File

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

View File

@ -1,135 +1,135 @@
<?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";
}
public function GetRelativeFilePath($id) : string {
return "$this->UploadFolder/$id";
}
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 = gmp_strval(gmp_init("0x" . hash(Config::$Instance->public_hash_algo, $bf->Hash)), 62);
$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 = gmp_strval(gmp_init("0x" . hash(Config::$Instance->public_hash_algo, $hash["hash256"])), 62);
$file_path = $this->GetAbsoluteFilePath($id);
if(!file_exists($file_path)){
rename($temp_name, $file_path);
return $id;
}
return null;
}
public function GetFileSize($id) : int {
return filesize($this->GetAbsoluteFilePath($id));
}
public function ListFiles() : array {
return glob($this->GetUploadDirAbsolute() . "/*");
}
}
<?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";
}
public function GetRelativeFilePath($id) : string {
return "$this->UploadFolder/$id";
}
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 = gmp_strval(gmp_init("0x" . hash(Config::$Instance->public_hash_algo, $bf->Hash)), 62);
$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 = gmp_strval(gmp_init("0x" . hash(Config::$Instance->public_hash_algo, $hash["hash256"])), 62);
$file_path = $this->GetAbsoluteFilePath($id);
if(!file_exists($file_path)){
rename($temp_name, $file_path);
return $id;
}
return null;
}
public function GetFileSize($id) : int {
return filesize($this->GetAbsoluteFilePath($id));
}
public function ListFiles() : array {
return glob($this->GetUploadDirAbsolute() . "/*");
}
}
?>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,35 +1,35 @@
<?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){
$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;
}
}
<?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){
$rep = self::$Instance->info();
if($rep["role"] == "slave"){
self::$IsConnectedToSlave = true;
self::$MasterInstance = new Redis();
$mcon = self::$MasterInstance->pconnect($rep["master_host"], $rep["master_port"]);
return $con && $mcon;
}
}
return $con;
}
}
?>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,151 +1,158 @@
<?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();
$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;
} 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;
}
} 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"];
}
}
}
<?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"];
}
}
}
?>