Diferencia entre revisiones de «PHP Paso de datos con formularios»
Ir a la navegación
Ir a la búsqueda
Etiqueta: Deshacer |
|||
| Línea 1: | Línea 1: | ||
== Introdución == | == Introdución == | ||
| − | * | + | * A través da etiqueta <code><form></code> podemos enviar datos dunha páxina a outra. |
| − | : Os principais elementos | + | : Os principais elementos HTML que poden enviar información dentro dun formulario son [https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/form os elementos asociados aos formularios HTML]. |
| + | * Dentro da etiqueta <code>form</code> temos, entre outros: | ||
| + | :* O atributo '''method''', que normalmente toma os valores '''get''' ou '''post'''. | ||
| + | :* O atributo '''action''', que indica a URL que vai procesar os datos enviados polo formulario. | ||
| − | * | + | * Se queremos que o formulario se procese na mesma páxina podemos deixar o atributo <code>action</code> baleiro: |
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | ::<syntaxhighlight lang="html" line enclose="div" highlight="1"> | |
| + | <form name="form1" action="" method="post"> | ||
| + | |||
| + | </form> | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| + | : Outra posibilidade é empregar <code>$_SERVER['PHP_SELF']</code>. Neste caso debemos escapar o seu valor antes de introducilo no HTML: | ||
| + | ::<syntaxhighlight lang="php" line enclose="div" highlight="1"> | ||
| + | <form name="form1" | ||
| + | action="<?php echo htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?>" | ||
| + | method="post"> | ||
| − | + | </form> | |
| + | </syntaxhighlight> | ||
| − | + | * <u>Importante:</u> os datos recibidos do cliente non deben considerarse fiables. Isto é válido tanto para GET como para POST. A validación HTML ou JavaScript mellora a experiencia de usuario, pero a validación de seguridade debe facerse tamén no servidor. | |
| − | * | + | * <u>Nota:</u> Para pasar datos dunha páxina a outra tamén podemos empregar campos ocultos (<code>hidden</code>). Un campo oculto non é secreto: o usuario pode velo e modificalo, polo que tamén debe validarse no servidor. |
| − | |||
| − | : | ||
| − | + | == Método GET == | |
| − | + | * Co método GET os datos envíanse como parámetros na URL. Por exemplo: | |
| − | |||
| − | |||
| − | : | ||
| − | + | ::<syntaxhighlight lang="text"> | |
| + | https://exemplo.test/buscar.php?q=php&orde=asc | ||
| + | </syntaxhighlight> | ||
| − | : | + | : Os datos situados despois do signo <code>?</code> forman a cadea de consulta ou ''query string''. Os diferentes parámetros sepáranse normalmente mediante <code>&</code>. |
| + | * '''Características do método GET:''' | ||
| + | :* Os parámetros son visibles na URL. | ||
| + | :* A URL pode gardarse nos marcadores, copiarse ou compartirse conservando os parámetros. | ||
| + | :* Os parámetros poden quedar rexistrados no historial do navegador, nos logs do servidor e noutros sistemas intermedios. | ||
| + | :* O usuario pode modificar libremente os valores enviados. Polo tanto, '''os datos GET sempre deben validarse no servidor'''. | ||
| + | :* Non existe un límite único de tamaño establecido por PHP para unha URL completa; os límites prácticos dependen do navegador, do servidor web, dos proxies e da infraestrutura empregada. | ||
| + | :* GET é apropiado principalmente para operacións de consulta que non deberían modificar o estado da aplicación. | ||
| + | * As variables enviadas deste xeito están dispoñibles en PHP mediante o array superglobal [https://www.php.net/manual/es/reserved.variables.get.php $_GET]. | ||
| − | * | + | * Vexamos un exemplo utilizando un formulario. |
| + | '''Arquivo: UD2_Form_Ex1_eleccion_pelicula.php''' | ||
| − | + | ::<syntaxhighlight lang="html" line enclose="div"> | |
| + | <!DOCTYPE html> | ||
| + | <html lang="gl"> | ||
| + | <head> | ||
| + | <meta charset="UTF-8"> | ||
| + | <title>Formulario</title> | ||
| + | </head> | ||
| + | <body> | ||
| + | <form id="frmPelicula" method="get" action="UD2_Form_Ex1_datos_pelicula.php"> | ||
| + | <div> | ||
| + | <label for="lstPelicula">Película preferida:</label> | ||
| + | <select id="lstPelicula" name="lstPelicula"> | ||
| + | <option value="ET">E.T. O extraterrestre</option> | ||
| + | <option value="Superman">Supermán</option> | ||
| + | </select> | ||
| + | </div> | ||
| − | + | <div> | |
| − | + | <label for="lstXenero">Xénero preferido:</label> | |
| − | < | + | <select id="lstXenero" name="lstXenero"> |
| − | + | <option value="Acción">Acción</option> | |
| − | + | <option value="Ciencia ficción">Ciencia ficción</option> | |
| − | + | </select> | |
| − | + | </div> | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | < | ||
| − | |||
| − | |||
| − | |||
| − | < | ||
| − | |||
| − | </ | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | </body> | + | <button type="submit">ENVIAR</button> |
| + | </form> | ||
| + | </body> | ||
</html> | </html> | ||
| − | |||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | :* Fixarse que enviamos os datos utilizando o '''método | + | :* Fixarse en que enviamos os datos utilizando o '''método GET'''. |
'''Arquivo: UD2_Form_Ex1_datos_pelicula.php''' | '''Arquivo: UD2_Form_Ex1_datos_pelicula.php''' | ||
| − | |||
| − | |||
| − | |||
| − | <html> | + | ::<syntaxhighlight lang="php" line enclose="div"> |
| − | + | <!DOCTYPE html> | |
| − | + | <html lang="gl"> | |
| − | + | <head> | |
| − | + | <meta charset="UTF-8"> | |
| − | + | <title>Datos da película</title> | |
| − | + | </head> | |
| − | + | <body> | |
| − | + | <?php | |
| − | + | $pelicula = $_GET['lstPelicula'] ?? ''; | |
| − | + | $xenero = $_GET['lstXenero'] ?? ''; | |
| − | + | $peliculaHtml = htmlspecialchars($pelicula, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); | |
| − | + | $xeneroHtml = htmlspecialchars($xenero, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); | |
| − | + | ||
| − | + | echo 'Os datos que escolliches son:<br><br>'; | |
| + | printf('PELÍCULA: %s<br>XÉNERO: %s', $peliculaHtml, $xeneroHtml); | ||
| + | ?> | ||
| + | </body> | ||
</html> | </html> | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| + | * <u>Nota:</u> O operador <code>??</code> permite indicar un valor alternativo cando unha clave non existe. Deste xeito evitamos acceder directamente a unha clave inexistente de <code>$_GET</code>. | ||
| + | |||
| + | * <u>Nota:</u> Para visualizar durante o desenvolvemento o conxunto de valores recibidos pódese empregar [https://www.php.net/manual/es/function.var-dump.php var_dump()]: | ||
| + | ::<syntaxhighlight lang="php"> | ||
| + | var_dump($_GET); | ||
| + | </syntaxhighlight> | ||
| − | |||
== Método POST == | == Método POST == | ||
| − | * | + | * Co método POST os datos do formulario envíanse no corpo da petición HTTP e non forman parte da URL. |
| + | |||
| + | * Isto '''non significa que POST sexa secreto nin que os seus datos sexan fiables'''. Un usuario pode inspeccionar e modificar unha petición POST empregando as ferramentas de desenvolvemento do navegador, <code>curl</code>, Postman ou outras ferramentas. Para protexer os datos durante o transporte debe empregarse HTTPS. | ||
| − | * | + | * O tamaño máximo dos datos recibidos mediante POST está condicionado, entre outras cousas, pola directiva [https://www.php.net/manual/es/ini.core.php#ini.post-max-size post_max_size] de PHP. O seu valor por defecto é 8M, pero pode ser modificado pola configuración do servidor. |
| − | |||
| − | |||
| − | |||
| − | : | + | : No caso da subida de arquivos tamén intervén [https://www.php.net/manual/es/ini.core.php#ini.upload-max-filesize upload_max_filesize], cuxo valor por defecto é 2M. Para permitir arquivos de maior tamaño, <code>post_max_size</code> debe ser maior que <code>upload_max_filesize</code>. |
| − | |||
| − | + | * A forma de modificar estas directivas depende de como estea instalado PHP. O arquivo <code>.htaccess</code> só permite empregar directivas <code>php_value</code> cando PHP funciona como módulo de Apache e a configuración do servidor o permite. Con CGI/FastCGI ou PHP-FPM poden existir outros mecanismos, por exemplo <code>.user.ini</code>, segundo a configuración do servidor. | |
| − | * | ||
| + | * As variables enviadas mediante POST están dispoñibles no array superglobal [https://www.php.net/manual/es/reserved.variables.post.php $_POST]. | ||
| − | * Modificamos o exemplo anterior para que | + | * Modificamos o exemplo anterior para que empregue POST. |
'''Arquivo: UD2_Form_Ex2_eleccion_pelicula.php''' | '''Arquivo: UD2_Form_Ex2_eleccion_pelicula.php''' | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | </body> | + | ::<syntaxhighlight lang="html" line enclose="div"> |
| + | <!DOCTYPE html> | ||
| + | <html lang="gl"> | ||
| + | <head> | ||
| + | <meta charset="UTF-8"> | ||
| + | <title>Formulario</title> | ||
| + | </head> | ||
| + | <body> | ||
| + | <form id="frmPelicula" method="post" action="UD2_Form_Ex2_datos_pelicula.php"> | ||
| + | <div> | ||
| + | <label for="lstPelicula">Película preferida:</label> | ||
| + | <select id="lstPelicula" name="lstPelicula"> | ||
| + | <option value="ET">E.T. O extraterrestre</option> | ||
| + | <option value="Superman">Supermán</option> | ||
| + | </select> | ||
| + | </div> | ||
| + | |||
| + | <div> | ||
| + | <label for="lstXenero">Xénero preferido:</label> | ||
| + | <select id="lstXenero" name="lstXenero"> | ||
| + | <option value="Acción">Acción</option> | ||
| + | <option value="Ciencia ficción">Ciencia ficción</option> | ||
| + | </select> | ||
| + | </div> | ||
| + | |||
| + | <button type="submit">ENVIAR</button> | ||
| + | </form> | ||
| + | </body> | ||
</html> | </html> | ||
| − | |||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | + | '''Arquivo: UD2_Form_Ex2_datos_pelicula.php''' | |
| − | + | ::<syntaxhighlight lang="php" line enclose="div"> | |
| − | ::<syntaxhighlight lang="php" line enclose="div" | + | <!DOCTYPE html> |
| − | < | + | <html lang="gl"> |
| − | + | <head> | |
| + | <meta charset="UTF-8"> | ||
| + | <title>Datos da película</title> | ||
| + | </head> | ||
| + | <body> | ||
| + | <?php | ||
| + | $pelicula = $_POST['lstPelicula'] ?? ''; | ||
| + | $xenero = $_POST['lstXenero'] ?? ''; | ||
| − | + | $peliculaHtml = htmlspecialchars($pelicula, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); | |
| − | + | $xeneroHtml = htmlspecialchars($xenero, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | echo 'Os datos que escolliches son:<br><br>'; | |
| − | + | printf('PELÍCULA: %s<br>XÉNERO: %s', $peliculaHtml, $xeneroHtml); | |
| − | + | ?> | |
| − | + | </body> | |
</html> | </html> | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| + | * <u>Nota:</u> Durante o desenvolvemento podemos comprobar os datos recibidos con: | ||
| + | ::<syntaxhighlight lang="php"> | ||
| + | var_dump($_POST); | ||
| + | </syntaxhighlight> | ||
| − | |||
| − | |||
== Comprobando se hai datos == | == Comprobando se hai datos == | ||
| − | * | + | * A páxina que recibe un formulario normalmente vai validar os datos e realizar algunha operación con eles. Por exemplo, pode facer unha consulta, gardar información ou modificar datos existentes. |
| − | * | + | * A validación no cliente mediante HTML ou JavaScript é útil, pero '''non substitúe a validación no servidor'''. |
| − | |||
| + | === Comprobando o método da petición === | ||
| + | * Unha forma robusta de saber se unha páxina foi enviada mediante POST é consultar <code>$_SERVER['REQUEST_METHOD']</code>: | ||
| − | + | ::<syntaxhighlight lang="php" line enclose="div"> | |
| + | if ($_SERVER['REQUEST_METHOD'] !== 'POST') { | ||
| + | exit('Non se enviou o formulario mediante POST'); | ||
| + | } | ||
| + | </syntaxhighlight> | ||
| − | + | * Se queremos saber se chegou un campo concreto podemos utilizar <code>isset()</code>, <code>array_key_exists()</code> ou o operador <code>??</code>, segundo o caso: | |
| − | + | ::<syntaxhighlight lang="php" line enclose="div"> | |
| − | + | $nome = $_POST['txtNome'] ?? ''; | |
| − | </syntaxhighlight> | + | if ($nome === '') { |
| + | echo 'O campo nome non ten datos'; | ||
| + | } | ||
| + | </syntaxhighlight> | ||
| − | <u>Nota:</u> | + | * <u>Nota:</u> [https://www.php.net/manual/es/function.exit.php exit] e <code>die</code> finalizan a execución do script. |
| − | + | === Tipo dos datos recibidos === | |
| − | |||
| + | * Os valores simples procedentes dos campos dun formulario chegan normalmente como cadeas de caracteres. Por exemplo, aínda que un <code>input</code> conteña o texto <code>25</code>, o valor recibido será unha cadea. | ||
| − | * | + | * Non obstante, <code>$_POST</code> tamén pode conter arrays se empregamos nomes do tipo <code>campo[]</code> ou estruturas como <code>usuario[nome]</code>. Os arquivos subidos non aparecen en <code>$_POST</code>, senón en <code>$_FILES</code>. |
| − | |||
| − | + | * Por este motivo non debemos facer un ''cast'' antes de validar o dato. Primeiro comprobamos que a cadea ten o formato esperado e despois facemos a conversión. | |
| − | |||
| − | |||
| − | + | : Exemplo para unha idade formada só por díxitos: | |
| − | : | + | ::<syntaxhighlight lang="php" line enclose="div"> |
| + | $idadeTexto = $_POST['txtIdade'] ?? ''; | ||
| − | + | if (is_string($idadeTexto) && ctype_digit($idadeTexto)) { | |
| − | if (ctype_digit($ | + | $idade = (int) $idadeTexto; |
| − | + | } else { | |
| − | } | + | echo 'A idade non é un número enteiro válido'; |
| − | else { | ||
| − | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | + | * Tamén podemos empregar filtros de validación, como veremos máis adiante. | |
| − | + | ||
| + | === Comprobando se chegan datos de varios formularios diferentes á mesma páxina === | ||
| + | |||
| + | * Se varios formularios envían datos á mesma páxina é preferible identificar explicitamente a orixe cun campo oculto, en lugar de depender do botón de envío. O botón pode non aparecer na petición nalgúns modos de envío, por exemplo ao premer Intro nun campo. | ||
| + | '''Formulario da primeira páxina:''' | ||
| − | === | + | ::<syntaxhighlight lang="html"> |
| + | <form method="post" action="segunda.php"> | ||
| + | <input type="hidden" name="formulario" value="primeira"> | ||
| + | ... | ||
| + | <button type="submit">Enviar</button> | ||
| + | </form> | ||
| + | </syntaxhighlight> | ||
| − | + | '''Formulario da segunda páxina:''' | |
| − | |||
| − | : | + | ::<syntaxhighlight lang="html"> |
| − | + | <form method="post" action=""> | |
| − | + | <input type="hidden" name="formulario" value="segunda"> | |
| − | + | ... | |
| − | + | <button type="submit">Enviar</button> | |
| + | </form> | ||
| + | </syntaxhighlight> | ||
| − | + | '''Código de segunda.php:''' | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | : | + | ::<syntaxhighlight lang="php"> |
| − | + | $orixe = $_POST['formulario'] ?? null; | |
| − | == | + | if ($orixe === 'primeira') { |
| + | // Datos enviados dende primeira.php | ||
| + | } elseif ($orixe === 'segunda') { | ||
| + | // Datos enviados dende o formulario de segunda.php | ||
| + | } else { | ||
| + | // A páxina cargouse directamente ou chegou unha orixe non válida | ||
| + | } | ||
| + | </syntaxhighlight> | ||
| − | * | + | * <u>Importante:</u> un campo oculto tamén pode ser modificado polo usuario. Só serve para identificar a intención do formulario; o seu valor debe comprobarse no servidor. |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| + | == Validación, normalización e escapado dos campos == | ||
| + | * Ao traballar con datos externos convén diferenciar tres operacións: | ||
| + | :* '''Validación''': comprobar se un dato cumpre as regras esperadas. Por exemplo, comprobar se unha idade é un enteiro entre 0 e 130. | ||
| + | :* '''Normalización ou saneamento''': transformar un dato cando realmente o necesitemos, por exemplo aplicar <code>trim()</code> para eliminar espazos ao principio e ao final. | ||
| + | :* '''Escapado da saída''': adaptar un dato ao contexto no que se vai amosar. Para escribir texto dentro de HTML empregaremos normalmente <code>htmlspecialchars()</code>. | ||
| − | < | + | * Non debemos asumir que un dato queda seguro para todos os contextos por aplicarlle un filtro xenérico. A protección depende do uso: |
| − | * | + | :* HTML → <code>htmlspecialchars()</code>. |
| + | :* SQL → consultas preparadas con parámetros. | ||
| + | :* URL → <code>rawurlencode()</code> ou <code>http_build_query()</code>. | ||
| + | :* Números, correos, IP, etc. → validación específica. | ||
| − | + | === No cliente === | |
| − | |||
| − | * | + | * HTML permite realizar validacións básicas mediante atributos como: |
| + | :* <code>required</code>: indica que o campo é obrigatorio. | ||
| + | :* <code>maxlength</code>, <code>minlength</code>, <code>min</code> e <code>max</code>: limitan lonxitudes ou rangos. | ||
| + | :* <code>type="email"</code>, <code>type="number"</code>, <code>type="date"</code>, etc. | ||
| + | :* <code>accept</code> nun campo de tipo <code>file</code>: axuda a indicar ao navegador os tipos de ficheiro que se queren seleccionar. | ||
| − | + | * Estas comprobacións poden ser evitadas polo usuario. Por iso deben repetirse no servidor as regras que afecten á integridade ou á seguridade da aplicación. | |
| − | |||
| − | : | + | === No servidor: filter_var() === |
| − | |||
| − | |||
| − | : | + | * PHP dispón da función [https://www.php.net/manual/es/function.filter-var.php filter_var()], que permite validar ou sanear unha variable segundo o filtro indicado. |
| − | : | + | * Sintaxe simplificada: |
| + | ::<syntaxhighlight lang="php"> | ||
| + | filter_var($valor, $filtro, $opcion); | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | * Parámetros: | ||
| + | :* <code>$valor</code>: dato que queremos comprobar. | ||
| + | :* <code>$filtro</code>: constante <code>FILTER_VALIDATE_*</code> ou <code>FILTER_SANITIZE_*</code>. | ||
| + | :* <code>$opcion</code>: opcións ou ''flags'' adicionais do filtro. | ||
=== Filtros para a validación === | === Filtros para a validación === | ||
| − | :* FILTER_VALIDATE_BOOLEAN | + | :* <code>FILTER_VALIDATE_BOOLEAN</code>: valida un valor booleano. |
| − | :* FILTER_VALIDATE_EMAIL | + | :* <code>FILTER_VALIDATE_EMAIL</code>: valida unha dirección de correo electrónico. |
| − | :* FILTER_VALIDATE_FLOAT | + | :* <code>FILTER_VALIDATE_FLOAT</code>: valida un número de punto flotante. |
| − | :* FILTER_VALIDATE_INT | + | :* <code>FILTER_VALIDATE_INT</code>: valida un número enteiro. |
| − | :* FILTER_VALIDATE_IP | + | :* <code>FILTER_VALIDATE_IP</code>: valida unha dirección IP. |
| − | :* FILTER_VALIDATE_REGEXP | + | :* <code>FILTER_VALIDATE_REGEXP</code>: valida mediante unha expresión regular. |
| − | :* FILTER_VALIDATE_URL | + | :* <code>FILTER_VALIDATE_URL</code>: valida unha URL. |
| + | === Filtros de saneamento === | ||
| − | + | * Algúns filtros de saneamento que continúan dispoñibles son: | |
| + | :* <code>FILTER_SANITIZE_EMAIL</code> | ||
| + | :* <code>FILTER_SANITIZE_ENCODED</code> | ||
| + | :* <code>FILTER_SANITIZE_NUMBER_FLOAT</code> | ||
| + | :* <code>FILTER_SANITIZE_NUMBER_INT</code> | ||
| + | :* <code>FILTER_SANITIZE_SPECIAL_CHARS</code> | ||
| + | :* <code>FILTER_SANITIZE_FULL_SPECIAL_CHARS</code> | ||
| + | :* <code>FILTER_SANITIZE_URL</code> | ||
| − | + | * <u>Importante en PHP 8:</u> | |
| − | :* | + | :* <code>FILTER_SANITIZE_MAGIC_QUOTES</code> foi eliminado en PHP 8.0 e non debe empregarse. |
| − | + | :* <code>FILTER_SANITIZE_STRING</code> e <code>FILTER_SANITIZE_STRIPPED</code> están obsoletos dende PHP 8.1. Para escapar texto que vai ser mostrado en HTML empregaremos directamente <code>htmlspecialchars()</code>. | |
| − | |||
| − | :* | ||
| − | |||
| − | |||
| − | + | * Un filtro de saneamento non substitúe unha validación. Por exemplo, se esperamos un enteiro é preferible rexeitar <code>12abc</code> como inválido en lugar de transformalo silenciosamente en <code>12</code>. | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
=== Exemplos === | === Exemplos === | ||
| − | = | + | ==== Validando ==== |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | * Validando un número enteiro | + | * Validando un número enteiro: |
| − | : | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | ::<syntaxhighlight lang="php" line enclose="div"> | |
| − | ::<syntaxhighlight lang="php" line enclose="div | + | $valor = '123'; |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| + | $enteiro = filter_var($valor, FILTER_VALIDATE_INT); | ||
| − | + | if ($enteiro === false) { | |
| − | + | echo 'Valor incorrecto'; | |
| − | + | } else { | |
| − | + | echo "Valor correcto: $enteiro"; | |
| − | + | } | |
| − | + | </syntaxhighlight> | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | </syntaxhighlight> | ||
| − | |||
| + | * Validando un número flotante: | ||
| − | + | ::<syntaxhighlight lang="php" line enclose="div"> | |
| − | ::<syntaxhighlight lang="php" line enclose="div | + | $valor = '-1.45'; |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | = | + | $decimal = filter_var($valor, FILTER_VALIDATE_FLOAT); |
| − | + | if ($decimal === false) { | |
| + | echo 'Valor incorrecto'; | ||
| + | } else { | ||
| + | echo "Valor correcto: $decimal"; | ||
| + | } | ||
| + | </syntaxhighlight> | ||
| − | * | + | * Validando un correo electrónico: |
| − | : | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| + | ::<syntaxhighlight lang="php" line enclose="div"> | ||
| + | $correo = 'usuario@example.com'; | ||
| − | + | if (filter_var($correo, FILTER_VALIDATE_EMAIL) === false) { | |
| − | + | echo 'Correo incorrecto'; | |
| − | + | } else { | |
| − | + | echo 'Correo correcto'; | |
| + | } | ||
| + | </syntaxhighlight> | ||
| − | + | ==== Escapando texto para HTML ==== | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | * Se queremos mostrar nunha páxina un texto recibido do usuario, escapámolo no momento da saída: | |
| − | + | ::<syntaxhighlight lang="php" line enclose="div"> | |
| + | $texto = '<script>alert("ola")</script>'; | ||
| − | + | echo htmlspecialchars( | |
| − | + | $texto, | |
| − | + | ENT_QUOTES | ENT_SUBSTITUTE, | |
| − | $ | + | 'UTF-8' |
| − | + | ); | |
| − | + | </syntaxhighlight> | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | </syntaxhighlight> | ||
| − | |||
| + | : O navegador mostrará o texto recibido, pero non interpretará as etiquetas como código HTML. | ||
| − | * | + | * <u>Importante:</u> <code>strip_tags()</code> e <code>htmlspecialchars()</code> non fan o mesmo. <code>strip_tags()</code> elimina etiquetas; <code>htmlspecialchars()</code> codifica os caracteres especiais para que se mostren como texto. |
| − | |||
| − | < | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | ==== Saneando un número flotante ==== | |
| − | |||
| − | |||
| + | ::<syntaxhighlight lang="php" line enclose="div"> | ||
| + | $numero = '53.3pp'; | ||
| − | + | $resultado = filter_var( | |
| − | + | $numero, | |
| − | + | FILTER_SANITIZE_NUMBER_FLOAT, | |
| − | $ | + | FILTER_FLAG_ALLOW_FRACTION |
| − | + | ); | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | </syntaxhighlight> | + | var_dump($resultado); |
| − | + | </syntaxhighlight> | |
| + | : Neste caso o resultado será a cadea <code>53.3</code>. Esta operación transforma o dato; non é equivalente a comprobar que o dato orixinal fose un decimal válido. | ||
| − | + | === Establecendo criterios === | |
| − | + | * Podemos validar un enteiro dentro dun rango: | |
| + | ::<syntaxhighlight lang="php" line enclose="div"> | ||
| + | $valor = 25; | ||
| − | + | $opcion = [ | |
| + | 'options' => [ | ||
| + | 'min_range' => 10, | ||
| + | 'max_range' => 100, | ||
| + | ], | ||
| + | ]; | ||
| − | = | + | $resultado = filter_var($valor, FILTER_VALIDATE_INT, $opcion); |
| − | + | if ($resultado === false) { | |
| + | echo 'Valor incorrecto'; | ||
| + | } else { | ||
| + | echo "Valor correcto: $resultado"; | ||
| + | } | ||
| + | </syntaxhighlight> | ||
| + | * Tamén podemos indicar un valor por defecto: | ||
| − | + | ::<syntaxhighlight lang="php" line enclose="div"> | |
| − | + | $valor = -200; | |
| − | |||
| − | ::<syntaxhighlight lang="php" line enclose="div | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | $opcion = [ | |
| − | + | 'options' => [ | |
| − | + | 'default' => 3, | |
| + | 'min_range' => 10, | ||
| + | 'max_range' => 100, | ||
| + | ], | ||
| + | ]; | ||
| − | + | $resultado = filter_var($valor, FILTER_VALIDATE_INT, $opcion); | |
| − | + | echo $resultado; | |
| − | + | </syntaxhighlight> | |
| − | < | + | : Neste caso obteremos <code>3</code> se o valor non supera a validación. |
| − | + | * Para un número decimal podemos indicar outro separador decimal mediante a opción <code>decimal</code>: | |
| − | + | ::<syntaxhighlight lang="php" line enclose="div"> | |
| − | : | + | $valor = '1,23'; |
| − | + | $opcion = [ | |
| − | + | 'options' => [ | |
| − | + | 'decimal' => ',', | |
| − | + | ], | |
| − | + | ]; | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | $resultado = filter_var($valor, FILTER_VALIDATE_FLOAT, $opcion); | |
| − | |||
| − | |||
| − | : | + | if ($resultado === false) { |
| + | echo 'Valor incorrecto'; | ||
| + | } else { | ||
| + | echo "Valor correcto: $resultado"; | ||
| + | } | ||
| + | </syntaxhighlight> | ||
| − | + | == Seguridade no uso dos formularios == | |
| − | * | + | * Os datos dun formulario están controlados polo cliente e poden ser modificados. A aplicación debe validar a entrada e aplicar a protección apropiada no contexto no que vai empregar cada dato. |
| − | |||
| + | === Inxección SQL === | ||
| + | * Unha inxección SQL pode producirse cando unha aplicación constrúe unha consulta concatenando directamente datos controlados polo usuario. | ||
| − | + | : Exemplo '''incorrecto''': | |
| − | === | + | ::<syntaxhighlight lang="php" line enclose="div"> |
| + | $nome = $_POST['usuario'] ?? ''; | ||
| + | $password = $_POST['password'] ?? ''; | ||
| − | * | + | $sql = "SELECT count(*) FROM usuarios |
| + | WHERE nome='$nome' AND password='$password'"; | ||
| + | </syntaxhighlight> | ||
| + | * O problema non se resolve eliminando comiñas mediante filtros HTML nin empregando <code>htmlspecialchars()</code>. HTML e SQL son contextos diferentes. | ||
| − | + | ==== Solución á inxección SQL ==== | |
| − | |||
| + | * A solución habitual é empregar '''consultas preparadas con parámetros'''. Os datos e o código SQL envíanse ao xestor de base de datos de forma separada. | ||
| − | : | + | : Exemplo con MySQLi: |
| − | |||
| − | |||
| − | |||
| − | : | + | ::<syntaxhighlight lang="php" line enclose="div"> |
| − | + | $stmt = $mysqli->prepare( | |
| + | 'SELECT id, nome, password_hash | ||
| + | FROM usuarios | ||
| + | WHERE nome = ?' | ||
| + | ); | ||
| − | + | $stmt->bind_param('s', $nome); | |
| + | $stmt->execute(); | ||
| − | + | $resultado = $stmt->get_result(); | |
| + | $usuario = $resultado->fetch_assoc(); | ||
| + | </syntaxhighlight> | ||
| − | + | * Cando traballemos con contrasinais reais non debemos gardalos nin comparalos en texto claro. Empregaremos <code>password_hash()</code> para gardar o ''hash'' e <code>password_verify()</code> para comprobar o contrasinal. | |
| + | * <code>mysqli_real_escape_string()</code> pode ser útil nalgúns contextos específicos, pero '''non é a técnica principal que debemos ensinar para construír consultas con datos externos'''. A opción preferente son as consultas preparadas. | ||
| − | * | + | * Un procedemento almacenado tampouco evita por si só unha inxección SQL: dependerá de como estea implementado e de se constrúe SQL dinámico internamente. |
| − | + | === Cross-Site Scripting (XSS) === | |
| + | * Un ataque XSS pode producirse cando datos controlados por un usuario se incorporan a unha páxina e o navegador os interpreta como HTML ou JavaScript. | ||
| − | + | : Por exemplo, imaxinemos que recibimos: | |
| − | ::<syntaxhighlight lang=" | + | ::<syntaxhighlight lang="html" line enclose="div"> |
| − | < | + | <script>alert('Exemplo XSS')</script> |
| + | </syntaxhighlight> | ||
| − | + | : Se facemos directamente: | |
| − | < | + | ::<syntaxhighlight lang="php"> |
| − | </syntaxhighlight> | + | echo $_GET['texto']; |
| + | </syntaxhighlight> | ||
| + | : o navegador podería interpretar o contido como marcado ou código. | ||
==== Solucións ao ataque Cross-Site Scripting (XSS) ==== | ==== Solucións ao ataque Cross-Site Scripting (XSS) ==== | ||
| + | * Para mostrar texto dentro de HTML empregaremos normalmente: | ||
| + | |||
| + | ::<syntaxhighlight lang="php"> | ||
| + | $texto = $_GET['texto'] ?? ''; | ||
| − | + | echo htmlspecialchars( | |
| + | $texto, | ||
| + | ENT_QUOTES | ENT_SUBSTITUTE, | ||
| + | 'UTF-8' | ||
| + | ); | ||
| + | </syntaxhighlight> | ||
| − | * | + | * <code>htmlspecialchars()</code> non elimina o texto: codifica os caracteres con significado especial en HTML para que se mostren como texto. |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| + | * O escapado debe realizarse '''no momento da saída e segundo o contexto'''. Un valor preparado para HTML non queda automaticamente preparado para SQL, JavaScript, CSS ou unha URL. | ||
| − | + | * <code>strip_tags()</code> pode eliminar etiquetas HTML, pero non debe considerarse unha alternativa xeral ao escapado contextual. | |
| − | < | ||
| − | + | * Se queremos utilizar <code>$_SERVER['PHP_SELF']</code> nun atributo HTML, debemos escapar o valor: | |
| + | ::<syntaxhighlight lang="php"> | ||
| + | <form method="post" | ||
| + | action="<?php echo htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?>"> | ||
</form> | </form> | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| + | : Para un formulario que se procesa na mesma páxina tamén podemos empregar simplemente: | ||
| − | < | + | ::<syntaxhighlight lang="html"> |
| + | <form method="post" action=""> | ||
| + | </form> | ||
| + | </syntaxhighlight> | ||
| − | ===Ataques CSRF (Cross-Site Request Forgery)=== | + | === Ataques CSRF (Cross-Site Request Forgery) === |
| − | * | + | * Os ataques CSRF trataranse [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Sesi%C3%B3ns#Ataques_CSRF_.28Cross-Site_Request_Forgery.29 cando vexamos as variables de sesión na UD4]. |
| + | === Normas xerais === | ||
| + | * Como normas xerais: | ||
| + | :* Validaremos no servidor todos os datos procedentes do cliente. | ||
| + | :* Nos formularios que modifican o estado da aplicación empregaremos protección contra CSRF. | ||
| + | :* Para mostrar texto dentro de HTML empregaremos <code>htmlspecialchars(..., ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')</code>. | ||
| + | :* Para SQL empregaremos consultas preparadas con parámetros. | ||
| + | :* Para contrasinais empregaremos <code>password_hash()</code> e <code>password_verify()</code>. | ||
| + | :* Non empregaremos filtros HTML para intentar evitar inxección SQL. | ||
| + | :* Sanearemos ou normalizaremos datos só cando as regras da aplicación o requiran; non substituiremos a validación por unha transformación silenciosa. | ||
| − | |||
| − | |||
| + | == Obtendo variables de formularios (POST/GET) e filtrando ao mesmo tempo == | ||
| + | * PHP dispón da función [https://www.php.net/manual/es/function.filter-input.php filter_input()], que permite obter unha variable externa e aplicarlle opcionalmente un filtro. | ||
| − | * | + | * Sintaxe actual: |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| + | ::<syntaxhighlight lang="php"> | ||
| + | filter_input( | ||
| + | int $type, | ||
| + | string $var_name, | ||
| + | int $filter = FILTER_DEFAULT, | ||
| + | array|int $options = 0 | ||
| + | ): mixed | ||
| + | </syntaxhighlight> | ||
| + | * Parámetros: | ||
| + | :* '''type''': <code>INPUT_GET</code>, <code>INPUT_POST</code>, <code>INPUT_COOKIE</code>, <code>INPUT_SERVER</code> ou <code>INPUT_ENV</code>. | ||
| + | :* '''var_name''': nome da variable. | ||
| + | :* '''filter''': filtro a aplicar. | ||
| + | :* '''options''': opcións ou ''flags''. | ||
| + | * <u>Importante:</u> <code>FILTER_DEFAULT</code> é un alias de <code>FILTER_UNSAFE_RAW</code>; é dicir, por defecto non se aplica unha limpeza automática. | ||
| + | * <code>filter_input()</code> traballa sobre o valor orixinal proporcionado pola SAPI. Se modificamos previamente <code>$_GET</code> ou <code>$_POST</code> dentro do programa e queremos filtrar ese novo valor, empregaremos <code>filter_var()</code>. | ||
| − | + | * Exemplo validando un identificador enteiro recibido por GET: | |
| − | == | + | ::<syntaxhighlight lang="php" line enclose="div"> |
| + | $id = filter_input( | ||
| + | INPUT_GET, | ||
| + | 'id', | ||
| + | FILTER_VALIDATE_INT, | ||
| + | [ | ||
| + | 'options' => [ | ||
| + | 'min_range' => 1, | ||
| + | ], | ||
| + | ] | ||
| + | ); | ||
| − | + | if ($id === null) { | |
| − | + | echo 'Non se enviou o parámetro id.'; | |
| + | } elseif ($id === false) { | ||
| + | echo 'O identificador non é válido.'; | ||
| + | } else { | ||
| + | echo 'Identificador válido: ' . $id; | ||
| + | } | ||
| + | </syntaxhighlight> | ||
| − | * | + | * Exemplo para obter texto e mostralo en HTML: |
| − | |||
| − | |||
| − | |||
| − | : | + | ::<syntaxhighlight lang="php" line enclose="div"> |
| − | + | $buscar = filter_input(INPUT_GET, 'buscar', FILTER_UNSAFE_RAW); | |
| − | |||
| − | |||
| − | |||
| − | + | if (!is_string($buscar)) { | |
| + | $buscar = ''; | ||
| + | } | ||
| − | + | $buscarHtml = htmlspecialchars( | |
| + | $buscar, | ||
| + | ENT_QUOTES | ENT_SUBSTITUTE, | ||
| + | 'UTF-8' | ||
| + | ); | ||
| − | + | echo "Buscaches: $buscarHtml"; | |
| − | + | </syntaxhighlight> | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | echo " | ||
| − | |||
| − | |||
| − | |||
| − | </syntaxhighlight> | ||
| − | |||
| − | |||
== Redireccionamento de páxinas == | == Redireccionamento de páxinas == | ||
| − | * | + | * Despois de procesar un formulario pode ser necesario redireccionar ao usuario a outra páxina. |
| − | : | + | * Podemos facelo coa [https://www.php.net/manual/es/function.header.php función header()]: |
| − | + | ::<syntaxhighlight lang="php" line enclose="div"> | |
| − | + | header('Location: /nova_paxina.php'); | |
| − | |||
| − | |||
| − | ::<syntaxhighlight lang="php" line enclose="div | ||
| − | |||
| − | header('Location: | ||
exit; | exit; | ||
| − | + | </syntaxhighlight> | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | </syntaxhighlight> | ||
| − | |||
| − | |||
| + | :<u><b>Importante:</b></u> | ||
| + | :* A cabeceira debe enviarse antes de producir saída cara ao cliente. Non debe haber HTML, <code>echo</code>, <code>printf</code> nin espazos enviados antes da chamada a <code>header()</code>. | ||
| + | :* Despois dun redireccionamento é recomendable executar <code>exit;</code> para impedir que continúe o resto do script. | ||
| + | * Se queremos enviar parámetros GET nunha redirección é preferible construír a cadea mediante <code>http_build_query()</code>: | ||
| + | ::<syntaxhighlight lang="php" line enclose="div"> | ||
| + | $parametros = http_build_query([ | ||
| + | 'parametro1' => 'valor 1', | ||
| + | 'parametro2' => 'valor 2', | ||
| + | ]); | ||
| + | header('Location: /nova_paxina.php?' . $parametros); | ||
| + | exit; | ||
| + | </syntaxhighlight> | ||
| − | + | * Na páxina de destino poderemos obter os datos mediante <code>$_GET</code> e validalos. | |
| − | * | ||
| − | |||
| − | + | * Para inspeccionar as cabeceiras HTTP xa non é necesario instalar complementos específicos. Os navegadores actuais dispoñen das ferramentas de desenvolvemento: normalmente podemos abrir a lapela '''Rede/Network''', seleccionar unha petición e revisar as súas cabeceiras. | |
| − | |||
| − | |||
| − | |||
| − | |||
== Gardando imaxes no servidor == | == Gardando imaxes no servidor == | ||
| − | * | + | * Os formularios permiten subir arquivos ao servidor. Un caso típico é asociar unha imaxe a un produto. |
| − | |||
| + | * A subida de arquivos require especial atención á seguridade: | ||
| + | :* Non debemos confiar no nome orixinal enviado polo cliente. | ||
| + | :* Non debemos confiar en <code>$_FILES['...']['type']</code>, xa que esa información é proporcionada polo cliente e PHP non a verifica. | ||
| + | :* Debemos comprobar o código de erro, o tamaño e o tipo real do arquivo. | ||
| + | :* É recomendable xerar un nome novo no servidor. | ||
| + | :* Para arquivos que non necesiten ser servidos directamente polo servidor web é preferible almacenalos fóra do <code>DocumentRoot</code>. | ||
| + | :* Nunca debemos permitir a execución de código nun cartafol de subidas. | ||
| − | + | === Cartafol de destino === | |
| − | |||
| − | |||
| + | * Nun exemplo sinxelo no que Apache serve directamente as imaxes e o <code>DocumentRoot</code> é <code>/var/www/html</code>, poderiamos crear un cartafol específico: | ||
| − | + | ::<syntaxhighlight lang="bash" line enclose="div"> | |
| − | |||
| − | ::<syntaxhighlight lang=" | ||
cd /var/www/html | cd /var/www/html | ||
mkdir IMAXES | mkdir IMAXES | ||
chown www-data:www-data IMAXES | chown www-data:www-data IMAXES | ||
| − | chmod | + | chmod 750 IMAXES |
| − | </syntaxhighlight> | + | </syntaxhighlight> |
| − | |||
| − | |||
| − | + | * O usuario e grupo reais dependen da instalación. En Debian/Ubuntu é habitual <code>www-data</code>; noutras instalacións pode ser diferente. | |
| − | |||
| − | < | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | * Evitaremos permisos <code>777</code>. O proceso que executa PHP só necesita os permisos necesarios para escribir no directorio. | |
| − | |||
| + | === Páxina do formulario === | ||
| + | * Para subir arquivos é obrigatorio utilizar <code>enctype="multipart/form-data"</code>. | ||
| − | + | ::<syntaxhighlight lang="html" line enclose="div"> | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | ::<syntaxhighlight lang=" | ||
<!DOCTYPE html> | <!DOCTYPE html> | ||
| − | <html> | + | <html lang="gl"> |
| + | <head> | ||
| + | <meta charset="UTF-8"> | ||
| + | <title>Subida de imaxes</title> | ||
| + | </head> | ||
<body> | <body> | ||
<form action="GardaCampos.php" method="post" enctype="multipart/form-data"> | <form action="GardaCampos.php" method="post" enctype="multipart/form-data"> | ||
| − | Selecciona unha imaxe | + | <label for="fileToUpload">Selecciona unha imaxe:</label> |
| − | <input type="file" name="fileToUpload" id="fileToUpload"> | + | <input |
| − | < | + | type="file" |
| + | name="fileToUpload" | ||
| + | id="fileToUpload" | ||
| + | accept=".jpg,.jpeg,.png,.gif" | ||
| + | required | ||
| + | > | ||
| + | |||
| + | <button type="submit">Subir imaxe</button> | ||
</form> | </form> | ||
</body> | </body> | ||
</html> | </html> | ||
| − | </syntaxhighlight> | + | </syntaxhighlight> |
| − | ''' | + | * O atributo <code>accept</code> axuda ao usuario a seleccionar determinados ficheiros, pero '''non é unha medida de seguridade'''. O servidor debe comprobar igualmente o arquivo recibido. |
| − | |||
| − | |||
| − | * | + | === Páxina que procesa os datos === |
| + | |||
| + | * O seguinte exemplo: | ||
| + | :* comproba o método da petición; | ||
| + | :* comproba o código de erro; | ||
| + | :* limita o tamaño; | ||
| + | :* verifica que o arquivo procede dunha subida HTTP; | ||
| + | :* detecta o MIME real mediante <code>finfo</code>; | ||
| + | :* só permite determinados tipos de imaxe; | ||
| + | :* xera un nome aleatorio; | ||
| + | :* move o arquivo ao directorio definitivo. | ||
| + | |||
| + | ::<syntaxhighlight lang="php" line enclose="div"> | ||
| + | <?php | ||
| + | |||
| + | if ($_SERVER['REQUEST_METHOD'] !== 'POST') { | ||
| + | exit('Método non permitido.'); | ||
| + | } | ||
| + | if (!isset($_FILES['fileToUpload'])) { | ||
| + | exit('Non se recibiu ningún arquivo.'); | ||
| + | } | ||
| − | '' | + | $arquivo = $_FILES['fileToUpload']; |
| − | |||
| − | + | if ($arquivo['error'] !== UPLOAD_ERR_OK) { | |
| − | + | exit('Erro na subida. Código: ' . $arquivo['error']); | |
| − | + | } | |
| − | if | ||
| − | |||
| − | |||
| − | |||
| − | + | // Límite propio da aplicación: 2 MiB. | |
| − | + | $tamanoMaximo = 2 * 1024 * 1024; | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| + | if ($arquivo['size'] > $tamanoMaximo) { | ||
| + | exit('O arquivo supera o tamaño máximo permitido.'); | ||
| + | } | ||
| + | if (!is_uploaded_file($arquivo['tmp_name'])) { | ||
| + | exit('O arquivo non procede dunha subida HTTP válida.'); | ||
} | } | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| + | // Detectamos o MIME a partir do contido real do arquivo. | ||
| + | $finfo = new finfo(FILEINFO_MIME_TYPE); | ||
| + | $mime = $finfo->file($arquivo['tmp_name']); | ||
| − | + | $tiposPermitidos = [ | |
| − | + | 'image/jpeg' => 'jpg', | |
| − | + | 'image/png' => 'png', | |
| − | + | 'image/gif' => 'gif', | |
| − | + | ]; | |
| − | |||
| + | if ($mime === false || !isset($tiposPermitidos[$mime])) { | ||
| + | exit('Tipo de arquivo non permitido.'); | ||
| + | } | ||
| + | $extension = $tiposPermitidos[$mime]; | ||
| − | + | // Non empregamos o nome enviado polo cliente como nome definitivo. | |
| − | + | $nomeSeguro = bin2hex(random_bytes(16)) . '.' . $extension; | |
| − | |||
| − | |||
| − | |||
| + | $directorioDestino = __DIR__ . '/IMAXES'; | ||
| − | + | if (!is_dir($directorioDestino) || !is_writable($directorioDestino)) { | |
| − | + | exit('O directorio de destino non existe ou non permite escritura.'); | |
| − | + | } | |
| − | + | $destino = $directorioDestino . DIRECTORY_SEPARATOR . $nomeSeguro; | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| + | if (!move_uploaded_file($arquivo['tmp_name'], $destino)) { | ||
| + | exit('Non foi posible gardar o arquivo.'); | ||
| + | } | ||
| + | echo 'Imaxe gardada correctamente co nome: ' | ||
| + | . htmlspecialchars($nomeSeguro, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); | ||
| + | </syntaxhighlight> | ||
| − | * | + | * Neste exemplo empregamos a clase [https://www.php.net/manual/es/class.finfo.php finfo] coa constante <code>FILEINFO_MIME_TYPE</code>. Non é necesario chamar a <code>finfo_close()</code>; en PHP 8.5 esa función está obsoleta porque o obxecto se libera automaticamente. |
| − | |||
| + | === Códigos de erro de subida === | ||
| + | * O campo <code>$_FILES['fileToUpload']['error']</code> pode ter, entre outros, os seguintes valores: | ||
| + | :* <code>UPLOAD_ERR_OK (0)</code>: subida correcta. | ||
| + | :* <code>UPLOAD_ERR_INI_SIZE (1)</code>: o arquivo supera <code>upload_max_filesize</code>. | ||
| + | :* <code>UPLOAD_ERR_FORM_SIZE (2)</code>: supera <code>MAX_FILE_SIZE</code>, se o formulario enviou ese campo. | ||
| + | :* <code>UPLOAD_ERR_PARTIAL (3)</code>: subida parcial. | ||
| + | :* <code>UPLOAD_ERR_NO_FILE (4)</code>: non se seleccionou ningún arquivo. | ||
| + | :* <code>UPLOAD_ERR_NO_TMP_DIR (6)</code>: non existe o directorio temporal. | ||
| + | :* <code>UPLOAD_ERR_CANT_WRITE (7)</code>: PHP non puido escribir no disco. | ||
| + | :* <code>UPLOAD_ERR_EXTENSION (8)</code>: unha extensión de PHP detivo a subida. | ||
| − | + | === Funcións útiles === | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| + | :* [https://www.php.net/manual/es/function.basename.php basename()]: devolve a última parte dunha ruta. Pode ser útil para mostrar o nome orixinal, pero non debemos confiar nel como única medida de seguridade nin utilizalo necesariamente como nome final. | ||
| + | :* [https://www.php.net/manual/es/function.pathinfo.php pathinfo()]: permite obter partes dun nome de ficheiro. A extensión enviada polo usuario non demostra o tipo real do arquivo. | ||
| + | :* [https://www.php.net/manual/es/function.getimagesize.php getimagesize()]: pode obter información dunha imaxe e pode empregarse como comprobación adicional cando a aplicación espera imaxes. | ||
| + | :* [https://www.php.net/manual/es/function.is-uploaded-file.php is_uploaded_file()]: comproba que o ficheiro temporal procede dunha subida HTTP. | ||
| + | :* [https://www.php.net/manual/es/function.move-uploaded-file.php move_uploaded_file()]: move de forma segura un ficheiro subido ao seu destino. | ||
| + | === Limitar o tamaño === | ||
| − | * | + | * Ademais dos límites de PHP, a aplicación debería establecer o seu propio límite: |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| + | ::<syntaxhighlight lang="php"> | ||
| + | $tamanoMaximo = 2 * 1024 * 1024; | ||
| + | if ($_FILES['fileToUpload']['size'] > $tamanoMaximo) { | ||
| + | exit('O arquivo é demasiado grande.'); | ||
| + | } | ||
| + | </syntaxhighlight> | ||
| − | * | + | * HTML permite enviar un campo oculto <code>MAX_FILE_SIZE</code>: |
| − | |||
| − | < | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | ::<syntaxhighlight lang="html"> | |
| − | ::<syntaxhighlight lang=" | ||
<form action="GardaCampos.php" method="post" enctype="multipart/form-data"> | <form action="GardaCampos.php" method="post" enctype="multipart/form-data"> | ||
| − | + | <input type="hidden" name="MAX_FILE_SIZE" value="2097152"> | |
| − | <input type="hidden" name="MAX_FILE_SIZE" value=" | + | <input type="file" name="fileToUpload"> |
| − | <input type="file" name | + | <button type="submit">Subir</button> |
| − | < | ||
</form> | </form> | ||
| − | </syntaxhighlight> | + | </syntaxhighlight> |
| − | |||
| − | + | * <u>Importante:</u> este campo está controlado polo cliente e pode ser modificado. Non substitúe os límites configurados en PHP nin a comprobación do tamaño no servidor. | |
| − | + | ||
| − | < | + | === Configuración en php.ini === |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | * As directivas principais son: | |
| − | + | ::<syntaxhighlight lang="ini"> | |
| − | </syntaxhighlight> | + | upload_max_filesize = 2M |
| + | post_max_size = 8M | ||
| + | </syntaxhighlight> | ||
| + | * Estes son os valores por defecto de PHP 8.5, pero un servidor real pode ter outros valores. | ||
| + | * <code>post_max_size</code> debe ser maior que <code>upload_max_filesize</code> se queremos permitir a subida dun ficheiro dese tamaño. | ||
| − | + | * Para saber que arquivo de configuración está cargando PHP en liña de comandos podemos executar: | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | : | + | ::<syntaxhighlight lang="bash"> |
| − | + | php --ini | |
| − | + | </syntaxhighlight> | |
| − | |||
| − | : | + | * Nunha instalación Debian/Ubuntu poden existir arquivos diferentes segundo a SAPI empregada, por exemplo: |
| − | : | + | ::<syntaxhighlight lang="text"> |
| − | + | /etc/php/8.5/apache2/php.ini | |
| + | /etc/php/8.5/fpm/php.ini | ||
| + | /etc/php/8.5/cli/php.ini | ||
| + | </syntaxhighlight> | ||
| + | * <u>Nota:</u> a ruta exacta depende da instalación e da versión instalada. | ||
| − | + | * Se PHP funciona como módulo de Apache e o servidor o permite, algunhas directivas poden configurarse mediante <code>.htaccess</code>: | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | </ | ||
| − | |||
| − | + | ::<syntaxhighlight lang="apache"> | |
| − | |||
| − | |||
php_value upload_max_filesize 64M | php_value upload_max_filesize 64M | ||
| − | php_value post_max_size | + | php_value post_max_size 70M |
php_value max_execution_time 300 | php_value max_execution_time 300 | ||
php_value max_input_time 300 | php_value max_input_time 300 | ||
| − | </syntaxhighlight> | + | </syntaxhighlight> |
| − | |||
| + | * Se se utiliza CGI/FastCGI ou PHP-FPM, <code>php_value</code> en <code>.htaccess</code> pode non funcionar. Dependendo do servidor poden empregarse <code>.user.ini</code>, a configuración do ''pool'' de PHP-FPM ou a configuración proporcionada polo servizo de aloxamento. | ||
| − | |||
== Verificando os datos dun formulario == | == Verificando os datos dun formulario == | ||
| − | * | + | * Podemos comprobar varios campos obrigatorios percorrendo un array cos seus nomes. |
| − | ::<syntaxhighlight lang="php" line enclose="div | + | |
| − | + | ::<syntaxhighlight lang="php" line enclose="div"> | |
| − | + | $camposObrigatorios = ['txtNome', 'txtEmail']; | |
| − | $ | + | |
| + | $erros = []; | ||
| − | + | foreach ($camposObrigatorios as $campo) { | |
| − | + | if (!isset($_POST[$campo]) || !is_string($_POST[$campo]) || trim($_POST[$campo]) === '') { | |
| − | foreach ($ | + | $erros[] = "O campo $campo é obrigatorio."; |
| − | + | } | |
} | } | ||
| − | if ($erro) { | + | if ($erros !== []) { |
| − | + | foreach ($erros as $erro) { | |
| − | } | + | echo htmlspecialchars($erro, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '<br>'; |
| − | + | } | |
| + | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | + | * Empregamos <code>trim()</code> porque unha cadea formada só por espazos tampouco debería considerarse un valor válido para estes campos. | |
| − | * | + | * <u>Nota:</u> Non sempre debemos empregar <code>empty()</code> para validar campos obrigatorios, xa que a cadea <code>"0"</code> considérase baleira por <code>empty()</code> e nalgúns formularios pode ser un valor válido. |
| − | |||
| − | |||
| − | |||
| − | + | == Creando arrays cos datos dun formulario == | |
| − | |||
| − | + | * Nos formularios xerados dinamicamente pode haber un número variable de campos. HTML permite empregar corchetes no atributo <code>name</code> para que PHP reciba os valores nun array. | |
| − | |||
| − | |||
| − | < | ||
| − | |||
| − | |||
| − | + | * Imaxinemos que queremos introducir o estado de varios exemplares dun libro e que o número de exemplares non se coñece de antemán. | |
| − | + | : En lugar de crear nomes diferentes: | |
| − | + | ::<syntaxhighlight lang="html"> | |
| − | + | <input type="text" name="txtEstado1"> | |
| − | ::<syntaxhighlight lang=" | + | <input type="text" name="txtEstado2"> |
| − | + | <input type="text" name="txtEstado3"> | |
| − | <input type= | ||
| − | |||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | + | : podemos empregar: | |
| + | ::<syntaxhighlight lang="php"> | ||
| + | <?php for ($cont = 0; $cont < $numExemplares; $cont++): ?> | ||
| + | <input type="text" name="txtEstado[]"> | ||
| + | <?php endfor; ?> | ||
| + | </syntaxhighlight> | ||
| + | * Ao recibir o formulario, <code>$_POST['txtEstado']</code> será un array. | ||
| + | * Debemos comprobar que realmente recibimos un array antes de percorrelo: | ||
| − | < | + | ::<syntaxhighlight lang="php"> |
| − | = | + | $estados = $_POST['txtEstado'] ?? []; |
| − | + | if (!is_array($estados)) { | |
| + | $estados = []; | ||
| + | } | ||
| + | foreach ($estados as $estado) { | ||
| + | if (!is_string($estado)) { | ||
| + | continue; | ||
| + | } | ||
| + | $estado = trim($estado); | ||
| − | + | // Validar $estado segundo as regras da aplicación. | |
| + | } | ||
| + | </syntaxhighlight> | ||
| + | == Exercicios propostos == | ||
| − | * '''Exercicio | + | * '''Exercicio 1:''' Crea unha páxina cun formulario e dous campos, nome e idade. O método de envío será POST e o destino será a mesma páxina, sen escribir manualmente o seu nome na URL. O campo nome terá un máximo de vinte caracteres e o campo idade deberá conter exactamente dous díxitos. O formulario terá un botón "Enviar datos". Se algún dato non é válido, amosarase unha mensaxe de erro e volverase amosar o formulario conservando os valores correctos introducidos. Se todos os datos son válidos, amosaranse nunha lista e non se volverá amosar o formulario. Debes diferenciar entre validación da entrada e escapado da saída HTML. |
| − | |||
| + | * '''Exercicio 2:''' Fai o necesario para xestionar a alta, baixa e baleirado dunha lista de froitas. O nome dunha froita debe validarse no servidor e todos os valores deben escaparse ao seren mostrados en HTML. Se o campo está baleiro ou non cumpre o formato permitido, amosarase un erro. A lista amosarase nunha táboa e ao carón de cada froita haberá un botón para eliminala. Se non hai elementos amosarase "Cesta baleira". Nun arquivo separado <code>/utilidades/operacionsFroitas.php</code> gardaranse as funcións necesarias. O arquivo é imprescindible e só debe cargarse unha vez, polo que se empregará <code>require_once</code>. Para manter a lista entre peticións neste exercicio pódense empregar campos ocultos, lembrando que os seus valores poden ser modificados polo cliente e deben validarse de novo. Máis adiante substituiremos este mecanismo por sesións. | ||
| + | * '''Exercicio 3:''' Crea nun arquivo separado <code>/utilidades/usuarios.php</code> un array de usuarios. Cada usuario terá: id, nome, teléfono e correo electrónico. Nese mesmo arquivo define unha función <code>obterUsuarioPorID()</code> que reciba un enteiro e devolva o usuario correspondente ou <code>null</code> se non existe. Crea unha páxina <code>listaUsuarios.php</code> que amose os nomes como enlaces a <code>detalleUsuario.php</code>. O identificador enviarase mediante GET. Na páxina de detalle debes validar que o ID recibido é un enteiro positivo e escapar os datos antes de amosalos. Proba a modificar manualmente o parámetro <code>id</code> da URL. | ||
| + | === Solución Exercicios propostos === | ||
| + | ==== Solución Exercicio 1 ==== | ||
| + | ::<syntaxhighlight lang="php" enclose="div"> | ||
| + | <?php | ||
| + | $nome = ''; | ||
| + | $idadeTexto = ''; | ||
| + | $idade = null; | ||
| + | $erros = []; | ||
| + | $formularioEnviado = $_SERVER['REQUEST_METHOD'] === 'POST'; | ||
| + | if ($formularioEnviado) { | ||
| + | $nomeRecibido = $_POST['nome'] ?? ''; | ||
| + | $idadeRecibida = $_POST['idade'] ?? ''; | ||
| + | if (!is_string($nomeRecibido)) { | ||
| + | $nomeRecibido = ''; | ||
| + | } | ||
| + | if (!is_string($idadeRecibida)) { | ||
| + | $idadeRecibida = ''; | ||
| + | } | ||
| + | $nome = trim($nomeRecibido); | ||
| + | $idadeTexto = trim($idadeRecibida); | ||
| + | // Validación do nome. | ||
| + | if ($nome === '') { | ||
| + | $erros[] = 'O campo nome é obrigatorio.'; | ||
| + | } elseif (mb_strlen($nome) > 20) { | ||
| + | $erros[] = 'O campo nome non pode ter máis de 20 caracteres.'; | ||
| + | } elseif (!preg_match("/^[\p{L}\p{M} .'-]+$/u", $nome)) { | ||
| + | $erros[] = 'O nome contén caracteres non permitidos.'; | ||
| + | } | ||
| + | // Validación da idade: exactamente dous díxitos. | ||
| + | if ($idadeTexto === '') { | ||
| + | $erros[] = 'O campo idade é obrigatorio.'; | ||
| + | } elseif (!preg_match('/^\d{2}$/', $idadeTexto)) { | ||
| + | $erros[] = 'A idade debe estar formada exactamente por dous díxitos.'; | ||
| + | } else { | ||
| + | $idade = (int) $idadeTexto; | ||
| + | } | ||
| + | } | ||
| + | function escaparHtml(string $valor): string | ||
| + | { | ||
| + | return htmlspecialchars( | ||
| + | $valor, | ||
| + | ENT_QUOTES | ENT_SUBSTITUTE, | ||
| + | 'UTF-8' | ||
| + | ); | ||
| + | } | ||
| − | + | $datosValidos = $formularioEnviado && $erros === []; | |
| − | + | ?> | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
<!DOCTYPE html> | <!DOCTYPE html> | ||
<html lang="gl"> | <html lang="gl"> | ||
<head> | <head> | ||
<meta charset="UTF-8"> | <meta charset="UTF-8"> | ||
| − | <title>Formulario | + | <title>Formulario</title> |
</head> | </head> | ||
<body> | <body> | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| + | <?php if (!$datosValidos): ?> | ||
| − | + | <h1>Formulario</h1> | |
| − | |||
| − | |||
| − | |||
| + | <?php if ($formularioEnviado && $erros !== []): ?> | ||
| + | <h2>Erros</h2> | ||
| + | <ul> | ||
| + | <?php foreach ($erros as $erro): ?> | ||
| + | <li><?php echo escaparHtml($erro); ?></li> | ||
| + | <?php endforeach; ?> | ||
| + | </ul> | ||
| + | <?php endif; ?> | ||
| − | / | + | <form method="post" action=""> |
| − | + | <div> | |
| − | + | <label for="nome">Nome:</label> | |
| − | + | <input | |
| − | + | type="text" | |
| − | + | id="nome" | |
| + | name="nome" | ||
| + | maxlength="20" | ||
| + | required | ||
| + | value="<?php echo escaparHtml($nome); ?>" | ||
| + | > | ||
| + | </div> | ||
| − | + | <div> | |
| − | + | <label for="idade">Idade:</label> | |
| − | + | <input | |
| − | + | type="text" | |
| − | + | id="idade" | |
| − | + | name="idade" | |
| − | + | inputmode="numeric" | |
| − | + | maxlength="2" | |
| − | + | size="3" | |
| − | + | required | |
| − | + | value="<?php echo escaparHtml($idadeTexto); ?>" | |
| − | + | > | |
| − | + | </div> | |
| − | |||
| − | |||
| − | |||
| − | + | <button type="submit">Enviar datos</button> | |
| − | + | </form> | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | < | ||
| − | |||
| − | + | <?php else: ?> | |
| − | |||
| − | < | + | <h1>Datos enviados</h1> |
| − | </ | + | <ul> |
| + | <li>Nome: <?php echo escaparHtml($nome); ?></li> | ||
| + | <li>Idade: <?php echo $idade; ?></li> | ||
| + | </ul> | ||
| − | <?php | + | <?php endif; ?> |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | ?> | ||
</body> | </body> | ||
| Línea 1172: | Línea 1138: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| + | * Fixarse en que o nome non se transforma con filtros HTML antes de validalo. Gardamos o valor lóxico e aplicamos <code>htmlspecialchars()</code> unicamente cando o inserimos na saída HTML. | ||
| + | |||
| + | <br /> | ||
| + | ==== Solución Exercicio 2 ==== | ||
| − | + | : '''Arquivo /utilidades/operacionsFroitas.php''' | |
| − | |||
| − | + | ::<syntaxhighlight lang="php" enclose="div"> | |
| − | ::<syntaxhighlight lang="php" enclose="div | ||
<?php | <?php | ||
| − | |||
| − | |||
/** | /** | ||
| − | * | + | * Valida e normaliza o nome dunha froita. |
* | * | ||
| − | + | * @param string $nome Nome recibido. | |
| − | + | * @return string|false Nome normalizado ou false se non é válido. | |
| − | |||
| − | |||
| − | * @param string $nome Nome | ||
| − | |||
| − | * @return string|false Nome | ||
*/ | */ | ||
| − | function verificarNomeFroita(string $nome): string|false { | + | function verificarNomeFroita(string $nome): string|false |
| − | + | { | |
| − | + | $nome = trim($nome); | |
| − | $nome = | + | |
| + | if ($nome === '') { | ||
| + | return false; | ||
| + | } | ||
| + | |||
| + | if (mb_strlen($nome) > 40) { | ||
| + | return false; | ||
| + | } | ||
| − | if ( | + | // Permitimos letras Unicode, espazos, apóstrofos e guións. |
| + | if (!preg_match("/^[\p{L}\p{M} '-]+$/u", $nome)) { | ||
return false; | return false; | ||
| − | |||
| − | |||
} | } | ||
| + | |||
| + | return $nome; | ||
} | } | ||
/** | /** | ||
| − | * Engade unha froita | + | * Engade unha froita se aínda non existe. |
| − | |||
| − | |||
* | * | ||
| − | * @return | + | * @param array $lista Lista actual. |
| + | * @param string $froita Froita a engadir. | ||
| + | * @return array Nova lista. | ||
*/ | */ | ||
| − | function engadirFroita(string $ | + | function engadirFroita(array $lista, string $froita): array |
| − | + | { | |
| − | + | if (!in_array($froita, $lista, true)) { | |
| − | if (!in_array($ | + | $lista[] = $froita; |
| − | $ | ||
} | } | ||
| + | |||
| + | return $lista; | ||
} | } | ||
/** | /** | ||
| − | * Elimina unha froita da lista | + | * Elimina unha froita da lista. |
* | * | ||
| − | * @param string $ | + | * @param array $lista Lista actual. |
| − | + | * @param string $froita Froita a eliminar. | |
| − | * @return | + | * @return array Nova lista. |
*/ | */ | ||
| − | function eliminarFroita(string $ | + | function eliminarFroita(array $lista, string $froita): array |
| − | + | { | |
| − | $indice = array_search($ | + | $indice = array_search($froita, $lista, true); |
| + | |||
if ($indice !== false) { | if ($indice !== false) { | ||
| − | unset($ | + | unset($lista[$indice]); |
| + | $lista = array_values($lista); | ||
} | } | ||
| + | |||
| + | return $lista; | ||
} | } | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | : '''Arquivo xestionFroitas.php''' | ||
| + | |||
| + | ::<syntaxhighlight lang="php" enclose="div"> | ||
| + | <?php | ||
| + | |||
| + | require_once __DIR__ . '/utilidades/operacionsFroitas.php'; | ||
| + | |||
| + | function escaparHtml(string $valor): string | ||
| + | { | ||
| + | return htmlspecialchars( | ||
| + | $valor, | ||
| + | ENT_QUOTES | ENT_SUBSTITUTE, | ||
| + | 'UTF-8' | ||
| + | ); | ||
| + | } | ||
| + | |||
| + | $listaFroitas = []; | ||
| + | $erro = null; | ||
| + | |||
| + | // Recuperamos a lista dos campos ocultos. | ||
| + | // Estes valores veñen do cliente, polo que os validamos novamente. | ||
| + | $listaRecibida = $_POST['listaFroitas'] ?? []; | ||
| + | |||
| + | if (is_array($listaRecibida)) { | ||
| + | foreach ($listaRecibida as $froitaRecibida) { | ||
| + | if (!is_string($froitaRecibida)) { | ||
| + | continue; | ||
| + | } | ||
| + | |||
| + | $froitaValida = verificarNomeFroita($froitaRecibida); | ||
| − | + | if ($froitaValida !== false) { | |
| − | + | $listaFroitas = engadirFroita($listaFroitas, $froitaValida); | |
| − | + | } | |
| − | + | } | |
| − | |||
| − | |||
| − | |||
| − | |||
} | } | ||
| + | if ($_SERVER['REQUEST_METHOD'] === 'POST') { | ||
| + | if (isset($_POST['btnBaleirar'])) { | ||
| + | $listaFroitas = []; | ||
| + | } | ||
| + | |||
| + | if (isset($_POST['btnEngadir'])) { | ||
| + | $novaFroita = $_POST['novaFroita'] ?? ''; | ||
| + | |||
| + | if (!is_string($novaFroita)) { | ||
| + | $novaFroita = ''; | ||
| + | } | ||
| − | + | $novaFroita = verificarNomeFroita($novaFroita); | |
| − | + | if ($novaFroita === false) { | |
| + | $erro = 'O nome da froita non é válido.'; | ||
| + | } else { | ||
| + | $listaFroitas = engadirFroita($listaFroitas, $novaFroita); | ||
| + | } | ||
| + | } | ||
| + | if (isset($_POST['btnBorrar']) && is_string($_POST['btnBorrar'])) { | ||
| + | $froitaAEliminar = verificarNomeFroita($_POST['btnBorrar']); | ||
| − | + | if ($froitaAEliminar !== false) { | |
| − | + | $listaFroitas = eliminarFroita($listaFroitas, $froitaAEliminar); | |
| + | } | ||
| + | } | ||
| + | } | ||
| + | ?> | ||
<!DOCTYPE html> | <!DOCTYPE html> | ||
| − | <html> | + | <html lang="gl"> |
<head> | <head> | ||
| − | <title>Xestión de | + | <meta charset="UTF-8"> |
| + | <title>Xestión de froitas</title> | ||
</head> | </head> | ||
<body> | <body> | ||
| − | |||
| − | <?php | + | <h1>Xestión de froitas</h1> |
| − | + | ||
| − | + | <?php if ($erro !== null): ?> | |
| + | <p><?php echo escaparHtml($erro); ?></p> | ||
| + | <?php endif; ?> | ||
| + | |||
| + | <form method="post" action=""> | ||
| + | |||
| + | <?php foreach ($listaFroitas as $clave => $froita): ?> | ||
| + | <input | ||
| + | type="hidden" | ||
| + | name="listaFroitas[<?php echo $clave; ?>]" | ||
| + | value="<?php echo escaparHtml($froita); ?>" | ||
| + | > | ||
| + | <?php endforeach; ?> | ||
| − | + | <?php if ($listaFroitas === []): ?> | |
| − | + | <p>Cesta baleira.</p> | |
| − | + | <?php else: ?> | |
| + | <table> | ||
| + | <thead> | ||
| + | <tr> | ||
| + | <th>Froita</th> | ||
| + | <th>Acción</th> | ||
| + | </tr> | ||
| + | </thead> | ||
| + | <tbody> | ||
| − | + | <?php foreach ($listaFroitas as $froita): ?> | |
| − | + | <tr> | |
| − | + | <td><?php echo escaparHtml($froita); ?></td> | |
| − | + | <td> | |
| − | + | <button | |
| − | + | type="submit" | |
| − | + | name="btnBorrar" | |
| − | + | value="<?php echo escaparHtml($froita); ?>" | |
| − | + | > | |
| − | + | Eliminar | |
| + | </button> | ||
| + | </td> | ||
| + | </tr> | ||
| + | <?php endforeach; ?> | ||
| − | + | </tbody> | |
| − | + | </table> | |
| − | + | <?php endif; ?> | |
| − | |||
| − | |||
| − | // | + | <div> |
| − | + | <label for="novaFroita">Nome da froita:</label> | |
| + | <input type="text" id="novaFroita" name="novaFroita" maxlength="40"> | ||
| + | <button type="submit" name="btnEngadir" value="1">Engadir froita</button> | ||
| + | </div> | ||
| − | + | <?php if ($listaFroitas !== []): ?> | |
| − | + | <button type="submit" name="btnBaleirar" value="1">Baleirar cesta</button> | |
| − | + | <?php endif; ?> | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | </form> | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
</body> | </body> | ||
</html> | </html> | ||
| + | </syntaxhighlight> | ||
| − | </ | + | * <u>Nota:</u> Este exercicio conserva o estado mediante campos ocultos porque as sesións aínda non se estudaron. Os campos ocultos poden ser manipulados polo cliente; por iso a solución volve validar todas as froitas recibidas. Nunha aplicación real, para manter estado interno empregaremos normalmente sesións ou persistencia no servidor. |
| + | <br /> | ||
| + | ==== Solución Exercicio 3 ==== | ||
| − | + | : '''Arquivo utilidades/usuarios.php''' | |
| − | |||
| − | |||
| − | + | ::<syntaxhighlight lang="php" enclose="div"> | |
| − | ::<syntaxhighlight lang="php" enclose="div | ||
<?php | <?php | ||
| − | + | ||
$usuarios = [ | $usuarios = [ | ||
| − | [ | + | [ |
| − | [ | + | 'id' => 1, |
| − | [ | + | 'nome' => 'Usuario 1', |
| + | 'tfno' => '123456789', | ||
| + | 'email' => 'usuario1@example.com', | ||
| + | ], | ||
| + | [ | ||
| + | 'id' => 2, | ||
| + | 'nome' => 'Usuario 2', | ||
| + | 'tfno' => '987654321', | ||
| + | 'email' => 'usuario2@example.com', | ||
| + | ], | ||
| + | [ | ||
| + | 'id' => 3, | ||
| + | 'nome' => 'Usuario 3', | ||
| + | 'tfno' => '555555555', | ||
| + | 'email' => 'usuario3@example.com', | ||
| + | ], | ||
]; | ]; | ||
/** | /** | ||
* Obtén os datos dun usuario a partir do seu ID. | * Obtén os datos dun usuario a partir do seu ID. | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
*/ | */ | ||
| − | function obterUsuarioPorID(int $id): array | + | function obterUsuarioPorID(int $id): ?array |
| + | { | ||
global $usuarios; | global $usuarios; | ||
| − | + | ||
foreach ($usuarios as $usuario) { | foreach ($usuarios as $usuario) { | ||
| − | if ($usuario[ | + | if ($usuario['id'] === $id) { |
return $usuario; | return $usuario; | ||
} | } | ||
} | } | ||
| − | + | ||
| − | return null; | + | return null; |
} | } | ||
| − | |||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | : ''' | + | : '''Arquivo listaUsuarios.php''' |
| − | ::<syntaxhighlight lang="php" enclose="div" | + | |
| + | ::<syntaxhighlight lang="php" enclose="div"> | ||
| + | <?php | ||
| + | |||
| + | require_once __DIR__ . '/utilidades/usuarios.php'; | ||
| + | |||
| + | function escaparHtml(string $valor): string | ||
| + | { | ||
| + | return htmlspecialchars( | ||
| + | $valor, | ||
| + | ENT_QUOTES | ENT_SUBSTITUTE, | ||
| + | 'UTF-8' | ||
| + | ); | ||
| + | } | ||
| + | ?> | ||
<!DOCTYPE html> | <!DOCTYPE html> | ||
| − | <html> | + | <html lang="gl"> |
<head> | <head> | ||
| − | <title>Listado de | + | <meta charset="UTF-8"> |
| + | <title>Listado de usuarios</title> | ||
</head> | </head> | ||
<body> | <body> | ||
| − | |||
| + | <h1>Listado de usuarios</h1> | ||
| + | |||
| + | <ul> | ||
| + | <?php foreach ($usuarios as $usuario): ?> | ||
<?php | <?php | ||
| − | + | $url = 'detalleUsuario.php?' . http_build_query([ | |
| + | 'id' => $usuario['id'], | ||
| + | ]); | ||
| + | ?> | ||
| + | <li> | ||
| + | <a href="<?php echo escaparHtml($url); ?>"> | ||
| + | <?php echo escaparHtml($usuario['nome']); ?> | ||
| + | </a> | ||
| + | </li> | ||
| + | <?php endforeach; ?> | ||
| + | </ul> | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
</body> | </body> | ||
</html> | </html> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | : '''Arquivo detalleUsuario.php''' | ||
| + | |||
| + | ::<syntaxhighlight lang="php" enclose="div"> | ||
| + | <?php | ||
| − | + | require_once __DIR__ . '/utilidades/usuarios.php'; | |
| + | |||
| + | function escaparHtml(string $valor): string | ||
| + | { | ||
| + | return htmlspecialchars( | ||
| + | $valor, | ||
| + | ENT_QUOTES | ENT_SUBSTITUTE, | ||
| + | 'UTF-8' | ||
| + | ); | ||
| + | } | ||
| + | |||
| + | $idRecibido = $_GET['id'] ?? null; | ||
| + | |||
| + | $usuarioSeleccionado = null; | ||
| + | $erro = null; | ||
| + | |||
| + | if ($idRecibido === null) { | ||
| + | $erro = 'Non se indicou ningún identificador.'; | ||
| + | } elseif (!is_string($idRecibido)) { | ||
| + | $erro = 'O identificador non é válido.'; | ||
| + | } else { | ||
| + | $idUsuario = filter_var( | ||
| + | $idRecibido, | ||
| + | FILTER_VALIDATE_INT, | ||
| + | [ | ||
| + | 'options' => [ | ||
| + | 'min_range' => 1, | ||
| + | ], | ||
| + | ] | ||
| + | ); | ||
| + | if ($idUsuario === false) { | ||
| + | $erro = 'O identificador non é válido.'; | ||
| + | } else { | ||
| + | $usuarioSeleccionado = obterUsuarioPorID($idUsuario); | ||
| − | + | if ($usuarioSeleccionado === null) { | |
| − | + | $erro = 'Non existe ningún usuario con ese identificador.'; | |
| + | } | ||
| + | } | ||
| + | } | ||
| + | ?> | ||
<!DOCTYPE html> | <!DOCTYPE html> | ||
| − | <html> | + | <html lang="gl"> |
<head> | <head> | ||
| − | <title>Detalles do | + | <meta charset="UTF-8"> |
| + | <title>Detalles do usuario</title> | ||
</head> | </head> | ||
<body> | <body> | ||
| − | |||
| − | <?php | + | <h1>Detalles do usuario</h1> |
| − | + | ||
| + | <?php if ($erro !== null): ?> | ||
| + | |||
| + | <p><?php echo escaparHtml($erro); ?></p> | ||
| + | |||
| + | <?php else: ?> | ||
| + | |||
| + | <dl> | ||
| + | <dt>Nome</dt> | ||
| + | <dd><?php echo escaparHtml($usuarioSeleccionado['nome']); ?></dd> | ||
| + | |||
| + | <dt>Teléfono</dt> | ||
| + | <dd><?php echo escaparHtml($usuarioSeleccionado['tfno']); ?></dd> | ||
| + | |||
| + | <dt>Correo</dt> | ||
| + | <dd><?php echo escaparHtml($usuarioSeleccionado['email']); ?></dd> | ||
| + | </dl> | ||
| + | |||
| + | <?php endif; ?> | ||
| − | + | <p><a href="listaUsuarios.php">Volver á lista de usuarios</a></p> | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
</body> | </body> | ||
</html> | </html> | ||
| − | |||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | + | <br> -- [[Usuario:angelfg|Ángel D. Fernández González]] -- (2026). | |
| − | <br> -- [[Usuario:angelfg|Ángel D. Fernández González]] -- ( | ||
Revisión del 17:13 13 sep 2026
Sumario
- 1 Introdución
- 2 Método GET
- 3 Método POST
- 4 Comprobando se hai datos
- 5 Validación, normalización e escapado dos campos
- 6 Seguridade no uso dos formularios
- 7 Obtendo variables de formularios (POST/GET) e filtrando ao mesmo tempo
- 8 Redireccionamento de páxinas
- 9 Gardando imaxes no servidor
- 10 Verificando os datos dun formulario
- 11 Creando arrays cos datos dun formulario
- 12 Exercicios propostos
Introdución
- A través da etiqueta
<form>podemos enviar datos dunha páxina a outra.
- Os principais elementos HTML que poden enviar información dentro dun formulario son os elementos asociados aos formularios HTML.
- Dentro da etiqueta
formtemos, entre outros:
- O atributo method, que normalmente toma os valores get ou post.
- O atributo action, que indica a URL que vai procesar os datos enviados polo formulario.
- Se queremos que o formulario se procese na mesma páxina podemos deixar o atributo
actionbaleiro:
1 <form name="form1" action="" method="post"> 2 3 </form>
- Outra posibilidade é empregar
$_SERVER['PHP_SELF']. Neste caso debemos escapar o seu valor antes de introducilo no HTML:
1 <form name="form1" 2 action="<?php echo htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?>" 3 method="post"> 4 5 </form>
- Importante: os datos recibidos do cliente non deben considerarse fiables. Isto é válido tanto para GET como para POST. A validación HTML ou JavaScript mellora a experiencia de usuario, pero a validación de seguridade debe facerse tamén no servidor.
- Nota: Para pasar datos dunha páxina a outra tamén podemos empregar campos ocultos (
hidden). Un campo oculto non é secreto: o usuario pode velo e modificalo, polo que tamén debe validarse no servidor.
Método GET
- Co método GET os datos envíanse como parámetros na URL. Por exemplo:
https://exemplo.test/buscar.php?q=php&orde=asc
- Os datos situados despois do signo
?forman a cadea de consulta ou query string. Os diferentes parámetros sepáranse normalmente mediante&.
- Características do método GET:
- Os parámetros son visibles na URL.
- A URL pode gardarse nos marcadores, copiarse ou compartirse conservando os parámetros.
- Os parámetros poden quedar rexistrados no historial do navegador, nos logs do servidor e noutros sistemas intermedios.
- O usuario pode modificar libremente os valores enviados. Polo tanto, os datos GET sempre deben validarse no servidor.
- Non existe un límite único de tamaño establecido por PHP para unha URL completa; os límites prácticos dependen do navegador, do servidor web, dos proxies e da infraestrutura empregada.
- GET é apropiado principalmente para operacións de consulta que non deberían modificar o estado da aplicación.
- As variables enviadas deste xeito están dispoñibles en PHP mediante o array superglobal $_GET.
- Vexamos un exemplo utilizando un formulario.
Arquivo: UD2_Form_Ex1_eleccion_pelicula.php
1 <!DOCTYPE html> 2 <html lang="gl"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Formulario</title> 6 </head> 7 <body> 8 <form id="frmPelicula" method="get" action="UD2_Form_Ex1_datos_pelicula.php"> 9 <div> 10 <label for="lstPelicula">Película preferida:</label> 11 <select id="lstPelicula" name="lstPelicula"> 12 <option value="ET">E.T. O extraterrestre</option> 13 <option value="Superman">Supermán</option> 14 </select> 15 </div> 16 17 <div> 18 <label for="lstXenero">Xénero preferido:</label> 19 <select id="lstXenero" name="lstXenero"> 20 <option value="Acción">Acción</option> 21 <option value="Ciencia ficción">Ciencia ficción</option> 22 </select> 23 </div> 24 25 <button type="submit">ENVIAR</button> 26 </form> 27 </body> 28 </html>
- Fixarse en que enviamos os datos utilizando o método GET.
Arquivo: UD2_Form_Ex1_datos_pelicula.php
1 <!DOCTYPE html> 2 <html lang="gl"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Datos da película</title> 6 </head> 7 <body> 8 <?php 9 $pelicula = $_GET['lstPelicula'] ?? ''; 10 $xenero = $_GET['lstXenero'] ?? ''; 11 12 $peliculaHtml = htmlspecialchars($pelicula, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); 13 $xeneroHtml = htmlspecialchars($xenero, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); 14 15 echo 'Os datos que escolliches son:<br><br>'; 16 printf('PELÍCULA: %s<br>XÉNERO: %s', $peliculaHtml, $xeneroHtml); 17 ?> 18 </body> 19 </html>
- Nota: O operador
??permite indicar un valor alternativo cando unha clave non existe. Deste xeito evitamos acceder directamente a unha clave inexistente de$_GET.
- Nota: Para visualizar durante o desenvolvemento o conxunto de valores recibidos pódese empregar var_dump():
var_dump($_GET);
Método POST
- Co método POST os datos do formulario envíanse no corpo da petición HTTP e non forman parte da URL.
- Isto non significa que POST sexa secreto nin que os seus datos sexan fiables. Un usuario pode inspeccionar e modificar unha petición POST empregando as ferramentas de desenvolvemento do navegador,
curl, Postman ou outras ferramentas. Para protexer os datos durante o transporte debe empregarse HTTPS.
- O tamaño máximo dos datos recibidos mediante POST está condicionado, entre outras cousas, pola directiva post_max_size de PHP. O seu valor por defecto é 8M, pero pode ser modificado pola configuración do servidor.
- No caso da subida de arquivos tamén intervén upload_max_filesize, cuxo valor por defecto é 2M. Para permitir arquivos de maior tamaño,
post_max_sizedebe ser maior queupload_max_filesize.
- A forma de modificar estas directivas depende de como estea instalado PHP. O arquivo
.htaccesssó permite empregar directivasphp_valuecando PHP funciona como módulo de Apache e a configuración do servidor o permite. Con CGI/FastCGI ou PHP-FPM poden existir outros mecanismos, por exemplo.user.ini, segundo a configuración do servidor.
- As variables enviadas mediante POST están dispoñibles no array superglobal $_POST.
- Modificamos o exemplo anterior para que empregue POST.
Arquivo: UD2_Form_Ex2_eleccion_pelicula.php
1 <!DOCTYPE html> 2 <html lang="gl"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Formulario</title> 6 </head> 7 <body> 8 <form id="frmPelicula" method="post" action="UD2_Form_Ex2_datos_pelicula.php"> 9 <div> 10 <label for="lstPelicula">Película preferida:</label> 11 <select id="lstPelicula" name="lstPelicula"> 12 <option value="ET">E.T. O extraterrestre</option> 13 <option value="Superman">Supermán</option> 14 </select> 15 </div> 16 17 <div> 18 <label for="lstXenero">Xénero preferido:</label> 19 <select id="lstXenero" name="lstXenero"> 20 <option value="Acción">Acción</option> 21 <option value="Ciencia ficción">Ciencia ficción</option> 22 </select> 23 </div> 24 25 <button type="submit">ENVIAR</button> 26 </form> 27 </body> 28 </html>
Arquivo: UD2_Form_Ex2_datos_pelicula.php
1 <!DOCTYPE html> 2 <html lang="gl"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Datos da película</title> 6 </head> 7 <body> 8 <?php 9 $pelicula = $_POST['lstPelicula'] ?? ''; 10 $xenero = $_POST['lstXenero'] ?? ''; 11 12 $peliculaHtml = htmlspecialchars($pelicula, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); 13 $xeneroHtml = htmlspecialchars($xenero, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); 14 15 echo 'Os datos que escolliches son:<br><br>'; 16 printf('PELÍCULA: %s<br>XÉNERO: %s', $peliculaHtml, $xeneroHtml); 17 ?> 18 </body> 19 </html>
- Nota: Durante o desenvolvemento podemos comprobar os datos recibidos con:
var_dump($_POST);
Comprobando se hai datos
- A páxina que recibe un formulario normalmente vai validar os datos e realizar algunha operación con eles. Por exemplo, pode facer unha consulta, gardar información ou modificar datos existentes.
- A validación no cliente mediante HTML ou JavaScript é útil, pero non substitúe a validación no servidor.
Comprobando o método da petición
- Unha forma robusta de saber se unha páxina foi enviada mediante POST é consultar
$_SERVER['REQUEST_METHOD']:
1 if ($_SERVER['REQUEST_METHOD'] !== 'POST') { 2 exit('Non se enviou o formulario mediante POST'); 3 }
- Se queremos saber se chegou un campo concreto podemos utilizar
isset(),array_key_exists()ou o operador??, segundo o caso:
1 $nome = $_POST['txtNome'] ?? ''; 2 3 if ($nome === '') { 4 echo 'O campo nome non ten datos'; 5 }
- Nota: exit e
diefinalizan a execución do script.
Tipo dos datos recibidos
- Os valores simples procedentes dos campos dun formulario chegan normalmente como cadeas de caracteres. Por exemplo, aínda que un
inputconteña o texto25, o valor recibido será unha cadea.
- Non obstante,
$_POSTtamén pode conter arrays se empregamos nomes do tipocampo[]ou estruturas comousuario[nome]. Os arquivos subidos non aparecen en$_POST, senón en$_FILES.
- Por este motivo non debemos facer un cast antes de validar o dato. Primeiro comprobamos que a cadea ten o formato esperado e despois facemos a conversión.
- Exemplo para unha idade formada só por díxitos:
1 $idadeTexto = $_POST['txtIdade'] ?? ''; 2 3 if (is_string($idadeTexto) && ctype_digit($idadeTexto)) { 4 $idade = (int) $idadeTexto; 5 } else { 6 echo 'A idade non é un número enteiro válido'; 7 }
- Tamén podemos empregar filtros de validación, como veremos máis adiante.
Comprobando se chegan datos de varios formularios diferentes á mesma páxina
- Se varios formularios envían datos á mesma páxina é preferible identificar explicitamente a orixe cun campo oculto, en lugar de depender do botón de envío. O botón pode non aparecer na petición nalgúns modos de envío, por exemplo ao premer Intro nun campo.
Formulario da primeira páxina:
<form method="post" action="segunda.php"> <input type="hidden" name="formulario" value="primeira"> ... <button type="submit">Enviar</button> </form>
Formulario da segunda páxina:
<form method="post" action=""> <input type="hidden" name="formulario" value="segunda"> ... <button type="submit">Enviar</button> </form>
Código de segunda.php:
$orixe = $_POST['formulario'] ?? null; if ($orixe === 'primeira') { // Datos enviados dende primeira.php } elseif ($orixe === 'segunda') { // Datos enviados dende o formulario de segunda.php } else { // A páxina cargouse directamente ou chegou unha orixe non válida }
- Importante: un campo oculto tamén pode ser modificado polo usuario. Só serve para identificar a intención do formulario; o seu valor debe comprobarse no servidor.
Validación, normalización e escapado dos campos
- Ao traballar con datos externos convén diferenciar tres operacións:
- Validación: comprobar se un dato cumpre as regras esperadas. Por exemplo, comprobar se unha idade é un enteiro entre 0 e 130.
- Normalización ou saneamento: transformar un dato cando realmente o necesitemos, por exemplo aplicar
trim()para eliminar espazos ao principio e ao final. - Escapado da saída: adaptar un dato ao contexto no que se vai amosar. Para escribir texto dentro de HTML empregaremos normalmente
htmlspecialchars().
- Non debemos asumir que un dato queda seguro para todos os contextos por aplicarlle un filtro xenérico. A protección depende do uso:
- HTML →
htmlspecialchars(). - SQL → consultas preparadas con parámetros.
- URL →
rawurlencode()ouhttp_build_query(). - Números, correos, IP, etc. → validación específica.
- HTML →
No cliente
- HTML permite realizar validacións básicas mediante atributos como:
required: indica que o campo é obrigatorio.maxlength,minlength,minemax: limitan lonxitudes ou rangos.type="email",type="number",type="date", etc.acceptnun campo de tipofile: axuda a indicar ao navegador os tipos de ficheiro que se queren seleccionar.
- Estas comprobacións poden ser evitadas polo usuario. Por iso deben repetirse no servidor as regras que afecten á integridade ou á seguridade da aplicación.
No servidor: filter_var()
- PHP dispón da función filter_var(), que permite validar ou sanear unha variable segundo o filtro indicado.
- Sintaxe simplificada:
filter_var($valor, $filtro, $opcion);
- Parámetros:
$valor: dato que queremos comprobar.$filtro: constanteFILTER_VALIDATE_*ouFILTER_SANITIZE_*.$opcion: opcións ou flags adicionais do filtro.
Filtros para a validación
FILTER_VALIDATE_BOOLEAN: valida un valor booleano.FILTER_VALIDATE_EMAIL: valida unha dirección de correo electrónico.FILTER_VALIDATE_FLOAT: valida un número de punto flotante.FILTER_VALIDATE_INT: valida un número enteiro.FILTER_VALIDATE_IP: valida unha dirección IP.FILTER_VALIDATE_REGEXP: valida mediante unha expresión regular.FILTER_VALIDATE_URL: valida unha URL.
Filtros de saneamento
- Algúns filtros de saneamento que continúan dispoñibles son:
FILTER_SANITIZE_EMAILFILTER_SANITIZE_ENCODEDFILTER_SANITIZE_NUMBER_FLOATFILTER_SANITIZE_NUMBER_INTFILTER_SANITIZE_SPECIAL_CHARSFILTER_SANITIZE_FULL_SPECIAL_CHARSFILTER_SANITIZE_URL
- Importante en PHP 8:
FILTER_SANITIZE_MAGIC_QUOTESfoi eliminado en PHP 8.0 e non debe empregarse.FILTER_SANITIZE_STRINGeFILTER_SANITIZE_STRIPPEDestán obsoletos dende PHP 8.1. Para escapar texto que vai ser mostrado en HTML empregaremos directamentehtmlspecialchars().
- Un filtro de saneamento non substitúe unha validación. Por exemplo, se esperamos un enteiro é preferible rexeitar
12abccomo inválido en lugar de transformalo silenciosamente en12.
Exemplos
Validando
- Validando un número enteiro:
1 $valor = '123'; 2 3 $enteiro = filter_var($valor, FILTER_VALIDATE_INT); 4 5 if ($enteiro === false) { 6 echo 'Valor incorrecto'; 7 } else { 8 echo "Valor correcto: $enteiro"; 9 }
- Validando un número flotante:
1 $valor = '-1.45'; 2 3 $decimal = filter_var($valor, FILTER_VALIDATE_FLOAT); 4 5 if ($decimal === false) { 6 echo 'Valor incorrecto'; 7 } else { 8 echo "Valor correcto: $decimal"; 9 }
- Validando un correo electrónico:
1 $correo = 'usuario@example.com'; 2 3 if (filter_var($correo, FILTER_VALIDATE_EMAIL) === false) { 4 echo 'Correo incorrecto'; 5 } else { 6 echo 'Correo correcto'; 7 }
Escapando texto para HTML
- Se queremos mostrar nunha páxina un texto recibido do usuario, escapámolo no momento da saída:
1 $texto = '<script>alert("ola")</script>'; 2 3 echo htmlspecialchars( 4 $texto, 5 ENT_QUOTES | ENT_SUBSTITUTE, 6 'UTF-8' 7 );
- O navegador mostrará o texto recibido, pero non interpretará as etiquetas como código HTML.
- Importante:
strip_tags()ehtmlspecialchars()non fan o mesmo.strip_tags()elimina etiquetas;htmlspecialchars()codifica os caracteres especiais para que se mostren como texto.
Saneando un número flotante
1 $numero = '53.3pp'; 2 3 $resultado = filter_var( 4 $numero, 5 FILTER_SANITIZE_NUMBER_FLOAT, 6 FILTER_FLAG_ALLOW_FRACTION 7 ); 8 9 var_dump($resultado);
- Neste caso o resultado será a cadea
53.3. Esta operación transforma o dato; non é equivalente a comprobar que o dato orixinal fose un decimal válido.
Establecendo criterios
- Podemos validar un enteiro dentro dun rango:
1 $valor = 25; 2 3 $opcion = [ 4 'options' => [ 5 'min_range' => 10, 6 'max_range' => 100, 7 ], 8 ]; 9 10 $resultado = filter_var($valor, FILTER_VALIDATE_INT, $opcion); 11 12 if ($resultado === false) { 13 echo 'Valor incorrecto'; 14 } else { 15 echo "Valor correcto: $resultado"; 16 }
- Tamén podemos indicar un valor por defecto:
1 $valor = -200; 2 3 $opcion = [ 4 'options' => [ 5 'default' => 3, 6 'min_range' => 10, 7 'max_range' => 100, 8 ], 9 ]; 10 11 $resultado = filter_var($valor, FILTER_VALIDATE_INT, $opcion); 12 echo $resultado;
- Neste caso obteremos
3se o valor non supera a validación.
- Para un número decimal podemos indicar outro separador decimal mediante a opción
decimal:
1 $valor = '1,23'; 2 3 $opcion = [ 4 'options' => [ 5 'decimal' => ',', 6 ], 7 ]; 8 9 $resultado = filter_var($valor, FILTER_VALIDATE_FLOAT, $opcion); 10 11 if ($resultado === false) { 12 echo 'Valor incorrecto'; 13 } else { 14 echo "Valor correcto: $resultado"; 15 }
Seguridade no uso dos formularios
- Os datos dun formulario están controlados polo cliente e poden ser modificados. A aplicación debe validar a entrada e aplicar a protección apropiada no contexto no que vai empregar cada dato.
Inxección SQL
- Unha inxección SQL pode producirse cando unha aplicación constrúe unha consulta concatenando directamente datos controlados polo usuario.
- Exemplo incorrecto:
1 $nome = $_POST['usuario'] ?? ''; 2 $password = $_POST['password'] ?? ''; 3 4 $sql = "SELECT count(*) FROM usuarios 5 WHERE nome='$nome' AND password='$password'";
- O problema non se resolve eliminando comiñas mediante filtros HTML nin empregando
htmlspecialchars(). HTML e SQL son contextos diferentes.
Solución á inxección SQL
- A solución habitual é empregar consultas preparadas con parámetros. Os datos e o código SQL envíanse ao xestor de base de datos de forma separada.
- Exemplo con MySQLi:
1 $stmt = $mysqli->prepare( 2 'SELECT id, nome, password_hash 3 FROM usuarios 4 WHERE nome = ?' 5 ); 6 7 $stmt->bind_param('s', $nome); 8 $stmt->execute(); 9 10 $resultado = $stmt->get_result(); 11 $usuario = $resultado->fetch_assoc();
- Cando traballemos con contrasinais reais non debemos gardalos nin comparalos en texto claro. Empregaremos
password_hash()para gardar o hash epassword_verify()para comprobar o contrasinal.
mysqli_real_escape_string()pode ser útil nalgúns contextos específicos, pero non é a técnica principal que debemos ensinar para construír consultas con datos externos. A opción preferente son as consultas preparadas.
- Un procedemento almacenado tampouco evita por si só unha inxección SQL: dependerá de como estea implementado e de se constrúe SQL dinámico internamente.
Cross-Site Scripting (XSS)
- Un ataque XSS pode producirse cando datos controlados por un usuario se incorporan a unha páxina e o navegador os interpreta como HTML ou JavaScript.
- Por exemplo, imaxinemos que recibimos:
1 <script>alert('Exemplo XSS')</script>
- Se facemos directamente:
echo $_GET['texto'];
- o navegador podería interpretar o contido como marcado ou código.
Solucións ao ataque Cross-Site Scripting (XSS)
- Para mostrar texto dentro de HTML empregaremos normalmente:
$texto = $_GET['texto'] ?? ''; echo htmlspecialchars( $texto, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' );
htmlspecialchars()non elimina o texto: codifica os caracteres con significado especial en HTML para que se mostren como texto.
- O escapado debe realizarse no momento da saída e segundo o contexto. Un valor preparado para HTML non queda automaticamente preparado para SQL, JavaScript, CSS ou unha URL.
strip_tags()pode eliminar etiquetas HTML, pero non debe considerarse unha alternativa xeral ao escapado contextual.
- Se queremos utilizar
$_SERVER['PHP_SELF']nun atributo HTML, debemos escapar o valor:
<form method="post" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?>"> </form>
- Para un formulario que se procesa na mesma páxina tamén podemos empregar simplemente:
<form method="post" action=""> </form>
Ataques CSRF (Cross-Site Request Forgery)
- Os ataques CSRF trataranse cando vexamos as variables de sesión na UD4.
Normas xerais
- Como normas xerais:
- Validaremos no servidor todos os datos procedentes do cliente.
- Nos formularios que modifican o estado da aplicación empregaremos protección contra CSRF.
- Para mostrar texto dentro de HTML empregaremos
htmlspecialchars(..., ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'). - Para SQL empregaremos consultas preparadas con parámetros.
- Para contrasinais empregaremos
password_hash()epassword_verify(). - Non empregaremos filtros HTML para intentar evitar inxección SQL.
- Sanearemos ou normalizaremos datos só cando as regras da aplicación o requiran; non substituiremos a validación por unha transformación silenciosa.
Obtendo variables de formularios (POST/GET) e filtrando ao mesmo tempo
- PHP dispón da función filter_input(), que permite obter unha variable externa e aplicarlle opcionalmente un filtro.
- Sintaxe actual:
filter_input( int $type, string $var_name, int $filter = FILTER_DEFAULT, array|int $options = 0 ): mixed
- Parámetros:
- type:
INPUT_GET,INPUT_POST,INPUT_COOKIE,INPUT_SERVERouINPUT_ENV. - var_name: nome da variable.
- filter: filtro a aplicar.
- options: opcións ou flags.
- type:
- Importante:
FILTER_DEFAULTé un alias deFILTER_UNSAFE_RAW; é dicir, por defecto non se aplica unha limpeza automática.
filter_input()traballa sobre o valor orixinal proporcionado pola SAPI. Se modificamos previamente$_GETou$_POSTdentro do programa e queremos filtrar ese novo valor, empregaremosfilter_var().
- Exemplo validando un identificador enteiro recibido por GET:
1 $id = filter_input( 2 INPUT_GET, 3 'id', 4 FILTER_VALIDATE_INT, 5 [ 6 'options' => [ 7 'min_range' => 1, 8 ], 9 ] 10 ); 11 12 if ($id === null) { 13 echo 'Non se enviou o parámetro id.'; 14 } elseif ($id === false) { 15 echo 'O identificador non é válido.'; 16 } else { 17 echo 'Identificador válido: ' . $id; 18 }
- Exemplo para obter texto e mostralo en HTML:
1 $buscar = filter_input(INPUT_GET, 'buscar', FILTER_UNSAFE_RAW); 2 3 if (!is_string($buscar)) { 4 $buscar = ''; 5 } 6 7 $buscarHtml = htmlspecialchars( 8 $buscar, 9 ENT_QUOTES | ENT_SUBSTITUTE, 10 'UTF-8' 11 ); 12 13 echo "Buscaches: $buscarHtml";
Redireccionamento de páxinas
- Despois de procesar un formulario pode ser necesario redireccionar ao usuario a outra páxina.
- Podemos facelo coa función header():
1 header('Location: /nova_paxina.php'); 2 exit;
- Importante:
- A cabeceira debe enviarse antes de producir saída cara ao cliente. Non debe haber HTML,
echo,printfnin espazos enviados antes da chamada aheader(). - Despois dun redireccionamento é recomendable executar
exit;para impedir que continúe o resto do script.
- A cabeceira debe enviarse antes de producir saída cara ao cliente. Non debe haber HTML,
- Se queremos enviar parámetros GET nunha redirección é preferible construír a cadea mediante
http_build_query():
1 $parametros = http_build_query([ 2 'parametro1' => 'valor 1', 3 'parametro2' => 'valor 2', 4 ]); 5 6 header('Location: /nova_paxina.php?' . $parametros); 7 exit;
- Na páxina de destino poderemos obter os datos mediante
$_GETe validalos.
- Para inspeccionar as cabeceiras HTTP xa non é necesario instalar complementos específicos. Os navegadores actuais dispoñen das ferramentas de desenvolvemento: normalmente podemos abrir a lapela Rede/Network, seleccionar unha petición e revisar as súas cabeceiras.
Gardando imaxes no servidor
- Os formularios permiten subir arquivos ao servidor. Un caso típico é asociar unha imaxe a un produto.
- A subida de arquivos require especial atención á seguridade:
- Non debemos confiar no nome orixinal enviado polo cliente.
- Non debemos confiar en
$_FILES['...']['type'], xa que esa información é proporcionada polo cliente e PHP non a verifica. - Debemos comprobar o código de erro, o tamaño e o tipo real do arquivo.
- É recomendable xerar un nome novo no servidor.
- Para arquivos que non necesiten ser servidos directamente polo servidor web é preferible almacenalos fóra do
DocumentRoot. - Nunca debemos permitir a execución de código nun cartafol de subidas.
Cartafol de destino
- Nun exemplo sinxelo no que Apache serve directamente as imaxes e o
DocumentRooté/var/www/html, poderiamos crear un cartafol específico:
1 cd /var/www/html 2 mkdir IMAXES 3 chown www-data:www-data IMAXES 4 chmod 750 IMAXES
- O usuario e grupo reais dependen da instalación. En Debian/Ubuntu é habitual
www-data; noutras instalacións pode ser diferente.
- Evitaremos permisos
777. O proceso que executa PHP só necesita os permisos necesarios para escribir no directorio.
Páxina do formulario
- Para subir arquivos é obrigatorio utilizar
enctype="multipart/form-data".
1 <!DOCTYPE html> 2 <html lang="gl"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Subida de imaxes</title> 6 </head> 7 <body> 8 9 <form action="GardaCampos.php" method="post" enctype="multipart/form-data"> 10 <label for="fileToUpload">Selecciona unha imaxe:</label> 11 <input 12 type="file" 13 name="fileToUpload" 14 id="fileToUpload" 15 accept=".jpg,.jpeg,.png,.gif" 16 required 17 > 18 19 <button type="submit">Subir imaxe</button> 20 </form> 21 22 </body> 23 </html>
- O atributo
acceptaxuda ao usuario a seleccionar determinados ficheiros, pero non é unha medida de seguridade. O servidor debe comprobar igualmente o arquivo recibido.
Páxina que procesa os datos
- O seguinte exemplo:
- comproba o método da petición;
- comproba o código de erro;
- limita o tamaño;
- verifica que o arquivo procede dunha subida HTTP;
- detecta o MIME real mediante
finfo; - só permite determinados tipos de imaxe;
- xera un nome aleatorio;
- move o arquivo ao directorio definitivo.
1 <?php 2 3 if ($_SERVER['REQUEST_METHOD'] !== 'POST') { 4 exit('Método non permitido.'); 5 } 6 7 if (!isset($_FILES['fileToUpload'])) { 8 exit('Non se recibiu ningún arquivo.'); 9 } 10 11 $arquivo = $_FILES['fileToUpload']; 12 13 if ($arquivo['error'] !== UPLOAD_ERR_OK) { 14 exit('Erro na subida. Código: ' . $arquivo['error']); 15 } 16 17 // Límite propio da aplicación: 2 MiB. 18 $tamanoMaximo = 2 * 1024 * 1024; 19 20 if ($arquivo['size'] > $tamanoMaximo) { 21 exit('O arquivo supera o tamaño máximo permitido.'); 22 } 23 24 if (!is_uploaded_file($arquivo['tmp_name'])) { 25 exit('O arquivo non procede dunha subida HTTP válida.'); 26 } 27 28 // Detectamos o MIME a partir do contido real do arquivo. 29 $finfo = new finfo(FILEINFO_MIME_TYPE); 30 $mime = $finfo->file($arquivo['tmp_name']); 31 32 $tiposPermitidos = [ 33 'image/jpeg' => 'jpg', 34 'image/png' => 'png', 35 'image/gif' => 'gif', 36 ]; 37 38 if ($mime === false || !isset($tiposPermitidos[$mime])) { 39 exit('Tipo de arquivo non permitido.'); 40 } 41 42 $extension = $tiposPermitidos[$mime]; 43 44 // Non empregamos o nome enviado polo cliente como nome definitivo. 45 $nomeSeguro = bin2hex(random_bytes(16)) . '.' . $extension; 46 47 $directorioDestino = __DIR__ . '/IMAXES'; 48 49 if (!is_dir($directorioDestino) || !is_writable($directorioDestino)) { 50 exit('O directorio de destino non existe ou non permite escritura.'); 51 } 52 53 $destino = $directorioDestino . DIRECTORY_SEPARATOR . $nomeSeguro; 54 55 if (!move_uploaded_file($arquivo['tmp_name'], $destino)) { 56 exit('Non foi posible gardar o arquivo.'); 57 } 58 59 echo 'Imaxe gardada correctamente co nome: ' 60 . htmlspecialchars($nomeSeguro, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
- Neste exemplo empregamos a clase finfo coa constante
FILEINFO_MIME_TYPE. Non é necesario chamar afinfo_close(); en PHP 8.5 esa función está obsoleta porque o obxecto se libera automaticamente.
Códigos de erro de subida
- O campo
$_FILES['fileToUpload']['error']pode ter, entre outros, os seguintes valores:
UPLOAD_ERR_OK (0): subida correcta.UPLOAD_ERR_INI_SIZE (1): o arquivo superaupload_max_filesize.UPLOAD_ERR_FORM_SIZE (2): superaMAX_FILE_SIZE, se o formulario enviou ese campo.UPLOAD_ERR_PARTIAL (3): subida parcial.UPLOAD_ERR_NO_FILE (4): non se seleccionou ningún arquivo.UPLOAD_ERR_NO_TMP_DIR (6): non existe o directorio temporal.UPLOAD_ERR_CANT_WRITE (7): PHP non puido escribir no disco.UPLOAD_ERR_EXTENSION (8): unha extensión de PHP detivo a subida.
Funcións útiles
- basename(): devolve a última parte dunha ruta. Pode ser útil para mostrar o nome orixinal, pero non debemos confiar nel como única medida de seguridade nin utilizalo necesariamente como nome final.
- pathinfo(): permite obter partes dun nome de ficheiro. A extensión enviada polo usuario non demostra o tipo real do arquivo.
- getimagesize(): pode obter información dunha imaxe e pode empregarse como comprobación adicional cando a aplicación espera imaxes.
- is_uploaded_file(): comproba que o ficheiro temporal procede dunha subida HTTP.
- move_uploaded_file(): move de forma segura un ficheiro subido ao seu destino.
Limitar o tamaño
- Ademais dos límites de PHP, a aplicación debería establecer o seu propio límite:
$tamanoMaximo = 2 * 1024 * 1024; if ($_FILES['fileToUpload']['size'] > $tamanoMaximo) { exit('O arquivo é demasiado grande.'); }
- HTML permite enviar un campo oculto
MAX_FILE_SIZE:
<form action="GardaCampos.php" method="post" enctype="multipart/form-data"> <input type="hidden" name="MAX_FILE_SIZE" value="2097152"> <input type="file" name="fileToUpload"> <button type="submit">Subir</button> </form>
- Importante: este campo está controlado polo cliente e pode ser modificado. Non substitúe os límites configurados en PHP nin a comprobación do tamaño no servidor.
Configuración en php.ini
- As directivas principais son:
upload_max_filesize = 2M post_max_size = 8M
- Estes son os valores por defecto de PHP 8.5, pero un servidor real pode ter outros valores.
post_max_sizedebe ser maior queupload_max_filesizese queremos permitir a subida dun ficheiro dese tamaño.
- Para saber que arquivo de configuración está cargando PHP en liña de comandos podemos executar:
php --ini
- Nunha instalación Debian/Ubuntu poden existir arquivos diferentes segundo a SAPI empregada, por exemplo:
/etc/php/8.5/apache2/php.ini /etc/php/8.5/fpm/php.ini /etc/php/8.5/cli/php.ini
- Nota: a ruta exacta depende da instalación e da versión instalada.
- Se PHP funciona como módulo de Apache e o servidor o permite, algunhas directivas poden configurarse mediante
.htaccess:
php_value upload_max_filesize 64M php_value post_max_size 70M php_value max_execution_time 300 php_value max_input_time 300
- Se se utiliza CGI/FastCGI ou PHP-FPM,
php_valueen.htaccesspode non funcionar. Dependendo do servidor poden empregarse.user.ini, a configuración do pool de PHP-FPM ou a configuración proporcionada polo servizo de aloxamento.
Verificando os datos dun formulario
- Podemos comprobar varios campos obrigatorios percorrendo un array cos seus nomes.
1 $camposObrigatorios = ['txtNome', 'txtEmail']; 2 3 $erros = []; 4 5 foreach ($camposObrigatorios as $campo) { 6 if (!isset($_POST[$campo]) || !is_string($_POST[$campo]) || trim($_POST[$campo]) === '') { 7 $erros[] = "O campo $campo é obrigatorio."; 8 } 9 } 10 11 if ($erros !== []) { 12 foreach ($erros as $erro) { 13 echo htmlspecialchars($erro, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '<br>'; 14 } 15 }
- Empregamos
trim()porque unha cadea formada só por espazos tampouco debería considerarse un valor válido para estes campos.
- Nota: Non sempre debemos empregar
empty()para validar campos obrigatorios, xa que a cadea"0"considérase baleira porempty()e nalgúns formularios pode ser un valor válido.
Creando arrays cos datos dun formulario
- Nos formularios xerados dinamicamente pode haber un número variable de campos. HTML permite empregar corchetes no atributo
namepara que PHP reciba os valores nun array.
- Imaxinemos que queremos introducir o estado de varios exemplares dun libro e que o número de exemplares non se coñece de antemán.
- En lugar de crear nomes diferentes:
<input type="text" name="txtEstado1"> <input type="text" name="txtEstado2"> <input type="text" name="txtEstado3">
- podemos empregar:
<?php for ($cont = 0; $cont < $numExemplares; $cont++): ?> <input type="text" name="txtEstado[]"> <?php endfor; ?>
- Ao recibir o formulario,
$_POST['txtEstado']será un array.
- Debemos comprobar que realmente recibimos un array antes de percorrelo:
$estados = $_POST['txtEstado'] ?? []; if (!is_array($estados)) { $estados = []; } foreach ($estados as $estado) { if (!is_string($estado)) { continue; } $estado = trim($estado); // Validar $estado segundo as regras da aplicación. }
Exercicios propostos
- Exercicio 1: Crea unha páxina cun formulario e dous campos, nome e idade. O método de envío será POST e o destino será a mesma páxina, sen escribir manualmente o seu nome na URL. O campo nome terá un máximo de vinte caracteres e o campo idade deberá conter exactamente dous díxitos. O formulario terá un botón "Enviar datos". Se algún dato non é válido, amosarase unha mensaxe de erro e volverase amosar o formulario conservando os valores correctos introducidos. Se todos os datos son válidos, amosaranse nunha lista e non se volverá amosar o formulario. Debes diferenciar entre validación da entrada e escapado da saída HTML.
- Exercicio 2: Fai o necesario para xestionar a alta, baixa e baleirado dunha lista de froitas. O nome dunha froita debe validarse no servidor e todos os valores deben escaparse ao seren mostrados en HTML. Se o campo está baleiro ou non cumpre o formato permitido, amosarase un erro. A lista amosarase nunha táboa e ao carón de cada froita haberá un botón para eliminala. Se non hai elementos amosarase "Cesta baleira". Nun arquivo separado
/utilidades/operacionsFroitas.phpgardaranse as funcións necesarias. O arquivo é imprescindible e só debe cargarse unha vez, polo que se empregarárequire_once. Para manter a lista entre peticións neste exercicio pódense empregar campos ocultos, lembrando que os seus valores poden ser modificados polo cliente e deben validarse de novo. Máis adiante substituiremos este mecanismo por sesións.
- Exercicio 3: Crea nun arquivo separado
/utilidades/usuarios.phpun array de usuarios. Cada usuario terá: id, nome, teléfono e correo electrónico. Nese mesmo arquivo define unha funciónobterUsuarioPorID()que reciba un enteiro e devolva o usuario correspondente ounullse non existe. Crea unha páxinalistaUsuarios.phpque amose os nomes como enlaces adetalleUsuario.php. O identificador enviarase mediante GET. Na páxina de detalle debes validar que o ID recibido é un enteiro positivo e escapar os datos antes de amosalos. Proba a modificar manualmente o parámetroidda URL.
Solución Exercicios propostos
Solución Exercicio 1
<?php $nome = ''; $idadeTexto = ''; $idade = null; $erros = []; $formularioEnviado = $_SERVER['REQUEST_METHOD'] === 'POST'; if ($formularioEnviado) { $nomeRecibido = $_POST['nome'] ?? ''; $idadeRecibida = $_POST['idade'] ?? ''; if (!is_string($nomeRecibido)) { $nomeRecibido = ''; } if (!is_string($idadeRecibida)) { $idadeRecibida = ''; } $nome = trim($nomeRecibido); $idadeTexto = trim($idadeRecibida); // Validación do nome. if ($nome === '') { $erros[] = 'O campo nome é obrigatorio.'; } elseif (mb_strlen($nome) > 20) { $erros[] = 'O campo nome non pode ter máis de 20 caracteres.'; } elseif (!preg_match("/^[\p{L}\p{M} .'-]+$/u", $nome)) { $erros[] = 'O nome contén caracteres non permitidos.'; } // Validación da idade: exactamente dous díxitos. if ($idadeTexto === '') { $erros[] = 'O campo idade é obrigatorio.'; } elseif (!preg_match('/^\d{2}$/', $idadeTexto)) { $erros[] = 'A idade debe estar formada exactamente por dous díxitos.'; } else { $idade = (int) $idadeTexto; } } function escaparHtml(string $valor): string { return htmlspecialchars( $valor, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ); } $datosValidos = $formularioEnviado && $erros === []; ?> <!DOCTYPE html> <html lang="gl"> <head> <meta charset="UTF-8"> <title>Formulario</title> </head> <body> <?php if (!$datosValidos): ?> <h1>Formulario</h1> <?php if ($formularioEnviado && $erros !== []): ?> <h2>Erros</h2> <ul> <?php foreach ($erros as $erro): ?> <li><?php echo escaparHtml($erro); ?></li> <?php endforeach; ?> </ul> <?php endif; ?> <form method="post" action=""> <div> <label for="nome">Nome:</label> <input type="text" id="nome" name="nome" maxlength="20" required value="<?php echo escaparHtml($nome); ?>" > </div> <div> <label for="idade">Idade:</label> <input type="text" id="idade" name="idade" inputmode="numeric" maxlength="2" size="3" required value="<?php echo escaparHtml($idadeTexto); ?>" > </div> <button type="submit">Enviar datos</button> </form> <?php else: ?> <h1>Datos enviados</h1> <ul> <li>Nome: <?php echo escaparHtml($nome); ?></li> <li>Idade: <?php echo $idade; ?></li> </ul> <?php endif; ?> </body> </html>
- Fixarse en que o nome non se transforma con filtros HTML antes de validalo. Gardamos o valor lóxico e aplicamos
htmlspecialchars()unicamente cando o inserimos na saída HTML.
Solución Exercicio 2
- Arquivo /utilidades/operacionsFroitas.php
<?php /** * Valida e normaliza o nome dunha froita. * * @param string $nome Nome recibido. * @return string|false Nome normalizado ou false se non é válido. */ function verificarNomeFroita(string $nome): string|false { $nome = trim($nome); if ($nome === '') { return false; } if (mb_strlen($nome) > 40) { return false; } // Permitimos letras Unicode, espazos, apóstrofos e guións. if (!preg_match("/^[\p{L}\p{M} '-]+$/u", $nome)) { return false; } return $nome; } /** * Engade unha froita se aínda non existe. * * @param array $lista Lista actual. * @param string $froita Froita a engadir. * @return array Nova lista. */ function engadirFroita(array $lista, string $froita): array { if (!in_array($froita, $lista, true)) { $lista[] = $froita; } return $lista; } /** * Elimina unha froita da lista. * * @param array $lista Lista actual. * @param string $froita Froita a eliminar. * @return array Nova lista. */ function eliminarFroita(array $lista, string $froita): array { $indice = array_search($froita, $lista, true); if ($indice !== false) { unset($lista[$indice]); $lista = array_values($lista); } return $lista; }
- Arquivo xestionFroitas.php
<?php require_once __DIR__ . '/utilidades/operacionsFroitas.php'; function escaparHtml(string $valor): string { return htmlspecialchars( $valor, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ); } $listaFroitas = []; $erro = null; // Recuperamos a lista dos campos ocultos. // Estes valores veñen do cliente, polo que os validamos novamente. $listaRecibida = $_POST['listaFroitas'] ?? []; if (is_array($listaRecibida)) { foreach ($listaRecibida as $froitaRecibida) { if (!is_string($froitaRecibida)) { continue; } $froitaValida = verificarNomeFroita($froitaRecibida); if ($froitaValida !== false) { $listaFroitas = engadirFroita($listaFroitas, $froitaValida); } } } if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (isset($_POST['btnBaleirar'])) { $listaFroitas = []; } if (isset($_POST['btnEngadir'])) { $novaFroita = $_POST['novaFroita'] ?? ''; if (!is_string($novaFroita)) { $novaFroita = ''; } $novaFroita = verificarNomeFroita($novaFroita); if ($novaFroita === false) { $erro = 'O nome da froita non é válido.'; } else { $listaFroitas = engadirFroita($listaFroitas, $novaFroita); } } if (isset($_POST['btnBorrar']) && is_string($_POST['btnBorrar'])) { $froitaAEliminar = verificarNomeFroita($_POST['btnBorrar']); if ($froitaAEliminar !== false) { $listaFroitas = eliminarFroita($listaFroitas, $froitaAEliminar); } } } ?> <!DOCTYPE html> <html lang="gl"> <head> <meta charset="UTF-8"> <title>Xestión de froitas</title> </head> <body> <h1>Xestión de froitas</h1> <?php if ($erro !== null): ?> <p><?php echo escaparHtml($erro); ?></p> <?php endif; ?> <form method="post" action=""> <?php foreach ($listaFroitas as $clave => $froita): ?> <input type="hidden" name="listaFroitas[<?php echo $clave; ?>]" value="<?php echo escaparHtml($froita); ?>" > <?php endforeach; ?> <?php if ($listaFroitas === []): ?> <p>Cesta baleira.</p> <?php else: ?> <table> <thead> <tr> <th>Froita</th> <th>Acción</th> </tr> </thead> <tbody> <?php foreach ($listaFroitas as $froita): ?> <tr> <td><?php echo escaparHtml($froita); ?></td> <td> <button type="submit" name="btnBorrar" value="<?php echo escaparHtml($froita); ?>" > Eliminar </button> </td> </tr> <?php endforeach; ?> </tbody> </table> <?php endif; ?> <div> <label for="novaFroita">Nome da froita:</label> <input type="text" id="novaFroita" name="novaFroita" maxlength="40"> <button type="submit" name="btnEngadir" value="1">Engadir froita</button> </div> <?php if ($listaFroitas !== []): ?> <button type="submit" name="btnBaleirar" value="1">Baleirar cesta</button> <?php endif; ?> </form> </body> </html>
- Nota: Este exercicio conserva o estado mediante campos ocultos porque as sesións aínda non se estudaron. Os campos ocultos poden ser manipulados polo cliente; por iso a solución volve validar todas as froitas recibidas. Nunha aplicación real, para manter estado interno empregaremos normalmente sesións ou persistencia no servidor.
Solución Exercicio 3
- Arquivo utilidades/usuarios.php
<?php $usuarios = [ [ 'id' => 1, 'nome' => 'Usuario 1', 'tfno' => '123456789', 'email' => 'usuario1@example.com', ], [ 'id' => 2, 'nome' => 'Usuario 2', 'tfno' => '987654321', 'email' => 'usuario2@example.com', ], [ 'id' => 3, 'nome' => 'Usuario 3', 'tfno' => '555555555', 'email' => 'usuario3@example.com', ], ]; /** * Obtén os datos dun usuario a partir do seu ID. */ function obterUsuarioPorID(int $id): ?array { global $usuarios; foreach ($usuarios as $usuario) { if ($usuario['id'] === $id) { return $usuario; } } return null; }
- Arquivo listaUsuarios.php
<?php require_once __DIR__ . '/utilidades/usuarios.php'; function escaparHtml(string $valor): string { return htmlspecialchars( $valor, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ); } ?> <!DOCTYPE html> <html lang="gl"> <head> <meta charset="UTF-8"> <title>Listado de usuarios</title> </head> <body> <h1>Listado de usuarios</h1> <ul> <?php foreach ($usuarios as $usuario): ?> <?php $url = 'detalleUsuario.php?' . http_build_query([ 'id' => $usuario['id'], ]); ?> <li> <a href="<?php echo escaparHtml($url); ?>"> <?php echo escaparHtml($usuario['nome']); ?> </a> </li> <?php endforeach; ?> </ul> </body> </html>
- Arquivo detalleUsuario.php
<?php require_once __DIR__ . '/utilidades/usuarios.php'; function escaparHtml(string $valor): string { return htmlspecialchars( $valor, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ); } $idRecibido = $_GET['id'] ?? null; $usuarioSeleccionado = null; $erro = null; if ($idRecibido === null) { $erro = 'Non se indicou ningún identificador.'; } elseif (!is_string($idRecibido)) { $erro = 'O identificador non é válido.'; } else { $idUsuario = filter_var( $idRecibido, FILTER_VALIDATE_INT, [ 'options' => [ 'min_range' => 1, ], ] ); if ($idUsuario === false) { $erro = 'O identificador non é válido.'; } else { $usuarioSeleccionado = obterUsuarioPorID($idUsuario); if ($usuarioSeleccionado === null) { $erro = 'Non existe ningún usuario con ese identificador.'; } } } ?> <!DOCTYPE html> <html lang="gl"> <head> <meta charset="UTF-8"> <title>Detalles do usuario</title> </head> <body> <h1>Detalles do usuario</h1> <?php if ($erro !== null): ?> <p><?php echo escaparHtml($erro); ?></p> <?php else: ?> <dl> <dt>Nome</dt> <dd><?php echo escaparHtml($usuarioSeleccionado['nome']); ?></dd> <dt>Teléfono</dt> <dd><?php echo escaparHtml($usuarioSeleccionado['tfno']); ?></dd> <dt>Correo</dt> <dd><?php echo escaparHtml($usuarioSeleccionado['email']); ?></dd> </dl> <?php endif; ?> <p><a href="listaUsuarios.php">Volver á lista de usuarios</a></p> </body> </html>
-- Ángel D. Fernández González -- (2026).