Vérifier si une chaîne est sérialisée?

Réponses:

191

Je dirais, essayez- unserializele ;-)

Citant le manuel:

Dans le cas où la chaîne transmise n'est pas non sérialisable, FALSE est renvoyé et E_NOTICE est émis.

Donc, vous devez vérifier si la valeur de retour est falseou non (avec ===ou !==, pour être sûr de ne pas avoir de problème avec 0ou nullou quoi que ce soit qui équivaut à false, je dirais) .

Attention: vous voudrez peut-être / devrez peut-être utiliser l' opérateur @ .

Par exemple :

$str = 'hjkl';
$data = @unserialize($str);
if ($data !== false) {
    echo "ok";
} else {
    echo "not ok";
}

Vous obtiendrez:

not ok


EDIT: Oh, et comme @Peter l'a dit (grâce à lui!), Vous pourriez avoir des problèmes si vous essayez de désérialiser la représentation d'un booléen false :-(

Donc, vérifier que votre chaîne sérialisée n'est pas égale à " b:0;" peut également être utile; quelque chose comme ça devrait faire l'affaire, je suppose:

$data = @unserialize($str);
if ($str === 'b:0;' || $data !== false) {
    echo "ok";
} else {
    echo "not ok";
}

tester ce cas particulier avant d'essayer de désérialiser serait une optimisation - mais probablement pas si utile, si vous n'avez pas souvent une fausse valeur sérialisée.

Pascal MARTIN
la source
20
Mais que se passe-t-il si la valeur non sérialisée est un booléen avec une valeur FALSE?
Peter le
1
@Peter: excellente remarque; J'ai édité ma réponse avec une proposition pour traiter ce cas; Merci !
Pascal MARTIN
Merci. :) J'ai supposé que cela allait probablement être la réponse .. Il me semble juste qu'il devrait y avoir un moyen de savoir s'il est sérialisé avant de forcer l'analyseur à essayer de le traiter.
Dang le
1
Cette méthode a-t-elle un impact raisonnable sur les performances avec de plus gros éléments de données?
pie6k
2
IMPORTANT: ne désérialisez jamais les données utilisateur brutes car elles peuvent être utilisées comme vecteur d'attaque. OWASP: PHP_Object_Injection
ArtBIT
56

Je n'ai pas écrit ce code, il vient de WordPress en fait. Je pensais l'inclure pour toute personne intéressée, cela pourrait être exagéré mais cela fonctionne :)

<?php
function is_serialized( $data ) {
    // if it isn't a string, it isn't serialized
    if ( !is_string( $data ) )
        return false;
    $data = trim( $data );
    if ( 'N;' == $data )
        return true;
    if ( !preg_match( '/^([adObis]):/', $data, $badions ) )
        return false;
    switch ( $badions[1] ) {
        case 'a' :
        case 'O' :
        case 's' :
            if ( preg_match( "/^{$badions[1]}:[0-9]+:.*[;}]\$/s", $data ) )
                return true;
            break;
        case 'b' :
        case 'i' :
        case 'd' :
            if ( preg_match( "/^{$badions[1]}:[0-9.E-]+;\$/", $data ) )
                return true;
            break;
    }
    return false;
}
Brandon Wamboldt
la source
1
J'avais essentiellement besoin d'un regex pour faire une détection de base, j'ai fini par utiliser:^([adObis]:|N;)
farinspace
5
La version actuelle de WordPress est un peu plus sophistiquée: codex.wordpress.org/Function_Reference/…
ChrisV
3
+1 pour donner des crédits. Je ne savais pas que WordPress avait cela intégré. Merci pour l'idée - je vais maintenant créer une archive de fonctions utiles à partir de WordPress Core.
Amal Murali
Dernière référence de la fonction URL vers wordpress: developer.wordpress.org/reference/functions/is_serialized
Cédric Françoys
18

Optimiser la réponse de Pascal MARTIN

/**
 * Check if a string is serialized
 * @param string $string
 */
public static function is_serial($string) {
    return (@unserialize($string) !== false);
}
SoN9ne
la source
16

Si la $ string est une falsevaleur sérialisée , c'est-à-dire $string = 'b:0;' que la fonction de SoN9ne retourne false, c'est faux

donc la fonction serait

/**
 * Check if a string is serialized
 *
 * @param string $string
 *
 * @return bool
 */
function is_serialized_string($string)
{
    return ($string == 'b:0;' || @unserialize($string) !== false);
}
Hazem Noor
la source
2
Changer l'ordre de ces tests serait plus efficace.
artfulrobot
L'opérateur @ (at) doit être déconseillé. Utilisez plutôt le bloc try catch.
Francisco Luz
@FranciscoLuz du manuel php.net/manual/en/function.unserialize.php In case the passed string is not unserializeable, FALSE is returned and E_NOTICE is issued. Nous ne pouvons pas attraper l'erreur E_NOTICE car ce n'est pas une exception levée.
Hazem Noor
@HazemNoor Je l'ai testé avec PHP 7 et il se fait prendre. De plus, en PHP 7, il y a catch (\ Throwable $ e) qui attrape tout ce qui ne va pas sous le capot.
Francisco Luz
@FranciscoLuz comment avez-vous attrapé E_Notice en PHP 7?
user427969
13

Malgré l'excellente réponse de Pascal MARTIN, j'étais curieux de savoir si vous pouviez aborder cela d'une autre manière, alors je l'ai fait juste comme un exercice mental

<?php

ini_set( 'display_errors', 1 );
ini_set( 'track_errors', 1 );
error_reporting( E_ALL );

$valueToUnserialize = serialize( false );
//$valueToUnserialize = "a"; # uncomment this for another test

$unserialized = @unserialize( $valueToUnserialize );

if ( FALSE === $unserialized && isset( $php_errormsg ) && strpos( $php_errormsg, 'unserialize' ) !== FALSE )
{
  echo 'Value could not be unserialized<br>';
  echo $valueToUnserialize;
} else {
  echo 'Value was unserialized!<br>';
  var_dump( $unserialized );
}

Et cela fonctionne réellement. La seule mise en garde est qu'il se cassera probablement si vous avez un gestionnaire d'erreurs enregistré à cause du fonctionnement de $ php_errormsg .

Peter Bailey
la source
1
+1: Celui-ci est amusant, je dois l'admettre - je n'y aurais pas pensé! Et je ne trouve pas le moyen de le faire échouer aussi ^^ Beau travail! Et merci pour le commentaire sur ma réponse: sans elle, je n'aurais probablement pas vu cette réponse.
Pascal MARTIN
$ a = 'bla'; $ b = 'b: 0;'; Essayez de désérialiser $ a puis $ b avec ceci, les deux échoueront alors que $ b ne devrait pas.
bardiir
Pas s'il y avait un échec juste avant. Parce que $ php_errormsg contiendra toujours l'erreur de sérialisation d'avant et une fois que vous désérialisez false, il échouera.
bardiir
Ouais, mais seulement si vous ne faites pas de vérification d'erreur entre la désérialisation $aet la désérialisation $b, ce qui n'est pas une conception d'application pratique.
Peter Bailey
11
$data = @unserialize($str);
if($data !== false || $str === 'b:0;')
    echo 'ok';
else
    echo "not ok";

Gère correctement le cas de serialize(false). :)

le chaos
la source
3

intégrer à une fonction

function isSerialized($value)
{
   return preg_match('^([adObis]:|N;)^', $value);
}
RossW
la source
1
Cette expression régulière est dangereuse, elle retourne positif quand a:(ou b:etc) est présent quelque part dans $ value, pas au début. Et ^ici ne signifie pas le début d'une chaîne. C'est totalement trompeur.
Denis Chmel
3

Il existe une solution WordPress: (le détail est ici)

    function is_serialized($data, $strict = true)
    {
        // if it isn't a string, it isn't serialized.
        if (!is_string($data)) {
            return false;
        }
        $data = trim($data);
        if ('N;' == $data) {
            return true;
        }
        if (strlen($data) < 4) {
            return false;
        }
        if (':' !== $data[1]) {
            return false;
        }
        if ($strict) {
            $lastc = substr($data, -1);
            if (';' !== $lastc && '}' !== $lastc) {
                return false;
            }
        } else {
            $semicolon = strpos($data, ';');
            $brace = strpos($data, '}');
            // Either ; or } must exist.
            if (false === $semicolon && false === $brace)
                return false;
            // But neither must be in the first X characters.
            if (false !== $semicolon && $semicolon < 3)
                return false;
            if (false !== $brace && $brace < 4)
                return false;
        }
        $token = $data[0];
        switch ($token) {
            case 's' :
                if ($strict) {
                    if ('"' !== substr($data, -2, 1)) {
                        return false;
                    }
                } elseif (false === strpos($data, '"')) {
                    return false;
                }
            // or else fall through
            case 'a' :
            case 'O' :
                return (bool)preg_match("/^{$token}:[0-9]+:/s", $data);
            case 'b' :
            case 'i' :
            case 'd' :
                $end = $strict ? '$' : '';
                return (bool)preg_match("/^{$token}:[0-9.E-]+;$end/", $data);
        }
        return false;
    }
ingénieux
la source
2
/**
 * some people will look down on this little puppy
 */
function isSerialized($s){
if(
    stristr($s, '{' ) != false &&
    stristr($s, '}' ) != false &&
    stristr($s, ';' ) != false &&
    stristr($s, ':' ) != false
    ){
    return true;
}else{
    return false;
}

}
Björn3
la source
5
eh bien, cela donnerait vrai pour de nombreuses chaînes JSON, n'est-ce pas? Il n'est donc pas fiable de déterminer si la chaîne peut être dés / sérialisée.
Gordon le
C'est peut-être vrai, mais si l'alternative est sérialisée, ou simplement du texte brut, comme c'était le cas pour moi, cela fonctionne comme un charme.
Björn3
1
@ Björn3 "Eh bien, cela fonctionne pour moi dans ce cas précis" est une très mauvaise mentalité à avoir lors du codage. Il y a beaucoup de développeurs qui sont paresseux ou pas avant-gardistes comme celui-ci et cela fait un cauchemar plus tard sur toute la ligne lorsque d'autres développeurs doivent travailler avec leur code ou essayer de changer quelque chose et soudainement plus rien ne fonctionne correctement.
BadHorsie
Créer un code complètement solide (si cela était même possible) n'est pas toujours l'objectif ou la meilleure pratique. Pas quand cela arrive à un moment donné. Cela n'est vrai que du point de vue des programmeurs. Dans la vraie vie, il y a beaucoup de circonstances où rapide et sale est le moyen préféré.
Björn3
1

Cela fonctionne bien pour moi

<?php

function is_serialized($data){
    return (is_string($data) && preg_match("#^((N;)|((a|O|s):[0-9]+:.*[;}])|((b|i|d):[0-9.E-]+;))$#um", $data));
    }

?>
Daniel Lichtenberg
la source
Veuillez garder à l'esprit que cela vérifie si la chaîne donnée est une chaîne qui ressemble à une sérialisation - cela ne vérifiera pas réellement la validité de cette chaîne.
eithed
-2

Je préfère le faire de cette façon:

 if (is_array(unserialize($serialized_string))):
degres
la source
Pourquoi la variable sérialisée devrait-elle être un tableau? Cela peut vraiment être de n'importe quel type.
Valerio Bozz le