viernes, 30 de mayo de 2014

Leer ficheros, contar el número de líneas y procesarlas

¿Os gusta la lectura? A mí me encanta, uno de mis géneros preferidos es la ciencia ficción. Pero uno no siempre puede leer lo que quiere, y en ocasiones hay que leer cosas que no son tan entretenidas como una buena novela. Por ejemplo, programando, a veces tenemos que leer un fichero de texto línea a línea. (Vale, es un chiste muuuuyyyyy malo, pero al menos es corto ;-)

Bueno, ahora en serio, lo de leer el fichero lo podemos hacer como mínimo de dos formas:

1) Si no nos interesa todo el contenido del fichero, sino que únicamente estamos buscando las líneas que cumplan una determinada condición, podemos ir leyendo línea a línea y procesar esas líneas individualmente. Para ello, abrimos el fichero (fopen) y en un bucle vamos leyendo cada línea (fgets). Esto nos da control sobre cada línea individualmente, y no consume demasiada memoria, ya que una vez procesada la línea la podemos descartar (también la podemos almacenar si nos interesa, claro).

2) Si queremos todo el contenido del fichero de golpe, por ejemplo, en un array de líneas, con PHP podemos usar la función "file" que nos devuelve precisamente eso. Lógicamente, esta segunda opción ocupará más memoria, lo cual puede ser importante si el fichero leído tiene un tamaño grande, aunque me imagino que posiblemente sea una forma más óptima en tiempo, leerlo todo de un golpe. Ya sabéis, el eterno dilema de memoria frente al tiempo. Si después queremos analizar esas líneas leídas y almacenadas en el array, tendremos que hacer un bucle para recorrerlas.

Bien, ¿y qué técnica me conviene? Pues como siempre, depende de lo que quieras hacer. En un primer caso, para ilustrar ambas técnicas, mi objetivo va a ser contar el número de líneas del archivo, sin procesar cada línea individualmente. Os dejo aquí dos funciones en PHP (contar1 y contar2) que ilustran ambos métodos, aunque fácilmente se pueden traducir a cualquier otro lenguaje.

Según lo explicado en el caso 1)

 1 //-------------------------------------------------------------
 2 function contar1($filename) {
 3     $file = fopen($filename, "r");
 4     $num_lineas = 0;
 5     while (!feof($file)) {
 6         if ($line = fgets($file)){
 7            $num_lineas++;
 8         }
 9     }
10     fclose($file);
11     return $num_lineas;
12 }

Y para el caso 2), la función "count" nos da el número de elementos de un array, lo que en este caso coincide con el número de líneas del fichero

 1 function contar2($filename) {
 2     $num_lineas = count(file($filename));
 3     return $num_lineas;
 4 }



Rendimiento de cada opción

Para analizar el rendimiento de ambas opciones, he hecho pruebas contando el número de líneas con un grupo de unos 2.000 ficheros aproximadamente que tengo en un directorio, una aplicación web. He limitado la lectura a ficheros de código fuente en Javascript, PHP, CSS y HTML, con una longitud media de 316 líneas por fichero, aunque esta medida tiene una varianza alta, pues el fichero más largo tiene 30.000 líneas (no es un caso frecuente), y el fichero más pequeño está vacío (cero líneas).

Tras ocho o diez ejecuciones, contando las líneas por el método 1) tardo alrededor de 1.50 segundos, mientras que el método 2) está sobre los 1.25 segundos, es decir, un 17% más rápido. No he hecho pruebas de consumo de memoria RAM, pero supongo que cuando se cargó el fichero de 30.000 líneas en memoria (método 2), mi ordenador debió emitir una pequeña queja, aunque creo que nada preocupante para los estándares de memoria que puede tener un ordenador hoy en día. Claro, esto puede ser muy diferente si esta aplicación la va a ejecutar un único usuario o la van a utilizar varios usuarios a la vez sobre el mismo servidor web.

En mi caso, la diferencia entre ambas técnicas no es significativa, y sin embargo el método 1) me da mucha más flexibilidad para un objetivo que tengo en mente, pues me gustaría hacer algo de procesamiento con cada línea conforme las voy leyendo.

Pero esta entrada ya me está quedando demasiado larga, así que ya hablaré de ello en otra ocasión, y también pondré el código utilizado para el test, con las explicaciones pertinentes.

¿Y tú, sueles enfrentarte al problema de procesar ficheros línea a línea habitualmente? ¿Utilizas alguna de las técnicas de las que hablo aquí o tienes otra forma de hacerlo?

Referencias
La función file es quizás la más interesante de las aquí mencionadas.

lunes, 19 de mayo de 2014

María Jesús y su acordeón (o cómo aprovechar mejor la interfaz de usuario)


