CGI en Perl (y algo más para los Mongers) (Era Re: seguridad en linux)

Ernesto Hernandez-Novich emhn at telcel.net.ve
Fri Jul 13 15:37:40 CDT 2001


¿Qué tiene que ver ejecutar un CGI con el asunto? En el futuro procura
utilizar un asunto más descriptivo.

On Thu, 12 Jul 2001, Carlos Rodriguez wrote:
> tengo un problema al momento de ejecutar un script cgi-!!
> estoy haciendo un script de prueba para que me envie a una pagina html un
> mesaje.!!!
> necesito saber como poder enviar mensajes de un cgi a una pagina html ..
> pero no puedo.!!
> este enjemplo lo saque de un libro de perl pero no e funciona.!
> lo que debe de hacer  es leer un archivo .txt e incrustar el mensaje dentro
> de la pagina html.!
> les mano los 3 archivos que estoy utilizando.!!!
> ojala y alguno de ustedes me pueda ayudar.!!

El programa .cgi que anexas tiene un error de sintaxis. Lo reporta en la
línea 54, pero en realidad está más arriba por omitir un sub antes de
declarar una subrutina. Si en el _libro_ estaba así, excusado; si en el
libro está bien pero lo copiaste mal, más cuidado la próxima vez.

Antes de ejecutar un programa CGI, verifícalo en la línea de comandos con
un usuario que no sea root para saber que no tiene errores de sintaxis y
que puede leer los archivos que necesita. Luego lo llevas al directorio
cgi-bin correspondiente y le asignas permisos 755 y lo vuelves a correr
allí mismo desde la línea de comandos. Recién entonces lo pruebas a través
de un browser. Si tienes un problema al ejecutarlo a través de un browser,
en el error.log del servidor encontrarás los mensajes de error emitidos
por el programa que pueden servirte para diagnosticar la causa del problema.

(De aquí en adelante para los Perl Mongers o Perl Acolytes :-)

El programa es _correcto_, pero es exageradamente verboso e innecesariamente
lento y complejo. Hace mucho tiempo que en Perl se dispone del módulo CGI
para generar páginas HTML "al voleo".

Este programa opera para que sobre un archivo con N líneas (N desconocido a
priori), se escoja una línea aleatoriamente. El algoritmo es: leer todo
el archivo para contar las líneas y saber el valor de N, escoger un
número aleatorio P entre 1 y N, volver a leer el archivo desde el principio
para buscar y mostrar la línea P. El lector astuto notará que en el _mejor_
de los casos (P = 1), hay que leer el archivo completo más una línea y en
el _peor_ de los casos (P = N) hay que leer el archivo completo _dos_ veces
(omygosh!).

Alguien avispado podría sugerir leer el archivo en _memoria_ una sola vez y
luego navegarlo. Si, es algo mejor, hasta que intente leer un archivo
gigantesco.

No pude resistirme a resumir el programa en:

#!/usr/bin/perl
$|++;
use CGI;
my $q = new CGI;
srand;
$nombre_fichero = '/var/www/html/citas.txt';
print $q->header,
      $q->start_html,
      BlackJuju,
      $q->end_html;

sub BlackJuJu {
  my $l;
  open(C,$nombre_fichero);
  rand($.) < 1 && ($l = $_) while <C>;
  close <C>;
  return $l;
}

Lo trivial primero:

- Desactivar el buffering, esencial en una aplicación CGI.
- El módulo CGI forma parte de la distribución de Perl y está diseñado para
  generar páginas dinámicamente. $q será una página generada dinámicamente.
- Ubicación del archivo.
- Imprimir el encabezado HTML, el inicio de HTML, la magia negra y
  terminar el HTML.

Lo bonito ahora:

Es inconcebible leer el archivo completo dos veces... es demasiado lento
si el archivo es largo. Pensando en grande, ¿qué tal si fuese una base
de datos con decenas de miles de registros? ¡Horrible! Así que de entrada,
leer todo el archivo para averigüar N es una solución malísima.

¿Será posible escoger una línea sin necesidad de leer _todo_ el archivo y
sin conocer N de antemano? Por supuesto, con ayuda de las probabilidades
(José Luis, ¿aún ahí? ;-). Abro el archivo una sola vez.

(Con ustedes la "magia negra")

rand($.) < 1 && ($l = $_) while <C>;

