Petición HTTP

Nette encapsula la petición HTTP en objetos con una API clara y ofrece a la vez un filtro de saneamiento.

La petición HTTP está representada por el objeto Nette\Http\Request. Si trabaja con Nette, el framework crea este objeto automáticamente y puede hacer que se lo pasen mediante inyección de dependencias. En los presenters basta con llamar al método $this->getHttpRequest(). Si trabaja fuera de Nette Framework, puede crear el objeto con RequestFactory.

Una gran ventaja de Nette es que, al crear el objeto, sanea automáticamente todos los parámetros de entrada (GET, POST, COOKIE) y también la URL, eliminando los caracteres de control y las secuencias UTF-8 no válidas. Después puede trabajar con esos datos con seguridad. Los datos saneados se usan a continuación en los presenters y los formularios.

Instalación y requisitos

Nette\Http\Request

Este objeto es inmutable. No tiene setters; tiene solo un llamado wither, withUrl(), que no cambia el objeto sino que devuelve una nueva instancia con el valor modificado.

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

Devuelve un clon con otra URL.

getUrl(): Nette\Http\UrlScript

Devuelve la URL de la petición como objeto UrlScript.

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

Atención: los navegadores no envían el fragmento al servidor, así que $url->getFragment() devolverá una cadena vacía.

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

Devuelve los parámetros GET de la petición.

$all = $httpRequest->getQuery();    // array de todos los parámetros de la URL
$id = $httpRequest->getQuery('id'); // devuelve el parámetro GET 'id' (o null)

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

Devuelve los parámetros POST de la petición.

$all = $httpRequest->getPost();     // array de todos los parámetros POST
$id = $httpRequest->getPost('id');  // devuelve el parámetro POST 'id' (o null)

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

Devuelve un archivo subido como objeto Nette\Http\FileUpload:

$file = $httpRequest->getFile('avatar');
if ($file?->hasFile()) { // ¿se subió algún archivo?
	$file->getUntrustedName(); // nombre de archivo enviado por el usuario
	$file->getSanitizedName(); // nombre sin caracteres peligrosos
}

Para acceder a una estructura anidada, indique un array de claves.

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

Como no puede fiarse de los datos externos y por tanto tampoco de la estructura de los archivos, este enfoque es más seguro que, por ejemplo, $request->getFiles()['my-form']['details']['avatar'], que podría fallar.

getFiles(): array

Devuelve un árbol de todos los archivos subidos en una estructura normalizada cuyas hojas son objetos Nette\Http\FileUpload:

$files = $httpRequest->getFiles();

getCookie (string $key): ?string

Devuelve una cookie, o null si no existe.

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

getCookies(): array

Devuelve todas las cookies.

$cookies = $httpRequest->getCookies();

getMethod(): string

Devuelve el método HTTP usado en la petición.

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

isMethod (string $method)bool

Comprueba el método HTTP usado en la petición. El parámetro no distingue mayúsculas de minúsculas.

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

getHeader (string $header): ?string

Devuelve una cabecera HTTP, o null si no existe. El parámetro no distingue mayúsculas de minúsculas.

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

getHeaders(): array<string, string>

Devuelve todas las cabeceras HTTP como array asociativo. Las claves están normalizadas a minúsculas.

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

isSecured(): bool

¿Está cifrada la conexión (HTTPS)? Para que funcione correctamente puede hacer falta configurar un proxy.

isSameSite(): bool

¿Llegó la petición desde el mismo sitio? Desde la versión 3.4 lo sustituye el más capaz isFrom().

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

Le dice de dónde llegó la petición y cómo la hizo el navegador, a partir de las cabeceras Sec-Fetch-* (los llamados Fetch Metadata), que el navegador pone él mismo y que una página que se ejecute en el navegador de la víctima no puede falsificar ni eliminar. Nette las usa internamente para proteger automáticamente los formularios y las señales contra el Cross-Site Request Forgery (CSRF). Es útil cuando quiera proteger sus propias acciones sensibles, como endpoints de API o enlaces destructivos.

El método devuelve true solo cuando la petición cumple todas las condiciones que indique. El primer parámetro $site describe la relación entre la página que inició la petición y su sitio (la cabecera Sec-Fetch-Site). Acepta un único valor o una lista de estos casos de FetchSite:

  • FetchSite::SameOrigin: del mismo origen exacto (esquema, host y puerto)
  • FetchSite::SameSite: del mismo sitio, posiblemente de otro subdominio
  • FetchSite::CrossSite: de un sitio ajeno
  • FetchSite::None: la inició directamente el usuario, p. ej. escribiendo la URL o abriendo un marcador
// ¿procede la petición de nuestras propias páginas?
if (!$httpRequest->isFrom([FetchSite::SameOrigin, FetchSite::SameSite])) {
	// bloquea la acción
}

