sql >> Base de Datos >  >> RDS >> Mysql

php cómo vincular un archivo del servidor de archivos a esa información de la base de datos

Pensé que escribiría una "respuesta" breve (para mí, esto es breve) solo para poder resumir mis puntos.

Algunas "mejores prácticas" al crear un sistema de almacenamiento de archivos. El almacenamiento de archivos es una categoría amplia, por lo que su kilometraje puede variar para algunos de estos. Tómelos como sugerencia de lo que encontré que funciona bien.

Nombres de archivo No almacene el archivo con el nombre que le dio un usuario final. Pueden usar y usarán todo tipo de personajes de mierda que te harán la vida imposible. Algunos pueden ser tan malos como ' comillas simples, que en Linux básicamente hace que sea imposible leer, o incluso eliminar el archivo (directamente). Algunas cosas pueden parecer simples como un espacio, pero dependiendo de dónde lo use y del sistema operativo de su servidor, podría terminar con one%20two.txt o one+two.txt o one two.txt que pueden o no crear todo tipo de problemas en sus enlaces.

Lo mejor que puedes hacer es crear un hash, algo así como sha1 esto puede ser tan simple como {user_id}{orgianl_name} El nombre de usuario hace que sea menos probable que haya colisiones con los nombres de archivo de otros usuarios.

Prefiero hacer file_hash('sha1', $contents) de esa manera, si alguien carga el mismo archivo más de una vez, puede capturarlo (el contenido es el mismo, el hash es el mismo). Pero si espera tener archivos grandes, es posible que desee hacer una evaluación comparativa para ver qué tipo de rendimiento tiene. Principalmente manejo archivos pequeños, por lo que funciona bien para eso. Tenga en cuenta que con la marca de tiempo, el archivo aún se puede guardar porque el nombre completo es diferente, pero lo hace bastante fácil de ver y se puede verificar en la base de datos.

Independientemente de lo que haga, lo prefijaría con una marca de tiempo time().'-'.$filename . Es útil tener esta información, porque es la hora absoluta en que se creó el archivo.

En cuanto al nombre que un usuario le da al archivo. Simplemente guárdelo en el registro de la base de datos. De esta manera, puede mostrarles el nombre que esperan, pero use un nombre que sepa que siempre es seguro para los enlaces.

$filename ='algo de mierda^ fileane.jpg';

$ext = strrchr($filename, '.');

echo "\nExt: {$ext}\n";

$hash = sha1('some crapy^ fileane.jpg');

echo "Hash: {$hash}\n";

$time = time();

echo "Timestamp: {$time}\n";

$hashname = $time.'-'.$hash.$ext;

echo "Hashname: $hashname\n";

Salidas

Ext: .jpg
Hash: bb9d2c2c7c73bb8248537a701870e35742b41c02
Timestamp: 1511853063
Hashname: 1511853063-bb9d2c2c7c73bb8248537a701870e35742b41c02.jpg

Puedes probarlo aquí

Caminos nunca almacene la ruta completa al archivo. Todo lo que necesita en la base de datos es el hash de la creación del nombre con hash. La ruta "raíz" a la carpeta en la que está almacenado el archivo debe hacerse en PHP. Esto tiene varios beneficios.

  • evita la transferencia de directorios. Debido a que no pasa ninguna parte del camino a su alrededor, no tiene que preocuparse tanto de que alguien resbale un \..\.. allí y yendo a lugares que no deberían. Un mal ejemplo de esto sería que alguien sobrescribiera un .htpassword archivo cargando un archivo llamado así con el directorio transversal en él.
  • Tiene enlaces de aspecto más uniforme, tamaño uniforme, conjunto de caracteres uniforme.

https://en.wikipedia.org/wiki/Directory_traversal_attack

  • Mantenimiento. Los caminos cambian, los servidores cambian. Las demandas en su cambio de sistema. Si necesita reubicar esos archivos, pero almacenó la ruta completa absoluta a ellos en la base de datos, se atascó pegando todo junto con symlinks o actualizar todos sus registros.