$. es una variable especial de Perl que contiene el número de línea
actual, i.e. la primera vez que lea vale 1, la segunda vez vale 2, y así
hasta llegar a la última línea para la cual valdría N (que aún no conozco ;-).

El while <C> lee una línea (se incrementa $. automáticamente). Escojo
un número aleatorio entre 0 y $. (con el rand($.)); rand(X) devuelve un
número entre 0 y X inclusive. Si el resultado de rand() es menor
que 1 entonces guardo la línea leída en $l. Una vez recorrido todo el
archivo en este plan, lo cierro y retorno $l que ha sido mi línea
escogida aleatoriamente. No solamente leo el archivo _una_ sola vez, sino
que sólo ocupo tanta memoria como la línea más larga del archivo.

Y es _perfectamente_ aleatorio, todas las líneas tienen la misma oportunidad
de ser escogidas.

¿Por qué sostengo ésto?

Como rand(X) devuelve un número entre 0 y X, hay 1/X oportunidades de
quedarse con la línea. Esto es, hay 1/1 chance de escoger la 1, 1/2
de escoger la 2, 1/3 de escoger la 3 y así hasta 1/N de escoger N.

Ejemplos prácticos: 

- Si el archivo tiene 1 línea, _siempre_ va a salir escogida. Obviamente
  no sólo es justo, sino que es correcto retornar la única posibilidad.
- Si el archivo tiene 2 líneas, la primera fue escogida cuando se leyó y
  la segunda tiene 1/2 de chance de ser escogida... y entre dos eventos,
  la probabilidad uniforme es 1/2... sigue siendo justo.
- Para tres líneas, la primera fue escogida siempre, la segunda tuvo un
  chance 1/2 y la tercera tiene un chance de 1/3. Si la tercera tiene 1/3,
  para que sea "justo", los 2/3 anteriores deben repartirse a partes
  iguales. Pero ya mostramos que dos líneas tienen 1/2 cada una, de modo
  que los 2/3 se dividen a 1/2, i.e. 1/3 de probabilidad para cada una
  de las tres líneas. Probabilísticamente justo.
- Para cuatro líneas, la primera fue escogida siempre, la segunda tuvo
  un chance 1/2, la tercera tuvo chance 1/3 y la cuarta tiene un
  chance 1/4. Si la cuarta tiene 1/4, para que sea "justo", los 3/4
  anteriores deben repartirse a partes iguales. Pero ya mostramos que
  tres líneas tienen 1/3 cada una, de modo que los 3/4 se dividen
  a 1/3, i.e. 1/4 de probabilidad para cada una de las cuatro líneas.
  Probabilísticamente justo.

Ejemplo teórico (el más compacto):

Si tengo un conjunto de N+1 elementos ordenados y escojo el último con
probabilidad 1/N+1 y escojo cualquiera de los N previos con probabilidad
N/N+1, entonces la probabilidad combinada es N/N+1 dividido N cuyo
resultado es 1/N+1. Esto es, tengo igual probabilidad de escojer el
último elemento que escojer _cualquiera_ de los previos; por inducción
matemática se desprende que la probabilidad es justa.

Ciencia de la computación sin matemática... contradicción.

La magia negra es un "idioma Perl" precisamente para "escoger 1 de N
con probabilidad uniforme en una sola pasada"; generalmente se aplica a
líneas de archivos, pero también se puede aplicar a listas o cualquier
conjunto ordenado de objetos.
-- 
Ernesto Hernández-Novich - Running Linux 2.4.5 i686 - Unix: Live free or die!
-----BEGIN GEEK CODE BLOCK-----
Version: 3.1
GCS/E d+(++) s+: a C+++$ UBLAVHIOSC*++++$ P++++$ L+++$ E- W+ N++ o K++ w--- O-
M- V PS+ PE Y+ PGP>++ t+ 5 X+ R* tv+ b++ DI+++$ D++ G++ e++ h r++ y+
-----END GEEK CODE BLOCK-----

------------------------------------------------------------------------
Enviar e-mail a <majordomo at pm.org> colocando en el cuerpo:
"UNSUBSCRIBE caracas-pm-list" para desuscribirse.
"INFO caracas-pm-list" para conocer las reglas de etiqueta.
------------------------------------------------------------------------



More information about the caracas-pm mailing list