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:
File Browser
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:
Mime Types

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.
file browser file_browser

Comentarios
Gravatar THroLL@26.09.2006, 'Re: PHP File Browser'

Grande!

Un amigo mío tiene una herramienta por el estilo, pero un poco más avanzada. Se llama , pero para cosas básicas puede resultar demasiado grande.

Como nombre yo le pondría br0wsker ;D


Escribe tu comentario
 
 
Guardar datos
Escribe tu comentario:
captcha


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]