El parámetro opcional $dest (la cabecera Sec-Fetch-Dest) dice qué tipo de recurso está obteniendo el navegador, p. ej. FetchDest::Document para una navegación de nivel superior o FetchDest::Empty para una petición hecha desde JavaScript. El parámetro opcional $user (la cabecera Sec-Fetch-User) indica si la navegación la desencadenó una acción real del usuario, como pulsar un enlace o enviar un formulario; pase true para exigirlo.

Una comprobación de que una acción solo es accesible desde sus propias páginas y solo mediante una acción real del usuario tiene entonces este aspecto:

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

Los navegadores antiguos (Safari anterior a 16.4) no envían las cabeceras Sec-Fetch-*. Para ellos, Nette recurre a una cookie SameSite=Strict que solo demuestra que la petición no es cross-site. Una comprobación que exija además $dest o $user no se puede verificar así y devuelve false en esos navegadores; si eso es demasiado estricto, compruebe solo $site.

isAjax(): bool

¿Es una petición AJAX?

getRemoteAddress(): ?string

Devuelve la dirección IP del usuario. Para que funcione correctamente puede hacer falta configurar un proxy.

getRemoteHost(): ?string

Obsoleto, devuelve siempre null. Las consultas DNS inversas eran lentas y poco fiables; si necesita el nombre del host, resuélvalo usted mismo a partir de getRemoteAddress().

getBasicCredentials(): ?array

Devuelve las credenciales de autenticación de la autenticación HTTP Basic.

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

getRawBody(): ?string

Devuelve el cuerpo de la petición HTTP.

$body = $httpRequest->getRawBody();

getOrigin(): ?UrlImmutable

Devuelve el origen desde el que llegó la petición. Un origen se compone del esquema (protocolo), el nombre de host y el puerto, por ejemplo https://example.com:8080. Devuelve null si la cabecera de origen no está presente o vale 'null'.

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

El navegador envía la cabecera Origin en los siguientes casos:

  • peticiones cross-origin (llamadas AJAX a otro dominio)
  • peticiones POST, PUT, DELETE y otras que modifican
  • peticiones hechas con la Fetch API

El navegador NO envía la cabecera Origin en:

  • las peticiones GET corrientes al mismo dominio (navegación same-origin)
  • la navegación directa escribiendo una URL en la barra de direcciones
  • las peticiones de clientes que no son navegadores

A diferencia de la cabecera Referer, Origin contiene solo el esquema, el host y el puerto, no la ruta completa de la URL. Eso la hace más adecuada para las comprobaciones de seguridad y preserva la privacidad del usuario. La cabecera Origin se usa sobre todo para la validación CORS (Cross-Origin Resource Sharing).

detectLanguage (array $langs): ?string

Detecta el idioma. Pase como parámetro $langs un array de los idiomas que soporta la aplicación y devolverá el preferido por el navegador del visitante. No es magia; simplemente usa la cabecera Accept-Language. Si no encuentra ninguna coincidencia, devuelve null.

// El navegador envía p. ej.: Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3

$langs = ['hu', 'pl', 'en']; // idiomas soportados por la aplicación
echo $httpRequest->detectLanguage($langs); // en

RequestFactory

La clase Nette\Http\RequestFactory sirve para crear una instancia de Nette\Http\Request, que representa la petición HTTP actual. (Si trabaja con Nette, el framework crea automáticamente el objeto de la petición HTTP.)

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

El método fromGlobals() crea el objeto de la petición a partir de las variables globales actuales de PHP ($_GET, $_POST, $_COOKIE, $_FILES y $_SERVER). Al crear el objeto limpia automáticamente todos los parámetros de entrada (GET, POST, COOKIE) y también la URL de caracteres de control y secuencias UTF-8 no válidas, lo que garantiza la seguridad al trabajar después con esos datos.

RequestFactory se puede configurar antes de llamar a fromGlobals():

  • el método $factory->setBinary() desactiva la limpieza automática de los parámetros de entrada de caracteres de control y secuencias UTF-8 no válidas.
  • el método $factory->setProxy(...) indica la dirección IP del servidor proxy, necesaria para detectar correctamente la dirección IP del usuario.
  • el método $factory->setForceHttps() .{data-version:3.3.4} fuerza el esquema HTTPS de la petición independientemente del entorno del servidor.

RequestFactory permite definir filtros que transforman automáticamente partes de la URL de la petición. Estos filtros eliminan de las URL caracteres no deseados que pueden haber insertado, por ejemplo, implementaciones incorrectas de los sistemas de comentarios de distintos sitios web:

// elimina los espacios de la ruta
$requestFactory->urlFilters['path']['%20'] = '';