Habréis observado que muchas aplicaciones y páginas web, en particular en su versión para teléfonos móviles, organizan la información de una forma muy útil, que consiste en agrupar secciones de información bajo un título, de manera que cada uno de esos bloques se puede expandir o contraer. Eso permite aprovechar mejor la interfaz de usuario y agiliza la organización de la información en pantalla. Este tipo de interfaz se denomina "acordeón", por el hecho de que cada sección se puede expandir y contraer como el cuerpo de dicho instrumento.

Fig. 1. Las secciones del acordeón contienen info "en su interior"

Al desplegar la sección de Historia

Fig. 2.Al desplegar una sección, el icono cambia

En la documentación de jQuery sobre los "acordeones" [http://jqueryui.com/accordion/]se describe en detalle cómo utilizar este tipo de interfaz, pero aquí os dejo una brevísima introducción para que en un minuto podáis tener algo así funcionando con tres sencillos pasos:

1) Como siempre, crear una página e incluir en ella la referencia a jQuery

 1 <head>
 2 ...
 3     <link rel="stylesheet" href="//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css">
 4     <script src="//code.jquery.com/jquery-1.10.2.js"></script>
 5     <script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
 6 ...
 7 </head>

2) Crear una división (DIV) que contendrá todas las secciones del acordeón. Cada sección ha de ir precedida por una etiqueta de cabecera (H1, H2... H6). Si hacéis pruebas, veréis que da igual usar H1 que H2 que H3... e incluso se pueden mezclar indistintamente, que jQuery las usará como títulos de las secciones.

 1 <body>
 2 ...
 3 <div id="bloque_acordeon">
 4   <h1>Sección 1. Mis personas preferidas</h1>
 5     <div>
 6     <p>
 7     Esto está dentro del bloque1
 8         <ul>
 9             <li>Claudia Chífer</li>
10             <li>Mariano Gandhi</li>
11             <li>Jason Voorhees</li>
12         </ul>
13     </div>
14 
15   <h1>Sección 2. Mis animales preferidos</h1>
16     <div>
17     <p>
18     Esto está dentro del bloque2
19         <ul>
20             <li>Lassie</li>
21             <li>El pollo a la brasa</li>
22             <li>Claudia Chífer</li>
23         </ul>
24     </div>
25     
26   <h1>Sección 3. Mis plantas preferidas</h1>
27     <div>
28     Esto está dentro del bloque3
29         <ul>
30             <li>El algarrobo mediterráneo</li>
31             <li>María</li>
32             <li>Claudia Chífer</li>
33         </ul>   
34     </div>
35     
36 ...
37 </body>

y 3) Incluir el código jQuery que hace que esta sección funcione como un acordeón. Una simple llamada a una función

 1   <script>
 2   $(function() {
 3     $("#bloque_acordeon" ).accordion();  
 4   });
 5   </script>


Hay un detalle que a mí me gusta, y es el hecho de que la primera sección se pueda cerrar sin tener que abrir ninguna otra. Para esto, basta con añadir dentro de la función "accordion" la propiedad "collapsible: true", quedando así el paso 3:

 1   <script>
 2   $(function() {
 3     $("#bloque_acordeon" ).accordion(
 4         { collapsible: true }
 5     );
 6   });
 7   </script>

Pues aquí está todo junto en esta plantilla que podéis adaptar a vuestras necesidades:

 1 <html>
 2 <head>
 3     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 4     <title>Ejemplo de un acordeón con jQuery</title>
 5     <link rel="stylesheet" href="//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css">
 6     <script src="//code.jquery.com/jquery-1.10.2.js"></script>
 7     <script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
 8   <script>
 9   $(function() {
10     $("#bloque_acordeon" ).accordion(
11         { collapsible: true }
12     );
13   });
14   </script>
15   
16 </head>
17 
18 <body>
19 Organizar la interfaz de usuario con un "acordeón"
20 <div id="bloque_acordeon">
21     <h1>Mis personas favoritas</h1>
22     <div>
23         Esto está dentro del bloque1
24         <ul>
25             <li>Claudia Chífer</li>
26             <li>Mariano Gandhi</li>
27             <li>Jason Voorhees</li>
28         </ul>
29     </div>
30     
31     <h1>Mis animales favoritos</h1>
32     <div>
33         Esto está dentro del bloque2
34         <ul>
35             <li>Lassie</li>
36             <li>El pollo a la brasa</li>
37             <li>Claudia Chífer</li>
38         </ul>
39     </div>
40     
41         <h1>Mis plantas favoritas</h1>
42     </span>
43     <div id="contenido_bloque3" style='display: none'>
44         Esto está dentro del bloque3
45         <ul>
46             <li>El algarrobo mediterráneo</li>
47             <li>María</li>
48             <li>Claudia Chífer</li>
49         </ul>       
50     </div>
51 </div>
52  
53 </body>
54 </html>

