mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-26 18:31:29 +00:00
535 lines
14 KiB
C
535 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.0 (the "NPL"); you may not use this file except in
|
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|
* for the specific language governing rights and limitations under the
|
|
* NPL.
|
|
*
|
|
* The Initial Developer of this code under the NPL is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
|
* Reserved.
|
|
*/
|
|
|
|
/* 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 */
|