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

Open
opened 6 years ago by b_b · 8 comments
b_b commented 6 years ago
Owner

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 ?

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 ?
Owner

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)
Assigné à cedric
Statut changé à En cours

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) **Assigné à cedric** **Statut changé à En cours**
b_b commented 4 years ago
Poster
Owner

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

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

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 !

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 !
Owner

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

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
b_b commented 4 years ago
Poster
Owner

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.

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.

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...

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...
Owner

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

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
Owner

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)

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)
Sign in to join this conversation.
No Milestone
No project
No Assignees
4 Participants
Notifications
Due Date

No due date set.

Dependencies

This issue currently doesn't have any dependencies.

Loading…
There is no content yet.