martes, 8 de abril de 2014

Formatear números en PHP para un informe

Para mostrar los números con el separador de miles, el separador de la coma decimal y dos decimales (dinero en euros).

La función para esto se llama number_format.

Uso:


 1 $numero = 1002002.365;
 2 number_format($numero, 2, ",", ".");
 3 //devuelve 1.002.002,37 

El ejemplo está sacado directamente de desarrolloweb.com, donde hay una explicación mucho más detallada muy bien expuesta, para quien quiera conocer más detalles.

lunes, 7 de abril de 2014

Medir el tiempo de carga de una página en PHP

Insertar este código en la página:

 1 //al principio
 2 
 3 $hora_inicio = microtime(true);
 4 
 5 //... hacer cosas ...
 6 
 7 //al final
 8 
 9 $hora_fin = microtime(true);
10 $tiempo_total = round($hora_fin - $hora_inicio, 4);
11 echo "<div class=\"tiempocarga\">Página construida en " . $tiempo_total . " segundos </div>\n";

Como veréis, en el CSS he definido una clase para mostrar este mensaje con un tipo de letra más pequeño de lo habitual, pero vamos, esto va a gusto del consumidor

 1 .tiempocarga {
 2     font-family: Arial, Helvetica, sans-serif;
 3     color: #606680;
 4     font-size: 8pt;
 5     text-align: center
 6 }



viernes, 21 de marzo de 2014

Limitar el acceso a una página web por IP mediante el fichero .htaccess

En la última entrada, os proponía como ejercicio cómo limitar el acceso a una aplicación web mediante el uso del fichero .htaccess (propio de Apache y otros servidores web imitadores) a determinadas direcciones IP. Bueno, por si alguno no lo ha sacado, es tan sencillo como añadir al fichero .htaccess unas líneas tal que así:

… otras cosas…

order deny,allow
deny from all
allow from 10.15.xx.yy
allow from 10.15.xx.zz
…
…


Si uno intenta acceder desde una dirección que no se encuentra en la lista, obtendrá un mensaje similar a este: 



Otra cosa que también puede interesar a veces es tener un subdirectorio, dentro del directorio de la aplicación web, en el que queramos tener información (ficheros de datos, configuración, imágenes…) pero no queramos permitir que nadie acceda a esos ficheros a través del navegador (accederemos nosotros mediante código). En esos casos, es suficiente con poner ahí un fichero .htaccess con una única línea:

Deny from all

En este caso, el mensaje sería algo similar a



Si os fijáis, aunque el número del error es el mismo (403), los mensajes de descripción son ligeramente diferentes. ¿Alguien sabe por qué?

En estos dos casos, el fichero .htaccess no necesita ir acompañado del fichero de usuarios / claves (que hemos llamado htpasswd durante estas explicaciones, aunque este no ha de ser necesariamente su nombre real)

jueves, 13 de marzo de 2014

Limitar el acceso a una página web mediante usuario y clave

Como ya dije en una entrada anterior, una forma de incrementar un poco la seguridad de nuestras aplicaciones web consiste en limitar el acceso a la página por diversos medios. En aquella entrada explicaba cómo restringir con PHP el conjunto de direcciones IP a las que permitimos la conexión, cosa que puede venir particularmente bien en entornos controlados (corporativos, nuestra red doméstica, académica…).

Otra posibilidad muy rápida de poner en marcha consiste en utilizar una pareja que se lleva muy bien entre sí: los ficheros HTACCESS y un fichero de usuarios / claves (HTPASSWD). Estos ficheros se popularizaron con el servidor web Apache, pero hay otros servidores web que también los utilizan, según he leído (yo sólo los he probado con Apache). El nombre del fichero .htaccess comienza por un punto (herencia de cómo se identificaban los ficheros ocultos en los primeros sistemas UNIX).

