[SPIP] v3.2.1-->v3.2.2
[lhc/web/www.git] / www / ecrire / inc / distant.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2019 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
12
13 /**
14 * Ce fichier gère l'obtention de données distantes
15 *
16 * @package SPIP\Core\Distant
17 **/
18 if (!defined('_ECRIRE_INC_VERSION')) {
19 return;
20 }
21
22 if (!defined('_INC_DISTANT_VERSION_HTTP')) {
23 define('_INC_DISTANT_VERSION_HTTP', 'HTTP/1.0');
24 }
25 if (!defined('_INC_DISTANT_CONTENT_ENCODING')) {
26 define('_INC_DISTANT_CONTENT_ENCODING', 'gzip');
27 }
28 if (!defined('_INC_DISTANT_USER_AGENT')) {
29 define('_INC_DISTANT_USER_AGENT', 'SPIP-' . $GLOBALS['spip_version_affichee'] . ' (' . $GLOBALS['home_server'] . ')');
30 }
31 if (!defined('_INC_DISTANT_MAX_SIZE')) {
32 define('_INC_DISTANT_MAX_SIZE', 2097152);
33 }
34 if (!defined('_INC_DISTANT_CONNECT_TIMEOUT')) {
35 define('_INC_DISTANT_CONNECT_TIMEOUT', 10);
36 }
37
38 define('_REGEXP_COPIE_LOCALE', ',' .
39 preg_replace(
40 '@^https?:@',
41 'https?:',
42 (isset($GLOBALS['meta']['adresse_site']) ? $GLOBALS['meta']['adresse_site'] : '')
43 )
44 . '/?spip.php[?]action=acceder_document.*file=(.*)$,');
45
46 //@define('_COPIE_LOCALE_MAX_SIZE',2097152); // poids (inc/utils l'a fait)
47
48 /**
49 * Crée au besoin la copie locale d'un fichier distant
50 *
51 * Prend en argument un chemin relatif au rep racine, ou une URL
52 * Renvoie un chemin relatif au rep racine, ou false
53 *
54 * @link http://www.spip.net/4155
55 * @pipeline_appel post_edition
56 *
57 * @param string $source
58 * @param string $mode
59 * - 'test' - ne faire que tester
60 * - 'auto' - charger au besoin
61 * - 'modif' - Si deja present, ne charger que si If-Modified-Since
62 * - 'force' - charger toujours (mettre a jour)
63 * @param string $local
64 * permet de specifier le nom du fichier local (stockage d'un cache par exemple, et non document IMG)
65 * @param int $taille_max
66 * taille maxi de la copie local, par defaut _COPIE_LOCALE_MAX_SIZE
67 * @return bool|string
68 */
69 function copie_locale($source, $mode = 'auto', $local = null, $taille_max = null) {
70
71 // si c'est la protection de soi-meme, retourner le path
72 if ($mode !== 'force' and preg_match(_REGEXP_COPIE_LOCALE, $source, $match)) {
73 $source = substr(_DIR_IMG, strlen(_DIR_RACINE)) . urldecode($match[1]);
74
75 return @file_exists($source) ? $source : false;
76 }
77
78 if (is_null($local)) {
79 $local = fichier_copie_locale($source);
80 } else {
81 if (_DIR_RACINE and strncmp(_DIR_RACINE, $local, strlen(_DIR_RACINE)) == 0) {
82 $local = substr($local, strlen(_DIR_RACINE));
83 }
84 }
85
86 // si $local = '' c'est un fichier refuse par fichier_copie_locale(),
87 // par exemple un fichier qui ne figure pas dans nos documents ;
88 // dans ce cas on n'essaie pas de le telecharger pour ensuite echouer
89 if (!$local) {
90 return false;
91 }
92
93 $localrac = _DIR_RACINE . $local;
94 $t = ($mode == 'force') ? false : @file_exists($localrac);
95
96 // test d'existence du fichier
97 if ($mode == 'test') {
98 return $t ? $local : '';
99 }
100
101 // sinon voir si on doit/peut le telecharger
102 if ($local == $source or !tester_url_absolue($source)) {
103 return $local;
104 }
105
106 if ($mode == 'modif' or !$t) {
107 // passer par un fichier temporaire unique pour gerer les echecs en cours de recuperation
108 // et des eventuelles recuperations concurantes
109 include_spip('inc/acces');
110 if (!$taille_max) {
111 $taille_max = _COPIE_LOCALE_MAX_SIZE;
112 }
113 $res = recuperer_url(
114 $source,
115 array('file' => $localrac, 'taille_max' => $taille_max, 'if_modified_since' => $t ? filemtime($localrac) : '')
116 );
117 if (!$res or (!$res['length'] and $res['status'] != 304)) {
118 spip_log("copie_locale : Echec recuperation $source sur $localrac status : " . $res['status'], _LOG_INFO_IMPORTANTE);
119 }
120 if (!$res['length']) {
121 // si $t c'est sans doute juste un not-modified-since
122 return $t ? $local : false;
123 }
124 spip_log("copie_locale : recuperation $source sur $localrac taille " . $res['length'] . ' OK');
125
126 // pour une eventuelle indexation
127 pipeline(
128 'post_edition',
129 array(
130 'args' => array(
131 'operation' => 'copie_locale',
132 'source' => $source,
133 'fichier' => $local,
134 'http_res' => $res['length'],
135 ),
136 'data' => null
137 )
138 );
139 }
140
141 return $local;
142 }
143
144 /**
145 * Valider qu'une URL d'un document distant est bien distante
146 * et pas une url localhost qui permet d'avoir des infos sur le serveur
147 * inspiree de https://core.trac.wordpress.org/browser/trunk/src/wp-includes/http.php?rev=36435#L500
148 *
149 * @param string $url
150 * @param array $known_hosts
151 * url/hosts externes connus et acceptes
152 * @return false|string
153 * url ou false en cas d'echec
154 */
155 function valider_url_distante($url, $known_hosts = array()) {
156 if (!function_exists('protocole_verifier')){
157 include_spip('inc/filtres_mini');
158 }
159
160 if (!protocole_verifier($url, array('http', 'https'))) {
161 return false;
162 }
163
164 $parsed_url = parse_url($url);
165 if (!$parsed_url or empty($parsed_url['host']) ) {
166 return false;
167 }
168
169 if (isset($parsed_url['user']) or isset($parsed_url['pass'])) {
170 return false;
171 }
172
173 if (false !== strpbrk($parsed_url['host'], ':#?[]')) {
174 return false;
175 }
176
177 if (!is_array($known_hosts)) {
178 $known_hosts = array($known_hosts);
179 }
180 $known_hosts[] = $GLOBALS['meta']['adresse_site'];
181 $known_hosts[] = url_de_base();
182 $known_hosts = pipeline('declarer_hosts_distants', $known_hosts);
183
184 $is_known_host = false;
185 foreach ($known_hosts as $known_host) {
186 $parse_known = parse_url($known_host);
187 if ($parse_known
188 and strtolower($parse_known['host']) === strtolower($parsed_url['host'])) {
189 $is_known_host = true;
190 break;
191 }
192 }
193
194 if (!$is_known_host) {
195 $host = trim($parsed_url['host'], '.');
196 if (preg_match('#^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $host)) {
197 $ip = $host;
198 } else {
199 $ip = gethostbyname($host);
200 if ($ip === $host) {
201 // Error condition for gethostbyname()
202 $ip = false;
203 }
204 }
205 if ($ip) {
206 $parts = array_map('intval', explode( '.', $ip ));
207 if (127 === $parts[0] or 10 === $parts[0] or 0 === $parts[0]
208 or ( 172 === $parts[0] and 16 <= $parts[1] and 31 >= $parts[1] )
209 or ( 192 === $parts[0] && 168 === $parts[1] )
210 ) {
211 return false;
212 }
213 }
214 }
215
216 if (empty($parsed_url['port'])) {
217 return $url;
218 }
219
220 $port = $parsed_url['port'];
221 if ($port === 80 or $port === 443 or $port === 8080) {
222 return $url;
223 }
224
225 if ($is_known_host) {
226 foreach ($known_hosts as $known_host) {
227 $parse_known = parse_url($known_host);
228 if ($parse_known
229 and !empty($parse_known['port'])
230 and strtolower($parse_known['host']) === strtolower($parsed_url['host'])
231 and $parse_known['port'] == $port) {
232 return $url;
233 }
234 }
235 }
236
237 return false;
238 }
239
240 /**
241 * Preparer les donnes pour un POST
242 * si $donnees est une chaine
243 * - charge a l'envoyeur de la boundariser, de gerer le Content-Type etc...
244 * - on traite les retour ligne pour les mettre au bon format
245 * - on decoupe en entete/corps (separes par ligne vide)
246 * si $donnees est un tableau
247 * - structuration en chaine avec boundary si necessaire ou fournie et bon Content-Type
248 *
249 * @param string|array $donnees
250 * @param string $boundary
251 * @return array
252 * entete,corps
253 */
254 function prepare_donnees_post($donnees, $boundary = '') {
255
256 // permettre a la fonction qui a demande le post de formater elle meme ses donnees
257 // pour un appel soap par exemple
258 // l'entete est separe des donnees par un double retour a la ligne
259 // on s'occupe ici de passer tous les retours lignes (\r\n, \r ou \n) en \r\n
260 if (is_string($donnees) && strlen($donnees)) {
261 $entete = '';
262 // on repasse tous les \r\n et \r en simples \n
263 $donnees = str_replace("\r\n", "\n", $donnees);
264 $donnees = str_replace("\r", "\n", $donnees);
265 // un double retour a la ligne signifie la fin de l'entete et le debut des donnees
266 $p = strpos($donnees, "\n\n");
267 if ($p !== false) {
268 $entete = str_replace("\n", "\r\n", substr($donnees, 0, $p + 1));
269 $donnees = substr($donnees, $p + 2);
270 }
271 $chaine = str_replace("\n", "\r\n", $donnees);
272 } else {
273 /* boundary automatique */
274 // Si on a plus de 500 octects de donnees, on "boundarise"
275 if ($boundary === '') {
276 $taille = 0;
277 foreach ($donnees as $cle => $valeur) {
278 if (is_array($valeur)) {
279 foreach ($valeur as $val2) {
280 $taille += strlen($val2);
281 }
282 } else {
283 // faut-il utiliser spip_strlen() dans inc/charsets ?
284 $taille += strlen($valeur);
285 }
286 }
287 if ($taille > 500) {
288 $boundary = substr(md5(rand() . 'spip'), 0, 8);
289 }
290 }
291
292 if (is_string($boundary) and strlen($boundary)) {
293 // fabrique une chaine HTTP pour un POST avec boundary
294 $entete = "Content-Type: multipart/form-data; boundary=$boundary\r\n";
295 $chaine = '';
296 if (is_array($donnees)) {
297 foreach ($donnees as $cle => $valeur) {
298 if (is_array($valeur)) {
299 foreach ($valeur as $val2) {
300 $chaine .= "\r\n--$boundary\r\n";
301 $chaine .= "Content-Disposition: form-data; name=\"{$cle}[]\"\r\n";
302 $chaine .= "\r\n";
303 $chaine .= $val2;
304 }
305 } else {
306 $chaine .= "\r\n--$boundary\r\n";
307 $chaine .= "Content-Disposition: form-data; name=\"$cle\"\r\n";
308 $chaine .= "\r\n";
309 $chaine .= $valeur;
310 }
311 }
312 $chaine .= "\r\n--$boundary\r\n";
313 }
314 } else {
315 // fabrique une chaine HTTP simple pour un POST
316 $entete = 'Content-Type: application/x-www-form-urlencoded' . "\r\n";
317 $chaine = array();
318 if (is_array($donnees)) {
319 foreach ($donnees as $cle => $valeur) {
320 if (is_array($valeur)) {
321 foreach ($valeur as $val2) {
322 $chaine[] = rawurlencode($cle) . '[]=' . rawurlencode($val2);
323 }
324 } else {
325 $chaine[] = rawurlencode($cle) . '=' . rawurlencode($valeur);
326 }
327 }
328 $chaine = implode('&', $chaine);
329 } else {
330 $chaine = $donnees;
331 }
332 }
333 }
334
335 return array($entete, $chaine);
336 }
337
338 /**
339 * Convertir une URL dont le host est en utf8 en ascii
340 * Utilise la librairie https://github.com/phlylabs/idna-convert/tree/v0.9.1
341 * dans sa derniere version compatible toutes version PHP 5
342 * La fonction PHP idn_to_ascii depend d'un package php5-intl et est rarement disponible
343 *
344 * @param string $url_idn
345 * @return array|string
346 */
347 function url_to_ascii($url_idn) {
348
349 if ($parts = parse_url($url_idn)) {
350 $host = $parts['host'];
351 if (!preg_match(',^[a-z0-9_\.\-]+$,i', $host)) {
352 include_spip('inc/idna_convert.class');
353 $IDN = new idna_convert();
354 $host_ascii = $IDN->encode($host);
355 $url_idn = explode($host, $url_idn, 2);
356 $url_idn = implode($host_ascii, $url_idn);
357 }
358 }
359
360 return $url_idn;
361 }
362
363 /**
364 * Récupère le contenu d'une URL
365 * au besoin encode son contenu dans le charset local
366 *
367 * @uses init_http()
368 * @uses recuperer_entetes()
369 * @uses recuperer_body()
370 * @uses transcoder_page()
371 *
372 * @param string $url
373 * @param array $options
374 * bool transcoder : true si on veut transcoder la page dans le charset du site
375 * string methode : Type de requête HTTP à faire (HEAD, GET ou POST)
376 * 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
377 * string|array datas : Pour envoyer des donnees (array) et/ou entetes (string) (force la methode POST si donnees non vide)
378 * string boundary : boundary pour formater les datas au format array
379 * bool refuser_gz : Pour forcer le refus de la compression (cas des serveurs orthographiques)
380 * 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
381 * string uri_referer : Pour préciser un référer différent
382 * string file : nom du fichier dans lequel copier le contenu
383 * int follow_location : nombre de redirections a suivre (0 pour ne rien suivre)
384 * string version_http : version du protocole HTTP a utiliser (par defaut defini par la constante _INC_DISTANT_VERSION_HTTP)
385 * @return array|bool
386 * false si echec
387 * array sinon :
388 * int status : le status de la page
389 * string headers : les entetes de la page
390 * string page : le contenu de la page (vide si copie dans un fichier)
391 * int last_modified : timestamp de derniere modification
392 * string location : url de redirection envoyee par la page
393 * string url : url reelle de la page recuperee
394 * int length : taille du contenu ou du fichier
395 *
396 * string file : nom du fichier si enregistre dans un fichier
397 */
398 function recuperer_url($url, $options = array()) {
399 $default = array(
400 'transcoder' => false,
401 'methode' => 'GET',
402 'taille_max' => null,
403 'datas' => '',
404 'boundary' => '',
405 'refuser_gz' => false,
406 'if_modified_since' => '',
407 'uri_referer' => '',
408 'file' => '',
409 'follow_location' => 10,
410 'version_http' => _INC_DISTANT_VERSION_HTTP,
411 );
412 $options = array_merge($default, $options);
413 // copier directement dans un fichier ?
414 $copy = $options['file'];
415
416 if ($options['methode'] == 'HEAD') {
417 $options['taille_max'] = 0;
418 }
419 if (is_null($options['taille_max'])) {
420 $options['taille_max'] = $copy ? _COPIE_LOCALE_MAX_SIZE : _INC_DISTANT_MAX_SIZE;
421 }
422
423 if (!empty($options['datas'])) {
424 list($head, $postdata) = prepare_donnees_post($options['datas'], $options['boundary']);
425 if (stripos($head, 'Content-Length:') === false) {
426 $head .= 'Content-Length: ' . strlen($postdata);
427 }
428 $options['datas'] = $head . "\r\n\r\n" . $postdata;
429 if (strlen($postdata)) {
430 $options['methode'] = 'POST';
431 }
432 }
433
434 // Accepter les URLs au format feed:// ou qui ont oublie le http:// ou les urls relatives au protocole
435 $url = preg_replace(',^feed://,i', 'http://', $url);
436 if (!tester_url_absolue($url)) {
437 $url = 'http://' . $url;
438 } elseif (strncmp($url, '//', 2) == 0) {
439 $url = 'http:' . $url;
440 }
441
442 $url = url_to_ascii($url);
443
444 $result = array(
445 'status' => 0,
446 'headers' => '',
447 'page' => '',
448 'length' => 0,
449 'last_modified' => '',
450 'location' => '',
451 'url' => $url
452 );
453
454 // si on ecrit directement dans un fichier, pour ne pas manipuler en memoire refuser gz
455 $refuser_gz = (($options['refuser_gz'] or $copy) ? true : false);
456
457 // ouvrir la connexion et envoyer la requete et ses en-tetes
458 list($handle, $fopen) = init_http(
459 $options['methode'],
460 $url,
461 $refuser_gz,
462 $options['uri_referer'],
463 $options['datas'],
464 $options['version_http'],
465 $options['if_modified_since']
466 );
467 if (!$handle) {
468 spip_log("ECHEC init_http $url");
469
470 return false;
471 }
472
473 // Sauf en fopen, envoyer le flux d'entree
474 // et recuperer les en-tetes de reponses
475 if (!$fopen) {
476 $res = recuperer_entetes_complets($handle, $options['if_modified_since']);
477 if (!$res) {
478 fclose($handle);
479 $t = @parse_url($url);
480 $host = $t['host'];
481 // Chinoisierie inexplicable pour contrer
482 // les actions liberticides de l'empire du milieu
483 if (!need_proxy($host)
484 and $res = @file_get_contents($url)
485 ) {
486 $result['length'] = strlen($res);
487 if ($copy) {
488 ecrire_fichier($copy, $res);
489 $result['file'] = $copy;
490 } else {
491 $result['page'] = $res;
492 }
493 $res = array(
494 'status' => 200,
495 );
496 } else {
497 return false;
498 }
499 } elseif ($res['location'] and $options['follow_location']) {
500 $options['follow_location']--;
501 fclose($handle);
502 include_spip('inc/filtres');
503 $url = suivre_lien($url, $res['location']);
504 spip_log("recuperer_url recommence sur $url");
505
506 return recuperer_url($url, $options);
507 } elseif ($res['status'] !== 200) {
508 spip_log('HTTP status ' . $res['status'] . " pour $url");
509 }
510 $result['status'] = $res['status'];
511 if (isset($res['headers'])) {
512 $result['headers'] = $res['headers'];
513 }
514 if (isset($res['last_modified'])) {
515 $result['last_modified'] = $res['last_modified'];
516 }
517 if (isset($res['location'])) {
518 $result['location'] = $res['location'];
519 }
520 }
521
522 // on ne veut que les entetes
523 if (!$options['taille_max'] or $options['methode'] == 'HEAD' or $result['status'] == '304') {
524 return $result;
525 }
526
527
528 // s'il faut deballer, le faire via un fichier temporaire
529 // sinon la memoire explose pour les gros flux
530
531 $gz = false;
532 if (preg_match(",\bContent-Encoding: .*gzip,is", $result['headers'])) {
533 $gz = (_DIR_TMP . md5(uniqid(mt_rand())) . '.tmp.gz');
534 }
535
536 // si on a pas deja recuperer le contenu par une methode detournee
537 if (!$result['length']) {
538 $res = recuperer_body($handle, $options['taille_max'], $gz ? $gz : $copy);
539 fclose($handle);
540 if ($copy) {
541 $result['length'] = $res;
542 $result['file'] = $copy;
543 } elseif ($res) {
544 $result['page'] = &$res;
545 $result['length'] = strlen($result['page']);
546 }
547 $result['status'] = 200; // on a reussi, donc !
548 }
549 if (!$result['page']) {
550 return $result;
551 }
552
553 // Decompresser au besoin
554 if ($gz) {
555 $result['page'] = implode('', gzfile($gz));
556 supprimer_fichier($gz);
557 }
558
559 // Faut-il l'importer dans notre charset local ?
560 if ($options['transcoder']) {
561 include_spip('inc/charsets');
562 $result['page'] = transcoder_page($result['page'], $result['headers']);
563 }
564
565 return $result;
566 }
567
568 /**
569 * Récuperer une URL si on l'a pas déjà dans un cache fichier
570 *
571 * Le délai de cache est fourni par l'option `delai_cache`
572 * Les autres options et le format de retour sont identiques à la fonction `recuperer_url`
573 * @uses recuperer_url()
574 *
575 * @param string $url
576 * @param array $options
577 * int delai_cache : anciennete acceptable pour le contenu (en seconde)
578 * @return array|bool|mixed
579 */
580 function recuperer_url_cache($url, $options = array()) {
581 if (!defined('_DELAI_RECUPERER_URL_CACHE')) {
582 define('_DELAI_RECUPERER_URL_CACHE', 3600);
583 }
584 $default = array(
585 'transcoder' => false,
586 'methode' => 'GET',
587 'taille_max' => null,
588 'datas' => '',
589 'boundary' => '',
590 'refuser_gz' => false,
591 'if_modified_since' => '',
592 'uri_referer' => '',
593 'file' => '',
594 'follow_location' => 10,
595 'version_http' => _INC_DISTANT_VERSION_HTTP,
596 'delai_cache' => _DELAI_RECUPERER_URL_CACHE,
597 );
598 $options = array_merge($default, $options);
599
600 // cas ou il n'est pas possible de cacher
601 if (!empty($options['data']) or $options['methode'] == 'POST') {
602 return recuperer_url($url, $options);
603 }
604
605 // ne pas tenter plusieurs fois la meme url en erreur (non cachee donc)
606 static $errors = array();
607 if (isset($errors[$url])) {
608 return $errors[$url];
609 }
610
611 $sig = $options;
612 unset($sig['if_modified_since']);
613 unset($sig['delai_cache']);
614 $sig['url'] = $url;
615
616 $dir = sous_repertoire(_DIR_CACHE, 'curl');
617 $cache = md5(serialize($sig)) . '-' . substr(preg_replace(',\W+,', '_', $url), 0, 80);
618 $sub = sous_repertoire($dir, substr($cache, 0, 2));
619 $cache = "$sub$cache";
620
621 $res = false;
622 $is_cached = file_exists($cache);
623 if ($is_cached
624 and (filemtime($cache) > $_SERVER['REQUEST_TIME'] - $options['delai_cache'])
625 ) {
626 lire_fichier($cache, $res);
627 if ($res = unserialize($res)) {
628 // mettre le last_modified et le status=304 ?
629 }
630 }
631 if (!$res) {
632 $res = recuperer_url($url, $options);
633 // ne pas recharger cette url non cachee dans le meme hit puisque non disponible
634 if (!$res) {
635 if ($is_cached) {
636 // on a pas reussi a recuperer mais on avait un cache : l'utiliser
637 lire_fichier($cache, $res);
638 $res = unserialize($res);
639 }
640
641 return $errors[$url] = $res;
642 }
643 ecrire_fichier($cache, serialize($res));
644 }
645
646 return $res;
647 }
648
649 /**
650 * Obsolète : Récupère une page sur le net et au besoin l'encode dans le charset local
651 *
652 * Gère les redirections de page (301) sur l'URL demandée (maximum 10 redirections)
653 *
654 * @deprecated
655 * @uses recuperer_url()
656 *
657 * @param string $url
658 * URL de la page à récupérer
659 * @param bool|string $trans
660 * - chaîne longue : c'est un nom de fichier (nom pour sa copie locale)
661 * - true : demande d'encodage/charset
662 * - null : ne retourner que les headers
663 * @param bool $get_headers
664 * Si on veut récupérer les entêtes
665 * @param int|null $taille_max
666 * Arrêter le contenu au-delà (0 = seulement les entetes ==> requête HEAD).
667 * Par defaut taille_max = 1Mo.
668 * @param string|array $datas
669 * Pour faire un POST de données
670 * @param string $boundary
671 * Pour forcer l'envoi par cette méthode
672 * @param bool $refuser_gz
673 * Pour forcer le refus de la compression (cas des serveurs orthographiques)
674 * @param string $date_verif
675 * Un timestamp unix pour arrêter la récuperation si la page distante
676 * n'a pas été modifiée depuis une date donnée
677 * @param string $uri_referer
678 * Pour préciser un référer différent
679 * @return string|bool
680 * - Code de la page obtenue (avec ou sans entête)
681 * - false si la page n'a pu être récupérée (status different de 200)
682 **/
683 function recuperer_page(
684 $url,
685 $trans = false,
686 $get_headers = false,
687 $taille_max = null,
688 $datas = '',
689 $boundary = '',
690 $refuser_gz = false,
691 $date_verif = '',
692 $uri_referer = ''
693 ) {
694 // $copy = copier le fichier ?
695 $copy = (is_string($trans) and strlen($trans) > 5); // eviter "false" :-)
696
697 if (!is_null($taille_max) and ($taille_max == 0)) {
698 $get = 'HEAD';
699 } else {
700 $get = 'GET';
701 }
702
703 $options = array(
704 'transcoder' => $trans === true,
705 'methode' => $get,
706 'datas' => $datas,
707 'boundary' => $boundary,
708 'refuser_gz' => $refuser_gz,
709 'if_modified_since' => $date_verif,
710 'uri_referer' => $uri_referer,
711 'file' => $copy ? $trans : '',
712 'follow_location' => 10,
713 );
714 if (!is_null($taille_max)) {
715 $options['taille_max'] = $taille_max;
716 }
717 // dix tentatives maximum en cas d'entetes 301...
718 $res = recuperer_url($url, $options);
719 if (!$res) {
720 return false;
721 }
722 if ($res['status'] !== 200) {
723 return false;
724 }
725 if ($get_headers) {
726 return $res['headers'] . "\n" . $res['page'];
727 }
728
729 return $res['page'];
730 }
731
732
733 /**
734 * Obsolete Récupère une page sur le net et au besoin l'encode dans le charset local
735 *
736 * @deprecated
737 *
738 * @uses recuperer_url()
739 *
740 * @param string $url
741 * URL de la page à récupérer
742 * @param bool|null|string $trans
743 * - chaîne longue : c'est un nom de fichier (nom pour sa copie locale)
744 * - true : demande d'encodage/charset
745 * - null : ne retourner que les headers
746 * @param string $get
747 * Type de requête HTTP à faire (HEAD, GET ou POST)
748 * @param int|bool $taille_max
749 * Arrêter le contenu au-delà (0 = seulement les entetes ==> requête HEAD).
750 * Par defaut taille_max = 1Mo.
751 * @param string|array $datas
752 * Pour faire un POST de données
753 * @param bool $refuser_gz
754 * Pour forcer le refus de la compression (cas des serveurs orthographiques)
755 * @param string $date_verif
756 * Un timestamp unix pour arrêter la récuperation si la page distante
757 * n'a pas été modifiée depuis une date donnée
758 * @param string $uri_referer
759 * Pour préciser un référer différent
760 * @return string|array|bool
761 * - Retourne l'URL en cas de 301,
762 * - Un tableau (entête, corps) si ok,
763 * - false sinon
764 **/
765 function recuperer_lapage(
766 $url,
767 $trans = false,
768 $get = 'GET',
769 $taille_max = 1048576,
770 $datas = '',
771 $refuser_gz = false,
772 $date_verif = '',
773 $uri_referer = ''
774 ) {
775 // $copy = copier le fichier ?
776 $copy = (is_string($trans) and strlen($trans) > 5); // eviter "false" :-)
777
778 // si on ecrit directement dans un fichier, pour ne pas manipuler
779 // en memoire refuser gz
780 if ($copy) {
781 $refuser_gz = true;
782 }
783
784 $options = array(
785 'transcoder' => $trans === true,
786 'methode' => $get,
787 'datas' => $datas,
788 'refuser_gz' => $refuser_gz,
789 'if_modified_since' => $date_verif,
790 'uri_referer' => $uri_referer,
791 'file' => $copy ? $trans : '',
792 'follow_location' => false,
793 );
794 if (!is_null($taille_max)) {
795 $options['taille_max'] = $taille_max;
796 }
797 // dix tentatives maximum en cas d'entetes 301...
798 $res = recuperer_url($url, $options);
799
800 if (!$res) {
801 return false;
802 }
803 if ($res['status'] !== 200) {
804 return false;
805 }
806
807 return array($res['headers'], $res['page']);
808 }
809
810 /**
811 * Recuperer le contenu sur lequel pointe la resource passee en argument
812 * $taille_max permet de tronquer
813 * de l'url dont on a deja recupere les en-tetes
814 *
815 * @param resource $handle
816 * @param int $taille_max
817 * @param string $fichier
818 * fichier dans lequel copier le contenu de la resource
819 * @return bool|int|string
820 * bool false si echec
821 * int taille du fichier si argument fichier fourni
822 * string contenu de la resource
823 */
824 function recuperer_body($handle, $taille_max = _INC_DISTANT_MAX_SIZE, $fichier = '') {
825 $taille = 0;
826 $result = '';
827 $fp = false;
828 if ($fichier) {
829 include_spip('inc/acces');
830 $tmpfile = "$fichier." . creer_uniqid() . '.tmp';
831 $fp = spip_fopen_lock($tmpfile, 'w', LOCK_EX);
832 if (!$fp and file_exists($fichier)) {
833 return filesize($fichier);
834 }
835 if (!$fp) {
836 return false;
837 }
838 $result = 0; // on renvoie la taille du fichier
839 }
840 while (!feof($handle) and $taille < $taille_max) {
841 $res = fread($handle, 16384);
842 $taille += strlen($res);
843 if ($fp) {
844 fwrite($fp, $res);
845 $result = $taille;
846 } else {
847 $result .= $res;
848 }
849 }
850 if ($fp) {
851 spip_fclose_unlock($fp);
852 spip_unlink($fichier);
853 @rename($tmpfile, $fichier);
854 if (!file_exists($fichier)) {
855 return false;
856 }
857 }
858
859 return $result;
860 }
861
862 /**
863 * Lit les entetes de reponse HTTP sur la socket $handle
864 * et retourne
865 * false en cas d'echec,
866 * un tableau associatif en cas de succes, contenant :
867 * - le status
868 * - le tableau complet des headers
869 * - la date de derniere modif si connue
870 * - l'url de redirection si specifiee
871 *
872 * @param resource $handle
873 * @param int|bool $if_modified_since
874 * @return bool|array
875 * int status
876 * string headers
877 * int last_modified
878 * string location
879 */
880 function recuperer_entetes_complets($handle, $if_modified_since = false) {
881 $result = array('status' => 0, 'headers' => array(), 'last_modified' => 0, 'location' => '');
882
883 $s = @trim(fgets($handle, 16384));
884 if (!preg_match(',^HTTP/[0-9]+\.[0-9]+ ([0-9]+),', $s, $r)) {
885 return false;
886 }
887 $result['status'] = intval($r[1]);
888 while ($s = trim(fgets($handle, 16384))) {
889 $result['headers'][] = $s . "\n";
890 preg_match(',^([^:]*): *(.*)$,i', $s, $r);
891 list(, $d, $v) = $r;
892 if (strtolower(trim($d)) == 'location' and $result['status'] >= 300 and $result['status'] < 400) {
893 $result['location'] = $v;
894 } elseif ($d == 'Last-Modified') {
895 $result['last_modified'] = strtotime($v);
896 }
897 }
898 if ($if_modified_since
899 and $result['last_modified']
900 and $if_modified_since > $result['last_modified']
901 and $result['status'] == 200
902 ) {
903 $result['status'] = 304;
904 }
905
906 $result['headers'] = implode('', $result['headers']);
907
908 return $result;
909 }
910
911 /**
912 * Obsolete : version simplifiee de recuperer_entetes_complets
913 * Retourne les informations d'entête HTTP d'un socket
914 *
915 * Lit les entêtes de reponse HTTP sur la socket $f
916 *
917 * @uses recuperer_entetes_complets()
918 * @deprecated
919 *
920 * @param resource $f
921 * Socket d'un fichier (issu de fopen)
922 * @param int|string $date_verif
923 * Pour tester une date de dernière modification
924 * @return string|int|array
925 * - la valeur (chaîne) de l'en-tete Location si on l'a trouvée
926 * - la valeur (numerique) du statut si different de 200, notamment Not-Modified
927 * - le tableau des entetes dans tous les autres cas
928 **/
929 function recuperer_entetes($f, $date_verif = '') {
930 //Cas ou la page distante n'a pas bouge depuis
931 //la derniere visite
932 $res = recuperer_entetes_complets($f, $date_verif);
933 if (!$res) {
934 return false;
935 }
936 if ($res['location']) {
937 return $res['location'];
938 }
939 if ($res['status'] != 200) {
940 return $res['status'];
941 }
942
943 return explode("\n", $res['headers']);
944 }
945
946 /**
947 * Calcule le nom canonique d'une copie local d'un fichier distant
948 *
949 * Si on doit conserver une copie locale des fichiers distants, autant que ca
950 * soit à un endroit canonique
951 *
952 * @note
953 * Si ca peut être bijectif c'est encore mieux,
954 * mais là tout de suite je ne trouve pas l'idee, étant donné les limitations
955 * des filesystems
956 *
957 * @param string $source
958 * URL de la source
959 * @param string $extension
960 * Extension du fichier
961 * @return string
962 * Nom du fichier pour copie locale
963 **/
964 function nom_fichier_copie_locale($source, $extension) {
965 include_spip('inc/documents');
966
967 $d = creer_repertoire_documents('distant'); # IMG/distant/
968 $d = sous_repertoire($d, $extension); # IMG/distant/pdf/
969
970 // on se place tout le temps comme si on etait a la racine
971 if (_DIR_RACINE) {
972 $d = preg_replace(',^' . preg_quote(_DIR_RACINE) . ',', '', $d);
973 }
974
975 $m = md5($source);
976
977 return $d
978 . substr(preg_replace(',[^\w-],', '', basename($source)) . '-' . $m, 0, 12)
979 . substr($m, 0, 4)
980 . ".$extension";
981 }
982
983 /**
984 * Donne le nom de la copie locale de la source
985 *
986 * Soit obtient l'extension du fichier directement de l'URL de la source,
987 * soit tente de le calculer.
988 *
989 * @uses nom_fichier_copie_locale()
990 * @uses recuperer_infos_distantes()
991 *
992 * @param string $source
993 * URL de la source distante
994 * @return string
995 * Nom du fichier calculé
996 **/
997 function fichier_copie_locale($source) {
998 // Si c'est deja local pas de souci
999 if (!tester_url_absolue($source)) {
1000 if (_DIR_RACINE) {
1001 $source = preg_replace(',^' . preg_quote(_DIR_RACINE) . ',', '', $source);
1002 }
1003
1004 return $source;
1005 }
1006
1007 // optimisation : on regarde si on peut deviner l'extension dans l'url et si le fichier
1008 // a deja ete copie en local avec cette extension
1009 // dans ce cas elle est fiable, pas la peine de requeter en base
1010 $path_parts = pathinfo($source);
1011 if (!isset($path_parts['extension'])) {
1012 $path_parts['extension'] = '';
1013 }
1014 $ext = $path_parts ? $path_parts['extension'] : '';
1015 if ($ext
1016 and preg_match(',^\w+$,', $ext) // pas de php?truc=1&...
1017 and $f = nom_fichier_copie_locale($source, $ext)
1018 and file_exists(_DIR_RACINE . $f)
1019 ) {
1020 return $f;
1021 }
1022
1023
1024 // Si c'est deja dans la table des documents,
1025 // ramener le nom de sa copie potentielle
1026 $ext = sql_getfetsel('extension', 'spip_documents', 'fichier=' . sql_quote($source) . " AND distant='oui' AND extension <> ''");
1027
1028 if ($ext) {
1029 return nom_fichier_copie_locale($source, $ext);
1030 }
1031
1032 // voir si l'extension indiquee dans le nom du fichier est ok
1033 // et si il n'aurait pas deja ete rapatrie
1034
1035 $ext = $path_parts ? $path_parts['extension'] : '';
1036
1037 if ($ext and sql_getfetsel('extension', 'spip_types_documents', 'extension=' . sql_quote($ext))) {
1038 $f = nom_fichier_copie_locale($source, $ext);
1039 if (file_exists(_DIR_RACINE . $f)) {
1040 return $f;
1041 }
1042 }
1043
1044 // Ping pour voir si son extension est connue et autorisee
1045 // avec mise en cache du resultat du ping
1046
1047 $cache = sous_repertoire(_DIR_CACHE, 'rid') . md5($source);
1048 if (!@file_exists($cache)
1049 or !$path_parts = @unserialize(spip_file_get_contents($cache))
1050 or _request('var_mode') == 'recalcul'
1051 ) {
1052 $path_parts = recuperer_infos_distantes($source, 0, false);
1053 ecrire_fichier($cache, serialize($path_parts));
1054 }
1055 $ext = !empty($path_parts['extension']) ? $path_parts['extension'] : '';
1056 if ($ext and sql_getfetsel('extension', 'spip_types_documents', 'extension=' . sql_quote($ext))) {
1057 return nom_fichier_copie_locale($source, $ext);
1058 }
1059 spip_log("pas de copie locale pour $source");
1060 }
1061
1062
1063 /**
1064 * Récupérer les infos d'un document distant, sans trop le télécharger
1065 *
1066 * @param string $source
1067 * URL de la source
1068 * @param int $max
1069 * Taille maximum du fichier à télécharger
1070 * @param bool $charger_si_petite_image
1071 * Pour télécharger le document s'il est petit
1072 * @return array
1073 * Couples des informations obtenues parmis :
1074 *
1075 * - 'body' = chaine
1076 * - 'type_image' = booleen
1077 * - 'titre' = chaine
1078 * - 'largeur' = intval
1079 * - 'hauteur' = intval
1080 * - 'taille' = intval
1081 * - 'extension' = chaine
1082 * - 'fichier' = chaine
1083 * - 'mime_type' = chaine
1084 **/
1085 function recuperer_infos_distantes($source, $max = 0, $charger_si_petite_image = true) {
1086
1087 // pas la peine de perdre son temps
1088 if (!tester_url_absolue($source)) {
1089 return false;
1090 }
1091
1092 # charger les alias des types mime
1093 include_spip('base/typedoc');
1094
1095 $a = array();
1096 $mime_type = '';
1097 // On va directement charger le debut des images et des fichiers html,
1098 // de maniere a attrapper le maximum d'infos (titre, taille, etc). Si
1099 // ca echoue l'utilisateur devra les entrer...
1100 if ($headers = recuperer_page($source, false, true, $max, '', '', true)) {
1101 list($headers, $a['body']) = preg_split(',\n\n,', $headers, 2);
1102
1103 if (preg_match(",\nContent-Type: *([^[:space:];]*),i", "\n$headers", $regs)) {
1104 $mime_type = (trim($regs[1]));
1105 } else {
1106 $mime_type = '';
1107 } // inconnu
1108
1109 // Appliquer les alias
1110 while (isset($GLOBALS['mime_alias'][$mime_type])) {
1111 $mime_type = $GLOBALS['mime_alias'][$mime_type];
1112 }
1113
1114 // Si on a un mime-type insignifiant
1115 // text/plain,application/octet-stream ou vide
1116 // c'est peut-etre que le serveur ne sait pas
1117 // ce qu'il sert ; on va tenter de detecter via l'extension de l'url
1118 // ou le Content-Disposition: attachment; filename=...
1119 $t = null;
1120 if (in_array($mime_type, array('text/plain', '', 'application/octet-stream'))) {
1121 if (!$t
1122 and preg_match(',\.([a-z0-9]+)(\?.*)?$,i', $source, $rext)
1123 ) {
1124 $t = sql_fetsel('extension', 'spip_types_documents', 'extension=' . sql_quote($rext[1], '', 'text'));
1125 }
1126 if (!$t
1127 and preg_match(',^Content-Disposition:\s*attachment;\s*filename=(.*)$,Uims', $headers, $m)
1128 and preg_match(',\.([a-z0-9]+)(\?.*)?$,i', $m[1], $rext)
1129 ) {
1130 $t = sql_fetsel('extension', 'spip_types_documents', 'extension=' . sql_quote($rext[1], '', 'text'));
1131 }
1132 }
1133
1134 // Autre mime/type (ou text/plain avec fichier d'extension inconnue)
1135 if (!$t) {
1136 $t = sql_fetsel('extension', 'spip_types_documents', 'mime_type=' . sql_quote($mime_type));
1137 }
1138
1139 // Toujours rien ? (ex: audio/x-ogg au lieu de application/ogg)
1140 // On essaie de nouveau avec l'extension
1141 if (!$t
1142 and $mime_type != 'text/plain'
1143 and preg_match(',\.([a-z0-9]+)(\?.*)?$,i', $source, $rext)
1144 ) {
1145 # eviter xxx.3 => 3gp (> SPIP 3)
1146 $t = sql_fetsel('extension', 'spip_types_documents', 'extension=' . sql_quote($rext[1], '', 'text'));
1147 }
1148
1149 if ($t) {
1150 spip_log("mime-type $mime_type ok, extension " . $t['extension']);
1151 $a['extension'] = $t['extension'];
1152 } else {
1153 # par defaut on retombe sur '.bin' si c'est autorise
1154 spip_log("mime-type $mime_type inconnu");
1155 $t = sql_fetsel('extension', 'spip_types_documents', "extension='bin'");
1156 if (!$t) {
1157 return false;
1158 }
1159 $a['extension'] = $t['extension'];
1160 }
1161
1162 if (preg_match(",\nContent-Length: *([^[:space:]]*),i", "\n$headers", $regs)) {
1163 $a['taille'] = intval($regs[1]);
1164 }
1165 }
1166
1167 // Echec avec HEAD, on tente avec GET
1168 if (!$a and !$max) {
1169 spip_log("tenter GET $source");
1170 $a = recuperer_infos_distantes($source, _INC_DISTANT_MAX_SIZE);
1171 }
1172
1173 // si on a rien trouve pas la peine d'insister
1174 if (!$a) {
1175 return false;
1176 }
1177
1178 // S'il s'agit d'une image pas trop grosse ou d'un fichier html, on va aller
1179 // recharger le document en GET et recuperer des donnees supplementaires...
1180 if (preg_match(',^image/(jpeg|gif|png|swf),', $mime_type)) {
1181 if ($max == 0
1182 and (empty($a['taille']) or $a['taille'] < _INC_DISTANT_MAX_SIZE)
1183 and isset($GLOBALS['meta']['formats_graphiques'])
1184 and (strpos($GLOBALS['meta']['formats_graphiques'], $a['extension']) !== false)
1185 and $charger_si_petite_image
1186 ) {
1187 $a = recuperer_infos_distantes($source, _INC_DISTANT_MAX_SIZE);
1188 } else {
1189 if ($a['body']) {
1190 $a['fichier'] = _DIR_RACINE . nom_fichier_copie_locale($source, $a['extension']);
1191 ecrire_fichier($a['fichier'], $a['body']);
1192 $size_image = @getimagesize($a['fichier']);
1193 $a['largeur'] = intval($size_image[0]);
1194 $a['hauteur'] = intval($size_image[1]);
1195 $a['type_image'] = true;
1196 }
1197 }
1198 }
1199
1200 // Fichier swf, si on n'a pas la taille, on va mettre 425x350 par defaut
1201 // ce sera mieux que 0x0
1202 if ($a and isset($a['extension']) and $a['extension'] == 'swf'
1203 and empty($a['largeur'])
1204 ) {
1205 $a['largeur'] = 425;
1206 $a['hauteur'] = 350;
1207 }
1208
1209 if ($mime_type == 'text/html') {
1210 include_spip('inc/filtres');
1211 $page = recuperer_page($source, true, false, _INC_DISTANT_MAX_SIZE);
1212 if (preg_match(',<title>(.*?)</title>,ims', $page, $regs)) {
1213 $a['titre'] = corriger_caracteres(trim($regs[1]));
1214 }
1215 if (!isset($a['taille']) or !$a['taille']) {
1216 $a['taille'] = strlen($page); # a peu pres
1217 }
1218 }
1219 $a['mime_type'] = $mime_type;
1220
1221 return $a;
1222 }
1223
1224
1225 /**
1226 * Tester si un host peut etre recuperer directement ou doit passer par un proxy
1227 *
1228 * On peut passer en parametre le proxy et la liste des host exclus,
1229 * pour les besoins des tests, lors de la configuration
1230 *
1231 * @param string $host
1232 * @param string $http_proxy
1233 * @param string $http_noproxy
1234 * @return string
1235 */
1236 function need_proxy($host, $http_proxy = null, $http_noproxy = null) {
1237 if (is_null($http_proxy)) {
1238 $http_proxy = isset($GLOBALS['meta']['http_proxy']) ? $GLOBALS['meta']['http_proxy'] : null;
1239 }
1240 // rien a faire si pas de proxy :)
1241 if (is_null($http_proxy) or !$http_proxy = trim($http_proxy)) {
1242 return '';
1243 }
1244
1245 if (is_null($http_noproxy)) {
1246 $http_noproxy = isset($GLOBALS['meta']['http_noproxy']) ? $GLOBALS['meta']['http_noproxy'] : null;
1247 }
1248 // si pas d'exception, on retourne le proxy
1249 if (is_null($http_noproxy) or !$http_noproxy = trim($http_noproxy)) {
1250 return $http_proxy;
1251 }
1252
1253 // si le host ou l'un des domaines parents est dans $http_noproxy on fait exception
1254 // $http_noproxy peut contenir plusieurs domaines separes par des espaces ou retour ligne
1255 $http_noproxy = str_replace("\n", " ", $http_noproxy);
1256 $http_noproxy = str_replace("\r", " ", $http_noproxy);
1257 $http_noproxy = " $http_noproxy ";
1258 $domain = $host;
1259 // si le domaine exact www.example.org est dans les exceptions
1260 if (strpos($http_noproxy, " $domain ") !== false)
1261 return '';
1262
1263 while (strpos($domain, '.') !== false) {
1264 $domain = explode('.', $domain);
1265 array_shift($domain);
1266 $domain = implode('.', $domain);
1267
1268 // ou si un domaine parent commencant par un . est dans les exceptions (indiquant qu'il couvre tous les sous-domaines)
1269 if (strpos($http_noproxy, " .$domain ") !== false) {
1270 return '';
1271 }
1272 }
1273
1274 // ok c'est pas une exception
1275 return $http_proxy;
1276 }
1277
1278
1279 /**
1280 * Initialise une requete HTTP avec entetes
1281 *
1282 * Décompose l'url en son schema+host+path+port et lance la requete.
1283 * Retourne le descripteur sur lequel lire la réponse.
1284 *
1285 * @uses lance_requete()
1286 *
1287 * @param string $method
1288 * HEAD, GET, POST
1289 * @param string $url
1290 * @param bool $refuse_gz
1291 * @param string $referer
1292 * @param string $datas
1293 * @param string $vers
1294 * @param string $date
1295 * @return array
1296 */
1297 function init_http($method, $url, $refuse_gz = false, $referer = '', $datas = '', $vers = 'HTTP/1.0', $date = '') {
1298 $user = $via_proxy = $proxy_user = '';
1299 $fopen = false;
1300
1301 $t = @parse_url($url);
1302 $host = $t['host'];
1303 if ($t['scheme'] == 'http') {
1304 $scheme = 'http';
1305 $noproxy = '';
1306 } elseif ($t['scheme'] == 'https') {
1307 $scheme = 'ssl';
1308 $noproxy = 'ssl://';
1309 if (!isset($t['port']) || !($port = $t['port'])) {
1310 $t['port'] = 443;
1311 }
1312 } else {
1313 $scheme = $t['scheme'];
1314 $noproxy = $scheme . '://';
1315 }
1316 if (isset($t['user'])) {
1317 $user = array($t['user'], $t['pass']);
1318 }
1319
1320 if (!isset($t['port']) || !($port = $t['port'])) {
1321 $port = 80;
1322 }
1323 if (!isset($t['path']) || !($path = $t['path'])) {
1324 $path = '/';
1325 }
1326
1327 if (!empty($t['query'])) {
1328 $path .= '?' . $t['query'];
1329 }
1330
1331 $f = lance_requete($method, $scheme, $user, $host, $path, $port, $noproxy, $refuse_gz, $referer, $datas, $vers, $date);
1332 if (!$f or !is_resource($f)) {
1333 // fallback : fopen si on a pas fait timeout dans lance_requete
1334 // ce qui correspond a $f===110
1335 if ($f !== 110
1336 and !need_proxy($host)
1337 and !_request('tester_proxy')
1338 and (!isset($GLOBALS['inc_distant_allow_fopen']) or $GLOBALS['inc_distant_allow_fopen'])
1339 ) {
1340 $f = @fopen($url, 'rb');
1341 spip_log("connexion vers $url par simple fopen");
1342 $fopen = true;
1343 } else {
1344 // echec total
1345 $f = false;
1346 }
1347 }
1348
1349 return array($f, $fopen);
1350 }
1351
1352 /**
1353 * Lancer la requete proprement dite
1354 *
1355 * @param string $method
1356 * type de la requete (GET, HEAD, POST...)
1357 * @param string $scheme
1358 * protocole (http, tls, ftp...)
1359 * @param array $user
1360 * couple (utilisateur, mot de passe) en cas d'authentification http
1361 * @param string $host
1362 * nom de domaine
1363 * @param string $path
1364 * chemin de la page cherchee
1365 * @param string $port
1366 * port utilise pour la connexion
1367 * @param bool $noproxy
1368 * protocole utilise si requete sans proxy
1369 * @param bool $refuse_gz
1370 * refuser la compression GZ
1371 * @param string $referer
1372 * referer
1373 * @param string $datas
1374 * donnees postees
1375 * @param string $vers
1376 * version HTTP
1377 * @param int|string $date
1378 * timestamp pour entente If-Modified-Since
1379 * @return bool|resource
1380 * false|int si echec
1381 * resource socket vers l'url demandee
1382 */
1383 function lance_requete(
1384 $method,
1385 $scheme,
1386 $user,
1387 $host,
1388 $path,
1389 $port,
1390 $noproxy,
1391 $refuse_gz = false,
1392 $referer = '',
1393 $datas = '',
1394 $vers = 'HTTP/1.0',
1395 $date = ''
1396 ) {
1397
1398 $proxy_user = '';
1399 $http_proxy = need_proxy($host);
1400 if ($user) {
1401 $user = urlencode($user[0]) . ':' . urlencode($user[1]);
1402 }
1403
1404 $connect = '';
1405 if ($http_proxy) {
1406 if (defined('_PROXY_HTTPS_VIA_CONNECT') and in_array($scheme , array('tls','ssl'))) {
1407 $path_host = (!$user ? '' : "$user@") . $host . (($port != 80) ? ":$port" : '');
1408 $connect = 'CONNECT ' . $path_host . " $vers\r\n"
1409 . "Host: $path_host\r\n"
1410 . "Proxy-Connection: Keep-Alive\r\n";
1411 } else {
1412 $path = (in_array($scheme , array('tls','ssl')) ? 'https://' : "$scheme://")
1413 . (!$user ? '' : "$user@")
1414 . "$host" . (($port != 80) ? ":$port" : '') . $path;
1415 }
1416 $t2 = @parse_url($http_proxy);
1417 $first_host = $t2['host'];
1418 if (!($port = $t2['port'])) {
1419 $port = 80;
1420 }
1421 if ($t2['user']) {
1422 $proxy_user = base64_encode($t2['user'] . ':' . $t2['pass']);
1423 }
1424 } else {
1425 $first_host = $noproxy . $host;
1426 }
1427
1428 if ($connect) {
1429 $streamContext = stream_context_create(array(
1430 'ssl' => array(
1431 'verify_peer' => false,
1432 'allow_self_signed' => true,
1433 'SNI_enabled' => true,
1434 'peer_name' => $host,
1435 )
1436 ));
1437 if (version_compare(phpversion(), '5.6', '<')) {
1438 stream_context_set_option($streamContext, 'ssl', 'SNI_server_name', $host);
1439 }
1440 $f = @stream_socket_client(
1441 "tcp://$first_host:$port",
1442 $errno,
1443 $errstr,
1444 _INC_DISTANT_CONNECT_TIMEOUT,
1445 STREAM_CLIENT_CONNECT,
1446 $streamContext
1447 );
1448 spip_log("Recuperer $path sur $first_host:$port par $f (via CONNECT)", 'connect');
1449 if (!$f) {
1450 spip_log("Erreur connexion $errno $errstr", _LOG_ERREUR);
1451 return $errno;
1452 }
1453 stream_set_timeout($f, _INC_DISTANT_CONNECT_TIMEOUT);
1454
1455 fputs($f, $connect);
1456 fputs($f, "\r\n");
1457 $res = fread($f, 1024);
1458 if (!$res
1459 or !count($res = explode(' ', $res))
1460 or $res[1] !== '200'
1461 ) {
1462 spip_log("Echec CONNECT sur $first_host:$port", 'connect' . _LOG_INFO_IMPORTANTE);
1463 fclose($f);
1464
1465 return false;
1466 }
1467 // important, car sinon on lit trop vite et les donnees ne sont pas encore dispo
1468 stream_set_blocking($f, true);
1469 // envoyer le handshake
1470 stream_socket_enable_crypto($f, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT);
1471 spip_log("OK CONNECT sur $first_host:$port", 'connect');
1472 } else {
1473 $ntry = 3;
1474 do {
1475 $f = @fsockopen($first_host, $port, $errno, $errstr, _INC_DISTANT_CONNECT_TIMEOUT);
1476 } while (!$f and $ntry-- and $errno !== 110 and sleep(1));
1477 spip_log("Recuperer $path sur $first_host:$port par $f");
1478 if (!$f) {
1479 spip_log("Erreur connexion $errno $errstr", _LOG_ERREUR);
1480
1481 return $errno;
1482 }
1483 stream_set_timeout($f, _INC_DISTANT_CONNECT_TIMEOUT);
1484 }
1485
1486 $site = isset($GLOBALS['meta']['adresse_site']) ? $GLOBALS['meta']['adresse_site'] : '';
1487
1488 $host_port = $host;
1489 if ($port != (in_array($scheme , array('tls','ssl')) ? 443 : 80)) {
1490 $host_port .= ":$port";
1491 }
1492 $req = "$method $path $vers\r\n"
1493 . "Host: $host_port\r\n"
1494 . 'User-Agent: ' . _INC_DISTANT_USER_AGENT . "\r\n"
1495 . ($refuse_gz ? '' : ('Accept-Encoding: ' . _INC_DISTANT_CONTENT_ENCODING . "\r\n"))
1496 . (!$site ? '' : "Referer: $site/$referer\r\n")
1497 . (!$date ? '' : 'If-Modified-Since: ' . (gmdate('D, d M Y H:i:s', $date) . " GMT\r\n"))
1498 . (!$user ? '' : ('Authorization: Basic ' . base64_encode($user) . "\r\n"))
1499 . (!$proxy_user ? '' : "Proxy-Authorization: Basic $proxy_user\r\n")
1500 . (!strpos($vers, '1.1') ? '' : "Keep-Alive: 300\r\nConnection: keep-alive\r\n");
1501
1502 # spip_log("Requete\n$req");
1503 fputs($f, $req);
1504 fputs($f, $datas ? $datas : "\r\n");
1505
1506 return $f;
1507 }