Sei sulla pagina 1di 12

Validar y sanear datos en PHP

Tutorial en el que mostramos y enseamos los mtodos ms efectivos a la hora de validar y sanear datos en PHP. Todo programador debera conocer este tipo de tcnicas que permitirn mantener la integridad y seguridad de sus aplicaciones no os lo perdis!

Introduccin: la importancia de validar y sanear datos


Un punto crtico de cualquier tipo de aplicacin es la entrada de datos por parte del usuario, ya que, si no se toman las medidas oportunas, un usuario puede (intencionadamente o no) causar fallos en la aplicacin. En el mundo del desarrollo web, esto cobra mucha ms importancia, pues se incrementa mucho el nmero de usuarios potenciales y por tanto, el nmero de usuarios malintencionados. Ataques conocidos y fciles de llevar a cabo son SQL Inyection y XSS (Cross Site Scripting). En aplicaciones web es importante realizar siempre la validacin en el lado del servidor. Otra vulnerabilidad que tiene que ver con la validacin, que es debido muchas veces a la inexperiencia del desarrollador, es confiar la validacin de lados al lado de cliente (Javascript). Esto es algo grave, pues existen herramientas para enviar datos a una web sin que intervenga para nada un navegador ni Javascript.

La extensin Filter
Esta extensin de PHP nos expone un conjunto de funciones que nos permitirn la validacin y saneamiento de datos de una forma sencilla. La funcion filter_var nos permite filtrar una variable segn el filtro especificado. El orden de los parmetros es el siguiente:

$var: Variable que se quiere filtrar $filter: Filtro que se desea aplicar. Ser una constante numrica $options: Conjunto de opciones que modificarn el funcionamiento del filtro. Ser una constante numrica o un array

A continuacin un listado de filtros de validacin posibles: FILTER_VALIDATE_BOOLEAN Valida la variable como un booleano. FILTER_VALIDATE_EMAIL Valida la variable como una direccin de correo electrnico correcta. FILTER_VALIDATE_FLOAT Valida que la variable sea del tipo float. FILTER_VALIDATE_INT Valida la variable como un nmero entero.

FILTER_VALIDATE_IP Valida la variable como una direccin IP. FILTER_VALIDATE_REGEXP Valida la variable contra una expresin regular enviada en la variable de opciones. FILTER_VALIDATE_URL Valida el valor coma una URL de acuerdo con la RFC 2396. A continuacin el listado de filtros para sanear: FILTER_SANITIZE_EMAIL Elimina todos los caracteres execpto letras, nmeros y !#$%&*+-/=?^_`{|}~@.[]. FILTER_SANITIZE_ENCODED Codifica la cadena como una URL vlida. FILTER_SANITIZE_MAGIC_QUOTES Aplica la funcin addslashes. FILTER_SANITIZE_NUMBER_FLOAT Elimina todos los caracteres excepto nmeros, +- y opcionalmente ,.eE. FILTER_SANITIZE_NUMBER_INT Elimina todos los caracteres excepto nmeros y los signos + -. FILTER_SANITIZE_SPECIAL_CHARS Escapa caracteres HTML y caracteres con ASCII menor a 32. FILTER_SANITIZE_STRING Elimina etiquetas, opcionalmente elimina o codifica caracteres especiales. FILTER_SANITIZE_STRIPPED Alias del filtro anterior. FILTER_SANITIZE_URL Elimina todos los caracteres excepto nmeros, letras y $-_.+!*(),{}|\\^~[]`<>#%;/?:@&=.

Validar y sanear un nmero entero


Vamos a ver un sencillo ejemplo de validacin de un nmero entero.
view plaincopy to clipboardprint?

1. <?php 2. $var = 123; 3. echo filter_var($var, FILTER_VALIDATE_INT);

La funcin nos devolver el entero en caso de que la validacin sea correcta, en caso contrario, nos devolver FALSE.

view plaincopy to clipboardprint?

1. 2. 3. 4. 5. 6. 7.

