Newer
Older
$path_parts = recuperer_infos_distantes($source, ['charger_si_petite_image' => false]);
ecrire_fichier($cache, serialize($path_parts));
}
$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);
}
cerdic
a validé
spip_log("pas de copie locale pour $source", 'distant' . _LOG_ERREUR);
}
/**
* 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
*
* Couples des informations obtenues parmis :
*
* - 'body' = chaine
* - 'type_image' = booleen
* - 'titre' = chaine
* - 'largeur' = intval
* - 'hauteur' = intval
* - 'taille' = intval
* - 'extension' = chaine
* - 'fichier' = chaine
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;
$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');
$mime_type = '';
// 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]);
&& is_callable($callback_valider_url)
&& !$callback_valider_url($reponse['url'])
return false;
}
$headers = $reponse['headers'] ?? '';
$a['body'] = $reponse['page'] ?? '';
if ($headers) {
$mime_type = distant_trouver_mime_type_selon_headers($source, $headers);
if (!$extension = distant_trouver_extension_selon_headers($source, $headers)) {
return false;
if (preg_match(",\nContent-Length: *([^[:space:]]*),i", "\n$headers", $regs)) {
cerdic
a validé
}
}
// Echec avec HEAD, on tente avec GET
cerdic
a validé
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');
RastaPopoulos
a validé
include_spip('inc/documents');
str_starts_with($mime_type, 'image/')
&& ($extension = _image_trouver_extension_depuis_mime($mime_type))
$taille_max == 0
&& (empty($a['taille']) || $a['taille'] < _INC_DISTANT_MAX_SIZE)
&& in_array($extension, formats_image_acceptables())
&& $charger_si_petite_image
cerdic
a validé
) {
$options['taille_max'] = _INC_DISTANT_MAX_SIZE;
$a = recuperer_infos_distantes($source, $options);
cerdic
a validé
} else {
if ($a['body']) {
RastaPopoulos
a validé
$a['extension'] = corriger_extension($extension);
$a['fichier'] = _DIR_RACINE . nom_fichier_copie_locale($source, $extension);
cerdic
a validé
ecrire_fichier($a['fichier'], $a['body']);
$a['largeur'] = (int) $size_image[0];
$a['hauteur'] = (int) $size_image[1];
cerdic
a validé
$a['type_image'] = true;
}
}
}
// Fichier swf, si on n'a pas la taille, on va mettre 425x350 par defaut
// ce sera mieux que 0x0
// Flash is dead!
$a
&& isset($a['extension'])
&& $a['extension'] == 'swf'
&& empty($a['largeur'])
cerdic
a validé
) {
$a['largeur'] = 425;
$a['hauteur'] = 350;
}
cerdic
a validé
if ($mime_type == 'text/html') {
$page = recuperer_url($source, ['transcoder' => true, 'taille_max' => _INC_DISTANT_MAX_SIZE]);
$page = $page['page'] ?? '';
if (preg_match(',<title>(.*?)</title>,ims', (string) $page, $regs)) {
$a['titre'] = corriger_caracteres(trim($regs[1]));
marcimat
a validé
}
if (!isset($a['taille']) || !$a['taille']) {
$a['taille'] = strlen((string) $page); # a peu pres
marcimat
a validé
}
return $a;
}
* Retrouver un mime type depuis les headers
function distant_trouver_mime_type_selon_headers(string $source, string $headers): string {
$mime_type = preg_match(",\nContent-Type: *([^[:space:];]*),i", "\n$headers", $regs) ? trim($regs[1]) : ''; // inconnu
// Appliquer les alias
while (isset($GLOBALS['mime_alias'][$mime_type])) {
$mime_type = $GLOBALS['mime_alias'][$mime_type];
}
return $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);
// pour corriger_extension()
include_spip('inc/documents');
// 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'])) {
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'));
&& preg_match(',^Content-Disposition:\s*attachment;\s*filename=(.*)$,Uims', $headers, $m)
&& preg_match(',\.([a-z0-9]+)(\?.*)?$,i', $m[1], $rext)
$t = sql_fetsel('extension', 'spip_types_documents', 'extension=' . sql_quote(corriger_extension($rext[1]), '', 'text'));
}
}
// 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
&& $mime_type != 'text/plain'
&& preg_match(',\.([a-z0-9]+)(\?.*)?$,i', $source, $rext)
$t = sql_fetsel('extension', 'spip_types_documents', 'extension=' . sql_quote(corriger_extension($rext[1]), '', 'text'));
}
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
cerdic
a validé
*
* On peut passer en parametre le proxy et la liste des host exclus,
* pour les besoins des tests, lors de la configuration
* @param string $host
* @param string $http_proxy
* @param string $http_noproxy
* @return string
*/
cerdic
a validé
function need_proxy($host, $http_proxy = null, $http_noproxy = null) {
$http_proxy ??= $GLOBALS['meta']['http_proxy'] ?? null;
// rien a faire si pas de proxy :)
if (is_null($http_proxy) || !$http_proxy = trim((string) $http_proxy)) {
return '';
}
if (is_null($http_noproxy)) {
$http_noproxy = $GLOBALS['meta']['http_noproxy'] ?? null;
// si pas d'exception, on retourne le proxy
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
$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
if (str_contains($http_noproxy, (string) " $domain ")) {
return '';
$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)
if (str_contains($http_noproxy, (string) " .$domain ")) {
return '';
}
}
// ok c'est pas une exception
return $http_proxy;
}
/**
* Initialise une requete HTTP avec entetes
cerdic
a validé
*
* 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
*/
function init_http($method, $url, $refuse_gz = false, $referer = '', $datas = '', $vers = 'HTTP/1.0', $date = '') {
$fopen = false;
$t = @parse_url($url);
$host = $t['host'];
cerdic
a validé
if ($t['scheme'] == 'http') {
cerdic
a validé
} elseif ($t['scheme'] == 'https') {
$scheme = 'ssl';
$noproxy = 'ssl://';
cerdic
a validé
if (!isset($t['port']) || !($port = $t['port'])) {
$t['port'] = 443;
}
// user et pass doivent être passés en urlencodé dans l'URL, on redecode ici
$user = [urldecode($t['user']), urldecode($t['pass'])];
cerdic
a validé
if (!isset($t['port']) || !($port = $t['port'])) {
$port = 80;
}
if (!isset($t['path']) || !($path = $t['path'])) {
cerdic
a validé
}
if (!empty($t['query'])) {
$f = lance_requete($method, $scheme, $user, $host, $path, $port, $noproxy, $refuse_gz, $referer, $datas, $vers, $date);
// fallback : fopen si on a pas fait timeout dans lance_requete
// ce qui correspond a $f===110
&& !need_proxy($host)
&& !_request('tester_proxy')
&& (!isset($GLOBALS['inc_distant_allow_fopen']) || $GLOBALS['inc_distant_allow_fopen'])
cerdic
a validé
) {
cerdic
a validé
spip_log("connexion vers $url par simple fopen", 'distant');
esj
a validé
$fopen = true;
cerdic
a validé
} else {
// echec total
}
esj
a validé
}
esj
a validé
}
/**
* Lancer la requete proprement dite
cerdic
a validé
*
* @param string $method
* type de la requete (GET, HEAD, POST...)
* @param string $scheme
* protocole (http, tls, ftp...)
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
* @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
* false|int si echec
* resource socket vers l'url demandee
*/
cerdic
a validé
function lance_requete(
$method,
$scheme,
$user,
$host,
$path,
$port,
$noproxy,
$refuse_gz = false,
$referer = '',
cerdic
a validé
$date = ''
) {
esj
a validé
$proxy_user = '';
$http_proxy = need_proxy($host);
cerdic
a validé
if ($user) {
$user = urlencode((string) $user[0]) . ':' . urlencode((string) $user[1]);
cerdic
a validé
}
cerdic
a validé
if ($http_proxy) {
if (!defined('_PROXY_HTTPS_NOT_VIA_CONNECT') && in_array($scheme, ['tls','ssl'])) {
$path_host = ($user ? "$user@" : '') . $host . (($port != 80) ? ":$port" : '');
$connect = 'CONNECT ' . $path_host . " $vers\r\n"
. "Host: $path_host\r\n"
. "Proxy-Connection: Keep-Alive\r\n";
} else {
$path = (in_array($scheme, ['tls','ssl']) ? 'https://' : "$scheme://")
. "$host" . (($port != 80) ? ":$port" : '') . $path;
}
$t2 = @parse_url($http_proxy);
$first_host = $t2['host'];
$first_port = ($t2['port'] ?? null) ?: 80;
$proxy_user = base64_encode($t2['user'] . ':' . $t2['pass']);
cerdic
a validé
}
} else {
$first_port = $port;
}
esj
a validé
cerdic
a validé
if ($connect) {
$streamContext = stream_context_create([
'ssl' => [
'verify_peer' => false,
'allow_self_signed' => true,
'SNI_enabled' => true,
'peer_name' => $host,
"tcp://$first_host:$first_port",
$errno,
$errstr,
_INC_DISTANT_CONNECT_TIMEOUT,
STREAM_CLIENT_CONNECT,
$streamContext
);
spip_log("Recuperer $path sur $first_host:$first_port par $f (via CONNECT)", 'connect');
if (!$f) {
cerdic
a validé
spip_log("Erreur connexion $errno $errstr", 'distant' . _LOG_ERREUR);
return $errno;
}
stream_set_timeout($f, _INC_DISTANT_CONNECT_TIMEOUT);
$res = fread($f, 1024);
|| ($res = explode(' ', $res)) === []
|| $res[1] !== '200'
cerdic
a validé
) {
spip_log("Echec CONNECT sur $first_host:$first_port", 'connect' . _LOG_INFO_IMPORTANTE);
fclose($f);
cerdic
a validé
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
stream_socket_enable_crypto($f, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT);
spip_log("OK CONNECT sur $first_host:$first_port", 'connect');
cerdic
a validé
} else {
$ntry = 3;
do {
$f = @fsockopen($first_host, $first_port, $errno, $errstr, _INC_DISTANT_CONNECT_TIMEOUT);
} while (!$f && $ntry-- && $errno !== 110 && sleep(1));
spip_log("Recuperer $path sur $first_host:$first_port par $f");
if (!$f) {
cerdic
a validé
spip_log("Erreur connexion $errno $errstr", 'distant' . _LOG_ERREUR);
cerdic
a validé
return $errno;
}
stream_set_timeout($f, _INC_DISTANT_CONNECT_TIMEOUT);
}
esj
a validé
$site = $GLOBALS['meta']['adresse_site'] ?? '';
esj
a validé
$host_port = $host;
if ($port != (in_array($scheme, ['tls','ssl']) ? 443 : 80)) {
$host_port .= ":$port";
}
esj
a validé
$req = "$method $path $vers\r\n"
. "Host: $host_port\r\n"
. 'User-Agent: ' . _INC_DISTANT_USER_AGENT . "\r\n"
. ($refuse_gz ? '' : ('Accept-Encoding: ' . _INC_DISTANT_CONTENT_ENCODING . "\r\n"))
. ($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" : '')
. ($proxy_user ? "Proxy-Authorization: Basic $proxy_user\r\n" : '')
. (strpos($vers, '1.1') ? "Keep-Alive: 300\r\nConnection: keep-alive\r\n" : '');
esj
a validé
cerdic
a validé
# spip_log("Requete\n$req", 'distant');
fwrite($f, $req);
fwrite($f, $datas ?: "\r\n");
cerdic
a validé
esj
a validé
return $f;