PHP : l'extension cURL

La bibliothèque cURL permet de communiquer facilement avec de nombreux types de serveurs applicatifs en parlant le même langage que celui-ci. Ce langage est défini par ce qu'on appelle un protocole dont les plus connus sont sans aucun doute HTTP et FTP. L'extension cURL permet d'interagir en PHP avec tous ces protocoles que nous employons de manière quotidienne sans avoir à gérer la connexion ou encore sans se soucier de la manière dont il faut écrire la requête ou en recevoir la réponse.

Date de publication : 12/06/2007

Par : julp

1. Installation

1.1. Windows

Récupérez la version binaire zippée de PHP pour Windows. Placez ensuite php_curl.dll avec les autres (généralement il s'agit du répertoire extensions pour PHP 4 et ext pour PHP 5). Indiquez à PHP de charger l'extension en éditant le fichier php.ini pour y ajouter la ligne suivante :

Fichier : php.ini
extension=php_curl.dll

Vous aurez également besoin de copier les bibliothèques libeay32.dll ainsi que ssleay32.dll dans un des répertoires pointés par votre variable d'environnement PATH. Modifiez la au besoin ou alors utilisez directement C:\WINDOWS\system32\.

Enfin, un redémarrage de votre serveur sera probablement nécessaire pour prendre en charge immédiatement cette nouvelle extension.

1.2. Unix/Linux

L'installation de PHP ou de l'extension à partir de ses sources s'adresse à des utilisateurs initiés pour répondre à des besoins particuliers. Privilégiez autant que possible les paquets mis à disposition par le distributeur de votre distribution Linux ou de votre système Unix auquel cas il ne devrait vous rester qu'à activer cette extension en éditant le fichier php.ini (voir ci-dessous la partie intitulée "Dynamique"). Par exemple, dans le cas de Mandriva où vos médias sont convenablement renseignés, l'installation se fera par cette commande : urpmi phpVERSION-curl (remplacez VERSION par 4 ou 5 suivant la version de PHP utilisée).

1.2.1. Statique

Les sources de cette extension étant distribuées avec celles de PHP nous devons simplement ajouter l'option --with-curl lors du script configure :

./configure --prefix=/usr/local/php ... --with-curl
make
make install

1.2.2. Dynamique

Cette méthode présente l'avantage de ne pas requérir une recompilation complète de PHP et d'être indépendante (jusqu'à un certain point) du coeur de PHP. Voici comment procéder à la compilation :

cd /répertoire/des/sources/de/l/extension
phpize
./configure
make
make install

Vous devez ensuite modifier le fichier php.ini afin de disposer des fonctions cURL en chargeant l'extension à l'aide de la ligne suivante :

Fichier : php.ini
extension=curl.so

Assurez-vous que les chemins indiqués par la directive extension_dir sont corrects puisque PHP sera alors incapable de charger les extensions demandées auquel cas vous obtiendriez des messages d'erreur et aucune des fonctions qu'elles sont censées fournir.

2. Vue d'ensemble

2.1. Les fonctions

Dans la plupart des cas, le recours à cURL en PHP se résume à quelques appels de fonctions dont l'ordre vous est présenté par le schéma ci-dessous :

Pile des appels nécessaires à l'emploi de cURL

Voyons maintenant le rôle que joue chacune de ces fonctions ainsi que leurs prototypes :

  • ressource curl_init([chaîne url]) :

    Initialise une nouvelle session cURL. Son seul paramètre url peut être omis mais il ne faudra pas oublier de l'indiquer par la suite avec la fonction curl_setopt, option CURLOPT_URL. Cette fonction renverra une ressource exploitable par les autres fonctions curl ou FALSE en cas d'erreur.

  • booléen curl_setopt(ressource curl, entier option, variable valeur) :

    Définit le comportement de la session. Elle renvoie TRUE en cas de succès et FALSE si une erreur est rencontrée. Ces paramètres sont les suivants :

    • curl : la session cURL (résultat de l'appel à curl_init)

    • option : le nom de l'option sous la forme d'une constante, celles-ci commencent par CURLOPT_* et les principales seront présentées plus bas

    • valeur : la valeur à donner, son type (numérique, booléen, etc) est variable et est propre à l'option utilisée

  • variable curl_exec(ressource curl) :

    Exécute la session cURL représentée par curl (résultat de la fonction curl_init). Toutes les informations nécessaires doivent être fournies au préalable avec la fonction curl_setopt. Elle retourne FALSE si une erreur survient et dans le cas contraire une valeur qui dépend de la valeur de l'option CURLOPT_RETURNTRANSFER. En effet, si cette option est fixée à FALSE (valeur par défaut), curl_exec renvoie alors TRUE sinon le résultat de la requête (du texte).

  • rien curl_close(ressource curl) :

    Met fin à la session cURL désignée par le paramètre curl (résultat de la fonction curl_init) et libère les ressources allouées.

Fonctions supplémentaires utiles :

  • chaîne curl_error(ressource curl) :

    Permet d'obtenir le texte décrivant la dernière erreur produite. S'il n'y en a pas eu, vous récupérerez alors une chaîne vide. Notez que cette fonction attend la session cURL courante issue de la fonction curl_init, elle doit donc par conséquent être appelée avant curl_close.

  • variable curl_getinfo(ressource curl [, entier option]) :

    Fournit diverses informations concernant la dernière session indiquée par le paramètre curl. Si l'argument option est fourni alors seule l'information correspondante sera retournée et s'il est omis vous les obtiendrez toutes sous la forme d'un tableau associatif.

    Le paramètre option peut prendre l'une des valeurs suivantes sous les traits d'une constante :

    Tableau 1. 
    Nom Description
    CURLINFO_EFFECTIVE_URL Dernière URL effective utilisée (permet de déterminer l'URL finale, après redirections)
    CURLINFO_HTTP_CODE Code HTTP de la dernière réponse reçue. La valeur sera nulle si aucune réponse n'a (encore) été reçue
    CURLINFO_FILETIME Timestamp indiquant la date du fichier distant. La valeur -1 sera renvoyée si cette information n'est pas disponible et ce quelqu'en soit la raison
    CURLINFO_TOTAL_TIME Durée totale exprimée en secondes du précédent transfert (ceci inclue toutes les étapes de la connexion comme la résolution d'adresse)
    CURLINFO_NAMELOOKUP_TIME Temps (en secondes) mis depuis le début (de la fonction curl_exec) jusqu'à la résolution de nom
    CURLINFO_CONNECT_TIME Temps mis en secondes depuis le début à l'établissement de la connexion avec l'hôte distant ou le proxy
    CURLINFO_PRETRANSFER_TIME Délai (en secondes) séparant le début au début du transfert. Cette étape inclue l'envoi des pré-commandes comme le permet l'option CURLOPT_QUOTE, par exemple
    CURLINFO_STARTTRANSFER_TIME Temps, en secondes, écoulé entre le début et l'envoi du premier octet du transfert
    CURLINFO_REDIRECT_TIME Durée totale du transfert incluant les différentes redirections. La valeur sera nulle si la requête n'a fait l'objet d'aucune redirection
    CURLINFO_REDIRECT_COUNT Nombre de redirections suivies
    CURLINFO_SIZE_UPLOAD Nombre d'octets envoyés
    CURLINFO_SIZE_DOWNLOAD Nombre d'octets reçus pour le dernier transfert
    CURLINFO_SPEED_DOWNLOAD Vitesse de téléchargement moyenne mesurée en octets/seconde
    CURLINFO_SPEED_UPLOAD Vitesse d'envoi moyenne exprimée en octets/seconde
    CURLINFO_HEADER_SIZE En octets, taille totale de tous les en-têtes reçus
    CURLINFO_REQUEST_SIZE Taille totale des requêtes effectuées (inclue les pages de redirection si CURLOPT_FOLLOWLOCATION est à TRUE). Cette information ne concerne que le protocole HTTP
    CURLINFO_SSL_VERIFYRESULT Résultat de la vérification de la certification demandée via l'option CURLOPT_SSL_VERIFYPEER
    CURLINFO_CONTENT_LENGTH_DOWNLOAD Valeur lue et correspondant au champ Content-Length dans la réponse
    CURLINFO_CONTENT_LENGTH_UPLOAD Taille spécifiée pour l'envoi (c'est à dire à la valeur de l'option CURLOPT_INFILESIZE)
    CURLINFO_CONTENT_TYPE La valeur de l'en-tête Content-Type telle qu'elle a été lue. Vous pouvez obtenir la valeur NULL si le protocole ne gère pas cette en-tête ou si le serveur en a envoyé une qui s'avère être invalide

    En revanche si vous n'utilisez pas ce deuxième paramètre, voilà la forme que prendra le tableau ainsi renvoyé par la fonction curl_getinfo :

    Array
    (                                          // Voir ci-dessus la description de la constante :
        'url' => 'http://www.developpez.com/', // CURLINFO_EFFECTIVE_URL
        'content_type' => 'text/html',         // CURLINFO_CONTENT_TYPE
        'http_code' => 200,                    // CURLINFO_HTTP_CODE
        'header_size' => 145,                  // CURLINFO_HEADER_SIZE
        'request_size' => 58,                  // CURLINFO_REQUEST_SIZE
        'filetime' => -1,                      // CURLINFO_FILETIME
        'ssl_verify_result' => 0,              // CURLINFO_SSL_VERIFYRESULT
        'redirect_count' => 0,                 // CURLINFO_REDIRECT_COUNT
        'total_time' => 0.125,                 // CURLINFO_TOTAL_TIME
        'namelookup_time' => 0,                // CURLINFO_NAMELOOKUP_TIME
        'connect_time' => 0.052,               // CURLINFO_CONNECT_TIME
        'pretransfer_time' => 0.052,           // CURLINFO_PRETRANSFER_TIME
        'size_upload' => 0,                    // CURLINFO_SIZE_UPLOAD
        'size_download' => 0,                  // CURLINFO_SPEED_DOWNLOAD
        'speed_download' => 0,                 // CURLINFO_SPEED_DOWNLOAD
        'speed_upload' => 0,                   // CURLINFO_SPEED_UPLOAD
        'download_content_length' => 0,        // CURLINFO_CONTENT_LENGTH_DOWNLOAD
        'upload_content_length' => 0,          // CURLINFO_CONTENT_LENGTH_UPLOAD
        'starttransfer_time' => 0.124,         // CURLINFO_STARTTRANSFER_TIME
        'redirect_time' => 0                   // CURLINFO_REDIRECT_TIME
    )
    

2.2. Les options

La liste des options que nous allons voir est loin d'être exhaustive : nous n'aborderons en effet que les principales options de l'extension suivant leur rôle et en prenant en compte le ou les protocoles avec lesquels elles peuvent être employées.

Passons en revue tout d'abord les paramètres généraux de la connexion qui sera établie par cURL :

Tableau 2. 
Nom Description
CURLOPT_URL URL à utiliser pour établir la connexion (alternative au paramètre url de la fonction curl_init). La forme de celle-ci dépend du protocole employé
CURLOPT_TIMEOUT Temps maximal d'exécution, exprimé en secondes, de la fonction curl_exec
CURLOPT_CONNECTTIMEOUT Durée maximale de la tentative d'établissement de la connexion vers l'hôte distant (en secondes). Une valeur nulle aura pour effet de laisser cette tâche au système

Voyons en maintenant d'autres toutes aussi générales qui, elles, sont liées au transfert des données :

Tableau 3. 
Nom Description
CURLOPT_NOBODY Fixée à TRUE la partie "contenu" ne sera pas renvoyée. Par exemple avec le protocole HTTP, la requête employée sera de type HEAD au lieu de GET par défaut. Sa valeur par défaut est FALSE, rapatriant ainsi les données distantes
CURLOPT_HEADER Suivant le protocole employé, la valeur TRUE permettra d'inclure les en-têtes dans le résultat renvoyé. La valeur par défaut est FALSE
CURLOPT_RETURNTRANSFER Avec la valeur TRUE, le contenu de la page distante est retourné sous la forme d'une chaîne par la fonction curl_exec. La valeur par défaut FALSE a pour effet d'en afficher directement la sortie
CURLOPT_FILE Un descripteur de fichier (préalablement obtenu et ouvert avec fopen en mode écriture) dans lequel sera alors écrit les données renvoyées par le serveur distant. La valeur par défaut est la sortie standard soit le navigateur dans une utilisation "web" de PHP
CURLOPT_UPLOAD Permet d'indiquer, en attribuant la valeur TRUE à cette option, à cURL qu'un envoi de fichier va avoir lieu et qu'il doit s'y préparer
CURLOPT_INFILE Un descripteur de fichier, auparavant obtenu et ouvert en lecture par la fonction fopen, duquel les données pour l'upload seront lues
CURLOPT_INFILESIZE Taille du fichier qui sera envoyé au serveur distant

Abordons à présent les particularités du protocole HTTP :

Tableau 4. Les principales options de cURL liées au protocole HTTP
Nom Description
CURLOPT_HTTP_VERSION Forcer la version du protocole HTTP utilisée. La valeur par défaut est CURL_HTTP_VERSION_NONE, laissant ainsi le soin à cURL de décider sinon forcez la à l'aide de CURL_HTTP_VERSION_1_0 ou CURL_HTTP_VERSION_1_1 qui correspondent respectivement aux versions 1.0 et 1.1
CURLOPT_FOLLOWLOCATION TRUE pour suivre (toutes) les redirections de type "Location:". On peut toutefois limiter le nombre de celles-ci avec l'option CURLOPT_MAXREDIRS
CURLOPT_MAXREDIRS Spécifie le nombre maximum de redirections qui seront suivies avant de faire échouer la requête. Une valeur nulle implique aucune redirection alors que -1 (valeur par défaut) signifie toutes
CURLOPT_HTTPGET La méthode HTTP employée sera GET. Il s'agit du type de requête par défaut
CURLOPT_POST Définir cette option à la valeur TRUE aura pour effet d'employer la méthode POST à la place de la méthode GET par défaut
CURLOPT_POSTFIELDS Les données à envoyer par la méthode POST sous la forme d'une chaîne (doivent être encodées) ou d'un tableau associant le nom du champ à sa valeur. Cette deuxième forme ne nécessite pas l'encodage des données car elle sera réalisée en interne et permet d'envoyer des fichiers en faisant précéder leur nom d'une arobase
CURLOPT_USERPWD Permet de fournir un identifiant et un mot de passe pour passer les systèmes d'authentification HTTP. Ils prennent alors la forme login:mot_de_passe
CURLOPT_USERAGENT Définit l'en-tête User-Agent (le navigateur) pour la requête HTTP
CURLOPT_REFERER Renseigne l'entête Referer, la page d'où on provient en temps normal
CURLOPT_AUTOREFERER Avec la valeur TRUE cURL renseignera automatiquement l'en-tête Referer lors de redirections
CURLOPT_HTTPHEADER Ensemble d'en-têtes à présenter lors de la requête sous la forme d'un tableau numériquement indexé
CURLOPT_COOKIEFILE Indique le fichier duquel seront lues les données relatives aux cookies
CURLOPT_COOKIEJAR Fichier où seront écrites les données des différents cookies reçus

Et enfin la gestion du protocole FTP :

Tableau 5. Les principales options pour le protocole FTP
Nom Description
CURLOPT_QUOTE Un tableau numériquement indexé des commandes FTP à exécuter avant notre requête
CURLOPT_POSTQUOTE Les commandes FTP à exécuter après notre requête sous la forme d'un tableau numériquement indexé
CURLOPT_TRANSFERTEXT Une valeur vraie indique à cURL d'utiliser le mode de transfert appelé ASCII au lieu du mode binaire par défaut. Toutefois, attention : le mode ascii est implémenté de manière incomplète au sein de cURL
CURLOPT_FTP_SSL

Permet de spécifier, à l'aide d'une des constantes ci-dessous, le niveau de sécurité des données qui vont transiter :

  • CURLFTPSSL_NONE : connexion FTP normale, aucun recours à SSL

  • CURLFTPSSL_TRY : tente d'utiliser SSL ou à défaut le protocole normal

  • CURLFTPSSL_CONTROL : SSL sera requis sur la connexion de contrôle sinon cURL échouera

  • CURLFTPSSL_ALL : SSL sera requis à la fois pour la connexion de contrôle ainsi que sur le canal de données

CURLOPT_FTPSSLAUTH

Détermine l'ordre des versions du protocole SSL à employer :

  • CURLFTPAUTH_DEFAULT : laisse à cURL le soin de décider

  • CURLFTPAUTH_SSL : tente d'utiliser SSL avant TLS

  • CURLFTPAUTH_TLS : tente d'utiliser TLS avant SSL

3. Utilisation

3.1. Le protocole HTTP

3.1.1. Vérifier l'existence d'une URL

Le but du jeu consiste à envoyer une requête de type HEAD vers la page indiquée et d'en obtenir le code HTTP correspondant. Bien sûr s'il n'y a aucun serveur HTTP à l'adresse et port indiqués, la fonction curl_exec échouera (à commencer lors de la résolution du nom).

Un code HTTP 200 (OK) sera attendu en réponse de notre requête pour un document existant. Cependant, afin d'être un peu moins laxiste dans cette interprétation, les redirections HTTP seront suivies (de codes 301 - Moved Permanently - et 302 - Temporary Redirect).

Outre la gestion des redirections qu'il faut suivre (CURLOPT_FOLLOWLOCATION à vrai et éventuellement leur nombre maximum : CURLOPT_MAXREDIRS), nous utiliserons donc les options de timeout (CURLOPT_TIMEOUT et CURLOPT_CONNECTTIMEOUT) pour assurer que le script nous rende, quoi qu'il arrive, rapidement la main (serveur inexistant ou temporairement indisponible, lenteur) ainsi que CURLOPT_NOBODY car le contenu de la page ne nous intéresse pas ici. Nous obtiendrons uniquement ce code d'erreur en combinant la fonction curl_getinfo à sa constante CURLINFO_HTTP_CODE que nous comparerons ensuite à 200 :

function http_check_url($url, $timeout = 10, $maxredirs = 10)
{
    $ret = FALSE;

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_NOBODY, TRUE);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
    curl_setopt($ch, CURLOPT_MAXREDIRS, $maxredirs);
    if (strpos($url, 'https://') === 0) {
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); // On ne vérifie que l'existence de la page
    }
    if (curl_exec($ch)) {
        $ret = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    }
    curl_close($ch);

    return ($ret == 200);
}

// Exemple d'utilisation :
define('OK', '<span style="color: green">OK</span>');
define('KO', '<span style="color: red">KO</span>');
$urls = array(
    'http://www.non-existant.fr',
    'http://www.developpez.com/',
    'http://php.developpez.com/faq/'
);
foreach ($urls as $u) {
    echo $u . ' : ' . (check_url($u) ? OK : KO) . '<br/>';
}

3.1.2. Récupérer le contenu d'une page

Nous sommes parfois amenés à vouloir récupérer le contenu d'une page distante sous la forme d'une chaîne de caractères à des fins toutes aussi diverses comme par exemple pour en extraire certaines informations, constituer un cache, etc. Cette pratique est monnaie courante avec le protocole HTTP mais puisque nous utilisons cURL est aussi valable pour d'autres, citons par exemple FTP.

L'option cURL la plus importante ici est CURLOPT_RETURNTRANSFER. En effet, si celle-ci est omise ou n'est pas fixée à une valeur vraie, le contenu du fichier distant sera directement renvoyé au navigateur de votre visiteur or nous souhaitons le traiter. Nous réutiliserons des options que nous avons déjà vues auparavant, notamment celles liées à la durée maximale d'exécution de la requête par cURL. Voyons comment implémenter une telle fonction :

function http_fetch_url($url, $timeout = 10, $userpwd = '', $maxredirs = 10)
{
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
    curl_setopt($ch, CURLOPT_MAXREDIRS, $maxredirs);
    if ($userpwd) {
        curl_setopt($ch, CURLOPT_USERPWD, $userpwd);
    }
    $data = curl_exec($ch);
    curl_close($ch);

    return $data;
}

// Exemple d'utilisation :
if (($content = http_fetch_url('http://www.monsite.fr/zone_privee/sqldump.php', 10, 'utilisateur:mot_de_passe')) === FALSE) {
    die("Une erreur est survenue");
} else {
    echo htmlentities($content);
}

Les paramètres d'identification (login et mot de passe) peuvent directement figurer dans l'URL comme ils peuvent être spécifiés à part via l'option CURLOPT_USERPWD, introduite ci-dessus à titre informatif.

Pour des raisons légales, vous êtes fortement invités à prendre préalablement contact avec les responsables des sites sur lesquels vous voudriez reprendre des informations.

3.1.3. Envoyer des données par la méthode POST

Pour utiliser la méthode POST, nous permettant ainsi d'envoyer des données conséquentes et de simuler la soumission d'un formulaire, l'une des méthodes consiste à regrouper l'ensemble de ces données sous la forme d'une chaîne encodée qui sera ensuite fournie à la session cURL via l'option CURLOPT_POSTFIELDS. Parfaitement, les informations à transmettre doivent être encodées conformément à la définition du protocole HTTP pour garantir la syntaxe de la requête, rappelons que les données doivent se présenter sous la forme suivante : param1=valeur1&param2=valeur2&...&paramN=valeurN et qu'un certain nombre de caractères spéciaux vis-à-vis du protocole HTTP doivent être encodés. En PHP 5, la fonction http_build_query permet de gérer directement tous ces aspects, en revanche, pour PHP 4 il nous faudra la réécrire (voir ci-dessous).

Voyons un exemple sans réel intérêt, dont le rôle est de retransmettre les données POST reçues par un script à destination d'une autre page :

// Pour assurer la compatibilité avec les versions PHP 4
if (!function_exists('http_build_query')) {
    function http_build_query($formdata, $numeric_prefix = NULL, $arg_separator = '', $parent_key = '') {
        $ret = array();
        if (is_array($formdata)) {
            if (empty($arg_separator)) {
                $arg_separator = ini_get('arg_separator.output');
            }
            foreach ($formdata as $k => $v) {
                if (is_int($k) && $numeric_prefix != NULL) {
                    $k = $numeric_prefix . $k;
                }
                if ($parent_key != '') {
                    $k = sprintf('%s[%s]', $parent_key, $k);
                }
                if (is_array($v)) {
                    array_push($ret, http_build_query($v, NULL, $arg_separator, $k));
                } elseif (is_object($v)) {
                    array_push($ret, http_build_query(get_object_vars($v), NULL, $arg_separator, $k));
                } else {
                    array_push($ret, urlencode($k) . '=' . urlencode($v));
                }
            }
        }
        return implode($arg_separator, $ret);
    }
}

// Faire suivre les données POST à une autre page
$ch = curl_init('http://www.monsite.fr/formulaire.html');
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($_POST));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
$ret = curl_exec($ch);
if (!$ret) {
    echo curl_error($ch);
} else {
    echo $ret;
}
curl_close($ch);

L'autre méthode simplifiant quelque peu les choses est de donner à cette option non pas une chaîne de caractères mais un tableau associant le nom du paramètre à sa valeur. L'intérêt est double :

  • vous n'avez pas à vous soucier de la forme des données : elles seront regroupées et encodées en interne par cURL ;

  • cela permet l'upload de fichiers : tout paramètre dont la valeur commence par une arobase et correspondant à un fichier local sera joint.

Voyons un exemple visant à simuler la validation d'un formulaire comportant un champ de type textarea (nommé description) et un de type file (photo) :

<form action="envoi_photo.php" method="post" enctype="multipart/form-data">
    Description : <textarea name="description"></textarea>
    Fichier : <input type="file" name="photo"/>
    <input type="submit" value="Envoyer"/>
</form>
// Données à envoyer
$post = array(
    // La zone de texte "description"
    'description' => "Photo n°1 de mon week-end en Bretagne.\r\nPique-nique dans la forêt de Broceliande.",
    // Le fichier à uploader sous "photo"
    'photo'       => '@' . realpath('P1010001.JPG')
);

// On effectue la requête avec cURL
$ch = curl_init('http://www.monsite.fr/envoi_photo.php');
curl_setopt($ch, CURLOPT_NOBODY, TRUE);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
$ret = curl_exec($ch);
if (!$ret) {
    echo curl_error($ch);
} else {
    echo 'Envoi OK !';
}
curl_close($ch);

3.1.4. Débuter une session puis la réutiliser

Certaines pages requièrent parfois une authentification avant de donner accès à un contenu plus vaste. Il est alors nécessaire d'établir une première connexion pour créer une session côté serveur afin de mémoriser cette identification et de pouvoir lire par la suite l'ensemble des données du site. Pour cela nous devons faire parvenir son identifiant au serveur pour ne plus avoir à repasser par la case authentification (sur un temps limité) et consulter directement les pages qui nous intéressent par la suite.

Un serveur employant PHP (versions 4 et supérieures) propose deux méthodes indépendantes et complémentaires pour faire transiter l'identifiant de session à savoir : l'utilisation d'un cookie de session ou la réécriture des liens internes et formulaires de sorte à le transmettre à la prochaine requête.

Pour correspondre à un maximum de configuration nous utiliserons les options proposées par cURL pour recourir aux cookies et nous parserons le corps de la page à la recherche d'un tel identifiant. La première étape consiste donc à s'authentifier pour initialiser une session "valide" et de tenter de capturer cet identifiant de session, suivant la configuration du serveur distant, pour pouvoir ensuite le réexploiter lors de notre deuxième requête.

Lors de la seconde, nous renverrons cet identifiant, si nous l'avons trouvé, en méthode GET (la méthode POST le permet aussi), ce qui devrait nous donner directement accès au document que nous souhaitons consulter.

Voici une méthode de procéder propre à un serveur PHP :

define('LOGIN', 'julp');
define('PASSWORD', 'mdp');
define('AUTHENTIFICATION', 'http://localhost/curl/session/login.php');
define('PAGE_PRIVEE', 'http://localhost/curl/session/admin.php');

$sid = '';

/**
 * Première connexion : authentification
 **/
$ch = curl_init(AUTHENTIFICATION);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS,
    array(
        'login' => LOGIN,
        'mdp' => PASSWORD
    )
);
curl_setopt($ch, CURLOPT_COOKIEJAR, realpath('cookie.txt'));
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
curl_setopt($ch, CURLOPT_COOKIESESSION, TRUE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
$ret = curl_exec($ch);
if ($ret === FALSE) {
    die(curl_error());
}
curl_close($ch);
if (preg_match('/(PHPSESSID=[0-9a-z,-]{32,40})/i', $ret, $m)) {
    $sid = '?' . $m[1];
} else if (preg_match('#<input\s+type="hidden"\s+name="([^\r\n\t <>\'"\\\]+)"\s+value="([0-9a-z,-]{32,40})"\s*/?>#i', $ret, $m)) {
    $sid = '?' . $m[1] . '=' . $m[2];
}

/**
 * Deuxième partie : réutilisation de la session sur une page tierce
 **/
$ch = curl_init(PAGE_PRIVEE . $sid);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_COOKIEFILE, realpath('cookie.txt'));
$ret = curl_exec($ch);
if ($ret === FALSE) {
    die(curl_error());
}
curl_close($ch);
echo $ret;

Pour rappel : cURL n'interprète aucun contenu renvoyé (HTML ou Javascript notamment) ainsi il peut être mis à mal par des redirections à effectuer côté client (balise meta, window.location.href, etc). Ce problème ne se pose pas lorsqu'elles sont effectuées au niveau même du protocole HTTP grâce à l'en-tête "Location:" (sous réserve d'indiquer à cURL de les suivre).

