Acceso al registro en PHP

Quiero registrar el acceso a cualquier archivo en el /files carpeta, por lo que puedo procesarlo con PHP para generar algunas estadísticas.

No quiero escribir un controlador PHP personalizado llamado a través de RewriteRule porque no quiero tener que lidiar con códigos de estado, tipos MIME y encabezados de almacenamiento en caché, y problemas de bloqueo de archivos.

No tengo acceso a la configuración del servidor, por lo que no puedo usar CustomLog (Tengo acceso a .htacess).

No puedo usar X-Sendfile porque no está habilitado.

No tengo acceso al access.log.


Buscando una respuesta autoritaria.

preguntado el 31 de enero de 12 a las 16:01

¿Tiene acceso a la access_log para analizar? -

¿Los archivos de / files se pueden descargar o son solo archivos que se sirven al usuario, como imágenes, hojas de estilo, etc.? -

Tantas limitaciones ... no creo que valga la pena. Si desea registrar, necesita control. -

¿Seriamente? Quiere iniciar sesión, pero no quiere hacer nada para iniciar sesión y no tiene acceso a la real registros. Bueno ... simplemente haga index.php? File = 12345.txt un nivel más arriba de / files y registre en algún lugar que alguien solicitó files / 12345.txt y luego haga una redirección de encabezado ("Location: files / 12345.txt"). Por supuesto, cualquiera que quiera puede omitir su seguimiento yendo a files / 12345.txt, pero ... bueno. -

Si es el sitio apache mod_php, intente la función virtual (). Funciona de manera similar al encabezado X-Sendfile. Ver ejemplo. -

10 Respuestas

Son bastantes restricciones que ha puesto allí.

Puede hacer esto con un controlador personalizado instalado a través de PHP include en la parte superior de cada aplicable (o, con __FILE__ análisis, no aplicable) script. Debe tener una secuencia de comandos que se ejecute cuando se golpea cada archivoy ha excluido las alteraciones en la configuración del servidor (incluidas, creo, .htaccess cuando dijiste RewriteRule no era lo suficientemente bueno), por lo que eso significa que hacer esto a través de un controlador de acceso basado en secuencias de comandos. usted podrá tenga una solución que cumpla con sus limitaciones y haga que los usuarios accedan a los archivos sin tener que presionar PHP (u otro lenguaje dinámico del lado del servidor) primero. El almacenamiento en caché se puede conservar mediante redireccionamiento el usuario a los archivos reales en lugar de ejecutar contenido estático a través de PHP.

Puede almacenar la información del registro en una base de datos o un archivo en una ubicación en la que el servidor pueda escribir (tenga cuidado con la contención si usa archivos; el modo de agregar es complicado).

EDIT: quickshiftin señala dos formas en que puede invocar PHP sin tener que agregar include llamadas a mano.

contestado el 23 de mayo de 17 a las 13:05

A RewriteRule está bien, pero solo puedo ver que el resultado es un controlador completo que me obliga a mirrir los encabezados de tipo MIME y los encabezados de almacenamiento en caché (lo que no quiero hacer). - Martín pescador

No estoy buscando necesariamente una solución PHP. los CustomLog no está basado en PHP (pero no puedo usarlo). Estoy tratando de tener la mente lo más abierta posible porque creo que mis limitaciones son bastante severas, simplemente me parece extraño que no parece haber una solución fácil. No me gusta la forma en que @ quickshiftin envuelve un script PHP. - Martín pescador

@Fritsvacampen ¿No le gusta mi forma de invocar un script PHP pero está bien enrutando todas las solicitudes a través de un archivo PHP como sugirió yes123? ¿Cómo eso tiene sentido? - quickshiftin

@quickshiftin - Enrutar una solicitud a través de PHP está bien, porque tengo control sobre lo que hace PHP. Si yo ejecutar cualquier archivo binario aleatorio como PHP no tengo idea de lo que va a pasar. Me gusta el auto_append/prepend característica: la he usado con éxito (para la carga automática de cosas) en el pasado, pero no funciona en archivos que no son PHP. - Martín pescador

Crear una auto_prepend_file y defina una función para iniciar sesión w / e que desee. Necesitará acceso a .htaccess para configurarlos (y el servidor web necesitará algo como AllowOverride todo en el vhost) o con PHP 5.3 puede utilizar el función INI por directorio.

.htaccess

php_value auto_prepend_file /path/to/file.php