<?php $var = 'dos'; if(filter_var($var, FILTER_VALIDATE_INT) === false){ echo 'Valor incorrecto'; }else{ echo 'Valor correcto'; }

A la hora de validar un entero, podemos jugar con las opciones para hacer ajustar el comportamiento de la funcin a nuestras necesidades. En el siguiente ejemplo se muestra como validar un entero que est dentro de un rango especfico:
view plaincopy to clipboardprint?

1. 2. 3. 4. 5. 6. 7.

$options = array('min_range'=>10, 'max_range' => 20)); if(filter_var($var, FILTER_VALIDATE_INT, $options) === false){ echo 'Valor incorrecto'; }else{ echo 'Valor correcto'; }

Ahora vamos a ver como sanear un entero, para los que no entiendan, sanear significa limpiar, es decir, quitamos todo lo que no tiene cabida en nuestro campo.
view plaincopy to clipboardprint?

1. <?php 2. $var = 'uno23'; 3. 4. echo filter_var($var, FILTER_SANITIZE_NUMBER_INT);

El cdigo anterior nos devuelve 23 debido a que elimina todos los caracteres no numricos de la cadena.

Validar y sanear un nmero float


La validacin simple de un float es practicamente igual que la de un entero:
view plaincopy to clipboardprint?

1. <php 2. $var = 1.3; 3. 4. echo filter_var($var, FILTER_VALIDATE_FLOAT);

Sin embargo, con este filtro tenemos una opcion interesante que nos permite especificar cual es el caracter que separa los decimales.
view plaincopy to clipboardprint?

1. 2. 3. 4.

<php $var = '1,3'; $options = array('options'=>array('decimal'=>',')); echo filter_var($var, FILTER_VALIDATE_FLOAT, $options);

Saneamiento de textos
La limpieza de los textos es algo muy importante y por ello tenemos varios flags que modifican el comporamiento del filtro FILTER_SANITIZE_STRING: FILTER_FLAG_NO_ENCODE_QUOTES No codificar las comillas simples ni dobles. FILTER_FLAG_STRIP_LOW Elimina caracteres cuyo varlor ASCII sea menor a 32. FILTER_FLAG_STRIP_HIGH Elimina caracteres cuyo valor ASCII sea mayor a 127. FILTER_FLAG_ENCODE_LOW Codifica caracteres cuyo valor ASCII sea mennor a 32. FILTER_FLAG_ENCODE_HIGH Codifica caracteres cuyo valor ASCII sea mayor a 127. FILTER_FLAG_ENCODE_AMP Codifica ampersands (&). A continuacin algunos ejemplos:
view plaincopy to clipboardprint?

1. 2. 3. 4.

<php $text = '<p>"Hola mundo!"</p>'; echo filter_var($text, FILTER_SANITIZE_STRING); //Resultado: &#34;Hola mundo!&#34;

view plaincopy to clipboardprint?

1. 2. 3. 4.

<php $text = '<p>"Hola mundo!"</p>'; echo filter_var($text, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES); //Resultado: "Hola mundo!"

view plaincopy to clipboardprint?

1. <php 2. $text = '"Ontuts & Cokidoo"'; 3. 4. echo filter_var($text, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES | FILTER_FLAG_ ENCODE_AMP); 5. //Resultado: "Ontuts &amp; Cokidoo"

Obtener variables externas


Adems de la funcin filter_var, existe una muy interesante llamada filter_input. Esta funcion nos permite obtener una variable externa por su nombre, filtrndola si es necesario.

Es importante que todas las variables externas, es decir, que nos llegan desde el lado del cliente ($_GET, $_POST, $_COOKIE) las obtengamos a travs de esta funcin para ahorrarnos disgustos. En el siguiente ejemplo vemos como obtener un parmetro GET de la peticin con esta funcin:
view plaincopy to clipboardprint?

1. 2. 3. 4. 5. 6. 7. 8.

