Siempre quise programar un explorador de archivos/directorios en PHP. Sé que es crÃtico jugar con el contenido de un disco duro y su visualización vÃa web, pero sin riesgo no hay adrenalina. La única premisa que me habÃa marcado era un enjaulamiento complicado de saltar, algo asà como definir una ruta
base de la cual no se podrÃa subir, por seguridad. Esto es lo que ha salido:
El
File Browser (todavÃa por bautizar) consta de tres funciones:
- evalua_ruta(): Encargada de evaluar si la ruta que se le pasa como parámetro es válida o no, básicamente comprueba que se encuentre dentro del chroot virtual (que también se pasa por parámetro).
- contenido_ruta(): Siempre que la ruta sea válida, imprime su contenido.
- rmime(): No estoy demasiado orgulloso de esta función, pero el entorno-servidor ha hecho que sea usable. Se encarga de identificar el tipo de archivo (o directorio) en función a la extensión y añadirle una clase CSS para imprimir un icono correspondiente. Lo lógico serÃa haberlo hecho con Mimetype, pero no me parecÃa apropiado instalar/habilitar más extensiones en el servidor solamente para ésto.
El pseudocódigo es sencillo, atiende a un par de condiciones y poco más:
definimos_base;
evaluamos_base+ruta;
si es correcta:
imprimimos su contenido;
si no es correcta:
imprimimos contenido de base;
En un principio
ruta será una cadena vacÃa porque el directorio principal es
base, pero a medida que vayamos entrando en subdirectorios dentro de
base, ésto se pasan por url como la variable
dir, (que la función llama
ruta).
evalua_ruta()
Siempre la ruta real será
$base+$ruta, para asegurarnos de que no salimos del
chroot, asà que le pasamos ambos parámetros, la función los convierte en rutas absolutas y usa
strncmp() para ver que una está contenida en la otra. En un principio habÃa pensado en devolver un array con dos valores, 0 si la ruta era incorrecta y 1 si era una ruta válida; pero pensé que serÃa más sencillo hacer toda validación dentro de esta función. Si la ruta es correcta se devuelve, si no lo es, se devuelve la
$base:
<?php
// Parametros: Ruta a evaluar y base desde la que enjaulamos
function evalua_ruta($ruta, $base)
{
if($ruta) $ruta=$base."/".$ruta."/";
else $ruta=$base."/";
$ruta_real=realpath($ruta);
$base_real=realpath($base);
// Miramos si es real o no
if(strncmp($base_real, $ruta_real, strlen($base_real))==0)
return $ruta_real;
else
return $base_real;
return $arr;
}
?>
contenido_ruta()
Una vez la ruta es correcta pasamos a imprimir su contenido de forma explorable, con enlaces en sus directorios para poder entrar en ellos, etc. Se presentan varios problemas, como descomponer la ruta relativa en arrays para calcular los enlaces de los
directorios especiales . y
... Una vez calculados dichos enlaces (
$ruta_dot y
$ruta_dotdot) evaluamos si se trata de un directorio o un archivo para enlazar de forma correspondiente. También calculo los Kb. de cada archivo como
extra:
<?php
// Parametros: Ruta a leer, base desde la que enjaulamos y base del enlace para directorios
function contenido_ruta($ruta, $base, $baselink)
{
$ruta_real=realpath($ruta);
$base_real=realpath($base);
$ruta_dot=str_replace($base_real, "", $ruta_real);
$arr_dotdot=explode("/", $ruta_dot); array_pop($arr_dotdot);
$ruta_dotdot=implode("/",$arr_dotdot);
$output="<h1>{$base}{$ruta_dot}</h1>ntt<ul>n";
$dir=opendir($ruta);
while($f = readdir($dir))
{
// Si es directorio modificamos enlace de . y ..
if(is_dir($ruta."/".$f))
{
switch($f)
{
case ".": $txt="."; $link="{$baselink}$ruta_dot"; break;
case "..": $txt="..";$link="{$baselink}$ruta_dotdot"; break;
default: $txt="$f";$link="{$baselink}{$ruta_dot}/$f"; break;
}
$filldot="";
$clase="folder";
}
// Si es archivo enlazamos a lo bruto
else
{
$filldot="";
for($i=0; $i<=(50-strlen($f)); $i++) $filldot.=" ";
$kb=(filesize("{$base}{$ruta_dot}/{$f}")/1024);
$kb=number_format($kb, 0); $filldot.=$kb." Kb.";
$txt="$f"; $link="{$base}{$ruta_dot}/{$f}";
$clase=rmime("{$base}{$ruta_dot}/{$f}");
}
$output.="ttt<pre><li class="{$clase}"><a href="{$link}">{$txt}</a>{$filldot}</pre></li>n";
}
$output.="tt</ul>n";
return $output;
}
?>
El parámetro que veis como
$baselink es una variable que paso para completar el enlace. Para que se entienda, si la página donde está el
File Browser es:
http://www.midominio.com/index.php?p=browser
$baselink=index.php?p=browser;
Imagino que se podrÃa automatizar con las variables
$_SERVER, pero no vamos a cubrir todo en la versión 0.1 :D
rmime()
Poco que comentar, le pasamos una cadena con el nombre de archivo y mira su extensión, dependiendo del
case devuelve un valor u otro. Tiene los
mimetypes más comunes, sencillo de completar con más:
<?php
// Parametro: archivo
function rmime($file)
{
$ext=array_pop(explode('.', basename($file)));
$ext=strtolower($ext);
switch ($ext)
{
case "jpeg": $img="jpg"; break;
case "gif": $img="gif"; break;
case "jpg": $img="jpg"; break;
case "png": $img="png"; break;
case "bmp": $img="bmp"; break;
case "pdf": $img="pdf"; break;
case "txt": $img="txt"; break;
case "doc": $img="doc"; break;
case "xls": $img="xls"; break;
case "mp3": $img="audio"; break;
case "wav": $img="audio"; break;
case "wma": $img="audio"; break;
case "ogg": $img="audio"; break;
case "mpeg": $img="video"; break;
case "mpg": $img="video"; break;
case "avi": $img="video"; break;
case "qt": $img="video"; break;
case "wmv": $img="video"; break;
case "arj": $img="arj"; break;
case "gz": $img="gz"; break;
case "rar": $img="rar"; break;
case "zip": $img="zip"; break;
case "tar": $img="tar"; break;
case "ttf": $img="fuente"; break;
default: $img="empty"; break;
}
return $img;
}
?>
main()
En tres lineas creamos el flujo principal del programa, tan simple como definir la base del chroot, llamar a una función y llamar a la siguiente:
<?php
$base="pub";
$rruta=evalua_ruta($_GET[dir],$base);
echo contenido_ruta($rruta, $base, "index.php?p=pub&dir=");
?>
CSS
Para imprimir el contenido utilizo un listado no numerado (ul, li) de forma que cada elemento del listado tiene una clase de un tipo (jpg, png, rar...) previamente definido en un fichero css:
<li class="folder"><a href="#">.</a></li>
<li class="folder"><a href="#">..</a></li>
<li class="gz"><a href="#">rweb.tar.gz</a></li>
li.folder {background: url("images/mime/folder.png") no-repeat 8px 4px; }
li.gz {background: url("images/mime/gz.png") no-repeat 8px 4px; }
El set de iconos usado para los tipos de archivo es el
Vista Inspirate sacados de
Gnome-Look:
Agradecimientos
Como siempre a
Juanjo por algunas ideas referidas a
realpath y por
beta-tester. Esto solo es el intento de materialización de una idea más, imagino que todo este código se podrá mejorar, asà que cualquier comentario será apreciado y valorado.
Comentarios
Un amigo mÃo tiene una herramienta por el estilo, pero un poco más avanzada. Se llama PHPfileNavigator, pero para cosas básicas puede resultar demasiado grande.
Como nombre yo le pondrÃa br0wsker ;D
Te lo agradezco.
Un saludo
Gracias
Te lo agradezco.
Un saludo
Gracias
Aunque si la base es "/" no funciona. Hay un problema con las /
Un saludo.Gracias.