317 lines
8.8 KiB
C
317 lines
8.8 KiB
C
/*
|
|
* Jim - zlib bindings
|
|
*
|
|
* Copyright 2015, 2016 Dima Krasner <dima@dimakrasner.com>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following
|
|
* disclaimer in the documentation and/or other materials
|
|
* provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE JIM TCL PROJECT ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* JIM TCL PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* The views and conclusions contained in the software and documentation
|
|
* are those of the authors and should not be interpreted as representing
|
|
* official policies, either expressed or implied, of the Jim Tcl Project.
|
|
*/
|
|
|
|
#include <zlib.h>
|
|
|
|
#include <jim.h>
|
|
#include <jim-subcmd.h>
|
|
|
|
#define WBITS_GZIP (MAX_WBITS | 16)
|
|
/* use small 64K chunks if no size was specified during decompression, to reduce memory consumption */
|
|
#define DEF_DECOMPRESS_BUFSIZ (64 * 1024)
|
|
|
|
static int JimZlibCheckBufSize(Jim_Interp *interp, jim_wide bufsiz)
|
|
{
|
|
if ((bufsiz <= 0) || (bufsiz > INT_MAX)) {
|
|
Jim_SetResultFormatted(interp, "buffer size must be 0 to %#s", Jim_NewIntObj(interp, INT_MAX));
|
|
return JIM_ERR;
|
|
}
|
|
return JIM_OK;
|
|
}
|
|
|
|
static int Jim_Crc32(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
|
|
{
|
|
long init;
|
|
const char *in;
|
|
int len;
|
|
|
|
if (argc == 1) {
|
|
init = crc32(0L, Z_NULL, 0);
|
|
} else {
|
|
if (Jim_GetLong(interp, argv[1], &init) != JIM_OK) {
|
|
return JIM_ERR;
|
|
}
|
|
}
|
|
|
|
in = Jim_GetString(argv[0], &len);
|
|
Jim_SetResultInt(interp, crc32((uLong)init, (const Bytef *)in, (uInt)len) & 0xFFFFFFFF);
|
|
|
|
return JIM_OK;
|
|
}
|
|
|
|
static int Jim_Compress(Jim_Interp *interp, const char *in, int len, long level, int wbits)
|
|
{
|
|
z_stream strm = {0};
|
|
Bytef *buf;
|
|
|
|
if ((level != Z_DEFAULT_COMPRESSION) && ((level < Z_NO_COMPRESSION) || (level > Z_BEST_COMPRESSION))) {
|
|
Jim_SetResultString(interp, "level must be 0 to 9", -1);
|
|
return JIM_ERR;
|
|
}
|
|
|
|
if (deflateInit2(&strm, level, Z_DEFLATED, wbits, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) {
|
|
return JIM_ERR;
|
|
}
|
|
|
|
strm.avail_out = deflateBound(&strm, (uLong)len);
|
|
|
|
/* Some compression methods may need a little more space */
|
|
strm.avail_out += 100;
|
|
|
|
if (strm.avail_out > INT_MAX) {
|
|
deflateEnd(&strm);
|
|
return JIM_ERR;
|
|
}
|
|
buf = (Bytef *)Jim_Alloc((int)strm.avail_out);
|
|
strm.next_out = buf;
|
|
strm.next_in = (Bytef *)in;
|
|
strm.avail_in = (uInt)len;
|
|
|
|
/* always compress in one pass - the return value holds the entire
|
|
* decompressed data anyway, so there's no reason to do chunked
|
|
* decompression */
|
|
if (deflate(&strm, Z_FINISH) != Z_STREAM_END) {
|
|
Jim_Free(buf);
|
|
deflateEnd(&strm);
|
|
Jim_SetResultString(interp, "not enough output space", -1);
|
|
return JIM_ERR;
|
|
}
|
|
|
|
deflateEnd(&strm);
|
|
|
|
if (strm.total_out > INT_MAX) {
|
|
Jim_Free(buf);
|
|
Jim_SetResultString(interp, "too much output", -1);
|
|
return JIM_ERR;
|
|
}
|
|
|
|
Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, (char *)buf, (int)strm.total_out));
|
|
return JIM_OK;
|
|
}
|
|
|
|
static int Jim_Deflate(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
|
|
{
|
|
long level = Z_DEFAULT_COMPRESSION;
|
|
const char *in;
|
|
int len;
|
|
|
|
if (argc != 1) {
|
|
if (Jim_GetLong(interp, argv[1], &level) != JIM_OK) {
|
|
return JIM_ERR;
|
|
}
|
|
}
|
|
|
|
in = Jim_GetString(argv[0], &len);
|
|
return Jim_Compress(interp, in, len, level, -MAX_WBITS);
|
|
}
|
|
|
|
static int Jim_Gzip(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
|
|
{
|
|
long level = Z_DEFAULT_COMPRESSION;
|
|
const char *in;
|
|
int len;
|
|
|
|
if (argc == 3) {
|
|
if (!Jim_CompareStringImmediate(interp, argv[1], "-level")) {
|
|
return -1;
|
|
}
|
|
|
|
if (Jim_GetLong(interp, argv[2], &level) != JIM_OK) {
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
else if (argc != 1) {
|
|
return -1;
|
|
}
|
|
|
|
in = Jim_GetString(argv[0], &len);
|
|
return Jim_Compress(interp, in, len, level, WBITS_GZIP);
|
|
}
|
|
|
|
static int Jim_Decompress(Jim_Interp *interp, const char *in, int len, long bufsiz, int wbits)
|
|
{
|
|
z_stream strm = {0};
|
|
void *buf;
|
|
Jim_Obj *out;
|
|
int ret;
|
|
|
|
if (JimZlibCheckBufSize(interp, bufsiz)) {
|
|
return JIM_ERR;
|
|
}
|
|
|
|
if (inflateInit2(&strm, wbits) != Z_OK) {
|
|
return JIM_ERR;
|
|
}
|
|
|
|
/* allocate a buffer - decompression is done in chunks, into this buffer;
|
|
* when the decompressed data size is given, decompression is faster because
|
|
* it's done in one pass, with less memcpy() overhead */
|
|
buf = Jim_Alloc((int)bufsiz);
|
|
|
|
out = Jim_NewEmptyStringObj(interp);
|
|
Jim_IncrRefCount(out);
|
|
|
|
strm.next_in = (Bytef*)in;
|
|
strm.avail_in = (uInt)len;
|
|
do {
|
|
do {
|
|
strm.next_out = buf;
|
|
strm.avail_out = (uInt)bufsiz;
|
|
|
|
ret = inflate(&strm, Z_NO_FLUSH);
|
|
switch (ret) {
|
|
case Z_OK:
|
|
case Z_STREAM_END:
|
|
/* append each chunk to the output object */
|
|
Jim_AppendString(interp, out, buf, (int)(bufsiz - (long)strm.avail_out));
|
|
break;
|
|
|
|
default:
|
|
Jim_DecrRefCount(interp, out);
|
|
Jim_Free(buf);
|
|
inflateEnd(&strm);
|
|
if (strm.msg != NULL)
|
|
Jim_SetResultString(interp, strm.msg, -1);
|
|
return JIM_ERR;
|
|
}
|
|
} while (strm.avail_out == 0);
|
|
} while (ret != Z_STREAM_END);
|
|
|
|
/* free memory used for decompression before we assign the return value */
|
|
Jim_Free(buf);
|
|
inflateEnd(&strm);
|
|
|
|
Jim_SetResult(interp, out);
|
|
Jim_DecrRefCount(interp, out);
|
|
|
|
return JIM_OK;
|
|
}
|
|
|
|
static int Jim_Inflate(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
|
|
{
|
|
long bufsiz = DEF_DECOMPRESS_BUFSIZ;
|
|
const char *in;
|
|
int len;
|
|
|
|
if (argc != 1) {
|
|
if (Jim_GetLong(interp, argv[1], &bufsiz) != JIM_OK) {
|
|
return JIM_ERR;
|
|
}
|
|
|
|
if (JimZlibCheckBufSize(interp, bufsiz)) {
|
|
return JIM_ERR;
|
|
}
|
|
}
|
|
|
|
in = Jim_GetString(argv[0], &len);
|
|
return Jim_Decompress(interp, in, len, bufsiz, -MAX_WBITS);
|
|
}
|
|
|
|
static int Jim_Gunzip(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
|
|
{
|
|
long bufsiz = DEF_DECOMPRESS_BUFSIZ;
|
|
const char *in;
|
|
int len;
|
|
|
|
if (argc == 3) {
|
|
if (!Jim_CompareStringImmediate(interp, argv[1], "-buffersize")) {
|
|
return -1;
|
|
}
|
|
|
|
if (Jim_GetLong(interp, argv[2], &bufsiz) != JIM_OK) {
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
else if (argc != 1) {
|
|
return -1;
|
|
}
|
|
|
|
in = Jim_GetString(argv[0], &len);
|
|
return Jim_Decompress(interp, in, len, bufsiz, WBITS_GZIP);
|
|
}
|
|
|
|
static const jim_subcmd_type zlib_command_table[] = {
|
|
{ "crc32",
|
|
"data ?startValue?",
|
|
Jim_Crc32,
|
|
1,
|
|
2,
|
|
/* Description: Calculates the CRC32 checksum of a string */
|
|
},
|
|
{ "deflate",
|
|
"string ?level?",
|
|
Jim_Deflate,
|
|
1,
|
|
2,
|
|
/* Description: Compresses a string and outputs a raw, zlib-compressed stream */
|
|
},
|
|
{ "gzip",
|
|
"data ?-level level?",
|
|
Jim_Gzip,
|
|
1,
|
|
3,
|
|
/* Description: Compresses a string and outputs a gzip-compressed stream */
|
|
},
|
|
{ "inflate",
|
|
"data ?bufferSize?",
|
|
Jim_Inflate,
|
|
1,
|
|
2,
|
|
/* Description: Decompresses a raw, zlib-compressed stream */
|
|
},
|
|
{ "gunzip",
|
|
"data ?-buffersize size?",
|
|
Jim_Gunzip,
|
|
1,
|
|
3,
|
|
/* Description: Decompresses a gzip-compressed stream */
|
|
},
|
|
{ NULL }
|
|
};
|
|
|
|
static int JimZlibCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
|
|
{
|
|
return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, zlib_command_table, argc, argv), argc, argv);
|
|
}
|
|
|
|
int Jim_zlibInit(Jim_Interp *interp)
|
|
{
|
|
Jim_PackageProvideCheck(interp, "zlib");
|
|
Jim_CreateCommand(interp, "zlib", JimZlibCmd, 0, 0);
|
|
|
|
return JIM_OK;
|
|
}
|