php.ini por directorio (PHP 5.3 CGI / Fast CGI SAPI)

user_ini.auto_prepend_file = /path/to/file.php

Luego, para su archivo /path/to/file.php (algo más elegante, estoy seguro;))

file_put_contents(
    LOG_FILE,
    implode(PHP_EOL . PHP_EOL, array(
                'SERVER: ' . PHP_EOL . print_r($_SERVER, true),
                'REQUEST: ' . PHP_EOL . print_r($_REQUEST, true)
            )),
    FILE_APPEND
);

La belleza de este enfoque es que probablemente podrá salirse con la suya y solo tendrá que definir / incluir el código de registro en un solo lugar.

EDIT:

En retrospección, veo que desea que esto funcione para tipos arbitrarios de archivos ... Sí, eso sería bastante tosco. Lo mejor que se me ocurre es etiquetar estos archivos como .php o definir tipos de mime personalizados en .htaccess. La idea sería ejecutar los archivos a través del intérprete de PHP, ejecutando así el auto_prepend_file y dado que no hay etiquetas PHP en el archivo, el contenido se envía directamente al cliente. Tal vez incluso un poquito de PHP encima de cada archivo de contenido configurando el Tipo de contenido encabezamiento. Ni siquiera estoy seguro de que funcionaría, pero podría funcionar.

Respondido el 17 de diciembre de 13 a las 10:12

¡no tiene acceso a .htaccess! - lugar de trabajo dinámico

Veo que ahora dice que no tiene acceso a la configuración del servidor. Originalmente lo tomé como una configuración de nivel de vhost, me sorprende que .htaccess ni siquiera esté disponible. - quickshiftin

Tengo acceso a .htacess, solo que no httpd.conf y php.ini. Que yo sepa php_auto_prepend funciona solo en archivos ejecutables PHP. Quiero registrar archivos binarios 'regulares'. - Martín pescador

No me siento realmente cómodo con ningún archivo aleatorio que se pase a través del analizador PHP. Aparte de los problemas de seguridad, parece un desperdicio. - Martín pescador

@FritsvanCampen Si estos archivos son los que ha puesto en el servidor, entonces no son aleatorios; y con el enfoque que he sugerido, los clientes nunca sabrían que hay PHP en ellos (suponiendo que la configuración de ContentType funcione correctamente). Además, ¿qué problemas de seguridad habría con PHP colocado encima de un archivo de datos que no estaría presente en ningún archivo PHP normal? Por último, con respecto al despilfarro, esté preparado para hacer algunos sacrificios en el rendimiento (y la elegancia de la implementación) dadas todas las restricciones en este escenario. - quickshiftin

Eso es bastante simple de hacer considerando que no necesita restringir el acceso.

construir una página logger.php que toma en entrada el archivo solicitado como:

logger.php?file=abc.exe

En los logger.php solo tiene que registrar este acceso y luego redirigir al archivo:

file_put_contents('log', $_GET['file'] . ' requested',FILE_APPEND);
header('Location: files/'.$_GET['file']);

Solo revisa el $_GET['file'] para archivos maliciosos

Por supuesto, debe reemplazar los enlaces en su sitio, desde:

<a href="files/abc.exe">

a

<a href="logger.php?file=abc.exe">

Respondido 04 Feb 12, 05:02

Luego, los clientes tienen que acceder a los archivos a través del script PHP. Quizás eso esté bien. Parecía que OP quería que los usuarios pudieran acceder a los archivos directamente y aún así poder iniciar sesión, pero en realidad no hay un punto específico al respecto. - quickshiftin

Esto resuelve el problema de tener que reflejar los tipos MIME y otros encabezados, pero no estoy seguro de cuál es el efecto en el almacenamiento en caché. - Martín pescador

Supongo que podría probar los efectos de un 301 Location encabezamiento. - Martín pescador

¿El efecto? Es solo una redirección oO - lugar de trabajo dinámico

Una simple búsqueda en Google le mostraría que las redirecciones 301 están perfectamente bien para todos los motores de búsqueda. Incluso Google le sugiere que utilice redirecciones 301 cuando sea posible. - lugar de trabajo dinámico

Parece que la intención aquí es eludir todos los sistemas que están inherentemente en su lugar en Apache y PHP. Si estas restricciones están realmente vigentes en la instancia de su servidor, es mucho mejor solicitar un cambio en sus privilegios que idear una solución alternativa que el administrador de su sistema puede o no estar contento con su implementación.