Pour en savoir plus sur les sessions php (utilisation comme configuration), je vous invite à consulter l'article éponyme : Les sessions en PHP.

3.1.5. Source : simplifier l'utilisation de cURL pour le protocole HTTP

Voici une classe PHP 5 qui vous permet d'interroger facilement et rapidement des serveurs HTTP :

class HTTPQuery
{
    /**
     * Tableau : les données POST qui seront envoyées (nom du champ => valeur)
     **/
    protected $_post;

    /**
     * Tableau des options cURL définies par l'utilisateur (option => valeur)
     **/
    protected $_options;

    /**
     * La ressource cURL
     **/
    protected $_ch;

    /**
     * Constructeur
     * @param url URL à laquelle la requête sera envoyée
     * @throws Exception si l'extension cURL n'est pas active
     **/
    public function __construct($url)
    {
        if (!extension_loaded('curl')) {
            throw new Exception("L'extension curl n'est pas disponible");
        }
        $this->_ch = curl_init($url);
        $this->_options = array();
    }

    /**
     * Obtenir la valeur des options cURL avec la syntaxe $ojet->CURLOPT_X définie par l'utilisateur
     * @param nom le nom de l'option cURL
     * @return NULL si l'option n'a pas été définie sinon sa valeur
     **/
    public function __get($nom)
    {
        $resultat = NULL;
        if (defined($nom)) {
            $valeur = constant($nom);
            if (isset($this->_options[$valeur])) {
                $resultat = $this->_options[$valeur];
            }
        }
        return $resultat;
    }