Hay algunas excepciones a esto. Si desea almacenarlos en una carpeta mensual o por nombre de usuario. Puede guardar esa parte de la ruta, en un campo separado. Pero incluso en ese caso, podría crearlo dinámicamente en función de los datos guardados en el registro. He descubierto que es mejor guardar la menor información de ruta posible. Y ellos hacen una configuración o una constante que puede usar en todos los lugares que necesita para poner la ruta al archivo.

También la path y el link son muy diferentes, por lo que al guardar solo el nombre, puede vincularlo desde cualquier página PHP que desee sin tener que restar datos de la ruta. Siempre me ha parecido más fácil agregar al nombre del archivo que restar de una ruta.

Base de datos (solo algunas sugerencias, el uso puede variar) Como siempre con los datos, pregúntese quién, qué, dónde, cuándo

  • identificación - int incremento automático de clave principal
  • id_usuario - int clave foránea, quién lo subí
  • hachís - char[40] *sha1*, unique que el hachís
  • nombre hash - varchar {timestampl}-{hash}.{ext} dónde el nombre de los archivos en el disco duro
  • nombre de archivo - varchar el nombre original dado por el usuario, de esa manera podemos mostrarles el nombre que esperan (si eso es importante)
  • estado - enum[public,private,deleted,pending.. etc] estado del archivo, dependiendo de su caso de uso, es posible que deba revisar los archivos, o tal vez algunos sean privados y solo el usuario pueda verlos, tal vez algunos sean públicos, etc.
  • fecha_estado - timestamp|datetime momento en que se cambió el estado.
  • crear_fecha - timestamp|datetime cuándo hora en que se creó el archivo, se prefiere una marca de tiempo, ya que facilita algunas cosas, pero en ese caso debería ser el mismo uso de marca de tiempo en el nombre hash.
  • tipo - varchar - tipo mime, puede ser útil para configurar el tipo mime al descargar, etc.

Si espera que diferentes usuarios carguen el mismo archivo y usa el file_hash puedes hacer el hash campo un índice único combinado del user_id y el hash de esta forma, solo entraría en conflicto si el mismo usuario subiera el mismo archivo. También puede hacerlo en función de la marca de tiempo y el hash, según sus necesidades.

Eso es lo básico que se me ocurrió, esto no es absoluto, solo algunos campos que pensé que serían útiles.

Es útil tener el hash solo, si lo almacena solo, puede almacenarlo en un CHAR(40) para sha1 (ocupa menos espacio en la base de datos que VARCHAR ) y establezca la intercalación en UTF8_bin que es binario. Esto hace que las búsquedas distingan entre mayúsculas y minúsculas. Aunque hay poca posibilidad de una colisión de hash, esto agrega un poco más de protección porque los hash son letras mayúsculas y minúsculas.

Siempre puedes construir el hashname sobre la marcha si almacena la extensión y la marca de tiempo por separado. Si se encuentra creando cosas una y otra vez, es posible que desee almacenarlas en la base de datos para simplificar el trabajo en PHP.

Me gusta simplemente poner el hash en el enlace, sin extensión ni nada, así que mis enlaces se ven así.

http://www.example.com/download/ad87109bfff0765f4dd8cf4943b04d16a4070fea

Muy simple, muy genérico, seguro en las URL siempre del mismo tamaño, etc.

El hashname para este "archivo" sería así

1511848005-ad87109bfff0765f4dd8cf4943b04d16a4070fea.jpg

Si tiene conflictos con el mismo archivo y un usuario diferente (que mencioné anteriormente). Siempre puede agregar la parte de la marca de tiempo en el enlace, el ID de usuario o ambos. Si usa el ID_usuario, podría ser útil rellenarlo con ceros a la izquierda. Por ejemplo, algunos usuarios pueden tener ID:1 y algunos pueden ser ID:234 por lo que podría dejar el relleno en 4 lugares y convertirlos en 0001 y 0234 . Luego agregue eso al hash, que es casi imperceptible:

