Ladrones de ancho de banda
Sabiendo o sin saberlo mucha gente enlaza las imágenes en páginas web (en foros sobretodo) directamente de los servidores originales, sin copiarlas en sus propios servidores y muchas veces sin acreditar su procedencia. Una manera de evitarlo es restringir a que el servidor sólo responda a peticiones dentro de unos dominios determinados (normalmente el dominio donde se alojan las imágenes que quieres mostrar).
El otro día en neuroticweb explicaban un método diferente para luchar contra esta práctica. En resumen permitiremos que la gente utilice nuestro ancho de banda pero a la vez haremos publicidad de nuestra página.
La solución expuesta en neuroticweb para mi falla en una pequeña cosa: utiliza la llamada system de PHP, una llamada muy peligrosa y que muchos hosting (el que utiliza Ruido Blanco, sin ir más lejos) no permiten.
Así que me decidí a encontrar una forma de realizar lo mismo y que pudiera ser utilizado en la mayoría de hosting “económicos”. Obviamente soluciones como Python, Mono o Ruby quedaban descartadas por su poca implantación, un guión de shell como CGI hubiera sido una gran opción (es como hacer llamadas a system continuamente) pero, para que vamos a engañarnos, si hay algo extendido y soportado en los servidores de hosting es PHP.
En la mayoría de ocasiones PHP está instalado junto como un módulo llamado GD (no en Mac OS X, pero para solucionarlo el modulo de PHP de Apache para Mac OS X de entropy.ch es una sencilla solución). Este módulo permite crear imágenes utilizando funciones relativamente sencillas. Otros requisitos del guión serán que disponga de caché de imágenes (como el original) y que “falle” relativamente bien (“gracefully“, que dirían los anglosajones). La estructura general del guión está tomada del guión de neuroticweb.com, pero ya no es necesario ni permisos para realizar la llamada system ni el paquete ImageMagick, además de un par de mejoras en algunos puntos.
<?php
// *** Comienzo de la configuracion
// Camino a la raiz del sitio web
$web_root = "/home/username/public_html";
// Directorio de caché. El usuario que "ejecute" el script necesita permisos de escritura en él.
$path_cache = "/home/username/cache/";
// Imagen por defecto.
$default_photo = "/default.jpeg";
// Frase a añadir
$tag = "Tagline? We don't need no stinking tagline!";
// *** Fin de la configuración.
/*
* Transforma una imagen de paleta en una imagen truecolor. No toca las imagenes
* truecolor.
*/
function imagepalettetotruecolor(&$img)
{
if (!imageistruecolor($img)) { // El formato GIF no es truecolor
$w = imagesx($img);
$h = imagesy($img);
$img1 = imagecreatetruecolor($w, $h); // Creamos una imagen truecolor nueva
imagecopy($img1, $img, 0, 0, 0, 0, $w, $h); // Copiamos la imagen vieja encima
$img = $img1; // Modificamos el manejador de la imagen al nuevo
}
}
/*
* Procesa una imagen. El parametro es el camino a la imagen. La imagen resultante
* se salva sobre la vieja imagen. Modificar esta función para cambiar el efecto añadido.
*/
function process_image($photo)
{
global $tag;
// Crear un manejador desde cualquier formato conocido
$im = imagecreatefromstring(file_get_contents($photo));
imagepalettetotruecolor($im);
$white = imagecolorallocate($im, 255, 255, 255);
$black = imagecolorallocate($im, 0, 0, 0);
// Añadir la frasec on la fuente #2
// Añadir un rectangulo como fondo.
$tag_width = imagefontwidth(2) * strlen($tag) + 1;
$tag_height = imagefontheight(2) + 2;
imagefilledrectangle($im, 0, 0, $tag_width, $tag_height, $black);
imagestring($im, 2, 1, 1, $tag, $white);
// Salvar la imagen como jpeg.
imagejpeg($im, $photo);
imagedestroy($im);
}
// Obtenemos la imagen a marcar
if ($_GET['photo'] != "") { // Desde parámetro de la URL para las pruebas
$photo_url = $_GET['photo'];
} else if ($_SERVER['REQUEST_URI'] != "") { // Desde la URL pedida para producción
$photo_url = $_SERVER['REQUEST_URI'];
} else { // o una imagen por defecto
$photo_url = $default_photo;
}
// El camino a imagen real
$photo_path = $web_root . $photo_url;
// El camino a la imagen cacheada. La función md5 previene que varias
// imagenes que se llamen igual no se visualizen bien.
$photo_cache = $path_cache . md5($photo_url);
if (is_file($photo_path)) { // Miramos si la imagen existe
if (!is_file($photo_cache)) { // Si la imagen no está cacheada
// La copiamos a la cache
copy($photo_path, $photo_cache) or die('Error copiando la imagen');
// Procesar la imagen
process_image($photo_cache);
}
if (is_file($photo_cache)) { // Si la imagen esta en la cache
// Enviar el tipo MIME y la longitud de la imagen
header("Content-Type: $mime_type");
header("Content-Length: " . filesize($photo_cache));
readfile($photo_cache);
} else {
die('La imagen no está en la cache');
}
} else {
die('Imagen no encontrada ' . $photo_url);
}
?>
Se puede descargar desde imagen.phps.
El guión tiene el pequeño fallo de que solo funciona si Apache (o el servidor que sea) no empieza a crear “alias” de los directorios y demás. Es decir, la estructura del sitio debe estar bajo la variable $web_root y no salirse de él. Además puede tener un pequeño fallo de seguridad, aunque parece que en Mac OS X no funciona como yo pienso: a través del parámetro photo o del URL pedido quizá mediante .. se pueda acceder a lugares del disco duro que no se deberían acceder. Al parecer PHP o Apache (o quizá Mac OS X) no permitan esto, pero por si acaso ahí está el aviso. Si alguien sabe como evitar que un camino se salga de un directorio raíz en PHP se lo agradecería.
El código hay que completarlo añadiendo al archivo .htaccess de la raíz donde tengamos las imágenes el siguiente código (en el caso de Wordpress un buen lugar es el directorio wp-content):
RewriteEngine on
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://eldominio.com(/)?.*$ [NC]
RewriteRule .*.(gif|jpg|jpeg|bmp|png)$ /image.php [NC]
Según este código sólo se aplicará el guión a las peticiones realizadas fuera del domino “http://eldominio.com” y que tengan las extensiones indicadas (el “NC” del final hace que las comparaciones no tengan en cuenta mayúsculas y minúsculas). El guión de PHP deberá estar en la raíz del sitio y llamarse image.php (o cualquier otra cosa si se modifica la regla).
El guión está ahora mismo funcionando, haciendo publicidad en un par de sitios que utilizan las fotos que he colgado, y como efecto colateral (como comentan en neuroticweb.com) en algunos lectores de feeds como Bloglines (y quizá alguno de escritorio). Añadiendo un par de reglas para esos dominios y para algunos user agents debería arreglarse la cosa, pero un poco de publicidad extra nunca viene mal (soy vago, lo se).


