spip_nursit/ecrire/inc/texte_mini.php
2023-06-01 17:30:12 +02:00

565 lines
17 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/***************************************************************************\
* SPIP, Systeme de publication pour l'internet *
* *
* Copyright (c) 2001-2019 *
* Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
* *
* Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
* Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
\***************************************************************************/
/**
* Gestion des textes et échappements (fonctions d'usages fréquents)
*
* @package SPIP\Core\Texte
**/
if (!defined('_ECRIRE_INC_VERSION')) {
return;
}
include_spip('inc/filtres');
include_spip('inc/lang');
/**
* Retourne une image d'une puce
*
* Le nom de l'image est déterminé par la globale 'puce' ou 'puce_prive'
* ou les mêmes suffixées de '_rtl' pour ce type de langues.
*
* @note
* On initialise la puce pour éviter `find_in_path()` à chaque rencontre de `\n-`
* Mais attention elle depend de la direction et de X_fonctions.php, ainsi que
* de l'espace choisi (public/prive)
*
* @return string
* Code HTML de la puce
**/
function definir_puce() {
// Attention au sens, qui n'est pas defini de la meme facon dans
// l'espace prive (spip_lang est la langue de l'interface, lang_dir
// celle du texte) et public (spip_lang est la langue du texte)
$dir = _DIR_RESTREINT ? lang_dir() : lang_dir($GLOBALS['spip_lang']);
$p = 'puce' . (test_espace_prive() ? '_prive' : '');
if ($dir == 'rtl') {
$p .= '_rtl';
}
if (!isset($GLOBALS[$p])) {
$img = find_in_path($p . '.gif');
list(, , , $size) = @getimagesize($img);
$GLOBALS[$p] = '<img src="' . $img . '" ' . $size . ' class="puce" alt="-" />';
}
return $GLOBALS[$p];
}
// XHTML - Preserver les balises-bloc : on liste ici tous les elements
// dont on souhaite qu'ils provoquent un saut de paragraphe
if (!defined('_BALISES_BLOCS')) {
define('_BALISES_BLOCS',
'address|applet|article|aside|blockquote|button|center|d[ltd]|div|fieldset|fig(ure|caption)|footer|form|h[1-6r]|hgroup|head|header|iframe|li|map|marquee|nav|noscript|object|ol|pre|section|t(able|[rdh]|body|foot|extarea)|ul|script|style'
);
}
if (!defined('_BALISES_BLOCS_REGEXP')) {
define('_BALISES_BLOCS_REGEXP', ',</?(' . _BALISES_BLOCS . ')[>[:space:]],iS');
}
//
// Echapper les elements perilleux en les passant en base64
//
// Creer un bloc base64 correspondant a $rempl ; au besoin en marquant
// une $source differente ; le script detecte automagiquement si ce qu'on
// echappe est un div ou un span
// http://code.spip.net/@code_echappement
function code_echappement($rempl, $source = '', $no_transform = false, $mode = null) {
if (!strlen($rempl)) {
return '';
}
// Tester si on echappe en span ou en div
if (is_null($mode) or !in_array($mode, array('div', 'span'))) {
$mode = preg_match(',</?(' . _BALISES_BLOCS . ')[>[:space:]],iS', $rempl) ? 'div' : 'span';
}
// Decouper en morceaux, base64 a des probleme selon la taille de la pile
$taille = 30000;
$return = "";
for ($i = 0; $i < strlen($rempl); $i += $taille) {
// Convertir en base64 et cacher dans un attribut
// utiliser les " pour eviter le re-encodage de ' et &#8217
$base64 = base64_encode(substr($rempl, $i, $taille));
$return .= "<$mode class=\"base64$source\" title=\"$base64\"></$mode>";
}
return $return;
}
// Echapper les <html>...</ html>
// http://code.spip.net/@traiter_echap_html_dist
function traiter_echap_html_dist($regs) {
return $regs[3];
}
// Echapper les <code>...</ code>
// http://code.spip.net/@traiter_echap_code_dist
function traiter_echap_code_dist($regs) {
list(, , $att, $corps) = $regs;
$echap = spip_htmlspecialchars($corps); // il ne faut pas passer dans entites_html, ne pas transformer les &#xxx; du code !
// ne pas mettre le <div...> s'il n'y a qu'une ligne
if (is_int(strpos($echap, "\n"))) {
// supprimer les sauts de ligne debut/fin
// (mais pas les espaces => ascii art).
$echap = preg_replace("/^[\n\r]+|[\n\r]+$/s", "", $echap);
$echap = nl2br($echap);
$echap = "<div style='text-align: left;' "
. "class='spip_code' dir='ltr'><code$att>"
. $echap . "</code></div>";
} else {
$echap = "<code$att class='spip_code' dir='ltr'>" . $echap . "</code>";
}
$echap = str_replace("\t", "&nbsp; &nbsp; &nbsp; &nbsp; ", $echap);
$echap = str_replace(" ", " &nbsp;", $echap);
return $echap;
}
// Echapper les <cadre>...</ cadre> aka <frame>...</ frame>
// http://code.spip.net/@traiter_echap_cadre_dist
function traiter_echap_cadre_dist($regs) {
$echap = trim(entites_html($regs[3]));
// compter les lignes un peu plus finement qu'avec les \n
$lignes = explode("\n", trim($echap));
$n = 0;
foreach ($lignes as $l) {
$n += floor(strlen($l) / 60) + 1;
}
$n = max($n, 2);
$echap = "\n<textarea readonly='readonly' cols='40' rows='$n' class='spip_cadre' dir='ltr'>$echap</textarea>";
return $echap;
}
// http://code.spip.net/@traiter_echap_frame_dist
function traiter_echap_frame_dist($regs) {
return traiter_echap_cadre_dist($regs);
}
// http://code.spip.net/@traiter_echap_script_dist
function traiter_echap_script_dist($regs) {
// rendre joli (et inactif) si c'est un script language=php
if (preg_match(',<script\b[^>]+php,ims', $regs[0])) {
return highlight_string($regs[0], true);
}
// Cas normal : le script passe tel quel
return $regs[0];
}
define('_PROTEGE_BLOCS', ',<(html|code|cadre|frame|script|style)(\b[^>]*)?>(.*)</\1>,UimsS');
// - pour $source voir commentaire infra (echappe_retour)
// - pour $no_transform voir le filtre post_autobr dans inc/filtres
// http://code.spip.net/@echappe_html
function echappe_html(
$letexte,
$source = '',
$no_transform = false,
$preg = ''
) {
if (!is_string($letexte) or !strlen($letexte)) {
return $letexte;
}
// si le texte recu est long PCRE risque d'exploser, on
// fait donc un mic-mac pour augmenter pcre.backtrack_limit
if (($len = strlen($letexte)) > 100000) {
if (!$old = @ini_get('pcre.backtrack_limit')) {
$old = 100000;
}
if ($len > $old) {
$a = @ini_set('pcre.backtrack_limit', $len);
spip_log("ini_set pcre.backtrack_limit=$len ($old)");
}
}
if (($preg or strpos($letexte, "<") !== false)
and preg_match_all($preg ? $preg : _PROTEGE_BLOCS, $letexte, $matches, PREG_SET_ORDER)
) {
foreach ($matches as $regs) {
// echappements tels quels ?
if ($no_transform) {
$echap = $regs[0];
} // sinon les traiter selon le cas
else {
if (function_exists($f = 'traiter_echap_' . strtolower($regs[1]))) {
$echap = $f($regs);
} else {
if (function_exists($f = $f . '_dist')) {
$echap = $f($regs);
}
}
}
$p = strpos($letexte, $regs[0]);
$letexte = substr_replace($letexte, code_echappement($echap, $source, $no_transform), $p, strlen($regs[0]));
}
}
if ($no_transform) {
return $letexte;
}
// Echapper le php pour faire joli (ici, c'est pas pour la securite)
// seulement si on a echappe les <script>
// (derogatoire car on ne peut pas faire passer < ? ... ? >
// dans une callback autonommee
if (strpos($preg ? $preg : _PROTEGE_BLOCS, 'script') !== false) {
if (strpos($letexte, "<" . "?") !== false and preg_match_all(',<[?].*($|[?]>),UisS',
$letexte, $matches, PREG_SET_ORDER)
) {
foreach ($matches as $regs) {
$letexte = str_replace($regs[0],
code_echappement(highlight_string($regs[0], true), $source),
$letexte);
}
}
}
return $letexte;
}
//
// Traitement final des echappements
// Rq: $source sert a faire des echappements "a soi" qui ne sont pas nettoyes
// par propre() : exemple dans multi et dans typo()
// http://code.spip.net/@echappe_retour
function echappe_retour($letexte, $source = '', $filtre = "") {
if (strpos($letexte, "base64$source")) {
# spip_log(spip_htmlspecialchars($letexte)); ## pour les curieux
$max_prof = 5;
while (strpos($letexte, "<") !== false
and
preg_match_all(',<(span|div)\sclass=[\'"]base64' . $source . '[\'"]\s(.*)>\s*</\1>,UmsS',
$letexte, $regs, PREG_SET_ORDER)
and $max_prof--) {
foreach ($regs as $reg) {
$rempl = base64_decode(extraire_attribut($reg[0], 'title'));
// recherche d'attributs supplementaires
$at = array();
foreach (array('lang', 'dir') as $attr) {
if ($a = extraire_attribut($reg[0], $attr)) {
$at[$attr] = $a;
}
}
if ($at) {
$rempl = '<' . $reg[1] . '>' . $rempl . '</' . $reg[1] . '>';
foreach ($at as $attr => $a) {
$rempl = inserer_attribut($rempl, $attr, $a);
}
}
if ($filtre) {
$rempl = $filtre($rempl);
}
$letexte = str_replace($reg[0], $rempl, $letexte);
}
}
}
return $letexte;
}
// Reinserer le javascript de confiance (venant des modeles)
// http://code.spip.net/@echappe_retour_modeles
function echappe_retour_modeles($letexte, $interdire_scripts = false) {
$letexte = echappe_retour($letexte);
// Dans les appels directs hors squelette, securiser aussi ici
if ($interdire_scripts) {
$letexte = interdire_scripts($letexte);
}
return trim($letexte);
}
/**
* Coupe un texte à une certaine longueur.
*
* Il essaie de ne pas couper les mots et enlève le formatage du texte.
* Si le texte original est plus long que lextrait coupé, alors des points
* de suite sont ajoutés à l'extrait, tel que ` (...)`.
*
* @note
* Les points de suite ne sont pas ajoutés sur les extraits
* très courts.
*
* @filtre
* @link http://www.spip.net/4275
*
* @param string $texte
* Texte à couper
* @param int $taille
* Taille de la coupe
* @param string $suite
* Points de suite ajoutés.
* @return string
* Texte coupé
**/
function couper($texte, $taille = 50, $suite = null) {
if (!($length = strlen($texte)) or $taille <= 0) {
return '';
}
$offset = 400 + 2 * $taille;
while ($offset < $length
and strlen(preg_replace(",<(!--|\w|/)[^>]+>,Uims", "", substr($texte, 0, $offset))) < $taille) {
$offset = 2 * $offset;
}
if ($offset < $length
&& ($p_tag_ouvrant = strpos($texte, '<', $offset)) !== null
) {
$p_tag_fermant = strpos($texte, '>', $offset);
if ($p_tag_fermant && ($p_tag_fermant < $p_tag_ouvrant)) {
$offset = $p_tag_fermant + 1;
} // prolonger la coupe jusqu'au tag fermant suivant eventuel
}
$texte = substr($texte, 0, $offset); /* eviter de travailler sur 10ko pour extraire 150 caracteres */
if (!function_exists('nettoyer_raccourcis_typo')) {
include_spip('inc/lien');
}
$texte = nettoyer_raccourcis_typo($texte);
// balises de sauts de ligne et paragraphe
$texte = preg_replace("/<p( [^>]*)?" . ">/", "\r", $texte);
$texte = preg_replace("/<br( [^>]*)?" . ">/", "\n", $texte);
// on repasse les doubles \n en \r que nettoyer_raccourcis_typo() a pu modifier
$texte = str_replace("\n\n", "\r", $texte);
// supprimer les tags
$texte = supprimer_tags($texte);
$texte = trim(str_replace("\n", " ", $texte));
$texte .= "\n"; // marquer la fin
// corriger la longueur de coupe
// en fonction de la presence de caracteres utf
if ($GLOBALS['meta']['charset'] == 'utf-8') {
$long = charset2unicode($texte);
$long = spip_substr($long, 0, max($taille, 1));
$nbcharutf = preg_match_all('/(&#[0-9]{3,6};)/S', $long, $matches);
$taille += $nbcharutf;
}
// couper au mot precedent
$long = spip_substr($texte, 0, max($taille - 4, 1));
$u = $GLOBALS['meta']['pcre_u'];
$court = preg_replace("/([^\s][\s]+)[^\s]*\n?$/" . $u, "\\1", $long);
if (is_null($suite)) {
$suite = (defined('_COUPER_SUITE') ? _COUPER_SUITE : '&nbsp;(...)');
}
$points = $suite;
// trop court ? ne pas faire de (...)
if (spip_strlen($court) < max(0.75 * $taille, 2)) {
$points = '';
$long = spip_substr($texte, 0, $taille);
$texte = preg_replace("/([^\s][\s]+)[^\s]*\n?$/" . $u, "\\1", $long);
// encore trop court ? couper au caractere
if (spip_strlen($texte) < 0.75 * $taille) {
$texte = $long;
}
} else {
$texte = $court;
}
if (strpos($texte, "\n")) // la fin est encore la : c'est qu'on n'a pas de texte de suite
{
$points = '';
}
// remettre les paragraphes
$texte = preg_replace("/\r+/", "\n\n", $texte);
// supprimer l'eventuelle entite finale mal coupee
$texte = preg_replace('/&#?[a-z0-9]*$/S', '', $texte);
return quote_amp(trim($texte)) . $points;
}
// http://code.spip.net/@protege_js_modeles
function protege_js_modeles($t) {
if (isset($GLOBALS['visiteur_session'])) {
if (preg_match_all(',<script.*?($|</script.),isS', $t, $r, PREG_SET_ORDER)) {
if (!defined('_PROTEGE_JS_MODELES')) {
include_spip('inc/acces');
define('_PROTEGE_JS_MODELES', creer_uniqid());
}
foreach ($r as $regs) {
$t = str_replace($regs[0], code_echappement($regs[0], 'javascript' . _PROTEGE_JS_MODELES), $t);
}
}
if (preg_match_all(',<\?php.*?($|\?' . '>),isS', $t, $r, PREG_SET_ORDER)) {
if (!defined('_PROTEGE_PHP_MODELES')) {
include_spip('inc/acces');
define('_PROTEGE_PHP_MODELES', creer_uniqid());
}
foreach ($r as $regs) {
$t = str_replace($regs[0], code_echappement($regs[0], 'php' . _PROTEGE_PHP_MODELES), $t);
}
}
}
return $t;
}
function echapper_faux_tags($letexte) {
if (strpos($letexte, '<') === false) {
return $letexte;
}
$textMatches = preg_split(',(</?[a-z!][^<>]*>),', $letexte, null, PREG_SPLIT_DELIM_CAPTURE);
$letexte = "";
while (count($textMatches)) {
// un texte a echapper
$letexte .= str_replace("<", '&lt;', array_shift($textMatches));
// un tag html qui a servit a faite le split
$letexte .= array_shift($textMatches);
}
return $letexte;
}
/**
* Si le html contenu dans un texte ne passe pas sans transformation a travers safehtml
* on l'echappe
* si safehtml ne renvoie pas la meme chose on echappe les < en &lt; pour montrer le contenu brut
*
* @param string $texte
* @param bool $strict
* @return string
*/
function echapper_html_suspect($texte, $strict=true) {
static $echapper_html_suspect;
if (!$texte or !is_string($texte)) {
return $texte;
}
if (!isset($echapper_html_suspect)) {
$echapper_html_suspect = charger_fonction('echapper_html_suspect', 'inc', true);
}
// si fonction personalisee, on delegue
if ($echapper_html_suspect) {
return $echapper_html_suspect($texte, $strict);
}
if (strpos($texte, '<') === false
or strpos($texte, '=') === false) {
return $texte;
}
// quand c'est du texte qui passe par propre on est plus coulant tant qu'il y a pas d'attribut du type onxxx=
// car sinon on declenche sur les modeles ou ressources
if (!$strict and
(strpos($texte,'on') === false or !preg_match(",<\w+.*\bon\w+\s*=,UimsS", $texte))
){
return $texte;
}
// on teste sur strlen car safehtml supprime le contenu dangereux
// mais il peut aussi changer des ' en " sur les attributs html,
// donc un test d'egalite est trop strict
if (strlen(safehtml($texte)) !== strlen($texte)) {
$texte = str_replace("<", "&lt;", $texte);
if (!function_exists('attribut_html')) {
include_spip('inc/filtres');
}
$texte = "<mark class='danger-js' title='".attribut_html(_T('erreur_contenu_suspect'))."'>⚠️</mark> ".$texte;
}
return $texte;
}
/**
* Sécurise un texte HTML
*
* Échappe le code PHP et JS.
* Applique en plus safehtml si un plugin le définit dans inc/safehtml.php
*
* Permet de protéger les textes issus d'une origine douteuse (forums, syndications...)
*
* @filtre
* @link http://www.spip.net/4310
*
* @param string $t
* Texte à sécuriser
* @return string
* Texte sécurisé
**/
function safehtml($t) {
static $safehtml;
if (!$t or !is_string($t)) {
return $t;
}
# attention safehtml nettoie deux ou trois caracteres de plus. A voir
if (strpos($t, '<') === false) {
return str_replace("\x00", '', $t);
}
$t = interdire_scripts($t); // jolifier le php
$t = echappe_js($t);
if (!isset($safehtml)) {
$safehtml = charger_fonction('safehtml', 'inc', true);
}
if ($safehtml) {
$t = $safehtml($t);
}
return interdire_scripts($t); // interdire le php (2 precautions)
}
/**
* Supprime les modèles d'image d'un texte
*
* Fonction en cas de texte extrait d'un serveur distant:
* on ne sait pas (encore) rapatrier les documents joints
* Sert aussi à nettoyer un texte qu'on veut mettre dans un `<a>` etc.
*
* @todo
* gérer les autres modèles ?
*
* @param string $letexte
* Texte à nettoyer
* @param string|null $message
* Message de remplacement pour chaque image enlevée
* @return string
* Texte sans les modèles d'image
**/
function supprime_img($letexte, $message = null) {
if ($message === null) {
$message = '(' . _T('img_indisponible') . ')';
}
return preg_replace(',<(img|doc|emb)([0-9]+)(\|([^>]*))?' . '\s*/?' . '>,i',
$message, $letexte);
}