web-dev-qa-db-fra.com

Python: implémentations de gonflage et de dégonflage

J'interface avec un serveur qui nécessite que les données qui lui sont envoyées soient compressées avec l'algorithme Deflate (encodage Huffman + LZ77) et envoie également les données dont j'ai besoin Gonflez .

Je sais que Python inclut Zlib, et que les bibliothèques C de Zlib prennent en charge les appels à Inflate et Dégonfler , mais ceux-ci ne sont apparemment pas fournis par le module Python Zlib. Il fournit Compression et Décompressez , mais quand je fais un appel tel que le suivant:

result_data = zlib.decompress( base64_decoded_compressed_string )

Je reçois l'erreur suivante:

Error -3 while decompressing data: incorrect header check

Gzip ne fait pas mieux; lors d'un appel tel que:

result_data = gzip.GzipFile( fileobj = StringIO.StringIO( base64_decoded_compressed_string ) ).read()

Je reçois l'erreur:

IOError: Not a gzipped file

ce qui est logique car les données sont un fichier dégonflé pas un vrai Gzippé fichier.

Maintenant, je sais qu'il existe une implémentation Deflate disponible (Pyflate), mais je ne connais pas de Inflate implémentation.

Il semble qu'il y ait quelques options:

  1. Trouver une implémentation existante (idéale) de Gonfler et Dégonfler en Python
  2. Écrire mon propre Python à la bibliothèque c zlib qui comprend Gonfler et Dégonfler
  3. Appelez quelque chose d'autre qui peut être exécuté à partir de la ligne de commande (comme un script Ruby, puisque Gonflez / Deflate les appels dans zlib sont entièrement enveloppés dans Ruby)
  4. ?

Je cherche une solution, mais sans solution, je serai reconnaissant pour les idées, les opinions constructives et les idées.

