# DeAnim FilterProxy Module

# Copyright (C) 2000, 2001 Bob McElrath.
# See the file COPYING for redistribution and modification terms.
#
#    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

package FilterProxy::DeAnim;

use strict;
no strict 'subs';
use vars qw($VERSION $CONFIG);
push @FilterProxy::MODULES,     # add myself to FilterProxy's list of known 
   "DeAnim";                    # modules
$VERSION = 0.01;
$CONFIG = {};
$CONFIG->{order} = 4;
$CONFIG->{mime_types} = [       # is a list of mime-types that this module
  "image/gif"                   # knows how to filter.  Can contain an empty
];                              # string to specify all mime-types.

*logger = \&FilterProxy::logger;

sub filter {                    # The main filtering routine.  Does the dirty work.
  my($req) = shift;             # $req is the HTTP::Request object 
  my($res) = shift;             # $res is the HTTP::Response object.  
  my($siteconfig) = shift;      

  if(defined $res && defined $res->content_ref) {
    my $oldsize = length ${$res->content_ref};
    my $modified = 0;
    my $G =
	{ Gifstream => ${$res->content_ref},
	  Offset => 0,	# current parsing offset
	};		# gif "object"
    my $newgif = '';
    my $readgif = sub {
	my $len = shift;
#	logger(DEBUG, "gif_read:  offset=$G->{Offset}  len=$len");
	my $data = substr(($G->{Gifstream}), $G->{Offset}, $len);
	if ((my $newlen = length($data)) != $len) {
	    logger(ERROR, "gif_read: wanted $len bytes, but could only read $newlen bytes\n");
            return;
	}
	$G->{Offset} += $len;
	$newgif .= $data;
	return($data);
    };
    # GIF89 GRAMMAR:
    ###############
    # <GIF Data Stream> ::=     Header <Logical Screen> <Data>* Trailer(3B)
    # <Logical Screen> ::=      Logical Screen Descriptor [Global Color Table]
    # <Data> ::=                <Graphic Block>  |  <Special-Purpose Block>
    # <Graphic Block> ::=       [Graphic Control Extension(21F9)] <Graphic-Rendering Block>
    # <Graphic-Rendering Block> ::=  <Table-Based Image>  |  Plain Text Extension(2101)
    # <Table-Based Image> ::=   Image Descriptor(2C) [Local Color Table] Image Data
    # <Special-Purpose Block> ::=    Application Extension(21FF)  |  Comment Extension(21FE)
    ###############
    # HEADER
    if (length($G->{Gifstream}) < 10) {
      logger(ERROR, "  DeAnim: gif of less than 10 bytes?  Whadafuk?\n");
      return;
    }
    my $header = $readgif->(6);
#    logger(DEBUG, "  DeAnim: HEADER");
    unless ($header eq "GIF89a") {
      return if($header eq "GIF87a"); # GIF87a's cant be animated anyway.
      logger(ERROR, sprintf("  expected GIF header, found %s\n", $header));
      return;
    }
#    logger(DEBUG, "  DeAnim: LOGICAL SCREEN");
    my $ls_desc = $readgif->(7);
    my ($ls_size, $ls_flag, $ls_misc) = unpack("A4 C A2", $ls_desc);
    if ($ls_flag & 0x80) {
        my $size = $ls_flag & 0x07;
        my $bytes = 3 * 2**($size+1);
#        logger(DEBUG, "  DeAnim: GLOBAL COLOR TABLE (OPTIONAL)");
        my $global_color_table = $readgif->($bytes);
    }
    while (1) {
#        logger(DEBUG, "  DeAnim: DATA*");
        last if (substr($G->{Gifstream}, $G->{Offset}, 1) eq "\x3B"); # TRAILER
        my $label = $readgif->(1);
        last if (not $label); # EOF
        if ($label eq "\x21") { # EXTENSION INTRODUCER
            my $start_ext = $G->{Offset} - 1;
#            logger(DEBUG, "  DeAnim: EXTENSION INTRODUCER");
            $label = $readgif->(1);
            if ($label eq "\xFF") {
#                logger(DEBUG, "  DeAnim: APPLICATION EXTENSION");
                my $size = unpack("C", $readgif->(1));
                if ($size != 11) {
                    logger(ERROR, "  DeAnim: expected application extension size==11, got $size\n");
                    return;
                }
                my $app_ex = $readgif->($size);
                while ($size = unpack("C", $readgif->(1))) {
                    # read data sub-block(s)
                    my $sub_block = $readgif->($size);
                }
                # $size should be zero -- the data sub-block terminator value
            } elsif ($label eq "\xFE") {
#                logger(DEBUG, "  DeAnim: COMMENT EXTENSION");
                while (my $size = unpack("C", $readgif->(1))) {
                    # read data sub-block(s)
                    my $sub_block = $readgif->($size);
                }
                # $size should be zero -- the data sub-block terminator value
            } elsif ($label eq "\xF9") {
#                logger(DEBUG, "  DeAnim: GRAPHIC BLOCK/GRAPHIC CONTROL EXTENSION (OPTIONAL)");
                my $size = unpack("C", $readgif->(1));
                if ($size != 4) {
#                    logger(ERROR, "  DeAnim: expected graphic control extension size==4, got $size\n");
                    return;
                }
                my $gr_con = $readgif->($size);
                my $terminator = $readgif->(1);
                if (defined $terminator && !$terminator) {
                    logger(ERROR, "  DeAnim: expected graphic control extension terminator==0, got '$terminator'(len ", 
                      length $terminator, ", ", sprintf("%#02x", $terminator), ")\n");
                    return;
                }
                # we allow graphic control extensions, so don't delete them.
                next;
            } elsif ($label eq "\x01") {
#                logger(DEBUG, "  DeAnim: PLAIN TEXT EXTENSION");
                my $size = unpack("C", $readgif->(1));
                if ($size != 12) {
                    logger(ERROR, "  DeAnim: expected plain text extension size==12, got $size\n");
                    return;
                }
                my $pl_txt_ex = $readgif->($size);
                while ($size = unpack("C", $readgif->(1))) {
                    # read data sub-block(s)
                    my $sub_block = $readgif->($size);
                }
            } else {
                logger(ERROR, "  DeAnim: expected an extension type\n");
                return;
            }
            # delete this extension
            my $len = length($newgif);
            my $delta = $G->{Offset} - $start_ext;
            logger(DEBUG, "  DeAnim: deleting extension ($delta bytes)");
            my $stuff = substr($newgif, $len - $delta, $delta);
#            logger(DEBUG, "    ", map { sprintf("%02x", (ord($_))) } split(//,$stuff));
            $newgif = substr($newgif, 0, $len - $delta);
            $modified = 1;
            next;
        }
        if ($label eq "\x2C") {
#            logger(DEBUG, "  DeAnim: IMAGE DESCRIPTOR");
            my $im_desc = $readgif->(9);
            my ($im_misc, $im_flag) = unpack("A8 C", $im_desc);
            if ($im_flag & 0x80) {
#                logger(DEBUG, "  DeAnim: LOCAL COLOR TABLE (OPTIONAL)");
                my $size = $im_flag & 0x07;
                my $bytes = 3 * 2**($size+1);
                $readgif->($bytes);	# local color table
            }
#            logger(DEBUG, "  DeAnim: IMAGE DATA");
            my $lzw_min = $readgif->(1);
            while (my $size = unpack("C", $readgif->(1))) {
                # read data sub-block(s)
                my $sub_block = $readgif->($size);
            }
            # we are finished since we've extracted the first gif frame
            last;
        } else {
            logger(ERROR, "  DeAnim: expected a table based image\n");
            return;
        }
    }
    $newgif .= "\x3B";		# add final "trailer"
    if ($@) {
	chomp($@);
	logger(ERROR, "  DeAnim: Error parsing GIF: $@");
	logger(ERROR, "  DeAnim: Current Offset: $G->{Offset}");
	$G->{Offset} = $G->{Offset} - 4 if ($G->{Offset} >= 4);
	my $data = $readgif->(8);
	logger(ERROR, sprintf("  DeAnim: bytes before/after offset:  %x %x %x %x / %x %x %x %x  %s",
		    unpack("C8", $data), $data));
	return;	# return (gif unchanged)
    }
    my $newsize = length $newgif;
    if($modified || $newsize != $oldsize) {
      $res->content($newgif);
      logger(DEBUG, "  DeAnim: GIF was modified ($oldsize->$newsize bytes), returning new GIF.\n");
    }
  }
  return;                       # nothing needs to be returned.
}

sub Config {
  my($req, $cgi, $siteconfig) = @_;

  return "It worked.\n";
}
