105 votes

Comment réparer une chaîne sérialisée qui a été corrompue par une longueur incorrecte du compte d'octets ?

J'utilise Hotaru CMS avec le plugin Image Upload, j'obtiens cette erreur si j'essaie d'attacher une image à un article, sinon il n'y a pas d'erreur :

unserialize() [function.unserialize] : Erreur à l'offset

Le code incriminé (l'erreur pointe sur la ligne avec **) :

/**
     * Retrieve submission step data
     *
     * @param $key - empty when setting
     * @return bool
     */
    public function loadSubmitData($h, $key = '')
    {
        // delete everything in this table older than 30 minutes:
        $this->deleteTempData($h->db);

        if (!$key) { return false; }

        $cleanKey = preg_replace('/[^a-z0-9]+/','',$key);
        if (strcmp($key,$cleanKey) != 0) {
            return false;
        } else {
            $sql = "SELECT tempdata_value FROM " . TABLE_TEMPDATA . " WHERE tempdata_key = %s ORDER BY tempdata_updatedts DESC LIMIT 1";
            $submitted_data = $h->db->get_var($h->db->prepare($sql, $key));
            **if ($submitted_data) { return unserialize($submitted_data); } else { return false; }** 
        }
    }

Les données de la table, remarquez que le bit de fin a l'information de l'image, je ne suis pas un expert en PHP donc je me demandais ce que vous pensez ?

tempdata_value :

a:10:{s:16:"submit_editorial";b:0;s:15:"submit_orig_url";s:13:"www.bbc.co.uk";s:12:"submit_title";s:14:"No title found";s:14:"submit_content";s:12:"dnfsdkfjdfdf";s:15:"submit_category";i:2;s:11:"submit_tags";s:3:"bbc";s:9:"submit_id";b:0;s:16:"submit_subscribe";i:0;s:15:"submit_comments";s:4:"open";s:5:"image";s:19:"C:fakepath100.jpg";}

Edit : Je pense que j'ai trouvé le bit de sérialisation...

/**
     * Save submission step data
     *
     * @return bool
     */
    public function saveSubmitData($h)
    {
        // delete everything in this table older than 30 minutes:
        $this->deleteTempData($h->db);

        $sid = preg_replace('/[^a-z0-9]+/i', '', session_id());
        $key = md5(microtime() . $sid . rand());
        $sql = "INSERT INTO " . TABLE_TEMPDATA . " (tempdata_key, tempdata_value, tempdata_updateby) VALUES (%s,%s, %d)";
        $h->db->query($h->db->prepare($sql, $key, serialize($h->vars['submitted_data']), $h->currentUser->id));
        return $key;
    }

3 votes

Pour moi, la solution rapide a été d'utiliser base64_encode / decode avant de sérialiser / désérialiser. davidwalsh.name/php-serialize-unserialize-issues

1 votes

Je ne sais pas pourquoi mais le mien s'est résolu avec l'ajout de @, @unserialize($product->des_txtmopscol);

2 votes

@BhavinRana ajoutant @ n'est pas une résolution d'erreur, c'est une suppression d'erreur - rien n'est réellement "réparé" avec cette technique.

224voto

Baba Points 49157

unserialize() [function.unserialize]: Error at offset était due à invalid serialization data en raison d'une longueur non valide

Correction rapide

Ce que vous pouvez faire, c'est recalculating the length des éléments du tableau sérialisé

Vos données sérialisées actuelles

$data = 'a:10:{s:16:"submit_editorial";b:0;s:15:"submit_orig_url";s:13:"www.bbc.co.uk";s:12:"submit_title";s:14:"No title found";s:14:"submit_content";s:12:"dnfsdkfjdfdf";s:15:"submit_category";i:2;s:11:"submit_tags";s:3:"bbc";s:9:"submit_id";b:0;s:16:"submit_subscribe";i:0;s:15:"submit_comments";s:4:"open";s:5:"image";s:19:"C:fakepath100.jpg";}';

Exemple sans recalcul

var_dump(unserialize($data));

Sortie

Notice: unserialize() [function.unserialize]: Error at offset 337 of 338 bytes

Recalculer

$data = preg_replace('!s:(\d+):"(.*?)";!e', "'s:'.strlen('$2').':\"$2\";'", $data);
var_dump(unserialize($data));

Sortie

array
  'submit_editorial' => boolean false
  'submit_orig_url' => string 'www.bbc.co.uk' (length=13)
  'submit_title' => string 'No title found' (length=14)
  'submit_content' => string 'dnfsdkfjdfdf' (length=12)
  'submit_category' => int 2
  'submit_tags' => string 'bbc' (length=3)
  'submit_id' => boolean false
  'submit_subscribe' => int 0
  'submit_comments' => string 'open' (length=4)
  'image' => string 'C:fakepath100.jpg' (length=17)

Recommandation .. I

Au lieu d'utiliser ce genre de solution rapide ... je vous conseille de mettre à jour la question avec

  • Comment vous sérialisez vos données

  • Comment vous l'économisez

\================================ EDIT 1 ===============================

L'erreur

L'erreur a été générée par l'utilisation de guillemets doubles. " au lieu de guillemets simples ' c'est pourquoi C:\fakepath\100.png a été converti en C:fakepath100.jpg

Pour corriger l'erreur

Vous devez changer $h->vars['submitted_data'] De (Notez le singe tout à fait ' )

Remplacer

 $h->vars['submitted_data']['image'] = "C:\fakepath\100.png" ;

Avec

 $h->vars['submitted_data']['image'] = 'C:\fakepath\100.png' ;

Filtre supplémentaire

Vous pouvez aussi ajouter ce filtre simple avant d'appeler serialize

function satitize(&$value, $key)
{
    $value = addslashes($value);
}

array_walk($h->vars['submitted_data'], "satitize");

Si vous avez des caractères UTF, vous pouvez également exécuter

 $h->vars['submitted_data'] = array_map("utf8_encode",$h->vars['submitted_data']);

Comment détecter le problème dans les futures données sérialisées ?

  findSerializeError ( $data1 ) ;

Sortie

Diffrence 9 != 7
    -> ORD number 57 != 55
    -> Line Number = 315
    -> Section Data1  = pen";s:5:"image";s:19:"C:fakepath100.jpg
    -> Section Data2  = pen";s:5:"image";s:17:"C:fakepath100.jpg
                                            ^------- The Error (Element Length)

findSerializeError Fonction

function findSerializeError($data1) {
    echo "<pre>";
    $data2 = preg_replace ( '!s:(\d+):"(.*?)";!e', "'s:'.strlen('$2').':\"$2\";'",$data1 );
    $max = (strlen ( $data1 ) > strlen ( $data2 )) ? strlen ( $data1 ) : strlen ( $data2 );

    echo $data1 . PHP_EOL;
    echo $data2 . PHP_EOL;

    for($i = 0; $i < $max; $i ++) {

        if (@$data1 {$i} !== @$data2 {$i}) {

            echo "Diffrence ", @$data1 {$i}, " != ", @$data2 {$i}, PHP_EOL;
            echo "\t-> ORD number ", ord ( @$data1 {$i} ), " != ", ord ( @$data2 {$i} ), PHP_EOL;
            echo "\t-> Line Number = $i" . PHP_EOL;

            $start = ($i - 20);
            $start = ($start < 0) ? 0 : $start;
            $length = 40;

            $point = $max - $i;
            if ($point < 20) {
                $rlength = 1;
                $rpoint = - $point;
            } else {
                $rpoint = $length - 20;
                $rlength = 1;
            }

            echo "\t-> Section Data1  = ", substr_replace ( substr ( $data1, $start, $length ), "<b style=\"color:green\">{$data1 {$i}}</b>", $rpoint, $rlength ), PHP_EOL;
            echo "\t-> Section Data2  = ", substr_replace ( substr ( $data2, $start, $length ), "<b style=\"color:red\">{$data2 {$i}}</b>", $rpoint, $rlength ), PHP_EOL;
        }

    }

}

Une meilleure façon d'enregistrer dans la base de données

$toDatabse = base64_encode(serialize($data));  // Save to database
$fromDatabase = unserialize(base64_decode($data)); //Getting Save Format

1 votes

Baba, j'ai utilisé votre incroyable findSerializeError et j'ai trouvé un grand nombre d'erreurs. Jetez un coup d'œil à mon sujet

1 votes

Utiliser base64 sur l'article avant de l'ajouter à la base de données ... cela préserverait le caractère nul.

0 votes

Merci ! J'ai utilisé base64_encode \base64_decode et ça a marché. Mais maintenant j'ai la même erreur avec des données différentes et ça ne fonctionne pas même avec base64_encode. \base64_decode en place. A quoi sert votre fonction ?

94voto

r00tAcc3ss Points 91

Je n'ai pas assez de réputation pour commenter, j'espère donc que les personnes qui utilisent la réponse "correcte" ci-dessus verront ce message :

Depuis la version 5.5 de php, le modificateur /e dans preg_replace() a été complètement déprécié et la fonction preg_match ci-dessus donnera lieu à une erreur. La documentation de php recommande d'utiliser preg_match_callback à sa place.

Veuillez trouver la solution suivante comme alternative à la solution preg_match proposée ci-dessus.

$fixed_data = preg_replace_callback ( '!s:(\d+):"(.*?)";!', function($match) {      
    return ($match[1] == strlen($match[2])) ? $match[0] : 's:' . strlen($match[2]) . ':"' . $match[2] . '";';
},$bad_data );

3 votes

Cela semble être la seule réponse sur la page qui utilise réellement le premier groupe de capture à bon escient. Bien qu'il soit raisonnable de programmer de ne faire des remplacements que lorsque le nombre d'octets est en fait erronée, cette solution ne met pas en cache le strlen() et fait donc des appels de fonction redondants. Personnellement, je trouve que l'ajout d'une condition en ligne est trop verbeux, mais cet extrait fait de bonnes choses pour de bonnes raisons.

6 votes

Cela a fonctionné pour moi avec la regex suivante '!s:(\d+):"(.*?)";!s' (avec un "s" final pour prendre également de nouvelles lignes). Merci au commentaire d'adilbo ci-dessous.

0 votes

Pour moi, cela a échoué sur certains objets sérialisés complexes, alors j'ai utilisé rexeg '#s:(\d+):"(.*?)";(?=\\}*(?:[aOsidbN][:;]|\\z))#s'

16voto

Ge Rong Points 89

Il y a une autre raison unserialize() a échoué parce que vous avez mal placé les données sérialisées dans la base de données voir Explication officielle ici. Depuis serialize() renvoie des données binaires et les variables php ne se soucient pas des méthodes d'encodage, de sorte que les mettre dans TEXT, VARCHAR() provoquera cette erreur.

Solution : stockez les données sérialisées en BLOB dans votre table.

0 votes

Cela a résolu mon problème dans Laravel 5. J'ai changé la définition de la colonne de string() à binary().

0 votes

La question de l'OP ne semble pas avoir un problème de type de colonne mysql. Elle est apparemment corrompue par un calcul d'octets incorrect sur le fichier image valeur. Votre réponse ne se rapporte pas à la question spécifique du PO. Vous pouvez déplacer votre conseil vers : stackoverflow.com/q/5544749/2943403

6voto

Will Points 21

Cette erreur est due au fait que votre jeu de caractères est incorrect.

Définir le jeu de caractères après la balise open :

header('Content-Type: text/html; charset=utf-8');

Et définissez le charset utf8 dans votre base de données :

mysql_query("SET NAMES 'utf8'");

0 votes

Je ne vois aucune indication dans la question postée par l'OP qui suggère que la corruption est due au charset. N'hésitez pas à défendre votre point de vue, mais d'après ce que je sais, quelqu'un a mis à jour manuellement le jeu de caractères de l'OP. image et n'a pas mis à jour le nombre d'octets. Sauf information contraire, je dois supposer que cette réponse est incorrecte pour la question de l'OP.

1voto

Vous devrez modifier le type de collation en utf8_unicode_ci et le problème sera réglé.

0 votes

Quel caractère spécifique de l'échantillon de données de l'OP pensez-vous être modifié par le changement de collation en utf8_unicode_ci ? J'ai des doutes sur ce point.

0 votes

Cela a également fonctionné pour moi (à part la réponse de r00tAcc3ss). Quelqu'un peut-il m'expliquer pourquoi ? Pour rappel, je prends des données à partir d'un appel API vers une application ResourceSpace, je les stocke dans un tableau, je les sérialise et je les enregistre. Les données sérialisées avaient des problèmes pour être sauvegardées, j'ai donc dû les encoder manuellement en UTF-8, j'ai joué avec la collation et le jeu de caractères dans la base de données, et finalement je me suis retrouvé avec la collation utf8_general_ci, quand je l'ai changé en utf8_unicode_ci, ça a marché.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X