Sprawdź, czy ciąg jest serializowany?

Odpowiedzi:

191

Powiedziałbym, spróbuj unserialize;-)

Cytując instrukcję:

W przypadku, gdy przekazany ciąg nie nadaje się do serializacji, zwracany jest FALSE i wysyłany jest E_NOTICE.

Musisz więc sprawdzić, czy wartość zwracana jest, falseczy nie (z ===lub !==, aby upewnić się, że nie masz żadnego problemu z 0lub nulllub czymkolwiek, co jest równe false, powiedziałbym) .

Po prostu uważaj na uwagę: możesz chcieć / potrzebować użyć operatora @ .

Na przykład :

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

Dostaniesz:

not ok


EDYCJA: Aha, i tak jak powiedział @Peter (dzięki niemu!), Możesz napotkać kłopoty, jeśli próbujesz odserializować reprezentację logicznego fałszu :-(

Zatem sprawdzenie, czy serializowany ciąg nie jest równy „ b:0;”, może być również pomocne; coś takiego powinno załatwić sprawę, jak sądzę:

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

testowanie tego specjalnego przypadku przed próbą odserializacji byłoby optymalizacją - ale prawdopodobnie nie jest to użyteczne, jeśli nie masz często fałszywych wartości serializowanych.

Pascal MARTIN
źródło
20
Ale co, jeśli nieserializowana wartość jest wartością logiczną o wartości FALSE?
Peter
1
@Peter: doskonała uwaga; Zredagowałem odpowiedź z propozycją zajęcia się tym przypadkiem; dzięki !
Pascal MARTIN
Dzięki. :) Zakładałem, że to prawdopodobnie będzie odpowiedź .. Wydaje mi się, że powinien istnieć sposób, aby dowiedzieć się, czy jest on serializowany, zanim faktycznie zmusi parser do próby przetworzenia.
Dang
1
Czy ta metoda ma jakikolwiek rozsądny wpływ na wydajność przy większych ilościach danych?
pie6k
2
WAŻNE: Nigdy, przenigdy nie usuwaj serializacji surowych danych użytkownika, ponieważ można ich użyć jako wektora ataku. OWASP: PHP_Object_Injection
ArtBIT
56

Nie napisałem tego kodu, właściwie pochodzi z WordPressa. Pomyślałem, że dołączę to dla wszystkich zainteresowanych, może to przesada, ale działa :)

<?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
źródło
1
Zasadniczo potrzebowałem wyrażenia regularnego, aby wykonać podstawowe wykrycie, ostatecznie użyłem:^([adObis]:|N;)
farinspace
5
Obecna wersja WordPressa jest nieco bardziej wyrafinowana: codex.wordpress.org/Function_Reference/ ...
ChrisV
3
+1 za przyznawanie kredytów. Nie wiedziałem, że WordPress ma to wbudowane. Dzięki za pomysł - teraz zrobię archiwum przydatnych funkcji z WordPress Core.
Amal Murali
Najnowszy adres URL do odniesienia do funkcji wordpress: developer.wordpress.org/reference/functions/is_serialized
Cédric Françoys
18

Optymalizacja odpowiedzi Pascala MARTINA

/**
 * Check if a string is serialized
 * @param string $string
 */
public static function is_serial($string) {
    return (@unserialize($string) !== false);
}
SoN9ne
źródło
16

Jeśli ciąg $ jest zserializowaną falsewartością, tj. $string = 'b:0;' Funkcja SoN9ne zwraca false, jest błędna

więc funkcja byłaby

/**
 * 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
źródło
2
Zmiana kolejności tych testów byłaby bardziej wydajna.
artfulrobot
Należy odradzać stosowanie operatora @ (at). Zamiast tego użyj bloku try catch.
Francisco Luz
@FranciscoLuz z podręcznika php.net/manual/en/function.unserialize.php In case the passed string is not unserializeable, FALSE is returned and E_NOTICE is issued. Nie możemy złapać błędu E_NOTICE, ponieważ nie jest to wyrzucony wyjątek.
Hazem Noor
@HazemNoor Przetestowałem to z PHP 7 i daje się złapać. Ponadto w PHP 7 jest haczyk (\ Throwable $ e), który wyłapuje wszystko, co się nie udaje pod maską.
Francisco Luz
@FranciscoLuz Jak złapałeś E_Notice w PHP 7?
user427969
13

Pomimo doskonałej odpowiedzi Pascala MARTINA, byłem ciekawy, czy możesz podejść do tego inaczej, więc zrobiłem to jako ćwiczenie umysłowe

<?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 );
}

I faktycznie działa. Jedynym zastrzeżeniem jest to, że prawdopodobnie zepsuje się, jeśli masz zarejestrowaną procedurę obsługi błędów z powodu działania $ php_errormsg .

Peter Bailey
źródło
1
+1: Ten jest fajny, muszę przyznać - nie pomyślałbym o tym! I nie znajduję też sposobu, żeby to się nie udało ^^ Dobra robota! Dziękuję za komentarz do mojej odpowiedzi: bez niej pewnie bym nie widział tej odpowiedzi.
Pascal MARTIN
$ a = 'bla'; $ b = 'b: 0;'; Spróbuj odserializować $ a, a następnie $ b za pomocą tego, oba zawiodą, a $ b nie powinno.
bardiir
Nie, jeśli zaraz wcześniej była awaria. Ponieważ $ php_errormsg nadal będzie zawierał błąd serializacji sprzed, a po deserializacji false, zakończy się niepowodzeniem.
bardiir
Tak, ale tylko wtedy, gdy nie sprawdzasz błędów między deserializacją $aa deserializacją $b, co nie jest praktycznym projektem aplikacji.
Peter Bailey,
11
$data = @unserialize($str);
if($data !== false || $str === 'b:0;')
    echo 'ok';
else
    echo "not ok";

Prawidłowo obsługuje przypadek serialize(false). :)

chaos
źródło
3

wbudować w funkcję

function isSerialized($value)
{
   return preg_match('^([adObis]:|N;)^', $value);
}
RossW
źródło
1
To wyrażenie regularne jest niebezpieczne, zwraca wartość dodatnią, gdy a:(lub b:itp.) Występuje gdzieś wewnątrz $ value, a nie na początku. I ^tutaj nie oznacza początku łańcucha. To całkowicie mylące.
Denis Chmel
3

Jest rozwiązanie WordPress: (szczegóły tutaj)

    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;
    }
pomysłowy
źródło
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
źródło
5
cóż, dałoby to prawdę również dla wielu ciągów JSON, prawda? Dlatego nie jest wiarygodne określenie, czy ciąg można cofnąć / serializować.
Gordon,
Może to prawda, ale jeśli alternatywa jest zserializowana lub po prostu zwykły tekst, jak dla mnie, działa to jak urok.
Björn3,
1
@ Björn3 „Cóż, w tym konkretnym przypadku to działa” to bardzo zła mentalność podczas kodowania. Jest wielu programistów, którzy są leniwi lub nie myślą w ten sposób w przyszłość, a później staje się koszmarem, gdy inni programiści muszą pracować z ich kodem lub próbować coś zmienić i nagle nic już nie działa poprawnie.
BadHorsie
Tworzenie całkowicie solidnego kodu (jeśli to w ogóle było możliwe) nie zawsze jest celem lub najlepszą praktyką. Nie wtedy, gdy dzieje się to kosztem czasu. Jest to prawdą tylko z punktu widzenia programistów. W prawdziwym życiu jest wiele sytuacji, w których preferowany jest szybki i brudny sposób.
Björn3
1

U mnie to działa dobrze

<?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
źródło
Pamiętaj, że to sprawdza, czy dany ciąg jest ciągiem przypominającym serializację - w rzeczywistości nie sprawdza poprawności tego ciągu.
eithed
-2

Wolę to robić w ten sposób:

 if (is_array(unserialize($serialized_string))):
degers
źródło
Dlaczego zmienna serializowana powinna być tablicą? To naprawdę może być dowolnego typu.
Valerio Bozz