Mod_rewrite, ou la réécriture des URL "à la volée"

Découvrons le module Apache mod_rewrite

29 août 2003, par Dan

Le module Apache mod_rewrite et la réécriture d’URL

Une des fonctions les plus puissantes permises par le fichier .htaccess est la réécriture « à la volée » des URL.
Sur le site officiel Apache, le module mod_rewrite est présenté à raison comme le couteau suisse de la manipulation.
Comme dans notre article sur le fichier .htaccess, il est utile de préciser que certains hébergeurs n’ont pas activé le module de réécriture. Dans ce cas, vous n’avez malheureusement aucune possibilité de l’utiliser, à moins de casser le petit cochon en porcelaine qui traîne chez vous et changer d’hébergeur.

Si vous gérez votre propre serveur dédié, assurez-vous que le module mod_rewrite est activé en modifiant le cas échéant le fichier de configuration du serveur Apache (httpd.conf).
Vérifiez que les deux lignes suivantes ne soient pas mises en commentaire :

LoadModule rewrite_module modules/mod_rewrite.so
AddModule mod_rewrite.c

Si vous devez changer ces deux lignes, il vous faudra redémarrer Apache pour que vos modifications soient prises en compte.

On teste d’abord !

La meilleure manière de s’assurer que le module mod_rewrite est chargé est encore de consulter le phpinfo. La mention de mod_rewrite dans la section Apache/Loaded Modules reste la meilleure garantie.

Avant de se lancer plus loin dans les explications, voici comment tester si le module mod_rewrite est actif chez votre hébergeur. Comme pour toutes manipulations qui peuvent impacter le bon fonctionnement de votre site, nous vous conseillons de faire ces essais en période creuse, en évitant par exemple la période de « full crawl » de Google.

1. Créez un fichier html simple, nommez le « trouve.html ».
2. Modifiez le fichier .htaccess en y ajoutant les 3 lignes suivantes. Faites très attention à utiliser la syntaxe précise ou mieux, utilisez le copier/coller :

Options +FollowSymlinks
RewriteEngine on
RewriteRule   ^nexistepas.html$   trouve.html  [L]

Nous reviendrons plus tard sur l’explication de ces deux instructions

3. Télécharger le fichier .htaccess et le fichier trouve.html à la racine de votre site web, ou mieux encore dans un répertoire de test créé pour l’occasion. Laissez votre client FTP ouvert pour pouvoir enlever le fichier .htaccess au cas où cela ne fonctionne pas.
4. Lancez votre navigateur et entrez l’URL : http://www.votresite.com/nexistepas.html

Et là, deux solutions se présentent :

  Soit votre page test « trouve.html » s’affiche c’est parfait, le module est activé.
  Soit vous avez une erreur 404 ou encore plus probablement une erreur 500 et malheureusement il n’y a pas grand-chose à faire... si ce n’est retirer tout de suite le fichier .htaccess avec le client FTP (vous l’aviez bien laissé ouvert comme suggéré plus haut, non ?).
Il est possible que votre hébergeur ne vous permette pas d’ajouter le "FollowSymLinks" dans les options Apache (résolution des liens symboliques, l’équivalent des raccourcis de Windows).
Vous pouvez supprimer cette ligne sans problème.

Si vous êtes face à ce deuxième cas, vous comprendrez mieux pourquoi nous vous avons suggéré de choisir une période creuse ainsi qu’un répertoire de test. Nous ne pouvons que répéter ici que toute modification du fichier .htaccess peut fortement impacter le fonctionnement de votre site web.
Heureusement, les problèmes rencontrés ne sont pas irréversibles et disparaissent avec la suppression du fichier ou des règles erronées. La prudence s’impose.

Quelques explications sur la règle précédente.