    /**
     * Fixer les valeurs des options cURL avec la syntaxe $objet->CURLOPT_X = Y
     * @param nom    le nom de l'option cURL (constantes CURLOPT_*)
     * @param valeur la nouvelle valeur de l'option (écrase la précédente)
     * @throws Exception si l'option "nom" n'est pas valide (inexistante ou ne commençant pas par CURLOPT_) ou est
     *                   protégée de façon à ce que vous passiez par les méthodes déléguées à la fonctionnalité ciblée
     **/
    public function __set($nom, $valeur)
    {
        if (defined($nom) && preg_match('/^CURLOPT_(?!POSTFIELDS)/', $nom)) {
            $this->_options[constant($nom)] = $valeur;
        } else {
            throw new Exception("Option '$nom' invalide ou protégée");
        }
    }

    /**
     * Prendre connaissance de la définition d'une option cURL par l'utilisateur
     * @param nom le nom de l'option cURL
     * @return un booléen indiquant si cette option a été définie
     **/
    public function __isset($nom)
    {
        return (defined($nom) && isset($this->_options[constant($nom)]));
    }

    /**
     * Détruire la définition d'une option cURL
     * @param nom le nom de l'option cURL à détruire
     **/
    public function __unset($nom)
    {
        if (defined($nom) && isset($this->_options[constant($nom)])) {
            unset($this->_options[constant($nom)]);
        }
    }