<php $page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT); if($page !== null && $page !== false){ //El parametro ha sido enviado y es un entero }else{ //El parametro no se ha enviado o no es un entero }

Esta funcin nos devolver NULL si la variable no ha sido enviada, FALSE si la variable ha sido enviada pero no validada o el valor de la variable si ha sido enviada y validada. El primer parmetro indica de que mbito ser extrada la variable, y puede ser uno de los siguientes valores:

INPUT_GET INPUT_POST INPUT_COOKIE INPUT_SERVER INPUT_ENV

Otra funcin relacionada y muy interesante es filter_input_array que nos permite definir una serie de validaciones para mltiples campos. Es decir, podemos hacer la validacin todos los campos de un formulario en un solo paso. La definicin de los campos con sus validaciones se hace mediante un array como se muestra en el siguiente ejemplo:
view plaincopy to clipboardprint?

1. <?php 2. $args = array( 3. 'product_id' => FILTER_SANITIZE_ENCODED, 4. 'component' => array('filter' => FILTER_VALIDATE_INT, 5. 'flags' => FILTER_REQUIRE_ARRAY, 6. 'options' => array('min_range' => 1, 'max_range' => 10) 7. ), 8. 'versions' => FILTER_SANITIZE_ENCODED 9. ); 10. 11. $myinputs = filter_input_array(INPUT_POST, $args);

Esta funcin devuelve un array del tipo clave/valor que contiene los nombres de los campos junto con su filtrado.

Conclusin
Como puedes observar, PHP nos brinda una gran librera con la cual podemos llevar a cabo validaciones y saneamientos en cuestin de segundos, cosa que antes se haca bastante engorrosa.

Ahora no hay excusa que valga para asegurarnos de que nuestros datos estn bien limpios y seguros. Creando una capa de conexin abstracta a base de datos con PHP

Introduccin
Hasta hace muy poco, usaba una librera de PHP llamada ADOdb Lite para abstraerme un poco de la base de datos en mis proyectos. En realidad, no era por ser vago y no querer desarrolar mi propio cdigo, si no que la comenc a usar porque siempre uso procedimientos y funciones de MySQL en mis modelos y las funciones de la librera nativa de PHP no me permitan hacer llamadas a estos (no se ahora, antes no). Desde hace unos pocos aos la empec a utilizar y desde entonces la carpeta adodb_lite se iba copiando de proyecto en proyecto casi de manera inconsciente. Hasta que un da me encontr con la librera Mysqli (MySQL Improved Extension) que es una librera nativa de PHP que nos permite hacer muchas ms cosas, entre otras, ejecutar procedimientos de la base de datos . Mediante la librera nativa de PHP Mysqli podemos ejecutar procedimientos definidos previamente en nuestra base de datos. Fue en ese momento cuando decid borrar la librera AdoDB Lite para crear mi propio cdigo de comunicacin con la base de datos. La verdad, que en esto, se puede hacer desde la cosa ms sencilla (lo que vamos a ver en este tutorial) hasta cosas realmente complejas como doctrine. Supongo que es cuestin de gustos y del tipo proyectoa m personalmente me gustan las cosas sencillas y potentes, no me importa escribir un poco ms de cdigo mientras tenga control total con la aplicacin, vosotros que prefers?. Bueno pues vamos a ello!

Qu vamos a hacer?
Antes de nada os explicar un poco qu caractersticas va a tener nuestra capa de comunicacin:

Abstraccin del tipo de proveedor: esto es algo bsico, nunca sabes cundo puedes cambiar de tipo de servidor (base de datos). A m ya me ha pasado en un proyecto tener que cambiar de PostgresSQL a MySQL. Por desgracia fue en mi primer proyecto y todava no saba estas cosas, por lo tanto, a cambiar cdigo por todos lados. Instancia nica: nuestro cdigo ser construdo usando el patrn de diseo Singleton, lo que nos garantizar que slo se establece una conexin con la base de datos, para qu ms?. Autocargar en array: si ejecutamos consultas de extraccin de filas, nuestra capa nos devolver dicha informacin cargada en un array. Parmetros en consultas: se gestionarn de forma fcil los parmetros que se envan en las consultas.

Paso 1: Crear la estructura de nuestro proveedor


Como ya he comentado anteriormente, nuestra capa ser abstracta al tipo de proveedor de bases de datos. Para cumplir con esto, necesitamos crear una clase base que defina qu es un proveedor y qu funciones va a poder llevar a cabo, luego, cada tipo de proveedor que necesitemos, se extender de dicha clase. Lo vemos con este ejemplo:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13.

abstract class DatabaseProvider{ /*Definicion de que mtodos y propiedades van a tener todos los proveedores*/ } class MySqlProvider extends DatabaseProvider { /*Implementacin de los mtodos definidos en la clase base*/ } class SQLServerProvider extends DatabaseProvider { /*Implementacin de los mtodos definidos en la clase base*/ }

No hace falta entender mucho de POO para entederlo, no?. Simplemente se define una clase abstracta para definir lo que todos sus descendientes deben de implementar, as nos aseguramos de que todos los proveedores que hagamos tengan la misma estructura. De modo que si hacemos algo como $ob->foo(), tanto nos d si $ob es de la clase MysqlProvider o SQLServerProvider, sabemos que dicho mtodo tiene que existir y que en teora debera hacer lo mismo en un motor u otro de base de datos. Al crear nuestra propia capa de comunicacin con la base de datos, no importa cul sea el motor de base de datos: MySQL, PostgreSQL, Oracle no tendremos que rescribir todo el cdigo creado anteriormente, ser abstracta a nuestro motor. Pues bien, una vez entendida la teora, vamos a definir realmente nuestra clase base para los proveedores, yo he includo el siguiente esquema, que es el bsico: ejecucin de consultas y control de errores, vosotros lgicamente lo podris ampliar cuanto queris.
1. abstract class DatabaseProvider 2. { 3. //Guarda internamente el objeto de conexin 4. protected $resource; 5. //Se conecta segn los datos especificados 6. public abstract function connect($host, $user, $pass, $dbname); 7. //Obtiene el nmero del error 8. public abstract function getErrorNo(); 9. //Obtiene el texto del error 10. public abstract function getError(); 11. //Enva una consulta 12. public abstract function query($q); 13. //Convierte en array la fila actual y mueve el cursor 14. public abstract function fetchArray($resource); 15. //Comprueba si est conectado 16. public abstract function isConnected(); 17. //Escapa los parmetros para prevenir inyeccin 18. public abstract function escape($var); 19. }

Como podis ver, los mtodos son todos abstractos para que se implementen en sus clases hijas.

Paso 2: Crear nuestro proveedor


Una vez creada la base, tenemos que crear el proveedor. Para ello tenemos que heredar de la clase anterior e implementar los mtodos:

1. class MySqlProvider extends DatabaseProvider 2. { 3. public function connect($host, $user, $pass, $dbname){ 4. $this->resource = new mysqli($host, $user, $pass, $dbname); 5. return $this->resource; 6. } 7. public function getErrorNo(){ 8. return mysqli_errno($this->resource); 9. } 10. public function getError(){ 11. return mysqli_error($this->resource); 12. } 13. public function query($q){ 14. return mysqli_query($this->resource,$q); 15. } 16. public function fetchArray($result){ 17. return mysqli_fetch_array($result); 18. } 19. public function isConnected(){ 20. return !is_null($this->resource); 21. } 22. public function escape($var){ 23. return mysqli_real_escape_string($this->resource,$var); 24. } 25. }

Me he enfocado en el proveedor de MySQL, que supongo que es el ms usado. Como podis ver, todas las funciones definidas en nuestra clase base, tienen su correspondiente funcin en la librera Mysqli, por lo que se puede decir que funciona a modo de wrapper.

Paso 3: Crear nuestra capa de comunicacin


Lo hecho hasta ahora est muy bien, pero lo nico que nos permite es abstraer el tipo de proveedor, el resto de puntos que he comentado al inicio todava estn si cumplir. Esto es porque an nos falta por crear una nueva clase que se encargar de enviar las consultas y gestionar las conexiones y respuestas del servidor. Os pongo la estructura de la clase para que le echis un ojo:
1. class DatabaseLayer 2. { 3. //Almacena internamente el proveedor 4. private $provider; 5. //Usado para las callbacks, se explica luego 6. private $params; 7. //Almacena la instancia para el Singleton 8. private static $_con; 9. //Constructor privado 10. private function __construct($provider){} 11. //Funcion del Singleton que devuelve o crea la instancia 12. public static function getConnection($provider){} 13. //Funcion callback, se explica luego 14. private function replaceParams($coincidencias){} 15. //Se encarga de poner los parmetros en su sitio 16. private function prepare($sql, $params){} 17. //Envia la consulta al servidor 18. private function sendQuery($q, $params){} 19. //Ejecuta una consulta, extrayendo solo la primera columna de la primera fila 20. public function executeScalar($q, $params=null){} 21. //Ejecuta una consulta y devuelve un array con las filas 22. public function execute($q, $params=null){} 23. }

Antes de continuar, es importante ver cul ser el formato de las consultas. Para que el cdigo permanezca limpio y poder escapar los parmetros (para prevenir inyecciones maliciosas), los parmetros sern especificados en un array, de forma que sern insertados en la consulta reemplazando al caracter ?. Veamos un ejemplo:
1. $bd->execute("SELECT * FROM foo WHERE id=?", array(5)) 2. $bd>executeScalar("SELECT id FROM foo WHERE id=? AND name like '%?%'"), array(5,"ivan"));

Ahora vamos por pasos, rellenendo ls mtodos con la funcionalidad requerida.


Patrn singleton

La implementacin de este patrn nos asegura que solo un objeto de dicha clase es creado. Para ello tenemos que poner el constructor como privado y llamarlo desde una funcin esttica, en este caso getConnection. En el constructor creamos una conexin a la base de datos y se comprueba si se ha establecido.
1. private function __construct($provider){ 2. if(!class_exists($provider)){ 3. throw new Exception("El proveedor especificado no ha sido implentado o aadido."); 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. } $this->provider = new $provider; $this->provider>connect("localhost","usuarioBaseDatos", "tuPassword", "tuBaseDatos"); if(!$this->provider->isConnected()){ /*Controlar error de conexion*/ } } public static function getConnection($provider){ if(self::$_con){ return self::$_con; } else{ $class = __CLASS__; self::$_con = new $class($provider); return self::$_con; } }

sendQuery

Este mtodo se encarga de enviar las consultas y comprobar errores:


1. private function sendQuery($q, $params){ 2. $query = $this->prepare($q, $params); 3. $result = $this->provider->query($query); 4. if($this->provider->getErrorNo()){ 5. /*Controlar errores*/ 6. } 7. return $result; 8. }

prepare

Este mtodo privado es importante, ya que es el que se encarga de limpiar los parmetros para prevenir errores tpicos e insertarlos en su sitio correspondiente. Para reemplazar se usa una expresin regular mediante la funcion preg_replace_callback(), que nos permite ejecutar una funcion para cada coincidencia encontrada. En este caso la callback es replaceParams (la cual definiremos a continuacin).
1. private function prepare($sql, $params){ 2. for($i=0;$i<sizeof($params); $i++){ 3. if(is_bool($params[$i])){ 4. $params[$i] = $params[$i]? 1:0; 5. } 6. elseif(is_double($params[$i])) 7. $params[$i] = str_replace(',', '.', $params[$i]); 8. elseif(is_numeric($params[$i])) 9. $params[$i] = $this->provider->escape($params[$i]); 10. elseif(is_null($params[$i])) 11. $params[$i] = "NULL"; 12. else 13. $params[$i] = "'".$this->provider->escape($params[$i])."'"; 14. } 15. 16. $this->params = $params; 17. $q = preg_replace_callback("/(\?)/i", array($this,"replaceParams"), $sql); 18. 19. return $q; 20. }

replaceParams

Esta es la funcin llamada por preg_replace_callback() y que se encarga de devolver el parmetro especificado para cada interrogante (?) encontrado en la consulta. Para hacer esto se usa la funcin next() que va moviendo el cursor del array para cada coincidencia.
1. private function replaceParams($coincidencias){ 2. $b=current($this->params); 3. next($this->params); 4. return $b; 5. }

execute y executeScalar

Estas son las funciones que vamos a usar pblicamente para realizar consultas y son muy parecidas. Tan solo se diferencian en que la primera recorre el resultado cargndolo en un array, mientras que la segunda simplemente devuelve la primera columna de la primera fila:
1. public function executeScalar($q, $params=null){ 2. $result = $this->sendQuery($q, $params); 3. if(!is_null($result)){ 4. if(!is_object($result)){ 5. return $result; 6. } 7. else{ 8. $row = $this->provider->fetchArray($result); 9. return $row[0]; 10. }

11. } 12. return null; 13. } 14. public function execute($q, $params=null){ 15. $result = $this->sendQuery($q, $params); 16. if(is_object($result)){ 17. $arr = array(); 18. while($row = $this->provider->fetchArray($result)){ 19. $arr[] = $row; 20. } 21. return $arr; 22. } 23. return null; 24. 25. }

Paso 4: Probarlo
Una vez hecho todo lo anterior la forma de uso es muy sencilla:
1. 2. 3. 4. //Como parmetro va el nombre del proveedor que queris cargar $db = DatabaseLayer::getConnection("MySqlProvider"); //Imprimira la estructura del array print_r($db>execute("SELECT id,email FROM users WHERE name like ? LIMIT 20",array("ontuts%"))); 5. //Imprime un valor nmerico 6. echo($db->executeScalar("SELECT count(*) FROM users WHERE active=?",array(true)));

Reflexin final
Antes de despedirme recordaros que os podis descargar el cdigo completo en la zona superior de la pgina. Como siempre, espero que la informacin compartida en este tutorial os resulte til, con el que pretendo demostraros que no hay que tener miedo a crear tus propias libreras, siempre y cuando sepis lo que ests haciendo. No olvidis dejar un comentario para cualquier duda, fallo o valoracin. Un saludo y hasta el prximo tutorial!

Imprimir sin botones javascript


Para imprimir la pagina sin que aparezca el boton imprimir, no es necesario abrir otra ventana, puede estar el boton en la misma ventana, pero yo hago lo siguiente: mando a llamar una funcion en JavaScript y en esa funcion esta el truco: <input name=btnImprimir id=btnImprimir type=button value=Imprimir onClick=imprime()> Y el codigo de la funcion imprime() es: function imprime(){ //desaparece el boton document.getElementById(btnImprimir).style.display=none //se imprime la pagina

window.print() //reaparece el boton document.getElementById(btnImprimir).style.display=inline }

Ocultar y mostrar capas ( div , table ) javascript


Funciona tanto para un div como para un table. hay que poner id=flotante style=display:none; o en el div o en table, a m personalmente me gusta que cada id, tenga un nombre diferente. El ejemplo est puesto para flotante y flotante2. <script> //codigo en javascript para ocultar y mostrar capas function mostrardiv() { div = document.getElementById(flotante); div.style.display = ; div2 = document.getElementById(flotante2); div2.style.display = ; } function ocultardiv() { div = document.getElementById(flotante); div.style.display=none; div2 = document.getElementById(flotante2); div2.style.display=none; } </script>

Luego creamos los botones para mostra u ocultar <table border=0> <tr> <td> <input type=button value=Mostrar onclick=mostrardiv() /> <input type=button value=Ocultar onclick=ocultardiv() /> </td> </tr> </table>

Potrebbero piacerti anche