Skip to content
Snippets Groups Projects
zlib_into.c 6.39 KiB
Newer Older
Thomas Kluyver's avatar
Thomas Kluyver committed
/* Copyright (C) European XFEL GmbH Schenefeld. All rights reserved.
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/. */

Thomas Kluyver's avatar
Thomas Kluyver committed
#define PY_SSIZE_T_CLEAN
//#define Py_LIMITED_API 0x030B0000
Thomas Kluyver's avatar
Thomas Kluyver committed

#include <stdlib.h>
#include <Python.h>
#include "zlib.h"

#define DEF_MEM_LEVEL 8

Thomas Kluyver's avatar
Thomas Kluyver committed
// malloc & free functions copied from CPython
Thomas Kluyver's avatar
Thomas Kluyver committed
static void*
PyZlib_Malloc(voidpf ctx, uInt items, uInt size)
{
    if (size != 0 && items > (size_t)PY_SSIZE_T_MAX / size)
        return NULL;
    /* PyMem_Malloc() cannot be used: the GIL is not held when
       inflate() and deflate() are called */
    /* The builtin zlib module uses PyMem_RawMalloc here, but this was only
       added to the stable ABI from Python 3.13, so use plain malloc for now */
    return malloc((size_t)items * (size_t)size);
}

static void
PyZlib_Free(voidpf ctx, void *ptr)
{
    /* The builtin zlib module uses PyMem_RawFree here, but this was only
       added to the stable ABI from Python 3.13, so use plain free for now */
    free(ptr);
}

Thomas Kluyver's avatar
Thomas Kluyver committed
// zlib_error copied from CPython
static void
zlib_error(z_stream zst, int err, const char *msg)
{
    const char *zmsg = Z_NULL;
    /* In case of a version mismatch, zst.msg won't be initialized.
       Check for this case first, before looking at zst.msg. */
    if (err == Z_VERSION_ERROR)
        zmsg = "library version mismatch";
    if (zmsg == Z_NULL)
        zmsg = zst.msg;
    if (zmsg == Z_NULL) {
        switch (err) {
        case Z_BUF_ERROR:
            zmsg = "incomplete or truncated stream";
            break;
        case Z_STREAM_ERROR:
            zmsg = "inconsistent stream state";
            break;
        case Z_DATA_ERROR:
            zmsg = "invalid input data";
            break;
        }
    }
    if (zmsg == Z_NULL)
        PyErr_Format(PyExc_RuntimeError, "Error %d %s", err, msg);
    else
        PyErr_Format(PyExc_RuntimeError, "Error %d %s: %.200s", err, msg, zmsg);
}

Thomas Kluyver's avatar
Thomas Kluyver committed
static PyObject *
compress_into(PyObject *module, PyObject *args, PyObject *kwargs) {
    static char *keywords[] = {"data", "output", "level", "wbits", NULL};
Thomas Kluyver's avatar
Thomas Kluyver committed
    PyObject *return_value = NULL;
    Py_ssize_t bytes_written;
    Py_buffer input, output;
    int level=Z_DEFAULT_COMPRESSION, wbits=MAX_WBITS;
Thomas Kluyver's avatar
Thomas Kluyver committed
    z_stream zst;

    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "y*w*|ii", keywords,
                                     &input, &output, &level, &wbits)) {
Thomas Kluyver's avatar
Thomas Kluyver committed
        return NULL;
    }

    zst.opaque = NULL;
    zst.zalloc = PyZlib_Malloc;
    zst.zfree = PyZlib_Free;
    zst.next_in = input.buf;
    zst.avail_in = input.len;
    zst.next_out = output.buf;
    zst.avail_out = output.len;
    int err = deflateInit2(&zst, level, Z_DEFLATED, wbits, DEF_MEM_LEVEL,
                           Z_DEFAULT_STRATEGY);

    switch (err) {
    case Z_OK:
        break;
    case Z_MEM_ERROR:
        PyErr_SetString(PyExc_MemoryError,
                        "Out of memory while compressing data");
        goto done;
    case Z_STREAM_ERROR:
        PyErr_SetString(PyExc_ValueError, "Bad compression level");
        goto done;
    default:
        deflateEnd(&zst);
Thomas Kluyver's avatar
Thomas Kluyver committed
        zlib_error(zst, err, "while preparing to compress data");
Thomas Kluyver's avatar
Thomas Kluyver committed
        goto done;
    }

    Py_BEGIN_ALLOW_THREADS
    err = deflate(&zst, Z_FINISH);
    Py_END_ALLOW_THREADS

    switch (err) {
        case Z_STREAM_END:
            break;
        case Z_OK:
        case Z_BUF_ERROR:
            deflateEnd(&zst);
            PyErr_SetString(PyExc_ValueError, "Not enough space in output buffer");
Thomas Kluyver's avatar
Thomas Kluyver committed
            goto done;
        default:
            deflateEnd(&zst);
Thomas Kluyver's avatar
Thomas Kluyver committed
            zlib_error(zst, err, "while compressing data");
Thomas Kluyver's avatar
Thomas Kluyver committed
            goto done;
    }
    bytes_written = output.len - zst.avail_out;