Dans les trois lignes de l’exemple ci-dessus, la première autorise le serveur Apache à suivre les liens symboliques dans ce répertoire. Son utilité permet de corriger un éventuel défaut de configuration dans le fichier httpd.conf.
La deuxième ligne est une instruction d’activation de la réécriture d’URL. Quelles que soient les règles de réécriture que vous voulez mettre en place, de la plus triviale à la plus complexe, l’instruction « RewriteEngine on » devra toujours être insérée dans le fichier .htaccess.
Elle donne simplement au serveur Apache l’instruction de lancer le moteur de réécriture.
La troisième ligne est la règle de réécriture proprement dite, analysons la plus en détail :

RewriteRule ce mot-clé introduit toute règle de réécriture, il est indispensable
^nexistepas.html$ c’est la première partie de la règle, celle qui determine la chaîne de caractères que le module devra rechercher pour la réécrire. Elle contient deux caractères spéciaux marquant le début (^) et la fin ($) de la ligne
trouve.html la chaîne par laquelle il faudra remplacer celle trouvée à l’étape précédente. En règle générale, elle correspond au nom d’un fichier existant réellement dans votre espace Web.
[L] Un flag (drapeau) signifiant que cette règle est la dernière à appliquer dans ce cas ( L = last = dernier ) et que le module ne doit plus rechercher à réécrire cette chaîne.

Ce premier exemple est bien évidemment trivial mais vous servira de base à l’établissement de toutes les règles de réécriture que vous serez amené à rédiger.
Vous la trouvez trop simple ? Assurez-vous d’avoir parfaitement compris le mécanisme avant de passer aux étapes suivantes, cela va se corser !

Les pièges dans lesquels il ne faut pas tomber.

Nous l’avons déjà mentionné, mais jugeons utile de le répéter. La réécriture d’URL permet le meilleur comme le pire.
Imaginez 2 règles, la première réécrivant abc.html en def.html, la seconde réécrivant def.html en abc.html . Si aucune des deux règles ne comporte le flag [L], vous voilà face à une version informatisée du mouvement perpétuel. Vous avez créé une boucle de laquelle votre serveur ne pourrait pas sortir s’il n’avait ses propres mécanismes de sécurité.
L’aisance avec laquelle une règle mal écrite peut mettre un serveur « sur les genoux » est la raison principale de la non implémentation du module de réécriture chez certains hébergeurs.

Des règles plus utiles.

Il est clair que l’exemple précédent n’a pas de véritable utilité. Ce simple exemple aurait pu s’écrire beaucoup plus simplement avec une seule instruction « Redirect ».
Prenons un cas plus concret...
Les réécritures d’URL sont le plus souvent utilisées pour présenter aux visiteurs une URL plus mnémotechnique ou pour permettre à certains moteurs d’indexer des pages dynamiques avec de nombreux paramètres qu’ils n’auraient pas visité sans réécriture.
Pour les robots d’indexation, la raison en est simple.
Dans le cas d’une URL dynamique du type article.php?num=12 , un moteur ne peut pas déterminer s’il ne va pas tomber dans une boucle sans fin. Un script article.php mal écrit - volontairement ou non - peut l’entraîner vers une multitude de pages satellites ne différant que par leur URL. C’est pour la même raison qu’ils n’indexent pas les pages avec des identifiants de session PHP, une même page étant retournée au navigateur avec une multitude d’identifiants de session différents.

Vous avez un site sur lequel vous présentez un catalogue en ligne. Sur ce site, chaque article comporte 2 pages, par exemple une page commerciale et une fiche technique.
De plus, les informations concernant l’article sont extraites d’une base de données, en se basant sur le numéro d’article.
Les URL des deux pages de l’article 8125 seront donc sous la forme (si votre script se nomme article.php) :

http://www.votresite.tld/article.php?numero=8125&page=1
http://www.votresite.tld/article.php?numero=8125&page=2

Vous préféreriez, et cela se comprend, que vos visiteurs accèdent à cet article par :

http://www.votresite.tld/article-8125-1.html
http://www.votresite.tld/article-8125-2.html

Analysons point par point comment réécrire cette règle toujours simple.
Nous voyons dans ces URL qu’elles contiennent deux parties variables : le numéro d’article et le numéro de page, tout le reste étant fixe comme le nom du script et le nom des variables.
La règle s’écrirait comme ceci :

RewriteEngine on
RewriteRule ^article-([0-9]+)-([0-9]+)\.html$  article.php?numero=$1&page=$2  [L]

Cela vous semble compliqué ? Il n’en est rien, voici l’explication :

Nous ne reviendrons pas sur la ligne RewriteEngine on qui est, vous le savez, indispensable. Nous l’omettrons d’ailleurs de manière systématique pour la suite de nos exemples.

Nous retrouvons dans notre règle les parties constantes « article - - .html » et « article.php ?numero= &page= » que nous avons identifiées.
De même, les caractères de début (^) et fin ($) de ligne ont été expliqués précédemment.

Appliquons nous à remplir les blancs.

  Partie gauche de l’expression

Dans cette partie, nous trouvons deux fois une même chaîne de caractères « ([0-9]+) » qui est basée sur les expressions régulières (regular expressions) familières aux utilisateurs Unix/Linux.
Les parenthèses carrées [ ] déterminent un intervalle, donc [0-9] détermine l’intervalle des nombres « 0 » à « 9 ».
Le signe « + » qui suit immédiatement l’intervalle signifie « une ou plusieurs occurrence(s) de l’expression qui précède », notre intervalle [0-9] dans cet exemple.
Ce qui signifie qu’avec l’intervalle suivi du signe « + », nous sommes en mesure de matérialiser tout nombre entier supérieur ou égal à 0 , ce qui correspond bien à la forme de notre numéro d’article.
Enfin, les parenthèses qui entourent le tout « ([0-9]+) » donnent instruction au moteur de réécriture de grouper la chaîne trouvée et la stocker dans une variable interne parce que nous souhaitons l’utiliser plus tard. Apache stockera donc ces chaînes dans les variables $1, $2, ... $n dans l’ordre dans lequel elles sont analysées, de gauche à droite et nous pourrons y faire référence dans la partie droite de notre règle.
Dans notre exemple, Apache aura stocké les chaînes « 8125 » dans la variable interne $1 et « 2 » dans la variable $2.
Le point décimal ayant une signification particulière dans les expressions, il est utile dans notre cas de le faire précéder par le caractère d’échappement « \. ». Nous verrons ceci plus en détail par la suite.

  Partie droite de l’expression

Une fois compris ce qui précède, elle est vraiment triviale à comprendre.
Dans l’expression « article.php ?numero=$1&page=$2 » les variables $1 et $2 sont remplacées respectivement par les chaînes « 8125 » et « 2 » ce qui nous donne bien l’URL avec les paramètres que notre script article.php s’attend à recevoir.
Le dernier élément « [L] » fait comprendre, comme expliqué précédemment que c’est la dernière règle qui s’applique pour cet élément.

Quelques expressions régulières à connaître :

. n’importe quel caractère
[abcd] n’importe lequel de cette liste de caractères
[^abcd] tout caractère non compris dans la liste (autre que a, b, c ou d)
blanc|noir alternative, soit « blanc », soit « noir »
+ Une ou N occurrence(s) de l’expression qui précède (N > 1)
* Zéro ou N occurrence(s) de l’expression qui précède (N>0)
(texte) Groupement permettant l’utilisation des références inverses ($1,... $n) Est aussi utilisé pour délimiter une alternative comme dans (blanc|noir)
^ ancre de début de ligne
$ ancre de fin de ligne
\ permet d’échapper tout caractère qui suit et lui ôter sa signification particulière, par exemple \.

Quelques drapeaux (ou flags) utiles.

Voici quelques drapeaux utiles pour faciliter la maintenance d’un site :

