Newer
Older
<?php
/***************************************************************************\
* SPIP, Système de publication pour l'internet *
* Copyright © avec tendresse depuis 2001 *
* Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James *
* Ce programme est un logiciel libre distribué sous licence GNU/GPL. *
* Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne. *
\***************************************************************************/
/**
* Ce fichier gère l'obtention de données distantes
*
* @package SPIP\Core\Distant
cerdic
a validé
if (!defined('_ECRIRE_INC_VERSION')) {
return;
}
cerdic
a validé
if (!defined('_INC_DISTANT_VERSION_HTTP')) {
define('_INC_DISTANT_VERSION_HTTP', 'HTTP/1.0');
cerdic
a validé
}
if (!defined('_INC_DISTANT_CONTENT_ENCODING')) {
define('_INC_DISTANT_CONTENT_ENCODING', 'gzip');
cerdic
a validé
}
if (!defined('_INC_DISTANT_USER_AGENT')) {
define('_INC_DISTANT_USER_AGENT', 'SPIP-' . $GLOBALS['spip_version_affichee'] . ' (' . $GLOBALS['home_server'] . ')');
cerdic
a validé
}
if (!defined('_INC_DISTANT_MAX_SIZE')) {
define('_INC_DISTANT_MAX_SIZE', 2097152);
}
if (!defined('_INC_DISTANT_CONNECT_TIMEOUT')) {
define('_INC_DISTANT_CONNECT_TIMEOUT', 10);
}
define('_REGEXP_COPIE_LOCALE', ',' .
preg_replace(
'@^https?:@',
'https?:',
(isset($GLOBALS['meta']['adresse_site']) ? $GLOBALS['meta']['adresse_site'] : '')
)
. '/?spip.php[?]action=acceder_document.*file=(.*)$,');
cerdic
a validé
//@define('_COPIE_LOCALE_MAX_SIZE',2097152); // poids (inc/utils l'a fait)
/**
* Crée au besoin la copie locale d'un fichier distant
*
* Prend en argument un chemin relatif au rep racine, ou une URL
* Renvoie un chemin relatif au rep racine, ou false
*
* @link https://www.spip.net/4155
*
* @param string $mode
* - 'test' - ne faire que tester
* - 'auto' - charger au besoin
* - 'modif' - Si deja present, ne charger que si If-Modified-Since
* - 'force' - charger toujours (mettre a jour)
* @param string $local
* permet de specifier le nom du fichier local (stockage d'un cache par exemple, et non document IMG)
* @param int $taille_max
* taille maxi de la copie local, par defaut _COPIE_LOCALE_MAX_SIZE
* @return bool|string
*/
cerdic
a validé
function copie_locale($source, $mode = 'auto', $local = null, $taille_max = null) {
// si c'est la protection de soi-meme, retourner le path
marcimat
a validé
if ($mode !== 'force' and preg_match(_REGEXP_COPIE_LOCALE, $source, $match)) {
$source = substr(_DIR_IMG, strlen(_DIR_RACINE)) . urldecode($match[1]);
cerdic
a validé
return @file_exists($source) ? $source : false;
}
cerdic
a validé
if (is_null($local)) {
$local = fichier_copie_locale($source);
cerdic
a validé
} else {
marcimat
a validé
if (_DIR_RACINE and strncmp(_DIR_RACINE, $local, strlen(_DIR_RACINE)) == 0) {
$local = substr($local, strlen(_DIR_RACINE));
}
}
// si $local = '' c'est un fichier refuse par fichier_copie_locale(),
// par exemple un fichier qui ne figure pas dans nos documents ;
// dans ce cas on n'essaie pas de le telecharger pour ensuite echouer
cerdic
a validé
if (!$local) {
return false;
}
cerdic
a validé
$t = ($mode == 'force') ? false : @file_exists($localrac);
// test d'existence du fichier
cerdic
a validé
if ($mode == 'test') {
return $t ? $local : '';
}
// sinon voir si on doit/peut le telecharger
marcimat
a validé
if ($local == $source or !tester_url_absolue($source)) {
return $local;
cerdic
a validé
}
marcimat
a validé
if ($mode == 'modif' or !$t) {
// passer par un fichier temporaire unique pour gerer les echecs en cours de recuperation
// et des eventuelles recuperations concurantes
cerdic
a validé
if (!$taille_max) {
$taille_max = _COPIE_LOCALE_MAX_SIZE;
}
$res = recuperer_url(
$source,
array('file' => $localrac, 'taille_max' => $taille_max, 'if_modified_since' => $t ? filemtime($localrac) : '')
);
if (!$res or (!$res['length'] and $res['status'] != 304)) {
cerdic
a validé
spip_log("copie_locale : Echec recuperation $source sur $localrac status : " . $res['status'], 'distant' . _LOG_INFO_IMPORTANTE);
cerdic
a validé
}
if (!$res['length']) {
// si $t c'est sans doute juste un not-modified-since
return $t ? $local : false;
}
cerdic
a validé
spip_log("copie_locale : recuperation $source sur $localrac taille " . $res['length'] . ' OK', 'distant');
// pour une eventuelle indexation
array(
'args' => array(
'operation' => 'copie_locale',
'source' => $source,
'fichier' => $local,
'http_res' => $res['length'],
}
return $local;
}
cerdic
a validé
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/**
* Valider qu'une URL d'un document distant est bien distante
* et pas une url localhost qui permet d'avoir des infos sur le serveur
* inspiree de https://core.trac.wordpress.org/browser/trunk/src/wp-includes/http.php?rev=36435#L500
*
* @param string $url
* @param array $known_hosts
* url/hosts externes connus et acceptes
* @return false|string
* url ou false en cas d'echec
*/
function valider_url_distante($url, $known_hosts = array()) {
if (!function_exists('protocole_verifier')){
include_spip('inc/filtres_mini');
}
if (!protocole_verifier($url, array('http', 'https'))) {
return false;
}
$parsed_url = parse_url($url);
if (!$parsed_url or empty($parsed_url['host']) ) {
return false;
}
if (isset($parsed_url['user']) or isset($parsed_url['pass'])) {
return false;
}
if (false !== strpbrk($parsed_url['host'], ':#?[]')) {
return false;
}
if (!is_array($known_hosts)) {
$known_hosts = array($known_hosts);
}
$known_hosts[] = $GLOBALS['meta']['adresse_site'];
$known_hosts[] = url_de_base();
cerdic
a validé
$known_hosts = pipeline('declarer_hosts_distants', $known_hosts);
$is_known_host = false;
foreach ($known_hosts as $known_host) {
$parse_known = parse_url($known_host);
cerdic
a validé
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
if ($parse_known
and strtolower($parse_known['host']) === strtolower($parsed_url['host'])) {
$is_known_host = true;
break;
}
}
if (!$is_known_host) {
$host = trim($parsed_url['host'], '.');
if (preg_match('#^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $host)) {
$ip = $host;
} else {
$ip = gethostbyname($host);
if ($ip === $host) {
// Error condition for gethostbyname()
$ip = false;
}
}
if ($ip) {
$parts = array_map('intval', explode( '.', $ip ));
if (127 === $parts[0] or 10 === $parts[0] or 0 === $parts[0]
or ( 172 === $parts[0] and 16 <= $parts[1] and 31 >= $parts[1] )
or ( 192 === $parts[0] && 168 === $parts[1] )
) {
return false;
}
}
}
if (empty($parsed_url['port'])) {
return $url;
}
$port = $parsed_url['port'];
if ($port === 80 or $port === 443 or $port === 8080) {
return $url;
}
if ($is_known_host) {
foreach ($known_hosts as $known_host) {
$parse_known = parse_url($known_host);
cerdic
a validé
if ($parse_known
and !empty($parse_known['port'])
and strtolower($parse_known['host']) === strtolower($parsed_url['host'])
and $parse_known['port'] == $port) {
return $url;
}
}
}
return false;
}
/**
* Preparer les donnes pour un POST
* si $donnees est une chaine
* - charge a l'envoyeur de la boundariser, de gerer le Content-Type,
* de séparer les entetes des données par une ligne vide etc...
* - on traite les retour ligne pour les mettre au bon format
* - on decoupe en entete/corps (separes par ligne vide)
* si $donnees est un tableau
* - structuration en chaine avec boundary si necessaire ou fournie et bon Content-Type
*
* @param string|array $donnees
* @param string $boundary
* @return array
* entete,corps
*/
cerdic
a validé
function prepare_donnees_post($donnees, $boundary = '') {
// permettre a la fonction qui a demande le post de formater elle meme ses donnees
// pour un appel soap par exemple
// l'entete est separe des donnees par un double retour a la ligne
// on s'occupe ici de passer tous les retours lignes (\r\n, \r ou \n) en \r\n
cerdic
a validé
if (is_string($donnees) && strlen($donnees)) {
// on repasse tous les \r\n et \r en simples \n
$donnees = str_replace("\r\n", "\n", $donnees);
$donnees = str_replace("\r", "\n", $donnees);
// un double retour a la ligne signifie la fin de l'entete et le debut des donnees
cerdic
a validé
if ($p !== false) {
$entete = str_replace("\n", "\r\n", substr($donnees, 0, $p + 1));
$donnees = substr($donnees, $p + 2);
/* boundary automatique */
// Si on a plus de 500 octects de donnees, on "boundarise"
cerdic
a validé
if ($boundary === '') {
cerdic
a validé
foreach ($donnees as $cle => $valeur) {
if (is_array($valeur)) {
foreach ($valeur as $val2) {
$taille += strlen($val2);
}
} else {
// faut-il utiliser spip_strlen() dans inc/charsets ?
$taille += strlen($valeur);
}
}
cerdic
a validé
if ($taille > 500) {
cerdic
a validé
if (is_string($boundary) and strlen($boundary)) {
// fabrique une chaine HTTP pour un POST avec boundary
$entete = "Content-Type: multipart/form-data; boundary=$boundary\r\n";
$chaine = '';
cerdic
a validé
if (is_array($donnees)) {
foreach ($donnees as $cle => $valeur) {
if (is_array($valeur)) {
foreach ($valeur as $val2) {
$chaine .= "\r\n--$boundary\r\n";
$chaine .= "Content-Disposition: form-data; name=\"{$cle}[]\"\r\n";
$chaine .= "\r\n";
$chaine .= $val2;
}
} else {
$chaine .= "\r\n--$boundary\r\n";
$chaine .= "Content-Disposition: form-data; name=\"$cle\"\r\n";
$chaine .= "\r\n";
$chaine .= $valeur;
}
}
JamesRezo
a validé
$chaine .= "\r\n--$boundary\r\n";
}
} else {
// fabrique une chaine HTTP simple pour un POST
$entete = 'Content-Type: application/x-www-form-urlencoded' . "\r\n";
$chaine = array();
cerdic
a validé
if (is_array($donnees)) {
foreach ($donnees as $cle => $valeur) {
if (is_array($valeur)) {
foreach ($valeur as $val2) {
}
} else {
JamesRezo
a validé
}
}
$chaine = implode('&', $chaine);
} else {
$chaine = $donnees;
JamesRezo
a validé
}
}
cerdic
a validé
JamesRezo
a validé
return array($entete, $chaine);
}
/**
* Convertir une URL dont le host est en utf8 en ascii
* Utilise la librairie https://github.com/phlylabs/idna-convert/tree/v0.9.1
* dans sa derniere version compatible toutes version PHP 5
* La fonction PHP idn_to_ascii depend d'un package php5-intl et est rarement disponible
*
* @param string $url_idn
* @return array|string
*/
function url_to_ascii($url_idn) {
if ($parts = parse_url($url_idn)) {
$host = $parts['host'];
if (!preg_match(',^[a-z0-9_\.\-]+$,i', $host)) {
include_spip('inc/idna_convert.class');
$IDN = new idna_convert();
$host_ascii = $IDN->encode($host);
$url_idn = explode($host, $url_idn, 2);
$url_idn = implode($host_ascii, $url_idn);
}
// et on urlencode les char utf si besoin dans le path
$url_idn = preg_replace_callback('/[^\x20-\x7f]/', function($match) { return urlencode($match[0]); }, $url_idn);
}
return $url_idn;
}
/**
* Récupère le contenu d'une URL
* au besoin encode son contenu dans le charset local
*
* @uses init_http()
* @uses recuperer_body()
* @uses transcoder_page()
*
* @param string $url
* @param array $options
* bool transcoder : true si on veut transcoder la page dans le charset du site
* string methode : Type de requête HTTP à faire (HEAD, GET ou POST)
* int taille_max : Arrêter le contenu au-delà (0 = seulement les entetes ==> requête HEAD). Par defaut taille_max = 1Mo ou 16Mo si copie dans un fichier
* string|array datas : Pour envoyer des donnees (array) et/ou entetes au complet, avec saut de ligne entre headers et donnees ( string @see prepare_donnees_post()) (force la methode POST si donnees non vide)
* string boundary : boundary pour formater les datas au format array
* bool refuser_gz : Pour forcer le refus de la compression (cas des serveurs orthographiques)
* int if_modified_since : Un timestamp unix pour arrêter la récuperation si la page distante n'a pas été modifiée depuis une date donnée
* string uri_referer : Pour préciser un référer différent
* string file : nom du fichier dans lequel copier le contenu
* int follow_location : nombre de redirections a suivre (0 pour ne rien suivre)
* string version_http : version du protocole HTTP a utiliser (par defaut defini par la constante _INC_DISTANT_VERSION_HTTP)
* @return array|bool
* false si echec
* array sinon :
* int status : le status de la page
* string headers : les entetes de la page
* string page : le contenu de la page (vide si copie dans un fichier)
* int last_modified : timestamp de derniere modification
* string location : url de redirection envoyee par la page
* string url : url reelle de la page recuperee
* int length : taille du contenu ou du fichier
*
* string file : nom du fichier si enregistre dans un fichier
*/
cerdic
a validé
function recuperer_url($url, $options = array()) {
$default = array(
'transcoder' => false,
'methode' => 'GET',
'taille_max' => null,
'datas' => '',
'boundary' => '',
'refuser_gz' => false,
'if_modified_since' => '',
'uri_referer' => '',
'file' => '',
'follow_location' => 10,
'version_http' => _INC_DISTANT_VERSION_HTTP,
);
// copier directement dans un fichier ?
$copy = $options['file'];
$options['taille_max'] = 0;
cerdic
a validé
}
if (is_null($options['taille_max'])) {
$options['taille_max'] = $copy ? _COPIE_LOCALE_MAX_SIZE : _INC_DISTANT_MAX_SIZE;
cerdic
a validé
}
cerdic
a validé
if (!empty($options['datas'])) {
list($head, $postdata) = prepare_donnees_post($options['datas'], $options['boundary']);
if (stripos($head, 'Content-Length:') === false) {
$head .= 'Content-Length: ' . strlen($postdata);
cerdic
a validé
}
$options['datas'] = $head . "\r\n\r\n" . $postdata;
if (strlen($postdata)) {
$options['methode'] = 'POST';
}
}
// 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);
cerdic
a validé
if (!tester_url_absolue($url)) {
$url = 'http://' . $url;
cerdic
a validé
$url = 'http:' . $url;
}
$url = url_to_ascii($url);
cerdic
a validé
$result = array(
'status' => 0,
'headers' => '',
'page' => '',
'length' => 0,
'last_modified' => '',
'location' => '',
'url' => $url
);
// si on ecrit directement dans un fichier, pour ne pas manipuler en memoire refuser gz
marcimat
a validé
$refuser_gz = (($options['refuser_gz'] or $copy) ? true : false);
// ouvrir la connexion et envoyer la requete et ses en-tetes
list($handle, $fopen) = init_http(
$options['methode'],
$url,
$refuser_gz,
$options['uri_referer'],
$options['datas'],
$options['version_http'],
$options['if_modified_since']
);
cerdic
a validé
if (!$handle) {
cerdic
a validé
spip_log("ECHEC init_http $url", 'distant' . _LOG_ERREUR);
cerdic
a validé
return false;
}
// Sauf en fopen, envoyer le flux d'entree
// et recuperer les en-tetes de reponses
cerdic
a validé
if (!$fopen) {
$res = recuperer_entetes_complets($handle, $options['if_modified_since']);
cerdic
a validé
if (!$res) {
fclose($handle);
$t = @parse_url($url);
$host = $t['host'];
// Chinoisierie inexplicable pour contrer
// les actions liberticides de l'empire du milieu
if (!need_proxy($host)
marcimat
a validé
and $res = @file_get_contents($url)
cerdic
a validé
) {
$result['length'] = strlen($res);
cerdic
a validé
if ($copy) {
$result['page'] = $res;
}
$res = array(
'status' => 200,
);
cerdic
a validé
} else {
cerdic
a validé
spip_log("ECHEC chinoiserie $url", 'distant' . _LOG_ERREUR);
return false;
cerdic
a validé
}
marcimat
a validé
} elseif ($res['location'] and $options['follow_location']) {
$options['follow_location']--;
fclose($handle);
include_spip('inc/filtres');
$url = suivre_lien($url, $res['location']);
cerdic
a validé
spip_log("recuperer_url recommence sur $url", 'distant');
cerdic
a validé
cerdic
a validé
} elseif ($res['status'] !== 200) {
cerdic
a validé
spip_log('HTTP status ' . $res['status'] . " pour $url", 'distant');
}
$result['status'] = $res['status'];
cerdic
a validé
if (isset($res['headers'])) {
$result['headers'] = $res['headers'];
cerdic
a validé
}
if (isset($res['last_modified'])) {
$result['last_modified'] = $res['last_modified'];
cerdic
a validé
}
if (isset($res['location'])) {
$result['location'] = $res['location'];
cerdic
a validé
}
}
// on ne veut que les entetes
if (!$options['taille_max'] or $options['methode'] == 'HEAD' or $result['status'] == '304') {
return $result;
cerdic
a validé
}
// s'il faut deballer, le faire via un fichier temporaire
// sinon la memoire explose pour les gros flux
$gz = false;
cerdic
a validé
if (preg_match(",\bContent-Encoding: .*gzip,is", $result['headers'])) {
$gz = (_DIR_TMP . md5(uniqid(mt_rand())) . '.tmp.gz');
cerdic
a validé
}
// si on a pas deja recuperer le contenu par une methode detournee
cerdic
a validé
if (!$result['length']) {
$res = recuperer_body($handle, $options['taille_max'], $gz ? $gz : $copy);
fclose($handle);
cerdic
a validé
if ($copy) {
$result['length'] = $res;
$result['file'] = $copy;
$result['page'] = &$res;
$result['length'] = strlen($result['page']);
if (!$result['status']) {
$result['status'] = 200; // on a reussi, donc !
}
}
cerdic
a validé
if (!$result['page']) {
return $result;
cerdic
a validé
}
// Decompresser au besoin
cerdic
a validé
if ($gz) {
$result['page'] = implode('', gzfile($gz));
supprimer_fichier($gz);
}
// Faut-il l'importer dans notre charset local ?
cerdic
a validé
if ($options['transcoder']) {
include_spip('inc/charsets');
$result['page'] = transcoder_page($result['page'], $result['headers']);
}
return $result;
}
* Récuperer une URL si on l'a pas déjà dans un cache fichier
cerdic
a validé
*
* Le délai de cache est fourni par l'option `delai_cache`
* Les autres options et le format de retour sont identiques à la fonction `recuperer_url`
* @uses recuperer_url()
*
* @param string $url
* @param array $options
* int delai_cache : anciennete acceptable pour le contenu (en seconde)
* @return array|bool|mixed
*/
cerdic
a validé
function recuperer_url_cache($url, $options = array()) {
if (!defined('_DELAI_RECUPERER_URL_CACHE')) {
define('_DELAI_RECUPERER_URL_CACHE', 3600);
}
$default = array(
'transcoder' => false,
'methode' => 'GET',
'taille_max' => null,
'datas' => '',
'boundary' => '',
'refuser_gz' => false,
'if_modified_since' => '',
'uri_referer' => '',
'file' => '',
'follow_location' => 10,
'version_http' => _INC_DISTANT_VERSION_HTTP,
cerdic
a validé
'delai_cache' => in_array(_VAR_MODE, ['preview', 'recalcul']) ? 0 : _DELAI_RECUPERER_URL_CACHE,
// cas ou il n'est pas possible de cacher
marcimat
a validé
if (!empty($options['data']) or $options['methode'] == 'POST') {
cerdic
a validé
}
// ne pas tenter plusieurs fois la meme url en erreur (non cachee donc)
static $errors = array();
cerdic
a validé
if (isset($errors[$url])) {
cerdic
a validé
}
$sig = $options;
unset($sig['if_modified_since']);
unset($sig['delai_cache']);
$sig['url'] = $url;
$cache = md5(serialize($sig)) . '-' . substr(preg_replace(',\W+,', '_', $url), 0, 80);
$cache = "$sub$cache";
$res = false;
$is_cached = file_exists($cache);
and (filemtime($cache) > $_SERVER['REQUEST_TIME'] - $options['delai_cache'])
cerdic
a validé
) {
cerdic
a validé
if ($res = unserialize($res)) {
// mettre le last_modified et le status=304 ?
}
cerdic
a validé
if (!$res) {
$res = recuperer_url($url, $options);
// ne pas recharger cette url non cachee dans le meme hit puisque non disponible
cerdic
a validé
if (!$res) {
if ($is_cached) {
// on a pas reussi a recuperer mais on avait un cache : l'utiliser
lire_fichier($cache, $res);
$res = unserialize($res);
}
cerdic
a validé
return $errors[$url] = $res;
}
ecrire_fichier($cache, serialize($res));
}
return $res;
* Obsolète : Récupère une page sur le net et au besoin l'encode dans le charset local
*
* Gère les redirections de page (301) sur l'URL demandée (maximum 10 redirections)
*
* @deprecated 3.1
* @see recuperer_url()
* @uses recuperer_url()
*
* @param string $url
* URL de la page à récupérer
* @param bool|string $trans
* - chaîne longue : c'est un nom de fichier (nom pour sa copie locale)
* - true : demande d'encodage/charset
* - null : ne retourner que les headers
* @param bool $get_headers
* Si on veut récupérer les entêtes
* @param int|null $taille_max
* Arrêter le contenu au-delà (0 = seulement les entetes ==> requête HEAD).
* Par defaut taille_max = 1Mo.
* @param string|array $datas
* Pour faire un POST de données
* @param string $boundary
* Pour forcer l'envoi par cette méthode
* @param bool $refuser_gz
* Pour forcer le refus de la compression (cas des serveurs orthographiques)
* @param string $date_verif
* Un timestamp unix pour arrêter la récuperation si la page distante
* n'a pas été modifiée depuis une date donnée
* @param string $uri_referer
* Pour préciser un référer différent
* @return string|bool
* - Code de la page obtenue (avec ou sans entête)
* - false si la page n'a pu être récupérée (status different de 200)
cerdic
a validé
function recuperer_page(
$url,
$trans = false,
$get_headers = false,
$taille_max = null,
$datas = '',
$boundary = '',
$refuser_gz = false,
$date_verif = '',
$uri_referer = ''
) {
// $copy = copier le fichier ?
marcimat
a validé
$copy = (is_string($trans) and strlen($trans) > 5); // eviter "false" :-)
cerdic
a validé
if (!is_null($taille_max) and ($taille_max == 0)) {
$get = 'HEAD';
cerdic
a validé
} else {
$get = 'GET';
cerdic
a validé
}
$options = array(
cerdic
a validé
'transcoder' => $trans === true,
'methode' => $get,
'datas' => $datas,
'boundary' => $boundary,
'refuser_gz' => $refuser_gz,
'if_modified_since' => $date_verif,
'uri_referer' => $uri_referer,
'follow_location' => 10,
);
cerdic
a validé
if (!is_null($taille_max)) {
cerdic
a validé
}
// dix tentatives maximum en cas d'entetes 301...
cerdic
a validé
if (!$res) {
return false;
}
if ($res['status'] !== 200) {
return false;
}
if ($get_headers) {
return $res['headers'] . "\n" . $res['page'];
}
return $res['page'];
* Obsolete Récupère une page sur le net et au besoin l'encode dans le charset local
*
*
* @uses recuperer_url()
*
* @param string $url
* URL de la page à récupérer
* @param bool|null|string $trans
* - chaîne longue : c'est un nom de fichier (nom pour sa copie locale)
* - true : demande d'encodage/charset
* - null : ne retourner que les headers
* @param string $get
* Type de requête HTTP à faire (HEAD, GET ou POST)
* @param int|bool $taille_max
* Arrêter le contenu au-delà (0 = seulement les entetes ==> requête HEAD).
* Par defaut taille_max = 1Mo.
* @param string|array $datas
* Pour faire un POST de données
* @param bool $refuser_gz
* Pour forcer le refus de la compression (cas des serveurs orthographiques)
* @param string $date_verif
* Un timestamp unix pour arrêter la récuperation si la page distante
* n'a pas été modifiée depuis une date donnée
* @param string $uri_referer
* Pour préciser un référer différent
* @return string|array|bool
* - Retourne l'URL en cas de 301,
* - Un tableau (entête, corps) si ok,
* - false sinon
cerdic
a validé
function recuperer_lapage(
$url,
$trans = false,
$get = 'GET',
$taille_max = 1048576,
$datas = '',
$refuser_gz = false,
$date_verif = '',
$uri_referer = ''
) {
// $copy = copier le fichier ?
marcimat
a validé
$copy = (is_string($trans) and strlen($trans) > 5); // eviter "false" :-)
// si on ecrit directement dans un fichier, pour ne pas manipuler
// en memoire refuser gz
cerdic
a validé
if ($copy) {
$refuser_gz = true;
cerdic
a validé
}
$options = array(
cerdic
a validé
'transcoder' => $trans === true,
'methode' => $get,
'datas' => $datas,
'refuser_gz' => $refuser_gz,
'if_modified_since' => $date_verif,
'uri_referer' => $uri_referer,
'follow_location' => false,
);
cerdic
a validé
if (!is_null($taille_max)) {
cerdic
a validé
}
// dix tentatives maximum en cas d'entetes 301...
if (!$res) {
cerdic
a validé
return false;
}
if ($res['status'] !== 200) {
return false;
cerdic
a validé
}
return array($res['headers'], $res['page']);
}
/**
* Recuperer le contenu sur lequel pointe la resource passee en argument
* $taille_max permet de tronquer
* de l'url dont on a deja recupere les en-tetes
*
* @param resource $handle
* @param int $taille_max
* @param string $fichier
* fichier dans lequel copier le contenu de la resource
* @return bool|int|string
* bool false si echec
* int taille du fichier si argument fichier fourni
* string contenu de la resource
*/
cerdic
a validé
function recuperer_body($handle, $taille_max = _INC_DISTANT_MAX_SIZE, $fichier = '') {
$taille = 0;
$result = '';
cerdic
a validé
if ($fichier) {
include_spip('inc/acces');
$tmpfile = "$fichier." . creer_uniqid() . '.tmp';
$fp = spip_fopen_lock($tmpfile, 'w', LOCK_EX);
marcimat
a validé
if (!$fp and file_exists($fichier)) {
return filesize($fichier);
}
cerdic
a validé
if (!$fp) {
cerdic
a validé
}
$result = 0; // on renvoie la taille du fichier
}
marcimat
a validé
while (!feof($handle) and $taille < $taille_max) {
$res = fread($handle, 16384);
$taille += strlen($res);
cerdic
a validé
if ($fp) {
$result = $taille;
cerdic
a validé
} else {
$result .= $res;
cerdic
a validé
}
cerdic
a validé
if ($fp) {
spip_fclose_unlock($fp);
spip_unlink($fichier);
@rename($tmpfile, $fichier);
cerdic
a validé
if (!file_exists($fichier)) {
return false;
cerdic
a validé
}
}
cerdic
a validé
return $result;
}
/**
* Lit les entetes de reponse HTTP sur la socket $handle
* et retourne
* false en cas d'echec,
* un tableau associatif en cas de succes, contenant :
* - le status
* - le tableau complet des headers
* - la date de derniere modif si connue
* - l'url de redirection si specifiee
*
* @param resource $handle
* @param int|bool $if_modified_since
* @return bool|array
* int status
* string headers
* int last_modified
* string location
*/
cerdic
a validé
function recuperer_entetes_complets($handle, $if_modified_since = false) {
$result = array('status' => 0, 'headers' => array(), 'last_modified' => 0, 'location' => '');
$s = @trim(fgets($handle, 16384));
cerdic
a validé
if (!preg_match(',^HTTP/[0-9]+\.[0-9]+ ([0-9]+),', $s, $r)) {
return false;
}
$result['status'] = intval($r[1]);
cerdic
a validé
while ($s = trim(fgets($handle, 16384))) {
$result['headers'][] = $s . "\n";
preg_match(',^([^:]*): *(.*)$,i', $s, $r);
list(, $d, $v) = $r;
marcimat
a validé
if (strtolower(trim($d)) == 'location' and $result['status'] >= 300 and $result['status'] < 400) {
$result['location'] = $v;
cerdic
a validé
} elseif ($d == 'Last-Modified') {
$result['last_modified'] = strtotime($v);
}
}
if ($if_modified_since
marcimat
a validé
and $result['last_modified']
and $if_modified_since > $result['last_modified']
and $result['status'] == 200
cerdic
a validé
) {
$result['status'] = 304;
cerdic
a validé
}
$result['headers'] = implode('', $result['headers']);
return $result;
}
/**
* Calcule le nom canonique d'une copie local d'un fichier distant
*
* Si on doit conserver une copie locale des fichiers distants, autant que ca
* soit à un endroit canonique
*
* @note
* Si ca peut être bijectif c'est encore mieux,
* mais là tout de suite je ne trouve pas l'idee, étant donné les limitations
* des filesystems
*
* @param string $source
* URL de la source
* @param string $extension
* Extension du fichier
* @return string
* Nom du fichier pour copie locale
cerdic
a validé
function nom_fichier_copie_locale($source, $extension) {
include_spip('inc/documents');
$d = creer_repertoire_documents('distant'); # IMG/distant/
$d = sous_repertoire($d, $extension); # IMG/distant/pdf/
// on se place tout le temps comme si on etait a la racine
cerdic
a validé
if (_DIR_RACINE) {
cerdic
a validé
}
$m = md5($source);
return $d
. substr(preg_replace(',[^\w-],', '', basename($source)) . '-' . $m, 0, 12)
. substr($m, 0, 4)
. ".$extension";
}
/**
* Donne le nom de la copie locale de la source
*
* Soit obtient l'extension du fichier directement de l'URL de la source,
* soit tente de le calculer.
*
* @uses nom_fichier_copie_locale()
* @uses recuperer_infos_distantes()
*
* @param string $source
* URL de la source distante
* @return string
* Nom du fichier calculé
cerdic
a validé
function fichier_copie_locale($source) {
// Si c'est deja local pas de souci
cerdic
a validé
if (!tester_url_absolue($source)) {
if (_DIR_RACINE) {
$source = preg_replace(',^' . preg_quote(_DIR_RACINE) . ',', '', $source);
cerdic
a validé
}
return $source;
// optimisation : on regarde si on peut deviner l'extension dans l'url et si le fichier
// a deja ete copie en local avec cette extension
// dans ce cas elle est fiable, pas la peine de requeter en base
$path_parts = pathinfo($source);
if (!isset($path_parts['extension'])) {
$path_parts['extension'] = '';
}
$ext = $path_parts ? $path_parts['extension'] : '';
if ($ext
marcimat
a validé
and preg_match(',^\w+$,', $ext) // pas de php?truc=1&...
and $f = nom_fichier_copie_locale($source, $ext)
and file_exists(_DIR_RACINE . $f)
cerdic
a validé
) {
cerdic
a validé
}
// Si c'est deja dans la table des documents,
// ramener le nom de sa copie potentielle
$ext = sql_getfetsel('extension', 'spip_documents', 'fichier=' . sql_quote($source) . " AND distant='oui' AND extension <> ''");