@@ -20,22 +20,23 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.


#include <v8.h>
#include "v8.h"
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <zlib.h>

#include <node.h>
#include <node_buffer.h>
#include "zlib.h"
#include "node.h"
#include "node_buffer.h"


namespace node {
using namespace v8;


static Persistent<String> callback_sym;
static Persistent<String> onerror_sym;

enum node_zlib_mode {
DEFLATE = 1,
@@ -47,35 +48,34 @@ enum node_zlib_mode {
UNZIP
};

template <node_zlib_mode mode> class ZCtx;


void InitZlib(v8::Handle<v8::Object> target);


/**
* Deflate/Inflate
*/
template <node_zlib_mode mode> class ZCtx : public ObjectWrap {
class ZCtx : public ObjectWrap {
public:

ZCtx() : ObjectWrap() {
}
ZCtx(node_zlib_mode mode) : ObjectWrap(), dictionary_(NULL), mode_(mode) {}

~ZCtx() {
if (mode == DEFLATE || mode == GZIP || mode == DEFLATERAW) {
if (mode_ == DEFLATE || mode_ == GZIP || mode_ == DEFLATERAW) {
(void)deflateEnd(&strm_);
} else if (mode == INFLATE || mode == GUNZIP || mode == INFLATERAW) {
} else if (mode_ == INFLATE || mode_ == GUNZIP || mode_ == INFLATERAW) {
(void)inflateEnd(&strm_);
}

if (dictionary_ != NULL) delete[] dictionary_;
}

// write(flush, in, in_off, in_len, out, out_off, out_len)
static Handle<Value> Write(const Arguments& args) {
HandleScope scope;
assert(args.Length() == 7);

ZCtx<mode> *ctx = ObjectWrap::Unwrap< ZCtx<mode> >(args.This());
ZCtx *ctx = ObjectWrap::Unwrap<ZCtx>(args.This());
assert(ctx->init_done_ && "write before init");

assert(!ctx->write_in_progress_ && "write already in progress");
@@ -124,8 +124,8 @@ template <node_zlib_mode mode> class ZCtx : public ObjectWrap {

uv_queue_work(uv_default_loop(),
work_req,
ZCtx<mode>::Process,
ZCtx<mode>::After);
ZCtx::Process,
ZCtx::After);

ctx->Ref();

@@ -138,28 +138,46 @@ template <node_zlib_mode mode> class ZCtx : public ObjectWrap {
// for a single write() call, until all of the input bytes have
// been consumed.
static void Process(uv_work_t* work_req) {
ZCtx<mode> *ctx = container_of(work_req, ZCtx<mode>, work_req_);
ZCtx *ctx = container_of(work_req, ZCtx, work_req_);

// If the avail_out is left at 0, then it means that it ran out
// of room. If there was avail_out left over, then it means
// that all of the input was consumed.
int err;
switch (mode) {
switch (ctx->mode_) {
case DEFLATE:
case GZIP:
case DEFLATERAW:
err = deflate(&ctx->strm_, ctx->flush_);
ctx->err_ = deflate(&ctx->strm_, ctx->flush_);
break;
case UNZIP:
case INFLATE:
case GUNZIP:
case INFLATERAW:
err = inflate(&ctx->strm_, ctx->flush_);
ctx->err_ = inflate(&ctx->strm_, ctx->flush_);

// If data was encoded with dictionary
if (ctx->err_ == Z_NEED_DICT) {
assert(ctx->dictionary_ != NULL && "Stream has no dictionary");
if (ctx->dictionary_ != NULL) {

// Load it
ctx->err_ = inflateSetDictionary(&ctx->strm_,
ctx->dictionary_,
ctx->dictionary_len_);
assert(ctx->err_ == Z_OK && "Failed to set dictionary");
if (ctx->err_ == Z_OK) {

// And try to decode again
ctx->err_ = inflate(&ctx->strm_, ctx->flush_);
}
}
}
break;
default:
assert(0 && "wtf?");
}
assert(err != Z_STREAM_ERROR);

// pass any errors back to the main thread to deal with.

// now After will emit the output, and
// either schedule another call to Process,
@@ -169,7 +187,20 @@ template <node_zlib_mode mode> class ZCtx : public ObjectWrap {
// v8 land!
static void After(uv_work_t* work_req) {
HandleScope scope;
ZCtx<mode> *ctx = container_of(work_req, ZCtx<mode>, work_req_);
ZCtx *ctx = container_of(work_req, ZCtx, work_req_);

// Acceptable error states depend on the type of zlib stream.
switch (ctx->err_) {
case Z_OK:
case Z_STREAM_END:
case Z_BUF_ERROR:
// normal statuses, not fatal
break;
default:
// something else.
ZCtx::Error(ctx, "Zlib error");
return;
}

Local<Integer> avail_out = Integer::New(ctx->strm_.avail_out);
Local<Integer> avail_in = Integer::New(ctx->strm_.avail_in);
@@ -185,9 +216,37 @@ template <node_zlib_mode mode> class ZCtx : public ObjectWrap {
ctx->Unref();
}

static void Error(ZCtx *ctx, const char *msg_) {
const char *msg;
if (ctx->strm_.msg != NULL) {
msg = ctx->strm_.msg;
} else {
msg = msg_;
}

assert(ctx->handle_->Get(onerror_sym)->IsFunction() &&
"Invalid error handler");
HandleScope scope;
Local<Value> args[2] = { String::New(msg),
Local<Value>::New(Number::New(ctx->err_)) };
MakeCallback(ctx->handle_, "onerror", ARRAY_SIZE(args), args);

// no hope of rescue.
ctx->Unref();
}

static Handle<Value> New(const Arguments& args) {
HandleScope scope;
ZCtx<mode> *ctx = new ZCtx<mode>();
if (args.Length() < 1 || !args[0]->IsInt32()) {
return ThrowException(Exception::TypeError(String::New("Bad argument")));
}
node_zlib_mode mode = (node_zlib_mode) args[0]->Int32Value();

if (mode < DEFLATE || mode > UNZIP) {
return ThrowException(Exception::TypeError(String::New("Bad argument")));
}

ZCtx *ctx = new ZCtx(mode);
ctx->Wrap(args.This());
return args.This();
}
@@ -196,10 +255,10 @@ template <node_zlib_mode mode> class ZCtx : public ObjectWrap {
static Handle<Value> Init(const Arguments& args) {
HandleScope scope;

assert(args.Length() == 4 &&
"init(windowBits, level, memLevel, strategy)");
assert((args.Length() == 4 || args.Length() == 5) &&
"init(windowBits, level, memLevel, strategy, [dictionary])");

ZCtx<mode> *ctx = ObjectWrap::Unwrap< ZCtx<mode> >(args.This());
ZCtx *ctx = ObjectWrap::Unwrap<ZCtx>(args.This());

int windowBits = args[0]->Uint32Value();
assert((windowBits >= 8 && windowBits <= 15) && "invalid windowBits");
@@ -217,12 +276,35 @@ template <node_zlib_mode mode> class ZCtx : public ObjectWrap {
strategy == Z_FIXED ||
strategy == Z_DEFAULT_STRATEGY) && "invalid strategy");

Init(ctx, level, windowBits, memLevel, strategy);
char* dictionary = NULL;
size_t dictionary_len = 0;
if (args.Length() >= 5 && Buffer::HasInstance(args[4])) {
Local<Object> dictionary_ = args[4]->ToObject();

dictionary_len = Buffer::Length(dictionary_);
dictionary = new char[dictionary_len];

memcpy(dictionary, Buffer::Data(dictionary_), dictionary_len);
}

Init(ctx, level, windowBits, memLevel, strategy,
dictionary, dictionary_len);
SetDictionary(ctx);
return Undefined();
}

static Handle<Value> Reset(const Arguments &args) {
HandleScope scope;

ZCtx *ctx = ObjectWrap::Unwrap<ZCtx>(args.This());

Reset(ctx);
SetDictionary(ctx);
return Undefined();
}

static void Init(ZCtx *ctx, int level, int windowBits, int memLevel,
int strategy) {
int strategy, char* dictionary, size_t dictionary_len) {
ctx->level_ = level;
ctx->windowBits_ = windowBits;
ctx->memLevel_ = memLevel;
@@ -234,43 +316,93 @@ template <node_zlib_mode mode> class ZCtx : public ObjectWrap {

ctx->flush_ = Z_NO_FLUSH;

if (mode == GZIP || mode == GUNZIP) {
ctx->err_ = Z_OK;

if (ctx->mode_ == GZIP || ctx->mode_ == GUNZIP) {
ctx->windowBits_ += 16;
}

if (mode == UNZIP) {
if (ctx->mode_ == UNZIP) {
ctx->windowBits_ += 32;
}

if (mode == DEFLATERAW || mode == INFLATERAW) {
if (ctx->mode_ == DEFLATERAW || ctx->mode_ == INFLATERAW) {
ctx->windowBits_ *= -1;
}

int err;
switch (mode) {
switch (ctx->mode_) {
case DEFLATE:
case GZIP:
case DEFLATERAW:
err = deflateInit2(&(ctx->strm_),
ctx->level_,
Z_DEFLATED,
ctx->windowBits_,
ctx->memLevel_,
ctx->strategy_);
ctx->err_ = deflateInit2(&ctx->strm_,
ctx->level_,
Z_DEFLATED,
ctx->windowBits_,
ctx->memLevel_,
ctx->strategy_);
break;
case INFLATE:
case GUNZIP:
case INFLATERAW:
case UNZIP:
err = inflateInit2(&(ctx->strm_), ctx->windowBits_);
ctx->err_ = inflateInit2(&ctx->strm_, ctx->windowBits_);
break;
default:
assert(0 && "wtf?");
}

if (ctx->err_ != Z_OK) {
ZCtx::Error(ctx, "Init error");
}


ctx->dictionary_ = reinterpret_cast<Bytef *>(dictionary);
ctx->dictionary_len_ = dictionary_len;

ctx->write_in_progress_ = false;
ctx->init_done_ = true;
assert(err == Z_OK);
}

static void SetDictionary(ZCtx* ctx) {
if (ctx->dictionary_ == NULL) return;

ctx->err_ = Z_OK;

switch (ctx->mode_) {
case DEFLATE:
case DEFLATERAW:
ctx->err_ = deflateSetDictionary(&ctx->strm_,
ctx->dictionary_,
ctx->dictionary_len_);
break;
default:
break;
}

if (ctx->err_ != Z_OK) {
ZCtx::Error(ctx, "Failed to set dictionary");
}
}

static void Reset(ZCtx* ctx) {
ctx->err_ = Z_OK;

switch (ctx->mode_) {
case DEFLATE:
case DEFLATERAW:
ctx->err_ = deflateReset(&ctx->strm_);
break;
case INFLATE:
case INFLATERAW:
ctx->err_ = inflateReset(&ctx->strm_);
break;
default:
break;
}

if (ctx->err_ != Z_OK) {
ZCtx::Error(ctx, "Failed to reset stream");
}
}

private:
@@ -283,45 +415,47 @@ template <node_zlib_mode mode> class ZCtx : public ObjectWrap {
int memLevel_;
int strategy_;

int err_;

Bytef* dictionary_;
size_t dictionary_len_;

int flush_;

int chunk_size_;

bool write_in_progress_;

uv_work_t work_req_;
node_zlib_mode mode_;
};


#define NODE_ZLIB_CLASS(mode, name) \
{ \
Local<FunctionTemplate> z = FunctionTemplate::New(ZCtx<mode>::New); \
z->InstanceTemplate()->SetInternalFieldCount(1); \
NODE_SET_PROTOTYPE_METHOD(z, "write", ZCtx<mode>::Write); \
NODE_SET_PROTOTYPE_METHOD(z, "init", ZCtx<mode>::Init); \
z->SetClassName(String::NewSymbol(name)); \
target->Set(String::NewSymbol(name), z->GetFunction()); \
}

void InitZlib(Handle<Object> target) {
HandleScope scope;

NODE_ZLIB_CLASS(INFLATE, "Inflate")
NODE_ZLIB_CLASS(DEFLATE, "Deflate")
NODE_ZLIB_CLASS(INFLATERAW, "InflateRaw")
NODE_ZLIB_CLASS(DEFLATERAW, "DeflateRaw")
NODE_ZLIB_CLASS(GZIP, "Gzip")
NODE_ZLIB_CLASS(GUNZIP, "Gunzip")
NODE_ZLIB_CLASS(UNZIP, "Unzip")
Local<FunctionTemplate> z = FunctionTemplate::New(ZCtx::New);

z->InstanceTemplate()->SetInternalFieldCount(1);

NODE_SET_PROTOTYPE_METHOD(z, "write", ZCtx::Write);
NODE_SET_PROTOTYPE_METHOD(z, "init", ZCtx::Init);
NODE_SET_PROTOTYPE_METHOD(z, "reset", ZCtx::Reset);

z->SetClassName(String::NewSymbol("Zlib"));
target->Set(String::NewSymbol("Zlib"), z->GetFunction());

callback_sym = NODE_PSYMBOL("callback");
onerror_sym = NODE_PSYMBOL("onerror");

NODE_DEFINE_CONSTANT(target, Z_NO_FLUSH);
NODE_DEFINE_CONSTANT(target, Z_PARTIAL_FLUSH);
NODE_DEFINE_CONSTANT(target, Z_SYNC_FLUSH);
NODE_DEFINE_CONSTANT(target, Z_FULL_FLUSH);
NODE_DEFINE_CONSTANT(target, Z_FINISH);
NODE_DEFINE_CONSTANT(target, Z_BLOCK);

// return/error codes
NODE_DEFINE_CONSTANT(target, Z_OK);
NODE_DEFINE_CONSTANT(target, Z_STREAM_END);
NODE_DEFINE_CONSTANT(target, Z_NEED_DICT);
@@ -331,6 +465,7 @@ void InitZlib(Handle<Object> target) {
NODE_DEFINE_CONSTANT(target, Z_MEM_ERROR);
NODE_DEFINE_CONSTANT(target, Z_BUF_ERROR);
NODE_DEFINE_CONSTANT(target, Z_VERSION_ERROR);

NODE_DEFINE_CONSTANT(target, Z_NO_COMPRESSION);
NODE_DEFINE_CONSTANT(target, Z_BEST_SPEED);
NODE_DEFINE_CONSTANT(target, Z_BEST_COMPRESSION);
@@ -342,6 +477,14 @@ void InitZlib(Handle<Object> target) {
NODE_DEFINE_CONSTANT(target, Z_DEFAULT_STRATEGY);
NODE_DEFINE_CONSTANT(target, ZLIB_VERNUM);

NODE_DEFINE_CONSTANT(target, DEFLATE);
NODE_DEFINE_CONSTANT(target, INFLATE);
NODE_DEFINE_CONSTANT(target, GZIP);
NODE_DEFINE_CONSTANT(target, GUNZIP);
NODE_DEFINE_CONSTANT(target, DEFLATERAW);
NODE_DEFINE_CONSTANT(target, INFLATERAW);
NODE_DEFINE_CONSTANT(target, UNZIP);

target->Set(String::NewSymbol("ZLIB_VERSION"), String::New(ZLIB_VERSION));
}

@@ -27,12 +27,46 @@ var common = require('../common.js'),

var nonStringInputs = [1, true, {a: 1}, ['a']];

console.error('Doing the non-strings');
nonStringInputs.forEach(function(input) {
// zlib.gunzip should not throw an error when called with bad input.
assert.doesNotThrow(function () {
zlib.gunzip(input, function (err, buffer) {
assert.doesNotThrow(function() {
zlib.gunzip(input, function(err, buffer) {
// zlib.gunzip should pass the error to the callback.
assert.ok(err);
});
});
});

console.error('Doing the unzips');
// zlib.Unzip classes need to get valid data, or else they'll throw.
var unzips = [ zlib.Unzip(),
zlib.Gunzip(),
zlib.Inflate(),
zlib.InflateRaw() ];
var hadError = [];
unzips.forEach(function (uz, i) {
console.error('Error for '+uz.constructor.name);
uz.on('error', function(er) {
console.error('Error event', er);
hadError[i] = true;

// to be friendly to the Stream API, zlib objects just return true and
// ignore data on the floor after an error. It's up to the user to
// catch the 'error' event and do something intelligent. They do not
// emit any more data, however.
assert.equal(uz.write('also invalid'), true);
assert.equal(uz.end(), true);
});

uz.on('end', function(er) {
throw new Error('end event should not be emitted '+uz.constructor.name);
});

// this will trigger error event
uz.write('this is not valid compressed data.');
});

process.on('exit', function() {
assert.deepEqual(hadError, [true, true, true, true], 'expect 4 errors');
});
@@ -19,6 +19,7 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

var common = require('../common');
var crypto = require('crypto');
var stream = require('stream');
var Stream = stream.Stream;