[L] Celui-ci vous semble familier, comme nous l’avons vu dans notre précédent exemple. Il mérite toutefois une précision. Lorsque le module de réécriture est actif, les règles sont lues séquentiellement et l’URL est comparée ligne à ligne avec le premier argument de celles-ci jusqu’à la dernière.
Si une réécriture est effectuée, c’est la forme réécrite qui sera utilisée en entrée pour les règles suivantes.
Le flag [L] permet de sortir prématurément de la boucle.
Un autre exemple serait, en début d’une liste de règles :
RewriteRule ^.*\.gif$  -  [L]
RewriteRule ^.*\.jpg$  -  [L]
Nous introduisons ici un nouveau concept, à savoir un second argument vide (ou presque, car il consiste en un seul caractère « - » ) . Cette règle particulière implique qu’il n’y a pas de réécriture, l’URL étant passée sans modification aucune. Elle signale au serveur Apache de passer toutes les URL d’images gif ou jpg sans réécriture, ni traitement successif.
[R]
[R=code]
Dans ces deux formes une redirection est effectuée.
Si l’argument code n’est pas précisé, une redirection 302 (déplacé temporairement) est effectuée. Si vous souhaitez faire savoir au navigateur/robot qu’une page a été remplacée définitivement, utiliser le code 301 comme dans :
RewriteRule ^ancien\.html$ http://domaine.tld/nouveau.html [R=301,L]
Dans ce cas précis, une réécriture "externe" s’impose (utilisation de http://...)
Vous voyez ci-dessus que nous avons combiné deux flags en les séparant par une virgule.
[F] Forbidden - interdit. Retourne un code 403, par exemple :
RewriteRule ^secret.html$ - [F]
( pas de réécriture vu le deuxième argument - )
[NC] NoCase, ou « insensible à la casse ». La règle suivante :
RewriteRule  ^script\.php$  programme.php  [NC,L]
S’appliquera aussi bien à script .php, SCRIPT.PHP ou ScRiPt .PhP
[G] Gone. Cette page n’existe plus et retourne une entête http 410
[N] Force l’analyse et l’exécution de toutes les règles en repartant du début de la liste. Ici encore, comme expliqué plus haut ([L]), c’est l’URL modifiée après exécution de la dernière règle qui est utilisée en entrée, et non l’URL originelle. Attention aux boucles infinies !!
[C] Chain, chaînage avec la ou les règles suivantes jusqu’à la première règle ne se terminant pas par [C]
Apache interprète ce flag comme suit : s’il y a réécriture (la règle est vérifiée), la règle suivante est exécutée avec la chaîne réécrite en entrée.
Si la règle ne se vérifie pas, toutes les règles qui suivent jusqu’à la première ne comportant pas le flag [C] ne sont pas appliquées.
[QSA] Query String Append. Rajoute le QUERY_STRING à la fin de l’expression, après la réécriture. A réserver pour la dernière règle de réécriture. Utilisée le plus souvent avec le flag [L], comme dans [QSA,L]
   
[flags]

as the third argument to the RewriteRule directive. Flags is a comma-separated list of the following flags:

* 'chain|C' (chained with next rule)
This flag chains the current rule with the next rule (which itself can be chained with the following rule, etc.). This has the following effect: if a rule matches, then processing continues as usual, i.e., the flag has no effect. If the rule does not match, then all following chained rules are skipped. For instance, use it to remove the ``.www'' part inside a per-directory rule set when you let an external redirect happen (where the ``.www'' part should not to occur!).


* 'cookie|CO=NAME:VAL:domain[:lifetime[:path]]' (set cookie)
This sets a cookie on the client's browser. The cookie's name is specified by NAME and the value is VAL. The domain field is the domain of the cookie, such as '.apache.org',the optional lifetime is the lifetime of the cookie in minutes, and the optional path is the path of the cookie
* 'env|E=VAR:VAL' (set environment variable)
This forces an environment variable named VAR to be set to the value VAL, where VAL can contain regexp backreferences $N and %N which will be expanded. You can use this flag more than once to set more than one variable. The variables can be later dereferenced in many situations, but usually from within XSSI (via <!--#echo var="VAR"-->) or CGI (e.g. $ENV{'VAR'}). Additionally you can dereference it in a following RewriteCond pattern via %{ENV:VAR}. Use this to strip but remember information from URLs.


* 'forbidden|F' (force URL to be forbidden)
This forces the current URL to be forbidden, i.e., it immediately sends back a HTTP response of 403 (FORBIDDEN). Use this flag in conjunction with appropriate RewriteConds to conditionally block some URLs.


* 'gone|G' (force URL to be gone)
This forces the current URL to be gone, i.e., it immediately sends back a HTTP response of 410 (GONE). Use this flag to mark pages which no longer exist as gone.


* 'last|L' (last rule)
Stop the rewriting process here and don't apply any more rewriting rules. This corresponds to the Perl last command or the break command from the C language. Use this flag to prevent the currently rewritten URL from being rewritten further by following rules. For example, use it to rewrite the root-path URL ('/') to a real one, e.g., '/e/www/'.


* 'next|N' (next round)
Re-run the rewriting process (starting again with the first rewriting rule). Here the URL to match is again not the original URL but the URL from the last rewriting rule. This corresponds to the Perl next command or the continue command from the C language. Use this flag to restart the rewriting process, i.e., to immediately go to the top of the loop.
But be careful not to create an infinite loop!


* 'nocase|NC' (no case)
This makes the Pattern case-insensitive, i.e., there is no difference between 'A-Z' and 'a-z' when Pattern is matched against the current URL.


* 'noescape|NE' (no URI escaping of output)
This flag keeps mod_rewrite from applying the usual URI escaping rules to the result of a rewrite. Ordinarily, special characters (such as '%', '$', ';', and so on) will be escaped into their hexcode equivalents ('%25', '%24', and '%3B', respectively); this flag prevents this from being done. This allows percent symbols to appear in the output, as in

RewriteRule /foo/(.*) /bar?arg=P1\%3d$1 [R,NE]
which would turn '/foo/zed' into a safe request for '/bar?arg=P1=zed'.


* 'nosubreq|NS' (used only if no internal sub-request)
This flag forces the rewriting engine to skip a rewriting rule if the current request is an internal sub-request. For instance, sub-requests occur internally in Apache when mod_include tries to find out information about possible directory default files (index.xxx). On sub-requests it is not always useful and even sometimes causes a failure to if the complete set of rules are applied. Use this flag to exclude some rules.

Use the following rule for your decision: whenever you prefix some URLs with CGI-scripts to force them to be processed by the CGI-script, the chance is high that you will run into problems (or even overhead) on sub-requests. In these cases, use this flag.


* 'proxy|P' (force proxy)
This flag forces the substitution part to be internally forced as a proxy request and immediately (i.e., rewriting rule processing stops here) put through the proxy module. You have to make sure that the substitution string is a valid URI (e.g., typically starting with http://hostname) which can be handled by the Apache proxy module. If not you get an error from the proxy module. Use this flag to achieve a more powerful implementation of the ProxyPass directive, to map some remote stuff into the namespace of the local server.

Notice: To use this functionality make sure you have the proxy module compiled into your Apache server program. If you don't know please check whether mod_proxy.c is part of the ``httpd -l'' output. If yes, this functionality is available to mod_rewrite. If not, then you first have to rebuild the ``httpd'' program with mod_proxy enabled.


* 'passthrough|PT' (pass through to next handler)
This flag forces the rewriting engine to set the uri field of the internal request_rec structure to the value of the filename field. This flag is just a hack to be able to post-process the output of RewriteRule directives by Alias, ScriptAlias, Redirect, etc. directives from other URI-to-filename translators. A trivial example to show the semantics: If you want to rewrite /abc to /def via the rewriting engine of mod_rewrite and then /def to /ghi with mod_alias:

RewriteRule ^/abc(.*) /def$1 [PT]
Alias /def /ghi
If you omit the PT flag then mod_rewrite will do its job fine, i.e., it rewrites uri=/abc/... to filename=/def/... as a full API-compliant URI-to-filename translator should do. Then mod_alias comes and tries to do a URI-to-filename transition which will not work.

Note: You have to use this flag if you want to intermix directives of different modules which contain URL-to-filename translators. The typical example is the use of mod_alias and mod_rewrite..


* 'qsappend|QSA' (query string append)
This flag forces the rewriting engine to append a query string part in the substitution string to the existing one instead of replacing it. Use this when you want to add more data to the query string via a rewrite rule.


* 'redirect|R [=code]' (force redirect)
Prefix Substitution with http://thishost[:thisport]/ (which makes the new URL a URI) to force a external redirection. If no code is given a HTTP response of 302 (MOVED TEMPORARILY) is used. If you want to use other response codes in the range 300-400 just specify them as a number or use one of the following symbolic names: temp (default), permanent, seeother. Use it for rules which should canonicalize the URL and give it back to the client, e.g., translate ``/~'' into ``/u/'' or always append a slash to /u/user, etc.

Note: When you use this flag, make sure that the substitution field is a valid URL! If not, you are redirecting to an invalid location! And remember that this flag itself only prefixes the URL with http://thishost[:thisport]/, rewriting continues. Usually you also want to stop and do the redirection immediately. To stop the rewriting you also have to provide the 'L' flag.


* 'skip|S=num' (skip next rule(s))
This flag forces the rewriting engine to skip the next num rules in sequence when the current rule matches. Use this to make pseudo if-then-else constructs: The last rule of the then-clause becomes skip=N where N is the number of rules in the else-clause. (This is not the same as the 'chain|C' flag!)


* 'type|T=MIME-type' (force MIME type)
Force the MIME-type of the target file to be MIME-type. For instance, this can be used to setup the content-type based on some conditions. For example, the following snippet allows .php files to be displayed by mod_php if they are called with the .phps extension:

Cette liste n’est pas exhaustive, car il existe d’autres flags supportés. La liste complète est décrite dans la documentation du module mod_rewrite sur le site d’Apache.

Attention aux « répertoires virtuels »

Dans les exemples qui précèdent, nous avons effectué des réécritures qui n’impactaient pas l’arborescence apparente de vos pages, pour simplifier les exemples.

Si, au lieu de réécrire, en reprenant l’exemple précédent :
RewriteRule ^article-([0-9]+)-([0-9]+)\.html$  article.php?numero=$1&page=$2  [L]
nous utilisons
RewriteRule ^article/([0-9]+)/([0-9]+)\.html$  article.php?numero=$1&page=$2  [L]

L’URL apparente aurait la forme /article/8126/2.html au lieu de /article-8126-2.html
Dans ce cas, le navigateur « estime » que la page se trouve dans un répertoire /article/8126 qui n’a pas d’existence réelle sur votre site. Toute tentative de résolution de liens relatifs se fera donc à partir de ce répertoire inexistant et sera vouée à l’échec.

Pour éviter cela, deux solutions se présentent :

  Utiliser des liens absolus, ou mieux...
  Faire usage de la balise <base href="http://www.votresite.tld/repertoire/" > à mettre dans l'entête de votre page, entre <head> et  </head>

Les réécritures conditionnelles

Dans les quelques exemples qui précèdent, nous n’avons vu que des réécritures d’URL inconditionnelles, c.à.d. s’appliquant indépendamment du navigateur, de l’adresse IP ou du domaine émettant la requête. Nous allons maintenant passer à l’étape suivante, à savoir la réécriture sous conditions, à travers quelques exemples concrets.

Une page d’accueil différente selon le navigateur

RewriteCond  %{HTTP_USER_AGENT}  ^Mozilla.*
RewriteRule  ^/$    /complexe.html  [L]
RewriteCond  %{HTTP_USER_AGENT}  ^Lynx.*
RewriteRule  ^/$    /simple.html  [L]
RewriteRule  ^/$    /standard.html  [L]

Un nouveau mot-clé fait son apparition ici : "RewriteCond" ou "condition de réécriture".
La syntaxe est simple et de la forme : RewriteCond variable_testée valeur_de_comparaison

Dans l’exemple, testons si l’identifiant du navigateur (%{HTTP_USER_AGENT}) commence par Mozilla (^Mozilla) et est suivi par une chaîne quelconque. (.*)
Si cette règle est vraie, nous réécrivons le répertoire racine du site(^/$ signifie "début de ligne/fin de ligne" ou simplement / seul sur la ligne)) en page "complexe.html" et arrêtons nos réécritures [L]
Procédons de meme pour Lynx, qui se satisfera d’une page simple vu ses fonctionnalités réduites et enfin, si aucune des 2 règles précédentes ne s’applique, soit pour tous les autres navigateurs, redirigons les vers notre page "standard.html"

Protégeons nos fichiers images

Evitons maintenant que d’autres sites ne fassent un lien direct vers nos images, en nous détournant de la bande passante :

RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://www.votredomaine.net/.*$ [NC]
ReWriteRule .*\.(gif|png|jpe?g)$ - [F]

En mettant plusieurs conditions à la suite, un ET logique est effectué entre elles. Pour que la règle de réécritue soit effectuée, il faut donc que toutes les conditions soient vraies prises isolément. A la première condition FAUSSE, le moteur de réécriture branche directement après la règle et ne teste pas les conditions suivantes. Si un OU logique est nécessaire, on rajoute le drapeau [OR] en fin de ligne, en le combinant aux autres le cas échéant [NC,OR]

Dans notre exemple, on compare la variable HTTP_REFERER au domaine du site.
Les conditions s’énonceraient en clair « Si la variable HTTP_REFERER n’est pas vide et n’est pas égale au nom de domaine http://www.votredomaine.net/ suivi de n’importe quelle chaîne de caractères (même vide) en faisant abstraction de la casse [NC], alors... »

Notez que le point d’exclamation inverse le test et signifie donc « n’est pas ». Changez aussi le nom de domaine pour qu’il corresponde au vôtre.

La règle donne instruction de ne pas réécrire l’URL (grâce au signe - utilisé en second argument) mais de retourner une entête « 403 - Forbidden » pour tout fichier se terminant en .gif, .png , .jpeg et .jpg [F]

Le point d’interrogation suivant le « e » dans « jpe ?g » rend cette lettre facultative. Il y aura donc correspondance pour « jpg » et « jpeg ».

Un commentaire toutefois : Certains navigateurs permettent de masquer le HTTP_REFERER, et certains proxies ou firewall ne transmettent pas cette référence.
C’est la raison pour laquelle nous avons la première condition testant si HTTP_REFERER n’est pas vide. Sans cette règle, les visiteurs derrière certains firewall ou proxies ne verraient pas vos images.
Cette dernière limitation démontre bien qu’il n’est pas possible d’éliminer 100% des liens sauvages vers vos images puisqu’il suffit de masquer le HTTP_REFERER pour éviter l’interdiction. Une élimination de 95-98% des liens représente déjà une économie substantielle de bande passante.

Si vous souhaitez autoriser certains domaines amis à faire des liens directs, il suffit d’ajouter pour chacun d’eux une condition supplémentaire :

RewriteCond %{HTTP_REFERER} !^http://votredomaine.net/.*$ [NC]

Cet exemple permet d’accéder aux images dans le cas où votre domaine serait invoqué sans le sous-domaine « www ».

Débarrassons-nous des visiteurs indésirables

La condition s’écrira généralement sous une des formes suivantes :

RewriteCond %{REMOTE_HOST} ^badhost\.baddomain\.tld$
  teste le nom d’un ordinateur hôte spécifique

RewriteCond %{REMOTE_HOST}  baddomain\.tld$
  teste le domaine complet (se termine par..., notez l’absence du caractère ^)

RewriteCond %{HTTP_USER_AGENT}   ^VilainRobot.*
  teste le nom du robot indésirable (HTTP_USER_AGENT commence par la chaîne "VilainRobot")

RewriteCond %{REMOTE_ADDR}  ^123\.45\.67\.12[5-9]$
  teste une plage d’adresses IP (de 123.45.67.125 à 123.45.67.129 inclus)

Pourquoi éviter certains robots ?

Tous les robots ne sont pas bénéfiques pour votre sites.
Certains d’entre-eux sont des aspirateurs de site, d’autres collectent les adresses email et finissent par remplir votre boîte aux lettres de courrier non-sollicité (spam). Ils ont tous une caractéristique commune : utiliser les resources de votre serveur sans vous apporter aucun visiteur "utile".
Tous ces robots "indélicats" ne respectent pas le protocole d’exclusion représenté sous la forme du fichier "/robots.txt".

Soyez très attentifs dans l’écriture de vos règles d’exclusion, par exemple la condition :

RewriteCond %{HTTP_USER_AGENT}   Bot

est beaucoup trop générique et vous priverait du passage de GoogleBot, ce qui n’est sûrement pas ce que vous souhaitez.

Un exemple concret :

RewriteCond %{REMOTE_HOST}  \.laurion\.(com|net)$  [OR]
RewriteCond %{REMOTE_HOST} \.cn$ [OR]
RewriteRule ^.*$   -   [F]

La première condition interdit toute visite en provenance de laurion.com et laurion.net. Cela peut sembler un peu brutal comme règle mais ce robot ne respectant pas le protocole d’exclusion et ne se gênant pas pour « pomper » plus de 100 pages/minutes nous n’avons pas vraiment eu envie de mettre de gants le concernant.

Elle aurait pu s’écrire, en se basant sur le HTTP_USER_AGENT :

RewriteCond %{HTTP_USER_AGENT}  ^IPiumBot   [OR]

La deuxième condition élimine encore plus radicalement tout visiteur provenant de Chine.
Ces règles et conditions ne sont que des exemples et ne sont pas dictées par une quelconque xénophobie de la part de l’auteur. Elles ont néanmoins contribué à réduire de manière significative la bande passante utilisée.

Comment tester différents HTTP_USER_AGENT ?

Il est bien évident que nous ne pouvons pas installer tous les USER_AGENT possible, la liste est trop longue. Certains navigateurs tels que Opera permettent de choisir le USER_AGENT sous lequel on « butine »...
Certains sites Web permettent de vérifier les entêtes reçues très facilement, par exemple http://www.wannabrowser.com/
Cette page, combinée avec une analyse approfondie de vos fichiers logs, vous permettra de mettre au point vos conditions de réécriture pour les différents visiteurs de votre site.

Pour effectuer vos tests, il est judicieux de créer un répertoire temporaire sur votre site, dans lequel vous mettrez un fichier index.html et le fichier .htaccess sur lequel vous travaillez.
Une fois votre fichier .htaccess mis au point, déplacez le dans le répertoire que vous voulez protéger, ou à la racine de votre site.

Des règles différentes selon les répertoires

Un fichier .htaccess placé dans un répertoire régit l’accès à ce répertoire ainsi qu’à tous les sous-répertoires et fichiers de celui-ci.
Vous pouvez bien sûr avoir plusieurs fichiers .htaccess dans des répertoires différents, selon les différentes protections ou réécritures que vous désirez appliquer.

Dans le cas d’un fichier .htaccess situé dans un sous-répertoire du site, les règles et conditions remplacent celles définies à l’échelon supérieur.
Si votre souhait est d’ajouter des règles de réécriture à celles du niveau supérieur au lieu de les remplacer, ajoutez la ligne suivante juste après le « RewriteEngine on » :

RewriteOptions inherit

Cette instruction spécifie que toutes les règles et conditions définies au niveau supérieur sont héritées, en supplément à celles que vous rajouterez dans le fichier .htaccess