Aunque su contenido puede ser muy variado, yo me voy a limitar a explicar un uso básico. (Más info sobre este fichero en donde siempre: http://es.wikipedia.org/wiki/Htaccess)

En mi caso, los voy a utilizar tanto para autenticación como para autorización. Para ello, crearemos dos ficheros, de nombres .htaccess y un fichero de claves que puede llamarse como queramos, y al que me referiré como HTPASSWD (obsérvese el punto inicial en el nombre del primero). El nombre del segundo lo elegiremos a voluntad.

El fichero .htaccess se puede crear con cualquier editor de texto, y contendrá algo similar a:

 1 AuthName "Usuario de MiAplicación"
 2 AuthType Basic
 3 AuthUserFile "C:\tmp\miapp.users"
 4 require valid-user

La línea 3 indica cuál será el fichero de claves, con la lista de los usuarios permitidos. En este caso, los usuarios estarán en el fichero “miapp.users”, el cual podemos crear con la herramienta htpasswd.exe (en la carpeta bin dentro del directorio de instalación de Apache), mediante el comando:

htpasswd.exe -c <ruta>miapp.users <usuario>

Y a continuación teclear la clave que queremos para ese usuario.

Si queremos añadir usuarios adicionales, simplemente repetiremos el comando sin la opción “-c”


Cómo funciona

Cuando el usuario intente acceder a nuestra página, le aparecerá un mensaje para que introduzca el usuario y la contraseña. En el navegador Opera se ve así (otros navegadores mostrarán pantallas equivalentes):


Fijaos que la palabra con tilde se muestra mal. Esto se debe a que mi editor de texto codifica el fichero de texto con codificación UTF-8 y ya no sé si el problema está en cómo sirve el Apache ese mensaje (¿puede que esté configurado para servir las páginas en ANSI?), o en cómo lo visualizan los navegadores. Me inclino más por lo primero, ya que ese fallo de la tilde me ocurre en TODOS los navegadores en los que lo he probado (y he probado con Opera, IE, Firefox y Epiphany), pero como tampoco quiero extenderme más, aquí lo dejo por hoy.



Ejercicio adicional: El fichero .htaccess también se puede utilizar para limitar las IPs desde las que permitir las conexiones a nuestra página web. ¿Cómo se hace esto?

lunes, 3 de marzo de 2014

Cabecera fija en una página web con CSS

1. Las cabeceras fijas en aplicaciones web y de escritorio

Muchas aplicaciones web presentan una serie de botones, titulares y menús que por diversos motivos han de estar siempre a la vista del usuario, aunque este haga scroll para mostrar contenidos que estén más abajo. Esta parte fija se suele dejar como una cabecera al principio de la página o una barra de enlaces o botones a la izquierda (en algunos casos, como Blogger, también a la derecha, o como Gmail, tanto en la cabecera como a la izquierda). Eso permite moverse por los datos manteniendo los botones para las acciones más habituales al alcance de la vista todo el rato. Como se entenderá mejor con una imagen, os muestro cómo se ve esto en el caso de Gmail:

Imagen 1. Cabeceras y barras de menú laterales fijas en aplicaciones web

Muchas otras aplicaciones tanto web como de escritorio también hacen cosas parecidas. Por ejemplo, en Twitter (al menos en su versión para Android) también ocurre así, aunque en este caso no se trate de una aplicación web, sino de escritorio). Aplicaciones de escritorio como Excel y Calc también permiten dejar fijas unas celdas de la hoja para que estén siempre visibles, arriba y/o a la izquierda.

El caso es que me interesaba hacer algo así para una página web que estoy haciendo y, ya que estoy intentando utilizar el CSS de una manera un poco más sistemática de lo que lo he venido haciendo hasta ahora, quería hacerlo con esta técnica.

2. Planteamiento

En mi caso, quería un diseño de página donde se quedara fija una cabecera siempre visible que tuviera tres partes:

- A la izquierda, espacio para un logo
- En el centro, tres líneas de texto informativo al usuario
- A la derecha, uno o varios iconos con botones y/o enlaces

Vamos, algo parecido a esto:

Imagen 2. Qué queremos conseguir

La solución: secciones (div) en la página

La solución a la que he llegado ha consistido en definir 4 bloques o divisiones (<div>) en la página (pagina1.php):

1) Una división para el rectángulo que contiene a los otros tres elementos
2) Una división para el logotipo
3) Una división para el texto informativo (en este caso, para las 3 líneas)
4) Una división para los botones de acción

Más o menos, algo así:

 1 <?php
 2 $modname = "PAGINA1"; 
 3 $modversion = "E31"; 
 4 $moddescri = "Descripción de la página 1";
 5 $linea1 = "<h1>" . $modname . " - versión " . $modversion . "</h1>";
 6 $linea2 = "<h2>" . $moddescri . "</h2>";
 7 $linea3 = "<h3>" . date("d/m/Y",time()) . "</h3>";
 8 $logo   = "img/logo_pagina1.jpg";
 9 ?>
10 <div id="rectangulo_fondo"> 
11     <div id="logo"> 
12         <img src="<?php echo $logo; ?>" height=100 alt="Logo" title="Logo"/>
13     </div> <!-- fin logo -->
14     
15     <div id="textos-cabecera"> 
16         <?php echo $linea1 . $linea2 . $linea3; ?>
17     </div>
18     
19     <div id="opciones-derecha">
20         <a href="ref1.htm" target=_blank><img src="img/info.png" title="Opción imagen"></a>
21         <a href="ref1.htm" target=_blank><img src="img/info.png" title="Opción imagen"></a>
22         <a href="ref1.htm" target=_blank><img src="img/info.png" title="Opción imagen"></a>
23     </div> 
24 </div>  <!-- fin rectangulo_fondo -->

