The previous MediaAttacher had ~5 responsibilities crammed in one class (URL validation, HTTP fetch, streaming, MIME check, Storage write, post.media merge) plus an `if ($app->runningUnitTests()) return true` inside the SSRF guard — production code that knew about test mode. Split into three: - UrlSafetyGuard (interface) + DnsUrlSafetyGuard (default impl). The full SSRF check is now isolated and unit-testable in tests/Unit/Services/Post/DnsUrlSafetyGuardTest. Tests bind a permissive guard in tests/TestCase::setUp so feature tests using Http::fake() with synthetic hosts (cdn.example.com) still work — the runningUnitTests() check inside production code is gone. - MediaDownloader: takes a URL, returns a temp file + MIME or null. Uses Http::sink with a Guzzle progress callback that throws once maxBytes is exceeded, so we abort mid-stream without buffering the body in PHP memory (the previous chunk loop was append-to-string which defeated the whole point of streaming). - MediaAttacher: pure orchestrator. Calls MediaDownloader, validates the MIME against the post's enabled platforms, persists via Storage::putFileAs, appends the Media record under a row lock. Drops from 275 to ~190 lines with zero responsibility overlap. The IO contract (`null` on failure, `['path','mime','bytes']` on success) keeps MediaAttacher free of the temp-file lifecycle on the happy path — ownership is documented in MediaDownloader's PHPDoc and the orchestrator unlinks via try/finally in processOne(). Bind the interface in AppServiceProvider so production resolves DnsUrlSafetyGuard automatically; tests override it. 7 new unit tests cover the SSRF cases (loopback, RFC1918, link-local, zero/broadcast, IPv6 loopback/ULA/link-local, plus a public-IP positive test). The 14 existing AttachMedia feature tests stay green.
39 lines
996 B
PHP
39 lines
996 B
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests;
|
|
|
|
use App\Services\Post\UrlSafetyGuard;
|
|
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
|
|
|
abstract class TestCase extends BaseTestCase
|
|
{
|
|
use CreatesApplication;
|
|
|
|
/**
|
|
* Indicates whether the default seeder should run before each test.
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $seed = true;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->withoutVite();
|
|
|
|
// Bypass the DNS-based SSRF guard during tests. Feature tests use
|
|
// Http::fake() with synthetic hosts (e.g. cdn.example.com) that
|
|
// wouldn't resolve to public IPs; the real guard is exercised by
|
|
// tests/Unit/Services/Post/DnsUrlSafetyGuardTest in isolation.
|
|
$this->app->bind(UrlSafetyGuard::class, fn () => new class implements UrlSafetyGuard
|
|
{
|
|
public function isSafe(string $url): bool
|
|
{
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
}
|