#include "stdafx.h"
#include 
#include "socket.h"
#include "http.h"
#include 
#include "zlib.h"

static char gz_magic[2] = { '\x1f', '\x8b'};
#define BUFSIZE (4095)
/* 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 */


class GzipFilter : public IReceiver
{
public:
	IReceiver *old;
	z_stream z;
	int m_inflate_total;
	char *outbuf;
	enum gz_mode {
		CHECK_HEADER,
		INFLATE,
		ERROR_INFLATE,
	} mode;

	GzipFilter(IReceiver *a_old)
		:old( a_old )
		,mode( CHECK_HEADER )
	{
		/* すべてのメモリ管理をライブラリに任せる */
		z.zalloc = Z_NULL;
		z.zfree = Z_NULL;
		z.opaque = Z_NULL;

		/* 初期化 */
		z.next_in = Z_NULL;
		z.avail_in = 0;
		m_inflate_total=0;

		if (inflateInit2(&z, -MAX_WBITS) != Z_OK) {
			return;
		}
		outbuf = new char [ BUFSIZE+1 ];
	}
	~GzipFilter()
	{
		delete []outbuf;
		inflateEnd(&z);
	}

	int check(const char *buf, int len)
	{
		if( buf[0] != gz_magic[0] ||
			buf[1] != gz_magic[1] )
		{
			mode = ERROR_INFLATE;
			return -1;
		}
		int i=2;
		int method = buf[i++];
		int flags  = buf[i++];
		if (method != Z_DEFLATED || (flags & RESERVED) != 0) {
			mode = ERROR_INFLATE;
			return -1;
		}
		i+=6;

		if (flags & EXTRA_FIELD) { /* skip the extra field */
			int len = 0;
			len  =  (uInt)buf[i++];
			len += ((uInt)buf[i++])<<8;
			/* len is garbage if EOF but the loop below will quit anyway */
			i+=len;
		}
		if (flags & ORIG_NAME) { /* skip the original file name */
			while (buf[i++] !=0) ;
		}
		if (flags & COMMENT) {   /* skip the .gz file comment */
			while (buf[i++] != 0) ;
		}
		if (flags & HEAD_CRC) {  /* skip the header crc */
			i+=2;
		}
		mode = INFLATE;
		return i;
	} 

	virtual void recv(const char *buf, int len )
	{	
		if( mode == CHECK_HEADER ){
			int ret =  check(buf,len);
			if( ret > 0 )
			{
				buf += ret;
				len -= ret;
				z.avail_out = BUFSIZE;
				z.next_out  = (unsigned char*)outbuf;
			}
		}

		if( mode == INFLATE )
		{
			z.next_in  =  (unsigned char*)buf;
			z.avail_in = len;

			int status = Z_OK;
			while (status != Z_STREAM_END) {
				if (z.avail_in == 0) {	/* 入力残量がゼロになれば */
					break;
				}
				status = inflate(&z, Z_NO_FLUSH); /* 展開 */
				if (status == Z_STREAM_END)
				{
					int used = BUFSIZE - z.avail_out;
					outbuf[used] = '\0';
					m_inflate_total += used;
					old->recv( outbuf, used);
//					receiver->notify( IReceiver::CONTENT_LENGTH, (void*)(m_total_out) );\
//						NOTIFY( _ContentLength );

					break;
				}
				if (status != Z_OK) {	/* エラー */
					mode = ERROR_INFLATE;
					break;
				}
				if (z.avail_out == 0) { 
					outbuf[BUFSIZE] = '\0';
					m_inflate_total += BUFSIZE;

					old->recv( outbuf,  BUFSIZE );
					z.avail_out = BUFSIZE;
					z.next_out  =  (unsigned char*)outbuf;
				}
			}
		}
		if( mode == ERROR_INFLATE ){
			old->recv(buf,len);
		}
	}
	virtual void notify(int code, void*data)
	{
		if( code == IReceiver::FINISH)	
		{			
			old->notify( IReceiver::CONTENT_LENGTH, (void*)m_inflate_total );
			old->notify( code, data );
			delete this;
			return;
		}
		old->notify( code, data );
	
		if( code == IReceiver::TERMINATE_TRANSMIT){
			delete this;
		}
	};
	virtual void status(const char*status)
	{	old->status( status );	}
	virtual void error( int reason )
	{	old->error( reason );	}

};
IReceiver *Insert_GzipFilter( IReceiver *old )
{
	return new GzipFilter( old );
}

    Source: geocities.com/ghanyan