    /**
     * Description de l'objet
     * @return une chaîne de caractères décrivant l'objet
     **/
    public function __toString()
    {
        return sprintf("%s (%s)", __CLASS__, curl_getinfo($this->_ch, CURLINFO_EFFECTIVE_URL));
    }

    /**
     * Fixer la durée maximale d'exécution de la requête
     * @param timeout cette durée exprimée en secondes
     **/
    public function setTimeout($timeout)
    {
        $timeout = intval($timeout);
        if ($timeout > 0) {
            $this->CURLOPT_TIMEOUT = $timeout;
            $this->CURLOPT_CONNECTTIMEOUT = $timeout;
        }
    }

    /**
     * Ajouter des données textuelles aux données POST à envoyer
     * @param nom_champ le nom du champ (permet d'exploiter les données côté serveur - $_POST)
     * @param valeur    les données correspondantes à envoyer
     * @return un booléen indiquant que les données ont été prises en compte
     **/
    public function addPostData($nom_champ, $valeur)
    {
        if (!isset($this->_post[$nom_champ]) && !is_array($valeur)) {
            $this->_post[$nom_champ] = $valeur;
            return TRUE;
        } else {
            return FALSE;
        }
    }

    /**
     * Ajouter un fichier aux données POST à envoyer (upload de fichiers)
     * @param nom_champ le nom du champ (permet d'exploiter le fichier à sa réception - $_FILES)
     * @param fichier   le fichier à envoyer
     * @throws Exception si le fichier indiqué est inexistant ou n'est pas un fichier régulier
     **/
    public function addPostFile($nom_champ, $fichier)
    {
        if (is_file($fichier)) {
            $this->_post[$nom_champ] = '@' . realpath($fichier);
        } else {
            throw new Exception("Le fichier '$fichier' n'existe pas ou n'est pas un fichier régulier");
        }
    }

