PHP File Browser
25.sep 2006
Envía un trackback
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:

- 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.
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
Grande!
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
Escribe tu comentario
Intenta que tu comentario sea interesante y con información relevante al tema de la entrada. BBCodes disponibles:
[url=http://direccion]texto[/url], negrita: [b]texto[/b],
itálica: [i]texto[/i], subrayada: [u]texto[/u].
Para mencionar o citar a alguien (quote): [cita]texto[/cita]