Respondido 08 Feb 12, 04:02

Estas son algunas de las restricciones con las que trabaja si obtiene un plan de alojamiento que no sea un servidor autogestionado. Aproximadamente el 99% de nosotros estamos lidiando con estas restricciones. Creo que he explicado suficientemente por qué no puedo usar algunas soluciones. - Martín pescador

Entiendo, sin embargo, estas restricciones están vigentes en estos entornos por razones muy específicas. Intentar hacer algo que parece que el administrador del servidor explícitamente no quiere que hagas me parece una mala idea. - PFY

Sí, pero no creo que el registro sea algo que un administrador no quiera que haga. Es solo que CustomLog es un server config ajuste de nivel (que creo que es un error). No creo que Apache se haya escrito con "seguridad a través de la falta de funciones" en mente. Bloquear el acceso al server config es una forma realmente conveniente de proteger su servidor: P - Martín pescador

Sí, por eso te recomiendo que te pongas en contacto con tu administrador. He tenido problemas como este en el pasado y, en la mayoría de los casos, cuando señala que la restricción no es razonable, puede aflojar un poco el cinturón. No te olvides de la navaja de Occam :) - PFY

Creo que dada la separación lógica entre Apache y PHP y la situación restrictiva en la que se encuentra, su única opción lógica es una que ya ha descartado; un script de controlador personalizado. Dado que el problema es lidiar con todos los problemas que pueden surgir, ¿ha buscado soluciones existentes como zubrag.com/scripts/download.php - PFY

Puede que no sea exactamente lo que desea, pero ¿por qué no usa una solución completamente diferente?

Puede utilizar Google Analytics VirtualPageviews para realizar un seguimiento de las descargas de archivos a través de Javascript.

Vea aquí para más información: http://support.google.com/googleanalytics/bin/answer.py?hl=en&answer=55529

Incluso puede crear su propio JS para rastrear las descargas de archivos a través del navegador sin tener que preocuparse por GA.

Noticias:

Como dije, podría crear fácilmente su propio JS para rastrearlos sin tener que preocuparse por GA. Aquí hay un ejemplo tonto en jQuery que funcionaría (no lo he probado, solo lo escribí en la parte superior de mi cabeza):

Muestra de código:

Lado JS:

$(document).ready(function() {
  $("a").click(function() {
    if( $(this).attr('href').match(/\/files\/(.*)/) ) {
      $.ajax({
        url: '/tracking/the/file/downloads.php'
        data: {
          'ok': 'let\'s',
          'add': 'some information',
          'about': 'the user that initiated',
          'the': 'request',
          'file': $(this).attr('href')
        }
      });
    }

    return true;
  });
});

Respondido 10 Feb 12, 17:02

He considerado esto, pero realmente no puedo usar esto porque quiero que los datos estén disponibles. Sé que hay API para Google Analytics (y las estoy usando para visitas de página regulares) pero no es adecuado para lo que intento hacer aquí. - Martín pescador

@FritsvanCampen Verifique mi respuesta actualizada. No es necesario ir con GA. Puede hacerlo manualmente y tener sus datos disponibles al instante. - mobius

Supongo que eso también podría funcionar. Sin embargo, veo algunos problemas: en primer lugar, tiene algunos gastos generales en cada solicitud, independientemente del almacenamiento en caché. En segundo lugar, debe volver a activar este controlador si está tratando con contenido ed ajax. No puede detectar usos de archivos si se refieren a otros dominios o una página que no incluye el código de seguimiento. Y, por último, tiene una dependencia de JavaScript, realmente no me importa esto, pero puede ser relevante. - Martín pescador

@FritsvanCampen Bueno, existe la solicitud adicional, es cierto, pero esto se puede optimizar en gran medida. Incluso podría omitir la solicitud AJAX y hacer que funcione usando la imagen mágica nginx 1x1 (wiki.nginx.org/HttpEmptyGifModule) y obtenga el registro desde allí. (que sería súper rápido) Esto requeriría un servidor separado, sí, pero por otro lado no contará hasta el máximo de solicitudes web realizadas por el navegador. También con jquery se encadenan los eventos. Entonces, si tuviera un oyente de clic () en todas las etiquetas, se activará sin importar qué otros clics haya vinculado en el enlace. - mobius