    /**
     * Exécuter la requête
     * @param fichier_sortie, renseigné le contenu de la page distante est écrit dans le fichier indiqué
     * @return le contenu de la page distante ou alors TRUE si le paramètre fichier_sortie a été utilisé
     * @throws Exception en cas d'erreur liée à cURL ou à l'écriture du fichier
     **/
    public function doRequest($fichier_sortie = FALSE)
    {
        if ($this->_options) {
            if (function_exists('curl_setopt_array')) {
                curl_setopt_array($this->_ch, $this->_options);
            } else {
                foreach ($this->_options as $option => $valeur) {
                    curl_setopt($this->_ch, $option, $valeur);
                }
            }
        }
        if ($fichier_sortie) {
            @ $fp = fopen($fichier_sortie, 'w');
            if (!$fp) {
                throw new Exception("Impossible d'ouvrir en écriture le fichier '$fichier_sortie'");
            }
            curl_setopt($this->_ch, CURLOPT_FILE, $fp);
        } else {
            curl_setopt($this->_ch, CURLOPT_RETURNTRANSFER, TRUE);
        }
        if ($this->_post) {
            curl_setopt($this->_ch, CURLOPT_POST, TRUE);
            curl_setopt($this->_ch, CURLOPT_POSTFIELDS, $this->_post);
        }
        $ret = curl_exec($this->_ch);
        if ($fichier_sortie) {
            fclose($fp);
        }
        if ($ret === FALSE) {
            throw new Exception("Une erreur est survenue : '" . curl_error($this->_ch) . "'");
        }
        return $ret;
    }

    /**
     * Destructeur
     **/
    public function __destruct()
    {
        unset($this->_options);
        unset($this->_post);
        curl_close($this->_ch);
    }
}

Un exemple complet visant à afficher la sortie résultant de la soumission d'un formulaire distant d'upload de photos comprenant d'autres champs plus classiques, le tout avec une authentification HTTP et utilisant le protocole HTTPS pour la sécurité :

require_once('/var/www/offline/librairies/HTTPQuery.php');

try {
    $http = new HTTPQuery('https://julp:mdp@mon_serveur/admin/ajouter_photo.php');
    /* Quelques fichiers à uploader */
    $http->addPostFile('images[0]', '../images1.jpg');  // Récupérée sur le serveur distant via $_FILES['images']['name'][0]
    $http->addPostFile('images[1]', '../images2.jpg');  // Récupérée sur le serveur distant via $_FILES['images']['name'][1]
    /* Les données correspondant aux différents champs du formulaire */
    $http->addPostData('visibilite[0]', 'visiteurs');   // $_POST['visibilite'][0] : simulation d'une checkbox
    $http->addPostData('visibilite[1]', 'enregistrés'); // $_POST['visibilite'][1] : simulation d'une checkbox
    $http->addPostData('description', "Les photos de ce week-end.\r\nC'est fou ce qu'il faisait beau."); // $_POST['description'] : simulation d'une textarea
    $http->addPostData('submit', 'x');                  // $_POST['submit'] : le bouton submit
    /* Vérification du certificat présenté par le serveur */
    $http->CURLOPT_CAINFO = 'ca-root.crt';              // Obtenu sur http://www.freebsd.org/cgi/cvsweb.cgi/ports/security/ca-roots/files/
    $http->CURLOPT_SSL_VERIFYPEER = TRUE;
    /* On effectue enfin la requête */
    echo $http->doRequest();
} catch (Exception $e) {
    die($e->getMessage());
}

3.2. Le protocole FTP

3.2.1. Lister un répertoire distant

Il est possible d'obtenir la liste des fichiers présents dans un répertoire distant en utilisant le protocole FTP et l'option CURLOPT_FTPLISTONLY. Le résultat est une liste des noms des fichiers (et aucune autre information comme leur poids) séparés par des nouvelles lignes. Par conséquent voici une méthode pour exploiter et réutiliser ce résultat :

function curl_ftp_list($url, $timeout = 10)
{
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($ch, CURLOPT_FTPLISTONLY, TRUE);
    $ret = curl_exec($ch);
    curl_close($ch);
    if ($ret === FALSE) {
        return FALSE;
    } else {
        return preg_split('/[\r\n]+/', $ret, -1, PREG_SPLIT_NO_EMPTY);
    }
}

// Exemple d'utilisation :
$fichiers = curl_ftp_list('ftp://login:mot_de_passe@serveur/');
natsort($fichiers);
if ($fichiers === FALSE) {
    die("La connexion n'a pu être établie");
} else {
    echo '<ul>';
    foreach ($fichiers as $f) {
        if ($f != '.' and $f != '..') {
            echo '<li>' . $f . '</li>';
        }
    }
    echo '</ul>';
}

On peut cependant aller plus loin en envoyant la commande FTP LIST (via l'option CURLOPT_CUSTOMREQUEST) qui nous permettra de récupérer en plus des noms des fichiers, leurs permissions, propriétaire, taille, etc. Toutefois, ces informations étant renvoyées sous forme de texte monolithique il va nous falloir utiliser une expression régulière afin de séparer ces différentes informations :

function curl_ftp_list_bis($url, $timeout = 10)
{
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "LIST");
    $ret = curl_exec($ch);
    echo curl_error($ch);
    curl_close($ch);
    if ($ret === FALSE) {
        return FALSE;
    } else {
        $fichiers = array();
        $nbFichiers = 0;
        if (preg_match_all('/([-dl])([rwxst-]{9})[ ]+([0-9]+)[ ]+([^ ]+)[ ]+(.+)[ ]+([0-9]+)[ ]+([a-zA-Z]+[ ]+[0-9]+)[ ]+([0-9:]+)[ ]+(.*)/', $ret, $m, PREG_SET_ORDER)) {
            foreach ($m as $f) {
                $fichiers[$nbFichiers] = array();
                $fichiers[$nbFichiers]['dir']         = $f[1] == 'd';  // Répertoire ?
                $fichiers[$nbFichiers]['filename']    = $f[9];         // Nom
                $fichiers[$nbFichiers]['size']        = $f[6];         // Taille
                $fichiers[$nbFichiers]['owner']       = $f[4];         // Propriétaire
                $fichiers[$nbFichiers]['group']       = $f[5];         // Groupe
                $fichiers[$nbFichiers]['permissions'] = $f[2];         // Permissions
                $fichiers[$nbFichiers]['mtime']       = "$f[7] $f[8]"; // Date de dernière modification
                $nbFichiers++;
            }
        }
        return $fichiers;
    }
}

