---
title: "Vzdialené URL — Media Library for Laravel | Roundly"
description: "Sťahujte, preverujte a validujte URL od používateľov ochranou pred SSRF z addFromUrl() — MediaLibrary::remote(), SafeRemoteUrl a dočasné chyby."
url: https://roundly-consulting.com/sk/open-source/navody/media-library-for-laravel/vzdialene-url
language: sk
---

[Všetky balíky](https://roundly-consulting.com/sk/open-source.md)

[Media Library for Laravel](https://roundly-consulting.com/sk/open-source/navody/media-library-for-laravel.md)

# Vzdialené URL

addFromUrl() stiahne URL od používateľa bez toho, aby sa dostala do vašej vlastnej siete. MediaLibrary::remote() sprístupňuje tú istú ochranu ako verejné API, ktoré nič neukladá: stiahnete stránku na indexovanie, preveríte URL, ktorú používateľ zadal, alebo posúdite jednu adresu. Obom slúži jedna ochrana a jedna slučka krokov, takže addFromUrl() aj remote() odmietnu presne tie isté ciele:

```php
use RoundlyConsulting\MediaLibrary\Facades\MediaLibrary;

$page = MediaLibrary::remote()->fetch($url);           // RemoteResponse (2xx only)
$page->body;          // string, at most the byte cap
$page->status;        // int
$page->contentType;   // ?string
$page->url;           // the URL the last redirect led to
$page->headers;       // array<string, list<string>>

MediaLibrary::remote()->fetch($url, maxBytes: 5_000_000, timeout: 15, maxRedirects: 3, accept: 'text/html, text/plain');

$vetted = MediaLibrary::remote()->vet($url);           // VettedUrl, or throws — fetches nothing
MediaLibrary::remote()->isPrivateAddress('10.0.0.1');  // true
```

## Handle remote()

| Metóda | Vracia | Čo robí |
| --- | --- | --- |
| `fetch($url, maxBytes:, timeout:, maxRedirects:, accept:)` | `RemoteResponse` | Odpoveď 2xx, inak RemoteFetchFailed. Nezadané voľby preberú media.remote.fetch\_max\_bytes, timeout (sekundy za všetky kroky spolu) a max\_redirects; accept sa pošle ako hlavička Accept pri každom kroku. |
| `vet($url)` | `VettedUrl` | Verdikt ochrany bez sťahovania. Vyhodí RemoteFileRejected, alebo RemoteFileUnavailable, keď sa host nepreloží; posudzuje aj pri vypnutom block\_private\_networks. |
| `isPrivateAddress($address)` | `bool` | Či na túto adresu nesmie viesť žiadne sťahovanie. Nečitateľný vstup sa považuje za súkromný; zone id v IPv6 sa ignoruje. |

Manažér ponúka ten istý handle remote() a obe volania existujú aj ako akcie:

```php
use RoundlyConsulting\MediaLibrary\Actions\FetchRemoteUrlAction;
use RoundlyConsulting\MediaLibrary\Actions\VetRemoteUrlAction;
use RoundlyConsulting\MediaLibrary\DataTransferObjects\FetchOptions;
use RoundlyConsulting\MediaLibrary\MediaLibraryManager;

public function __construct(private MediaLibraryManager $media) {}
$this->media->remote()->fetch($url, maxBytes: 1_000_000);

app(FetchRemoteUrlAction::class)->execute($url, new FetchOptions(maxBytes: 1_000_000, accept: 'text/html'));
app(VetRemoteUrlAction::class)->execute($url);
```

Pod MediaLibrary::fake() zostáva remote() skutočné a nezaznamenáva sa: nič sa neukladá a požiadavky idú cez HTTP klienta Laravelu, takže ich obslúži Http::fake() — ako podvrhnúť aj preklady DNS, nájdete v časti Testovanie.

## Vlastná požiadavka

vet() vráti VettedUrl: url (kanonická URL, ktorú by sťahovanie požadovalo — schéma a host malými písmenami, bez koncovej bodky, predvoleného portu a fragmentu), host, port a addresses (každá overená adresa; žiadna pre názov hostu, ktorému dôveruje media.remote.allowed\_private\_hosts). Pri vlastnej požiadavke odovzdajte HTTP klientovi Laravelu httpOptions() — pripne overené adresy a udrží požiadavku mimo proxy z prostredia, ktorá by názov inak preložila znova:

```php
$vetted = MediaLibrary::remote()->vet($url);

Http::withOptions($vetted->httpOptions())->withoutRedirecting()->get($vetted->url);
// ['proxy' => '', 'curl' => [CURLOPT_RESOLVE => ['example.com:443:93.184.216.34,[2606:…]']]]
```

Presmerovanie s ním nenasledujte: presmerovanie je nová URL, ktorú treba preveriť. resolveEntry() je holý záznam CURLOPT\_RESOLVE (null, keď nie je čo pripnúť), ktorý sám osebe požiadavku mimo proxy neudrží. httpOptions() vyhodí RemoteFileRejected, keď je čo pripnúť a chýba ext-curl. fetch() to všetko urobí za vás.

## Čím prejde každý krok

- Kanonizácia — ochrana si URL rozoberie sama, pretože PHP, curl a prehliadače čítajú niektoré hosty rozdielne. Odmietne sa: riadiaci znak kdekoľvek, iná schéma ako http/https, chýbajúca autorita, prihlasovacie údaje (aj samotné @), spätná lomka, medzera či % v hoste (percentami zakódovaný host, zone id v IPv6), port mimo 1–65535 alebo media.remote.allowed\_ports, číselné zápisy IPv4 (2130706433, 0x7f.1, 0177.0.0.1, 127.1) a spätná lomka v ceste, query či fragmente. URL požiadavky sa zostaví nanovo z častí, ktoré prešli.
- Internacionalizované hosty — host s ne-ASCII znakom alebo punycode (xn--) časťou prejde s ext-intl cez UTS #46, bez neho sa odmietne. Host, ktorý IDNA prevedie na adresu (８.８.８.８, 127。0。0。1), sa odmietne, nikdy sa neznormalizuje.
- Preverenie — localhost a \*.localhost sa odmietnu podľa názvu, priamo zadaná adresa sa posúdi tak, ako je, a názov sa preloží raz, pričom každá jeho adresa musí byť verejná. media.remote.allowed\_private\_hosts vyníma názvy (dôveruje sa im tak, ako sú, bez prekladu), adresy a rozsahy.
- Pripnutie — CURLOPT\_RESOLVE v curl dostane každú overenú adresu, takže druhá odpoveď DNS nemôže podstrčiť internú a nedostupná prvá adresa prejde len na adresy, ktoré ochrana skontrolovala. Požiadavka obíde HTTP\_PROXY / HTTPS\_PROXY, ktoré by názov preložili samy.
- Požiadavka — bez sledovania presmerovaní, so zvyšnou časťou jedného rozpočtu timeout. media.remote.headers idú len na pôvod prvej URL (schéma, host, port), pokiaľ nezapnete forward\_headers\_on\_redirect, takže hlavička Authorization určená jednému hostu sa nikdy nedostane k hostu, na ktorý presmeruje.
- Odpoveď podľa stavu — odpoveď 1xx (103 Early Hints) sa preskočí. Číta sa len telo odpovede 2xx, cez limit bajtov počítaný po dekomprimovaní, takže zastaví aj malý gzip, ktorý sa nafúkne nad limit. Každá iná odpoveď skončí po hlavičkách: 503 s obrovskou chybovou stránkou zostane dočasnou chybou 503.
- Presmerovanie — 301, 302, 303, 307 alebo 308 s hlavičkou Location začne znova od prvého kroku, najviac max\_redirects-krát. Relatívna Location sa vyhodnotí podľa RFC 3986; taká, ktorá obsahuje spätnú lomku alebo riadiaci znak, sa odmietne.

Pri vypnutom media.remote.block\_private\_networks sa kanonizácia, preverenie aj pripnutie zúžia na kontrolu schémy: nič sa neposudzuje ani nepripína a platí proxy z prostredia. Pravidlo pre hlavičky, časový rozpočet, limit presmerovaní aj limit bajtov však platia ďalej. Každý limit je kľúč media.remote.\* — fetch\_max\_bytes, timeout, max\_redirects, allowed\_ports, headers, forward\_headers\_on\_redirect, block\_private\_networks a allowed\_private\_hosts (pozrite Konfigurácia).

## Chyby

Obe chyby sťahovania dedia od RemoteFetchFailed (MediaLibraryException), ktorá ponúka retryable(), status() (HTTP stav, ak je dôvodom), retryAfter() (sekundy z Retry-After) a reason():

| Výnimka | retryable() | Kedy |
| --- | --- | --- |
| `RemoteFileRejected` | `false` | Trvalá: neplatná URL, schéma či port, prihlasovacie údaje v URL, nekanonický host, zablokovaný cieľ; priveľa presmerovaní; telo nad limitom; chýbajúce ext-curl na pripnutie; iný stav ako 2xx, 408, 429 a 5xx; TLS certifikát, ktorý sa nedá overiť. |
| `RemoteFileUnavailable` | `true` | Dočasná: 5xx, 408 alebo 429 (retryAfter() v sekundách, ak server poslal Retry-After); odmietnuté či prerušené spojenie; vyčerpaný časový rozpočet; host, ktorý sa nepreloží. |

reason() vráti RemoteFetchReason — InvalidUrl, Scheme, Credentials, NonCanonicalHost, Blocked, Port, Unresolved, TooManyRedirects, TooLarge, CannotPin, Status, Tls, Connection alebo Timeout —, takže job vo fronte vie rozlíšiť, kedy to skúsiť znova a kedy to vzdať:

```php
use RoundlyConsulting\MediaLibrary\Exceptions\RemoteFileRejected;
use RoundlyConsulting\MediaLibrary\Exceptions\RemoteFileUnavailable;

try {
    $page = MediaLibrary::remote()->fetch($document->url, accept: 'text/html, text/plain');
} catch (RemoteFileUnavailable $e) {
    $this->release($e->retryAfter() ?? 60);   // a queued job tries again
} catch (RemoteFileRejected $e) {
    $document->fail($e->reason()->value);     // terminal: do not retry
}
```

Hlásenia uvádzajú URL so zamaskovanými prihlasovacími údajmi a nikdy adresu, na ktorú sa preložila — tá by tomu, kto URL zadal, zmapovala vašu internú sieť —, a preto sa nereťazí ani pôvodná výnimka spojenia. addFromUrl() si zachováva vlastný kontrakt: odmietnutá URL alebo host, ktorý sa nepreloží, vyhodí RemoteFileRejected, odpoveď iná ako 2xx vyhodí FileDoesNotExist a zlyhanie spojenia sa prejaví ako ConnectionException od Laravelu. FetchOptions vyhodí InvalidArgumentException pri limite pod 1, zápornom počte presmerovaní alebo prázdnom accept.

## Validácia — SafeRemoteUrl

URL skontrolujte hneď, keď ju používateľ zadá, ešte predtým, než ju niečo uloží:

```php
use RoundlyConsulting\MediaLibrary\Rules\SafeRemoteUrl;

'documents.*.origin' => ['required', 'string', 'max:2048', new SafeRemoteUrl],
```

Pravidlo prejde, keď vet() URL prijme, a pri každom odmietnutí zlyhá s jedným preloženým riadkom, media::validation.remote\_url. Odlišné hlásenie pre súkromný a neznámy host by používateľovi prezradilo, ktoré interné názvy existujú, preto ich pravidlo nikdy nerozlišuje — presný dôvod vráti vet(). Hodnota, ktorá nie je reťazec, zlyhá rovnako a sťahovanie neskôr preverí každý krok znova, keďže DNS sa medzitým môže zmeniť. Anglické a slovenské hlásenia sa načítajú aj bez publikovania; ak ich chcete upraviť, publikujte ich:

```bash
php artisan vendor:publish --tag="media-translations"
```

## Blokované siete

IPv4: 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10 (carrier-grade NAT), 127.0.0.0/8, 168.63.129.16/32 (Azure WireServer), 169.254.0.0/16 (link-local, cloud metadata), 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, 192.88.99.0/24, 192.168.0.0/16, 198.18.0.0/15, 198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/4 a 240.0.0.0/4. IPv6: 64:ff9b:1::/48, 100::/64, 2001::/23 (vrátane Teredo), 2001:db8::/32, 3fff::/20, 5f00::/16, fc00::/7 (AWS metadata na fd00:ec2::254), fe80::/10, fec0::/10 a ff00::/8.

IPv6 adresa, ktorá nesie IPv4 adresu — IPv4-mapped, IPv4-compatible (takže :: aj ::1 sú blokované), NAT64 (64:ff9b::/96), IPv4-translated a 6to4 (2002::/16) —, sa posúdi podľa IPv4 adresy, ktorú nesie. Klastre s DNS64 tak verejné hosty dosiahnu, no 64:ff9b::7f00:1 zostane blokovaná.

## Známe obmedzenia

- Resolver v PHP nemá timeout: host, ktorého DNS sa zasekne, podrží volanie, kým to systémový resolver nevzdá. Preklad sa ráta do časového rozpočtu, ale rozpočet ho nevie predčasne ukončiť.
- IPv6 adresy pochádzajú zo záznamov AAAA, takže IPv6 záznam v súbore hosts sa neuvidí. To je bezpečný smer: na adresu, ktorú ochrana nevidela, sa nikdy nepripojí.
- Http::fake() vynechá socket, takže vaše testy dokazujú odmietnutia ochrany, nie pripnutie. Spojte ho s MediaLibrary::fakeDns(), aby boli deterministické aj preklady.

## Prejavte lásku k open source

Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.

[Ďalšie spôsoby podpory vrátane kryptomien](https://roundly-consulting.com/sk/podporte-nas.md)

Odoslaním daru súhlasíte s našimi [podmienkami prijímania darov](https://roundly-consulting.com/sk/podmienky-darov.md).

[Podporte našu open-source prácu (otvorí sa v novej karte)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Pridajte sa k nám na Patreone (otvorí sa v novej karte)](https://www.patreon.com/cw/roundly)

## Chcete to zabudovať do svojho produktu?

Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.

[Ponuka do 48 hodín](https://roundly-consulting.com/sk/kontakt.md) [Prezrieť všetky balíky](https://roundly-consulting.com/sk/open-source/navody/media-library-for-laravel.md)
