/*
* Copyright (c) 2007, Tisza Daniel
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Tisza Daniel nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Tisza Daniel ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Tisza Daniel BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/*
 Name:		jpegstrip
 Version:	1.05
 Author(s):	Tisza Daniel

 Description:	Strips all non-compulsory data from a .jpg file.
		Based on ISO/IEC 10918-1 : 1993(E) specifications.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Control markers */
#define	SOI	0xD8
#define	EOI	0xD9
#define	SOS	0xDA
#define SOF0	0xC0
#define SOF1	0xC1
#define SOF2	0xC2
#define SOF3	0xC3
#define SOF5	0xC5
#define SOF6	0xC6
#define SOF7	0xC7
#define SOF8	0xC8
#define SOF9	0xC9
#define SOF10	0xCA
#define SOF11	0xCB
#define SOF12	0xCD
#define SOF13	0xCE
#define SOF14	0xCF
#define RST0	0xD0
#define RST1	0xD1
#define RST2	0xD2
#define RST3	0xD3
#define RST4	0xD4
#define RST5	0xD5
#define RST6	0xD6
#define RST7	0xD7
#define DHP	0xDE
#define EXP	0xDF
#define DNL	0xDC

/* Interpret markers - copied */
#define DHT	0xC4	
#define DAC	0xCC
#define DQT	0xDB
#define DRI	0xDD
#define APP0	0xE0

/* Interpret markers - stripped */
#define COM	0xFE
#define APP1	0xE1
#define APP2	0xE2
#define APP3	0xE3
#define APP4	0xE4
#define APP5	0xE5
#define APP6	0xE6
#define APP7	0xE7
#define APP8	0xE8
#define APP9	0xE9
#define APP10	0xEA
#define APP11	0xEB
#define APP12	0xEC
#define APP13	0xED
#define APP14	0xEE
#define APP15	0xEF

FILE *in=NULL;
FILE *out=NULL;
char inName[256];
char outName[256];
char newInName[256];

int handleUntilNextMarker(int copy);
void handleSegment(int marker,int copy);
void stripImage(void);
void stripFrame(void);
void error(const char* msg);
void finished(void);

/*
 handleUntilNextMarker

 Scans input file for a marker. If a marker is detected, the marker
 code is returned. Marker bytes are always consumed from the
 input stream, but never copied. If copying is set, only bytes between
 current position and next marker are copied.
*/
int handleUntilNextMarker(int copy)
{
	int i;
	int next;

	i=fgetc(in);	
	while(i!=EOF)
	{
		next=fgetc(in);
		if(next==EOF)
		{
			error("Premature end of file, readNextMarker");
		}
		else
		{
			if(i==0xFF && next!=0x0 && next!= 0xFF)
			{
				return next;
			}
			else if(copy==1)
			{
				fputc(i,out);
			}
			i=next;
		}
	}
	error("Premature end of file, readNextMarker");
	return -1; /* Never reached */
}

/*
 handleSegment

 Scans input file for a 2-byte segment length field.
 Consumes length-2 bytes from input stream in addition to the
 2 consumed length field bytes (length bytes total).
 If copying is set, every consumed byte is also copied.
*/
void handleSegment(int marker,int copy)
{
	int j;
	int length;
	int lengthMSB;
	int lengthLSB;

	/* Read segment length msb and lsb */
	lengthMSB=fgetc(in);
	if(lengthMSB==EOF)
		error("Premature end of file, handleSegment lengthMSB");

	lengthLSB=fgetc(in);
	if(lengthLSB==EOF)
		error("Premature end of file, handleSegment lengthLSB");

	length=(lengthMSB<<8)+lengthLSB;

	if(copy==1)
	{
		printf("Copying segment: %X with length: %d\n",marker,length);
		fputc(lengthMSB,out);
		fputc(lengthLSB,out);
	}
	else
	{
		printf("Stripping segment: %X with length: %d\n",marker,length);
	}

	/* Handle length-2 amount of segment bytes */
	for(j=0;j<(length-2);j++)
	{
		int i=fgetc(in);
		if(copy==1)
			fputc(i,out);
	}
}

