Python: Inflate и Deflate реализации
Я взаимодействую с сервером, который требует, чтобы отправленные на него данные были сжаты с помощью алгоритма Deflate (кодировка Хаффмана + LZ77), а также отправляет данные, которые мне нужны для Inflate.
Я знаю, что Python включает Zlib, и что библиотеки C в Zlib поддерживают вызовы Inflate и Deflate, но они, очевидно, не предоставляются модулем Python Zlib. Это обеспечивает Compress и Decompress, но когда я звоню, например, следующее:
result_data = zlib.decompress( base64_decoded_compressed_string )
Я получаю следующую ошибку:
Error -3 while decompressing data: incorrect header check
Gzip не лучше; при совершении звонка, например:
result_data = gzip.GzipFile( fileobj = StringIO.StringIO( base64_decoded_compressed_string ) ).read()
Я получаю ошибку:
IOError: Not a gzipped file
что имеет смысл, поскольку данные представляют собой дефлированный файл, а не настоящий сжатый файл.
Теперь я знаю, что существует реализация Deflate (Pyflate), но я не знаю реализации Inflate.
Кажется, есть несколько вариантов:
- Найти существующую реализацию (идеал) Inflate и Deflate в Python
- Написать свое собственное расширение Python для библиотеки zlib c, которая включает в себя Inflate и Deflate
- Вызовите что-нибудь еще, что можно выполнить из командной строки (например, скрипт Ruby, поскольку вызовы Inflate/Deflate в zlib полностью упакованы в Ruby)
- ?
Я ищу решение, но в отсутствие решения я буду благодарен за идеи, конструктивные мнения и идеи.
Дополнительная информация: Результат дефлятирования (и кодирования) строки должен, для нужных мне целей, дать тот же результат, что и следующий фрагмент кода C#, где входным параметром является массив байтов UTF, соответствующих данным для сжатия:
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;
}
Запуск этого кода.NET для строки "выкачать и закодировать меня" дает результат
7b0HYBxJliUmL23Ke39K9UrX4HShCIBgEyTYkEAQ7MGIzeaS7B1pRyMpqyqBymVWZV1mFkDM7Z28995777333nvvvfe6O51OJ/ff/z9cZmQBbPbOStrJniGAqsgfP358Hz8iZvl5mbV5mi1nab6cVrM8XeT/Dw==
Когда "deflate and encode me" запускается через Python Zlib.compress(), а затем кодируется base64, результатом является "eJxLSU3LSSxJVUjMS1FIzUvOT0lVyE0FAFXHB6k=".
Понятно, что zlib.compress() не является реализацией того же алгоритма, что и стандартный алгоритм Deflate.
Больше информации:
Первые 2 байта данных.NET для дефлятирования ("7b0HY...") после декодирования b64 равны 0xEDBD, что не соответствует данным Gzip (0x1f8b), данным BZip2 (0x425A) или данным Zlib (0x789C).
Первые 2 байта сжатых данных Python ("eJxLS...") после декодирования b64 равны 0x789C. Это заголовок Zlib.
РЕШИТЬ
Чтобы справиться с необработанным выдуванием и раздуванием без заголовка и контрольной суммы, необходимо выполнить следующее:
При выкачивании / сжатии: удалите первые два байта (заголовок) и последние четыре байта (контрольная сумма).
При накачке / распаковке: есть второй аргумент для размера окна. Если это значение отрицательно, оно подавляет заголовки. Вот мои методы в настоящее время, включая кодирование / декодирование base64 - и работают должным образом:
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 )
2 ответа
Это дополнение к ответу MizardX, содержащее некоторые пояснения и справочную информацию.
См. http://www.chiramattel.com/george/blog/2007/09/09/deflatestream-block-length-does-not-match.html
Согласно RFC 1950 поток zlib, построенный по умолчанию, состоит из:
- 2-байтовый заголовок (например, 0x78 0x9C)
- поток сдувания - см. RFC 1951
- контрольная сумма Adler-32 несжатых данных (4 байта)
C# DeflateStream
работает на (как вы уже догадались) потоке-дефляте. Код MizardX сообщает модулю zlib, что данные являются необработанным потоком сдувания.
Наблюдения: (1) Можно надеяться, что метод "дефляции" C#, создающий более длинную строку, происходит только с коротким вводом (2) Использование необработанного потока deflate без контрольной суммы Adler-32? Немного рискованно, если не заменить чем-то лучшим.
Обновления
сообщение об ошибке Block length does not match with its complement
Если вы пытаетесь надуть некоторые сжатые данные с помощью C# DeflateStream
и вы получите это сообщение, тогда вполне возможно, что вы даете ему поток zlib, а не поток deflate.
Смотрите Как вы используете DeflateStream для части файла?
Также скопируйте / вставьте сообщение об ошибке в поиск Google, и вы получите множество обращений (включая одно в начале этого ответа), говорящих примерно то же самое.
Ява Deflater
... используемый "веб-сайтом" ... C# DeflateStream "довольно прост и был протестирован с реализацией Java". Какой из следующих возможных конструкторов Java Deflater использует веб-сайт?
public Deflater(int level, boolean nowrap)
Создает новый компрессор, используя указанный уровень сжатия. Если "nowrap" имеет значение true, тогда поля заголовка и контрольной суммы ZLIB не будут использоваться для поддержки формата сжатия, используемого как в GZIP, так и в PKZIP.
public Deflater(int level)
Создает новый компрессор, используя указанный уровень сжатия. Сжатые данные будут сгенерированы в формате ZLIB.
public Deflater()
Создает новый компрессор с уровнем сжатия по умолчанию. Сжатые данные будут сгенерированы в формате ZLIB.
Однострочный дефлятор после отбрасывания 2-байтового заголовка zlib и 4-байтовой контрольной суммы:
uncompressed_string.encode('zlib')[2:-4] # does not work in Python 3.x
или же
zlib.compress(uncompressed_string)[2:-4]
Вы все еще можете использовать zlib
модуль для раздувания / выкачивания данных. gzip
модуль использует его внутренне, но добавляет заголовок файла, чтобы превратить его в gzip-файл. Глядя на gzip.py
файл, что-то вроде этого может работать:
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
Я не знаю, соответствует ли это в точности тому, что требует ваш сервер, но эти две функции способны обойти любые данные, которые я пробовал.
Параметры отображаются непосредственно на то, что передается функциям библиотеки zlib.
Python ⇒ C zlib.compressobj(...)
⇒ deflateInit(...)
compressobj.compress(...)
⇒ deflate(...)
zlib.decompressobj(...)
⇒ inflateInit(...)
decompressobj.decompress(...)
⇒ inflate(...)
Конструкторы создают структуру, заполняют ее значениями по умолчанию и передают ее функциям init. compress
/ decompress
методы обновляют структуру и передают ее inflate
/ deflate
,