Introduzione
Credo sia utile spiegare come memorizzare file binari (come immagini, file compressi, ecc..) in un Database (MySql nel nostro caso), per poi recuperarli per vari scopi (ad esempio per scaricarli).
Alcune nozioni prendono spunto dall'articolo di Florian Dittmer, pubblicato su phpbuilder.com, che comunque, stando alle varie discussioni sui forum, ha dato qualche problema nelle sue applicazioni pratiche.
Le ragioni per effettuare lo storage di un file in un DB anzichè in una directory, possono essere molteplici; una di queste è certamente la necessità di proteggere adeguatamente i files da visualizzazioni o download indesiderati o non autorizzati, risultato che si ottiene in modo più facile e, forse, più efficace con il metodo che si sta proponendo. Pensiamo infatti, riprendendo l'esempio di cui sopra, all'ipotesi in cui si volesse consentire soltanto ad utenti registrati la possibilità di scaricare dei files da una zona protetta; appare evidente che avere i files stessi all'interno del Database, assicurerebbe una protezione ed una efficacia maggiore rispetto all'alternativa di avere gli stessi in qualche directory sperduta all'interno del disco rigido.
Cercheremo quindi di costruire uno script che ci consenta di inserire il file desiderato nel db, tramite Upload con metodo POST (su cui vi rimando al Upload con Php presente in questa sezione), e che successivamente ci permetta di scaricare il file stesso, magari dopo un'autenticazione, o di visualizzarlo.
Creazione della tabella
Anzitutto dobbiamo realizzare Il Database e la relativa tabella destinata a contenere il file uploadato (lo faremo tramite script, ma naturalmente si può fare da linea di comando, o tramite tools quali phpMyAdmin).
Ecco il codice PHP necessario per compiere queste operazioni (da copiare ed incollare in un file di testo da salvare come quello_che_vuoi.php):
<?php
// CONNESSIONE AL MYSQL
@mysql_connect("host", "user", "password") or die("Connessione fallita !");
// CREAZIONE DATABASE
@mysql_create_db("FileBinari") or die("Creazione Database fallita !");
// SELEZIONE DATABASE
@mysql_select_db("FileBinari") or die("Selezione Database fallita !");
// CREAZIONE TABELLA
@mysql_query("
CREATE TABLE file_binari (
Id int(4) NOT NULL auto_increment,
Descrizione varchar(255) NOT NULL default '',
DatiBinari mediumblob NOT NULL,
Nome varchar(50) NOT NULL default '',
Size int(10) NOT NULL default '0',
Type varchar(50) NOT NULL default '',
PRIMARY KEY (Id)
)
") or die("Creazione Tabella fallita !");
?>
Il campo destinato ad accogliere i dati binari (DatiBinari) è di tipo mediumblob e può contenere fino a 1,6 Megabyte di dati (dovrebbe essere sufficiente, ma volendo si può optare per il tipo longblob, che può contenere fino a 4,2 Gigabyte di dati. Nel caso di file superiori ai due Mega, ci si dovrà preoccupare di cambiare il settaggio del parametro upload_max_filesize nel file php.ini, che di default è 2 Megabyte.
Inseriamo il file
Una volta creati il Database e la tabella, possiamo occuparci della pagina che conterrà sia il modulo html, che ci consentirà di fare l'Upload del file, sia il codice PHP che processerà il contenuto del form e provvederà ad inserire il file uploadato nel DB (anche qui, copiare ed incollare in un file di testo da salvare come quello_che_vuoi.php):
<?php
if(!isset($_POST)) $_POST = $HTTP_POST_VARS;
$self = isset($_SERVER) ? $_SERVER["PHP_SELF"] : $HTTP_SERVER_VARS["PHP_SELF"];
if($_POST["invia"]) {
// CONNESSIONE AL MYSQL
@mysql_connect("host", "user", "password") or die("Connessione fallita !");
// SELEZIONE DATABASE
@mysql_select_db("FileBinari") or die("Selezione Database fallita !");
// MEMORIZZIAMO NELLA VARIABILE $data IL CONTENUTO DEL FILE
$data = addslashes(fread(fopen($_FILES["file_binario"]["tmp_name"], "rb"), $_FILES["file_binario"]["size"]));
// ESEGUIAMO LA QUERY DI INSERIMENTO
$result = @mysql_query("INSERT INTO file_binari (Descrizione, DatiBinari, Nome, Size, Type)
VALUES ('" . $_POST["Descrizione"] . "','$data','" . $_FILES["file_binario"]["name"] . "',
'" . $_FILES["file_binario"]["size"] . "','" . $_FILES["file_binario"]["type"] . "')")
or die("Query di inserimento fallita !");
// ESITO POSITIVO
echo "Il file " . basename($_FILES["file_binario"]["name"]) . " è stato correttamente inserito nel Database.";
// CHIUDIAMO LA CONNESSIONE A MYSQL
@mysql_close();
} else {
echo "
<html>
<head>
<title>Form per l'inserimento</title>
</head>
<body>
<div align="center">
<table>>
<form action="$self" method="POST" enctype="multipart/form-data">
<tr>
<td valign="top">Descrizione </td>
<td><textarea name="Descrizione" cols="40" rows="5"></textarea></td>
</tr>
<tr>
<td>File</td>
<td><input type="file" name="file_binario" size="40"></td>
</tr>
<tr>
<td colspan="2" valign="bottom" align="center" height="30">
<input type="submit" value="Invia il file" name="invia"></td>
</tr>
</form>
</table>
</div>
</body>
</html>
";
}
?>
La parte che voglio rimarcare di questo codice (non mi soffermo sul modulo HTML, ovviamente, nè sull'inserimento vero e proprio nel Database su cui si possono guardare le guide presenti in questo sito), è la lettura e la "preparazione" del file all'inserimento. Questo avviene grazie alla funzione fread(), che salvaguarda la corrispondenza binaria, ed alla funzione addslashes(), che fa l'escape (aggiungendo un backslash) dei caratteri che potrebbero dare "fastidio" (apici singoli, apici doppi e lo stesso backslash).
Inseriamo qualche file di esempio nel Database (possibilmente di tipi diversi, magari qualche immagine e qualche altro tipo di file).
Visualizzare o scaricare il file
A questo punto abbiamo bisogno del codice per visualizzare e/o scaricare questi files ma, siccome siamo dei bravi programmatori, prepareremo uno script che a seconda del tipo proponga o meno l'opzione "visualizza" oltre a "scarica".
Ecco il possibile codice della pagina che visualizzerà i files con le opzioni(da copiare ed incollare in un file di testo da salvare come quello_che_vuoi.php):
<?php
// CONNESSIONE AL MYSQL
@mysql_connect("host", "user", "password") or die("Connessione fallita !");
// SELEZIONE DATABASE
@mysql_select_db("FileBinari") or die("Selezione Database fallita !");
// TIPI DI FILE VISUALIZZABILI
$visualizzabili = array("image/jpeg", "image/pjpeg", "image/gif", "image/png");
$query = "SELECT * FROM file_binari";
$select = @mysql_query($query) or die("Query fallita !");
echo "
<div align="center">
<table>
";
while($result = @mysql_fetch_array($select)) {
echo " <tr>n <td align="center">" . $result["Nome"];
echo "<font size="1">(" . $result["Size"] . " bytes)</font>";
if($result["Descrizione"]) echo "<br>" . $result["Descrizione"];
echo "</td>n <td valign="top"> <a href="actions.php?action=download";
echo "&Id=" . $result["Id"] . "" target="_blank">download</a> </td>n";
echo " <td valign="top">";
if(in_array($result["Type"], $visualizzabili)) {
echo " <a href="actions.php?action=view&Id=" . $result["Id"] . "" ";
echo "target="_blank">visualizza</a> ";
}
echo "</td>n </tr>n";
}
echo "</table>n</div>n";
// CHIUDIAMO LA CONNESSIONE A MYSQL
@mysql_close();
?>
Molto semplice: estraiamo tutto il contenuto del Database e lo visualizziamo, a seconda poi che il file sia o meno di un tipo compreso nell'array $visualizzabili (immagini .gif, .jpg e .png), viene o non viene proposta l'opzione "visualizza" (l'opzione download è comunque offerta).
Problemi di Content-Type
Ed ecco il possibile codice della pagina actions.php che provvederà a visualizzare o a far scaricare il file (da copiare ed incollare in un file di testo da salvare come actions.php):
<?php
if(!isset($_GET)) $_GET = $HTTP_GET_VARS;
if($_GET["action"] && $_GET["Id"] && is_numeric($_GET["Id"])) {
// CONNESSIONE AL MYSQL
@mysql_connect("host", "user", "password") or die("Connessione fallita !");
// SELEZIONE DATABASE
@mysql_select_db("FileBinari") or die("Selezione Database fallita !");
switch($_GET["action"]) {
// VISUALIZZAZIONE
case "view" :
$query = "SELECT DatiBinari, Type FROM file_binari WHERE Id = '" . $_GET["Id"] . "'";
$select = @mysql_query($query) or die("Query fallita !");
$result = @mysql_fetch_array($select);
$data = $result["DatiBinari"];
$type = $result["Type"];
Header("Content-type: $type");
echo $data;
break;
// DOWNLOAD
case "download" :
$query = "SELECT DatiBinari, Nome, Type FROM file_binari WHERE Id = '" . $_GET["Id"] . "'";
$select = @mysql_query($query) or die("Query fallita !");
$result = @mysql_fetch_array($select);
$data = $result["DatiBinari"];
$name = $result["Nome"];
$type = $result["Type"];
// SE IL BROWSER È INTERNET EXPLORER
if(ereg("MSIE ([0-9].[0-9]{1,2})", $_SERVER["HTTP_USER_AGENT"])) {
header("Content-Type: application/octetstream");
header("Content-Disposition: inline; filename=$name");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Pragma: public");
} else {
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=$name");
header("Expires: 0");
header("Pragma: no-cache");
}
echo $data;
break;
default :
// DEFAULT CASE, NESSUNA AZIONE
break;
} // endswitch
// CHIUDIAMO LA CONNESSIONE
@mysql_close();
} //endif
?>
Come detto, la pagina chiamata actions.php visualizza o forza il download del file desiderato. A quest'ultimo proposito però vanno fatte alcune considerazioni in relazione al Content-Type octet-stream (o octetstream come preferisce, per oscure ragioni, Internet Explorer), che può essere considerato come il tipo "standard" per i file binari.
Octet Stream vuol dire, letteralmente, "flusso di ottetti", ossia flusso di numeri a otto bit.
Ebbene il problema sorge perchè si è lontani dall'affermarsi di uno standard in tema di Content-Type, per cui quando si inviano intestazioni di questo tipo, occorre porre attenzione ai vari browser che queste intestazioni possono ricevere; a questo proposito ecco i browser (su piattaforma Windows e Linux) su cui ho testato con successo questo script:
WINDOWS: Explorer 5.5 e Mozilla 1.1
LINUX: Konqueror 2.2.1, Mozilla 1.1, Galeon 1.2.5, Netscape Communicator 4.78
I soli problemi riscontrati hanno riguardato il download dei file su OPERA (6.05 per Windows e 6.03 per Linux) il quale, nonostante il Content-Type octet-stream fosse perfettamente riconosciuto, se si trattava di un'immagine .xxx ignorava del tutto il Content-Type passato e si comportava come se questo fosse stato image/xxx, basandosi sul riconoscimento dell'estensione e questo nonostante la relativa opzione non fosse stata settata.
Conclusioni
A conclusione di questo articolo, voglio segnalarvi anche un altro modo per visualizzare le immagini. Inserire il link alla pagina che abbiamo chiamato actions.php (passando nella query string anche l'ID e l'action uguale a view, naturalmente) all'interno del tag <img> (anche all'interno di una normale pagina .HTML). Per vedere quanto questo sia facile, provate a sostituire, all'interno del codice della pagina che ci offre l'opportunità di scaricare e/o visualizzare i files, il ciclo while ivi presente con questo:
while($result = @mysql_fetch_array($select)) {
if(in_array($result["Type"], $visualizzabili)) {
echo " <tr>n <td align="center"><img src="actions.php?action=view&Id=" . $result["Id"] . "">";
echo "</td>n </tr>n";
}
}