// elimina el punto, la coma o el paréntesis derecho del final del URI
$requestFactory->urlFilters['url']['[.,)]$'] = '';

// limpia la ruta de barras dobles (filtro predeterminado)
$requestFactory->urlFilters['path']['/{2,}'] = '/';

La primera clave, 'path' o 'url', determina a qué parte de la URL se aplicará el filtro. La segunda clave es la expresión regular que se busca y el valor es el reemplazo que se usará en lugar del texto encontrado.

Archivos subidos

El método Nette\Http\Request::getFiles() devuelve un array de todos los archivos subidos en una estructura normalizada cuyas hojas son objetos Nette\Http\FileUpload. Estos encapsulan los datos enviados por el elemento de formulario <input type=file>.

La estructura refleja los nombres de los elementos en HTML. En el caso más simple puede tratarse de un único elemento de formulario con nombre, enviado como:

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

En ese caso, $request->getFiles() devuelve el array:

[
	'avatar' => /* instancia de FileUpload */
]

El objeto FileUpload se crea aunque el usuario no haya subido ningún archivo o la subida haya fallado. El método hasFile() devuelve true si se envió un archivo:

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

En el caso de un nombre de elemento con notación de array:

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

el árbol devuelto tiene este aspecto:

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

También puede crear arrays de archivos:

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

En ese caso, la estructura tiene este aspecto:

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

La mejor forma de acceder al índice 1 del array anidado es esta:

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

Como no puede fiarse de los datos externos y por tanto tampoco de la estructura de los archivos, este enfoque es más seguro que, por ejemplo, $request->getFiles()['my-form']['details']['avatars'][1], que podría fallar.

Resumen de los métodos de FileUpload

hasFile(): bool

Devuelve true si el usuario subió un archivo.

isOk(): bool

Devuelve true si el archivo se subió correctamente.

getError(): int

Devuelve el código de error asociado al archivo subido. Es una de las constantes UPLOAD_ERR_XXX. Si el archivo se subió correctamente, devuelve UPLOAD_ERR_OK.

move (string $dest)

Mueve un archivo subido a una nueva ubicación. Si el archivo de destino ya existe, se sobrescribirá.

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

getContents(): ?string

Devuelve el contenido del archivo subido. Si la subida no fue correcta, devuelve null.

getContentType(): ?string

Detecta el tipo de contenido MIME del archivo subido a partir de su firma. Si la subida no fue correcta o la detección falló, devuelve null.

Requiere la extensión de PHP fileinfo.

getUntrustedName(): string

Devuelve el nombre original del archivo tal y como lo envió el navegador.

No se fíe del valor que devuelve este método. Un cliente podría enviar un nombre de archivo malicioso con la intención de dañar o comprometer su aplicación.

getSanitizedName(): string

Devuelve el nombre de archivo saneado. Contiene solo caracteres ASCII [a-zA-Z0-9.-]. Si el nombre no contiene esos caracteres, devuelve 'unknown'. Si el archivo es una imagen JPEG, PNG, GIF, WebP o AVIF, devuelve además la extensión de archivo correcta.

Requiere la extensión de PHP fileinfo.

getSuggestedExtension(): ?string

Devuelve la extensión de archivo adecuada (sin el punto) que corresponde al tipo MIME detectado.

Requiere la extensión de PHP fileinfo.

getUntrustedFullPath(): string

Devuelve la ruta original del archivo tal y como la envió el navegador al subir un directorio. La ruta completa solo está disponible en PHP 8.1 y superior. En versiones anteriores, este método devuelve el nombre original del archivo.

No se fíe del valor que devuelve este método. Un cliente podría enviar un nombre de archivo malicioso con la intención de dañar o comprometer su aplicación.

getSize(): int

Devuelve el tamaño del archivo subido. Si la subida no fue correcta, devuelve 0.

getTemporaryFile(): string

Devuelve la ruta a la ubicación temporal del archivo subido. Si la subida no fue correcta, devuelve ''.

__toString(): string

Devuelve la ruta a la ubicación temporal del archivo subido. Eso permite usar el objeto FileUpload directamente como cadena.

isImage(): bool

Devuelve true si el archivo subido es una imagen JPEG, PNG, GIF, WebP o AVIF. La detección se basa en su firma y no verifica la integridad del archivo entero. Si una imagen está dañada se puede averiguar, por ejemplo, intentando cargarla.

Requiere la extensión de PHP fileinfo.

getImageSize(): ?array

Devuelve el par [width, height] con las dimensiones de la imagen subida. Si la subida no fue correcta o no es una imagen válida, devuelve null.

toImage(): Nette\Utils\Image

Carga la imagen como objeto Image. Si la subida no fue correcta o no es una imagen válida, lanza una Nette\Utils\ImageException.

versión: 4.x