Funciona solo en el caso de mod_php. Hay algún impacto en el rendimiento: apache_lookup_uri () realiza una sub-solicitud interna adicional de Apache.

Como otros señalaron, necesita .htaccess como

RewriteEngine On
RewriteRule ^/handler.php$ - [L]
RewriteRule ^/([a-zA-Z0-9\.]+)$ /handler.php?filename=$1 [L]

En el archivo handler.php, use la función virtual () para realizar la subquest de Apache. Ejemplo aquí: http://www.php.net/manual/en/function.virtual.php#88722

Solución actualizada y probada (pero bastante mínima):

<?php
//add some request logging here
$file = $_GET["filename"];

$file_info = apache_lookup_uri($file);
header('content-type: ' . $file_info -> content_type);
// add other headers?
virtual($file);
exit(0);
?>

Respondido 10 Feb 12, 18:02

virtual no parece enviar los encabezados correctos, vea mi 'respuesta' - Martín pescador

Corregí este código para obtener (por apache_lookup_uri) y enviar los encabezados adecuados. - Kupson

¡Eso podría ser exactamente lo que necesito! - Martín pescador

Bien, aquí tienes una idea. Tenga paciencia conmigo, al principio puede parecer inadecuado, pero lea el fragmento al final. Ojalá funcione con lo que tienes. En la carpeta que contiene sus archivos, coloca un .htaccess que reescribe todas las solicitudes a un script de controlador PHP en el mismo directorio, algo como esto (no probado):

RewriteEngine On
RewriteRule ^/handler.php$ - [L]
RewriteRule ^/([a-zA-Z0-9\.]+)$ /handler.php?filename=$1 [L]

En el script PHP, realiza cualquier registro que sea necesario utilizando file_put_contents(). Luego, crea handler.php con este código:

<?php
if (!file_exists) {
    header("Status: 404 Not Found");
    //if you have a 404 error page, you can use an include here to show it
    exit(0);
}

header("Content-disposition: attachment; filename={$_GET["filename"]}");
header("Content-type: ".get_mime_type($_GET["filename"]));
readfile($filename);

function get_mime_type($filename, $mimePath = '/etc') {
    $fileext = substr(strrchr($filename, '.'), 1);
    if (empty($fileext)) return (false);
    $regex = "/^([\w\+\-\.\/]+)\s+(\w+\s)*($fileext\s)/i";
    $lines = file("$mimePath/mime.types");
    foreach($lines as $line) {
        if (substr($line, 0, 1) == '#') continue; // skip comments
        $line = rtrim($line) . " ";
        if (!preg_match($regex, $line, $matches)) continue; // no match to the extension
        return ($matches[1]);
    }
    return (false); // no match at all
}
?>

Básicamente, está creando una capa entre la solicitud del archivo y la entrega real del archivo. Esta capa de PHP registra el acceso al archivo y luego sirve el archivo. Dijiste que no querías perder el tiempo con los códigos de estado y los tipos MIME, pero la belleza de esto es que todo eso está arreglado. En caso de que el archivo no exista, solo genera un 404 estándar y puede incluir una página de error 404 personalizada. Sí, aquí se cambia el encabezado de estado, pero no es nada complicado. En cuanto a los tipos MIME, se detectan según las mismas reglas de tipo MIME que usa Apache. Apunte la función get_mime_type al archivo mime.types en su servidor. Si no sabe dónde está, simplemente descargue una copia de aquí. Lo admito, esta solución es probablemente más técnica de lo que estaba buscando, pero con las restricciones que tiene, es una buena solución. La mejor parte es que es completamente transparente para el usuario final, así como para aquellos que suben cosas.

Respondido 08 Feb 12, 19:02

Es exactamente el tipo de manejador que yo no querer. No tengo un camino a un mime.types expediente. No está generando ningún encabezado de almacenamiento en caché ni ETag. los Content-disposition: attachment El encabezado también es incorrecto porque obliga a los navegadores a descargar el archivo. Y file_put_contents, incluso con el FILE_APPEND flag, no es atómico, pero supongo que hay formas de evitarlo. - Martín pescador

El único monitoreo discreto que podría hacer sin filtrar cosas a través de PHP sería verificar todos los archivos y anotar sus tiempos de acceso a archivos cada vez que se solicita un archivo PHP (simplemente agrega una función a tus archivos php o usa una reescritura). Incurrirá un poco en gastos generales, pero es la única estadística discreta que puede obtener.

Obviamente, de esta manera no se pueden obtener números exactos de accesos, sino más frecuencias similares, por lo que también es una especie de estadística (viable). Para obtener algo como números de visita (esto se abrió 1000k veces el 25 de marzo a las 2 a.m.), debe tener acceso a los registros o canalizarlo todo a través de un script PHP o cgi; algo solo tiene que hacer el conteo manual.

Respondido 09 Feb 12, 08:02

Interesante. El único problema que preveo es que el uso del explorador de archivos en el backend de mi aplicación podría 'tocar' los archivos también, por lo que fileatime no será precisa. Es de nivel demasiado bajo (nivel de SO). - Martín pescador

Suponiendo que está utilizando PHP como un módulo de Apache compilado, la función virtual () podría hacer que esto suceda. Ver: http://www.php.net/manual/en/function.virtual.php

<?php

$fn = $_GET['fn'];

log_file_access($fn); // You define how you want this to happen    
virtual($fn);

Luego, hace referencia a los archivos a través de:

http://example.com/file.php?fn=files/lolcat.jpg

Respondido 10 Feb 12, 10:02

Sin embargo, eso es interesante citando uno de los comentarios de los usuarios en el manual de php: "El problema con la mayoría de los scripts publicados a continuación es que virtual () vacía los encabezados pendientes antes de realizar la subquest. Solicitar una imagen con virtual () todavía devuelve un tipo de texto / html documento." - Bathz

virtual no parece enviar los encabezados correctos, vea mi 'respuesta'. - Martín pescador

He intentado muchas cosas y parece que no hay una solución fácil.

Mi solución usa el Location truco de encabezado propuesto por @ yes123 pero lo modifiqué para que coincida con mis preferencias.

Los enlaces a los archivos se mantienen intactos, por lo que sigue siendo: /files/path/to/my/file.abc Tengo un RewriteRule:

RewriteRule ^files/(.*) path/to/tracker.php?path=/$1

Luego, en el archivo, publico un Location encabezado agregando ?track=no a la URL y una excepción a la anterior RewriteRule:

RewriteCond %{QUERY_STRING} !(&|^)track=no(&|$)

Agregué una optimización más. He habilitado E-Tags, así que si el cliente envía un encabezado de E-Tag, vea si coincide con el archivo y devuelva un 304 Not Modified en lugar de un Location.

$fs = stat($document_root . $path);
$apache_etag = calculate_apache_etag($fs);
if ((isset($_SERVER["HTTP_IF_MATCH"]) && etag_within_range($_SERVER["HTTP_IF_MATCH"], $apache_etag))
    || (isset($_SERVER["HTTP_IF_NONE_MATCH"]) && etag_within_range($_SERVER["HTTP_IF_NONE_MATCH"], $apache_etag))
) {
    header("ETag: " . $apache_etag, true, 304);
    exit;
}

function etag_within_range($etag1, $etag2) {
    list($size1, $mtime1) = explode("-", $etag1);
    list($size2, $mtime2) = explode("-", $etag2);
    $mtime1 = floor(hexdec($mtime1) / 1000000);
    $mtime2 = floor(hexdec($mtime2) / 1000000);
    return $mtime1 === $mtime2 && $size1 === $size2;
}

E implementación para calculate_apache_etag se puede encontrar aquí: ¿Cómo se hace un etag que coincida con Apache?

etag_withing_range resuelve el problema de comparar con una mayor precisión mtime en Apache.


Notas sobre soluciones que no funcionaron

virtual

Guión de prueba:

var_dump(apache_response_headers());
virtual("/path/to/image.jpg");
var_dump(apache_response_headers());

Salidas:

array(1) { ["X-Powered-By"]=> string(10) "PHP/5.2.11" }
[[binary junk]]
array(5) { ["X-Powered-By"]=> string(10) "PHP/5.2.11" ["Keep-Alive"]=> string(18) "timeout=5, max=100" ["Connection"]=> string(10) "Keep-Alive" ["Transfer-Encoding"]=> string(7) "chunked" ["Content-Type"]=> string(9) "text/html" }

Content-Type: text/html reaaaaalllly? :(

Quizás PHP5.3's header_remove función puede resolver esto? No lo he intentado.

contestado el 23 de mayo de 17 a las 14:05

deberías asignar tu recompensa a alguien - lugar de trabajo dinámico

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.