Forcer le téléchargement d’un PDF au lieu de l’ouvrir dans le navigateur. Tout passe par l’entête HTTP qui identifie le type de chaîne que le serveur doit retourner.
Il suffit d’un clic droit sur le lien pour forcer le téléchargement, pourquoi se donner autant de mal? Parce que c’est bien plus qu’un simple téléchargement. C’est aussi intercepter une requête HTTP et utiliser les ressources du serveur avant de retourner le fichier. Et les applications sont nombreuses.
- Protégez vos images, dans un dossier sous la racine du serveur qui autorise le téléchargement une fois connecté par exemple;
- crypter ou décrypter vos documents avant de les télécharger;
- compter le nombre d’ouverture d’un fichier X par un usager Y;
- etc.
Exemple d’en-tête HTTP envoyé avec une page web par un serveur Apache.
HTTP/1.0 200 OK
Date: Fri, 31 Dec 1999 23:59:59 GMT
Server: Apache/0.8.4
Content-Type: text/html
Content-Length: 59
Expires: Sat, 01 Jan 2000 00:59:59 GMT
Last-modified: Fri, 09 Aug 1996 14:21:40 GMT
<title>Exemple</title>
<p>Ceci est une page d’exemple.</p>
Le « ContentType »
Introduit avec le protocole HTTP 1.0, l’utilisation d’en-têtes spécifiés par le Content-Type
permet non seulement de transmettre un document de type text/html comme l’exemple précédent mais aussi binaire (application/octet-stream). La plupart des types de fichier ont leurs propres types MIMES, par exemple un fichier de type application/pdf
, mais les types peuvent réagir différemment selon le navigateur.
EN ASP on peut simuler ou forcer un type de contenu avec Response.ContentType
. NOTE : avant IIS5 Il fallait absolument placer les instructions tout en haut de la page, avant tout autre code.
Response.ContentType = "application/octet-stream"
Différents types MIME
dim sContentType
Select Case sExtension
Case ".asf"
sContentType = "video/x-ms-asf"
Case ".avi"
sContentType = "video/avi"
Case ".doc"
sContentType = "application/msword"
Case ".zip"
sContentType = "application/zip"
Case ".xls"
sContentType = "application/vnd.ms-excel"
Case ".gif"
sContentType = "image/gif"
Case ".jpg", "jpeg"
sContentType = "image/jpeg"
Case ".wav"
sContentType = "audio/wav"
Case ".mp3"
sContentType = "audio/mpeg3"
Case ".mpg", "mpeg"
sContentType = "video/mpeg"
Case ".rtf"
sContentType = "application/rtf"
Case ".htm", "html"
sContentType = "text/html"
Case ".asp"
sContentType = "text/asp"
Case ".pdf"
sContentType = "application/pdf"
Case Else
sContentType = "application/octet-stream"
End Select
Response.ContentType = sContentType
Ajouter l’en-tête
On a un type de document mais il faut aussi spécifier le fichier à télécharger en ajoutant une couche à l’en-tête avec AddHeader
. Attention, le fichier n’est pas encore physiquement attaché à la réponse. Il ne s’agit, encore une fois, que d’indication au navigateur, à savoir « comment traité le contenu ». Nous pourrions aussi spécifier le poids avec Content-Length
. Notez en passant le délai d’exécution du script (Server.ScriptTimeout) qui ajouter 18000 secondes. :
Response.Buffer = true
Server.ScriptTimeout = 18000
// Vide le « tampon http »
Response.Clear
// Indique un nouveau type de contenu
Response.ContentType = "application/octet-stream"
// Indique un fichier à télécharger avec une suggestion de nom
Response.AddHeader "Content-Disposition", "attachment; filename=document.pdf"
...
// Envoie la réponse
Response.Flush
Faille de sécurité
Cette technique permet de télécharger tout document à partir d’une page web ASP. Un .exe pourra être exécuté hors contexte, un code ASP pourrait être capturé. Vous devez protéger vos fichiers, ASP entres autres, pour éviter qu’un internaute astucieux ne détourne votre script à des fins malhonnêtes. Forcer le téléchargement d’un fichier .asp par exemple permet d’ouvrir la page sans exécuter le code ASP qui sera tout simplement retourné en texte clair à l’internaute! Il faut donc s’assurer de n’accepter que les bons types de documents, essentiellement en vérifiant l’extension et surtout en limitant l’accès à un dossier. Par exemple un dossier /doc/. N’ENVOYEZ JAMAIS LE CHEMIN D’ACCÈS (PATH) VIA UNE REQUÊTE HTTP.
Une bonne pratique consiste à ouvrir le fichier au préalable à l’aide de l’objet FSO, vérifier tout d’abord que le fichier existe puis récupérer l’extension.
<%
// twFichierValide([Nom de fichier], [dossier], [extensions valide])
dim sFichier : sFichier = twFichierValide(request.querystring("f"),"/documents/doc/","pdf,doc,jpg,gif,png,xml")
if sFichier <> "" then
// fichier accepté
else
response.write "Erreur"
end if
function twFichierValide(sFichierTemp,sDossier,sExtensions)
twFichierValide = ""
if sFichierTemp <> "" then
dim oFso : Set oFso = Server.CreateObject("Scripting.FileSystemObject")
dim sCheminFichier : sCheminFichier = Server.MapPath(sDossier) & "\" & sFichierTemp
if oFso.FileExists(sCheminFichier) then
dim oFichier : Set oFichier = oFso.GetFile(sCheminFichier)
dim sExt : sExt = oFso.GetExtensionName(sCheminFichier)
if InStr(sExtensions,sExt) > 0 then
twFichierValide = sCheminFichier
exit function
end if
set oFichier = nothing
end if
set oFso = nothing
end if
End function
%>
J’ai utilisé un simple instr
pour tester l’extension. On aurait pu utiliser la méthode du « split » dans une matrice ou l’usage d’expressions régulières qui demande autant sinon plus de trouble!
Le contenu
Voilà pour l’en-tête, le fichier physique maintenant. On a donc un fichier valide avec son chemin d’accès. Il reste seulement à ouvrir le fichier dans un stream
et l’afficher en binaire avec Response.BinaryWrite
. Puis on « flush » le tampon http pour envoyer la réponse, c’est-à-dire les en-têtes, et le fichier en binaire. Le navigateur se bornera à traiter le fichier comme l’en-tête le suggère.
dim Stream
Set oStream = Server.CreateObject("ADODB.Stream")
oStream.Open
oStream.Type = 1
Response.CharSet = "UTF-8"
oStream.LoadFromFile(sFichier)
Response.BinaryWrite(oStream.Read)
Response.flush
oStream.Close
Set oStream = Nothing
Code complet : twTelechargement.asp
Il suffit de passer le nom de fichier dans la requête : twTelechargement.asp?f=monFichier.pdf
<%
Response.Buffer = true
Server.ScriptTimeout = 18000
dim sFichier : sFichier = twFichierValide(request.querystring("f"), "/documents/doc/", "pdf,doc,jpg,gif,png,xml")
if sFichier = "" then
Response.Clear
Response.Write("Aucun fichier!")
Response.End
end if
function twFichierValide(sFichierTemp,sDossier,sExtensions)
twFichierValide = ""
if sFichierTemp <> "" then
dim oFso : Set oFso = Server.CreateObject("Scripting.FileSystemObject")
dim sCheminFichier : sCheminFichier = Server.MapPath(sDossier) & "\" & sFichierTemp
if oFso.FileExists(sCheminFichier) then
dim oFichier : Set oFichier = oFso.GetFile(sCheminFichier)
dim sExt : sExt = oFso.GetExtensionName(sCheminFichier)
if InStr(sExtensions,sExt) > 0 then
Response.Clear
Response.ContentType = "application/octet-stream"
Response.AddHeader "Content-Disposition", "attachment; filename="&oFichier.name
Response.AddHeader "Content-Length", oFichier.Size
dim oStream
Set oStream = Server.CreateObject("ADODB.Stream")
oStream.Open
oStream.Type = 1 // Binaire
Response.CharSet = "UTF-8"
oStream.LoadFromFile(sCheminFichier)
Response.BinaryWrite(oStream.Read)
Response.flush
oStream.Close
Set oStream = Nothing
twFichierValide = sCheminFichier
exit function
end if
set oFichier = nothing
end if
set oFso = nothing
end if
End function
%>
Bien sûr cette technique sous-entend que vous contrôlez et avez confiance dans les fichiers déposés sur le serveur.
Limite de poids
IIS limite généralement le poids d’un téléchargement à 4 Mo. Vous pouvez modifier la préférence ou livrer le fichier par paquet. Chose très facile avec un flot binaire et notre objet « stream » (ce code est probablement compatible uniquement IIS 5 et plus) :
do while mot oStream.EOS ’ fin du flot
response.binaryWrite oStream.read(3670016) ’ Lecture par paquet de 3670016 octets.
response.flush
loop