3.2.2. Envoyer un fichier

Le fichier à envoyer doit tout d'abord être ouvert en écriture avec la fonction fopen (mode r). Ce descripteur de fichier ainsi obtenu sera transmis à cURL par l'option CURLOPT_INFILE. Nous devons également indiquer la partie du fichier concernée via CURLOPT_INFILESIZE mais notre but étant de faire parvenir ce fichier dans son intégralité nous lui fournirons donc la taille réelle de celui-ci grâce à la fonction filesize. Enfin, nous signalons à cURL qu'il s'agit d'un upload et qu'il doit s'y préparer en positionnant l'option CURLOPT_UPLOAD à vrai :

function curl_ftp_put($url, $nom_local, $mode_ascii = FALSE, $chmod = FALSE)
{
    $ret = FALSE;

    if (is_file($nom_local)) {
        $fp = fopen($nom_local, 'r');
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_INFILE, $fp);
        curl_setopt($ch, CURLOPT_INFILESIZE, filesize($nom_local));
        curl_setopt($ch, CURLOPT_UPLOAD, TRUE);
        if ($mode_ascii) {
            curl_setopt($ch, CURLOPT_TRANSFERTEXT, TRUE);
        }
        if ($chmod) {
            $path = parse_url($url, PHP_URL_PATH);
            curl_setopt($ch, CURLOPT_POSTQUOTE, array("SITE CHMOD $chmod $path"));
        }
        $ret = curl_exec($ch);
        curl_close($ch);
        fclose($fp);
    }

    return $ret;
}

// Exemple d'utilisation :
if (!curl_ftp_put('ftp://login:mot_de_passe@serveur/mon_fichier.txt', 'mon_fichier.txt', TRUE, '0400')) {
    die("Le fichier n'a pu être uploadé sur le serveur");
}

L'URL désigne bien le fichier à créer ou écraser et non le répertoire parent.

Veillez, de préférence, à fermer le fichier ouvert (fonction fclose) à la fin de l'envoi.

J'ai introduit dans l'exemple ci-dessus deux options que vous serez peut être amenés à réutiliser. Tout d'abord, CURLOPT_TRANSFERTEXT qui indique que le mode de transfert à utiliser n'est pas le mode binaire (par défaut) mais le mode ascii. Le protocole FTP propose en effet deux modes qui dépendent de la nature des données à envoyer et qui lorsque le mode adéquat n'est pas employé donne souvent lieu à des corruptions de fichiers. La deuxième, CURLOPT_POSTQUOTE, permet d'exécuter après la requête une liste (sous forme de tableau) de commandes qui ici peut être employée pour fixer les permissions du fichier à l'issue de sa réception.

3.2.3. Télécharger un fichier

Sur le même principe que l'envoi, il faut en premier lieu ouvrir le fichier local qui va accueillir les données reçues en écriture. Chose que nous ferons avec la fonction fopen (mode w) et nous procurerons ensuite le descripteur de fichier ainsi obtenu à cURL par l'intermédiaire de l'option CURLOPT_FILE :

function ftp_curl_get($url, $sortie, $timeout = 10)
{
    if ($fp = fopen($sortie, 'w')) {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_FILE, $fp);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
        $ret = curl_exec($ch);
        curl_close($ch);
        fclose($fp);
        return $ret;
    }
    return FALSE;
}

// Exemple d'utilisation :
if (!ftp_curl_get('ftp://utilisateur:mot_de_passe@serveur/fichier_distant', 'nom_local')) {
    die("Le fichier indiqué n'a pu être récupéré");
}

3.3. Le protocole LDAP

cURL est également capable d'interroger un annuaire LDAP, le seul changement à apporter se situe alors au niveau de l'URL dont la forme vous est rappelée ci-dessous. Cependant, il faut savoir que cURL utilise la librairie cliente OpenLDAP pour interagir avec les annuaires (fonctionnalité pouvant être désactivée) et qu'elle est limitée (l'authentification n'est pas assurée par exemple).

ldap[s]://[hôte]:[port]/[dn_base]?[attributs]?[scope]?[filtre]?[extensions]

  • hôte : nom du serveur LDAP

  • port : le port TCP de connexion. Les valeurs par défaut sont 389 pour une connexion non chiffrée ou TLS et 636 pour une connexion cryptée à l'aide de SSL

  • dn_base : nom distingué de l'objet servant de point de référence pour la recherche

  • attributs : liste des attributs souhaités séparés par une virgule. Ils seront tous retournés si cette partie est omise. La valeur spéciale * renvoie en plus les attributs internes

  • scope : une des valeurs suivantes :

    • base (par défaut) : seul l'objet désigné par dn_base sera concerné par la recherche

    • one : les objets dont le père direct est l'objet indiqué par dn_base seront retenus

    • sub : la recherche touchera tous les objets ayant un lien de parenté avec dn_base

  • filtre : le filtre de recherche (par défaut : (objectclass=*))

  • extensions : liste de fonctionnalités diverses

En guise d'exemple une réimplémentation de la fonction ldap_get_entries entièrement basée sur les extensions cURL et PCRE :

function curl_ldap_get_entries($url, $timeout = 10)
{
    // Exécution de la recherche
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    $ldif = curl_exec($ch);
    curl_close($ch);

    // Traitement du résultat
    if ($ldif) {
        $objets = preg_split('/^DN:\s*/im', $ldif, -1, PREG_SPLIT_NO_EMPTY);
        $res = array('count' => 0);
        foreach ($objets as $o) {
            $lignes = preg_split('/[\r\n]+/', $o, -1, PREG_SPLIT_NO_EMPTY);
            $nbAttr = 0;
            foreach ($lignes as $l) {
                $l = trim($l);
                if (preg_match('/^([^:]+):\s*(.*)$/', $l, $m)) {
                    if (!in_array($m[1], $res[$res['count']])) {
                        $res[$res['count']][] = $m[1];
                        $nbAttr++;
                    }
                    $res[$res['count']][$m[1]][] = $m[2];
                    if (!isset($res[$res['count']][$m[1]]['count'])) {
                        $res[$res['count']][$m[1]]['count'] = 1;
                    } else {
                        $res[$res['count']][$m[1]]['count']++;
                    }
                } else { // DN
                    $res[$res['count']]['dn'] = $l;
                }
            }
            $res[$res['count']]['count'] = $nbAttr;
            $res['count']++;
        }
        return $res;
    }
    return FALSE;
}

// Un exemple :
$objets = curl_ldap_get_entries('ldap://ldap.mondomaine.fr/dc=developpez,dc=com??sub');
if ($objets) {
    print_r($objets);
}

4. Alternatives : mise en parallèle avec d'autres méthodes

Ces fonctions ont été développées pour un environnement PHP5 car certaines fonctionnalités ont évoluées (le deuxième paramètre de la fonction parse_url a été introduit à ce moment) ou bien de nouvelles fonctions sont apparues (stream_get_wrappers et http_build_query).

4.1. Vérifier l'existence d'une page