/*
 stripImage
*/
void stripImage(void)
{
	int i;
	i=handleUntilNextMarker(0);
	if(i!=SOI)
	{
		error("No SOI found!");
	}
	else
	{
		printf("SOI marker found\n");
		fputc(0xFF,out);
		fputc(SOI,out);

		while( (i=handleUntilNextMarker(0)) != EOF)
		{
			if(	i==APP0 || i==DHT || i==DAC || i==DQT ||
				i==DRI || i==DHP || i==EXP)
			{
				fputc(0xFF,out);
				fputc(i,out);
				handleSegment(i,1);
			}
			else if(i==COM || i==APP1 || i==APP2 || i==APP3 ||
				i==APP4 || i==APP5 || i==APP6 || i==APP7 ||
				i==APP8 || i==APP9 || i==APP10 || i==APP11 ||
				i==APP12 || i==APP13 || i==APP14 || i==APP15)
			{
				handleSegment(i,0);
			}
			else if(i==SOF0 || i==SOF1 || i==SOF2 || i==SOF3 ||
				i==SOF5 || i==SOF6 || i==SOF7 || i==SOF8 ||
				i==SOF9 || i==SOF10 || i==SOF11 || i==SOF12 ||
				i==SOF13 || i==SOF14)
			{
				fputc(0xFF,out);
				fputc(i,out);
				handleSegment(i,1);
				stripFrame();
			}
			else if(i==EOI)
			{
				fputc(0xFF,out);
				fputc(EOI,out);
				finished();
			}
		}
	}	
}

/*
 stripFrame
*/
void stripFrame(void)
{
	int i;
	int entropy_compressed_data=0;
	while( (i=handleUntilNextMarker(entropy_compressed_data)) != EOF)
	{
		if(	i==SOF0 || i==SOF1 || i==SOF2 || i==SOF3 ||
			i==SOF5 || i==SOF6 || i==SOF7 || i==SOF8 ||
			i==SOF9 || i==SOF10 || i==SOF11 || i==SOF12 ||
			i==SOF13 || i==SOF14 || i==EXP || i==EOI)
		{
			fseek(in,-2,SEEK_CUR);
			return;
		}
		else if(i==APP0 || i==DHT || i==DAC || i == DQT ||
			i == DRI || i==DNL)
		{
			fputc(0xFF,out);
			fputc(i,out);
			handleSegment(i,1);
			entropy_compressed_data=0;
		}
		else if(i==COM || i==APP1 || i==APP2 || i==APP3 ||
			i==APP4 || i==APP5 || i==APP6 || i==APP7 ||
			i==APP8 || i==APP9 || i==APP10 || i==APP11 ||
			i==APP12 || i==APP13 || i==APP14 || i==APP15)
		{
			handleSegment(i,0);
			entropy_compressed_data=0;
		}
		else if(i==SOS)
		{
			fputc(0xFF,out);
			fputc(i,out);
			handleSegment(i,1);
			entropy_compressed_data=1;
		}
		else if(i==RST0 || i==RST1 || i==RST2 || i==RST3 ||
			i==RST4 || i==RST5 || i==RST6 || i==RST7)
		{
			fputc(0xFF,out);
			fputc(i,out);
			entropy_compressed_data=1;
		}
	}
}

/*
 error

 Prints error message and exits.
*/
void error(const char* msg)
{
	printf("Error: %s\n",msg);

	if(in!=NULL)
		fclose(in);
	if(out!=NULL)
		fclose(out);
	exit(0);
}

/*
 Finished

 Print finish message and exit.
*/
void finished(void)
{
	printf("Finished\n");

	if(in!=NULL)
		fclose(in);
	if(out!=NULL)
		fclose(out);

	newInName[0]='o';
	newInName[1]='l';
	newInName[2]='d';
	newInName[3]='.';
	newInName[4]='\0';
	strcat(newInName,inName);

	remove(newInName);
	rename(inName,newInName);

	remove(inName);
	rename(outName,inName);
	exit(0);
}

int main(int argc,char*argv[])
{
	if(argc < 2)
	{
		printf("Use: jpegstrip image.jpg\n");
	}
	else
	{
		strcpy(inName,argv[1]);
		printf("Image: %s\n",inName);

		in=fopen(argv[1],"rb");
		if(in==NULL)
			error("Unable to open source file");

		tmpnam(outName);
		out=fopen(outName,"wb+");
		if(out==NULL)
			error("Unable to open temporary file");

		stripImage();
	}		
	return 0;
}
