Verifique se uma sequência é serializada?

Respostas:

191

Eu diria, tente unserialize;-)

Citando o manual:

Caso a sequência passada não seja desserializável, FALSE será retornado e E_NOTICE será emitido.

Portanto, você deve verificar se o valor de retorno é falseou não (com ===ou !==, para ter certeza de não ter nenhum problema com 0ou nullou qualquer coisa que seja igual a false, eu diria) .

Apenas tome cuidado com o aviso: você pode querer / precisar usar o operador @ .

Por exemplo :

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

Você receberá:

not ok


EDIT: Ah, e como @ Peter disse (graças a ele!), Você pode ter problemas se estiver tentando desserializar a representação de um falso booleano :-(

Portanto, verificar se sua string serializada não é igual a " b:0;" também pode ser útil; algo assim deve funcionar, suponho:

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

testar esse caso especial antes de tentar desserializar seria uma otimização - mas provavelmente não é útil se você não tiver um valor serializado falso com frequência.

Pascal MARTIN
fonte
20
Mas e se o valor não serializado for um booleano com um valor FALSE?
Peter Peter
1
@ Peter: excelente observação; Eu editei minha resposta com uma proposição para lidar com esse caso; obrigado !
Pascal MARTIN
Obrigado. :) Eu assumi que essa provavelmente seria a resposta .. Parece-me que deveria haver uma maneira de descobrir se ele foi serializado antes de forçar o analisador a tentar processá-lo.
Dang
1
Esse método tem algum impacto razoável no desempenho com dados maiores?
pie6k
2
IMPORTANTE: nunca desserialize dados brutos do usuário, pois eles podem ser usados ​​como um vetor de ataque. OWASP: PHP_Object_Injection
ArtBIT 01/09/17
56

Eu não escrevi esse código, é do WordPress, na verdade. Pensei em incluí-lo para qualquer pessoa interessada, pode ser um exagero, mas funciona :)

<?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
fonte
1
Eu basicamente precisava de um regex para fazer uma detecção básica, e acabei usando:^([adObis]:|N;)
farinspace 10/10
5
A versão atual do WordPress é um pouco mais sofisticada: codex.wordpress.org/Function_Reference/…
ChrisV
3
+1 para dar créditos. Eu não sabia que o WordPress tinha esse recurso embutido. Obrigado pela ideia - agora vou criar um arquivo de funções úteis a partir do WordPress Core.
Amal Murali
Referência mais recente da URL para a função wordpress: developer.wordpress.org/reference/functions/is_serialized
Cédric Françoys
18

Otimizando a resposta de Pascal MARTIN

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

Se a string $ é um falsevalor serializado , ou seja , a função do $string = 'b:0;' SoN9ne retorna false, está errado

então a função seria

/**
 * 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
fonte
2
Trocar a ordem desses testes seria mais eficiente.
Artfulrobot
O @ (no operador) deve ser desencorajado. Use try catch block em vez disso.
Francisco Luz
@FranciscoLuz do manual php.net/manual/en/function.unserialize.php In case the passed string is not unserializeable, FALSE is returned and E_NOTICE is issued. Não podemos pegar o erro E_NOTICE, pois não é uma exceção lançada.
Hazem Noor
@HazemNoor Eu testei com o PHP 7 e ele foi pego. Além disso, no PHP 7, existe o catch (\ Throwable $ e) que captura tudo o que dá errado sob o capô.
Francisco Luz
@FranciscoLuz como você pegou o E_Notice no PHP 7?
user427969
13

Apesar da excelente resposta de Pascal MARTIN, fiquei curioso para saber se você poderia abordar isso de outra maneira, então fiz isso apenas como um exercício 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 );
}

E realmente funciona. A única ressalva é que provavelmente será interrompida se você tiver um manipulador de erros registrado por causa do funcionamento do $ php_errormsg .

Peter Bailey
fonte
1
+1: Este é divertido, eu tenho que admitir - não teria pensado nisso! E também não encontro um meio de fazê-lo falhar ^^ Bom trabalho! E obrigado pelo comentário sobre a minha resposta: sem ela, eu provavelmente não teria visto essa resposta.
Pascal MARTIN
$ a = 'bla'; $ b = 'b: 0;'; Tente desserializar $ a então $ b com isso, ambos falharão enquanto $ b não deve.
bardiir
Não se houve uma falha logo antes. Como o $ php_errormsg ainda conterá o erro de serialização de antes e depois que você desserializar false, ele falhará.
bardiir
Sim, mas apenas se você não verificar o erro entre desserializar $ae desserializar $b, o que não é um design de aplicativo prático.
Peter Bailey
11
$data = @unserialize($str);
if($data !== false || $str === 'b:0;')
    echo 'ok';
else
    echo "not ok";

Lida corretamente com o caso de serialize(false). :)

caos
fonte
3

construir em uma função

function isSerialized($value)
{
   return preg_match('^([adObis]:|N;)^', $value);
}
RossW
fonte
1
Esse regex é perigoso, está retornando positivo quando a:(ou b:etc) está presente em algum lugar dentro do valor $, não no começo. E ^aqui não significa começo de uma string. É totalmente enganador.
Denis Chmel
3

Existe a solução WordPress: (o detalhe está aqui)

    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;
    }
engenhoso
fonte
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
fonte
5
bem, isso daria certo para muitas strings JSON também, não? Portanto, não é confiável determinar se a sequência pode ser des / serializada.
Gordon
Pode ser verdade, mas se a alternativa for serializada, ou apenas texto simples, como era para mim, funcionará como um encanto.
Björn3
1
@ Björn3 "Bem, funciona para mim neste caso específico" é uma mentalidade muito ruim de se ter ao codificar. Existem muitos desenvolvedores que são preguiçosos ou não pensam nisso, e isso cria um pesadelo mais tarde, quando outros desenvolvedores precisam trabalhar com seu código ou tentar mudar alguma coisa e, de repente, nada funciona mais.
BadHorsie 18/03/2014
Criar código completamente sólido (se isso fosse possível) nem sempre é o objetivo ou a melhor prática. Não quando se trata de uma despesa de tempo. Isso é verdade apenas da perspectiva dos programadores. Na vida real, existem muitas circunstâncias em que a maneira mais rápida e suja é a preferida.
Björn3
1

Este trabalho é bom para mim

<?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
fonte
Lembre-se de verificar se a sequência especificada é de aparência serializada - na verdade, ela não verifica a validade dessa sequência.
eithed 8/08/16
-2

Eu prefiro fazer dessa maneira:

 if (is_array(unserialize($serialized_string))):
degeneradores
fonte
Por que a variável serializada deve ser uma matriz? Pode realmente ser de qualquer tipo.
Valerio Bozz