Created
April 14, 2023 10:02
-
-
Save KarelWintersky/532c93d936c8ad06b4dfe3fff206cb3a to your computer and use it in GitHub Desktop.
Download proxy with logging
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* ?file=STORAGE:file.jpg | |
* or | |
* ?file=file.jpg | |
* | |
* @todo: separate logs for different buckets? | |
* @todo: testing | |
*/ | |
const LOGFILE_OK = __DIR__ . '/download-success.log'; | |
const LOGFILE_ERROR = __DIR__ . '/download-errors.log'; | |
const LOGFILE_SEPARATOR = '|'; | |
$buckets = [ | |
'_' => __DIR__ . '/frontend/images/', | |
'1st' => __DIR__ . '/files1/' | |
]; | |
/** | |
* Функции объявлены как методы класса для простоты и инкапсуляции | |
*/ | |
class FileDownloader { | |
public static function httpNotFound() { | |
http_response_code(404); | |
header("HTTP/1.0 404 Not Found"); | |
header('Content-type: text/html'); | |
die('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html lang="ru"><head><title>Storage proxy reporting: 404 Not Found</title></head><body><h1>Not Found</h1><p>The requested URL was not found on this server.</p></body></html>'); | |
} | |
public static function getMimeType($file) { | |
$types = [ | |
'7z' => 'application/x-7z-compressed', | |
'doc' => 'application/msword', | |
'docm' => 'application/vnd.ms-word.document.macroenabled.12', | |
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', | |
'gif' => 'image/gif', | |
'htm' => 'text/html', | |
'html' => 'text/html', | |
'jpe' => 'image/jpeg', | |
'jpeg' => 'image/jpeg', | |
'jpg' => 'image/jpeg', | |
'pdf' => 'application/pdf', | |
'png' => 'image/png', | |
]; | |
$ext = pathinfo($file, PATHINFO_EXTENSION); | |
return array_key_exists($ext, $types) ? $types[$ext] : "application/force-download"; | |
} | |
public static function getIP() { | |
if (PHP_SAPI === 'cli') { return '127.0.0.1'; } | |
if (!isset ($_SERVER['REMOTE_ADDR'])) { return NULL; } | |
if (array_key_exists("HTTP_X_FORWARDED_FOR", $_SERVER)) { | |
$http_x_forwarded_for = explode(",", $_SERVER["HTTP_X_FORWARDED_FOR"]); | |
$client_ip = trim(end($http_x_forwarded_for)); | |
if (filter_var($client_ip, FILTER_VALIDATE_IP)) { | |
return $client_ip; | |
} | |
} | |
return filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP) ? $_SERVER['REMOTE_ADDR'] : NULL; | |
} | |
public static function fWriteLog(string $file_with_path, string $file_name, string $bucket) { | |
if (empty(LOGFILE_OK)) { | |
return; | |
} | |
$fl = fopen(LOGFILE_OK, "w+"); | |
$fl_stat = stat(LOGFILE_OK); | |
if ($fl_stat === false) { | |
syslog(LOG_WARNING, __FILE__ . " reports: error creating log file " . LOGFILE_OK); | |
return; | |
} | |
if ($fl_stat['size'] == 0) { | |
fputcsv($fl, ["datetime", "ip", "bucket", "file"], LOGFILE_SEPARATOR); | |
} | |
fputcsv($fl, [ | |
date('c'), | |
self::getIP(), | |
$bucket, | |
$file_name | |
], LOGFILE_SEPARATOR); | |
fclose($fl); | |
} | |
public static function fWriteErrorLog(string $file, string $bucket, string $message) { | |
if (empty(LOGFILE_ERROR)) { | |
return; | |
} | |
$fl = fopen(LOGFILE_ERROR, "w+"); | |
$fl_stat = stat(LOGFILE_ERROR); | |
if ($fl_stat === false) { | |
syslog(LOG_WARNING, __FILE__ . " reports: error creating log file " . LOGFILE_ERROR); | |
return; | |
} | |
if ($fl_stat['size'] == 0) { | |
fputcsv($fl, ["datetime", "ip", "bucket", "file", "message"], LOGFILE_SEPARATOR); | |
} | |
fputcsv($fl, [ | |
date('c'), | |
self::getIP(), | |
$bucket, | |
$file, | |
$message | |
], LOGFILE_SEPARATOR); | |
fclose($fl); | |
} | |
} | |
class FileDownloaderException extends RuntimeException { } | |
try { | |
$file_name = ''; | |
$bucket = ''; | |
if (empty($_GET['file'])) { | |
throw new FileDownloaderException("Empty request"); | |
} | |
$arg = $_GET['file']; | |
$arg_parts = explode(':', $arg); | |
if (count($arg_parts) == 2) { | |
$bucket = $arg_parts[0]; | |
$file_name = $arg_parts[1]; | |
} elseif (count($arg_parts) == 1) { | |
$bucket = '_'; | |
$file_name = $arg_parts[0]; | |
} else { | |
throw new FileDownloaderException("Incorrect request format"); | |
} | |
if (!array_key_exists($bucket, $buckets)) { | |
throw new FileDownloaderException("Bucket not exist"); | |
} | |
$file_with_path = $buckets[ $bucket ] . $file_name; | |
if (!file_exists($file_with_path) || !is_readable($file_with_path)) { | |
throw new FileDownloaderException("File not exists or not readable"); | |
} | |
$file_mimetype = FileDownloader::getMimeType($file_with_path); | |
$file_size = stat($file_with_path); | |
if ($file_size === false) { | |
throw new FileDownloaderException("Invalid file size"); | |
} | |
$file_size = $file_size['size']; | |
$file_pointer = fopen($file_with_path, "rb"); | |
if ($file_pointer === false) { | |
throw new FileDownloaderException("Can't open file for reading"); | |
} | |
// looks like file exist. so, log and out file | |
header("Pragma: public"); | |
header("Expires: 0"); | |
header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); | |
header("Cache-Control: private", false); | |
header("Content-Type: {$file_mimetype}"); | |
header("Content-Disposition: attachment; filename=\"{$file_name}\";"); | |
header("Content-Transfer-Encoding: binary"); | |
header("Content-Length: {$file_size}"); | |
@ob_clean(); | |
rewind($file_pointer); | |
fpassthru($file_pointer); | |
FileDownloader::fWriteLog($file_with_path, $file_name, $bucket); | |
} catch (FileDownloaderException $e) { | |
FileDownloader::fWriteErrorLog($file_name, $bucket, $e->getMessage()); | |
FileDownloader::httpNotFound(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment