Żądanie HTTP

Nette zamyka żądanie HTTP w obiektach o przejrzystym API, a przy okazji udostępnia filtr oczyszczający.

Żądanie HTTP reprezentuje obiekt Nette\Http\Request. Jeśli pracujesz z Nette, obiekt ten tworzy framework automatycznie, a możesz sobie go pozwolić przekazać przez wstrzykiwanie zależności. W presenterach wystarczy wywołać metodę $this->getHttpRequest(). Jeśli pracujesz poza Nette Framework, możesz utworzyć obiekt za pomocą RequestFactory.

Ogromną zaletą Nette jest to, że przy tworzeniu obiektu automatycznie oczyszcza wszystkie parametry wejściowe (GET, POST, COOKIE) oraz URL ze znaków sterujących i nieprawidłowych sekwencji UTF-8. Z tymi danymi możesz potem bezpiecznie pracować. Oczyszczone dane są następnie używane w presenterach i formularzach.

Instalacja i wymagania

Nette\Http\Request

Obiekt ten jest niezmienny. Nie ma setterów, ma tylko jeden tak zwany wither, withUrl(), który nie zmienia obiektu, tylko zwraca nową instancję ze zmienioną wartością.

withUrl (Nette\Http\UrlScript $url): Nette\Http\Request

Zwraca klon z innym URL.

getUrl(): Nette\Http\UrlScript

Zwraca URL żądania jako obiekt UrlScript.

$url = $httpRequest->getUrl();
echo $url; // https://nette.org/en/documentation?action=edit
echo $url->getHost(); // nette.org

Uwaga: przeglądarki nie wysyłają fragmentu na serwer, więc $url->getFragment() zwróci pusty ciąg.

getQuery (?string $key=null): string|array|null

Zwraca parametry żądania GET.

$all = $httpRequest->getQuery();    // tablica wszystkich parametrów URL
$id = $httpRequest->getQuery('id'); // zwraca parametr GET 'id' (albo null)

getPost (?string $key=null): string|array|null

Zwraca parametry żądania POST.

$all = $httpRequest->getPost();     // tablica wszystkich parametrów POST
$id = $httpRequest->getPost('id');  // zwraca parametr POST 'id' (albo null)

getFile (string|string[] $key): ?Nette\Http\FileUpload

Zwraca upload jako obiekt Nette\Http\FileUpload:

$file = $httpRequest->getFile('avatar');
if ($file?->hasFile()) { // czy jakiś plik został wysłany?
	$file->getUntrustedName(); // nazwa pliku wysłana przez użytkownika
	$file->getSanitizedName(); // nazwa bez niebezpiecznych znaków
}

Żeby dostać się do struktury zagnieżdżonej, podaj tablicę kluczy.

// <input type="file" name="my-form[details][avatar]">
$file = $request->getFile(['my-form', 'details', 'avatar']);

Ponieważ danym zewnętrznym nie można ufać i tym samym polegać na strukturze plików, to podejście jest bezpieczniejsze niż na przykład $request->getFiles()['my-form']['details']['avatar'], które mogłoby zawieść.

getFiles(): array

Zwraca drzewo wszystkich uploadów w znormalizowanej strukturze, której liśćmi są obiekty Nette\Http\FileUpload:

$files = $httpRequest->getFiles();

getCookie (string $key): ?string

Zwraca cookie albo null, jeśli nie istnieje.

$sessId = $httpRequest->getCookie('sess_id');

getCookies(): array

Zwraca wszystkie cookies.

$cookies = $httpRequest->getCookies();

getMethod(): string

Zwraca metodę HTTP użytą w żądaniu.

$httpRequest->getMethod(); // GET, POST, HEAD, PUT

isMethod (string $method)bool

Testuje metodę HTTP użytą w żądaniu. Parametr nie rozróżnia wielkości liter.

if ($httpRequest->isMethod('GET')) // ...

getHeader (string $header): ?string

Zwraca nagłówek HTTP albo null, jeśli nie istnieje. Parametr nie rozróżnia wielkości liter.

$userAgent = $httpRequest->getHeader('User-Agent');

getHeaders(): array<string, string>

Zwraca wszystkie nagłówki HTTP jako tablicę asocjacyjną. Klucze są znormalizowane do małych liter.

$headers = $httpRequest->getHeaders();
echo $headers['content-type'];

isSecured(): bool

Czy połączenie jest szyfrowane (HTTPS)? Poprawne działanie może wymagać ustawienia proxy.

isSameSite(): bool

Czy żądanie przyszło z tej samej witryny? Od wersji 3.4 zastępuje je bardziej wszechstronne isFrom().

isFrom (FetchSite|array $site, FetchDest|array|null $dest=null, ?bool $user=null)bool

Mówi Ci, skąd żądanie przyszło i w jaki sposób przeglądarka je wykonała, na podstawie nagłówków Sec-Fetch-* (tak zwane Fetch Metadata), które przeglądarka ustawia sama i których strona działająca w przeglądarce ofiary nie może ani podrobić, ani usunąć. Nette używa tego wewnętrznie do automatycznej ochrony formularzy i sygnałów przed Cross-Site Request Forgery (CSRF). Przydaje się, gdy chcesz zabezpieczyć własne wrażliwe akcje, na przykład endpointy API albo destrukcyjne odnośniki.

Metoda zwraca true tylko wtedy, gdy żądanie spełnia wszystkie podane przez Ciebie warunki. Pierwszy parametr $site opisuje relację między stroną, która zainicjowała żądanie, a Twoją witryną (nagłówek Sec-Fetch-Site). Przyjmuje pojedynczą wartość albo listę tych przypadków FetchSite:

  • FetchSite::SameOrigin – z dokładnie tego samego origin (schemat, host i port)
  • FetchSite::SameSite – z tej samej witryny, ewentualnie z innej subdomeny
  • FetchSite::CrossSite – z obcej witryny
  • FetchSite::None – użytkownik zainicjował je bezpośrednio, np. wpisując URL albo otwierając zakładkę
// czy żądanie pochodzi z naszych własnych stron?
if (!$httpRequest->isFrom([FetchSite::SameOrigin, FetchSite::SameSite])) {
	// zablokuj akcję
}

Opcjonalny parametr $dest (nagłówek Sec-Fetch-Dest) mówi, jakiego rodzaju zasób przeglądarka pobiera, np. FetchDest::Document dla nawigacji najwyższego poziomu albo FetchDest::Empty dla żądania wykonanego z JavaScriptu. Opcjonalny parametr $user (nagłówek Sec-Fetch-User) wskazuje, czy nawigacja została wywołana prawdziwą akcją użytkownika, jak kliknięcie odnośnika albo wysłanie formularza; przekaż true, żeby tego wymagać.

Kontrola, że akcja jest osiągalna tylko z Twoich własnych stron i tylko przez prawdziwą akcję użytkownika, wygląda wtedy tak:

if (!$httpRequest->isFrom(FetchSite::SameOrigin, FetchDest::Document, user: true)) {
	$this->error();
}

Starsze przeglądarki (Safari przed 16.4) nie wysyłają nagłówków Sec-Fetch-*. Dla nich Nette wraca do cookie SameSite=Strict, które dowodzi tylko tego, że żądanie nie jest cross-site. Kontroli, która dodatkowo wymaga $dest albo $user, nie da się w ten sposób zweryfikować i w tych przeglądarkach zwraca false; jeśli to zbyt surowe, testuj tylko $site.

isAjax(): bool

Czy to żądanie AJAX?

getRemoteAddress(): ?string

Zwraca adres IP użytkownika. Poprawne działanie może wymagać ustawienia proxy.

getRemoteHost(): ?string

Przestarzała, zawsze zwraca null. Odwrotne zapytania DNS były wolne i zawodne; jeśli potrzebujesz nazwy hosta, rozwiąż ją sam z getRemoteAddress().

getBasicCredentials(): ?array

Zwraca dane uwierzytelniające dla Basic HTTP authentication.

[$user, $password] = $httpRequest->getBasicCredentials();

getRawBody(): ?string

Zwraca ciało żądania HTTP.

$body = $httpRequest->getRawBody();

getOrigin(): ?UrlImmutable

Zwraca origin, z którego przyszło żądanie. Origin składa się ze schematu (protokołu), nazwy hosta i portu, na przykład https://example.com:8080. Zwraca null, jeśli nagłówek origin nie jest obecny albo ma wartość 'null'.

$origin = $httpRequest->getOrigin();
echo $origin; // https://example.com:8080
echo $origin?->getHost(); // example.com

Przeglądarka wysyła nagłówek Origin w tych przypadkach:

  • żądania cross-origin (wywołania AJAX do innej domeny)
  • POST, PUT, DELETE i inne żądania modyfikujące
  • żądania wykonane przez Fetch API

Przeglądarka NIE wysyła nagłówka Origin dla:

  • zwykłych żądań GET do tej samej domeny (nawigacja same-origin)
  • bezpośredniej nawigacji przez wpisanie URL w pasku adresu
  • żądań od klientów niebędących przeglądarką

W przeciwieństwie do nagłówka Referer Origin zawiera tylko schemat, host i port, a nie pełną ścieżkę URL. Czyni to go odpowiedniejszym do kontroli bezpieczeństwa przy zachowaniu prywatności użytkownika. Nagłówek Origin używany jest przede wszystkim do walidacji CORS (Cross-Origin Resource Sharing).

detectLanguage (array $langs): ?string

Wykrywa język. Jako parametr $langs przekaż tablicę języków obsługiwanych przez aplikację, a metoda zwróci ten, który preferuje przeglądarka odwiedzającego. Nie ma w tym magii, wykorzystywany jest po prostu nagłówek Accept-Language. Jeśli nie znajdzie dopasowania, zwraca null.

// Przeglądarka wysyła np. Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3

$langs = ['hu', 'pl', 'en']; // języki obsługiwane przez aplikację
echo $httpRequest->detectLanguage($langs); // en

RequestFactory

Klasa Nette\Http\RequestFactory służy do utworzenia instancji Nette\Http\Request, która reprezentuje bieżące żądanie HTTP. (Jeśli pracujesz z Nette, obiekt żądania HTTP tworzy framework automatycznie.)

$factory = new Nette\Http\RequestFactory;
$httpRequest = $factory->fromGlobals();

Metoda fromGlobals() tworzy obiekt żądania na podstawie bieżących zmiennych globalnych PHP ($_GET, $_POST, $_COOKIE, $_FILES i $_SERVER). Przy tworzeniu obiektu automatycznie oczyszcza wszystkie parametry wejściowe (GET, POST, COOKIE) oraz URL ze znaków sterujących i nieprawidłowych sekwencji UTF-8, co zapewnia bezpieczeństwo przy późniejszej pracy z tymi danymi.

RequestFactory można skonfigurować przed wywołaniem fromGlobals():

  • metoda $factory->setBinary() wyłącza automatyczne oczyszczanie parametrów wejściowych ze znaków sterujących i nieprawidłowych sekwencji UTF-8.
  • metoda $factory->setProxy(...) podaje adres IP serwera proxy, co jest potrzebne do poprawnego wykrywania adresu IP użytkownika.
  • metoda $factory->setForceHttps() .{data-version:3.3.4} wymusza schemat żądania HTTPS niezależnie od środowiska serwera.

RequestFactory pozwala definiować filtry automatycznie przekształcające części URL żądania. Filtry te usuwają z URL-i niepożądane znaki, które mogły zostać do nich wstawione na przykład przez niepoprawne implementacje systemów komentarzy na różnych stronach:

// usuwa spacje ze ścieżki
$requestFactory->urlFilters['path']['%20'] = '';

// usuwa kropkę, przecinek albo prawy nawias z końca URI
$requestFactory->urlFilters['url']['[.,)]$'] = '';

// czyści ścieżkę z podwójnych ukośników (filtr domyślny)
$requestFactory->urlFilters['path']['/{2,}'] = '/';

Pierwszy klucz, 'path' albo 'url', określa, do której części URL filtr zostanie zastosowany. Drugi klucz to wyrażenie regularne do wyszukania, a wartość to zamiennik, który ma zostać użyty zamiast znalezionego tekstu.

Wysłane pliki

Metoda Nette\Http\Request::getFiles() zwraca tablicę wszystkich uploadów w znormalizowanej strukturze, której liśćmi są obiekty Nette\Http\FileUpload. Zamykają one w sobie dane wysłane elementem formularza <input type=file>.

Struktura odzwierciedla nazewnictwo elementów w HTML. W najprostszym przypadku może to być pojedynczy nazwany element formularza wysłany jako:

<input type="file" name="avatar">

W takim przypadku $request->getFiles() zwraca tablicę:

[
	'avatar' => /* instancja FileUpload */
]

Obiekt FileUpload powstaje także wtedy, gdy użytkownik żadnego pliku nie wysłał albo wysyłanie się nie powiodło. To, czy plik został wysłany, zwraca metoda hasFile():

$request->getFile('avatar')?->hasFile();

W przypadku nazwy elementu z zapisem tablicowym:

<input type="file" name="my-form[details][avatar]">

zwrócone drzewo wygląda tak:

[
	'my-form' => [
		'details' => [
			'avatar' => /* instancja FileUpload */
		],
	],
]

Możesz też tworzyć tablice plików:

<input type="file" name="my-form[details][avatars][]" multiple>

W takim przypadku struktura wygląda tak:

[
	'my-form' => [
		'details' => [
			'avatars' => [
				0 => /* instancja FileUpload */,
				1 => /* instancja FileUpload */,
				2 => /* instancja FileUpload */,
			],
		],
	],
]

Do indeksu 1 zagnieżdżonej tablicy najlepiej dostać się tak:

$file = $request->getFile(['my-form', 'details', 'avatars', 1]);
if ($file instanceof Nette\Http\FileUpload) {
	// ...
}

Ponieważ danym zewnętrznym nie można ufać i tym samym polegać na strukturze plików, to podejście jest bezpieczniejsze niż na przykład $request->getFiles()['my-form']['details']['avatars'][1], które mogłoby zawieść.

Przegląd metod FileUpload

hasFile(): bool

Zwraca true, jeśli użytkownik wysłał plik.

isOk(): bool

Zwraca true, jeśli plik został wysłany pomyślnie.

getError(): int

Zwraca kod błędu związany z wysłanym plikiem. Jest to jedna ze stałych UPLOAD_ERR_XXX. Jeśli plik został wysłany pomyślnie, zwraca UPLOAD_ERR_OK.

move (string $dest)

Przenosi wysłany plik w nowe miejsce. Jeśli plik docelowy już istnieje, zostanie nadpisany.

$file->move('/path/to/files/name.ext');

getContents(): ?string

Zwraca zawartość wysłanego pliku. Jeśli wysyłanie się nie powiodło, zwraca null.

getContentType(): ?string

Wykrywa typ zawartości MIME wysłanego pliku na podstawie jego sygnatury. Jeśli wysyłanie się nie powiodło albo wykrywanie zawiodło, zwraca null.

Wymaga rozszerzenia PHP fileinfo.

getUntrustedName(): string

Zwraca oryginalną nazwę pliku wysłaną przez przeglądarkę.

Nie ufaj wartości zwracanej przez tę metodę. Klient mógł wysłać złośliwą nazwę pliku z zamiarem uszkodzenia albo zhakowania Twojej aplikacji.

getSanitizedName(): string

Zwraca oczyszczoną nazwę pliku. Zawiera tylko znaki ASCII [a-zA-Z0-9.-]. Jeśli nazwa takich znaków nie zawiera, zwraca 'unknown'. Jeśli plik jest obrazkiem JPEG, PNG, GIF, WebP albo AVIF, zwraca też poprawne rozszerzenie pliku.

Wymaga rozszerzenia PHP fileinfo.

getSuggestedExtension(): ?string

Zwraca odpowiednie rozszerzenie pliku (bez kropki) odpowiadające wykrytemu typowi MIME.

Wymaga rozszerzenia PHP fileinfo.

getUntrustedFullPath(): string

Zwraca oryginalną ścieżkę pliku wysłaną przez przeglądarkę przy wysyłaniu katalogu. Pełna ścieżka dostępna jest tylko w PHP 8.1 i nowszym. W poprzednich wersjach ta metoda zwraca oryginalną nazwę pliku.

Nie ufaj wartości zwracanej przez tę metodę. Klient mógł wysłać złośliwą nazwę pliku z zamiarem uszkodzenia albo zhakowania Twojej aplikacji.

getSize(): int

Zwraca rozmiar wysłanego pliku. Jeśli wysyłanie się nie powiodło, zwraca 0.

getTemporaryFile(): string

Zwraca ścieżkę do tymczasowej lokalizacji wysłanego pliku. Jeśli wysyłanie się nie powiodło, zwraca ''.

__toString(): string

Zwraca ścieżkę do tymczasowej lokalizacji wysłanego pliku. Pozwala to używać obiektu FileUpload bezpośrednio jako ciągu.

isImage(): bool

Zwraca true, jeśli wysłany plik jest obrazkiem JPEG, PNG, GIF, WebP albo AVIF. Wykrywanie odbywa się na podstawie jego sygnatury i nie weryfikuje integralności całego pliku. To, czy obrazek nie jest uszkodzony, można ustalić na przykład, próbując go wczytać.

Wymaga rozszerzenia PHP fileinfo.

getImageSize(): ?array

Zwraca parę [width, height] z wymiarami wysłanego obrazka. Jeśli wysyłanie się nie powiodło albo nie jest to poprawny obrazek, zwraca null.

toImage(): Nette\Utils\Image

Wczytuje obrazek jako obiekt Image. Jeśli wysyłanie się nie powiodło albo nie jest to poprawny obrazek, rzuca Nette\Utils\ImageException.

wersja: 4.x