diff options
Diffstat (limited to 'generic/tclZlib.c')
-rw-r--r-- | generic/tclZlib.c | 4011 |
1 files changed, 4011 insertions, 0 deletions
diff --git a/generic/tclZlib.c b/generic/tclZlib.c new file mode 100644 index 0000000..9c1176e --- /dev/null +++ b/generic/tclZlib.c @@ -0,0 +1,4011 @@ +/* + * tclZlib.c -- + * + * This file provides the interface to the Zlib library. + * + * Copyright (C) 2004-2005 Pascal Scheffers <pascal@scheffers.net> + * Copyright (C) 2005 Unitas Software B.V. + * Copyright (c) 2008-2012 Donal K. Fellows + * + * Parts written by Jean-Claude Wippler, as part of Tclkit, placed in the + * public domain March 2003. + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#include "tclInt.h" +#ifdef HAVE_ZLIB +#include <zlib.h> +#include "tclIO.h" + +/* + * The version of the zlib "package" that this implements. Note that this + * thoroughly supersedes the versions included with tclkit, which are "1.1", + * so this is at least "2.0" (there's no general *commitment* to have the same + * interface, even if that is mostly true). + */ + +#define TCL_ZLIB_VERSION "2.0" + +/* + * Magic flags used with wbits fields to indicate that we're handling the gzip + * format or automatic detection of format. Putting it here is slightly less + * gross! + */ + +#define WBITS_RAW (-MAX_WBITS) +#define WBITS_ZLIB (MAX_WBITS) +#define WBITS_GZIP (MAX_WBITS | 16) +#define WBITS_AUTODETECT (MAX_WBITS | 32) + +/* + * Structure used for handling gzip headers that are generated from a + * dictionary. It comprises the header structure itself plus some working + * space that it is very convenient to have attached. + */ + +#define MAX_COMMENT_LEN 256 + +typedef struct { + gz_header header; + char nativeFilenameBuf[MAXPATHLEN]; + char nativeCommentBuf[MAX_COMMENT_LEN]; +} GzipHeader; + +/* + * Structure used for the Tcl_ZlibStream* commands and [zlib stream ...] + */ + +typedef struct { + Tcl_Interp *interp; + z_stream stream; /* The interface to the zlib library. */ + int streamEnd; /* If we've got to end-of-stream. */ + Tcl_Obj *inData, *outData; /* Input / output buffers (lists) */ + Tcl_Obj *currentInput; /* Pointer to what is currently being + * inflated. */ + int outPos; + int mode; /* Either TCL_ZLIB_STREAM_DEFLATE or + * TCL_ZLIB_STREAM_INFLATE. */ + int format; /* Flags from the TCL_ZLIB_FORMAT_* */ + int level; /* Default 5, 0-9 */ + int flush; /* Stores the flush param for deferred the + * decompression. */ + int wbits; /* The encoded compression mode, so we can + * restart the stream if necessary. */ + Tcl_Command cmd; /* Token for the associated Tcl command. */ + Tcl_Obj *compDictObj; /* Byte-array object containing compression + * dictionary (not dictObj!) to use if + * necessary. */ + int flags; /* Miscellaneous flag bits. */ + GzipHeader *gzHeaderPtr; /* If we've allocated a gzip header + * structure. */ +} ZlibStreamHandle; + +#define DICT_TO_SET 0x1 /* If we need to set a compression dictionary + * in the low-level engine at the next + * opportunity. */ + +/* + * Macros to make it clearer in some of the twiddlier accesses what is + * happening. + */ + +#define IsRawStream(zshPtr) ((zshPtr)->format == TCL_ZLIB_FORMAT_RAW) +#define HaveDictToSet(zshPtr) ((zshPtr)->flags & DICT_TO_SET) +#define DictWasSet(zshPtr) ((zshPtr)->flags |= ~DICT_TO_SET) + +/* + * Structure used for stacked channel compression and decompression. + */ + +typedef struct { + Tcl_Channel chan; /* Reference to the channel itself. */ + Tcl_Channel parent; /* The underlying source and sink of bytes. */ + int flags; /* General flag bits, see below... */ + int mode; /* Either the value TCL_ZLIB_STREAM_DEFLATE + * for compression on output, or + * TCL_ZLIB_STREAM_INFLATE for decompression + * on input. */ + int format; /* What format of data is going on the wire. + * Needed so that the correct [fconfigure] + * options can be enabled. */ + int readAheadLimit; /* The maximum number of bytes to read from + * the underlying stream in one go. */ + z_stream inStream; /* Structure used by zlib for decompression of + * input. */ + z_stream outStream; /* Structure used by zlib for compression of + * output. */ + char *inBuffer, *outBuffer; /* Working buffers. */ + int inAllocated, outAllocated; + /* Sizes of working buffers. */ + GzipHeader inHeader; /* Header read from input stream, when + * decompressing a gzip stream. */ + GzipHeader outHeader; /* Header to write to an output stream, when + * compressing a gzip stream. */ + Tcl_TimerToken timer; /* Timer used for keeping events fresh. */ + Tcl_DString decompressed; /* Buffer for decompression results. */ + Tcl_Obj *compDictObj; /* Byte-array object containing compression + * dictionary (not dictObj!) to use if + * necessary. */ +} ZlibChannelData; + +/* + * Value bits for the flags field. Definitions are: + * ASYNC - Whether this is an asynchronous channel. + * IN_HEADER - Whether the inHeader field has been registered with + * the input compressor. + * OUT_HEADER - Whether the outputHeader field has been registered + * with the output decompressor. + */ + +#define ASYNC 0x1 +#define IN_HEADER 0x2 +#define OUT_HEADER 0x4 + +/* + * Size of buffers allocated by default, and the range it can be set to. The + * same sorts of values apply to streams, except with different limits (they + * permit byte-level activity). Channels always use bytes unless told to use + * larger buffers. + */ + +#define DEFAULT_BUFFER_SIZE 4096 +#define MIN_NONSTREAM_BUFFER_SIZE 16 +#define MAX_BUFFER_SIZE 65536 + +/* + * Prototypes for private procedures defined later in this file: + */ + +static Tcl_CmdDeleteProc ZlibStreamCmdDelete; +static Tcl_DriverBlockModeProc ZlibTransformBlockMode; +static Tcl_DriverCloseProc ZlibTransformClose; +static Tcl_DriverGetHandleProc ZlibTransformGetHandle; +static Tcl_DriverGetOptionProc ZlibTransformGetOption; +static Tcl_DriverHandlerProc ZlibTransformEventHandler; +static Tcl_DriverInputProc ZlibTransformInput; +static Tcl_DriverOutputProc ZlibTransformOutput; +static Tcl_DriverSetOptionProc ZlibTransformSetOption; +static Tcl_DriverWatchProc ZlibTransformWatch; +static Tcl_ObjCmdProc ZlibCmd; +static Tcl_ObjCmdProc ZlibStreamCmd; +static Tcl_ObjCmdProc ZlibStreamAddCmd; +static Tcl_ObjCmdProc ZlibStreamHeaderCmd; +static Tcl_ObjCmdProc ZlibStreamPutCmd; + +static void ConvertError(Tcl_Interp *interp, int code, + uLong adler); +static Tcl_Obj * ConvertErrorToList(int code, uLong adler); +static void ExtractHeader(gz_header *headerPtr, Tcl_Obj *dictObj); +static int GenerateHeader(Tcl_Interp *interp, Tcl_Obj *dictObj, + GzipHeader *headerPtr, int *extraSizePtr); +static int ZlibPushSubcmd(Tcl_Interp *interp, int objc, + Tcl_Obj *const objv[]); +static inline int ResultCopy(ZlibChannelData *cd, char *buf, + int toRead); +static int ResultGenerate(ZlibChannelData *cd, int n, int flush, + int *errorCodePtr); +static Tcl_Channel ZlibStackChannelTransform(Tcl_Interp *interp, + int mode, int format, int level, int limit, + Tcl_Channel channel, Tcl_Obj *gzipHeaderDictPtr, + Tcl_Obj *compDictObj); +static void ZlibStreamCleanup(ZlibStreamHandle *zshPtr); +static int ZlibStreamSubcmd(Tcl_Interp *interp, int objc, + Tcl_Obj *const objv[]); +static inline void ZlibTransformEventTimerKill(ZlibChannelData *cd); +static void ZlibTransformTimerRun(ClientData clientData); + +/* + * Type of zlib-based compressing and decompressing channels. + */ + +static const Tcl_ChannelType zlibChannelType = { + "zlib", + TCL_CHANNEL_VERSION_3, + ZlibTransformClose, + ZlibTransformInput, + ZlibTransformOutput, + NULL, /* seekProc */ + ZlibTransformSetOption, + ZlibTransformGetOption, + ZlibTransformWatch, + ZlibTransformGetHandle, + NULL, /* close2Proc */ + ZlibTransformBlockMode, + NULL, /* flushProc */ + ZlibTransformEventHandler, + NULL, /* wideSeekProc */ + NULL, + NULL +}; + +/* + *---------------------------------------------------------------------- + * + * ConvertError -- + * + * Utility function for converting a zlib error into a Tcl error. + * + * Results: + * None. + * + * Side effects: + * Updates the interpreter result and errorcode. + * + *---------------------------------------------------------------------- + */ + +static void +ConvertError( + Tcl_Interp *interp, /* Interpreter to store the error in. May be + * NULL, in which case nothing happens. */ + int code, /* The zlib error code. */ + uLong adler) /* The checksum expected (for Z_NEED_DICT) */ +{ + const char *codeStr, *codeStr2 = NULL; + char codeStrBuf[TCL_INTEGER_SPACE]; + + if (interp == NULL) { + return; + } + + switch (code) { + /* + * Firstly, the case that is *different* because it's really coming + * from the OS and is just being reported via zlib. It should be + * really uncommon because Tcl handles all I/O rather than delegating + * it to zlib, but proving it can't happen is hard. + */ + + case Z_ERRNO: + Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_PosixError(interp),-1)); + return; + + /* + * Normal errors/conditions, some of which have additional detail and + * some which don't. (This is not defined by array lookup because zlib + * error codes are sometimes negative.) + */ + + case Z_STREAM_ERROR: + codeStr = "STREAM"; + break; + case Z_DATA_ERROR: + codeStr = "DATA"; + break; + case Z_MEM_ERROR: + codeStr = "MEM"; + break; + case Z_BUF_ERROR: + codeStr = "BUF"; + break; + case Z_VERSION_ERROR: + codeStr = "VERSION"; + break; + case Z_NEED_DICT: + codeStr = "NEED_DICT"; + codeStr2 = codeStrBuf; + sprintf(codeStrBuf, "%lu", adler); + break; + + /* + * These should _not_ happen! This function is for dealing with error + * cases, not non-errors! + */ + + case Z_OK: + Tcl_Panic("unexpected zlib result in error handler: Z_OK"); + case Z_STREAM_END: + Tcl_Panic("unexpected zlib result in error handler: Z_STREAM_END"); + + /* + * Anything else is bad news; it's unexpected. Convert to generic + * error. + */ + + default: + codeStr = "UNKNOWN"; + codeStr2 = codeStrBuf; + sprintf(codeStrBuf, "%d", code); + break; + } + Tcl_SetObjResult(interp, Tcl_NewStringObj(zError(code), -1)); + + /* + * Tricky point! We might pass NULL twice here (and will when the error + * type is known). + */ + + Tcl_SetErrorCode(interp, "TCL", "ZLIB", codeStr, codeStr2, NULL); +} + +static Tcl_Obj * +ConvertErrorToList( + int code, /* The zlib error code. */ + uLong adler) /* The checksum expected (for Z_NEED_DICT) */ +{ + Tcl_Obj *objv[4]; + + TclNewLiteralStringObj(objv[0], "TCL"); + TclNewLiteralStringObj(objv[1], "ZLIB"); + switch (code) { + case Z_STREAM_ERROR: + TclNewLiteralStringObj(objv[2], "STREAM"); + return Tcl_NewListObj(3, objv); + case Z_DATA_ERROR: + TclNewLiteralStringObj(objv[2], "DATA"); + return Tcl_NewListObj(3, objv); + case Z_MEM_ERROR: + TclNewLiteralStringObj(objv[2], "MEM"); + return Tcl_NewListObj(3, objv); + case Z_BUF_ERROR: + TclNewLiteralStringObj(objv[2], "BUF"); + return Tcl_NewListObj(3, objv); + case Z_VERSION_ERROR: + TclNewLiteralStringObj(objv[2], "VERSION"); + return Tcl_NewListObj(3, objv); + case Z_ERRNO: + TclNewLiteralStringObj(objv[2], "POSIX"); + objv[3] = Tcl_NewStringObj(Tcl_ErrnoId(), -1); + return Tcl_NewListObj(4, objv); + case Z_NEED_DICT: + TclNewLiteralStringObj(objv[2], "NEED_DICT"); + objv[3] = Tcl_NewWideIntObj((Tcl_WideInt) adler); + return Tcl_NewListObj(4, objv); + + /* + * These should _not_ happen! This function is for dealing with error + * cases, not non-errors! + */ + + case Z_OK: + Tcl_Panic("unexpected zlib result in error handler: Z_OK"); + case Z_STREAM_END: + Tcl_Panic("unexpected zlib result in error handler: Z_STREAM_END"); + + /* + * Catch-all. Should be unreachable because all cases are already + * listed above. + */ + + default: + TclNewLiteralStringObj(objv[2], "UNKNOWN"); + TclNewIntObj(objv[3], code); + return Tcl_NewListObj(4, objv); + } +} + +/* + *---------------------------------------------------------------------- + * + * GenerateHeader -- + * + * Function for creating a gzip header from the contents of a dictionary + * (as described in the documentation). GetValue is a helper function. + * + * Results: + * A Tcl result code. + * + * Side effects: + * Updates the fields of the given gz_header structure. Adds amount of + * extra space required for the header to the variable referenced by the + * extraSizePtr argument. + * + *---------------------------------------------------------------------- + */ + +static inline int +GetValue( + Tcl_Interp *interp, + Tcl_Obj *dictObj, + const char *nameStr, + Tcl_Obj **valuePtrPtr) +{ + Tcl_Obj *name = Tcl_NewStringObj(nameStr, -1); + int result = Tcl_DictObjGet(interp, dictObj, name, valuePtrPtr); + + TclDecrRefCount(name); + return result; +} + +static int +GenerateHeader( + Tcl_Interp *interp, /* Where to put error messages. */ + Tcl_Obj *dictObj, /* The dictionary whose contents are to be + * parsed. */ + GzipHeader *headerPtr, /* Where to store the parsed-out values. */ + int *extraSizePtr) /* Variable to add the length of header + * strings (filename, comment) to. */ +{ + Tcl_Obj *value; + int len, result = TCL_ERROR; + const char *valueStr; + Tcl_Encoding latin1enc; + static const char *const types[] = { + "binary", "text" + }; + + /* + * RFC 1952 says that header strings are in ISO 8859-1 (LATIN-1). + */ + + latin1enc = Tcl_GetEncoding(NULL, "iso8859-1"); + if (latin1enc == NULL) { + Tcl_Panic("no latin-1 encoding"); + } + + if (GetValue(interp, dictObj, "comment", &value) != TCL_OK) { + goto error; + } else if (value != NULL) { + valueStr = Tcl_GetStringFromObj(value, &len); + Tcl_UtfToExternal(NULL, latin1enc, valueStr, len, 0, NULL, + headerPtr->nativeCommentBuf, MAX_COMMENT_LEN-1, NULL, &len, + NULL); + headerPtr->nativeCommentBuf[len] = '\0'; + headerPtr->header.comment = (Bytef *) headerPtr->nativeCommentBuf; + if (extraSizePtr != NULL) { + *extraSizePtr += len; + } + } + + if (GetValue(interp, dictObj, "crc", &value) != TCL_OK) { + goto error; + } else if (value != NULL && + Tcl_GetBooleanFromObj(interp, value, &headerPtr->header.hcrc)) { + goto error; + } + + if (GetValue(interp, dictObj, "filename", &value) != TCL_OK) { + goto error; + } else if (value != NULL) { + valueStr = Tcl_GetStringFromObj(value, &len); + Tcl_UtfToExternal(NULL, latin1enc, valueStr, len, 0, NULL, + headerPtr->nativeFilenameBuf, MAXPATHLEN-1, NULL, &len, NULL); + headerPtr->nativeFilenameBuf[len] = '\0'; + headerPtr->header.name = (Bytef *) headerPtr->nativeFilenameBuf; + if (extraSizePtr != NULL) { + *extraSizePtr += len; + } + } + + if (GetValue(interp, dictObj, "os", &value) != TCL_OK) { + goto error; + } else if (value != NULL && Tcl_GetIntFromObj(interp, value, + &headerPtr->header.os) != TCL_OK) { + goto error; + } + + /* + * Ignore the 'size' field, since that is controlled by the size of the + * input data. + */ + + if (GetValue(interp, dictObj, "time", &value) != TCL_OK) { + goto error; + } else if (value != NULL && Tcl_GetLongFromObj(interp, value, + (long *) &headerPtr->header.time) != TCL_OK) { + goto error; + } + + if (GetValue(interp, dictObj, "type", &value) != TCL_OK) { + goto error; + } else if (value != NULL && Tcl_GetIndexFromObj(interp, value, types, + "type", TCL_EXACT, &headerPtr->header.text) != TCL_OK) { + goto error; + } + + result = TCL_OK; + error: + Tcl_FreeEncoding(latin1enc); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * ExtractHeader -- + * + * Take the values out of a gzip header and store them in a dictionary. + * SetValue is a helper macro. + * + * Results: + * None. + * + * Side effects: + * Updates the dictionary, which must be writable (i.e. refCount < 2). + * + *---------------------------------------------------------------------- + */ + +#define SetValue(dictObj, key, value) \ + Tcl_DictObjPut(NULL, (dictObj), Tcl_NewStringObj((key), -1), (value)) + +static void +ExtractHeader( + gz_header *headerPtr, /* The gzip header to extract from. */ + Tcl_Obj *dictObj) /* The dictionary to store in. */ +{ + Tcl_Encoding latin1enc = NULL; + Tcl_DString tmp; + + if (headerPtr->comment != Z_NULL) { + if (latin1enc == NULL) { + /* + * RFC 1952 says that header strings are in ISO 8859-1 (LATIN-1). + */ + + latin1enc = Tcl_GetEncoding(NULL, "iso8859-1"); + if (latin1enc == NULL) { + Tcl_Panic("no latin-1 encoding"); + } + } + + Tcl_ExternalToUtfDString(latin1enc, (char *) headerPtr->comment, -1, + &tmp); + SetValue(dictObj, "comment", TclDStringToObj(&tmp)); + } + SetValue(dictObj, "crc", Tcl_NewBooleanObj(headerPtr->hcrc)); + if (headerPtr->name != Z_NULL) { + if (latin1enc == NULL) { + /* + * RFC 1952 says that header strings are in ISO 8859-1 (LATIN-1). + */ + + latin1enc = Tcl_GetEncoding(NULL, "iso8859-1"); + if (latin1enc == NULL) { + Tcl_Panic("no latin-1 encoding"); + } + } + + Tcl_ExternalToUtfDString(latin1enc, (char *) headerPtr->name, -1, + &tmp); + SetValue(dictObj, "filename", TclDStringToObj(&tmp)); + } + if (headerPtr->os != 255) { + SetValue(dictObj, "os", Tcl_NewIntObj(headerPtr->os)); + } + if (headerPtr->time != 0 /* magic - no time */) { + SetValue(dictObj, "time", Tcl_NewLongObj((long) headerPtr->time)); + } + if (headerPtr->text != Z_UNKNOWN) { + SetValue(dictObj, "type", + Tcl_NewStringObj(headerPtr->text ? "text" : "binary", -1)); + } + + if (latin1enc != NULL) { + Tcl_FreeEncoding(latin1enc); + } +} + +static int +SetInflateDictionary( + z_streamp strm, + Tcl_Obj *compDictObj) +{ + if (compDictObj != NULL) { + int length; + unsigned char *bytes = Tcl_GetByteArrayFromObj(compDictObj, &length); + + return inflateSetDictionary(strm, bytes, (unsigned) length); + } + return Z_OK; +} + +static int +SetDeflateDictionary( + z_streamp strm, + Tcl_Obj *compDictObj) +{ + if (compDictObj != NULL) { + int length; + unsigned char *bytes = Tcl_GetByteArrayFromObj(compDictObj, &length); + + return deflateSetDictionary(strm, bytes, (unsigned) length); + } + return Z_OK; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_ZlibStreamInit -- + * + * This command initializes a (de)compression context/handle for + * (de)compressing data in chunks. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * The variable pointed to by zshandlePtr is initialised and memory + * allocated for internal state. Additionally, if interp is not null, a + * Tcl command is created and its name placed in the interp result obj. + * + * Note: + * At least one of interp and zshandlePtr should be non-NULL or the + * reference to the stream will be completely lost. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_ZlibStreamInit( + Tcl_Interp *interp, + int mode, /* Either TCL_ZLIB_STREAM_INFLATE or + * TCL_ZLIB_STREAM_DEFLATE. */ + int format, /* Flags from the TCL_ZLIB_FORMAT_* set. */ + int level, /* 0-9 or TCL_ZLIB_COMPRESS_DEFAULT. */ + Tcl_Obj *dictObj, /* Dictionary containing headers for gzip. */ + Tcl_ZlibStream *zshandlePtr) +{ + int wbits = 0; + int e; + ZlibStreamHandle *zshPtr = NULL; + Tcl_DString cmdname; + Tcl_CmdInfo cmdinfo; + GzipHeader *gzHeaderPtr = NULL; + + switch (mode) { + case TCL_ZLIB_STREAM_DEFLATE: + /* + * Compressed format is specified by the wbits parameter. See zlib.h + * for details. + */ + + switch (format) { + case TCL_ZLIB_FORMAT_RAW: + wbits = WBITS_RAW; + break; + case TCL_ZLIB_FORMAT_GZIP: + wbits = WBITS_GZIP; + if (dictObj) { + gzHeaderPtr = ckalloc(sizeof(GzipHeader)); + memset(gzHeaderPtr, 0, sizeof(GzipHeader)); + if (GenerateHeader(interp, dictObj, gzHeaderPtr, + NULL) != TCL_OK) { + ckfree(gzHeaderPtr); + return TCL_ERROR; + } + } + break; + case TCL_ZLIB_FORMAT_ZLIB: + wbits = WBITS_ZLIB; + break; + default: + Tcl_Panic("incorrect zlib data format, must be " + "TCL_ZLIB_FORMAT_ZLIB, TCL_ZLIB_FORMAT_GZIP or " + "TCL_ZLIB_FORMAT_RAW"); + } + if (level < -1 || level > 9) { + Tcl_Panic("compression level should be between 0 (no compression)" + " and 9 (best compression) or -1 for default compression " + "level"); + } + break; + case TCL_ZLIB_STREAM_INFLATE: + /* + * wbits are the same as DEFLATE, but FORMAT_AUTO is valid too. + */ + + switch (format) { + case TCL_ZLIB_FORMAT_RAW: + wbits = WBITS_RAW; + break; + case TCL_ZLIB_FORMAT_GZIP: + wbits = WBITS_GZIP; + gzHeaderPtr = ckalloc(sizeof(GzipHeader)); + memset(gzHeaderPtr, 0, sizeof(GzipHeader)); + gzHeaderPtr->header.name = (Bytef *) + gzHeaderPtr->nativeFilenameBuf; + gzHeaderPtr->header.name_max = MAXPATHLEN - 1; + gzHeaderPtr->header.comment = (Bytef *) + gzHeaderPtr->nativeCommentBuf; + gzHeaderPtr->header.name_max = MAX_COMMENT_LEN - 1; + break; + case TCL_ZLIB_FORMAT_ZLIB: + wbits = WBITS_ZLIB; + break; + case TCL_ZLIB_FORMAT_AUTO: + wbits = WBITS_AUTODETECT; + break; + default: + Tcl_Panic("incorrect zlib data format, must be " + "TCL_ZLIB_FORMAT_ZLIB, TCL_ZLIB_FORMAT_GZIP, " + "TCL_ZLIB_FORMAT_RAW or TCL_ZLIB_FORMAT_AUTO"); + } + break; + default: + Tcl_Panic("bad mode, must be TCL_ZLIB_STREAM_DEFLATE or" + " TCL_ZLIB_STREAM_INFLATE"); + } + + zshPtr = ckalloc(sizeof(ZlibStreamHandle)); + zshPtr->interp = interp; + zshPtr->mode = mode; + zshPtr->format = format; + zshPtr->level = level; + zshPtr->wbits = wbits; + zshPtr->currentInput = NULL; + zshPtr->streamEnd = 0; + zshPtr->compDictObj = NULL; + zshPtr->flags = 0; + zshPtr->gzHeaderPtr = gzHeaderPtr; + memset(&zshPtr->stream, 0, sizeof(z_stream)); + zshPtr->stream.adler = 1; + + /* + * No output buffer available yet + */ + + if (mode == TCL_ZLIB_STREAM_DEFLATE) { + e = deflateInit2(&zshPtr->stream, level, Z_DEFLATED, wbits, + MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); + if (e == Z_OK && zshPtr->gzHeaderPtr) { + e = deflateSetHeader(&zshPtr->stream, + &zshPtr->gzHeaderPtr->header); + } + } else { + e = inflateInit2(&zshPtr->stream, wbits); + if (e == Z_OK && zshPtr->gzHeaderPtr) { + e = inflateGetHeader(&zshPtr->stream, + &zshPtr->gzHeaderPtr->header); + } + } + + if (e != Z_OK) { + ConvertError(interp, e, zshPtr->stream.adler); + goto error; + } + + /* + * I could do all this in C, but this is easier. + */ + + if (interp != NULL) { + if (Tcl_Eval(interp, "::incr ::tcl::zlib::cmdcounter") != TCL_OK) { + goto error; + } + Tcl_DStringInit(&cmdname); + TclDStringAppendLiteral(&cmdname, "::tcl::zlib::streamcmd_"); + TclDStringAppendObj(&cmdname, Tcl_GetObjResult(interp)); + if (Tcl_GetCommandInfo(interp, Tcl_DStringValue(&cmdname), + &cmdinfo) == 1) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "BUG: Stream command name already exists", -1)); + Tcl_SetErrorCode(interp, "TCL", "BUG", "EXISTING_CMD", NULL); + Tcl_DStringFree(&cmdname); + goto error; + } + Tcl_ResetResult(interp); + + /* + * Create the command. + */ + + zshPtr->cmd = Tcl_CreateObjCommand(interp, Tcl_DStringValue(&cmdname), + ZlibStreamCmd, zshPtr, ZlibStreamCmdDelete); + Tcl_DStringFree(&cmdname); + if (zshPtr->cmd == NULL) { + goto error; + } + } else { + zshPtr->cmd = NULL; + } + + /* + * Prepare the buffers for use. + */ + + zshPtr->inData = Tcl_NewListObj(0, NULL); + Tcl_IncrRefCount(zshPtr->inData); + zshPtr->outData = Tcl_NewListObj(0, NULL); + Tcl_IncrRefCount(zshPtr->outData); + + zshPtr->outPos = 0; + + /* + * Now set the variable pointed to by *zshandlePtr to the pointer to the + * zsh struct. + */ + + if (zshandlePtr) { + *zshandlePtr = (Tcl_ZlibStream) zshPtr; + } + + return TCL_OK; + + error: + if (zshPtr->compDictObj) { + Tcl_DecrRefCount(zshPtr->compDictObj); + } + if (zshPtr->gzHeaderPtr) { + ckfree(zshPtr->gzHeaderPtr); + } + ckfree(zshPtr); + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * ZlibStreamCmdDelete -- + * + * This is the delete command which Tcl invokes when a zlibstream command + * is deleted from the interpreter (on stream close, usually). + * + * Results: + * None + * + * Side effects: + * Invalidates the zlib stream handle as obtained from Tcl_ZlibStreamInit + * + *---------------------------------------------------------------------- + */ + +static void +ZlibStreamCmdDelete( + ClientData cd) +{ + ZlibStreamHandle *zshPtr = cd; + + zshPtr->cmd = NULL; + ZlibStreamCleanup(zshPtr); +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_ZlibStreamClose -- + * + * This procedure must be called after (de)compression is done to ensure + * memory is freed and the command is deleted from the interpreter (if + * any). + * + * Results: + * A standard Tcl result. + * + * Side effects: + * Invalidates the zlib stream handle as obtained from Tcl_ZlibStreamInit + * + *---------------------------------------------------------------------- + */ + +int +Tcl_ZlibStreamClose( + Tcl_ZlibStream zshandle) /* As obtained from Tcl_ZlibStreamInit. */ +{ + ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) zshandle; + + /* + * If the interp is set, deleting the command will trigger + * ZlibStreamCleanup in ZlibStreamCmdDelete. If no interp is set, call + * ZlibStreamCleanup directly. + */ + + if (zshPtr->interp && zshPtr->cmd) { + Tcl_DeleteCommandFromToken(zshPtr->interp, zshPtr->cmd); + } else { + ZlibStreamCleanup(zshPtr); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * ZlibStreamCleanup -- + * + * This procedure is called by either Tcl_ZlibStreamClose or + * ZlibStreamCmdDelete to cleanup the stream context. + * + * Results: + * None + * + * Side effects: + * Invalidates the zlib stream handle. + * + *---------------------------------------------------------------------- + */ + +void +ZlibStreamCleanup( + ZlibStreamHandle *zshPtr) +{ + if (!zshPtr->streamEnd) { + if (zshPtr->mode == TCL_ZLIB_STREAM_DEFLATE) { + deflateEnd(&zshPtr->stream); + } else { + inflateEnd(&zshPtr->stream); + } + } + + if (zshPtr->inData) { + Tcl_DecrRefCount(zshPtr->inData); + } + if (zshPtr->outData) { + Tcl_DecrRefCount(zshPtr->outData); + } + if (zshPtr->currentInput) { + Tcl_DecrRefCount(zshPtr->currentInput); + } + if (zshPtr->compDictObj) { + Tcl_DecrRefCount(zshPtr->compDictObj); + } + if (zshPtr->gzHeaderPtr) { + ckfree(zshPtr->gzHeaderPtr); + } + + ckfree(zshPtr); +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_ZlibStreamReset -- + * + * This procedure will reinitialize an existing stream handle. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * Any data left in the (de)compression buffer is lost. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_ZlibStreamReset( + Tcl_ZlibStream zshandle) /* As obtained from Tcl_ZlibStreamInit */ +{ + ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) zshandle; + int e; + + if (!zshPtr->streamEnd) { + if (zshPtr->mode == TCL_ZLIB_STREAM_DEFLATE) { + deflateEnd(&zshPtr->stream); + } else { + inflateEnd(&zshPtr->stream); + } + } + Tcl_SetByteArrayLength(zshPtr->inData, 0); + Tcl_SetByteArrayLength(zshPtr->outData, 0); + if (zshPtr->currentInput) { + Tcl_DecrRefCount(zshPtr->currentInput); + zshPtr->currentInput = NULL; + } + + zshPtr->outPos = 0; + zshPtr->streamEnd = 0; + memset(&zshPtr->stream, 0, sizeof(z_stream)); + + /* + * No output buffer available yet. + */ + + if (zshPtr->mode == TCL_ZLIB_STREAM_DEFLATE) { + e = deflateInit2(&zshPtr->stream, zshPtr->level, Z_DEFLATED, + zshPtr->wbits, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); + if (e == Z_OK && HaveDictToSet(zshPtr)) { + e = SetDeflateDictionary(&zshPtr->stream, zshPtr->compDictObj); + if (e == Z_OK) { + DictWasSet(zshPtr); + } + } + } else { + e = inflateInit2(&zshPtr->stream, zshPtr->wbits); + if (IsRawStream(zshPtr) && HaveDictToSet(zshPtr) && e == Z_OK) { + e = SetInflateDictionary(&zshPtr->stream, zshPtr->compDictObj); + if (e == Z_OK) { + DictWasSet(zshPtr); + } + } + } + + if (e != Z_OK) { + ConvertError(zshPtr->interp, e, zshPtr->stream.adler); + /* TODO:cleanup */ + return TCL_ERROR; + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_ZlibStreamGetCommandName -- + * + * This procedure will return the command name associated with the + * stream. + * + * Results: + * A Tcl_Obj with the name of the Tcl command or NULL if no command is + * associated with the stream. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +Tcl_Obj * +Tcl_ZlibStreamGetCommandName( + Tcl_ZlibStream zshandle) /* As obtained from Tcl_ZlibStreamInit */ +{ + ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) zshandle; + Tcl_Obj *objPtr; + + if (!zshPtr->interp) { + return NULL; + } + + TclNewObj(objPtr); + Tcl_GetCommandFullName(zshPtr->interp, zshPtr->cmd, objPtr); + return objPtr; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_ZlibStreamEof -- + * + * This procedure This function returns 0 or 1 depending on the state of + * the (de)compressor. For decompression, eof is reached when the entire + * compressed stream has been decompressed. For compression, eof is + * reached when the stream has been flushed with TCL_ZLIB_FINALIZE. + * + * Results: + * Integer. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_ZlibStreamEof( + Tcl_ZlibStream zshandle) /* As obtained from Tcl_ZlibStreamInit */ +{ + ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) zshandle; + + return zshPtr->streamEnd; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_ZlibStreamChecksum -- + * + * Return the checksum of the uncompressed data seen so far by the + * stream. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_ZlibStreamChecksum( + Tcl_ZlibStream zshandle) /* As obtained from Tcl_ZlibStreamInit */ +{ + ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) zshandle; + + return zshPtr->stream.adler; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_ZlibStreamSetCompressionDictionary -- + * + * Sets the compression dictionary for a stream. This will be used as + * appropriate for the next compression or decompression action performed + * on the stream. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_ZlibStreamSetCompressionDictionary( + Tcl_ZlibStream zshandle, + Tcl_Obj *compressionDictionaryObj) +{ + ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) zshandle; + + if (compressionDictionaryObj != NULL) { + if (Tcl_IsShared(compressionDictionaryObj)) { + compressionDictionaryObj = + Tcl_DuplicateObj(compressionDictionaryObj); + } + Tcl_IncrRefCount(compressionDictionaryObj); + zshPtr->flags |= DICT_TO_SET; + } else { + zshPtr->flags &= ~DICT_TO_SET; + } + if (zshPtr->compDictObj != NULL) { + Tcl_DecrRefCount(zshPtr->compDictObj); + } + zshPtr->compDictObj = compressionDictionaryObj; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_ZlibStreamPut -- + * + * Add data to the stream for compression or decompression from a + * bytearray Tcl_Obj. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_ZlibStreamPut( + Tcl_ZlibStream zshandle, /* As obtained from Tcl_ZlibStreamInit */ + Tcl_Obj *data, /* Data to compress/decompress */ + int flush) /* TCL_ZLIB_NO_FLUSH, TCL_ZLIB_FLUSH, + * TCL_ZLIB_FULLFLUSH, or TCL_ZLIB_FINALIZE */ +{ + ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) zshandle; + char *dataTmp = NULL; + int e, size, outSize; + Tcl_Obj *obj; + + if (zshPtr->streamEnd) { + if (zshPtr->interp) { + Tcl_SetObjResult(zshPtr->interp, Tcl_NewStringObj( + "already past compressed stream end", -1)); + Tcl_SetErrorCode(zshPtr->interp, "TCL", "ZIP", "CLOSED", NULL); + } + return TCL_ERROR; + } + + if (zshPtr->mode == TCL_ZLIB_STREAM_DEFLATE) { + zshPtr->stream.next_in = Tcl_GetByteArrayFromObj(data, &size); + zshPtr->stream.avail_in = size; + + if (HaveDictToSet(zshPtr)) { + e = SetDeflateDictionary(&zshPtr->stream, zshPtr->compDictObj); + if (e != Z_OK) { + if (zshPtr->interp) { + ConvertError(zshPtr->interp, e, zshPtr->stream.adler); + } + return TCL_ERROR; + } + DictWasSet(zshPtr); + } + + /* + * Deflatebound doesn't seem to take various header sizes into + * account, so we add 100 extra bytes. + */ + + outSize = deflateBound(&zshPtr->stream, zshPtr->stream.avail_in)+100; + zshPtr->stream.avail_out = outSize; + dataTmp = ckalloc(zshPtr->stream.avail_out); + zshPtr->stream.next_out = (Bytef *) dataTmp; + + e = deflate(&zshPtr->stream, flush); + if ((e==Z_OK || e==Z_BUF_ERROR) && (zshPtr->stream.avail_out == 0)) { + if (outSize - zshPtr->stream.avail_out > 0) { + /* + * Output buffer too small. + */ + + obj = Tcl_NewByteArrayObj((unsigned char *) dataTmp, + outSize - zshPtr->stream.avail_out); + + /* + * Now append the compressed data to the outData list. + */ + + Tcl_ListObjAppendElement(NULL, zshPtr->outData, obj); + } + if (outSize < 0xFFFF) { + outSize = 0xFFFF; /* There may be *lots* of data left to + * output... */ + ckfree(dataTmp); + dataTmp = ckalloc(outSize); + } + zshPtr->stream.avail_out = outSize; + zshPtr->stream.next_out = (Bytef *) dataTmp; + + e = deflate(&zshPtr->stream, flush); + } + if (e != Z_OK && !(flush==Z_FINISH && e==Z_STREAM_END)) { + if (zshPtr->interp) { + ConvertError(zshPtr->interp, e, zshPtr->stream.adler); + } + return TCL_ERROR; + } + + /* + * And append the final data block. + */ + + if (outSize - zshPtr->stream.avail_out > 0) { + obj = Tcl_NewByteArrayObj((unsigned char *) dataTmp, + outSize - zshPtr->stream.avail_out); + + /* + * Now append the compressed data to the outData list. + */ + + Tcl_ListObjAppendElement(NULL, zshPtr->outData, obj); + } + + if (dataTmp) { + ckfree(dataTmp); + } + } else { + /* + * This is easy. Just append to the inData list. + */ + + Tcl_ListObjAppendElement(NULL, zshPtr->inData, data); + + /* + * and we'll need the flush parameter for the Inflate call. + */ + + zshPtr->flush = flush; + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_ZlibStreamGet -- + * + * Retrieve data (now compressed or decompressed) from the stream into a + * bytearray Tcl_Obj. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_ZlibStreamGet( + Tcl_ZlibStream zshandle, /* As obtained from Tcl_ZlibStreamInit */ + Tcl_Obj *data, /* A place to append the data. */ + int count) /* Number of bytes to grab as a maximum, you + * may get less! */ +{ + ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) zshandle; + int e, i, listLen, itemLen, dataPos = 0; + Tcl_Obj *itemObj; + unsigned char *dataPtr, *itemPtr; + int existing; + + /* + * Getting beyond the of stream, just return empty string. + */ + + if (zshPtr->streamEnd) { + return TCL_OK; + } + + (void) Tcl_GetByteArrayFromObj(data, &existing); + + if (zshPtr->mode == TCL_ZLIB_STREAM_INFLATE) { + if (count == -1) { + /* + * The only safe thing to do is restict to 65k. We might cause a + * panic for out of memory if we just kept growing the buffer. + */ + + count = MAX_BUFFER_SIZE; + } + + /* + * Prepare the place to store the data. + */ + + dataPtr = Tcl_SetByteArrayLength(data, existing+count); + dataPtr += existing; + + zshPtr->stream.next_out = dataPtr; + zshPtr->stream.avail_out = count; + if (zshPtr->stream.avail_in == 0) { + /* + * zlib will probably need more data to decompress. + */ + + if (zshPtr->currentInput) { + Tcl_DecrRefCount(zshPtr->currentInput); + zshPtr->currentInput = NULL; + } + Tcl_ListObjLength(NULL, zshPtr->inData, &listLen); + if (listLen > 0) { + /* + * There is more input available, get it from the list and + * give it to zlib. At this point, the data must not be shared + * since we require the bytearray representation to not vanish + * under our feet. [Bug 3081008] + */ + + Tcl_ListObjIndex(NULL, zshPtr->inData, 0, &itemObj); + if (Tcl_IsShared(itemObj)) { + itemObj = Tcl_DuplicateObj(itemObj); + } + itemPtr = Tcl_GetByteArrayFromObj(itemObj, &itemLen); + Tcl_IncrRefCount(itemObj); + zshPtr->currentInput = itemObj; + zshPtr->stream.next_in = itemPtr; + zshPtr->stream.avail_in = itemLen; + + /* + * And remove it from the list + */ + + Tcl_ListObjReplace(NULL, zshPtr->inData, 0, 1, 0, NULL); + } + } + + /* + * When dealing with a raw stream, we set the dictionary here, once. + * (You can't do it in response to getting Z_NEED_DATA as raw streams + * don't ever issue that.) + */ + + if (IsRawStream(zshPtr) && HaveDictToSet(zshPtr)) { + e = SetInflateDictionary(&zshPtr->stream, zshPtr->compDictObj); + if (e != Z_OK) { + if (zshPtr->interp) { + ConvertError(zshPtr->interp, e, zshPtr->stream.adler); + } + return TCL_ERROR; + } + DictWasSet(zshPtr); + } + e = inflate(&zshPtr->stream, zshPtr->flush); + if (e == Z_NEED_DICT && HaveDictToSet(zshPtr)) { + e = SetInflateDictionary(&zshPtr->stream, zshPtr->compDictObj); + if (e == Z_OK) { + DictWasSet(zshPtr); + e = inflate(&zshPtr->stream, zshPtr->flush); + } + }; + Tcl_ListObjLength(NULL, zshPtr->inData, &listLen); + + while ((zshPtr->stream.avail_out > 0) + && (e == Z_OK || e == Z_BUF_ERROR) && (listLen > 0)) { + /* + * State: We have not satisfied the request yet and there may be + * more to inflate. + */ + + if (zshPtr->stream.avail_in > 0) { + if (zshPtr->interp) { + Tcl_SetObjResult(zshPtr->interp, Tcl_NewStringObj( + "unexpected zlib internal state during" + " decompression", -1)); + Tcl_SetErrorCode(zshPtr->interp, "TCL", "ZIP", "STATE", + NULL); + } + Tcl_SetByteArrayLength(data, existing); + return TCL_ERROR; + } + + if (zshPtr->currentInput) { + Tcl_DecrRefCount(zshPtr->currentInput); + zshPtr->currentInput = 0; + } + + /* + * Get the next block of data to go to inflate. At this point, the + * data must not be shared since we require the bytearray + * representation to not vanish under our feet. [Bug 3081008] + */ + + Tcl_ListObjIndex(zshPtr->interp, zshPtr->inData, 0, &itemObj); + if (Tcl_IsShared(itemObj)) { + itemObj = Tcl_DuplicateObj(itemObj); + } + itemPtr = Tcl_GetByteArrayFromObj(itemObj, &itemLen); + Tcl_IncrRefCount(itemObj); + zshPtr->currentInput = itemObj; + zshPtr->stream.next_in = itemPtr; + zshPtr->stream.avail_in = itemLen; + + /* + * Remove it from the list. + */ + + Tcl_ListObjReplace(NULL, zshPtr->inData, 0, 1, 0, NULL); + listLen--; + + /* + * And call inflate again. + */ + + do { + e = inflate(&zshPtr->stream, zshPtr->flush); + if (e != Z_NEED_DICT || !HaveDictToSet(zshPtr)) { + break; + } + e = SetInflateDictionary(&zshPtr->stream,zshPtr->compDictObj); + DictWasSet(zshPtr); + } while (e == Z_OK); + } + if (zshPtr->stream.avail_out > 0) { + Tcl_SetByteArrayLength(data, + existing + count - zshPtr->stream.avail_out); + } + if (!(e==Z_OK || e==Z_STREAM_END || e==Z_BUF_ERROR)) { + Tcl_SetByteArrayLength(data, existing); + ConvertError(zshPtr->interp, e, zshPtr->stream.adler); + return TCL_ERROR; + } + if (e == Z_STREAM_END) { + zshPtr->streamEnd = 1; + if (zshPtr->currentInput) { + Tcl_DecrRefCount(zshPtr->currentInput); + zshPtr->currentInput = 0; + } + inflateEnd(&zshPtr->stream); + } + } else { + Tcl_ListObjLength(NULL, zshPtr->outData, &listLen); + if (count == -1) { + count = 0; + for (i=0; i<listLen; i++) { + Tcl_ListObjIndex(NULL, zshPtr->outData, i, &itemObj); + itemPtr = Tcl_GetByteArrayFromObj(itemObj, &itemLen); + if (i == 0) { + count += itemLen - zshPtr->outPos; + } else { + count += itemLen; + } + } + } + + /* + * Prepare the place to store the data. + */ + + dataPtr = Tcl_SetByteArrayLength(data, existing + count); + dataPtr += existing; + + while ((count > dataPos) && + (Tcl_ListObjLength(NULL, zshPtr->outData, &listLen) == TCL_OK) + && (listLen > 0)) { + /* + * Get the next chunk off our list of chunks and grab the data out + * of it. + */ + + Tcl_ListObjIndex(NULL, zshPtr->outData, 0, &itemObj); + itemPtr = Tcl_GetByteArrayFromObj(itemObj, &itemLen); + if (itemLen-zshPtr->outPos >= count-dataPos) { + unsigned len = count - dataPos; + + memcpy(dataPtr + dataPos, itemPtr + zshPtr->outPos, len); + zshPtr->outPos += len; + dataPos += len; + if (zshPtr->outPos == itemLen) { + zshPtr->outPos = 0; + } + } else { + unsigned len = itemLen - zshPtr->outPos; + + memcpy(dataPtr + dataPos, itemPtr + zshPtr->outPos, len); + dataPos += len; + zshPtr->outPos = 0; + } + if (zshPtr->outPos == 0) { + Tcl_ListObjReplace(NULL, zshPtr->outData, 0, 1, 0, NULL); + listLen--; + } + } + Tcl_SetByteArrayLength(data, existing + dataPos); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_ZlibDeflate -- + * + * Compress the contents of Tcl_Obj *data with compression level in + * output format, producing the compressed data in the interpreter + * result. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_ZlibDeflate( + Tcl_Interp *interp, + int format, + Tcl_Obj *data, + int level, + Tcl_Obj *gzipHeaderDictObj) +{ + int wbits = 0, inLen = 0, e = 0, extraSize = 0; + Byte *inData = NULL; + z_stream stream; + GzipHeader header; + gz_header *headerPtr = NULL; + Tcl_Obj *obj; + + if (!interp) { + return TCL_ERROR; + } + + /* + * Compressed format is specified by the wbits parameter. See zlib.h for + * details. + */ + + if (format == TCL_ZLIB_FORMAT_RAW) { + wbits = WBITS_RAW; + } else if (format == TCL_ZLIB_FORMAT_GZIP) { + wbits = WBITS_GZIP; + + /* + * Need to allocate extra space for the gzip header and footer. The + * amount of space is (a bit less than) 32 bytes, plus a byte for each + * byte of string that we add. Note that over-allocation is not a + * problem. [Bug 2419061] + */ + + extraSize = 32; + if (gzipHeaderDictObj) { + headerPtr = &header.header; + memset(headerPtr, 0, sizeof(gz_header)); + if (GenerateHeader(interp, gzipHeaderDictObj, &header, + &extraSize) != TCL_OK) { + return TCL_ERROR; + } + } + } else if (format == TCL_ZLIB_FORMAT_ZLIB) { + wbits = WBITS_ZLIB; + } else { + Tcl_Panic("incorrect zlib data format, must be TCL_ZLIB_FORMAT_ZLIB, " + "TCL_ZLIB_FORMAT_GZIP or TCL_ZLIB_FORMAT_ZLIB"); + } + + if (level < -1 || level > 9) { + Tcl_Panic("compression level should be between 0 (uncompressed) and " + "9 (best compression) or -1 for default compression level"); + } + + /* + * Allocate some space to store the output. + */ + + TclNewObj(obj); + + /* + * Obtain the pointer to the byte array, we'll pass this pointer straight + * to the deflate command. + */ + + inData = Tcl_GetByteArrayFromObj(data, &inLen); + memset(&stream, 0, sizeof(z_stream)); + stream.avail_in = (uInt) inLen; + stream.next_in = inData; + + /* + * No output buffer available yet, will alloc after deflateInit2. + */ + + e = deflateInit2(&stream, level, Z_DEFLATED, wbits, MAX_MEM_LEVEL, + Z_DEFAULT_STRATEGY); + if (e != Z_OK) { + goto error; + } + + if (headerPtr != NULL) { + e = deflateSetHeader(&stream, headerPtr); + if (e != Z_OK) { + goto error; + } + } + + /* + * Allocate the output buffer from the value of deflateBound(). This is + * probably too much space. Before returning to the caller, we will reduce + * it back to the actual compressed size. + */ + + stream.avail_out = deflateBound(&stream, inLen) + extraSize; + stream.next_out = Tcl_SetByteArrayLength(obj, stream.avail_out); + + /* + * Perform the compression, Z_FINISH means do it in one go. + */ + + e = deflate(&stream, Z_FINISH); + + if (e != Z_STREAM_END) { + e = deflateEnd(&stream); + + /* + * deflateEnd() returns Z_OK when there are bytes left to compress, at + * this point we consider that an error, although we could continue by + * allocating more memory and calling deflate() again. + */ + + if (e == Z_OK) { + e = Z_BUF_ERROR; + } + } else { + e = deflateEnd(&stream); + } + + if (e != Z_OK) { + goto error; + } + + /* + * Reduce the bytearray length to the actual data length produced by + * deflate. + */ + + Tcl_SetByteArrayLength(obj, stream.total_out); + Tcl_SetObjResult(interp, obj); + return TCL_OK; + + error: + ConvertError(interp, e, stream.adler); + TclDecrRefCount(obj); + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_ZlibInflate -- + * + * Decompress data in an object into the interpreter result. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_ZlibInflate( + Tcl_Interp *interp, + int format, + Tcl_Obj *data, + int bufferSize, + Tcl_Obj *gzipHeaderDictObj) +{ + int wbits = 0, inLen = 0, e = 0, newBufferSize; + Byte *inData = NULL, *outData = NULL, *newOutData = NULL; + z_stream stream; + gz_header header, *headerPtr = NULL; + Tcl_Obj *obj; + char *nameBuf = NULL, *commentBuf = NULL; + + if (!interp) { + return TCL_ERROR; + } + + /* + * Compressed format is specified by the wbits parameter. See zlib.h for + * details. + */ + + switch (format) { + case TCL_ZLIB_FORMAT_RAW: + wbits = WBITS_RAW; + gzipHeaderDictObj = NULL; + break; + case TCL_ZLIB_FORMAT_ZLIB: + wbits = WBITS_ZLIB; + gzipHeaderDictObj = NULL; + break; + case TCL_ZLIB_FORMAT_GZIP: + wbits = WBITS_GZIP; + break; + case TCL_ZLIB_FORMAT_AUTO: + wbits = WBITS_AUTODETECT; + break; + default: + Tcl_Panic("incorrect zlib data format, must be TCL_ZLIB_FORMAT_ZLIB, " + "TCL_ZLIB_FORMAT_GZIP, TCL_ZLIB_FORMAT_RAW or " + "TCL_ZLIB_FORMAT_AUTO"); + } + + if (gzipHeaderDictObj) { + headerPtr = &header; + memset(headerPtr, 0, sizeof(gz_header)); + nameBuf = ckalloc(MAXPATHLEN); + header.name = (Bytef *) nameBuf; + header.name_max = MAXPATHLEN - 1; + commentBuf = ckalloc(MAX_COMMENT_LEN); + header.comment = (Bytef *) commentBuf; + header.comm_max = MAX_COMMENT_LEN - 1; + } + + inData = Tcl_GetByteArrayFromObj(data, &inLen); + if (bufferSize < 1) { + /* + * Start with a buffer (up to) 3 times the size of the input data. + */ + + if (inLen < 32*1024*1024) { + bufferSize = 3*inLen; + } else if (inLen < 256*1024*1024) { + bufferSize = 2*inLen; + } else { + bufferSize = inLen; + } + } + + TclNewObj(obj); + outData = Tcl_SetByteArrayLength(obj, bufferSize); + memset(&stream, 0, sizeof(z_stream)); + stream.avail_in = (uInt) inLen+1; /* +1 because zlib can "over-request" + * input (but ignore it!) */ + stream.next_in = inData; + stream.avail_out = bufferSize; + stream.next_out = outData; + + /* + * Initialize zlib for decompression. + */ + + e = inflateInit2(&stream, wbits); + if (e != Z_OK) { + goto error; + } + if (headerPtr) { + e = inflateGetHeader(&stream, headerPtr); + if (e != Z_OK) { + goto error; + } + } + + /* + * Start the decompression cycle. + */ + + while (1) { + e = inflate(&stream, Z_FINISH); + if (e != Z_BUF_ERROR) { + break; + } + + /* + * Not enough room in the output buffer. Increase it by five times the + * bytes still in the input buffer. (Because 3 times didn't do the + * trick before, 5 times is what we do next.) Further optimization + * should be done by the user, specify the decompressed size! + */ + + if ((stream.avail_in == 0) && (stream.avail_out > 0)) { + e = Z_STREAM_ERROR; + goto error; + } + newBufferSize = bufferSize + 5 * stream.avail_in; + if (newBufferSize == bufferSize) { + newBufferSize = bufferSize+1000; + } + newOutData = Tcl_SetByteArrayLength(obj, newBufferSize); + + /* + * Set next out to the same offset in the new location. + */ + + stream.next_out = newOutData + stream.total_out; + + /* + * And increase avail_out with the number of new bytes allocated. + */ + + stream.avail_out += newBufferSize - bufferSize; + outData = newOutData; + bufferSize = newBufferSize; + } + + if (e != Z_STREAM_END) { + inflateEnd(&stream); + goto error; + } + + e = inflateEnd(&stream); + if (e != Z_OK) { + goto error; + } + + /* + * Reduce the BA length to the actual data length produced by deflate. + */ + + Tcl_SetByteArrayLength(obj, stream.total_out); + if (headerPtr != NULL) { + ExtractHeader(&header, gzipHeaderDictObj); + SetValue(gzipHeaderDictObj, "size", + Tcl_NewLongObj((long) stream.total_out)); + ckfree(nameBuf); + ckfree(commentBuf); + } + Tcl_SetObjResult(interp, obj); + return TCL_OK; + + error: + TclDecrRefCount(obj); + ConvertError(interp, e, stream.adler); + if (nameBuf) { + ckfree(nameBuf); + } + if (commentBuf) { + ckfree(commentBuf); + } + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_ZlibCRC32, Tcl_ZlibAdler32 -- + * + * Access to the checksumming engines. + * + *---------------------------------------------------------------------- + */ + +unsigned int +Tcl_ZlibCRC32( + unsigned int crc, + const unsigned char *buf, + int len) +{ + /* Nothing much to do, just wrap the crc32(). */ + return crc32(crc, (Bytef *) buf, (unsigned) len); +} + +unsigned int +Tcl_ZlibAdler32( + unsigned int adler, + const unsigned char *buf, + int len) +{ + return adler32(adler, (Bytef *) buf, (unsigned) len); +} + +/* + *---------------------------------------------------------------------- + * + * ZlibCmd -- + * + * Implementation of the [zlib] command. + * + *---------------------------------------------------------------------- + */ + +static int +ZlibCmd( + ClientData notUsed, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + int command, dlen, i, option, level = -1; + unsigned start, buffersize = 0; + Byte *data; + Tcl_Obj *headerDictObj; + const char *extraInfoStr = NULL; + static const char *const commands[] = { + "adler32", "compress", "crc32", "decompress", "deflate", "gunzip", + "gzip", "inflate", "push", "stream", + NULL + }; + enum zlibCommands { + CMD_ADLER, CMD_COMPRESS, CMD_CRC, CMD_DECOMPRESS, CMD_DEFLATE, + CMD_GUNZIP, CMD_GZIP, CMD_INFLATE, CMD_PUSH, CMD_STREAM + }; + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "command arg ?...?"); + return TCL_ERROR; + } + if (Tcl_GetIndexFromObj(interp, objv[1], commands, "command", 0, + &command) != TCL_OK) { + return TCL_ERROR; + } + + switch ((enum zlibCommands) command) { + case CMD_ADLER: /* adler32 str ?startvalue? + * -> checksum */ + if (objc < 3 || objc > 4) { + Tcl_WrongNumArgs(interp, 2, objv, "data ?startValue?"); + return TCL_ERROR; + } + if (objc>3 && Tcl_GetIntFromObj(interp, objv[3], + (int *) &start) != TCL_OK) { + return TCL_ERROR; + } + if (objc < 4) { + start = Tcl_ZlibAdler32(0, NULL, 0); + } + data = Tcl_GetByteArrayFromObj(objv[2], &dlen); + Tcl_SetObjResult(interp, Tcl_NewWideIntObj((Tcl_WideInt) + (uLong) Tcl_ZlibAdler32(start, data, dlen))); + return TCL_OK; + case CMD_CRC: /* crc32 str ?startvalue? + * -> checksum */ + if (objc < 3 || objc > 4) { + Tcl_WrongNumArgs(interp, 2, objv, "data ?startValue?"); + return TCL_ERROR; + } + if (objc>3 && Tcl_GetIntFromObj(interp, objv[3], + (int *) &start) != TCL_OK) { + return TCL_ERROR; + } + if (objc < 4) { + start = Tcl_ZlibCRC32(0, NULL, 0); + } + data = Tcl_GetByteArrayFromObj(objv[2], &dlen); + Tcl_SetObjResult(interp, Tcl_NewWideIntObj((Tcl_WideInt) + (uLong) Tcl_ZlibCRC32(start, data, dlen))); + return TCL_OK; + case CMD_DEFLATE: /* deflate data ?level? + * -> rawCompressedData */ + if (objc < 3 || objc > 4) { + Tcl_WrongNumArgs(interp, 2, objv, "data ?level?"); + return TCL_ERROR; + } + if (objc > 3) { + if (Tcl_GetIntFromObj(interp, objv[3], &level) != TCL_OK) { + return TCL_ERROR; + } + if (level < 0 || level > 9) { + goto badLevel; + } + } + return Tcl_ZlibDeflate(interp, TCL_ZLIB_FORMAT_RAW, objv[2], level, + NULL); + case CMD_COMPRESS: /* compress data ?level? + * -> zlibCompressedData */ + if (objc < 3 || objc > 4) { + Tcl_WrongNumArgs(interp, 2, objv, "data ?level?"); + return TCL_ERROR; + } + if (objc > 3) { + if (Tcl_GetIntFromObj(interp, objv[3], &level) != TCL_OK) { + return TCL_ERROR; + } + if (level < 0 || level > 9) { + goto badLevel; + } + } + return Tcl_ZlibDeflate(interp, TCL_ZLIB_FORMAT_ZLIB, objv[2], level, + NULL); + case CMD_GZIP: /* gzip data ?level? + * -> gzippedCompressedData */ + headerDictObj = NULL; + + /* + * Legacy argument format support. + */ + + if (objc == 4 + && Tcl_GetIntFromObj(interp, objv[3], &level) == TCL_OK) { + if (level < 0 || level > 9) { + extraInfoStr = "\n (in -level option)"; + goto badLevel; + } + return Tcl_ZlibDeflate(interp, TCL_ZLIB_FORMAT_GZIP, objv[2], + level, NULL); + } + + if (objc < 3 || objc > 7 || ((objc & 1) == 0)) { + Tcl_WrongNumArgs(interp, 2, objv, + "data ?-level level? ?-header header?"); + return TCL_ERROR; + } + for (i=3 ; i<objc ; i+=2) { + static const char *const gzipopts[] = { + "-header", "-level", NULL + }; + + if (Tcl_GetIndexFromObj(interp, objv[i], gzipopts, "option", 0, + &option) != TCL_OK) { + return TCL_ERROR; + } + switch (option) { + case 0: + headerDictObj = objv[i+1]; + break; + case 1: + if (Tcl_GetIntFromObj(interp, objv[i+1], + &level) != TCL_OK) { + return TCL_ERROR; + } + if (level < 0 || level > 9) { + extraInfoStr = "\n (in -level option)"; + goto badLevel; + } + break; + } + } + return Tcl_ZlibDeflate(interp, TCL_ZLIB_FORMAT_GZIP, objv[2], level, + headerDictObj); + case CMD_INFLATE: /* inflate rawcomprdata ?bufferSize? + * -> decompressedData */ + if (objc < 3 || objc > 4) { + Tcl_WrongNumArgs(interp, 2, objv, "data ?bufferSize?"); + return TCL_ERROR; + } + if (objc > 3) { + if (Tcl_GetIntFromObj(interp, objv[3], + (int *) &buffersize) != TCL_OK) { + return TCL_ERROR; + } + if (buffersize < MIN_NONSTREAM_BUFFER_SIZE + || buffersize > MAX_BUFFER_SIZE) { + goto badBuffer; + } + } + return Tcl_ZlibInflate(interp, TCL_ZLIB_FORMAT_RAW, objv[2], + buffersize, NULL); + case CMD_DECOMPRESS: /* decompress zlibcomprdata \ + * ?bufferSize? + * -> decompressedData */ + if (objc < 3 || objc > 4) { + Tcl_WrongNumArgs(interp, 2, objv, "data ?bufferSize?"); + return TCL_ERROR; + } + if (objc > 3) { + if (Tcl_GetIntFromObj(interp, objv[3], + (int *) &buffersize) != TCL_OK) { + return TCL_ERROR; + } + if (buffersize < MIN_NONSTREAM_BUFFER_SIZE + || buffersize > MAX_BUFFER_SIZE) { + goto badBuffer; + } + } + return Tcl_ZlibInflate(interp, TCL_ZLIB_FORMAT_ZLIB, objv[2], + buffersize, NULL); + case CMD_GUNZIP: { /* gunzip gzippeddata ?bufferSize? + * -> decompressedData */ + Tcl_Obj *headerVarObj; + + if (objc < 3 || objc > 5 || ((objc & 1) == 0)) { + Tcl_WrongNumArgs(interp, 2, objv, "data ?-headerVar varName?"); + return TCL_ERROR; + } + headerDictObj = headerVarObj = NULL; + for (i=3 ; i<objc ; i+=2) { + static const char *const gunzipopts[] = { + "-buffersize", "-headerVar", NULL + }; + + if (Tcl_GetIndexFromObj(interp, objv[i], gunzipopts, "option", 0, + &option) != TCL_OK) { + return TCL_ERROR; + } + switch (option) { + case 0: + if (Tcl_GetIntFromObj(interp, objv[i+1], + (int *) &buffersize) != TCL_OK) { + return TCL_ERROR; + } + if (buffersize < MIN_NONSTREAM_BUFFER_SIZE + || buffersize > MAX_BUFFER_SIZE) { + goto badBuffer; + } + break; + case 1: + headerVarObj = objv[i+1]; + headerDictObj = Tcl_NewObj(); + break; + } + } + if (Tcl_ZlibInflate(interp, TCL_ZLIB_FORMAT_GZIP, objv[2], + buffersize, headerDictObj) != TCL_OK) { + if (headerDictObj) { + TclDecrRefCount(headerDictObj); + } + return TCL_ERROR; + } + if (headerVarObj != NULL && Tcl_ObjSetVar2(interp, headerVarObj, NULL, + headerDictObj, TCL_LEAVE_ERR_MSG) == NULL) { + return TCL_ERROR; + } + return TCL_OK; + } + case CMD_STREAM: /* stream deflate/inflate/...gunzip \ + * ?options...? + * -> handleCmd */ + return ZlibStreamSubcmd(interp, objc, objv); + case CMD_PUSH: /* push mode channel options... + * -> channel */ + return ZlibPushSubcmd(interp, objc, objv); + }; + + return TCL_ERROR; + + badLevel: + Tcl_SetObjResult(interp, Tcl_NewStringObj("level must be 0 to 9", -1)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "COMPRESSIONLEVEL", NULL); + if (extraInfoStr) { + Tcl_AddErrorInfo(interp, extraInfoStr); + } + return TCL_ERROR; + badBuffer: + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "buffer size must be %d to %d", + MIN_NONSTREAM_BUFFER_SIZE, MAX_BUFFER_SIZE)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "BUFFERSIZE", NULL); + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * ZlibStreamSubcmd -- + * + * Implementation of the [zlib stream] subcommand. + * + *---------------------------------------------------------------------- + */ + +static int +ZlibStreamSubcmd( + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + static const char *const stream_formats[] = { + "compress", "decompress", "deflate", "gunzip", "gzip", "inflate", + NULL + }; + enum zlibFormats { + FMT_COMPRESS, FMT_DECOMPRESS, FMT_DEFLATE, FMT_GUNZIP, FMT_GZIP, + FMT_INFLATE + }; + int i, format, mode = 0, option, level; + enum objIndices { + OPT_COMPRESSION_DICTIONARY = 0, + OPT_GZIP_HEADER = 1, + OPT_COMPRESSION_LEVEL = 2, + OPT_END = -1 + }; + Tcl_Obj *obj[3] = { NULL, NULL, NULL }; +#define compDictObj obj[OPT_COMPRESSION_DICTIONARY] +#define gzipHeaderObj obj[OPT_GZIP_HEADER] +#define levelObj obj[OPT_COMPRESSION_LEVEL] + typedef struct { + const char *name; + enum objIndices offset; + } OptDescriptor; + static const OptDescriptor compressionOpts[] = { + { "-dictionary", OPT_COMPRESSION_DICTIONARY }, + { "-level", OPT_COMPRESSION_LEVEL }, + { NULL, OPT_END } + }; + static const OptDescriptor gzipOpts[] = { + { "-header", OPT_GZIP_HEADER }, + { "-level", OPT_COMPRESSION_LEVEL }, + { NULL, OPT_END } + }; + static const OptDescriptor expansionOpts[] = { + { "-dictionary", OPT_COMPRESSION_DICTIONARY }, + { NULL, OPT_END } + }; + static const OptDescriptor gunzipOpts[] = { + { NULL, OPT_END } + }; + const OptDescriptor *desc = NULL; + Tcl_ZlibStream zh; + + if (objc < 3 || !(objc & 1)) { + Tcl_WrongNumArgs(interp, 2, objv, "mode ?-option value...?"); + return TCL_ERROR; + } + if (Tcl_GetIndexFromObj(interp, objv[2], stream_formats, "mode", 0, + &format) != TCL_OK) { + return TCL_ERROR; + } + + /* + * The format determines the compression mode and the options that may be + * specified. + */ + + switch ((enum zlibFormats) format) { + case FMT_DEFLATE: + desc = compressionOpts; + mode = TCL_ZLIB_STREAM_DEFLATE; + format = TCL_ZLIB_FORMAT_RAW; + break; + case FMT_INFLATE: + desc = expansionOpts; + mode = TCL_ZLIB_STREAM_INFLATE; + format = TCL_ZLIB_FORMAT_RAW; + break; + case FMT_COMPRESS: + desc = compressionOpts; + mode = TCL_ZLIB_STREAM_DEFLATE; + format = TCL_ZLIB_FORMAT_ZLIB; + break; + case FMT_DECOMPRESS: + desc = expansionOpts; + mode = TCL_ZLIB_STREAM_INFLATE; + format = TCL_ZLIB_FORMAT_ZLIB; + break; + case FMT_GZIP: + desc = gzipOpts; + mode = TCL_ZLIB_STREAM_DEFLATE; + format = TCL_ZLIB_FORMAT_GZIP; + break; + case FMT_GUNZIP: + desc = gunzipOpts; + mode = TCL_ZLIB_STREAM_INFLATE; + format = TCL_ZLIB_FORMAT_GZIP; + break; + default: + Tcl_Panic("should be unreachable"); + } + + /* + * Parse the options. + */ + + for (i=3 ; i<objc ; i+=2) { + if (Tcl_GetIndexFromObjStruct(interp, objv[i], desc, + sizeof(OptDescriptor), "option", 0, &option) != TCL_OK) { + return TCL_ERROR; + } + obj[desc[option].offset] = objv[i+1]; + } + + /* + * If a compression level was given, parse it (integral: 0..9). Otherwise + * use the default. + */ + + if (levelObj == NULL) { + level = Z_DEFAULT_COMPRESSION; + } else if (Tcl_GetIntFromObj(interp, levelObj, &level) != TCL_OK) { + return TCL_ERROR; + } else if (level < 0 || level > 9) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("level must be 0 to 9",-1)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "COMPRESSIONLEVEL", NULL); + Tcl_AddErrorInfo(interp, "\n (in -level option)"); + return TCL_ERROR; + } + + /* + * Construct the stream now we know its configuration. + */ + + if (Tcl_ZlibStreamInit(interp, mode, format, level, gzipHeaderObj, + &zh) != TCL_OK) { + return TCL_ERROR; + } + if (compDictObj != NULL) { + Tcl_ZlibStreamSetCompressionDictionary(zh, compDictObj); + } + Tcl_SetObjResult(interp, Tcl_ZlibStreamGetCommandName(zh)); + return TCL_OK; +#undef compDictObj +#undef gzipHeaderObj +#undef levelObj +} + +/* + *---------------------------------------------------------------------- + * + * ZlibPushSubcmd -- + * + * Implementation of the [zlib push] subcommand. + * + *---------------------------------------------------------------------- + */ + +static int +ZlibPushSubcmd( + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + static const char *const stream_formats[] = { + "compress", "decompress", "deflate", "gunzip", "gzip", "inflate", + NULL + }; + enum zlibFormats { + FMT_COMPRESS, FMT_DECOMPRESS, FMT_DEFLATE, FMT_GUNZIP, FMT_GZIP, + FMT_INFLATE + }; + Tcl_Channel chan; + int chanMode, format, mode = 0, level, i, option; + static const char *const pushCompressOptions[] = { + "-dictionary", "-header", "-level", NULL + }; + static const char *const pushDecompressOptions[] = { + "-dictionary", "-header", "-level", "-limit", NULL + }; + const char *const *pushOptions = pushDecompressOptions; + enum pushOptions {poDictionary, poHeader, poLevel, poLimit}; + Tcl_Obj *headerObj = NULL, *compDictObj = NULL; + int limit = 1, dummy; + + if (objc < 4) { + Tcl_WrongNumArgs(interp, 2, objv, "mode channel ?options...?"); + return TCL_ERROR; + } + + if (Tcl_GetIndexFromObj(interp, objv[2], stream_formats, "mode", 0, + &format) != TCL_OK) { + return TCL_ERROR; + } + switch ((enum zlibFormats) format) { + case FMT_DEFLATE: + mode = TCL_ZLIB_STREAM_DEFLATE; + format = TCL_ZLIB_FORMAT_RAW; + pushOptions = pushCompressOptions; + break; + case FMT_INFLATE: + mode = TCL_ZLIB_STREAM_INFLATE; + format = TCL_ZLIB_FORMAT_RAW; + break; + case FMT_COMPRESS: + mode = TCL_ZLIB_STREAM_DEFLATE; + format = TCL_ZLIB_FORMAT_ZLIB; + pushOptions = pushCompressOptions; + break; + case FMT_DECOMPRESS: + mode = TCL_ZLIB_STREAM_INFLATE; + format = TCL_ZLIB_FORMAT_ZLIB; + break; + case FMT_GZIP: + mode = TCL_ZLIB_STREAM_DEFLATE; + format = TCL_ZLIB_FORMAT_GZIP; + pushOptions = pushCompressOptions; + break; + case FMT_GUNZIP: + mode = TCL_ZLIB_STREAM_INFLATE; + format = TCL_ZLIB_FORMAT_GZIP; + break; + default: + Tcl_Panic("should be unreachable"); + } + + if (TclGetChannelFromObj(interp, objv[3], &chan, &chanMode, 0) != TCL_OK){ + return TCL_ERROR; + } + + /* + * Sanity checks. + */ + + if (mode == TCL_ZLIB_STREAM_DEFLATE && !(chanMode & TCL_WRITABLE)) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "compression may only be applied to writable channels", -1)); + Tcl_SetErrorCode(interp, "TCL", "ZIP", "UNWRITABLE", NULL); + return TCL_ERROR; + } + if (mode == TCL_ZLIB_STREAM_INFLATE && !(chanMode & TCL_READABLE)) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "decompression may only be applied to readable channels",-1)); + Tcl_SetErrorCode(interp, "TCL", "ZIP", "UNREADABLE", NULL); + return TCL_ERROR; + } + + /* + * Parse options. + */ + + level = Z_DEFAULT_COMPRESSION; + for (i=4 ; i<objc ; i++) { + if (Tcl_GetIndexFromObj(interp, objv[i], pushOptions, "option", 0, + &option) != TCL_OK) { + return TCL_ERROR; + } + if (++i > objc-1) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "value missing for %s option", pushOptions[option])); + Tcl_SetErrorCode(interp, "TCL", "ZIP", "NOVAL", NULL); + return TCL_ERROR; + } + switch ((enum pushOptions) option) { + case poHeader: + headerObj = objv[i]; + if (Tcl_DictObjSize(interp, headerObj, &dummy) != TCL_OK) { + goto genericOptionError; + } + break; + case poLevel: + if (Tcl_GetIntFromObj(interp, objv[i], (int*) &level) != TCL_OK) { + goto genericOptionError; + } + if (level < 0 || level > 9) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "level must be 0 to 9", -1)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "COMPRESSIONLEVEL", + NULL); + goto genericOptionError; + } + break; + case poLimit: + if (Tcl_GetIntFromObj(interp, objv[i], (int*) &limit) != TCL_OK) { + goto genericOptionError; + } + if (limit < 1 || limit > MAX_BUFFER_SIZE) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "read ahead limit must be 1 to %d", + MAX_BUFFER_SIZE)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "BUFFERSIZE", NULL); + goto genericOptionError; + } + break; + case poDictionary: + if (format == TCL_ZLIB_FORMAT_GZIP) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "a compression dictionary may not be set in the " + "gzip format", -1)); + Tcl_SetErrorCode(interp, "TCL", "ZIP", "BADOPT", NULL); + goto genericOptionError; + } + compDictObj = objv[i]; + break; + } + } + + if (ZlibStackChannelTransform(interp, mode, format, level, limit, chan, + headerObj, compDictObj) == NULL) { + return TCL_ERROR; + } + Tcl_SetObjResult(interp, objv[3]); + return TCL_OK; + + genericOptionError: + Tcl_AddErrorInfo(interp, "\n (in "); + Tcl_AddErrorInfo(interp, pushOptions[option]); + Tcl_AddErrorInfo(interp, " option)"); + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * ZlibStreamCmd -- + * + * Implementation of the commands returned by [zlib stream]. + * + *---------------------------------------------------------------------- + */ + +static int +ZlibStreamCmd( + ClientData cd, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + Tcl_ZlibStream zstream = cd; + int command, count, code; + Tcl_Obj *obj; + static const char *const cmds[] = { + "add", "checksum", "close", "eof", "finalize", "flush", + "fullflush", "get", "header", "put", "reset", + NULL + }; + enum zlibStreamCommands { + zs_add, zs_checksum, zs_close, zs_eof, zs_finalize, zs_flush, + zs_fullflush, zs_get, zs_header, zs_put, zs_reset + }; + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "option data ?...?"); + return TCL_ERROR; + } + + if (Tcl_GetIndexFromObj(interp, objv[1], cmds, "option", 0, + &command) != TCL_OK) { + return TCL_ERROR; + } + + switch ((enum zlibStreamCommands) command) { + case zs_add: /* $strm add ?$flushopt? $data */ + return ZlibStreamAddCmd(zstream, interp, objc, objv); + case zs_header: /* $strm header */ + return ZlibStreamHeaderCmd(zstream, interp, objc, objv); + case zs_put: /* $strm put ?$flushopt? $data */ + return ZlibStreamPutCmd(zstream, interp, objc, objv); + + case zs_get: /* $strm get ?count? */ + if (objc > 3) { + Tcl_WrongNumArgs(interp, 2, objv, "?count?"); + return TCL_ERROR; + } + + count = -1; + if (objc >= 3) { + if (Tcl_GetIntFromObj(interp, objv[2], &count) != TCL_OK) { + return TCL_ERROR; + } + } + TclNewObj(obj); + code = Tcl_ZlibStreamGet(zstream, obj, count); + if (code == TCL_OK) { + Tcl_SetObjResult(interp, obj); + } else { + TclDecrRefCount(obj); + } + return code; + case zs_flush: /* $strm flush */ + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, NULL); + return TCL_ERROR; + } + TclNewObj(obj); + Tcl_IncrRefCount(obj); + code = Tcl_ZlibStreamPut(zstream, obj, Z_SYNC_FLUSH); + TclDecrRefCount(obj); + return code; + case zs_fullflush: /* $strm fullflush */ + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, NULL); + return TCL_ERROR; + } + TclNewObj(obj); + Tcl_IncrRefCount(obj); + code = Tcl_ZlibStreamPut(zstream, obj, Z_FULL_FLUSH); + TclDecrRefCount(obj); + return code; + case zs_finalize: /* $strm finalize */ + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, NULL); + return TCL_ERROR; + } + + /* + * The flush commands slightly abuse the empty result obj as input + * data. + */ + + TclNewObj(obj); + Tcl_IncrRefCount(obj); + code = Tcl_ZlibStreamPut(zstream, obj, Z_FINISH); + TclDecrRefCount(obj); + return code; + case zs_close: /* $strm close */ + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, NULL); + return TCL_ERROR; + } + return Tcl_ZlibStreamClose(zstream); + case zs_eof: /* $strm eof */ + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, NULL); + return TCL_ERROR; + } + Tcl_SetObjResult(interp, Tcl_NewIntObj(Tcl_ZlibStreamEof(zstream))); + return TCL_OK; + case zs_checksum: /* $strm checksum */ + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, NULL); + return TCL_ERROR; + } + Tcl_SetObjResult(interp, Tcl_NewWideIntObj((Tcl_WideInt) + (uLong) Tcl_ZlibStreamChecksum(zstream))); + return TCL_OK; + case zs_reset: /* $strm reset */ + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, NULL); + return TCL_ERROR; + } + return Tcl_ZlibStreamReset(zstream); + } + + return TCL_OK; +} + +static int +ZlibStreamAddCmd( + ClientData cd, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + Tcl_ZlibStream zstream = cd; + int index, code, buffersize = -1, flush = -1, i; + Tcl_Obj *obj, *compDictObj = NULL; + static const char *const add_options[] = { + "-buffer", "-dictionary", "-finalize", "-flush", "-fullflush", NULL + }; + enum addOptions { + ao_buffer, ao_dictionary, ao_finalize, ao_flush, ao_fullflush + }; + + for (i=2; i<objc-1; i++) { + if (Tcl_GetIndexFromObj(interp, objv[i], add_options, "option", 0, + &index) != TCL_OK) { + return TCL_ERROR; + } + + switch ((enum addOptions) index) { + case ao_flush: /* -flush */ + if (flush > -1) { + flush = -2; + } else { + flush = Z_SYNC_FLUSH; + } + break; + case ao_fullflush: /* -fullflush */ + if (flush > -1) { + flush = -2; + } else { + flush = Z_FULL_FLUSH; + } + break; + case ao_finalize: /* -finalize */ + if (flush > -1) { + flush = -2; + } else { + flush = Z_FINISH; + } + break; + case ao_buffer: /* -buffer */ + if (i == objc-2) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "\"-buffer\" option must be followed by integer " + "decompression buffersize", -1)); + Tcl_SetErrorCode(interp, "TCL", "ZIP", "NOVAL", NULL); + return TCL_ERROR; + } + if (Tcl_GetIntFromObj(interp, objv[++i], &buffersize) != TCL_OK) { + return TCL_ERROR; + } + if (buffersize < 1 || buffersize > MAX_BUFFER_SIZE) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "buffer size must be 1 to %d", + MAX_BUFFER_SIZE)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "BUFFERSIZE", NULL); + return TCL_ERROR; + } + break; + case ao_dictionary: + if (i == objc-2) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "\"-dictionary\" option must be followed by" + " compression dictionary bytes", -1)); + Tcl_SetErrorCode(interp, "TCL", "ZIP", "NOVAL", NULL); + return TCL_ERROR; + } + compDictObj = objv[++i]; + break; + } + + if (flush == -2) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "\"-flush\", \"-fullflush\" and \"-finalize\" options" + " are mutually exclusive", -1)); + Tcl_SetErrorCode(interp, "TCL", "ZIP", "EXCLUSIVE", NULL); + return TCL_ERROR; + } + } + if (flush == -1) { + flush = 0; + } + + /* + * Set the compression dictionary if requested. + */ + + if (compDictObj != NULL) { + int len; + + (void) Tcl_GetByteArrayFromObj(compDictObj, &len); + if (len == 0) { + compDictObj = NULL; + } + Tcl_ZlibStreamSetCompressionDictionary(zstream, compDictObj); + } + + /* + * Send the data to the stream core, along with any flushing directive. + */ + + if (Tcl_ZlibStreamPut(zstream, objv[objc-1], flush) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Get such data out as we can (up to the requested length). + */ + + TclNewObj(obj); + code = Tcl_ZlibStreamGet(zstream, obj, buffersize); + if (code == TCL_OK) { + Tcl_SetObjResult(interp, obj); + } else { + TclDecrRefCount(obj); + } + return code; +} + +static int +ZlibStreamPutCmd( + ClientData cd, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + Tcl_ZlibStream zstream = cd; + int index, flush = -1, i; + Tcl_Obj *compDictObj = NULL; + static const char *const put_options[] = { + "-dictionary", "-finalize", "-flush", "-fullflush", NULL + }; + enum putOptions { + po_dictionary, po_finalize, po_flush, po_fullflush + }; + + for (i=2; i<objc-1; i++) { + if (Tcl_GetIndexFromObj(interp, objv[i], put_options, "option", 0, + &index) != TCL_OK) { + return TCL_ERROR; + } + + switch ((enum putOptions) index) { + case po_flush: /* -flush */ + if (flush > -1) { + flush = -2; + } else { + flush = Z_SYNC_FLUSH; + } + break; + case po_fullflush: /* -fullflush */ + if (flush > -1) { + flush = -2; + } else { + flush = Z_FULL_FLUSH; + } + break; + case po_finalize: /* -finalize */ + if (flush > -1) { + flush = -2; + } else { + flush = Z_FINISH; + } + break; + case po_dictionary: + if (i == objc-2) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "\"-dictionary\" option must be followed by" + " compression dictionary bytes", -1)); + Tcl_SetErrorCode(interp, "TCL", "ZIP", "NOVAL", NULL); + return TCL_ERROR; + } + compDictObj = objv[++i]; + break; + } + if (flush == -2) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "\"-flush\", \"-fullflush\" and \"-finalize\" options" + " are mutually exclusive", -1)); + Tcl_SetErrorCode(interp, "TCL", "ZIP", "EXCLUSIVE", NULL); + return TCL_ERROR; + } + } + if (flush == -1) { + flush = 0; + } + + /* + * Set the compression dictionary if requested. + */ + + if (compDictObj != NULL) { + int len; + + (void) Tcl_GetByteArrayFromObj(compDictObj, &len); + if (len == 0) { + compDictObj = NULL; + } + Tcl_ZlibStreamSetCompressionDictionary(zstream, compDictObj); + } + + /* + * Send the data to the stream core, along with any flushing directive. + */ + + return Tcl_ZlibStreamPut(zstream, objv[objc-1], flush); +} + +static int +ZlibStreamHeaderCmd( + ClientData cd, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + ZlibStreamHandle *zshPtr = cd; + Tcl_Obj *resultObj; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, NULL); + return TCL_ERROR; + } else if (zshPtr->mode != TCL_ZLIB_STREAM_INFLATE + || zshPtr->format != TCL_ZLIB_FORMAT_GZIP) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "only gunzip streams can produce header information", -1)); + Tcl_SetErrorCode(interp, "TCL", "ZIP", "BADOP", NULL); + return TCL_ERROR; + } + + TclNewObj(resultObj); + ExtractHeader(&zshPtr->gzHeaderPtr->header, resultObj); + Tcl_SetObjResult(interp, resultObj); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * Set of functions to support channel stacking. + *---------------------------------------------------------------------- + * + * ZlibTransformClose -- + * + * How to shut down a stacked compressing/decompressing transform. + * + *---------------------------------------------------------------------- + */ + +static int +ZlibTransformClose( + ClientData instanceData, + Tcl_Interp *interp) +{ + ZlibChannelData *cd = instanceData; + int e, result = TCL_OK; + + /* + * Delete the support timer. + */ + + ZlibTransformEventTimerKill(cd); + + /* + * Flush any data waiting to be compressed. + */ + + if (cd->mode == TCL_ZLIB_STREAM_DEFLATE) { + cd->outStream.avail_in = 0; + do { + cd->outStream.next_out = (Bytef *) cd->outBuffer; + cd->outStream.avail_out = (unsigned) cd->outAllocated; + e = deflate(&cd->outStream, Z_FINISH); + if (e != Z_OK && e != Z_STREAM_END) { + /* TODO: is this the right way to do errors on close? */ + if (!TclInThreadExit()) { + ConvertError(interp, e, cd->outStream.adler); + } + result = TCL_ERROR; + break; + } + if (cd->outStream.avail_out != (unsigned) cd->outAllocated) { + if (Tcl_WriteRaw(cd->parent, cd->outBuffer, + cd->outAllocated - cd->outStream.avail_out) < 0) { + /* TODO: is this the right way to do errors on close? + * Note: when close is called from FinalizeIOSubsystem + * then interp may be NULL */ + if (!TclInThreadExit() && interp) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "error while finalizing file: %s", + Tcl_PosixError(interp))); + } + result = TCL_ERROR; + break; + } + } + } while (e != Z_STREAM_END); + e = deflateEnd(&cd->outStream); + } else { + e = inflateEnd(&cd->inStream); + } + + /* + * Release all memory. + */ + + Tcl_DStringFree(&cd->decompressed); + + if (cd->inBuffer) { + ckfree(cd->inBuffer); + cd->inBuffer = NULL; + } + if (cd->outBuffer) { + ckfree(cd->outBuffer); + cd->outBuffer = NULL; + } + ckfree(cd); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * ZlibTransformInput -- + * + * Reader filter that does decompression. + * + *---------------------------------------------------------------------- + */ + +static int +ZlibTransformInput( + ClientData instanceData, + char *buf, + int toRead, + int *errorCodePtr) +{ + ZlibChannelData *cd = instanceData; + Tcl_DriverInputProc *inProc = + Tcl_ChannelInputProc(Tcl_GetChannelType(cd->parent)); + int readBytes, gotBytes, copied; + + if (cd->mode == TCL_ZLIB_STREAM_DEFLATE) { + return inProc(Tcl_GetChannelInstanceData(cd->parent), buf, toRead, + errorCodePtr); + } + + gotBytes = 0; + while (toRead > 0) { + /* + * Loop until the request is satisfied (or no data available from + * below, possibly EOF). + */ + + copied = ResultCopy(cd, buf, toRead); + toRead -= copied; + buf += copied; + gotBytes += copied; + + if (toRead == 0) { + return gotBytes; + } + + /* + * The buffer is exhausted, but the caller wants even more. We now + * have to go to the underlying channel, get more bytes and then + * transform them for delivery. We may not get what we want (full EOF + * or temporarily out of data). + * + * Length (cd->decompressed) == 0, toRead > 0 here. + * + * The zlib transform allows us to read at most one character from the + * underlying channel to properly identify Z_STREAM_END without + * reading over the border. + */ + + readBytes = Tcl_ReadRaw(cd->parent, cd->inBuffer, cd->readAheadLimit); + + /* + * Three cases here: + * 1. Got some data from the underlying channel (readBytes > 0) so + * it should be fed through the decompression engine. + * 2. Got an error (readBytes < 0) which we should report up except + * for the case where we can convert it to a short read. + * 3. Got an end-of-data from EOF or blocking (readBytes == 0). If + * it is EOF, try flushing the data out of the decompressor. + */ + + if (readBytes < 0) { + /* + * Report errors to caller. The state of the seek system is + * unchanged! + */ + + if ((Tcl_GetErrno() == EAGAIN) && (gotBytes > 0)) { + /* + * EAGAIN is a special situation. If we had some data before + * we report that instead of the request to re-try. + */ + + return gotBytes; + } + + *errorCodePtr = Tcl_GetErrno(); + return -1; + } else if (readBytes == 0) { + /* + * Check wether we hit on EOF in 'parent' or not. If not, + * differentiate between blocking and non-blocking modes. In + * non-blocking mode we ran temporarily out of data. Signal this + * to the caller via EWOULDBLOCK and error return (-1). In the + * other cases we simply return what we got and let the caller + * wait for more. On the other hand, if we got an EOF we have to + * convert and flush all waiting partial data. + */ + + if (!Tcl_Eof(cd->parent)) { + /* + * The state of the seek system is unchanged! + */ + + if ((gotBytes == 0) && (cd->flags & ASYNC)) { + *errorCodePtr = EWOULDBLOCK; + return -1; + } + return gotBytes; + } + + /* + * (Semi-)Eof in parent. + * + * Now this is a bit different. The partial data waiting is + * converted and returned. + */ + + if (ResultGenerate(cd, 0, Z_SYNC_FLUSH, errorCodePtr) != TCL_OK) { + return -1; + } + + if (Tcl_DStringLength(&cd->decompressed) == 0) { + /* + * The drain delivered nothing. Time to deliver what we've + * got. + */ + + return gotBytes; + } + + /* + * Reset eof, force caller to drain result buffer. + */ + + ((Channel *) cd->parent)->state->flags &= ~CHANNEL_EOF; + } else /* readBytes > 0 */ { + /* + * Transform the read chunk, which was not empty. Anything we get + * back is a transformation result to be put into our buffers, and + * the next iteration will put it into the result. + */ + + if (ResultGenerate(cd, readBytes, Z_NO_FLUSH, + errorCodePtr) != TCL_OK) { + return -1; + } + } + } + return gotBytes; +} + +/* + *---------------------------------------------------------------------- + * + * ZlibTransformOutput -- + * + * Writer filter that does compression. + * + *---------------------------------------------------------------------- + */ + +static int +ZlibTransformOutput( + ClientData instanceData, + const char *buf, + int toWrite, + int *errorCodePtr) +{ + ZlibChannelData *cd = instanceData; + Tcl_DriverOutputProc *outProc = + Tcl_ChannelOutputProc(Tcl_GetChannelType(cd->parent)); + int e, produced; + Tcl_Obj *errObj; + + if (cd->mode == TCL_ZLIB_STREAM_INFLATE) { + return outProc(Tcl_GetChannelInstanceData(cd->parent), buf, toWrite, + errorCodePtr); + } + + cd->outStream.next_in = (Bytef *) buf; + cd->outStream.avail_in = toWrite; + do { + cd->outStream.next_out = (Bytef *) cd->outBuffer; + cd->outStream.avail_out = cd->outAllocated; + + e = deflate(&cd->outStream, Z_NO_FLUSH); + produced = cd->outAllocated - cd->outStream.avail_out; + + if (e == Z_OK && cd->outStream.avail_out > 0) { + if (Tcl_WriteRaw(cd->parent, cd->outBuffer, produced) < 0) { + *errorCodePtr = Tcl_GetErrno(); + return -1; + } + } + } while (e == Z_OK && produced > 0 && cd->outStream.avail_in > 0); + + if (e == Z_OK) { + return toWrite - cd->outStream.avail_in; + } + + errObj = Tcl_NewListObj(0, NULL); + Tcl_ListObjAppendElement(NULL, errObj, Tcl_NewStringObj("-errorcode",-1)); + Tcl_ListObjAppendElement(NULL, errObj, + ConvertErrorToList(e, cd->outStream.adler)); + Tcl_ListObjAppendElement(NULL, errObj, + Tcl_NewStringObj(cd->outStream.msg, -1)); + Tcl_SetChannelError(cd->parent, errObj); + *errorCodePtr = EINVAL; + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * ZlibTransformSetOption -- + * + * Writing side of [fconfigure] on our channel. + * + *---------------------------------------------------------------------- + */ + +static int +ZlibTransformSetOption( /* not used */ + ClientData instanceData, + Tcl_Interp *interp, + const char *optionName, + const char *value) +{ + ZlibChannelData *cd = instanceData; + Tcl_DriverSetOptionProc *setOptionProc = + Tcl_ChannelSetOptionProc(Tcl_GetChannelType(cd->parent)); + static const char *compressChanOptions = "dictionary flush"; + static const char *gzipChanOptions = "flush"; + static const char *decompressChanOptions = "dictionary limit"; + static const char *gunzipChanOptions = "flush limit"; + int haveFlushOpt = (cd->mode == TCL_ZLIB_STREAM_DEFLATE); + + if (optionName && (strcmp(optionName, "-dictionary") == 0) + && (cd->format != TCL_ZLIB_FORMAT_GZIP)) { + Tcl_Obj *compDictObj; + int code; + + TclNewStringObj(compDictObj, value, strlen(value)); + Tcl_IncrRefCount(compDictObj); + (void) Tcl_GetByteArrayFromObj(compDictObj, NULL); + if (cd->compDictObj) { + TclDecrRefCount(cd->compDictObj); + } + cd->compDictObj = compDictObj; + code = Z_OK; + if (cd->mode == TCL_ZLIB_STREAM_DEFLATE) { + code = SetDeflateDictionary(&cd->outStream, compDictObj); + if (code != Z_OK) { + ConvertError(interp, code, cd->outStream.adler); + return TCL_ERROR; + } + } else if (cd->format == TCL_ZLIB_FORMAT_RAW) { + code = SetInflateDictionary(&cd->inStream, compDictObj); + if (code != Z_OK) { + ConvertError(interp, code, cd->inStream.adler); + return TCL_ERROR; + } + } + return TCL_OK; + } + + if (haveFlushOpt) { + if (optionName && strcmp(optionName, "-flush") == 0) { + int flushType; + + if (value[0] == 'f' && strcmp(value, "full") == 0) { + flushType = Z_FULL_FLUSH; + } else if (value[0] == 's' && strcmp(value, "sync") == 0) { + flushType = Z_SYNC_FLUSH; + } else { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "unknown -flush type \"%s\": must be full or sync", + value)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "FLUSH", NULL); + return TCL_ERROR; + } + + /* + * Try to actually do the flush now. + */ + + cd->outStream.avail_in = 0; + while (1) { + int e; + + cd->outStream.next_out = (Bytef *) cd->outBuffer; + cd->outStream.avail_out = cd->outAllocated; + + e = deflate(&cd->outStream, flushType); + if (e == Z_BUF_ERROR) { + break; + } else if (e != Z_OK) { + ConvertError(interp, e, cd->outStream.adler); + return TCL_ERROR; + } else if (cd->outStream.avail_out == 0) { + break; + } + + if (Tcl_WriteRaw(cd->parent, cd->outBuffer, + cd->outStream.next_out - (Bytef *) cd->outBuffer)<0) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "problem flushing channel: %s", + Tcl_PosixError(interp))); + return TCL_ERROR; + } + } + return TCL_OK; + } + } else { + if (optionName && strcmp(optionName, "-limit") == 0) { + int newLimit; + + if (Tcl_GetInt(interp, value, &newLimit) != TCL_OK) { + return TCL_ERROR; + } else if (newLimit < 1 || newLimit > MAX_BUFFER_SIZE) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "-limit must be between 1 and 65536", -1)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "READLIMIT", NULL); + return TCL_ERROR; + } + } + } + + if (setOptionProc == NULL) { + if (cd->format == TCL_ZLIB_FORMAT_GZIP) { + return Tcl_BadChannelOption(interp, optionName, + (cd->mode == TCL_ZLIB_STREAM_DEFLATE) + ? gzipChanOptions : gunzipChanOptions); + } else { + return Tcl_BadChannelOption(interp, optionName, + (cd->mode == TCL_ZLIB_STREAM_DEFLATE) + ? compressChanOptions : decompressChanOptions); + } + } + + /* + * Pass all unknown options down, to deeper transforms and/or the base + * channel. + */ + + return setOptionProc(Tcl_GetChannelInstanceData(cd->parent), interp, + optionName, value); +} + +/* + *---------------------------------------------------------------------- + * + * ZlibTransformGetOption -- + * + * Reading side of [fconfigure] on our channel. + * + *---------------------------------------------------------------------- + */ + +static int +ZlibTransformGetOption( + ClientData instanceData, + Tcl_Interp *interp, + const char *optionName, + Tcl_DString *dsPtr) +{ + ZlibChannelData *cd = instanceData; + Tcl_DriverGetOptionProc *getOptionProc = + Tcl_ChannelGetOptionProc(Tcl_GetChannelType(cd->parent)); + static const char *compressChanOptions = "checksum dictionary"; + static const char *gzipChanOptions = "checksum"; + static const char *decompressChanOptions = "checksum dictionary limit"; + static const char *gunzipChanOptions = "checksum header limit"; + + /* + * The "crc" option reports the current CRC (calculated with the Adler32 + * or CRC32 algorithm according to the format) given the data that has + * been processed so far. + */ + + if (optionName == NULL || strcmp(optionName, "-checksum") == 0) { + uLong crc; + char buf[12]; + + if (cd->mode == TCL_ZLIB_STREAM_DEFLATE) { + crc = cd->outStream.adler; + } else { + crc = cd->inStream.adler; + } + + sprintf(buf, "%lu", crc); + if (optionName == NULL) { + Tcl_DStringAppendElement(dsPtr, "-checksum"); + Tcl_DStringAppendElement(dsPtr, buf); + } else { + Tcl_DStringAppend(dsPtr, buf, -1); + return TCL_OK; + } + } + + if ((cd->format != TCL_ZLIB_FORMAT_GZIP) && + (optionName == NULL || strcmp(optionName, "-dictionary") == 0)) { + /* + * Embedded NUL bytes are ok; they'll be C080-encoded. + */ + + if (optionName == NULL) { + Tcl_DStringAppendElement(dsPtr, "-dictionary"); + if (cd->compDictObj) { + Tcl_DStringAppendElement(dsPtr, + Tcl_GetString(cd->compDictObj)); + } else { + Tcl_DStringAppendElement(dsPtr, ""); + } + } else { + int len; + const char *str = Tcl_GetStringFromObj(cd->compDictObj, &len); + + Tcl_DStringAppend(dsPtr, str, len); + } + } + + /* + * The "header" option, which is only valid on inflating gzip channels, + * reports the header that has been read from the start of the stream. + */ + + if ((cd->flags & IN_HEADER) && ((optionName == NULL) || + (strcmp(optionName, "-header") == 0))) { + Tcl_Obj *tmpObj = Tcl_NewObj(); + + ExtractHeader(&cd->inHeader.header, tmpObj); + if (optionName == NULL) { + Tcl_DStringAppendElement(dsPtr, "-header"); + Tcl_DStringAppendElement(dsPtr, Tcl_GetString(tmpObj)); + Tcl_DecrRefCount(tmpObj); + } else { + TclDStringAppendObj(dsPtr, tmpObj); + Tcl_DecrRefCount(tmpObj); + return TCL_OK; + } + } + + /* + * Now we do the standard processing of the stream we wrapped. + */ + + if (getOptionProc) { + return getOptionProc(Tcl_GetChannelInstanceData(cd->parent), + interp, optionName, dsPtr); + } + if (optionName == NULL) { + return TCL_OK; + } + if (cd->format == TCL_ZLIB_FORMAT_GZIP) { + return Tcl_BadChannelOption(interp, optionName, + (cd->mode == TCL_ZLIB_STREAM_DEFLATE) + ? gzipChanOptions : gunzipChanOptions); + } else { + return Tcl_BadChannelOption(interp, optionName, + (cd->mode == TCL_ZLIB_STREAM_DEFLATE) + ? compressChanOptions : decompressChanOptions); + } +} + +/* + *---------------------------------------------------------------------- + * + * ZlibTransformWatch, ZlibTransformEventHandler -- + * + * If we have data pending, trigger a readable event after a short time + * (in order to allow a real event to catch up). + * + *---------------------------------------------------------------------- + */ + +static void +ZlibTransformWatch( + ClientData instanceData, + int mask) +{ + ZlibChannelData *cd = instanceData; + Tcl_DriverWatchProc *watchProc; + + /* + * This code is based on the code in tclIORTrans.c + */ + + watchProc = Tcl_ChannelWatchProc(Tcl_GetChannelType(cd->parent)); + watchProc(Tcl_GetChannelInstanceData(cd->parent), mask); + + if (!(mask & TCL_READABLE) || Tcl_DStringLength(&cd->decompressed) == 0) { + ZlibTransformEventTimerKill(cd); + } else if (cd->timer == NULL) { + cd->timer = Tcl_CreateTimerHandler(SYNTHETIC_EVENT_TIME, + ZlibTransformTimerRun, cd); + } +} + +static int +ZlibTransformEventHandler( + ClientData instanceData, + int interestMask) +{ + ZlibChannelData *cd = instanceData; + + ZlibTransformEventTimerKill(cd); + return interestMask; +} + +static inline void +ZlibTransformEventTimerKill( + ZlibChannelData *cd) +{ + if (cd->timer != NULL) { + Tcl_DeleteTimerHandler(cd->timer); + cd->timer = NULL; + } +} + +static void +ZlibTransformTimerRun( + ClientData clientData) +{ + ZlibChannelData *cd = clientData; + + cd->timer = NULL; + Tcl_NotifyChannel(cd->chan, TCL_READABLE); +} + +/* + *---------------------------------------------------------------------- + * + * ZlibTransformGetHandle -- + * + * Anything that needs the OS handle is told to get it from what we are + * stacked on top of. + * + *---------------------------------------------------------------------- + */ + +static int +ZlibTransformGetHandle( + ClientData instanceData, + int direction, + ClientData *handlePtr) +{ + ZlibChannelData *cd = instanceData; + + return Tcl_GetChannelHandle(cd->parent, direction, handlePtr); +} + +/* + *---------------------------------------------------------------------- + * + * ZlibTransformBlockMode -- + * + * We need to keep track of the blocking mode; it changes our behavior. + * + *---------------------------------------------------------------------- + */ + +static int +ZlibTransformBlockMode( + ClientData instanceData, + int mode) +{ + ZlibChannelData *cd = instanceData; + + if (mode == TCL_MODE_NONBLOCKING) { + cd->flags |= ASYNC; + } else { + cd->flags &= ~ASYNC; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * ZlibStackChannelTransform -- + * + * Stacks either compression or decompression onto a channel. + * + * Results: + * The stacked channel, or NULL if there was an error. + * + *---------------------------------------------------------------------- + */ + +static Tcl_Channel +ZlibStackChannelTransform( + Tcl_Interp *interp, /* Where to write error messages. */ + int mode, /* Whether this is a compressing transform + * (TCL_ZLIB_STREAM_DEFLATE) or a + * decompressing transform + * (TCL_ZLIB_STREAM_INFLATE). Note that + * compressing transforms require that the + * channel is writable, and decompressing + * transforms require that the channel is + * readable. */ + int format, /* One of the TCL_ZLIB_FORMAT_* values that + * indicates what compressed format to allow. + * TCL_ZLIB_FORMAT_AUTO is only supported for + * decompressing transforms. */ + int level, /* What compression level to use. Ignored for + * decompressing transforms. */ + int limit, /* The limit on the number of bytes to read + * ahead; always at least 1. */ + Tcl_Channel channel, /* The channel to attach to. */ + Tcl_Obj *gzipHeaderDictPtr, /* A description of header to use, or NULL to + * use a default. Ignored if not compressing + * to produce gzip-format data. */ + Tcl_Obj *compDictObj) /* Byte-array object containing compression + * dictionary (not dictObj!) to use if + * necessary. */ +{ + ZlibChannelData *cd = ckalloc(sizeof(ZlibChannelData)); + Tcl_Channel chan; + int wbits = 0; + int e; + + if (mode != TCL_ZLIB_STREAM_DEFLATE && mode != TCL_ZLIB_STREAM_INFLATE) { + Tcl_Panic("unknown mode: %d", mode); + } + + memset(cd, 0, sizeof(ZlibChannelData)); + cd->mode = mode; + cd->format = format; + cd->readAheadLimit = limit; + + if (format == TCL_ZLIB_FORMAT_GZIP || format == TCL_ZLIB_FORMAT_AUTO) { + if (mode == TCL_ZLIB_STREAM_DEFLATE) { + if (gzipHeaderDictPtr) { + cd->flags |= OUT_HEADER; + if (GenerateHeader(interp, gzipHeaderDictPtr, &cd->outHeader, + NULL) != TCL_OK) { + goto error; + } + } + } else { + cd->flags |= IN_HEADER; + cd->inHeader.header.name = (Bytef *) + &cd->inHeader.nativeFilenameBuf; + cd->inHeader.header.name_max = MAXPATHLEN - 1; + cd->inHeader.header.comment = (Bytef *) + &cd->inHeader.nativeCommentBuf; + cd->inHeader.header.comm_max = MAX_COMMENT_LEN - 1; + } + } + + if (compDictObj != NULL) { + cd->compDictObj = Tcl_DuplicateObj(compDictObj); + Tcl_IncrRefCount(cd->compDictObj); + Tcl_GetByteArrayFromObj(cd->compDictObj, NULL); + } + + if (format == TCL_ZLIB_FORMAT_RAW) { + wbits = WBITS_RAW; + } else if (format == TCL_ZLIB_FORMAT_ZLIB) { + wbits = WBITS_ZLIB; + } else if (format == TCL_ZLIB_FORMAT_GZIP) { + wbits = WBITS_GZIP; + } else if (format == TCL_ZLIB_FORMAT_AUTO) { + wbits = WBITS_AUTODETECT; + } else { + Tcl_Panic("bad format: %d", format); + } + + /* + * Initialize input inflater or the output deflater. + */ + + if (mode == TCL_ZLIB_STREAM_INFLATE) { + e = inflateInit2(&cd->inStream, wbits); + if (e != Z_OK) { + goto error; + } + cd->inAllocated = DEFAULT_BUFFER_SIZE; + cd->inBuffer = ckalloc(cd->inAllocated); + if (cd->flags & IN_HEADER) { + e = inflateGetHeader(&cd->inStream, &cd->inHeader.header); + if (e != Z_OK) { + goto error; + } + } + if (cd->format == TCL_ZLIB_FORMAT_RAW && cd->compDictObj) { + e = SetInflateDictionary(&cd->inStream, cd->compDictObj); + if (e != Z_OK) { + goto error; + } + TclDecrRefCount(cd->compDictObj); + cd->compDictObj = NULL; + } + } else { + e = deflateInit2(&cd->outStream, level, Z_DEFLATED, wbits, + MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); + if (e != Z_OK) { + goto error; + } + cd->outAllocated = DEFAULT_BUFFER_SIZE; + cd->outBuffer = ckalloc(cd->outAllocated); + if (cd->flags & OUT_HEADER) { + e = deflateSetHeader(&cd->outStream, &cd->outHeader.header); + if (e != Z_OK) { + goto error; + } + } + if (cd->compDictObj) { + e = SetDeflateDictionary(&cd->outStream, cd->compDictObj); + if (e != Z_OK) { + goto error; + } + } + } + + Tcl_DStringInit(&cd->decompressed); + + chan = Tcl_StackChannel(interp, &zlibChannelType, cd, + Tcl_GetChannelMode(channel), channel); + if (chan == NULL) { + goto error; + } + cd->chan = chan; + cd->parent = Tcl_GetStackedChannel(chan); + Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_GetChannelName(chan), -1)); + return chan; + + error: + if (cd->inBuffer) { + ckfree(cd->inBuffer); + inflateEnd(&cd->inStream); + } + if (cd->outBuffer) { + ckfree(cd->outBuffer); + deflateEnd(&cd->outStream); + } + if (cd->compDictObj) { + Tcl_DecrRefCount(cd->compDictObj); + } + ckfree(cd); + return NULL; +} + +/* + *---------------------------------------------------------------------- + * + * ResultCopy -- + * + * Copies the requested number of bytes from the buffer into the + * specified array and removes them from the buffer afterward. Copies + * less if there is not enough data in the buffer. + * + * Side effects: + * See above. + * + * Result: + * The number of actually copied bytes, possibly less than 'toRead'. + * + *---------------------------------------------------------------------- + */ + +static inline int +ResultCopy( + ZlibChannelData *cd, /* The location of the buffer to read from. */ + char *buf, /* The buffer to copy into */ + int toRead) /* Number of requested bytes */ +{ + int have = Tcl_DStringLength(&cd->decompressed); + + if (have == 0) { + /* + * Nothing to copy in the case of an empty buffer. + */ + + return 0; + } else if (have > toRead) { + /* + * The internal buffer contains more than requested. Copy the + * requested subset to the caller, shift the remaining bytes down, and + * truncate. + */ + + char *src = Tcl_DStringValue(&cd->decompressed); + + memcpy(buf, src, toRead); + memmove(src, src + toRead, have - toRead); + + Tcl_DStringSetLength(&cd->decompressed, have - toRead); + return toRead; + } else /* have <= toRead */ { + /* + * There is just or not enough in the buffer to fully satisfy the + * caller, so take everything as best effort. + */ + + memcpy(buf, Tcl_DStringValue(&cd->decompressed), have); + TclDStringClear(&cd->decompressed); + return have; + } +} + +/* + *---------------------------------------------------------------------- + * + * ResultGenerate -- + * + * Extract uncompressed bytes from the compression engine and store them + * in our working buffer. + * + * Result: + * TCL_OK/TCL_ERROR (with *errorCodePtr updated with reason). + * + * Side effects: + * See above. + * + *---------------------------------------------------------------------- + */ + +static int +ResultGenerate( + ZlibChannelData *cd, + int n, + int flush, + int *errorCodePtr) +{ +#define MAXBUF 1024 + unsigned char buf[MAXBUF]; + int e, written; + Tcl_Obj *errObj; + + cd->inStream.next_in = (Bytef *) cd->inBuffer; + cd->inStream.avail_in = n; + + while (1) { + cd->inStream.next_out = (Bytef *) buf; + cd->inStream.avail_out = MAXBUF; + + e = inflate(&cd->inStream, flush); + if (e == Z_NEED_DICT && cd->compDictObj) { + e = SetInflateDictionary(&cd->inStream, cd->compDictObj); + if (e == Z_OK) { + /* + * A repetition of Z_NEED_DICT is just an error. + */ + + cd->inStream.next_out = (Bytef *) buf; + cd->inStream.avail_out = MAXBUF; + e = inflate(&cd->inStream, flush); + } + } + + /* + * avail_out is now the left over space in the output. Therefore + * "MAXBUF - avail_out" is the amount of bytes generated. + */ + + written = MAXBUF - cd->inStream.avail_out; + if (written) { + Tcl_DStringAppend(&cd->decompressed, (char *) buf, written); + } + + /* + * The cases where we're definitely done. + */ + + if (((flush == Z_SYNC_FLUSH) && (e == Z_BUF_ERROR)) + || (e == Z_STREAM_END) + || (e == Z_OK && cd->inStream.avail_out == 0)) { + return TCL_OK; + } + + /* + * Z_BUF_ERROR can be ignored as per http://www.zlib.net/zlib_how.html + * + * Just indicates that the zlib couldn't consume input/produce output, + * and is fixed by supplying more input. + * + * Otherwise, we've got errors and need to report to higher-up. + */ + + if ((e != Z_OK) && (e != Z_BUF_ERROR)) { + goto handleError; + } + + /* + * Check if the inflate stopped early. + */ + + if (cd->inStream.avail_in <= 0 && flush != Z_SYNC_FLUSH) { + return TCL_OK; + } + } + + handleError: + errObj = Tcl_NewListObj(0, NULL); + Tcl_ListObjAppendElement(NULL, errObj, Tcl_NewStringObj("-errorcode",-1)); + Tcl_ListObjAppendElement(NULL, errObj, + ConvertErrorToList(e, cd->inStream.adler)); + Tcl_ListObjAppendElement(NULL, errObj, + Tcl_NewStringObj(cd->inStream.msg, -1)); + Tcl_SetChannelError(cd->parent, errObj); + *errorCodePtr = EINVAL; + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * Finally, the TclZlibInit function. Used to install the zlib API. + *---------------------------------------------------------------------- + */ + +int +TclZlibInit( + Tcl_Interp *interp) +{ + Tcl_Config cfg[2]; + + /* + * This does two things. It creates a counter used in the creation of + * stream commands, and it creates the namespace that will contain those + * commands. + */ + + Tcl_Eval(interp, "namespace eval ::tcl::zlib {variable cmdcounter 0}"); + + /* + * Create the public scripted interface to this file's functionality. + */ + + Tcl_CreateObjCommand(interp, "zlib", ZlibCmd, 0, 0); + + /* + * Store the underlying configuration information. + * + * TODO: Describe whether we're using the system version of the library or + * a compatibility version built into Tcl? + */ + + cfg[0].key = "zlibVersion"; + cfg[0].value = zlibVersion(); + cfg[1].key = NULL; + Tcl_RegisterConfig(interp, "zlib", cfg, "ascii"); + + /* + * Formally provide the package as a Tcl built-in. + */ + + return Tcl_PkgProvide(interp, "zlib", TCL_ZLIB_VERSION); +} + +/* + *---------------------------------------------------------------------- + * Stubs used when a suitable zlib installation was not found during + * configure. + *---------------------------------------------------------------------- + */ + +#else /* !HAVE_ZLIB */ +int +Tcl_ZlibStreamInit( + Tcl_Interp *interp, + int mode, + int format, + int level, + Tcl_Obj *dictObj, + Tcl_ZlibStream *zshandle) +{ + Tcl_SetObjResult(interp, Tcl_NewStringObj("unimplemented", -1)); + Tcl_SetErrorCode(interp, "TCL", "UNIMPLEMENTED", NULL); + return TCL_ERROR; +} + +int +Tcl_ZlibStreamClose( + Tcl_ZlibStream zshandle) +{ + return TCL_OK; +} + +int +Tcl_ZlibStreamReset( + Tcl_ZlibStream zshandle) +{ + return TCL_OK; +} + +Tcl_Obj * +Tcl_ZlibStreamGetCommandName( + Tcl_ZlibStream zshandle) +{ + return NULL; +} + +int +Tcl_ZlibStreamEof( + Tcl_ZlibStream zshandle) +{ + return 1; +} + +int +Tcl_ZlibStreamChecksum( + Tcl_ZlibStream zshandle) +{ + return 0; +} + +int +Tcl_ZlibStreamPut( + Tcl_ZlibStream zshandle, + Tcl_Obj *data, + int flush) +{ + return TCL_OK; +} + +int +Tcl_ZlibStreamGet( + Tcl_ZlibStream zshandle, + Tcl_Obj *data, + int count) +{ + return TCL_OK; +} + +int +Tcl_ZlibDeflate( + Tcl_Interp *interp, + int format, + Tcl_Obj *data, + int level, + Tcl_Obj *gzipHeaderDictObj) +{ + Tcl_SetObjResult(interp, Tcl_NewStringObj("unimplemented", -1)); + Tcl_SetErrorCode(interp, "TCL", "UNIMPLEMENTED", NULL); + return TCL_ERROR; +} + +int +Tcl_ZlibInflate( + Tcl_Interp *interp, + int format, + Tcl_Obj *data, + int bufferSize, + Tcl_Obj *gzipHeaderDictObj) +{ + Tcl_SetObjResult(interp, Tcl_NewStringObj("unimplemented", -1)); + Tcl_SetErrorCode(interp, "TCL", "UNIMPLEMENTED", NULL); + return TCL_ERROR; +} + +unsigned int +Tcl_ZlibCRC32( + unsigned int crc, + const char *buf, + int len) +{ + return 0; +} + +unsigned int +Tcl_ZlibAdler32( + unsigned int adler, + const char *buf, + int len) +{ + return 0; +} + +void +Tcl_ZlibStreamSetCompressionDictionary( + Tcl_ZlibStream zshandle, + Tcl_Obj *compressionDictionaryObj) +{ + /* Do nothing. */ +} +#endif /* HAVE_ZLIB */ + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |