gecko-dev/network/cnvts/cvunzip.c

539 lines
14 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
*/
/* Please leave outside of ifdef for windows precompiled headers */
#include "xp.h"
#include "prmem.h"
#include "plstr.h"
#include "netutils.h"
#include "mkselect.h"
#include "mktcp.h"
#include "mkgeturl.h"
#include "cvunzip.h"
/* BEGIN NEW_STRING_LIB */
#ifdef XP_MAC
#ifndef MOZ_NGLAYOUT
#include "allxpstr.h"
#endif
#endif
/* END NEW_STRING_LIB */
#ifdef MOZILLA_CLIENT
#include "mkstream.h"
#include "zlib.h"
extern int MK_OUT_OF_MEMORY;
extern int MK_BAD_GZIP_HEADER;
typedef struct _DataObject {
NET_StreamClass *next_stream;
z_stream d_stream; /* decompression stream */
unsigned char *dcomp_buf;
uint32 dcomp_buf_size;
PRBool is_done;
PRBool checking_crc_footer;
unsigned char *incoming_buf;
uint32 incoming_buf_size;
PRBool header_skipped;
URL_Struct *URL_s;
uint32 crc_check;
} DataObject;
#define DECOMP_BUF_SIZE NET_Socket_Buffer_Size*2
enum check_header_response {
HEADER_OK,
BAD_HEADER,
NEED_MORE_HEADER
};
static uint32 gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */
/* gzip flag byte */
#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */
#define HEAD_CRC 0x02 /* bit 1 set: header CRC present */
#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
#define ORIG_NAME 0x08 /* bit 3 set: original file name present */
#define COMMENT 0x10 /* bit 4 set: file comment present */
#define RESERVED 0xE0 /* bits 5..7: reserved */
/* code copied from Zlib please see zlib.h for copyright statement */
/* ===========================================================================
Check the gzip header of a gz_stream opened for reading. Set the stream
mode to transparent if the gzip magic header is not present; set s->err
to Z_DATA_ERROR if the magic header is present but the rest of the header
is incorrect.
IN assertion: the stream s has already been created sucessfully;
s->stream.avail_in is zero for the first time, but may be non-zero
for concatenated .gz files.
*/
static enum check_header_response
check_header(unsigned char *header, uint32 header_length, uint32 *actual_header_size)
{
int method; /* method byte */
int flags; /* flags byte */
uInt len;
uint32 c;
uint32 orig_header_size = header_length;
/* header must be at least 10 bytes */
if(header_length < 10)
return NEED_MORE_HEADER;
/* Check the gzip magic header */
for (len = 0; len < 2; len++) {
c = (uint32) *header++;
header_length--;
if (c != gz_magic[len]) {
return BAD_HEADER;
}
}
method = *header++;
header_length--;
flags = *header++;
header_length--;
if (method != Z_DEFLATED || (flags & RESERVED) != 0) {
return BAD_HEADER;
}
/* Discard time, xflags and OS code: */
for (len = 0; len < 6; len++)
{
header++;
header_length--;
}
/* OK we now passed the safe 10 byte boundary, we need to check from here
* on out to make sure we have enough data
*/
if ((flags & EXTRA_FIELD) != 0) { /* skip the extra field */
if(header_length < 2)
return NEED_MORE_HEADER;
len = (uInt)*header++;
header_length--;
len += ((uInt)*header++)<<8;
header_length--;
/* len is garbage if EOF but the loop below will quit anyway */
if(header_length < len)
return NEED_MORE_HEADER;
while (len-- != 0)
{
header++;
header_length--;
}
}
if ((flags & ORIG_NAME) != 0) { /* skip the original file name */
if(header_length < 1)
return NEED_MORE_HEADER;
while (*header != '\0')
{
header++;
header_length--;
if(header_length == 0)
return NEED_MORE_HEADER;
}
/* skip null byte */
header++;
header_length--;
}
if ((flags & COMMENT) != 0) { /* skip the .gz file comment */
if(header_length < 1)
return NEED_MORE_HEADER;
while (*header != '\0')
{
header++;
header_length--;
if(header_length == 0)
return NEED_MORE_HEADER;
}
/* skip null byte */
header++;
header_length--;
}
if ((flags & HEAD_CRC) != 0) { /* skip the header crc */
if(header_length < 2)
return NEED_MORE_HEADER;
for (len = 0; len < 2; len++)
{
header++;
header_length--;
}
}
*actual_header_size = orig_header_size - header_length;
return HEADER_OK;
}
PRIVATE int
do_end_crc_check(DataObject *obj)
{
if(obj->incoming_buf_size >= 8)
{
uint32 crc_int;
uint32 size_int;
obj->checking_crc_footer = PR_FALSE;
obj->is_done = PR_TRUE;
crc_int = (uint32)obj->incoming_buf[0];
crc_int += (uint32)obj->incoming_buf[1]<<8;
crc_int += (uint32)obj->incoming_buf[2]<<16;
crc_int += (uint32)obj->incoming_buf[3]<<24;
size_int = (uint32)obj->incoming_buf[4];
size_int += (uint32)obj->incoming_buf[5]<<8;
size_int += (uint32)obj->incoming_buf[6]<<16;
size_int += (uint32)obj->incoming_buf[7]<<24;
if(obj->crc_check != crc_int
|| obj->d_stream.total_out != size_int)
{
/* crc or size checksum failure */
obj->URL_s->error_msg = NET_ExplainErrorDetails(MK_BAD_GZIP_HEADER);
return MK_BAD_GZIP_HEADER;
}
return 1;
}
return 0; /* need more data */
}
/* Free up the unzip specific stream data object.
*/
PRIVATE void net_UnZipFreeDataObject (DataObject **objp)
{
DataObject *obj;
PR_ASSERT(objp && *objp);
if (objp && *objp)
{
obj = *objp;
PR_FREEIF(obj->dcomp_buf);
PR_FREEIF(obj->incoming_buf);
PR_Free(obj);
*objp = NULL;
}
}
PRIVATE int net_UnZipWrite (NET_StreamClass *stream, CONST char* s, int32 l)
{
int err;
uint32 prev_total_out;
uint32 new_data_total_out;
uint32 input_used_up, input_left_over;
char * tempPtr = NULL;
DataObject *obj;
PR_ASSERT(stream);
obj = stream->data_object;
if(obj->is_done)
{
/* multipart gzip? */
PR_ASSERT(0);
return (1);
}
PR_ASSERT(s && l);
tempPtr = (char *)obj->incoming_buf;
BlockAllocCat(tempPtr, obj->incoming_buf_size, s, l);
if(!tempPtr)
return MK_OUT_OF_MEMORY;
obj->incoming_buf = (unsigned char*)tempPtr;
obj->incoming_buf_size += l;
/* parse and skip the header */
if(!obj->header_skipped)
{
uint32 actual_header_size;
enum check_header_response status;
status = check_header((unsigned char *)obj->incoming_buf, obj->incoming_buf_size, &actual_header_size);
if(status == HEADER_OK)
{
/* squash the header */
obj->incoming_buf_size -= actual_header_size;
memmove(obj->incoming_buf,
obj->incoming_buf+actual_header_size,
obj->incoming_buf_size);
obj->header_skipped = PR_TRUE;
}
else if(status == BAD_HEADER)
{
obj->URL_s->error_msg = NET_ExplainErrorDetails(MK_BAD_GZIP_HEADER);
return MK_BAD_GZIP_HEADER;
}
else if(status == NEED_MORE_HEADER)
{
return 1;
}
else
{
PR_ASSERT(0);
return 1;
}
}
else if(obj->checking_crc_footer)
{
return do_end_crc_check(obj);
}
obj->d_stream.next_in = (unsigned char *)obj->incoming_buf;
obj->d_stream.avail_in = obj->incoming_buf_size;
obj->d_stream.next_out = (unsigned char *)obj->dcomp_buf;
obj->d_stream.avail_out = obj->dcomp_buf_size;
if(obj->d_stream.avail_in <= 0)
return 1; /* wait for more data */
prev_total_out = obj->d_stream.total_out;
/* need to loop to finish for small output bufs */
while(1)
{
err = inflate(&obj->d_stream, Z_NO_FLUSH);
/* the amount of new uncompressed data is: */
new_data_total_out = obj->d_stream.total_out - prev_total_out;
if(new_data_total_out > 0)
{
obj->crc_check = crc32(obj->crc_check,
obj->dcomp_buf,
new_data_total_out);
(*obj->next_stream->put_block)(obj->next_stream,
(char *) obj->dcomp_buf,
new_data_total_out);
}
obj->d_stream.avail_out = obj->dcomp_buf_size;
obj->d_stream.next_out = (unsigned char *)obj->dcomp_buf;
prev_total_out = obj->d_stream.total_out;
if(err == Z_STREAM_END)
{
obj->checking_crc_footer = PR_TRUE;
break;
}
else if(err != Z_OK)
{
/* need to get more data on next pass
* @@@ should check for more critical errors
*/
break;
}
else if(obj->d_stream.avail_in <= 0)
{
/* need more data */
break;
}
}
/* remove the part that has already been decoding from the incoming buf */
input_left_over = obj->d_stream.avail_in;
if(input_left_over > 0)
{
input_used_up = obj->incoming_buf_size - input_left_over;
memmove(obj->incoming_buf, obj->incoming_buf+input_used_up, input_left_over);
obj->incoming_buf_size = input_left_over;
}
else
{
obj->incoming_buf_size = 0;
}
if(obj->checking_crc_footer == PR_TRUE)
{
return do_end_crc_check(obj);
}
return(1);
}
/* is the stream ready for writeing?
*/
PRIVATE unsigned int net_UnZipWriteReady (NET_StreamClass * stream)
{
DataObject *obj=stream->data_object;
return((*obj->next_stream->is_write_ready)(obj->next_stream)); /* always ready for writing */
}
PRIVATE void net_UnZipComplete (NET_StreamClass *stream)
{
DataObject *obj=stream->data_object;
int err;
(*obj->next_stream->complete)(obj->next_stream);
err = inflateEnd(&(obj->d_stream));
PR_ASSERT(err == Z_OK);
if(!obj->is_done)
{
/* we didn't complete the crc and size checks */
/* @@@ not sure what to do here yet */
PR_ASSERT(0);
}
net_UnZipFreeDataObject((DataObject **)&stream->data_object);
return;
}
PRIVATE void net_UnZipAbort (NET_StreamClass *stream, int status)
{
DataObject *obj=stream->data_object;
int err;
(*obj->next_stream->abort)(obj->next_stream, status);
err = inflateEnd(&(obj->d_stream));
PR_ASSERT(err == Z_OK);
net_UnZipFreeDataObject((DataObject **)&stream->data_object);
return;
}
PUBLIC NET_StreamClass *
NET_UnZipConverter (int format_out,
void *data_obj,
URL_Struct *URL_s,
MWContext *window_id)
{
DataObject* obj;
NET_StreamClass* stream;
int err;
TRACEMSG(("Setting up display stream. Have URL: %s\n", URL_s->address));
PR_ASSERT(URL_s);
stream = PR_NEW(NET_StreamClass);
if(stream == NULL)
return(NULL);
obj = PR_NEWZAP(DataObject);
if (obj == NULL)
{
PR_Free(stream);
return(NULL);
}
stream->name = "UnZiper";
stream->complete = (MKStreamCompleteFunc) net_UnZipComplete;
stream->abort = (MKStreamAbortFunc) net_UnZipAbort;
stream->put_block = (MKStreamWriteFunc) net_UnZipWrite;
stream->is_write_ready = (MKStreamWriteReadyFunc) net_UnZipWriteReady;
stream->data_object = obj; /* document info object */
stream->window_id = window_id;
obj->dcomp_buf = PR_Malloc(DECOMP_BUF_SIZE);
obj->dcomp_buf_size = DECOMP_BUF_SIZE;
if(!obj->dcomp_buf)
{
net_UnZipFreeDataObject(&obj);
PR_Free(stream);
return NULL;
}
obj->URL_s = URL_s;
obj->d_stream.zalloc = (alloc_func)0;
obj->d_stream.zfree = (free_func)0;
obj->d_stream.opaque = (voidpf)0;
err = inflateInit2(&obj->d_stream, -15);
if(err != Z_OK)
{
net_UnZipFreeDataObject(&obj);
PR_Free(stream);
return NULL;
}
/* create the next stream, but strip the compressed encoding */
/*
* We might have been sent here from either a transfer-encoding: gzip
* or a content-encoding: gzip stream. There is no way to know for
* sure at this point. Based on knowing what NET_StreamBuilder does
* I know transfer-encoding is processed before content-encoding
* and that the only encoding currently sent here are
* ENCODING_GZIP and ENCODING_GZIP2. If this changes in the
* future it could cause an infinite recursion bug here.
*/
if ((URL_s->transfer_encoding != NULL)&&
((!PL_strcasecmp(URL_s->transfer_encoding, ENCODING_GZIP))||
(!PL_strcasecmp(URL_s->transfer_encoding, ENCODING_GZIP2))))
{
PR_FREEIF(URL_s->transfer_encoding);
}
else
{
PR_FREEIF(URL_s->content_encoding);
}
obj->next_stream = NET_StreamBuilder(format_out, URL_s, window_id);
if(!obj->next_stream)
{
inflateEnd(&obj->d_stream);
net_UnZipFreeDataObject(&obj);
PR_Free(stream);
return NULL;
}
TRACEMSG(("Returning stream from NET_UnZipConverter\n"));
return stream;
}
#endif /* MOZILLA_CLIENT */