Comme nous l'avons précédemment vu, une requête HEAD permet de connaître le statut d'une page HTTP. Il existe plusieurs méthodes pour envoyer une telle requête pour ensuite contrôler le code d'erreur retourné comme réponse parmi les en-têtes :

  • avec cURL

  • en exploitant la gestion native de flux HTTP par PHP si elle est possible (c'est à dire que la directive allow_url_fopen doit être à On)

  • à l'aide de la fonction fsockopen

function tester_url_http($url, $timeout = 10)
{
    if (extension_loaded('curl')) {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_NOBODY, TRUE);
        if (strpos($url, 'https://') === 0) {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); // On ne vérifie que l'existence de la page
        }
        if (!curl_exec($ch)) {
            return FALSE;
        }
        $ret = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        return in_array($ret, array(200, 301, 302));
    } elseif (ini_get('allow_url_fopen') && in_array(parse_url($url, PHP_URL_SCHEME), stream_get_wrappers())) {
        $infos = parse_url($url);
        $options = array(
            'http' => array(
                'method' => 'HEAD',
                'header' => '',
                'timeout' => $timeout // Effectif pour les versions 5.2.1 et supérieures
            )
        );
        if (isset($infos['user'])) {
            $options['http']['header'] .= 'Authorization: Basic ' . base64_encode($infos['user'] . ':' . $infos['pass']) . "\r\n";
        }
        $contexte = stream_context_create($options);
        @ $fp = fopen($url, 'r', FALSE, $contexte);
        if (!$fp) {
            return FALSE;
        }
        $meta = stream_get_meta_data($fp);
        fclose($fp);
        if (preg_match('#^HTTP/1\.[01] ([0-9]{3})#', $meta['wrapper_data'][0], $m)) {
            return in_array($m[1], array(200, 301, 302));
        } else {
            return FALSE;
        }
    } elseif (function_exists('fsockopen')) {
        $infos = parse_url($url);
        if (empty($infos['path'])) {
            $infos['path'] = '/';
        }
        if (!isset($infos['port'])) {
            if ($infos['scheme'] == 'http') {
                $infos['port'] = 80;
            } elseif ($infos['scheme'] == 'https') {
                $infos['port'] = 443;
            }
        }
        @ $fp = fsockopen($infos['host'] == 'https' ? 'ssl://' . $infos['host'] : $infos['host'], $infos['port'], $errno, $errstr, $timeout);
        if (!$fp) {
            return FALSE;
        }
        stream_set_timeout($fp, $timeout);
        $requete = 'HEAD ' . $infos['path'] . '?'  . (isset($infos['query']) ? $infos['query'] : '') . " HTTP/1.1\r\n";
        $requete .= 'Host: ' . $infos['host'] . ':' . $infos['port'] . "\r\n";
        if (isset($infos['user'])) {
            $requete .= 'Authorization: Basic ' . base64_encode($infos['user'] . ':' . $infos['pass']) . "\r\n";
        }
        fwrite($fp, $requete . "\r\n");
        $entete = fgets($fp, 32);
        fclose($fp);
        if (preg_match('#^HTTP/1\.[01] ([0-9]{3})#', $entete, $m)) {
            return in_array($m[1], array(200, 301, 302));
        } else {
            return FALSE;
        }
    } else {
        die("Aucune méthode permettant de vérifier l'état d'un document en ligne n'est disponible");
    }
}

4.2. Récupérer le corps d'une page

L'obtention du corps d'une page distante peut se faire de plusieurs manières :

  • Avec l'extension cURL

  • Avec la gestion de flux HTTP par PHP pour les fonctions capables de lire des fichiers

  • Avec la fonction fsockopen, relativement lente, avec laquelle nous devons forger et exploiter les requêtes de nous-mêmes

Vous trouverez ci-dessous une fonction générique pour obtenir le code source d'une page distante, gérant la méthode POST ainsi que l'authentification HTTP basique :

if (extension_loaded('curl')) {
    function recuperer_page_http($url, $timeout = 10, $nom_local = '', $post = NULL) {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
        if ($nom_local) {
            $fp = fopen($nom_local, 'w') or die("Le fichier '$nom_local' n'a pu être ouvert en écriture");
            curl_setopt($ch, CURLOPT_FILE, $fp);
        } else {
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        }
        if ($post) {
            curl_setopt($ch, CURLOPT_POST, TRUE);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
        }
        $ret = curl_exec($ch);
        if ($nom_local) {
            fclose($fp);
        }
        curl_close($ch);
        if ($ret === FALSE) {
            die("Une erreur a été rencontrée : " . curl_error());
        }
        return $ret;
    }
} elseif (ini_get('allow_url_fopen') && in_array(parse_url($url, PHP_URL_SCHEME), stream_get_wrappers())) {
    function recuperer_page_http($url, $timeout = 10, $nom_local = '', $post = NULL) {
        $infos = parse_url($url);
        $contexte = NULL;
        if (isset($infos['user']) || $post) {
            $options = array(
                'http' => array(
                    'method' => $post ? 'POST' : 'GET',
                    'header' => '',
                    'timeout' => $timeout // Effectif pour les versions 5.2.1 et supérieures
                )
            );
            if (isset($infos['user'])) {
                $options['http']['header'] .= 'Authorization: Basic ' . base64_encode($infos['user'] . ':' . $infos['pass']) . "\r\n";
            }
            if ($post) {
                $data = http_build_query($post);
                $options['http']['header'] .= "Content-type: application/x-www-form-urlencoded\r\n";
                $options['http']['header'] .= 'Content-Length: ' . strlen($data) . "\r\n";
                $options['http']['content'] = $data;
            }
            $contexte = stream_context_create($options);
        }
        if (!$nom_local) {
            return file_get_contents($url, FALSE, $contexte);
        } else {
            $in = fopen($url, 'r', FALSE, $contexte);
            stream_set_timeout($in, $timeout);
            $out = fopen($nom_local, 'w') or die("Le fichier '$nom_local' n'a pu être ouvert en écriture");
            while (!feof($in)) {
                fwrite($out, fread($in, 1024));
            }
            fclose($in);
            fclose($out);
        }
    }
} elseif (function_exists('fsockopen')) {
    function recuperer_page_http($url, $timeout = 10, $nom_local = '', $post = NULL) {
        $infos = parse_url($url);
        if ($nom_local) {
            $out = fopen($nom_local, 'w') or die("Le fichier '$nom_local' n'a pu être ouvert en écriture");
        }
        if (empty($infos['path'])) {
            $infos['path'] = '/';
        }
        if (!isset($infos['port'])) {
            if ($infos['scheme'] == 'http') {
                $infos['port'] = 80;
            } elseif ($infos['scheme'] == 'https') {
                $infos['port'] = 443;
            }
        }
        @ $fp = fsockopen($infos['host'] == 'https' ? 'ssl://' . $infos['host'] : $infos['host'], $infos['port'], $errno, $errstr, $timeout);
        if (!$fp) {
            return FALSE;
        }
        stream_set_timeout($fp, $timeout);
        $requete = ($post ? 'POST ' : 'GET ') . $infos['path'] . '?'  . (isset($infos['query']) ? $infos['query'] : '') . " HTTP/1.0\r\n";
        $requete .= 'Host: ' . $infos['host'] . ':' . (isset($infos['port']) ? $infos['port'] : '80') . "\r\n";
        if (isset($infos['user'])) {
            $requete .= 'Authorization: Basic ' . base64_encode($infos['user'] . ':' . $infos['pass']) . "\r\n";
        }
        if ($post) {
            $data = http_build_query($post);
            $requete .= "Content-type: application/x-www-form-urlencoded\r\n";
            $requete .= 'Content-Length: ' . strlen($data) . "\r\n";
        }
        fwrite($fp, $requete . "\r\n" . $data);
        $buffer = '';
        do { // Saut de l'entête HTTP
            $buffer .= fgets($fp, 1024);
        } while (!feof($fp) && strpos($buffer, "\r\n\r\n") === FALSE);
        $buffer = '';
        while (!feof($fp)) {
            $buffer .= fread($fp, 1024);
        }
        $buffer = ltrim($buffer);
        $pos = strpos($buffer, "\r\n");
        $len = hexdec(substr($buffer, 0, $pos));
        $buffer = substr($buffer, $pos + strlen("\r\n"), $len);
        if ($nom_local) {
            fwrite($out, $buffer);
            fclose($out);
            $buffer = TRUE;
        }
        fclose($fp);
        return $buffer;
    }
} else {
    die("Aucune méthode permettant de récupérer la source d'une page n'est disponible");
}