2 de Agosto de 2006 a las 22:51
[...] En ruido blanco han hecho un script que usa la librería GD (y así no tener que usar ni system ni imagemagick). [...]
3 de Agosto de 2006 a las 11:32
Publicar en meneame un script y al día siguiente alguien lo ha mejorado…
Hace unos dos día publiqué un script que había hecho para añadir el nombre de tu dominio a las imágenes "hotlinkeadas". Tenía algunas desventajas y al día siguiente de salir en meneame ya había alguien que lo había mejorado. Ahora us…
3 de Agosto de 2006 a las 11:48
Es un excelente trabajo, aunque esperaré a que alguien mejore todavia más el código
3 de Agosto de 2006 a las 12:05
Bueno, en realidad no es una mejora sobre el código original. Más bien es una adaptación para mi caso particular. Pero si me dices en que se puede mejorar quizá lo pueda implementar (o espérate a mañana a que alguien saque otra versión con más cosas
).
9 de Agosto de 2006 a las 13:40
[...] Hace unos días aparecía en menéame un script que nos permite utilizar el hotlinking como elemento de promoción sobreimpresionando un marco con nuestro url a cualquier imagen enlazada desde fuera de nuestro dominio. Si te sobra ancho de banda no dudes en echarle un vistazo: Ladrones de ancho de Banda Eficaz como insertar marcas de agua con algún programa de retoque y sin estropear las fotos. [...]
12 de Enero de 2007 a las 09:11
Muy interesante y util, lo voy a adaptar a mi sitio a ver que tal va, dado que tengo una ingente cantidad de imagenes y no se si las estan usando o no…
Solo me falta añadir al script que haga un LOG de los sitios desde que se enlazan
PD: tienes un error en el mismo, falta usar $tag_height en imagefilledrectangle.
12 de Enero de 2007 a las 09:42
Tienes razón. Ya está corregido. Muchas gracias.
Para hacer un registro de los sitios que enlazan tus imágenes simplemente tienes que escribir el valor de
$_SERVER['HTTP_REFERER']de cada petición para la que tengas que procesar una imagen.De cualquier forma cualquier paquete de estadísticas web (AW-Stats, Analog, Webalizer) que analice los registros de Apache tiene una sección donde se listan los principales “referer”.
14 de Enero de 2007 a las 08:57
Lo tengo funcionando ya, y he cazado a dos
Por si a alguien le interesa pongo el codigo para grabar la traza:
// grabar LOG con datos del ladron
$s = ' - ' ;
$linea = date('Y-m-d H:i:s'). $s. $_SERVER['REMOTE_ADDR']. $s. $_SERVER['REQUEST_URI'] ;
$linea .= $s. $_SERVER['HTTP_REFERER']. $s. $_SERVER['HTTP_USER_AGENT']. "\n" ;
$fh = fopen($path_cache. "log.txt", 'a') ;
fwrite($fh, $linea) ;
fclose($fh) ;
Esto hay que añadirlo obligatoriamente despues de hacer el “readfile”, pero yo lo he puesto al final del todo con un flag para que solo lo grabe la 1ª vez que cree la imagen en el cache y no se llene mucho este fichero.
PD: hay mucha gente que no pilla porque no manda referer :/
14 de Enero de 2007 a las 14:53
El problema de la gente que no manda referer es que puede ser un usuario legítimo de la web. Imagina esta situación: un usuario de tu página ve un enlace que lleva a la imagen y lo guarda en sus marcadores. Cuando accede desde los marcadores no enviará referer pero es un uso “ético” de la imagen, y por eso no se muestra la “marca de agua”.
Si decides que lo anterior no es un problema para ti (y quizá otras situaciones que no se me ocurren) la primera línea
RewriteConddel fichero.htaccesses la que captura los referer vacíos y les envia la imagen normal.