Diferencia entre revisiones de «PHP Paso de datos con formularios»
Ir a la navegación
Ir a la búsqueda
| (No se muestran 49 ediciones intermedias del mismo usuario) | |||
| 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 segura 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'] ?? ''; | ||
| − | + | if ($nome === '') { | |
| + | echo 'O campo nome non ten datos'; | ||
| + | } | ||
| + | </syntaxhighlight> | ||
| − | ::< | + | * <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 ( | + | $idade = (int) $idadeTexto; |
| + | } else { | ||
| + | echo 'A idade non é un número enteiro válido'; | ||
| + | } | ||
</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. | |
| − | ::<syntaxhighlight lang="php" | + | '''Formulario da primeira páxina:''' |
| − | + | ||
| − | + | ::<syntaxhighlight lang="html"> | |
| − | } | + | <form method="post" action="terceira.php"> |
| − | else { | + | <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="terceira.php"> | ||
| + | <input type="hidden" name="formulario" value="segunda"> | ||
| + | ... | ||
| + | <button type="submit">Enviar</button> | ||
| + | </form> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | '''Código de terceira.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> | </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 === |
| − | * | + | :* <code>FILTER_VALIDATE_BOOLEAN</code>: valida un valor booleano. |
| − | : | + | :* <code>FILTER_VALIDATE_EMAIL</code>: valida unha dirección de correo electrónico. |
| + | :* <code>FILTER_VALIDATE_FLOAT</code>: valida un número de punto flotante. | ||
| + | :* <code>FILTER_VALIDATE_INT</code>: valida un número enteiro. | ||
| + | :* <code>FILTER_VALIDATE_IP</code>: valida unha dirección IP. | ||
| + | :* <code>FILTER_VALIDATE_REGEXP</code>: valida mediante unha expresión regular. | ||
| + | :* <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 === | |
| + | ==== Validando ==== | ||
| − | + | * Validando un número enteiro: | |
| − | : | + | ::<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> | ||
| − | + | * Validando un número flotante: | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | : | ||
| − | : | + | ::<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'; | |
| − | ::<syntaxhighlight lang="php" line enclose="div | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | if (filter_var($correo, FILTER_VALIDATE_EMAIL) === false) { | |
| − | + | echo 'Correo incorrecto'; | |
| − | + | } else { | |
| − | + | echo 'Correo correcto'; | |
| − | + | } | |
| − | + | </syntaxhighlight> | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | </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"> | |
| − | ::<syntaxhighlight lang="php" line enclose="div | + | $texto = '<script>alert("ola")</script>'; |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | </ | ||
| − | |||
| + | echo htmlspecialchars( | ||
| + | $texto, | ||
| + | ENT_QUOTES | ENT_SUBSTITUTE, | ||
| + | 'UTF-8' | ||
| + | ); | ||
| + | </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"> | |
| − | ::<syntaxhighlight lang="php" line enclose="div | + | $numero = '53.3pp'; |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| + | $resultado = filter_var( | ||
| + | $numero, | ||
| + | FILTER_SANITIZE_NUMBER_FLOAT, | ||
| + | FILTER_FLAG_ALLOW_FRACTION | ||
| + | ); | ||
| − | + | 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 === | === Establecendo criterios === | ||
| − | * Podemos | + | * Podemos validar un enteiro dentro dun rango: |
| + | |||
| + | ::<syntaxhighlight lang="php" line enclose="div"> | ||
| + | $valor = 25; | ||
| − | + | $opcion = [ | |
| − | + | 'options' => [ | |
| − | |||
| − | |||
| − | |||
| − | |||
'min_range' => 10, | 'min_range' => 10, | ||
| − | 'max_range' => 100 | + | 'max_range' => 100, |
| − | + | ], | |
| − | + | ]; | |
| − | + | ||
| − | + | $resultado = filter_var($valor, FILTER_VALIDATE_INT, $opcion); | |
| − | + | ||
| − | + | if ($resultado === false) { | |
| − | + | echo 'Valor incorrecto'; | |
| − | + | } else { | |
| − | </syntaxhighlight> | + | echo "Valor correcto: $resultado"; |
| − | : | + | } |
| + | </syntaxhighlight> | ||
| + | |||
| + | * Tamén podemos indicar un valor por defecto: | ||
| + | ::<syntaxhighlight lang="php" line enclose="div"> | ||
| + | $valor = -200; | ||
| − | + | $opcion = [ | |
| − | + | 'options' => [ | |
| − | + | 'default' => 3, | |
| − | |||
| − | |||
| − | |||
| − | 'default' => 3, | ||
'min_range' => 10, | 'min_range' => 10, | ||
| − | 'max_range' => 100 | + | 'max_range' => 100, |
| − | + | ], | |
| − | + | ]; | |
| − | |||
| − | |||
| − | + | $resultado = filter_var($valor, FILTER_VALIDATE_INT, $opcion); | |
| − | </syntaxhighlight> | + | 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 | + | ==== Solución á inxección SQL ==== |
| − | * A | + | * 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 = $pdo->prepare( | |
| − | : | + | 'SELECT id, nome, password_hash |
| + | FROM usuarios | ||
| + | WHERE nome = :nome' | ||
| + | ); | ||
| − | + | $stmt->execute([ | |
| + | 'nome' => $nome | ||
| + | ]); | ||
| + | $usuario = $stmt->fetch(PDO::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) === | === 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="html" line enclose="div"> | |
| − | + | <script>alert('Exemplo XSS')</script> | |
| + | </syntaxhighlight> | ||
| + | : Se facemos directamente: | ||
| − | + | ::<syntaxhighlight lang="php"> | |
| − | ::<syntaxhighlight lang=" | + | echo $_GET['texto']; |
| − | + | </syntaxhighlight> | |
| − | </syntaxhighlight> | ||
| − | : | + | : 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: | |
| − | : | + | ::<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. |
| + | : Os parámetros empregados teñen o seguinte significado: | ||
| + | :* '''$texto''': cadea que queremos converter para poder amosala de forma segura dentro de HTML. | ||
| + | :* '''ENT_QUOTES''': indica que, ademais dos caracteres especiais de HTML, se deben converter tanto as comiñas dobres (") como as comiñas simples ('). | ||
| + | :* '''ENT_SUBSTITUTE''': emprégase para que en caso de enviar caracteres non válidos UTF-8 os sustitúa polo carácter �. | ||
| + | : Isto é especialmente importante cando o dato vai ser empregado dentro dun atributo HTML. | ||
| − | + | : <u>Visualmente non notaredes nada no navegador, pero se mirades o código fonte de páxina podedes comprobar como cambia as comillas e outros caracteres por códigos.</u> | |
| − | * | + | * 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) === |
| + | * 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>[https://www.php.net/manual/es/function.password-hash.php password_hash()]</code> e <code>[https://www.php.net/manual/es/function.password-verify.php 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> | </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); // Por defecto todos os datos que veñen en post e get son cadeas. Non ten sentido filtrar por 'cadeas' |
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | if (!is_string($buscar)) { // Con isto aseguramos que sexa unha cadea e non un array, por exemplo.... | |
| + | $buscar = ''; | ||
| + | } | ||
| − | + | $buscarHtml = htmlspecialchars( | |
| + | $buscar, | ||
| + | ENT_QUOTES | ENT_SUBSTITUTE, | ||
| + | 'UTF-8' | ||
| + | ); | ||
| + | echo "Buscaches: $buscarHtml"; | ||
| + | </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'); | ||
| + | exit; | ||
| + | </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 temos dúas opcións: | ||
| + | |||
| + | :* 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> | ||
| + | : Esta é a opción recomendable <u>se temos varios parámetros a enviar</u>. Automaticamente xa fai o url_encode. | ||
| − | |||
| − | ::<syntaxhighlight lang="php" line enclose="div | + | :* Empregar urlencode: |
| − | + | ::<syntaxhighlight lang="php" line enclose="div"> | |
| − | header('Location | + | header( |
| + | 'Location: /nova_paxina.php?parametro=' . | ||
| + | urlencode('Valor a enviar') | ||
| + | ); | ||
exit; | exit; | ||
| − | + | </syntaxhighlight> | |
| − | </ | + | |
| + | |||
| + | |||
| + | '''IMPORTANTE:''' Lembrar poñer <u>exit</u> xa que o script continúa a execución se non está posto. | ||
| + | |||
| − | |||
| − | |||
| + | * 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. | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | ||
| + | <br /> | ||
== Gardando imaxes no servidor == | == Gardando imaxes no servidor == | ||
| − | * | + | * '''NOTA:''' Este punto non vai entrar no exame, pero é convinte que o coñezades para poder subir un arquivo ao servidor web... |
| − | : | + | : Polo tanto facede o exemplo que vai a continuación. |
| − | * | + | * 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 === |
| − | + | ||
| − | ::<syntaxhighlight lang=" | + | * 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"> | ||
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"> | ||
| + | <!DOCTYPE html> | ||
| + | <html lang="gl"> | ||
| + | <head> | ||
| + | <meta charset="UTF-8"> | ||
| + | <title>Subida de imaxes</title> | ||
| + | </head> | ||
| + | <body> | ||
| + | |||
| + | <form action="GardaCampos.php" method="post" enctype="multipart/form-data"> | ||
| + | <label for="fileToUpload">Selecciona unha imaxe:</label> | ||
| + | <input | ||
| + | type="file" | ||
| + | name="fileToUpload" | ||
| + | id="fileToUpload" | ||
| + | accept=".jpg,.jpeg,.png,.gif" | ||
| + | required | ||
| + | > | ||
| + | |||
| + | <button type="submit">Subir imaxe</button> | ||
| + | </form> | ||
| + | |||
| + | </body> | ||
| + | </html> | ||
| + | </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']); | |
| − | + | } | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | : | + | // 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"> | ||
<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=" | + | <input type="file" name="fileToUpload"> |
| − | <input type=" | + | <button type="submit">Subir</button> |
</form> | </form> | ||
| + | </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"> | ||
| + | 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 post_max_size 70M | ||
| + | php_value max_execution_time 300 | ||
| + | php_value max_input_time 300 | ||
| + | </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 == | ||
| + | |||
| + | * Podemos comprobar varios campos obrigatorios percorrendo un array cos seus nomes. | ||
| + | |||
| + | ::<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]) === '') { | ||
| + | $erros[] = "O campo $campo é obrigatorio."; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | if ($erros !== []) { | ||
| + | foreach ($erros as $erro) { | ||
| + | echo htmlspecialchars($erro, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '<br>'; | ||
| + | } | ||
| + | } | ||
| + | </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"> | ||
| + | <input type="text" name="txtEstado2"> | ||
| + | <input type="text" name="txtEstado3"> | ||
| + | </syntaxhighlight> | ||
| − | </ | + | : podemos empregar: |
| − | </ | + | |
| − | </syntaxhighlight> | + | ::<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 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 nome será obrigatorio e terá unha lonxitude máxima de 20 caracteres. A idade deberá ser un número enteiro de dous díxitos. Todos os datos recibidos deberán validarse no servidor. Ao amosar datos introducidos polo usuario na páxina HTML deberán escaparse correctamente para evitar ataques XSS. 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=" | + | ::<syntaxhighlight lang="php" enclose="div"> |
<?php | <?php | ||
| − | |||
| − | + | $nome = ''; | |
| − | if( | + | $idadeTexto = ''; |
| − | $ | + | $idade = null; |
| − | $ | + | $erros = []; |
| + | $formularioEnviado = $_SERVER['REQUEST_METHOD'] === 'POST'; | ||
| + | |||
| + | if ($formularioEnviado) { | ||
| + | $nomeRecibido = $_POST['nome'] ?? ''; | ||
| + | $idadeRecibida = $_POST['idade'] ?? ''; | ||
| + | |||
| + | // Comprobamos que os datos recibidos sexan cadeas. | ||
| + | // Un usuario podería manipular a petición e enviar un array. | ||
| + | if (!is_string($nomeRecibido)) { | ||
| + | $nomeRecibido = ''; | ||
| + | } | ||
| + | |||
| + | if (!is_string($idadeRecibida)) { | ||
| + | $idadeRecibida = ''; | ||
| + | } | ||
| + | |||
| + | $nome = trim($nomeRecibido); | ||
| + | $idadeTexto = trim($idadeRecibida); | ||
| − | + | /* | |
| − | + | * Validación do nome. | |
| − | + | * | |
| − | if($ | + | * PHP non dispón dun FILTER_VALIDATE específico para nomes de persoa. |
| − | + | * Neste exemplo comprobamos unicamente as regras da nosa aplicación: | |
| − | + | * que exista e que non supere os 20 caracteres. | |
| − | } | + | */ |
| − | + | if ($nome === '') { | |
| − | + | $erros[] = 'O campo nome é obrigatorio.'; | |
| + | } elseif (mb_strlen($nome) > 20) { | ||
| + | $erros[] = 'O campo nome non pode ter máis de 20 caracteres.'; | ||
} | } | ||
| − | if ($ | + | /* |
| − | + | * Validación da idade. | |
| + | * | ||
| + | * Empregamos FILTER_VALIDATE_INT para comprobar que realmente | ||
| + | * representa un número enteiro. | ||
| + | * | ||
| + | * Ademais, limitamos o valor entre 10 e 99 porque o exercicio | ||
| + | * indica que a idade debe ter dous díxitos. | ||
| + | */ | ||
| + | $idadeValidada = filter_var( | ||
| + | $idadeTexto, | ||
| + | FILTER_VALIDATE_INT, | ||
| + | [ | ||
| + | 'options' => [ | ||
| + | 'min_range' => 10, | ||
| + | 'max_range' => 99 | ||
| + | ] | ||
| + | ] | ||
| + | ); | ||
| + | |||
| + | if ($idadeTexto === '') { | ||
| + | $erros[] = 'O campo idade é obrigatorio.'; | ||
| + | } elseif ($idadeValidada === false) { | ||
| + | $erros[] = 'A idade debe ser un número enteiro de dous díxitos.'; | ||
} else { | } else { | ||
| − | + | $idade = $idadeValidada; | |
| − | |||
| − | |||
| − | |||
| − | |||
} | } | ||
} | } | ||
| + | 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> |
| − | ::<syntaxhighlight lang=" | + | </html> |
| + | </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"> | ||
<?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)) { // => Non tedes por que saber expresións regulares que se ven noutro módulo. | ||
| + | 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; | ||
| + | } | ||
| + | </syntaxhighlight> | ||
| + | : '''Arquivo xestionFroitas.php''' | ||
| − | + | ::<syntaxhighlight lang="php" enclose="div"> | |
| − | ::<syntaxhighlight lang=" | ||
<?php | <?php | ||
| − | if($ | + | |
| − | && $ | + | 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> | |
| − | + | </syntaxhighlight> | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | </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 /> | <br /> | ||
| − | |||
| − | + | ==== Solución Exercicio 3 ==== | |
| − | ::<syntaxhighlight lang="php" | + | |
| − | <?php | + | : '''Arquivo utilidades/usuarios.php''' |
| − | / | + | |
| − | $ | + | ::<syntaxhighlight lang="php" enclose="div"> |
| + | <?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; | ||
| + | } | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | : '''Arquivo listaUsuarios.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' | ||
| + | ); | ||
} | } | ||
| + | ?> | ||
| + | <!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> | ||
</syntaxhighlight> | </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> | ||
| + | <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> | ||
| + | </syntaxhighlight> | ||
| − | <br> -- [[Usuario:angelfg|Ángel D. Fernández González]] -- ( | + | <br> -- [[Usuario:angelfg|Ángel D. Fernández González]] -- (2026). |
Revisión actual del 07:40 6 oct 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 segura 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="terceira.php"> <input type="hidden" name="formulario" value="primeira"> ... <button type="submit">Enviar</button> </form>
Formulario da segunda páxina:
<form method="post" action="terceira.php"> <input type="hidden" name="formulario" value="segunda"> ... <button type="submit">Enviar</button> </form>
Código de terceira.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 = $pdo->prepare( 2 'SELECT id, nome, password_hash 3 FROM usuarios 4 WHERE nome = :nome' 5 ); 6 7 $stmt->execute([ 8 'nome' => $nome 9 ]); 10 11 $usuario = $stmt->fetch(PDO::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.
- Os parámetros empregados teñen o seguinte significado:
- $texto: cadea que queremos converter para poder amosala de forma segura dentro de HTML.
- ENT_QUOTES: indica que, ademais dos caracteres especiais de HTML, se deben converter tanto as comiñas dobres (") como as comiñas simples (').
- ENT_SUBSTITUTE: emprégase para que en caso de enviar caracteres non válidos UTF-8 os sustitúa polo carácter �.
- Isto é especialmente importante cando o dato vai ser empregado dentro dun atributo HTML.
- Visualmente non notaredes nada no navegador, pero se mirades o código fonte de páxina podedes comprobar como cambia as comillas e outros caracteres por códigos.
- 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); // Por defecto todos os datos que veñen en post e get son cadeas. Non ten sentido filtrar por 'cadeas' 2 3 if (!is_string($buscar)) { // Con isto aseguramos que sexa unha cadea e non un array, por exemplo.... 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 temos dúas opcións:
- Construír a cadea mediante
http_build_query():
- Construír a cadea mediante
1 $parametros = http_build_query([ 2 'parametro1' => 'valor 1', 3 'parametro2' => 'valor 2', 4 ]); 5 6 header('Location: /nova_paxina.php?' . $parametros); 7 exit;
- Esta é a opción recomendable se temos varios parámetros a enviar. Automaticamente xa fai o url_encode.
- Empregar urlencode:
1 header( 2 'Location: /nova_paxina.php?parametro=' . 3 urlencode('Valor a enviar') 4 ); 5 exit;
IMPORTANTE: Lembrar poñer exit xa que o script continúa a execución se non está posto.
- 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
- NOTA: Este punto non vai entrar no exame, pero é convinte que o coñezades para poder subir un arquivo ao servidor web...
- Polo tanto facede o exemplo que vai a continuación.
- 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 nome será obrigatorio e terá unha lonxitude máxima de 20 caracteres. A idade deberá ser un número enteiro de dous díxitos. Todos os datos recibidos deberán validarse no servidor. Ao amosar datos introducidos polo usuario na páxina HTML deberán escaparse correctamente para evitar ataques XSS. 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'] ?? ''; // Comprobamos que os datos recibidos sexan cadeas. // Un usuario podería manipular a petición e enviar un array. if (!is_string($nomeRecibido)) { $nomeRecibido = ''; } if (!is_string($idadeRecibida)) { $idadeRecibida = ''; } $nome = trim($nomeRecibido); $idadeTexto = trim($idadeRecibida); /* * Validación do nome. * * PHP non dispón dun FILTER_VALIDATE específico para nomes de persoa. * Neste exemplo comprobamos unicamente as regras da nosa aplicación: * que exista e que non supere os 20 caracteres. */ if ($nome === '') { $erros[] = 'O campo nome é obrigatorio.'; } elseif (mb_strlen($nome) > 20) { $erros[] = 'O campo nome non pode ter máis de 20 caracteres.'; } /* * Validación da idade. * * Empregamos FILTER_VALIDATE_INT para comprobar que realmente * representa un número enteiro. * * Ademais, limitamos o valor entre 10 e 99 porque o exercicio * indica que a idade debe ter dous díxitos. */ $idadeValidada = filter_var( $idadeTexto, FILTER_VALIDATE_INT, [ 'options' => [ 'min_range' => 10, 'max_range' => 99 ] ] ); if ($idadeTexto === '') { $erros[] = 'O campo idade é obrigatorio.'; } elseif ($idadeValidada === false) { $erros[] = 'A idade debe ser un número enteiro de dous díxitos.'; } else { $idade = $idadeValidada; } } 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)) { // => Non tedes por que saber expresións regulares que se ven noutro módulo. 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).