(Como veréis, he parametrizado en variables la ruta hacia el logo y las tres líneas de información textual a mostrar, pero también podría haber puesto un contenido fijo).

Después de esto, he creado otro bloque con el resto de la información a mostrar, en este caso, 100 líneas de texto sin ningún sentido, pero suficientes para ilustrar lo que quiero hacer:

 1 <div id="bloque1">
 2 
 3 <?php
 4     for ($i=0; $i<100; $i++) {
 5         echo "<h1>Línea de texto número $i</h1>";
 6     }
 7 ?>
 8 
 9 </div> <!--bloque1-->

Si miramos ahora el aspecto de la página, sin haber creado aún el CSS, veremos algo así:

Imagen 3. La página sin CSS

Aplicando las hojas de estilos (CSS)

Feo, ¿verdad? Pero ahora entra en funcionamiento la magia del CSS. Vamos a indicar el color que queremos para el rectángulo de fondo (aquí he puesto un verde oliva), un borde para la parte inferior del rectángulo, una altura y un ancho:

 1 /* el rectángulo de fondo de la cabecera */
 2 #rectangulo_fondo {
 3     background: #e8e8a0; /* propiedad en modo shorthand, color verde oliva */
 4     border-bottom:1px solid #DDD;
 5     height: 100px !important;
 6     width: 100%;
 7 }

La altura de 100 píxeles que he establecido para mi rectángulo la he determinado por prueba y error, comprobando que es espacio suficiente para albergar las tres líneas de texto informativo que he establecido. Esto dependerá del formato que elijáis para vuestro texto, en mi caso he definido estos tres estilos:

 1 h1 {
 2     font-family: verdana,helvetica;
 3     font-size: 18px;
 4     font-weight: bold;
 5     color: #bb3902;
 6 }
 7 h2 {
 8     font-family: verdana,helvetica;
 9     font-size: 15px;
10     font-weight: bold;
11     color: #bb3902;
12 }
13 h3 {
14     font-family: verdana,helvetica;
15     font-size: 12px;
16     color: #000000;
17 }

Si usáis fuentes y/o tamaños diferentes, es posible que tengáis que establecer un poco más o un poco menos de altura a la cabecera.

Por otro lado, el ancho lo he establecido al 100% del ancho de la página, ya que quiero que la cabecera vaya de izquierda a derecha ocupando todo el frontal.

Si visualizáis la página ahora mismo, se verá mal. Las 3 líneas informativas se mezclan con las líneas de datos. Para evitar esto, indicaremos que el logo será flotante y esto hará que la sección que contiene a las líneas informativas ("textos-cabecera") se pegue al logo.

 1 #logo {
 2     float: left;
 3 }

El resultado hasta ahora es este:

Imagen 4. Esos iconos, a la derecha, ¡ARRRR!

Hmmm... vamos mejorando algo, pero nos interesa que los botones de acción se queden a la derecha. Para ello

 1 #opciones-derecha {
 2     float: right;
 3 }

Lo que nos deja un resultado así
Imagen 5. Los iconos de la derecha están que se arrastran. ¿Podríamos subirles el ánimo un poco?

Vaya, casi. Nos falta que queden arriba. Para conseguir esto, tenemos que indicar que el bloque con las 3 líneas de texto sea un bloque "inline", es decir, que se muestre en línea con el contenido que le precede y con el que va detrás. Es decir, ponemos

Y entonces se visualiza así:

 1 #textos-cabecera {
 2     display: inline-block;
 3 }

Imagen 6. Ya está todo en su sitio


Vale, esto casi está. Sin embargo, si hacéis el scroll de la página, comprobaréis que la cabecera, que tan chula nos ha quedado, se nos pierde por arriba.

Imagen 7. ¿A dónde vas, cabecera?

Necesitamos que no se mueva la cabecera y sí lo haga el bloque de líneas de datos. La clave es indicar que el bloque de cabecera debe quedarse FIJO. Esto se consigue con el atributo

 1 position: fixed;

Y ahora, al hacer el scroll, ya se queda la cabecera visible todo el rato
Imagen 8. Cabecera fijada

¿Hemos terminado? Pues no, aún nos quedan unos detalles importantes. Fijaos que al abrir la página, el bloque1, que contiene las líneas de datos, no se ve desde el principio. Las primeras líneas quedan ocultas por la cabecera

Imagen 9. ¿Dónde están las líneas 0, 1 y 2?

