jeudi 17 novembre 2011

Cross-site Ajax proxy version curl

La technique Ajax permet de modifier partiellement une page affichée par le navigateur pour la mettre à jour sans avoir à recharger cette dernière dans son intégralité. Elle dépend de XMLHttpRequest, objet intégré coté client utilisable en Javascript.

Il existe une multitude de tutoriels décrivant le fonctionnement et techniques.

Pour ce post, je m'intéresserai principalement à une limitation de la majorité des navigateurs actuels, due à des raisons de sécurité, concernant l'utilisation de domains différents.
En effet, une script JS lancé sur une page www.name.com ne peut normalement pas diriger d'objet vers un domain différent.

Là où cela devient vraiment embêtant, c'est que les subdomains sont égalements limités : impossible d'utiliser, par exemple, l'adresse ww1.name.com pour ces mêmes fonctions.
Dans le cas où vous devriez lancer des scripts sur différents serveurs, comme pour l'envoi d'images à un serveur Master, depuis un serveur Slave (Post à venir), le navigateur bloquerait ces communications.

Alors comment faire ?

La solution s'appelle le "Cross-site Ajax".
L'idée est de créer un fichier PHP fonctionnant comme un proxy et dont nous allons nous servir en appel Ajax pour communiquer avec les différents domains/subdomains.

Je vous propose donc un fichier permettant d'outrepasser les protections navigateurs, basé sur l'extension cURL (librairie libcurl, voir post Plus rapide que file_get_contents : optez pour cURL !) :

Contenu du fichier : csaproxy.php

 <?php
header('Content-type: text/html; charset=iso-8859-1');


if ( $_GET['pass'] != "abcde12345" ) die(); //protection par mot de passe lors de l'appel



function curl_http_request(
    $url = 'www.domain.com/index.php',         /* Target URL */
    $getdata = array(),        /* HTTP GET Data ie. array('var1' => 'val1', 'var2' => 'val2') */
    $postdata = array(),       /* HTTP POST Data ie. array('var1' => 'val1', 'var2' => 'val2') */
    $filesdata = array(),       /* HTTP FILES Data ie. array('var1' => 'val1', 'var2' => 'val2') */
    $cookie = array()         /* HTTP Cookie Data ie. array('var1' => 'val1', 'var2' => 'val2') */
    ) {

    $curlopt = array();
    $cookie_str = '';
    $getdata_str = count($getdata) ? '?' : '';

    foreach ($getdata as $k => $v) $getdata_str .= (strlen($getdata_str)>0?'&':'').urlencode($k).'='.urlencode($v);

   //assignation uploads en POST : _POST['champ']=filetmpname et _POST['champ_real']=filename
    foreach ($filesdata as $k => $v) {
        if ( $filesdata[$k]['error'] == UPLOAD_ERR_OK ) {
            $postdata[$k]='@'.$filesdata[$k]['tmp_name'];
            $postdata[$k."_real"]=$filesdata[$k]['name'];
        }
    }
   
    foreach ($cookie as $k => $v) $cookie_str .= (strlen($cookie_str)>0?'; ':'').urlencode($k).'='.urlencode($v);

    if ( count($postdata) ) {
        $curlopt = array (
        CURLOPT_POST => 1,
        CURLOPT_POSTFIELDS => $postdata
        );
    }

    $default_curlopt = array(
    CURLOPT_TIMEOUT => 2, //timeout 2sec
    CURLOPT_FAILONERROR => 1, //si page error, on ne d/l pas
    CURLOPT_RETURNTRANSFER => 1, //return le transfert as a string
    CURLOPT_FOLLOWLOCATION => 1, //on follow si redirection header desactive par défaut
    CURLOPT_USERAGENT => $_SERVER['HTTP_USER_AGENT']?$_SERVER['HTTP_USER_AGENT']:"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.13) Gecko/20101203 AlexaToolbar/alxf-1.54 Firefox/3.6.13 GTB7.1"
    );
    $curlopt = array(CURLOPT_URL => $url.$getdata_str) + $curlopt + $default_curlopt;
   
   
    // Création d'une nouvelle ressource cURL
    $ch = curl_init();
   
    // Configuration de l'URL et d'autres options
    curl_setopt_array($ch, $curlopt);
   
    //Envoi des données
    $results_returned = curl_exec($ch);
    if ( $results_returned === false ) trigger_error(curl_error($ch));

    // Fermeture de la session cURL
    curl_close($ch);
   
    return $results_returned;
}


//destinations autorisées, en double pour redoubler de vigilance
$accepted_file_proxycsa=array(
    '/script.php',
    '/script.php' => 'www.domain.com',
    '/main/postit.php',
    '/main/postit.php' => 'sub.domain.com'
);

if ( in_array($_GET['filename_csa'],$accepted_file_proxycsa) ) {
    $target_filename=$_GET['filename_csa'];
    $target_site=$accepted_file_proxycsa[$_GET['filename_csa']];
}

if (( strlen($target_filename) > 0 ) && ( strlen($target_site) > 0 )) {
    echo curl_http_request($target_site.$target_filename,$_GET,$_POST,$_FILES,$_COOKIE);
}

?>

Une fois ce fichier uploadé sur votre serveur, vous pouvez désormais appeler tout fichier présent dans n'importe quel domain/subdomain.
Ne pas oublier d'inclure les destinations autorisées dans le array $accepted_file_proxycsa. Vous pouvez également vous affranchir de ce passage et rediriger directement l'url contenu dans le $_GET.


Exemple de code Javascript incluant le cross-site Ajax :
<script type="text/javascript">

function csajax(CHOIX,idcible) {   
   
    var xhr_object = new Array;
    xhr_object[CHOIX] = null;
    if(window.XMLHttpRequest) // Firefox
        xhr_object[CHOIX] = new XMLHttpRequest();  
    else if(window.ActiveXObject) // Internet Explorer  
        xhr_object[CHOIX] = new ActiveXObject("Microsoft.XMLHTTP");  
    else { // XMLHttpRequest non supporté par le navigateur  
        alert("Votre navigateur ne supporte pas les objets XMLHTTPRequest...");  
        return;
    }


   xhr_object[CHOIX].open("POST", "http://<?php echo $_SERVER['SERVER_NAME']; ?>/csaprx.php?filename_csa=/main/postit.php", true);  


    xhr_object[CHOIX].onreadystatechange = function() {
        if ( CHOIX == "postit" ) {
            if (( xhr_object[CHOIX].readyState == 4) && ( xhr_object[CHOIX].responseText.length > 0 )) {
                document.getElementById(idcible).innerHTML=xhr_object[CHOIX].responseText;
                xhr_object[CHOIX].abort;
             }
        }
     };

    xhr_object[CHOIX].setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=iso-8859-1");

    if ( CHOIX == "postit" ) {
        var data = "message="+encodeURIComponent(document.getElementById('champ_msg').value)+'&auteur='+encodeURIComponent(document.getElementById('champ_auteur').value);

        xhr_object[CHOIX].send(data);
    }
}
</script>

Formulaire HTML correspondant:
<input type="text" name="champ_msg">
<input type="button" value="Envoyer" onclick="csajax('postit','reponse');">
<div id="reponse"></div>

Cet exemple enverra le contenu du champ_msg à http://sub.domain.com/main/postit.php et retournera le résultat dans le div reponse.
C'est aussi simple que ça !

Aucun commentaire:

Enregistrer un commentaire