Project

General

Profile

Evolution #3973

Brancher inc/distant recuperer_url() sur une librairie récente

Added by b b almost 2 years ago. Updated about 1 month ago.

Status:
En cours
Priority:
Normal
Assignee:
Category:
-
Target version:
Start date:
07/09/2017
Due date:
% Done:

0%

Resolution:

Description

Comme le disait fil sur #3967 :

Cette librairie réseau a été programmée à une époque reculée où curl n'était pas disponible partout. Je pense aussi qu'elle est obsolète et qu'on gagnerait à la remplacer.

Peut-être qu'une lib comme https://www.phpcurlclass.com/ pourrait faire l'affaire ?

History

#1 Updated by cedric - 7 months ago

  • Status changed from Nouveau to En cours
  • Assignee set to cedric -

why not, du moment qu'on maintient l'api recuperer_url/recuperer_page pour ne rien casser du code existant.

(Je veux bien m'y coller)

#2 Updated by b b 7 months ago

Super, il faudrait peut-être commencer par un travail de recensement des librairies disponibles pour ça ? (je peux m'y coller)

#3 Updated by Eric Camus 2 months ago

Bonjour,
Suite à un changement de machine pour nos SPIP (de WIMP vers LAMP (centos)) de nombreux flux RSS ne fonctionnaient plus, la cause : la fonction 'recuperer_url' !!!.
J'ai donc réécrit le corps de cette fonction avec la biblio PHP CURL a partir du code dans SPIP 3.2.0 (je sais que je suis en retard d'une version).
Je vous le livre comme je viens de le finir après un léger debug.

function recuperer_url($url, $options = array()) {
    $default = array(
        'transcoder' => false,
        'methode' => 'GET',
        'taille_max' => null,
        'datas' => '',
        'boundary' => '',
        'refuser_gz' => false,
        'if_modified_since' => 0,
        'uri_referer' => '',
        'file' => '',
        'follow_location' => 10,
        'version_http' => _INC_DISTANT_VERSION_HTTP,
    );
    $options = array_merge($default, $options);
    // copier directement dans un fichier ?
    $copy = $options['file'];

    if ($options['methode'] == 'HEAD') {
        $options['taille_max'] = 0;
    }
    if (is_null($options['taille_max'])) {
        $options['taille_max'] = $copy ? _COPIE_LOCALE_MAX_SIZE : _INC_DISTANT_MAX_SIZE;
    }

    // Accepter les URLs au format feed:// ou qui ont oublie le http:// ou les urls relatives au protocole
    $url = preg_replace(',^feed://,i', 'http://', $url);
    if (!tester_url_absolue($url)) {
        $url = 'http://' . $url;
    } elseif (strncmp($url, '//', 2) == 0) {
        $url = 'http:' . $url;
    }

    $url = url_to_ascii($url);

    $result = array(
        'status' => 0,
        'headers' => '',
        'page' => '',
        'length' => 0,
        'last_modified' => '',
        'location' => '',
        'url' => $url
    );

    $pCurl=curl_init($url);
    curl_setopt($pCurl,CURLOPT_HTTP_VERSION,CURL_HTTP_VERSION_1_1);
    curl_setopt($pCurl,CURLOPT_SSL_VERIFYPEER,false);
    curl_setopt($pCurl,CURLOPT_RETURNTRANSFER,true);
    curl_setopt($pCurl,CURLOPT_FOLLOWLOCATION,true);
    curl_setopt($pCurl,CURLOPT_TIMEOUT,30);  // timeout 30s
    if (!empty($options['datas'])) {  // des datas
        curl_setopt($pCurl,CURLOPT_POST,true);
        curl_setopt($pCurl,CURLOPT_SAFE_UPLOAD,true);
        curl_setopt($pCurl,CURLOPT_POSTFIELDS,$options['datas']);
        $options['methode']='POST';
    }
    if($options['methode']=='HEAD') {
        curl_setopt($pCurl,CURLOPT_NOBODY,true);
        $options['taille_max']=0;  // pas de taille
        $options['if_modified_since']=0;  // pas de date de modif
        $copy='';  // pas de copie dans fichier
    }
    if($options['if_modified_since']>0) {
        curl_setopt($pCurl,CURLOPT_TIMEVALUE,$options['if_modified_since']);
    }
    if($options['taille_max']>0) {
        curl_setopt($pCurl,CURLOPT_RANGE,'0-'.$options['taille_max']);
    }
    if($options['uri_referer']) {
        curl_setopt($pCurl,CURLOPT_REFERER,$options['uri_referer']);
    }
    $opt_header=true;
    $fheader='';  // nom du fichier contenant le header
    if($copy) {
        $fc=fopen($copy,'w');
        if($fc) {
            $fheader=_DIR_TMP.md5($copy).'_head.txt';
            $fh=fopen($fheader,'w');
            if($fh) {
                curl_setopt($pCurl,CURLOPT_FILE,$fc);
                curl_setopt($pCurl,CURLOPT_WRITEHEADER,$fh);
                $opt_header=false;
            }
            else {
                fclose($fc);
            }
        }
    }
    curl_setopt($pCurl,CURLOPT_HEADER,$opt_header);
    $cont=curl_exec($pCurl);
    $info=curl_getinfo($pCurl);
    curl_close($pCurl);  // fermer curl
    if($fc) {
        fclose($fc);  // fermer fichier
        $result['file']=$copy;
    }
    if($fh) {
        fclose($fh);
    }
    if($cont===false) {
        spip_log('ECHEC CURL '.$url,_LOG_ERREUR);
        if(file_exists($copy)) unlink($copy);  // suppr fichier sur erreur
        if(file_exists($fheader)) unlink($fheader);
        return false;
    }
    // on range le tout dans la var $result
    $body='';
    $head='';
    if($cont!==true) {  // cas de demande par fichier
        if($options['methode']=='HEAD') {
            $head=$cont;
        }
        else {
            if(isset($info['download_content_length']) and $info['download_content_length']>0) {
                $body=substr($cont,-$info['download_content_length']);
                $result['length']=$info['download_content_length'];
            }
            elseif(isset($info['size_download']) and $info['size_download']>0) {
                $body=substr($cont,-$info['size_download']);
                $result['length']=$info['size_download'];
            }
            if(isset($info['header_size']) and $info['header_size']>0) {
                $head=substr($cont,0,$info['header_size']);
                if($body=='') {  // cas ou download_content_length=-1 !!!!!!
                    $body=substr($cont,$info['header_size']);
                    $result['length']=strlen($body);  // pare absence
                }
            }
        }
    }
    if($fheader and file_exists($fheader)) {  // si un fichier de header est fait
        $head=file_get_contents($fheader);
        unlink($fheader);
    }
    $result['status']=$info['http_code'];
    if($info['http_code']==206) $result['status']=200;  // SPIP refuse les partial content alors qu'il les demande
    $result['headers']=$head;
    $result['page']=$body;
    if(isset($info['download_content_length']) and $info['download_content_length']>0) {
        $result['length']=$info['download_content_length'];
    }
    elseif(isset($info['size_download']) and $info['size_download']>0) {
        $result['length']=$info['size_download'];
    }
    if(isset($info['url']) and $info['url']!=$url) $result['location']=$info['url'];
    if($head) {  // analyse de head pour info
        $trouve=array('Content-Length'=>'length',  // texte avant : => cle du $result
                     'Last-Modified'=>'last_modified',
                     'Accept-Ranges'=>'range');
        $liste=preg_split('/[\r\n]+/',$head,-1,PREG_SPLIT_NO_EMPTY);
        if(is_array($liste)) {
            $liste=array_reverse($liste);
            foreach($liste as $l) {  // premier trouver donc dernier dans le head
                $lt=trim($l);
                if(strlen($lt)>5 and substr($lt,0,4)=='HTTP') break;  // on arrete sur derniere entete HTTP ?
                $item=explode(':',$lt,2);
                if(isset($item[0]) and isset($trouve[$item[0]])) {
                    $result[$trouve[$item[0]]]=trim($item[1]);
                    unset($trouve[$item[0]]);  // plus d'autre a chercher
                }
            }
        }
        // cas particuliers
        if($result['last_modified']) {
            $val=strtotime($result['last_modified']);
            if($val===false) {
                $result['last_modified']='';
            }
            else {
                $result['last_modified']=$val;
            }
        }
    }

    // Faut-il l'importer dans notre charset local ?
    if ($options['transcoder']) {
        include_spip('inc/charsets');
        $result['page'] = transcoder_page($result['page'], $result['headers']);
    }

    return $result;
}

Normalement, ce code doit être compatible avec l'ancienne fonction, il donne en plus en sortie ['range'] qui permet de savoir si un partial content peut être délivré.
Il ne prend plus en compte les entrées ['refuser_gz'], ['version_http'] et ['boundary'] et l'entrée ['data'] doit être compatible avec celle de 'curl_setopt : CURLOPT_POSTFIELDS'.
Si des gens veulent la tester et l'améliorer : bienvenue.

Petit nota : SPIP laisse le fichier créer dans cette fonction (['file']) dans le tmp sans le détruire (version de base idem) : accumulation !

#4 Updated by cedric - 2 months ago

Merci c'est une bonne base de départ !

Comme on a du vieux code legacy qui traine partout il faudrait faire le support des boundary et data et/ou la remise en forme de cette dernière dans les cas exotiques.
Ou simplement deleguer au vieux code legacy quand on a ces 2 entrées là (du coup la majorité des appels passent bien par curl, et les cas un peu tordus par l'ancien code, mais au moins continuent de marcher)

Dans ton cas je pense que c'est juste le bug sur ssl/tls qui te bloquait, qu'on a corrigé dans la dernière release des 3.1/3/2

#5 Updated by b b 2 months ago

Merci pour la proposition Eric :) Si tu es motivé, tu peux proposer le patch au format diff ou directement sur https://git.spip.net/ après l'avoir affiné suite aux remarques de Cedric.

#6 Updated by Eric Camus 2 months ago

Bonjour,

Pour les datas, je pense que qu'il y a une compatibilité entre celles attendus par la fonction 'recuperer_url' et celles attendue par l'option 'CURLOPT_POSTFIELDS' :
- SPIP 'recuperer_url' : string|array datas : Pour envoyer des donnees (array) et/ou entetes (string) (force la methode POST si donnees non vide)
- CUrl 'CURLOPT_POSTFIELDS' : Ce paramètre peut être passé sous la forme d'une chaîne encodée URL, comme 'para1=val1&para2=val2&...' ou sous la forme d'un tableau dont le nom du champ est la clé, et les données du champ la valeur. Si le paramètre value est un tableau, l'en-tête Content-Type sera définie à multipart/form-data (cf http://php.net/manual/fr/function.curl-setopt.php).
Je n'ai pas trouver de littérature sur le formatage de data en mode chaine pour 'recuperer_url' et pas analyser comment les fonctions dans 'recuperer_url' l'attendait, mais les tableaux doivent être compatibles non ?. Plus besoin de fournir le boundary qui n'est utile que pour les tableaux.

Pour ce qui est de mettre dans le git SPIP, je vais voir comment ça marche...

#7 Updated by cedric - about 1 month ago

Pour les datas, il faut faire de la conversion de format si besoin.

Je note ici pour mémoire et test de support d'un cas d'utilisation du format string pour poster du JSON vers une API qui attend un format json :
https://zone.spip.net/trac/spip-zone/browser/spip-zone/_plugins_/mailshot/trunk/lib/mailjet-api-php/mailjet-3.php#L116

#8 Updated by marcimat 🌈 about 1 month ago

Il me semble que Guzzle est relativement connu et utilisé pour ce genre de circonstance (et permettrait bien d’autres choses)
http://docs.guzzlephp.org/en/stable/
(et même du json http://docs.guzzlephp.org/en/stable/request-options.html#json)

Also available in: Atom PDF