Para corregir esto, como sabemos que la cabecera tiene una altura de 100 píxeles, podemos establecer un margen superior para el bloque1 a partir de 110 píxeles.

 1 #bloque1 {
 2     margin-top: 110px;
 3 }

¿Se verá ahora bien?

Imagen 10. Ese hueco encima de la cabecera me hace sentir "vacío"

Pues no. Resulta que la cabecera se ha "arrastrado" hacia abajo. ¿Cómo podemos dejarla fija arriba? Pues estableciendo su propiedad "top" de forma que quede pegada al borde de la pestaña del navegador. O sea,

 1     top: 0 !important;

Y ahora se ve así:

Imagen 11. Bien, Pepe, bien. Ahora casi que sí.


Bueno, ahora sí. Y podemos comprobar el scroll y ver que la cabecera se queda fija.

Últimos detalles

Por último, sólo nos quedarían pequeños detalles, como que el borde izquierdo de la cabecera se pegue a la ventana del navegador (ese pequeño margen blanco a la izquierda no me gusta nada), cosa que conseguiremos con un
 1 left: 0;

Y que el texto informativo se quede centrado, lo que conseguiremos con

 1 text-align: center;

y además, indicando que el texto ocupe un amplio porcentaje del espacio de la cabecera

 1 width: 75%;

Si no hiciéramos esto último, el texto se centraría únicamente en el espacio mínimo reservado para él. Para mostrarlo, voy a pintar el fondo de este bloque de otro color

 1 background: #a8e8aa;

Y mostraremos cómo queda cuando no especificamos ancho
Imagen 12. Centrado, sí, pero en un espacio lateral


Y cuando sí lo hacemos al 75%

Imagen 13. Centrado en la franja central (el 75% de la cabecera)

Si somos muy estrictos, podemos ver que en realidad podríamos establecer un poco más del 75% como ancho de la zona de texto informativo, pues queda un pequeño espacio hasta el primero de los botones de acción de la derecha. Tampoco he querido esmerarme más ya que dependiendo del número de opciones que tengamos a la derecha, esto puede que haya que ajustarlo a un poco más o un poco menos, así que creo que es mejor hacer los ajustes en cada caso concreto.

Todo junto

Bueno, ya podemos volver a dejar el color del fondo como estaba y dar por terminada la página.

Esta página se puede utilizar como plantilla para aquellas páginas en las que necesitemos dejar una cabecera fija. Yo así lo voy a hacer, así que añadida queda a mi repertorio de plantillas. ¡Pa la saca!

Aquí os dejo el HTML completo y el CSS

El HTML
 1 <?php
 2 $modname = "PAGINA1"; $modversion = "E31"; $moddescri = "Descripción de la página 1";
 3 ?>
 4 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 5 <html>
 6 <head>
 7     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 8     <link rel="stylesheet" id="style-css" href="pagina1.css">
 9     <title><?php echo $modname . " - " . $modversion; ?></title>
10 </head>
11 <body>
12 <!--cabecera frente1-->
13 <?php
14 $linea1 = "<h1>" . $modname . " - versión " . $modversion . "</h1>";
15 $linea2 = "<h2>" . $moddescri . "</h2>";
16 $linea3 = "<h3>" . date("d/m/Y",time()) . "</h3>";
17 $logo   = "img/logo_pagina1.jpg";
18 ?>
19 <div id="rectangulo_fondo"> 
20     <div id="logo"> 
21         <img src="<?php echo $logo; ?>" height=100 alt="Logo" title="Logo"/>
22     </div> <!-- fin logo -->
23     
24     <div id="textos-cabecera"> 
25         <?php echo $linea1 . $linea2 . $linea3; ?>
26     </div>
27     
28     <div id="opciones-derecha">
29         <a href="ref1.htm" target=_blank><img src="img/info.png" title="Opción imagen"></a>
30         <a href="ref1.htm" target=_blank><img src="img/info.png" title="Opción imagen"></a>
31         <a href="ref1.htm" target=_blank><img src="img/info.png" title="Opción imagen"></a>
32     </div> 
33 </div>  <!-- fin rectangulo_fondo -->
34     
35 <div id="bloque1">
36 
37 <?php
38     for ($i=0; $i<100; $i++) {
39         echo "<h1>Línea de texto número $i</h1>";
40     }
41 ?>
42 
43 </div> <!--bloque1-->
44 </body>
45 </html>


