Skip to content
Extraits de code Groupes Projets
distant.php 44,6 ko
Newer Older
		$path_parts = recuperer_infos_distantes($source, ['charger_si_petite_image' => false]);
		ecrire_fichier($cache, serialize($path_parts));
	}
marcimat's avatar
marcimat a validé
	$ext = empty($path_parts['extension']) ? '' : $path_parts['extension'];
	if ($ext && sql_getfetsel('extension', 'spip_types_documents', 'extension=' . sql_quote($ext))) {
		return nom_fichier_copie_locale($source, $ext);
	}
	spip_log("pas de copie locale pour $source", 'distant' . _LOG_ERREUR);
	return null;
/**
 * Récupérer les infos d'un document distant, sans trop le télécharger
 *
 * @param string $source
 *     URL de la source
 * @param array $options
 *     int $taille_max : Taille maximum du fichier à télécharger
 *     bool $charger_si_petite_image : Pour télécharger le document s'il est petit
 *     string $callback_valider_url : callback pour valider l'URL finale du document apres redirection
 *
 * @return array|false
 *     Couples des informations obtenues parmis :
 *
 *     - 'body' = chaine
 *     - 'type_image' = booleen
 *     - 'titre' = chaine
 *     - 'largeur' = intval
 *     - 'hauteur' = intval
 *     - 'taille' = intval
 *     - 'extension' = chaine
 *     - 'fichier' = chaine
 *     - 'mime_type' = chaine
cerdic's avatar
cerdic a validé
 **/
function recuperer_infos_distantes($source, $options = []) {
	// pas la peine de perdre son temps
	if (!tester_url_absolue($source)) {
		return false;
	}
	$taille_max = $options['taille_max'] ?? 0;
marcimat's avatar
marcimat a validé
	$charger_si_petite_image = (bool) ($options['charger_si_petite_image'] ?? true);
	$callback_valider_url = $options['callback_valider_url'] ?? null;

	# charger les alias des types mime
	include_spip('base/typedoc');

JamesRezo's avatar
JamesRezo a validé
	$a = [];
	// On va directement charger le debut des images et des fichiers html,
	// de maniere a attrapper le maximum d'infos (titre, taille, etc). Si
	// ca echoue l'utilisateur devra les entrer...
	$reponse = recuperer_url($source, ['taille_max' => $taille_max, 'refuser_gz' => true]);
marcimat's avatar
marcimat a validé
	if (
		$callback_valider_url
marcimat's avatar
marcimat a validé
		&& is_callable($callback_valider_url)
		&& !$callback_valider_url($reponse['url'])
marcimat's avatar
marcimat a validé
	) {
	$headers = $reponse['headers'] ?? '';
	$a['body'] = $reponse['page'] ?? '';
	if ($headers) {
		$mime_type = distant_trouver_mime_type_selon_headers($source, $headers);
cerdic's avatar
cerdic a validé
		if (!$extension = distant_trouver_extension_selon_headers($source, $headers)) {
			return false;
esj's avatar
esj a validé
		}
cerdic's avatar
cerdic a validé
		$a['extension'] = $extension;
kent1's avatar
kent1 a validé
		if (preg_match(",\nContent-Length: *([^[:space:]]*),i", "\n$headers", $regs)) {
marcimat's avatar
marcimat a validé
			$a['taille'] = (int) $regs[1];
marcimat's avatar
marcimat a validé
	if (!$a && !$taille_max) {
		spip_log("tenter GET $source", 'distant');
		$options['taille_max'] = _INC_DISTANT_MAX_SIZE;
		$a = recuperer_infos_distantes($source, $options);
	// si on a rien trouve pas la peine d'insister
	if (!$a) {
		return false;
	}

	// S'il s'agit d'une image pas trop grosse ou d'un fichier html, on va aller
	// recharger le document en GET et recuperer des donnees supplementaires...
	include_spip('inc/filtres_images_lib_mini');
JamesRezo's avatar
JamesRezo a validé
	if (
marcimat's avatar
marcimat a validé
		str_starts_with($mime_type, 'image/')
		&& ($extension = _image_trouver_extension_depuis_mime($mime_type))
JamesRezo's avatar
JamesRezo a validé
	) {
		if (
marcimat's avatar
marcimat a validé
			&& (empty($a['taille']) || $a['taille'] < _INC_DISTANT_MAX_SIZE)
			&& in_array($extension, formats_image_acceptables())
			&& $charger_si_petite_image
			$options['taille_max'] = _INC_DISTANT_MAX_SIZE;
			$a = recuperer_infos_distantes($source, $options);
				$a['extension'] = corriger_extension($extension);
				$a['fichier'] = _DIR_RACINE . nom_fichier_copie_locale($source, $extension);
				ecrire_fichier($a['fichier'], $a['body']);
cerdic's avatar
cerdic a validé
				$size_image = @spip_getimagesize($a['fichier']);
marcimat's avatar
marcimat a validé
				$a['largeur'] = (int) $size_image[0];
				$a['hauteur'] = (int) $size_image[1];

	// Fichier swf, si on n'a pas la taille, on va mettre 425x350 par defaut
	// ce sera mieux que 0x0
JamesRezo's avatar
JamesRezo a validé
	if (
marcimat's avatar
marcimat a validé
		$a
		&& isset($a['extension'])
		&& $a['extension'] == 'swf'
		&& empty($a['largeur'])
		$a['largeur'] = 425;
		$a['hauteur'] = 350;
	}

		include_spip('inc/filtres');
		$page = recuperer_url($source, ['transcoder' => true, 'taille_max' => _INC_DISTANT_MAX_SIZE]);
		$page = $page['page'] ?? '';
marcimat's avatar
marcimat a validé
		if (preg_match(',<title>(.*?)</title>,ims', (string) $page, $regs)) {
			$a['titre'] = corriger_caracteres(trim($regs[1]));
marcimat's avatar
marcimat a validé
		if (!isset($a['taille']) || !$a['taille']) {
			$a['taille'] = strlen((string) $page); # a peu pres
cerdic's avatar
cerdic a validé
	$a['mime_type'] = $mime_type;
cerdic's avatar
cerdic a validé
/**
 * Retrouver un mime type depuis les headers
cerdic's avatar
cerdic a validé
 */
function distant_trouver_mime_type_selon_headers(string $source, string $headers): string {
marcimat's avatar
marcimat a validé
	$mime_type = preg_match(",\nContent-Type: *([^[:space:];]*),i", "\n$headers", $regs) ? trim($regs[1]) : ''; // inconnu
cerdic's avatar
cerdic a validé

	// Appliquer les alias
	while (isset($GLOBALS['mime_alias'][$mime_type])) {
		$mime_type = $GLOBALS['mime_alias'][$mime_type];
	}
 * Retrouver une extension de fichier depuis les headers
 *
 * @return false|string
function distant_trouver_extension_selon_headers(string $source, string $headers) {
	$mime_type = distant_trouver_mime_type_selon_headers($source, $headers);
cerdic's avatar
cerdic a validé

	// pour corriger_extension()
	include_spip('inc/documents');
cerdic's avatar
cerdic a validé
	// Si on a un mime-type insignifiant
	// text/plain,application/octet-stream ou vide
	// c'est peut-etre que le serveur ne sait pas
	// ce qu'il sert ; on va tenter de detecter via l'extension de l'url
	// ou le Content-Disposition: attachment; filename=...
	$t = null;
	if (in_array($mime_type, ['text/plain', '', 'application/octet-stream'])) {
marcimat's avatar
marcimat a validé
		if (!$t && preg_match(',\.([a-z0-9]+)(\?.*)?$,i', $source, $rext)) {
			$t = sql_fetsel('extension', 'spip_types_documents', 'extension=' . sql_quote(corriger_extension($rext[1]), '', 'text'));
cerdic's avatar
cerdic a validé
		}
		if (
			!$t
marcimat's avatar
marcimat a validé
			&& preg_match(',^Content-Disposition:\s*attachment;\s*filename=(.*)$,Uims', $headers, $m)
			&& preg_match(',\.([a-z0-9]+)(\?.*)?$,i', $m[1], $rext)
cerdic's avatar
cerdic a validé
		) {
			$t = sql_fetsel('extension', 'spip_types_documents', 'extension=' . sql_quote(corriger_extension($rext[1]), '', 'text'));
cerdic's avatar
cerdic a validé
		}
	}

	// Autre mime/type (ou text/plain avec fichier d'extension inconnue)
	if (!$t) {
		$t = sql_fetsel('extension', 'spip_types_documents', 'mime_type=' . sql_quote($mime_type));
	}

	// Toujours rien ? (ex: audio/x-ogg au lieu de application/ogg)
	// On essaie de nouveau avec l'extension
	if (
		!$t
marcimat's avatar
marcimat a validé
		&& $mime_type != 'text/plain'
		&& preg_match(',\.([a-z0-9]+)(\?.*)?$,i', $source, $rext)
cerdic's avatar
cerdic a validé
	) {
		# eviter xxx.3 => 3gp (> SPIP 3)
		$t = sql_fetsel('extension', 'spip_types_documents', 'extension=' . sql_quote(corriger_extension($rext[1]), '', 'text'));
cerdic's avatar
cerdic a validé
	}

	if ($t) {
		spip_log("mime-type $mime_type ok, extension " . $t['extension'], 'distant');
		return $t['extension'];
	} else {
		# par defaut on retombe sur '.bin' si c'est autorise
		spip_log("mime-type $mime_type inconnu", 'distant');
		$t = sql_fetsel('extension', 'spip_types_documents', "extension='bin'");
		if (!$t) {
			return false;
		}
		return $t['extension'];
	}
}
/**
 * Tester si un host peut etre recuperer directement ou doit passer par un proxy
 * On peut passer en parametre le proxy et la liste des host exclus,
 * pour les besoins des tests, lors de la configuration
cerdic's avatar
cerdic a validé
 *
 * @param string $host
 * @param string $http_proxy
 * @param string $http_noproxy
 * @return string
 */
function need_proxy($host, $http_proxy = null, $http_noproxy = null) {

	$http_proxy ??= $GLOBALS['meta']['http_proxy'] ?? null;
marcimat's avatar
marcimat a validé

marcimat's avatar
marcimat a validé
	if (is_null($http_proxy) || !$http_proxy = trim((string) $http_proxy)) {
	if (is_null($http_noproxy)) {
		$http_noproxy = $GLOBALS['meta']['http_noproxy'] ?? null;
	// si pas d'exception, on retourne le proxy
marcimat's avatar
marcimat a validé
	if (is_null($http_noproxy) || !$http_noproxy = trim((string) $http_noproxy)) {
		return $http_proxy;
	}

	// si le host ou l'un des domaines parents est dans $http_noproxy on fait exception
	// $http_noproxy peut contenir plusieurs domaines separes par des espaces ou retour ligne
JamesRezo's avatar
JamesRezo a validé
	$http_noproxy = str_replace("\n", ' ', $http_noproxy);
	$http_noproxy = str_replace("\r", ' ', $http_noproxy);
	$http_noproxy = " $http_noproxy ";
	$domain = $host;
	// si le domaine exact www.example.org est dans les exceptions
marcimat's avatar
marcimat a validé
	if (str_contains($http_noproxy, (string) " $domain ")) {
JamesRezo's avatar
JamesRezo a validé
	}
marcimat's avatar
marcimat a validé
	while (str_contains($domain, '.')) {
		$domain = explode('.', $domain);
		array_shift($domain);
		$domain = implode('.', $domain);

		// ou si un domaine parent commencant par un . est dans les exceptions (indiquant qu'il couvre tous les sous-domaines)
marcimat's avatar
marcimat a validé
		if (str_contains($http_noproxy, (string) " .$domain ")) {
	// ok c'est pas une exception
	return $http_proxy;

/**
 * Initialise une requete HTTP avec entetes
 * Décompose l'url en son schema+host+path+port et lance la requete.
 * Retourne le descripteur sur lequel lire la réponse.
 *
 * @uses lance_requete()
 *
 * @param string $method
 *   HEAD, GET, POST
 * @param string $url
 * @param bool $refuse_gz
 * @param string $referer
 * @param string $datas
 * @param string $vers
 * @param string $date
 * @return array
 */
kent1's avatar
kent1 a validé
function init_http($method, $url, $refuse_gz = false, $referer = '', $datas = '', $vers = 'HTTP/1.0', $date = '') {
cerdic's avatar
cerdic a validé
	$user = $via_proxy = $proxy_user = '';

	$t = @parse_url($url);
	$host = $t['host'];
cerdic's avatar
cerdic a validé
		$scheme = 'http';
		$noproxy = '';
		if (!isset($t['port']) || !($port = $t['port'])) {
			$t['port'] = 443;
		}
cerdic's avatar
cerdic a validé
	} else {
cerdic's avatar
cerdic a validé
		$scheme = $t['scheme'];
		$noproxy = $scheme . '://';
	if (isset($t['user'])) {
		// user et pass doivent être passés en urlencodé dans l'URL, on redecode ici
		$user = [urldecode($t['user']), urldecode($t['pass'])];
	if (!isset($t['port']) || !($port = $t['port'])) {
		$port = 80;
	}
	if (!isset($t['path']) || !($path = $t['path'])) {
kent1's avatar
kent1 a validé
		$path = '/';

	if (!empty($t['query'])) {
kent1's avatar
kent1 a validé
		$path .= '?' . $t['query'];
kent1's avatar
kent1 a validé
	$f = lance_requete($method, $scheme, $user, $host, $path, $port, $noproxy, $refuse_gz, $referer, $datas, $vers, $date);
marcimat's avatar
marcimat a validé
	if (!$f || !is_resource($f)) {
		// fallback : fopen si on a pas fait timeout dans lance_requete
		// ce qui correspond a $f===110
JamesRezo's avatar
JamesRezo a validé
		if (
			$f !== 110
marcimat's avatar
marcimat a validé
			&& !need_proxy($host)
			&& !_request('tester_proxy')
			&& (!isset($GLOBALS['inc_distant_allow_fopen']) || $GLOBALS['inc_distant_allow_fopen'])
kent1's avatar
kent1 a validé
			$f = @fopen($url, 'rb');
			spip_log("connexion vers $url par simple fopen", 'distant');
cerdic's avatar
cerdic a validé
			$f = false;
JamesRezo's avatar
JamesRezo a validé
	return [$f, $fopen];
/**
 * Lancer la requete proprement dite
 * @param string $method
 *   type de la requete (GET, HEAD, POST...)
 * @param string $scheme
 * @param array $user
 *   couple (utilisateur, mot de passe) en cas d'authentification http
 * @param string $host
 *   nom de domaine
 * @param string $path
 *   chemin de la page cherchee
 * @param string $port
 *   port utilise pour la connexion
 * @param bool $noproxy
 *   protocole utilise si requete sans proxy
 * @param bool $refuse_gz
 *   refuser la compression GZ
 * @param string $referer
 *   referer
 * @param string $datas
 *   donnees postees
 * @param string $vers
 *   version HTTP
 * @param int|string $date
 *   timestamp pour entente If-Modified-Since
 * @return bool|resource
 *   resource socket vers l'url demandee
 */
function lance_requete(
	$method,
	$scheme,
	$user,
	$host,
	$path,
	$port,
	$noproxy,
	$refuse_gz = false,
	$referer = '',
kent1's avatar
kent1 a validé
	$datas = '',
	$vers = 'HTTP/1.0',
	$http_proxy = need_proxy($host);
marcimat's avatar
marcimat a validé
		$user = urlencode((string) $user[0]) . ':' . urlencode((string) $user[1]);
kent1's avatar
kent1 a validé
	$connect = '';
marcimat's avatar
marcimat a validé
		if (!defined('_PROXY_HTTPS_NOT_VIA_CONNECT') && in_array($scheme, ['tls','ssl'])) {
			$path_host = ($user ? "$user@" : '') . $host . (($port != 80) ? ":$port" : '');
kent1's avatar
kent1 a validé
			$connect = 'CONNECT ' . $path_host . " $vers\r\n"
cerdic's avatar
cerdic a validé
				. "Host: $path_host\r\n"
				. "Proxy-Connection: Keep-Alive\r\n";
		} else {
JamesRezo's avatar
JamesRezo a validé
			$path = (in_array($scheme, ['tls','ssl']) ? 'https://' : "$scheme://")
marcimat's avatar
marcimat a validé
				. ($user ? "$user@" : '')
kent1's avatar
kent1 a validé
				. "$host" . (($port != 80) ? ":$port" : '') . $path;
		$first_port = ($t2['port'] ?? null) ?: 80;
		if ($t2['user'] ?? null) {
kent1's avatar
kent1 a validé
			$proxy_user = base64_encode($t2['user'] . ':' . $t2['pass']);
cerdic's avatar
cerdic a validé
		$first_host = $noproxy . $host;
JamesRezo's avatar
JamesRezo a validé
		$streamContext = stream_context_create([
			'ssl' => [
				'verify_peer' => false,
				'allow_self_signed' => true,
				'SNI_enabled' => true,
				'peer_name' => $host,
JamesRezo's avatar
JamesRezo a validé
			]
		]);
kent1's avatar
kent1 a validé
		$f = @stream_socket_client(
			"tcp://$first_host:$first_port",
kent1's avatar
kent1 a validé
			$errno,
			$errstr,
			_INC_DISTANT_CONNECT_TIMEOUT,
			STREAM_CLIENT_CONNECT,
			$streamContext
		);
		spip_log("Recuperer $path sur $first_host:$first_port par $f (via CONNECT)", 'connect');
			spip_log("Erreur connexion $errno $errstr", 'distant' . _LOG_ERREUR);
		stream_set_timeout($f, _INC_DISTANT_CONNECT_TIMEOUT);
marcimat's avatar
marcimat a validé
		fwrite($f, $connect);
		fwrite($f, "\r\n");
JamesRezo's avatar
JamesRezo a validé
		if (
			!$res
marcimat's avatar
marcimat a validé
			|| ($res = explode(' ', $res)) === []
			|| $res[1] !== '200'
			spip_log("Echec CONNECT sur $first_host:$first_port", 'connect' . _LOG_INFO_IMPORTANTE);
			return false;
		}
		// important, car sinon on lit trop vite et les donnees ne sont pas encore dispo
		stream_set_blocking($f, true);
		// envoyer le handshake
cerdic's avatar
cerdic a validé
		stream_socket_enable_crypto($f, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT);
		spip_log("OK CONNECT sur $first_host:$first_port", 'connect');
			$f = @fsockopen($first_host, $first_port, $errno, $errstr, _INC_DISTANT_CONNECT_TIMEOUT);
marcimat's avatar
marcimat a validé
		} while (!$f && $ntry-- && $errno !== 110 && sleep(1));
		spip_log("Recuperer $path sur $first_host:$first_port par $f");
			spip_log("Erreur connexion $errno $errstr", 'distant' . _LOG_ERREUR);
		}
		stream_set_timeout($f, _INC_DISTANT_CONNECT_TIMEOUT);
	$site = $GLOBALS['meta']['adresse_site'] ?? '';
JamesRezo's avatar
JamesRezo a validé
	if ($port != (in_array($scheme, ['tls','ssl']) ? 443 : 80)) {
kent1's avatar
kent1 a validé
		. 'User-Agent: ' . _INC_DISTANT_USER_AGENT . "\r\n"
		. ($refuse_gz ? '' : ('Accept-Encoding: ' . _INC_DISTANT_CONTENT_ENCODING . "\r\n"))
marcimat's avatar
marcimat a validé
		. ($site ? "Referer: $site/$referer\r\n" : '')
		. ($date ? 'If-Modified-Since: ' . (gmdate('D, d M Y H:i:s', $date) . " GMT\r\n") : '')
		. ($user ? 'Authorization: Basic ' . base64_encode(urldecode($user)) . "\r\n" : '')
marcimat's avatar
marcimat a validé
		. ($proxy_user ? "Proxy-Authorization: Basic $proxy_user\r\n" : '')
		. (strpos($vers, '1.1') ? "Keep-Alive: 300\r\nConnection: keep-alive\r\n" : '');
marcimat's avatar
marcimat a validé
	fwrite($f, $req);
	fwrite($f, $datas ?: "\r\n");