// ddiff/dpatch - manipulate differences between debian packages.
// Copyright 2000 Tom Rothamel <tom-ddiff@onegeek.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  

#include <stdio.h>
#include <malloc.h>
#include "genfile.h"
#include "config.h"

// This is an implementtion of a GFILE that handles decompression of
// a gzip archive. It also records the header for later examination.
struct GzipGF : public GFILE {
	GFILE *parent;	
	GFILE *pipe;

	char *hdrb;
	int hdrl;
	int hdra;

	unsigned char byte();
	void readheader();
	
	char *gets(char *, int);
	size_t read(void *, size_t, size_t);
	void seek(long);
	void skip(long);
	long tell();
	GFILE *dup();
	
	GzipGF(GFILE *);
	~GzipGF();
};

char *GzipGF::gets(char *s, int l) {
	if (!pipe) return NULL;
	return pipe->gets(s, l);
}

size_t GzipGF::read(void *b, size_t size, size_t nmemb) {
	if (!pipe) return 0;
	return pipe->read(b, size, nmemb);
}

void GzipGF::seek(long to) {
	if (!pipe) return;
	pipe->seek(to);
}

void GzipGF::skip(long left) {
	if (!pipe) return;
	pipe->skip(left);
			   
}

long GzipGF::tell() {
	if (!pipe) return -1;
	return pipe->tell();
}

GFILE *GzipGF::dup() {
	return new GzipGF(parent->dup());
}

unsigned char GzipGF::byte() {
	unsigned char b;

	parent->read(&b, 1, 1);

	if (hdrl + 1 >= hdra) {
		hdra += 1024;
		hdrb = (char *) realloc(hdrb, hdra);
	}

	hdrb[hdrl++] = (char) b;
	return b;
}

// This code is based on sample code given to me by tom@debian.org, and
// also on rfc1952.
void GzipGF::readheader() {
	int flags;
	int i;

	// Header... Already checked as good below.
	byte();
	byte();

	// Method.
	finfo->method = byte();

	// Flags.
	flags = byte();

	// Timestamp.
	byte(); byte(); byte(); byte();

	// Extra Flags.
	finfo->extra = byte();

	// OS.
	byte();

	// Extra field present.
	if (flags & 0x04) {
		i = byte();
		i |= (byte() << 8);

		for(; i > 0; i--) byte();
	}

	// File name present.
	if (flags & 0x08) {
		while (byte()) ;
	}

	// File Comment present.
	if (flags & 0x10) {
		while (byte()) ;
	}
	
	// Header checksum is present.
	// This is different in gzip's algorithm.doc... I'm trusting the
	// rfc here.
	if (flags & 0x02) {
		byte(); byte();
	}	
}
			       
GzipGF::GzipGF(GFILE *p) {
	hdrb = NULL;			
	hdrl = 0;
	hdra = 0;
		
	parent = p;
	p->seek(0);

	finfo = new FINFO;
	
	readheader();

	p->seek(0);
	
	pipe = open_gfpipe(GZIP_DECOMPRESS, parent);

	finfo->hdr = hdrb;
	finfo->hl = hdrl;
}

GzipGF::~GzipGF() {
	free(hdrb);
	if (pipe) delete pipe;
}

GFILE *open_gfgzip(GFILE *parent) {
	unsigned char buf[2];

	if (parent->read(buf, 1, 2) != 2) return NULL;
	if (buf[0] != 0x1f || buf[1] != 0x8b) return NULL;

	return new GzipGF(parent);
}