1511848005-ad87109bfff0765f4dd8cf4943b04d16a4070fea0234.jpg

Lo importante aquí es que porque sha1 siempre es 40 y el id siempre es 4 podemos separar los dos con precisión y facilidad. Y de esta manera, aún puede buscarlo de forma única. Hay muchas opciones diferentes, pero mucho depende de tus necesidades.

Acceso Como la descarga. Siempre debe generar el archivo con PHP, no les dé acceso directo al archivo. La mejor manera es almacenar los archivos fuera de webroot (arriba de public_html o www carpeta). Luego, en PHP, puede establecer los encabezados en el tipo correcto y básicamente leer el archivo. Esto funciona para casi todo excepto el video. No manejo videos, así que ese es un tema fuera de mi experiencia. Pero creo que es mejor pensar que todos los datos del archivo son texto, son los encabezados los que convierten ese texto en una imagen, un archivo de Excel o un pdf.

La gran ventaja de no darles acceso directo al archivo es que si tiene un sitio de membresía, o no quiere que su contenido sea accesible sin iniciar sesión, puede verificar fácilmente en PHP si iniciaron sesión antes de darles el contenido. Y, como el archivo está fuera de webroot, no pueden acceder a él de ninguna otra manera.

Lo más importante es elegir algo consistente, que aún sea lo suficientemente flexible para manejar todas sus necesidades.

Estoy seguro de que se me ocurren más, pero si tienes alguna sugerencia, no dudes en comentar.

FLUJO DE PROCESO BÁSICO

  1. El usuario envía el formulario (enctype="multipart/form-data" )

https://www.w3schools.com/tags/att_form_enctype.asp

  1. El servidor recibe la publicación del formulario, Super Globals $_POST y el $_FILES

http://php.net/manual/en/reserved.variables.files .php

$_FILES = [
 'fieldname' => [
        'name' => "MyFile.txt" // (comes from the browser, so treat as tainted)
        'type' => "text/plain" //  (not sure where it gets this from - assume the browser, so treat as tainted)
        'tmp_name' => "/tmp/php/php1h4j1o" // (could be anywhere on your system, depending on your config settings, but the user has no control, so this isn't tainted)
        'error' => "0" //UPLOAD_ERR_OK  (= 0)
        'size' => "123" //   (the size in bytes)
    ]
 ];
  1. Buscar errores if(!$_FILES['fielname']['error'])

  2. Desinfecte el nombre para mostrar $filename = htmlentities($str, ENT_NOQUOTES, "UTF-8");

  3. Guardar archivo, crear registro DB (PSUDO-CODE)

Así:

 $path = __DIR__.'/uploads/'; //for exmaple

$time = time();
$hash = hash_file('sha1',$_FILES['fielname']['tmp_name']);
$type = $_FILES['fielname']['type'];
$hashname = $time.'-'.$hash.strrchr($_FILES['fielname']['name'], '.');
$status = 'pending';

if(!move_uploaded_file ($_FILES['fielname']['tmp_name'], $path.$hashname  )){
     //failed
     //do somehing for errors.
     die();
}


//store record in db

http://php.net/manual/en/function.move -archivo-cargado.php

  1. Cree un enlace (varía según el enrutamiento), la forma simple es hacer su enlace así http://www.example.com/download?file={$hash} pero es más feo que http://www.example.com/download/{$hash}

  2. el usuario hace clic en el enlace para ir a la página de descarga.

obtener INPUT y buscar registro

$hash = $_GET['file'];

$stmt = $PDO->prepare("SELECT * FROM attachments WHERE hash = :hash LIMIT 1");  
$stmt->execute([":hash" => $hash]);

$row = $stmt->fetch(PDO::FETCH_ASSOC);

print_r($row);

http://php.net/manual/es/intro.pdo.php

Etc....

¡Salud!