Thomas Kluyver's avatar
Thomas Kluyver committed
    err = deflateEnd(&zst);
    if (err != Z_OK) {
        zlib_error(zst, err, "while finishing compression");
        goto done;
    }
Thomas Kluyver's avatar
Thomas Kluyver committed

    return_value = PyLong_FromSsize_t(bytes_written);

    done:
      PyBuffer_Release(&input);
      PyBuffer_Release(&output);
      return return_value;
Thomas Kluyver's avatar
Thomas Kluyver committed
}
Thomas Kluyver's avatar
Thomas Kluyver committed

Thomas Kluyver's avatar
Thomas Kluyver committed

static PyObject *
decompress_into(PyObject *module, PyObject *args, PyObject *kwargs) {
    static char *keywords[] = {"data", "output", "wbits", NULL};
    PyObject *return_value = NULL;
    Py_ssize_t bytes_written;
    Py_buffer input, output;
    int wbits=MAX_WBITS;
    z_stream zst;

    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "y*w*|i", keywords,
                                     &input, &output, &wbits)) {
        return NULL;
    }

    zst.opaque = NULL;
    zst.zalloc = PyZlib_Malloc;
    zst.zfree = PyZlib_Free;
    zst.next_in = input.buf;
    zst.avail_in = input.len;
    zst.next_out = output.buf;
    zst.avail_out = output.len;
    int err = inflateInit2(&zst, wbits);

    switch (err) {
    case Z_OK:
        break;
    case Z_MEM_ERROR:
        PyErr_SetString(PyExc_MemoryError,
                        "Out of memory while decompressing data");
        goto done;
    default:
        deflateEnd(&zst);
        zlib_error(zst, err, "while preparing to decompress data");
        goto done;
    }

    Py_BEGIN_ALLOW_THREADS
    err = inflate(&zst, Z_FINISH);
    Py_END_ALLOW_THREADS

    switch (err) {
        case Z_STREAM_END:
            break;
        case Z_OK:
        case Z_BUF_ERROR:
            deflateEnd(&zst);
            PyErr_SetString(PyExc_ValueError, "Not enough space in output buffer");
Thomas Kluyver's avatar
Thomas Kluyver committed
            goto done;
        default:
            deflateEnd(&zst);
            zlib_error(zst, err, "while compressing data");
            goto done;
    }
    bytes_written = output.len - zst.avail_out;

    err = inflateEnd(&zst);
    if (err != Z_OK) {
        zlib_error(zst, err, "while finishing decompression");
        goto done;
    }

    return_value = PyLong_FromSsize_t(bytes_written);

    done:
      PyBuffer_Release(&input);
      PyBuffer_Release(&output);
      return return_value;
Thomas Kluyver's avatar
Thomas Kluyver committed
}

Thomas Kluyver's avatar
Thomas Kluyver committed

Thomas Kluyver's avatar
Thomas Kluyver committed
static PyMethodDef ZlibIntoMethods[] = {
    {"compress_into", (PyCFunction)compress_into, METH_VARARGS | METH_KEYWORDS, "zlib compress data into a buffer"},
Thomas Kluyver's avatar
Thomas Kluyver committed
    {"decompress_into", (PyCFunction)decompress_into, METH_VARARGS | METH_KEYWORDS, "zlib decompress data into a buffer"},
Thomas Kluyver's avatar
Thomas Kluyver committed
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef zlibintomodule = {
    PyModuleDef_HEAD_INIT,
    "zlib_into",
    NULL,  // docstring
    -1,
    ZlibIntoMethods
};

PyMODINIT_FUNC
PyInit_zlib_into(void) {
    return PyModule_Create(&zlibintomodule);
}