trypost/app/Helpers/Upload.php
Paulo Castellano 4a08913d70 fix(security): make SSRF private-network block configurable and guard the last user-URL fetches
Add config('trypost.security.allow_private_network') (env TRYPOST_ALLOW_PRIVATE_NETWORK, default off) so self-hosted operators can reach their own internal network; only the private-IP rejection is bypassed, scheme/host checks always apply. Add SafeHttpFetcher::guardedRequest() and route the last unguarded user-supplied-URL fetches through it: the Unsplash/Giphy asset import, the API/MCP attach-media-from-URL download, and the OAuth avatar download. Our-own-storage reads (media crop, Bluesky media) are intentionally left unguarded so internal storage keeps working when self-hosted.
2026-07-17 15:40:15 -03:00

63 lines
1.8 KiB
PHP

<?php
declare(strict_types=1);
use App\Services\Brand\SafeHttpFetcher;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
if (! function_exists('uploadFromUrl')) {
/**
* Download an image from URL and upload to storage.
*
* @param string|null $url The URL to download from
* @param string $directory The directory to store the file in
* @return string|null The stored file path or null on failure
*/
function uploadFromUrl(?string $url, string $directory = 'social-accounts'): ?string
{
if (! $url) {
return null;
}
try {
$response = app(SafeHttpFetcher::class)->guardedRequest($url)->timeout(10)->get($url);
if (! $response->successful()) {
Log::warning('uploadFromUrl: Failed to download', [
'url' => $url,
'status' => $response->status(),
]);
return null;
}
$contentType = $response->header('Content-Type') ?? 'image/jpeg';
$extension = match (true) {
str_contains($contentType, 'png') => 'png',
str_contains($contentType, 'gif') => 'gif',
str_contains($contentType, 'webp') => 'webp',
default => 'jpg',
};
$filename = sprintf(
'%s/%s.%s',
trim($directory, '/'),
Str::uuid(),
$extension
);
Storage::put($filename, $response->body(), 'public');
return $filename;
} catch (Exception $e) {
Log::warning('uploadFromUrl: Exception', [
'url' => $url,
'error' => $e->getMessage(),
]);
return null;
}
}
}