Informations supplémentaires: Le résultat du dégonflage (et de l'encodage) d'une chaîne devrait, pour les besoins dont j'ai besoin, donner le même résultat que l'extrait de code C # suivant, où le paramètre d'entrée est un tableau d'octets UTF correspondant aux données à compresser:

public static string DeflateAndEncodeBase64(byte[] data)
{
    if (null == data || data.Length < 1) return null;
    string compressedBase64 = "";

    //write into a new memory stream wrapped by a deflate stream
    using (MemoryStream ms = new MemoryStream())
    {
        using (DeflateStream deflateStream = new DeflateStream(ms, CompressionMode.Compress, true))
        {
            //write byte buffer into memorystream
            deflateStream.Write(data, 0, data.Length);
            deflateStream.Close();

            //rewind memory stream and write to base 64 string
            byte[] compressedBytes = new byte[ms.Length];
            ms.Seek(0, SeekOrigin.Begin);
            ms.Read(compressedBytes, 0, (int)ms.Length);
            compressedBase64 = Convert.ToBase64String(compressedBytes);
        }
    }
    return compressedBase64;
}

L'exécution de ce code .NET pour la chaîne "dégonfler et encoder moi" donne le résultat

7b0HYBxJliUmL23Ke39K9UrX4HShCIBgEyTYkEAQ7MGIzeaS7B1pRyMpqyqBymVWZV1mFkDM7Z28995777333nvvvfe6O51OJ/ff/z9cZmQBbPbOStrJniGAqsgfP358Hz8iZvl5mbV5mi1nab6cVrM8XeT/Dw==

Lorsque "me dégonfler et m'encoder" est exécuté via le Python Zlib.compress () puis encodé en base64, le résultat est "eJxLSU3LSSxJVUjMS1FIzUvOT0lVyE0FAFXHB6k =".

Il est clair que zlib.compress () n'est pas une implémentation du même algorithme que l'algorithme Deflate standard.

Plus d'informations:

Les 2 premiers octets des données de dégonflage .NET ("7b0HY ..."), après le décodage b64 sont 0xEDBD, ce qui ne correspond pas aux données Gzip (0x1f8b), BZip2 (0x425A) ou Zlib (0x789C).

Les 2 premiers octets des données compressées Python ("eJxLS ..."), après le décodage b64 sont 0x789C. Il s'agit d'un en-tête Zlib.

RÉSOLU

Pour gérer le dégonflage et le gonflement bruts, sans en-tête ni somme de contrôle, les choses suivantes devaient se produire:

Sur dégonfler/compresser: supprimer les deux premiers octets (en-tête) et les quatre derniers octets (somme de contrôle).

Sur gonfler/décompresser: il existe un deuxième argument pour la taille de la fenêtre Si cette valeur est négative, elle supprime les en-têtes. voici mes méthodes actuellement, y compris l'encodage/décodage base64 - et fonctionne correctement:

import zlib
import base64

def decode_base64_and_inflate( b64string ):
    decoded_data = base64.b64decode( b64string )
    return zlib.decompress( decoded_data , -15)

def deflate_and_base64_encode( string_val ):
    zlibbed_str = zlib.compress( string_val )
    compressed_string = zlibbed_str[2:-4]
    return base64.b64encode( compressed_string )
51
Demi

Ceci est un complément à la réponse de MizardX, donnant quelques explications et un contexte.

Voir http://www.chiramattel.com/george/blog/2007/09/09/deflatestream-block-length-does-not-match.html

Selon RFC 195 , un flux zlib construit de la manière par défaut est composé de:

  • un en-tête de 2 octets (par exemple 0x78 0x9C)
  • un flux de dégonflage - voir RFC 1951
  • une somme de contrôle Adler-32 des données non compressées (4 octets)

Le C # DeflateStream fonctionne (vous l'avez deviné) sur un flux de dégonflage. Le code de MizardX indique au module zlib que les données sont un flux de dégonflage brut.

Observations: (1) On espère que la méthode de "déflation" C # produisant une chaîne plus longue ne se produit qu'avec une entrée courte (2) Vous utilisez le flux de dégonflage brut sans la somme de contrôle Adler-32? Peu risqué, à moins qu'il ne soit remplacé par quelque chose de mieux.

Mises à jour

message d'erreur Block length does not match with its complement

Si vous essayez de gonfler des données compressées avec C # DeflateStream et que vous obtenez ce message, il est fort possible que vous lui donniez un flux zlib, pas un flux dégonflé.

Voir Comment utilisez-vous un DeflateStream sur une partie d'un fichier?

Copiez/collez également le message d'erreur dans une recherche Google et vous obtiendrez de nombreux résultats (y compris celui au début de cette réponse) disant à peu près la même chose.

Le Java Deflater ... utilisé par "le site Web "... C # DeflateStream" est assez simple et a été testé par rapport à l'implémentation Java ". Lequel des éléments suivants Java Deflater constructors est le site Web utilisant ?

public Deflater(int level, boolean nowrap)

Crée un nouveau compresseur en utilisant le niveau de compression spécifié. Si "nowrap" est vrai, les champs d'en-tête et de somme de contrôle ZLIB ne seront pas utilisés afin de prendre en charge le format de compression utilisé à la fois dans GZIP et PKZIP.

public Deflater(int level)

Crée un nouveau compresseur en utilisant le niveau de compression spécifié. Les données compressées seront générées au format ZLIB.

public Deflater()

Crée un nouveau compresseur avec le niveau de compression par défaut. Les données compressées seront générées au format ZLIB.

Un dégonfleur d'une ligne après avoir jeté l'en-tête zlib de 2 octets et la somme de contrôle de 4 octets:

uncompressed_string.encode('zlib')[2:-4] # does not work in Python 3.x

ou

zlib.compress(uncompressed_string)[2:-4]
21
John Machin

Vous pouvez toujours utiliser le module zlib pour gonfler/dégonfler les données. Le module gzip l'utilise en interne, mais ajoute un en-tête de fichier pour en faire un fichier gzip. En regardant le fichier gzip.py , quelque chose comme ça pourrait fonctionner:

import zlib

def deflate(data, compresslevel=9):
    compress = zlib.compressobj(
            compresslevel,        # level: 0-9
            zlib.DEFLATED,        # method: must be DEFLATED
            -zlib.MAX_WBITS,      # window size in bits:
                                  #   -15..-8: negate, suppress header
                                  #   8..15: normal
                                  #   16..30: subtract 16, gzip header
            zlib.DEF_MEM_LEVEL,   # mem level: 1..8/9
            0                     # strategy:
                                  #   0 = Z_DEFAULT_STRATEGY
                                  #   1 = Z_FILTERED
                                  #   2 = Z_HUFFMAN_ONLY
                                  #   3 = Z_RLE
                                  #   4 = Z_FIXED
    )
    deflated = compress.compress(data)
    deflated += compress.flush()
    return deflated

def inflate(data):
    decompress = zlib.decompressobj(
            -zlib.MAX_WBITS  # see above
    )
    inflated = decompress.decompress(data)
    inflated += decompress.flush()
    return inflated

Je ne sais pas si cela correspond exactement à tout ce dont votre serveur a besoin, mais ces deux fonctions sont capables de faire le tour des données que j'ai essayées.

Les paramètres correspondent directement à ce qui est transmis aux fonctions de la bibliothèque zlib.

Python [~ # ~] c [~ # ~]
zlib.compressobj(...)deflateInit(...)
compressobj.compress(...)deflate(...)
zlib.decompressobj(...)inflateInit(...)
decompressobj.decompress(...)inflate(...)

Les constructeurs créent la structure et la remplissent avec des valeurs par défaut, et la transmettent aux fonctions init. Les méthodes compress/decompress mettent à jour la structure et la transmettent à inflate/deflate.

21
Markus Jarderot