/* * tclZlib.c -- * * This file provides the interface to the Zlib library. * * Copyright © 2004-2005 Pascal Scheffers * Copyright © 2005 Unitas Software B.V. * Copyright © 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 #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.1" /* * 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. */ size_t 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. */ unsigned 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. */ size_t 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_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. * STREAM_DECOMPRESS - Signal decompress pending data. * STREAM_DONE - Flag to signal stream end up to transform input. */ #define ASYNC 0x01 #define IN_HEADER 0x02 #define OUT_HEADER 0x04 #define STREAM_DECOMPRESS 0x08 #define STREAM_DONE 0x10 /* * 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_DriverClose2Proc 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 inline int Deflate(z_streamp strm, void *bufferPtr, size_t bufferSize, int flush, size_t *writtenPtr); 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 int ResultDecompress(ZlibChannelData *cd, char *buf, int toRead, 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(void *clientData); /* * Type of zlib-based compressing and decompressing channels. */ static const Tcl_ChannelType zlibChannelType = { "zlib", TCL_CHANNEL_VERSION_5, NULL, ZlibTransformInput, ZlibTransformOutput, NULL, /* seekProc */ ZlibTransformSetOption, ZlibTransformGetOption, ZlibTransformWatch, ZlibTransformGetHandle, ZlibTransformClose, /* 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"); TclNewIntObj(objv[3], (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; size_t length; Tcl_WideInt wideValue = 0; 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, &length); Tcl_UtfToExternal(NULL, latin1enc, valueStr, length, 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, &length); Tcl_UtfToExternal(NULL, latin1enc, valueStr, length, 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_GetWideIntFromObj(interp, value, &wideValue) != TCL_OK) { goto error; } headerPtr->header.time = wideValue; 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_ExternalToUtfDStringEx(latin1enc, (char *) headerPtr->comment, -1, TCL_ENCODING_NOCOMPLAIN, &tmp); SetValue(dictObj, "comment", Tcl_DStringToObj(&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_ExternalToUtfDStringEx(latin1enc, (char *) headerPtr->name, -1, TCL_ENCODING_NOCOMPLAIN, &tmp); SetValue(dictObj, "filename", Tcl_DStringToObj(&tmp)); } if (headerPtr->os != 255) { SetValue(dictObj, "os", Tcl_NewWideIntObj(headerPtr->os)); } if (headerPtr->time != 0 /* magic - no time */) { SetValue(dictObj, "time", Tcl_NewWideIntObj(headerPtr->time)); } if (headerPtr->text != Z_UNKNOWN) { SetValue(dictObj, "type", Tcl_NewStringObj(headerPtr->text ? "text" : "binary", -1)); } if (latin1enc != NULL) { Tcl_FreeEncoding(latin1enc); } } /* * Disentangle the worst of how the zlib API is used. */ static int SetInflateDictionary( z_streamp strm, Tcl_Obj *compDictObj) { if (compDictObj != NULL) { size_t length = 0; unsigned char *bytes = Tcl_GetByteArrayFromObj(compDictObj, &length); if (bytes == NULL) { return Z_DATA_ERROR; } return inflateSetDictionary(strm, bytes, length); } return Z_OK; } static int SetDeflateDictionary( z_streamp strm, Tcl_Obj *compDictObj) { if (compDictObj != NULL) { size_t length = 0; unsigned char *bytes = Tcl_GetByteArrayFromObj(compDictObj, &length); if (bytes == NULL) { return Z_DATA_ERROR; } return deflateSetDictionary(strm, bytes, length); } return Z_OK; } static inline int Deflate( z_streamp strm, void *bufferPtr, size_t bufferSize, int flush, size_t *writtenPtr) { int e; strm->next_out = (Bytef *) bufferPtr; strm->avail_out = bufferSize; e = deflate(strm, flush); if (writtenPtr != NULL) { *writtenPtr = bufferSize - strm->avail_out; } return e; } static inline void AppendByteArray( Tcl_Obj *listObj, void *buffer, size_t size) { if (size > 0) { Tcl_Obj *baObj = Tcl_NewByteArrayObj((unsigned char *) buffer, size); Tcl_ListObjAppendElement(NULL, listObj, baObj); } } /* *---------------------------------------------------------------------- * * 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; 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 = (GzipHeader *)Tcl_Alloc(sizeof(GzipHeader)); memset(gzHeaderPtr, 0, sizeof(GzipHeader)); if (GenerateHeader(interp, dictObj, gzHeaderPtr, NULL) != TCL_OK) { Tcl_Free(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 = (GzipHeader *)Tcl_Alloc(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 = (ZlibStreamHandle *)Tcl_Alloc(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_EvalEx(interp, "::incr ::tcl::zlib::cmdcounter", -1, 0) != TCL_OK) { goto error; } Tcl_DStringInit(&cmdname); TclDStringAppendLiteral(&cmdname, "::tcl::zlib::streamcmd_"); TclDStringAppendObj(&cmdname, Tcl_GetObjResult(interp)); if (Tcl_FindCommand(interp, Tcl_DStringValue(&cmdname), NULL, 0) != NULL) { 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) { Tcl_Free(zshPtr->gzHeaderPtr); } Tcl_Free(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( void *cd) { ZlibStreamHandle *zshPtr = (ZlibStreamHandle *)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) { Tcl_Free(zshPtr->gzHeaderPtr); } Tcl_Free(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 == Tcl_GetByteArrayFromObj( compressionDictionaryObj, (size_t *)NULL))) { /* Missing or invalid compression dictionary */ compressionDictionaryObj = NULL; } 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. * *---------------------------------------------------------------------- */ #define BUFFER_SIZE_LIMIT 0xFFFF 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_t size = 0, outSize, toStore; unsigned char *bytes; 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; } bytes = Tcl_GetBytesFromObj(zshPtr->interp, data, &size); if (bytes == NULL) { return TCL_ERROR; } if (zshPtr->mode == TCL_ZLIB_STREAM_DEFLATE) { zshPtr->stream.next_in = bytes; zshPtr->stream.avail_in = size; /* * Must not do a zero-length compress unless finalizing. [Bug 25842c161] */ if (size == 0 && flush != Z_FINISH) { return TCL_OK; } if (HaveDictToSet(zshPtr)) { e = SetDeflateDictionary(&zshPtr->stream, zshPtr->compDictObj); if (e != Z_OK) { 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. However, we can also loop * around again so we also set an upper bound on the output buffer * size. */ outSize = deflateBound(&zshPtr->stream, size) + 100; if (outSize > BUFFER_SIZE_LIMIT) { outSize = BUFFER_SIZE_LIMIT; } dataTmp = (char *)Tcl_Alloc(outSize); while (1) { e = Deflate(&zshPtr->stream, dataTmp, outSize, flush, &toStore); /* * Test if we've filled the buffer up and have to ask deflate() to * give us some more. Note that the condition for needing to * repeat a buffer transfer when the result is Z_OK is whether * there is no more space in the buffer we provided; the zlib * library does not necessarily return a different code in that * case. [Bug b26e38a3e4] [Tk Bug 10f2e7872b] */ if ((e != Z_BUF_ERROR) && (e != Z_OK || toStore < outSize)) { if ((e == Z_OK) || (flush == Z_FINISH && e == Z_STREAM_END)) { break; } ConvertError(zshPtr->interp, e, zshPtr->stream.adler); return TCL_ERROR; } /* * Output buffer too small to hold the data being generated or we * are doing the end-of-stream flush (which can spit out masses of * data). This means we need to put a new buffer into place after * saving the old generated data to the outData list. */ AppendByteArray(zshPtr->outData, dataTmp, outSize); if (outSize < BUFFER_SIZE_LIMIT) { outSize = BUFFER_SIZE_LIMIT; /* There may be *lots* of data left to output... */ dataTmp = (char *)Tcl_Realloc(dataTmp, outSize); } } /* * And append the final data block to the outData list. */ AppendByteArray(zshPtr->outData, dataTmp, toStore); Tcl_Free(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. */ size_t count) /* Number of bytes to grab as a maximum, you * may get less! */ { ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) zshandle; int e; size_t listLen, i, itemLen = 0, dataPos = 0; Tcl_Obj *itemObj; unsigned char *dataPtr, *itemPtr; size_t existing = 0; /* * Getting beyond the of stream, just return empty string. */ if (zshPtr->streamEnd) { return TCL_OK; } if (NULL == Tcl_GetBytesFromObj(zshPtr->interp, data, &existing)) { return TCL_ERROR; } if (zshPtr->mode == TCL_ZLIB_STREAM_INFLATE) { if (count == TCL_INDEX_NONE) { /* * 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; } TclListObjLengthM(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) { 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); } }; TclListObjLengthM(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 { TclListObjLengthM(NULL, zshPtr->outData, &listLen); if (count == TCL_INDEX_NONE) { count = 0; for (i=0; ioutData, i, &itemObj); (void) 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) && (TclListObjLengthM(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 + dataPos >= count) { size_t len = count - dataPos; memcpy(dataPtr + dataPos, itemPtr + zshPtr->outPos, len); zshPtr->outPos += len; dataPos += len; if (zshPtr->outPos == itemLen) { zshPtr->outPos = 0; } } else { size_t 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, e = 0, extraSize = 0; size_t inLen = 0; Byte *inData = NULL; z_stream stream; GzipHeader header; gz_header *headerPtr = NULL; Tcl_Obj *obj; if (!interp) { return TCL_ERROR; } /* * Obtain the pointer to the byte array, we'll pass this pointer straight * to the deflate command. */ inData = Tcl_GetBytesFromObj(interp, data, &inLen); if (inData == NULL) { 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); memset(&stream, 0, sizeof(z_stream)); stream.avail_in = 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, size_t bufferSize, Tcl_Obj *gzipHeaderDictObj) { int wbits = 0, e = 0; size_t inLen = 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; } inData = Tcl_GetBytesFromObj(interp, data, &inLen); if (inData == NULL) { 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 = (char *)Tcl_Alloc(MAXPATHLEN); header.name = (Bytef *) nameBuf; header.name_max = MAXPATHLEN - 1; commentBuf = (char *)Tcl_Alloc(MAX_COMMENT_LEN); header.comment = (Bytef *) commentBuf; header.comm_max = MAX_COMMENT_LEN - 1; } 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 = 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) { inflateEnd(&stream); 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; break; } 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_NewWideIntObj(stream.total_out)); Tcl_Free(nameBuf); Tcl_Free(commentBuf); } Tcl_SetObjResult(interp, obj); return TCL_OK; error: TclDecrRefCount(obj); ConvertError(interp, e, stream.adler); if (nameBuf) { Tcl_Free(nameBuf); } if (commentBuf) { Tcl_Free(commentBuf); } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tcl_ZlibCRC32, Tcl_ZlibAdler32 -- * * Access to the checksumming engines. * *---------------------------------------------------------------------- */ unsigned int Tcl_ZlibCRC32( unsigned int crc, const unsigned char *buf, size_t len) { /* Nothing much to do, just wrap the crc32(). */ return crc32(crc, (Bytef *) buf, len); } unsigned int Tcl_ZlibAdler32( unsigned int adler, const unsigned char *buf, size_t len) { return adler32(adler, (Bytef *) buf, len); } /* *---------------------------------------------------------------------- * * ZlibCmd -- * * Implementation of the [zlib] command. * *---------------------------------------------------------------------- */ static int ZlibCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { int i, option, level = -1; size_t dlen = 0, start, buffersize = 0; Tcl_WideInt wideLen; 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 } command; 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 (command) { case CMD_ADLER: /* adler32 str ?startvalue? * -> checksum */ if (objc < 3 || objc > 4) { Tcl_WrongNumArgs(interp, 2, objv, "data ?startValue?"); return TCL_ERROR; } data = Tcl_GetBytesFromObj(interp, objv[2], &dlen); if (data == NULL) { 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); } 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; } data = Tcl_GetBytesFromObj(interp, objv[2], &dlen); if (data == NULL) { 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); } 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 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_GetWideIntFromObj(interp, objv[3], &wideLen) != TCL_OK) { return TCL_ERROR; } if (wideLen < MIN_NONSTREAM_BUFFER_SIZE || wideLen > MAX_BUFFER_SIZE) { goto badBuffer; } buffersize = wideLen; } 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_GetWideIntFromObj(interp, objv[3], &wideLen) != TCL_OK) { return TCL_ERROR; } if (wideLen < MIN_NONSTREAM_BUFFER_SIZE || wideLen > MAX_BUFFER_SIZE) { goto badBuffer; } buffersize = wideLen; } 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 MAX_BUFFER_SIZE) { goto badBuffer; } buffersize = wideLen; break; case 1: headerVarObj = objv[i+1]; TclNewObj(headerDictObj); 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 } fmt; 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, &fmt) != TCL_OK) { return TCL_ERROR; } /* * The format determines the compression mode and the options that may be * specified. */ switch (fmt) { 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 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; } if (compDictObj) { if (NULL == Tcl_GetBytesFromObj(interp, compDictObj, (size_t *)NULL)) { 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 } fmt; Tcl_Channel chan; int chanMode, format, mode = 0, level, i; 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 pushOptionsEnum {poDictionary, poHeader, poLevel, poLimit} option; Tcl_Obj *headerObj = NULL, *compDictObj = NULL; int limit = DEFAULT_BUFFER_SIZE; size_t 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, &fmt) != TCL_OK) { return TCL_ERROR; } switch (fmt) { 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-1) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "value missing for %s option", pushOptions[option])); Tcl_SetErrorCode(interp, "TCL", "ZIP", "NOVAL", NULL); return TCL_ERROR; } switch (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 (compDictObj && (NULL == Tcl_GetBytesFromObj(interp, compDictObj, (size_t *)NULL))) { return TCL_ERROR; } 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( void *cd, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_ZlibStream zstream = (Tcl_ZlibStream)cd; int 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 } command; 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 (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_NewWideIntObj(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( void *cd, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_ZlibStream zstream = (Tcl_ZlibStream)cd; int 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 } index; for (i=2; i= 0) { flush = -2; } else { flush = Z_SYNC_FLUSH; } break; case ao_fullflush: /* -fullflush */ if (flush >= 0) { flush = -2; } else { flush = Z_FULL_FLUSH; } break; case ao_finalize: /* -finalize */ if (flush >= 0) { 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) { size_t len = 0; if (NULL == Tcl_GetBytesFromObj(interp, compDictObj, &len)) { return TCL_ERROR; } 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( void *cd, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_ZlibStream zstream = (Tcl_ZlibStream)cd; int 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 } index; for (i=2; i= 0) { flush = -2; } else { flush = Z_SYNC_FLUSH; } break; case po_fullflush: /* -fullflush */ if (flush >= 0) { flush = -2; } else { flush = Z_FULL_FLUSH; } break; case po_finalize: /* -finalize */ if (flush >= 0) { 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) { size_t len = 0; if (NULL == Tcl_GetBytesFromObj(interp, compDictObj, &len)) { return TCL_ERROR; } 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( void *cd, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { ZlibStreamHandle *zshPtr = (ZlibStreamHandle *)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( void *instanceData, Tcl_Interp *interp, int flags) { ZlibChannelData *cd = (ZlibChannelData *)instanceData; int e, result = TCL_OK; size_t written; if ((flags & (TCL_CLOSE_READ | TCL_CLOSE_WRITE)) != 0) { return EINVAL; } /* * 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 { e = Deflate(&cd->outStream, cd->outBuffer, cd->outAllocated, Z_FINISH, &written); /* * Can't be sure that deflate() won't declare the buffer to be * full (with Z_BUF_ERROR) so handle that case. */ if (e == Z_BUF_ERROR) { e = Z_OK; written = cd->outAllocated; } 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 (written && Tcl_WriteRaw(cd->parent, cd->outBuffer, written) == TCL_IO_FAILURE) { /* 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); (void) deflateEnd(&cd->outStream); } else { /* * If we have unused bytes from the read input (overshot by * Z_STREAM_END or on possible error), unget them back to the parent * channel, so that they appear as not being read yet. */ if (cd->inStream.avail_in) { Tcl_Ungets (cd->parent, (char *)cd->inStream.next_in, cd->inStream.avail_in, 0); } (void) inflateEnd(&cd->inStream); } /* * Release all memory. */ if (cd->compDictObj) { Tcl_DecrRefCount(cd->compDictObj); cd->compDictObj = NULL; } if (cd->inBuffer) { Tcl_Free(cd->inBuffer); cd->inBuffer = NULL; } if (cd->outBuffer) { Tcl_Free(cd->outBuffer); cd->outBuffer = NULL; } Tcl_Free(cd); return result; } /* *---------------------------------------------------------------------- * * ZlibTransformInput -- * * Reader filter that does decompression. * *---------------------------------------------------------------------- */ static int ZlibTransformInput( void *instanceData, char *buf, int toRead, int *errorCodePtr) { ZlibChannelData *cd = (ZlibChannelData *)instanceData; Tcl_DriverInputProc *inProc = Tcl_ChannelInputProc(Tcl_GetChannelType(cd->parent)); int readBytes, gotBytes; if (cd->mode == TCL_ZLIB_STREAM_DEFLATE) { return inProc(Tcl_GetChannelInstanceData(cd->parent), buf, toRead, errorCodePtr); } gotBytes = 0; readBytes = cd->inStream.avail_in; /* how many bytes in buffer now */ while (!(cd->flags & STREAM_DONE) && toRead > 0) { unsigned int n; int decBytes; /* if starting from scratch or continuation after full decompression */ if (!cd->inStream.avail_in) { /* buffer to start, we can read to whole available buffer */ cd->inStream.next_in = (Bytef *) cd->inBuffer; } /* * If done - no read needed anymore, check we have to copy rest of * decompressed data, otherwise return with size (or 0 for Eof) */ if (cd->flags & STREAM_DECOMPRESS) { goto copyDecompressed; } /* * 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). */ /* Check free buffer size and adjust size of next chunk to read. */ n = cd->inAllocated - ((char *)cd->inStream.next_in - cd->inBuffer); if (n <= 0) { /* Normally unreachable: not enough input buffer to uncompress. * Todo: firstly try to realloc inBuffer upto MAX_BUFFER_SIZE. */ *errorCodePtr = ENOBUFS; return -1; } if (n > cd->readAheadLimit) { n = cd->readAheadLimit; } readBytes = Tcl_ReadRaw(cd->parent, (char *)cd->inStream.next_in, n); /* * 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 == -1) 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 == -1) { /* See ReflectInput() in tclIORTrans.c */ if (Tcl_InputBlocked(cd->parent) && (gotBytes > 0)) { break; } *errorCodePtr = Tcl_GetErrno(); return -1; } /* more bytes (or Eof if readBytes == 0) */ cd->inStream.avail_in += readBytes; copyDecompressed: /* * Transform the read chunk, if 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. * For the case readBytes is 0 which signaling Eof in parent, the * partial data waiting is converted and returned. */ decBytes = ResultDecompress(cd, buf, toRead, (readBytes != 0) ? Z_NO_FLUSH : Z_SYNC_FLUSH, errorCodePtr); if (decBytes == -1) { return -1; } gotBytes += decBytes; buf += decBytes; toRead -= decBytes; if (((decBytes == 0) || (cd->flags & STREAM_DECOMPRESS))) { /* * The drain delivered nothing (or buffer too small to decompress). * Time to deliver what we've got. */ if (!gotBytes && !(cd->flags & STREAM_DONE)) { /* if no-data, but not ready - avoid signaling Eof, * continue in blocking mode, otherwise EAGAIN */ if (Tcl_InputBlocked(cd->parent)) { continue; } *errorCodePtr = EAGAIN; return -1; } break; } /* * Loop until the request is satisfied (or no data available from * above, possibly EOF). */ } return gotBytes; } /* *---------------------------------------------------------------------- * * ZlibTransformOutput -- * * Writer filter that does compression. * *---------------------------------------------------------------------- */ static int ZlibTransformOutput( void *instanceData, const char *buf, int toWrite, int *errorCodePtr) { ZlibChannelData *cd = (ZlibChannelData *)instanceData; Tcl_DriverOutputProc *outProc = Tcl_ChannelOutputProc(Tcl_GetChannelType(cd->parent)); int e; size_t produced; Tcl_Obj *errObj; if (cd->mode == TCL_ZLIB_STREAM_INFLATE) { return outProc(Tcl_GetChannelInstanceData(cd->parent), buf, toWrite, errorCodePtr); } /* * No zero-length writes. Flushes must be explicit. */ if (toWrite == 0) { return 0; } cd->outStream.next_in = (Bytef *) buf; cd->outStream.avail_in = toWrite; while (cd->outStream.avail_in > 0) { e = Deflate(&cd->outStream, cd->outBuffer, cd->outAllocated, Z_NO_FLUSH, &produced); if (e != Z_OK || produced == 0) { break; } if (Tcl_WriteRaw(cd->parent, cd->outBuffer, produced) == TCL_IO_FAILURE) { *errorCodePtr = Tcl_GetErrno(); return -1; } } 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; } /* *---------------------------------------------------------------------- * * ZlibTransformFlush -- * * How to perform a flush of a compressing transform. * *---------------------------------------------------------------------- */ static int ZlibTransformFlush( Tcl_Interp *interp, ZlibChannelData *cd, int flushType) { int e; size_t len; cd->outStream.avail_in = 0; do { /* * Get the bytes to go out of the compression engine. */ e = Deflate(&cd->outStream, cd->outBuffer, cd->outAllocated, flushType, &len); if (e != Z_OK && e != Z_BUF_ERROR) { ConvertError(interp, e, cd->outStream.adler); return TCL_ERROR; } /* * Write the bytes we've received to the next layer. */ if (len > 0 && Tcl_WriteRaw(cd->parent, cd->outBuffer, len) == TCL_IO_FAILURE) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "problem flushing channel: %s", Tcl_PosixError(interp))); return TCL_ERROR; } /* * If we get to this point, either we're in the Z_OK or the * Z_BUF_ERROR state. In the former case, we're done. In the latter * case, it's because there's more bytes to go than would fit in the * buffer we provided, and we need to go round again to get some more. * * We also stop the loop if we would have done a zero-length write. * Those can cause problems at the OS level. */ } while (len > 0 && e == Z_BUF_ERROR); return TCL_OK; } /* *---------------------------------------------------------------------- * * ZlibTransformSetOption -- * * Writing side of [fconfigure] on our channel. * *---------------------------------------------------------------------- */ static int ZlibTransformSetOption( /* not used */ void *instanceData, Tcl_Interp *interp, const char *optionName, const char *value) { ZlibChannelData *cd = (ZlibChannelData *)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); if (NULL == Tcl_GetBytesFromObj(interp, compDictObj, (size_t *)NULL)) { Tcl_DecrRefCount(compDictObj); return TCL_ERROR; } 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. */ return ZlibTransformFlush(interp, cd, flushType); } } 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( void *instanceData, Tcl_Interp *interp, const char *optionName, Tcl_DString *dsPtr) { ZlibChannelData *cd = (ZlibChannelData *)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, TclGetString(cd->compDictObj)); } else { Tcl_DStringAppendElement(dsPtr, ""); } } else { if (cd->compDictObj) { size_t length; const char *str = Tcl_GetStringFromObj(cd->compDictObj, &length); Tcl_DStringAppend(dsPtr, str, length); } return TCL_OK; } } /* * 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; TclNewObj(tmpObj); ExtractHeader(&cd->inHeader.header, tmpObj); if (optionName == NULL) { Tcl_DStringAppendElement(dsPtr, "-header"); Tcl_DStringAppendElement(dsPtr, TclGetString(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( void *instanceData, int mask) { ZlibChannelData *cd = (ZlibChannelData *)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) || !(cd->flags & STREAM_DECOMPRESS)) { ZlibTransformEventTimerKill(cd); } else if (cd->timer == NULL) { cd->timer = Tcl_CreateTimerHandler(SYNTHETIC_EVENT_TIME, ZlibTransformTimerRun, cd); } } static int ZlibTransformEventHandler( void *instanceData, int interestMask) { ZlibChannelData *cd = (ZlibChannelData *)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( void *clientData) { ZlibChannelData *cd = (ZlibChannelData *)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( void *instanceData, int direction, void **handlePtr) { ZlibChannelData *cd = (ZlibChannelData *)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( void *instanceData, int mode) { ZlibChannelData *cd = (ZlibChannelData *)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 = (ZlibChannelData *)Tcl_Alloc(sizeof(ZlibChannelData)); Tcl_Channel chan; int wbits = 0; 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, (size_t *)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) { if (inflateInit2(&cd->inStream, wbits) != Z_OK) { goto error; } cd->inAllocated = DEFAULT_BUFFER_SIZE; if (cd->inAllocated < cd->readAheadLimit) { cd->inAllocated = cd->readAheadLimit; } cd->inBuffer = (char *)Tcl_Alloc(cd->inAllocated); if (cd->flags & IN_HEADER) { if (inflateGetHeader(&cd->inStream, &cd->inHeader.header) != Z_OK) { goto error; } } if (cd->format == TCL_ZLIB_FORMAT_RAW && cd->compDictObj) { if (SetInflateDictionary(&cd->inStream, cd->compDictObj) != Z_OK) { goto error; } } } else { if (deflateInit2(&cd->outStream, level, Z_DEFLATED, wbits, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) { goto error; } cd->outAllocated = DEFAULT_BUFFER_SIZE; cd->outBuffer = (char *)Tcl_Alloc(cd->outAllocated); if (cd->flags & OUT_HEADER) { if (deflateSetHeader(&cd->outStream, &cd->outHeader.header) != Z_OK) { goto error; } } if (cd->compDictObj) { if (SetDeflateDictionary(&cd->outStream, cd->compDictObj) != Z_OK) { goto error; } } } 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) { Tcl_Free(cd->inBuffer); inflateEnd(&cd->inStream); } if (cd->outBuffer) { Tcl_Free(cd->outBuffer); deflateEnd(&cd->outStream); } if (cd->compDictObj) { Tcl_DecrRefCount(cd->compDictObj); } Tcl_Free(cd); return NULL; } /* *---------------------------------------------------------------------- * * ResultDecompress -- * * Extract uncompressed bytes from the compression engine and store them * in our buffer (buf) up to toRead bytes. * * Result: * Number of bytes decompressed or -1 if error (with *errorCodePtr updated with reason). * * Side effects: * After execution it updates cd->inStream (next_in, avail_in) to reflect * the data that has been decompressed. * *---------------------------------------------------------------------- */ static int ResultDecompress( ZlibChannelData *cd, char *buf, int toRead, int flush, int *errorCodePtr) { int e, written, resBytes = 0; Tcl_Obj *errObj; cd->flags &= ~STREAM_DECOMPRESS; cd->inStream.next_out = (Bytef *) buf; cd->inStream.avail_out = toRead; while (cd->inStream.avail_out > 0) { 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. */ e = inflate(&cd->inStream, flush); } } /* * avail_out is now the left over space in the output. Therefore * "toRead - avail_out" is the amount of bytes generated. */ written = toRead - cd->inStream.avail_out; /* * The cases where we're definitely done. */ if (e == Z_STREAM_END) { cd->flags |= STREAM_DONE; resBytes += written; break; } if (e == Z_OK) { if (written == 0) { break; } resBytes += written; } if ((flush == Z_SYNC_FLUSH) && (e == Z_BUF_ERROR)) { break; } /* * 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) { break; } } if (!(cd->flags & STREAM_DONE)) { /* if we have pending input data, but no available output buffer */ if (cd->inStream.avail_in && !cd->inStream.avail_out) { /* next time try to decompress it got readable (new output buffer) */ cd->flags |= STREAM_DECOMPRESS; } } return resBytes; 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 -1; } /* *---------------------------------------------------------------------- * 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_EvalEx(interp, "namespace eval ::tcl::zlib {variable cmdcounter 0}", -1, 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, "utf-8"); /* * Allow command type introspection to do something sensible with streams. */ TclRegisterCommandTypeName(ZlibStreamCmd, "zlibStream"); /* * Formally provide the package as a Tcl built-in. */ return Tcl_PkgProvideEx(interp, "tcl::zlib", TCL_ZLIB_VERSION, NULL); } /* *---------------------------------------------------------------------- * 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) { if (interp) { 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, size_t count) { return TCL_OK; } int Tcl_ZlibDeflate( Tcl_Interp *interp, int format, Tcl_Obj *data, int level, Tcl_Obj *gzipHeaderDictObj) { if (interp) { 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, size_t bufferSize, Tcl_Obj *gzipHeaderDictObj) { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj("unimplemented", -1)); Tcl_SetErrorCode(interp, "TCL", "UNIMPLEMENTED", NULL); } return TCL_ERROR; } unsigned int Tcl_ZlibCRC32( TCL_UNUSED(unsigned int), TCL_UNUSED(const unsigned char *), TCL_UNUSED(size_t)) { return 0; } unsigned int Tcl_ZlibAdler32( TCL_UNUSED(unsigned int), TCL_UNUSED(const unsigned char *), TCL_UNUSED(size_t)) { 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: */