El CSS
 1 h1 {
 2     font-family: verdana,helvetica;
 3     font-size: 18px;
 4     font-weight: bold;
 5     color: #bb3902;
 6 }
 7 h2 {
 8     font-family: verdana,helvetica;
 9     font-size: 15px;
10     font-weight: bold;
11     color: #bb3902;
12 }
13 h3 {
14     font-family: verdana,helvetica;
15     font-size: 12px;
16     color: #000000;
17 }
18 
19 /* el rectángulo de fondo de la cabecera */
20 #rectangulo_fondo {
21     background: #e8e8a0; /* propiedad en modo shorthand, color verde olivoso */
22     border-bottom:1px solid #DDD;
23     height: 100px !important;
24     /* para que permanezca la cabecera al hacer scroll */
25     position: fixed;
26     top: 0 !important;
27     left: 0;
28     width: 100%;
29 }
30 
31 #logo {
32     float: left;
33 }
34 
35 #textos-cabecera {
36     display: inline-block;
37     text-align: center;
38     background: #a8e8aa;
39     width: 75%;
40 }
41 
42 #opciones-derecha {
43     float: right;
44 }
45 
46 #bloque1 {
47     margin-top: 110px;
48 }

Pues nada, si os viene bien esta plantilla ya la tenéis explicada en detalle.

Y tú, ¿tienes alguna otra plantilla interesante para hacer cabeceras o barras de opciones laterales que se queden fijas? ¿La compartes con nosotros?

martes, 4 de febrero de 2014

Las mejores inyecciones son chorizos y jamones (parte 4, última)

4. Qué se puede hacer

Hasta aquí, hemos visto algunos ejemplos muy sencillos de cómo se puede intentar inyectar código SQL en una aplicación web (ojo: esto también sirve para una aplicación de escritorio). Pero, ¿hay algo que la madre de Caperucita pueda hacer para que el lobo no le putee la base de datos con una inyección SQL?

Bueno, no hay una solución mágica, pero se pueden hacer algunas cosas que sirven como pequeñas ayudas. Entre unas cosas y otras, se puede mitigar algo el poder de los ataques. Vamos allá. Seguro que a algunos se os ocurren más, pero estas son algunas de las actuaciones más básicas.

4.1 Instrucciones SQL precompiladas (prepared statements)

En primer lugar, está el uso de instrucciones SQL preparadas o precompiladas (prepared statements). Esto depende tanto del lenguaje (los más utilizados soportan todos formas más o menos parecidas de hacerlo) como de la BD. Estas instrucciones preparadas son como consultas precompiladas en las que lo único que falta es rellenar algunos datos que se han establecido como parámetros. Al estar precompiladas, estas instrucciones sólo se analizan (parsing) una vez, y se gana velocidad de ejecución, ya que el motor de la BD puede optimizar la consulta cuando se compila.

Pero lo mejor de ellas, para el caso que nos ocupa, es que los parámetros son gestionados por el motor de la BD; no tenemos que componer la sentencia SQL concatenando partes fijas con los parámetros. Bien es cierto que el motor también puede cometer un fallo, pero, seamos realistas, me fío más del equipo de programadores del motor de la BD y de su sistema de revisión que de mí, aunque nada más sea por el tamaño del equipo que tiene que haber detrás de un motor como SQL Server, Oracle o MySQL, por poner algunos. Y mira que yo soy buen programador (y además, guapo), pero aún y con esas, no creo que les gane ;-)

El caso es que los parámetros normalmente tienen tipos (integer, string...) que son comprobados por el motor y eso facilita mucho evitar que se cuelen cosas "raras".

En el caso de PHP, que es el que me pilla más cercano ahora, las instrucciones preparadas se pueden utilizar con los PDOs. Más info aquí:
http://php.net/manual/en/pdo.prepared-statements.php

4.2 Sanear la entrada

Otra cosa que se puede hacer es analizar la entrada que está metiendo el usuario para ver si contiene caracteres raros. Por ejemplo, si en el campo donde espero leer un NIF me encuentro algo que no son dígitos ni letras, ¡alarma! Como norma general, se pueden validar campos habituales como el NIF, los apellidos, el nombre, etc... para alertar si contienen comillas, exclamaciones, barras, etc... Se puede funcionar por lista negra (un conjunto de caracteres prohibidos) o, mejor aún (en mi opinión), si se sabe de antemano, funcionar por lista blanca (conjuntos de caracteres permitidos). En un apellido no permitiría ni siquiera un dígito. No obstante, a veces habrá que permitir la comilla (se me ocurren apellidos como O'hara) y en particular hay que tener cuidado con las codificaciones de los caracteres con tildes, eñes, etc...

4.3 Permisos en la BD

Bueno, si a pesar de nuestras precauciones el atacante logra ejecutar una instrucción contra la BD, intentemos que al menos el daño sea el menor posible. Siguiendo el principio de mínimo privilegio, un usuario nunca debería tener más permisos de los estrictamente necesarios para realizar su labor. Si sólo va a consultar datos en la BD, no tiene por qué tener permisos para cargarse una tabla. De esta forma, aun en el caso de que un atacante consiguiera inyectar una instrucción del tipo "drop table", si la ejecución se lleva a cabo con los permisos de un usuario con acceso de sólo lectura, ésta fallaría.

Así que ya sabéis, para configurar bien los permisos, a revisar aquello del GRANT SELECT ON TABLA... para el caso específico de vuestra BD.

Principio del mínimo privilegio:
http://sophosiberia.es/principio-de-minimo-privilegio-reducir-el-area-de-ataque/

4.4 Otras pequeñas ayudas

Por último, hay algunas otras cosas que se pueden hacer que, aunque no son la panacea, pueden aportar su granito de arena.

4.4.1) Limitar el tamaño de los campos de entrada. 

Por ejemplo, los login de los usuarios. En entornos empresariales a veces hay reglas cuadriculadas para asignar los login (así ocurre en uno de los entornos en los que suelo trabajar). Si los nombres de usuarios no superan los 10 caracteres, por ejemplo, limitar el tamaño de ese campo puede contribuir a que desde el formulario no se puedan insertar peticiones con instrucciones largas (por ejemplo, no se podría insertar el DROP TABLE...). Esto no evita que se lance una petición a través de un programa elaborado específicamente para ello, pero dificulta un poco la realización de ataques.

2) Registrar lo que se ejecute

También puede ser conveniente llevar un registro (tabla de log, fichero, log del sistema...) con las operaciones que se van realizando desde los programas. Esto no va a evitar que se produzcan los problemas, pero al menos puede ayudar para, una vez producidos y detectados los mismos, determinar el origen (usuario, dirección IP...) y por lo menos saber por dónde buscar para intentar cazar al lobo. Si no hemos podido evitar el ataque, al menos nos queda el consuelo de intentar pillar al responsable y darle un capón.

3) Filtros por palabras

Se pueden filtrar determinadas palabras clave en las entradas del usuario (como "drop", "delete"...) en determinados campos donde esto no tiene sentido. Por ejemplo, normalmente no encontraremos nombres o apellidos con esas palabras, así que si un usuario se llama "Drop..." lo mínimo que se puede hacer es disparar una alerta y llamar al leñador, a que venga con el hacha.

4) Codificaciones de cadenas

Esta solución no la he probado, pero la vi apuntada en un foro y quizás merezca dedicarle un poco de atención. La idea es codificar las entradas del usuario haciendo una conversión de las cadenas de entrada a codificaciones puras ASCII.

Lo que se argumenta es que con estas codificaciones desaparecen las comillas, los puntos y coma y otros caracteres "peligrosos". Pero no tengo muy claro cómo se harían las búsquedas y comparaciones. En fin, ahí os dejo apuntados los nombres de las funciones que habría que mirar, para aquellos que deseéis investigar: base64, hex2bin, unhex, pack...

Aquí lo dejo

Bueno, con esto doy por zanjado el tema. Como veréis, la metáfora del cuento de Caperucita ha ido perdiendo fuerza con cada nueva entrada; fue una chorrada que se me ocurrió un día y después me ha dado un poco de pereza mantenerla, aunque ahí la dejo por si alguien quiere elaborar un poco sobre ella y darle mejor forma.

Por otro lado, como ya he dicho otras veces, estas entradas las escribo, en primer lugar, para mí, por lo que a veces puede que dé por supuestas ciertas cosas y no me esfuerce en explicar las bases, aunque creo que cualquier programador con un nivel básico puede seguir las explicaciones sin complicaciones. En este caso, apenas he tocado la punta del iceberg del tema en cuestión, pero a mí me bastaba con tener unas pruebas de concepto que funcionaran realmente, y lo he conseguido, así que al menos a mí me ha valido. El hecho de haberme obligado a escribir de forma más o menos estructurada me ha generado, como otras veces, más preguntas que respuestas, y un montón de ideas y tareas a realizar, así que ahora me toca dedicarme a implementar muchas de las ideas que han ido apareciendo durante la escritura de esta serie de entradas, así como hacer pruebas reales en mis aplicaciones, pues me he dado cuenta de algunos fallos que tengo que debería corregir cuanto antes.

Para eso me sirve escribir ;-)

Referencias

Aquí tenéis el enlace a todos los artículos anteriores de la serie:

Parte 1
http://cosicasdeinformatica.blogspot.com.es/2014/01/las-mejores-inyecciones-son-chorizos-y.html

Parte 2
http://cosicasdeinformatica.blogspot.com.es/2014/01/las-mejores-inyecciones-son-chorizos-y_23.html

Parte 3
http://cosicasdeinformatica.blogspot.com.es/2014/01/las-mejores-inyecciones-son-chorizos-y_27.html
Related Posts Plugin for WordPress, Blogger...