4.3. Recevoir un fichier par FTP

PHP offre différents moyens pour télécharger un fichier via le protocole FTP. À savoir :

  • l'extension ftp, le premier moyen qui nous viendrait à l'esprit

  • l'extension curl, comme nous l'avons déjà vu précédemment

  • la gestion interne par PHP de flux FTP, ce qui nous permet d'utiliser les traditionnelles fonctions de lecture et écriture sur des fichiers

Voilà donc une fonction capable de s'adapter à l'environnement afin de tenter de télécharger un fichier à partir d'une URL FTP :

if (extension_loaded('ftp')) {
    function recuperer_fichier_ftp($url, $chemin_local, $mode_ascii = FALSE, $timeout = 10) {
        $infos = parse_url($url);
        if ($infos['scheme'] == 'ftps') {
            if (function_exists('ftp_ssl_connect')) {
                $conn = ftp_ssl_connect($infos['host'], isset($infos['port']) ? $infos['port'] : 0, $timeout) or die("La connexion n'a pu être établie");
            } else {
                die("La fonction ftp_ssl_connect n'est pas disponible !");
            }
        } else {
            $conn = ftp_connect($infos['host'], isset($infos['port']) ? $infos['port'] : 0, $timeout) or die("La connexion n'a pu être établie");
        }
        ftp_login($conn, $infos['user'], $infos['pass']) or die("La phase d'authentifcation a échoué");
        ftp_get($conn, $chemin_local, $infos['path'], $mode_ascii ? FTP_ASCII : FTP_BINARY) or die("Le téléchargement a échoué");
        ftp_close($conn);
    }
} elseif (extension_loaded('curl')) {
    function recuperer_fichier_ftp($url, $chemin_local, $mode_ascii = FALSE, $timeout = 10) {
        $fp = fopen($chemin_local, 'w') or die("Le fichier '$chemin_local' n'a pu être ouvert en écriture");
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_FILE, $fp);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
        if ($mode_ascii) {
            curl_setopt($ch, CURLOPT_TRANSFERTEXT, TRUE);
        }
        if (strpos($url, 'ftps://') === 0) {
            // Options supplémentaires liées à SSL à utiliser
        }
        curl_exec($ch) or die("Une erreur a été rencontrée : " . curl_error());
        curl_close($ch);
        fclose($fp);
    }
} elseif (ini_get('allow_url_fopen') && in_array(parse_url($url, PHP_URL_SCHEME), stream_get_wrappers())) {
    function recuperer_fichier_ftp($url, $chemin_local, $mode_ascii = FALSE, $timeout = 10) {
        $in = fopen($url, $mode_ascii ? 'r' : 'rb');
        $out = fopen($chemin_local, $mode_ascii ? 'w' : 'wb');
        while (!feof($in)) {
            fwrite($out, fread($in, 1024));
        }
        fclose($in);
        fclose($out);
    }
} else {
    die("Aucune méthode permettant de récupérer un fichier par FTP n'est disponible");
}

4.4. Envoyer un fichier par FTP

Similaire en tout point au téléchargement d'un fichier par le biais du protocole FTP, un envoi n'entraîne des changements que sur le sens des échanges (les flux ouverts en lecture sont maintenant ouverts en écriture et vice-versa) :

if (extension_loaded('ftp')) {
    function envoyer_fichier_ftp($url, $chemin_local, $mode_ascii = FALSE, $timeout = 10) {
        if (!is_file($chemin_local)) {
            return FALSE;
        }
        $infos = parse_url($url);
        if ($infos['scheme'] == 'ftps') {
            if (function_exists('ftp_ssl_connect')) {
                $conn = ftp_ssl_connect($infos['host'], isset($infos['port']) ? $infos['port'] : 0, $timeout) or die("La connexion n'a pu être établie");
            } else {
                die("La fonction ftp_ssl_connect n'est pas disponible !");
            }
        } else {
            $conn = ftp_connect($infos['host'], isset($infos['port']) ? $infos['port'] : 0, $timeout) or die("La connexion n'a pu être établie");
        }
        ftp_login($conn, $infos['user'], $infos['pass']) or die("La phase d'authentifcation a échoué");
        ftp_put($conn, $infos['path'], $chemin_local, $mode_ascii ? FTP_ASCII : FTP_BINARY) or die("L'envoi a échoué");
        ftp_close($conn);

        return TRUE;
    }
} elseif (extension_loaded('curl')) {
    function envoyer_fichier_ftp($url, $chemin_local, $mode_ascii = FALSE, $timeout = 10) {
        if (!is_file($chemin_local)) {
            return FALSE;
        }
        $fp = fopen($chemin_local, 'r');
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_INFILE, $fp);
        curl_setopt($ch, CURLOPT_INFILESIZE, filesize($chemin_local));
        curl_setopt($ch, CURLOPT_UPLOAD, TRUE);
        if ($mode_ascii) {
            curl_setopt($ch, CURLOPT_TRANSFERTEXT, TRUE);
        }
        if (strpos($url, 'ftps://') === 0) {
            // Options supplémentaires liées à SSL à utiliser
        }
        curl_exec($ch) or die("Une erreur a été rencontrée : " . curl_error());
        curl_close($ch);
        fclose($fp);

        return TRUE;
    }
} elseif (ini_get('allow_url_fopen') && in_array(parse_url($url, PHP_URL_SCHEME), stream_get_wrappers())) {
    function envoyer_fichier_ftp($url, $chemin_local, $mode_ascii = FALSE, $timeout = 10) {
        if (!is_file($chemin_local)) {
            return FALSE;
        }
        $in = fopen($chemin_local, $mode_ascii ? 'r' : 'rb');
        $out = fopen($url, $mode_ascii ? 'w' : 'wb');
        while (!feof($in)) {
            fwrite($out, fread($in, 1024));
        }
        fclose($in);
        fclose($out);

        return TRUE;
    }
} else {
    die("Aucune méthode permettant d'envoyer un fichier par FTP n'est disponible");
}

5. Conclusion

5.1. Épilogue

J'espère vous avoir convaincu d'utiliser à votre tour l'extension cURL à la fois pour sa simplicité et sa souplesse dans la prise en charge des différents protocoles qu'elle est capable de gérer.

Cependant ceux qui n'ont pas la chance de profiter de son support (information que vous pouvez obtenir via la sortie de phpinfo) devront se tourner vers des alternatives comme fsockopen ou plus simplement vers les traditionnelles fonctions de lecture de fichiers comme file_get_contents et fopen, fread/fwrite, fclose mais requiert que la directive allow_url_fopen soit à On (elles prennent habituellement en charge les protocoles HTTP et FTP).

Liens externes :

Copyright © 2007 - 2012

Aucune reproduction, même partielle, ne peut être faite de ce document et de l'ensemble de son contenu : textes, documents, images, etc sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 € de dommages et intérêts. Cette page est déposée à la SACD.