#define CHUNK 0x4000
#define windowBits 15
#define DEFLATE_ENABLE_ZLIB_GZIP 16
#define INFLATE_ENABLE_ZLIB_GZIP 32
#define CALL_ZLIB(x) \
zlib_status = x; \
if
(zlib_status < 0) { \
deflateEnd (& gf->strm); \
croak (
"zlib call %s returned error status %d"
, \
#x, zlib_status); \
} \
typedef
struct
{
SV * in;
const
char
* in_char;
STRLEN in_length;
z_stream strm;
int
level;
unsigned
char
out_buffer[CHUNK];
int
wb;
SV * file_name;
SV * mod_time;
unsigned
int
is_gzip : 1;
unsigned
int
is_raw : 1;
unsigned
int
copy_perl_flags : 1;
unsigned
int
user_object : 1;
}
gzip_faster_t;
#define GZIP_PERL_ID "GF\1\0"
#define GZIP_PERL_ID_LENGTH 4
#define GZIP_PERL_LENGTH 1
#define EXTRA_LENGTH GZIP_PERL_ID_LENGTH + GZIP_PERL_LENGTH
#define GZIP_PERL_UTF8 (1<<0)
static
void
gf_set_up (gzip_faster_t * gf)
{
gf->in_char = SvPV (gf->in, gf->in_length);
gf->strm.next_in = (unsigned
char
*) gf->in_char;
gf->strm.avail_in = gf->in_length;
gf->strm.zalloc = Z_NULL;
gf->strm.zfree = Z_NULL;
gf->strm.opaque = Z_NULL;
if
(! gf->user_object) {
gf->level = Z_DEFAULT_COMPRESSION;
}
gf->wb = windowBits;
}
#define UO \
if
(! gf->user_object) { \
croak (
"THIS IS NOT A USER OBJECT"
); \
}
static
void
gf_delete_file_name (gzip_faster_t * gf)
{
UO;
if
(gf->file_name) {
SvREFCNT_dec (gf->file_name);
gf->file_name = 0;
}
}
static
void
gf_delete_mod_time (gzip_faster_t * gf)
{
UO;
if
(gf->mod_time) {
SvREFCNT_dec (gf->mod_time);
gf->mod_time = 0;
}
}
static
void
gf_set_file_name (gzip_faster_t * gf, SV * file_name)
{
UO;
if
(gf->file_name) {
gf_delete_file_name (gf);
}
SvREFCNT_inc (file_name);
gf->file_name = file_name;
}
static
SV *
gf_get_file_name (gzip_faster_t * gf)
{
UO;
if
(gf->file_name) {
return
gf->file_name;
}
return
& PL_sv_undef;
}
static
void
gf_set_mod_time (gzip_faster_t * gf, SV * mod_time)
{
UO;
if
(gf->mod_time) {
gf_delete_mod_time (gf);
}
SvREFCNT_inc (mod_time);
gf->mod_time = mod_time;
}
static
SV *
gf_get_mod_time (gzip_faster_t * gf)
{
UO;
if
(gf->mod_time) {
return
gf->mod_time;
}
return
& PL_sv_undef;
}
static
void
check_avail_in (gzip_faster_t * gf)
{
if
(gf->strm.avail_in != 0) {
croak (
"Zlib did not finish processing the string: %d bytes left"
,
gf->strm.avail_in);
}
}
static
void
check_zlib_status (
int
zlib_status)
{
if
(zlib_status != Z_STREAM_END) {
croak (
"Zlib did not come to the end of the string: zlib_status = %d"
,
zlib_status);
}
}
static
SV *
gzip_faster (gzip_faster_t * gf)
{
SV * zipped;
int
zlib_status;
if
(! SvOK (gf->in)) {
warn (
"Empty input"
);
return
& PL_sv_undef;
}
gf_set_up (gf);
if
(gf->in_length == 0) {
warn (
"Attempt to compress empty string"
);
return
& PL_sv_undef;
}
if
(gf->is_gzip) {
if
(gf->is_raw) {
croak (
"Raw deflate and gzip are incompatible"
);
}
gf->wb += DEFLATE_ENABLE_ZLIB_GZIP;
}
else
{
if
(gf->is_raw) {
gf->wb = -gf->wb;
}
}
CALL_ZLIB (deflateInit2 (& gf->strm, gf->level, Z_DEFLATED,
gf->wb, 8,
Z_DEFAULT_STRATEGY));
if
(gf->user_object) {
if
(gf->is_gzip) {
gz_header header = {0};
unsigned
char
extra[EXTRA_LENGTH];
int
set_header;
set_header = 0;
if
(gf->copy_perl_flags) {
memcpy
(extra, GZIP_PERL_ID, GZIP_PERL_ID_LENGTH);
extra[GZIP_PERL_ID_LENGTH] = 0;
if
(SvUTF8 (gf->in)) {
extra[GZIP_PERL_ID_LENGTH] |= GZIP_PERL_UTF8;
}
header.extra = extra;
header.extra_len = EXTRA_LENGTH;
set_header++;
}
if
(gf->file_name) {
char
* fn;
fn = SvPV_nolen (gf->file_name);
header.name = (Bytef *) fn;
set_header++;
}
if
(gf->mod_time) {
header.
time
= (uLong) SvUV (gf->mod_time);
set_header++;
}
if
(set_header) {
CALL_ZLIB (deflateSetHeader (& gf->strm, & header));
}
}
else
{
if
(gf->copy_perl_flags) {
warn (
"wrong format: perl flags not copied: use gzip_format(1)"
);
}
if
(gf->file_name || gf->mod_time) {
warn (
"wrong format: file name/modification time ignored: use gzip_format(1)"
);
}
}
}
zipped = 0;
do
{
unsigned
int
have;
gf->strm.avail_out = CHUNK;
gf->strm.next_out = gf->out_buffer;
zlib_status = deflate (& gf->strm, Z_FINISH);
switch
(zlib_status) {
case
Z_OK:
case
Z_STREAM_END:
case
Z_BUF_ERROR:
break
;
case
Z_STREAM_ERROR:
deflateEnd (& gf->strm);
croak (
"Z_STREAM_ERROR from zlib during deflate"
);
default
:
deflateEnd (& gf->strm);
croak (
"Unknown status %d from deflate"
, zlib_status);
break
;
}
have = CHUNK - gf->strm.avail_out;
if
(! zipped) {
zipped = newSVpv ((
const
char
*) gf->out_buffer, have);
}
else
{
sv_catpvn (zipped, (
const
char
*) gf->out_buffer, have);
}
}
while
(gf->strm.avail_out == 0);
check_avail_in (gf);
check_zlib_status (zlib_status);
deflateEnd (& gf->strm);
if
(gf->user_object) {
if
(gf->file_name) {
gf_delete_file_name (gf);
}
}
return
zipped;
}
#define GF_FILE_NAME_MAX 0x400
static
SV *
gunzip_faster (gzip_faster_t * gf)
{
SV * plain;
int
zlib_status;
gz_header header;
unsigned
char
name[GF_FILE_NAME_MAX];
unsigned
char
extra[EXTRA_LENGTH];
if
(! SvOK (gf->in)) {
warn (
"Empty input"
);
return
& PL_sv_undef;
}
gf_set_up (gf);
if
(gf->in_length == 0) {
warn (
"Attempt to uncompress empty string"
);
return
& PL_sv_undef;
}
if
(gf->is_gzip) {
if
(gf->is_raw) {
croak (
"Raw deflate and gzip are incompatible"
);
}
gf->wb += INFLATE_ENABLE_ZLIB_GZIP;
}
else
{
if
(gf->is_raw) {
gf->wb = -gf->wb;
}
}
CALL_ZLIB (inflateInit2 (& gf->strm, gf->wb));
if
(gf->user_object) {
if
(gf->is_gzip) {
if
(gf->copy_perl_flags) {
header.extra = extra;
header.extra_max = EXTRA_LENGTH;
}
if
(gf->file_name) {
gf_delete_file_name (gf);
}
if
(gf->mod_time) {
gf_delete_mod_time (gf);
}
header.name = name;
header.name_max = GF_FILE_NAME_MAX;
inflateGetHeader (& gf->strm, & header);
}
}
plain = 0;
do
{
unsigned
int
have;
gf->strm.avail_out = CHUNK;
gf->strm.next_out = gf->out_buffer;
zlib_status = inflate (& gf->strm, Z_FINISH);
switch
(zlib_status) {
case
Z_OK:
case
Z_STREAM_END:
case
Z_BUF_ERROR:
break
;
case
Z_DATA_ERROR:
inflateEnd (& gf->strm);
croak (
"Data input to inflate is not in libz format"
);
break
;
case
Z_MEM_ERROR:
inflateEnd (& gf->strm);
croak (
"Out of memory during inflate"
);
case
Z_STREAM_ERROR:
inflateEnd (& gf->strm);
croak (
"Internal error in zlib"
);
default
:
inflateEnd (& gf->strm);
croak (
"Unknown status %d from inflate"
, zlib_status);
break
;
}
have = CHUNK - gf->strm.avail_out;
if
(! plain) {
plain = newSVpv ((
const
char
*) gf->out_buffer, have);
}
else
{
sv_catpvn (plain, (
const
char
*) gf->out_buffer, have);
}
}
while
(gf->strm.avail_out == 0);
check_avail_in (gf);
check_zlib_status (zlib_status);
inflateEnd (& gf->strm);
if
(gf->user_object && gf->is_gzip && header.done == 1) {
if
(gf->copy_perl_flags) {
if
(
strncmp
((
const
char
*) header.extra, GZIP_PERL_ID,
GZIP_PERL_ID_LENGTH) == 0) {
unsigned is_utf8;
is_utf8 = header.extra[GZIP_PERL_ID_LENGTH] & GZIP_PERL_UTF8;
if
(is_utf8) {
SvUTF8_on (plain);
}
}
}
if
(header.name && header.name_max > 0) {
gf->file_name = newSVpv ((
const
char
*) header.name, 0);
SvREFCNT_inc (gf->file_name);
}
else
{
gf_delete_file_name (gf);
}
if
(header.
time
) {
gf->mod_time = newSVuv (header.
time
);
SvREFCNT_inc (gf->mod_time);
}
else
{
gf_delete_mod_time (gf);
}
}
return
plain;
}
static
void
new_user_object (gzip_faster_t * gf)
{
gf->file_name = 0;
gf->mod_time = 0;
gf->is_gzip = 1;
gf->is_raw = 0;
gf->user_object = 1;
gf->level = Z_DEFAULT_COMPRESSION;
}
static
void
set_compression_level (gzip_faster_t * gf,
int
level)
{
if
(level < Z_NO_COMPRESSION) {
warn (
"Cannot set compression level to less than %d"
,
Z_NO_COMPRESSION);
gf->level = Z_NO_COMPRESSION;
}
else
if
(level > Z_BEST_COMPRESSION) {
warn (
"Cannot set compression level to more than %d"
,
Z_BEST_COMPRESSION);
gf->level = Z_BEST_COMPRESSION;
}
else
{
gf->level = level;
}
}