Skip to content
Snippets Groups Projects
Commit 3c1e3a10 authored by Dominik Hebeler's avatar Dominik Hebeler
Browse files

added download route

parent d249f19d
No related branches found
No related tags found
1 merge request!20Resolve "Fix Downloading of files"
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Carbon\Carbon;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Log;
class DownloadController extends Controller
{
// How many hours is a Download link valid
const DOWNLOADLINKVALIDHOURS = 1;
public function redirector(Request $request)
{
return redirect(route('download', [
"url" => $request->input('url', 'https://metager.de'),
"valid-until" => $request->input('valid-until', ''),
"password" => $request->input('password', '')
]));
}
public function download(Request $request)
{
$url = $request->input("url");
$headers = get_headers($url, 1);
$filename = basename($url);
# From the headers we need to remove the first Element since it's the status code:
$status = $headers[0];
$status = intval(preg_split("/\s+/si", $status)[1]);
array_forget($headers, 0);
# Add the Filename if it's not set:
if (!isset($headers["Content-Disposition"])) {
$headers["Content-Disposition"] = "inline; filename=\"" . $filename . "\"";
} elseif (preg_match("/filename=\"{0,1}(.*?)(\"|\s|$)/", $headers["Content-Disposition"], $matches)) {
$filename = $matches[1];
}
$response = new StreamedResponse(function () use ($url) {
# We are gonna stream a large file
$wh = fopen('php://output', 'r+');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_BUFFERSIZE, 256);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_LOW_SPEED_LIMIT, 50000);
curl_setopt($ch, CURLOPT_LOW_SPEED_TIME, 5);
curl_setopt($ch, CURLOPT_FILE, $wh); // Data will be sent to our stream ;-)
curl_exec($ch);
curl_close($ch);
// Don't forget to close the "file" / stream
fclose($wh);
}, 200, $headers);
$response->send();
return $response;
}
public static function generateDownloadLinkParameters($url)
{
$validUntil = self::generateValidUntilDate();
$password = self::generatePassword($url, $validUntil);
return [
"url" => $url,
"valid-until" => $validUntil,
"password" => $password
];
}
/**
* This function generates a Date/Time String which is used by our Download Controller
* to check if a download link is valid.
* This Date/Time is used in the password hash, too to make sure it is not altered
*/
private static function generateValidUntilDate()
{
$validUntil = Carbon::now()->setTimezone("UTC");
$validUntil->addHours(self::DOWNLOADLINKVALIDHOURS);
return $validUntil->format("d-m-Y H:i:s P");
}
/**
* This function generates the password for Download Links
* The password is an hmac with the proxy password.
* Algo is SHA256
* Data is $url . $validUntil
* When verifying the password we can verify integrity of the supplied valid-until argument
*/
private static function generatePassword($url, $validUntil)
{
return hash_hmac("sha256", $url . $validUntil, env("PROXY_PASSWORD", "unsecure_password"));
}
}
......@@ -12,6 +12,7 @@ use URL;
use Illuminate\Support\Facades\Redis;
use App\Console\Commands\RequestFetcher;
use App\Models\HttpParser;
use Carbon\Carbon;
class ProxyController extends Controller
{
......@@ -174,16 +175,19 @@ class ProxyController extends Controller
Redis::rpush(RequestFetcher::FETCHQUEUE_KEY, $mission);
$answer = Redis::brpoplpush($hash, $hash, 10);
Redis::expire($hash, 15);
if($answer){
if ($answer) {
$answer = json_decode($answer, true);
}
} else {
$answer = Cache::get($hash);
}
if(!empty($answer["error"])){
if($answer["error"] === CURLE_ABORTED_BY_CALLBACK){
return response(view("errors.413"), 413);
if (!empty($answer["error"])) {
if ($answer["error"] === CURLE_ABORTED_BY_CALLBACK) {
return response(view("errors.413")->with([
"url" => $targetUrl,
"valid-until" => Carbond::now()->add("1h")->format('d-m-Y'),
]), 413);
}
}
......@@ -202,7 +206,12 @@ class ProxyController extends Controller
if (isset($answer["headers"]["content-disposition"])) {
// File Downloads aren't working anymore within an IFrame.
// We will show the user a page to download the File
return response(view("errors.413"), 413);
$postData = \App\Http\Controllers\DownloadController::generateDownloadLinkParameters($targetUrl);
return response(view("errors.413")->with([
"url" => $postData["url"],
"validuntil" => $postData["valid-until"],
"password" => $postData["password"]
]), 413);
}
$body = base64_decode($answer["body"]);
switch ($contentType) {
......@@ -264,13 +273,14 @@ class ProxyController extends Controller
->withHeaders($answer["headers"]);
}
public function streamFile(Request $request, $password, $id, $url){
public function streamFile(Request $request, $password, $id, $url)
{
/**
* Forms of proxied webpages might aswell Post to this URL
* Those need to be denied
*/
$check = md5(env('PROXY_PASSWORD') . date('dmy') . $request->ip());
if(!$request->filled("force-download") && $request->input("check", "") === $check){
if (!$request->filled("force-download") && $request->input("check", "") === $check) {
abort(405);
}
$targetUrl = str_replace("<<SLASH>>", "/", $url);
......@@ -295,11 +305,11 @@ class ProxyController extends Controller
# Add the Filename if it's not set:
if (!isset($headers["Content-Disposition"])) {
$headers["Content-Disposition"] = "inline; filename=\"" . $filename . "\"";
}elseif(preg_match("/filename=\"{0,1}(.*?)(\"|\s|$)/", $headers["Content-Disposition"], $matches)){
} elseif (preg_match("/filename=\"{0,1}(.*?)(\"|\s|$)/", $headers["Content-Disposition"], $matches)) {
$filename = $matches[1];
}
return response()->streamDownload(function() use ($url){
return response()->streamDownload(function () use ($url) {
# We are gonna stream a large file
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
......@@ -308,7 +318,7 @@ class ProxyController extends Controller
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_LOW_SPEED_LIMIT, 50000);
curl_setopt($ch, CURLOPT_LOW_SPEED_TIME, 5);
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data){
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $data) {
echo($data);
});
......@@ -316,7 +326,6 @@ class ProxyController extends Controller
curl_close($ch);
}, $filename, $headers);
}
public function proxifyUrl($url, $password = null, $key, $topLevel)
......
......@@ -31,8 +31,10 @@
<body>
<div>
<p>The File size of the requested resource exceeds our configured file size limit. If you want you can chose to download this file. Your request will stay anonymous but we will not try to anonymize the contents.</p>
<form method="post" target="_top">
<input type="hidden" name="force-download" value="{{ md5(env('PROXY_PASSWORD') . date('dmy') . Request::ip()) }}">
<form method="GET" action="{{ route('download') }}" target="_top">
<input type="hidden" name="url" value="{{ $url }}">
<input type="hidden" name="valid-until" value="{{ $validuntil }}">
<input type="hidden" name="password" value="{{ $password }}">
<button type="submit">Datei herunterladen</button>
</form>
</div>
......
......@@ -18,6 +18,10 @@ Route::get('healthz', function () {
->header('Content-Type', 'text/plain');
});
Route::group(['prefix' => 'download'], function () {
Route::get('/', 'DownloadController@download')->name("download");
});
Route::get('/', function () {
if (env("APP_ENV", "") !== "production") {
return view("development");
......@@ -44,6 +48,7 @@ Route::get('{password}/{url}', 'ProxyController@proxyPage')->middleware('throttl
Route::get('proxy/{password}/{id}/{url}', 'ProxyController@proxy')->middleware('checkpw:true');
Route::post('proxy/{password}/{id}/{url}', 'ProxyController@streamFile')->middleware('checkpw:true');
Route::post('/{url}', function ($url) {
abort(405);
});
\ No newline at end of file
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment