#!/usr/bin/perl -wT
#
# TWiki Collaboration Platform, http://TWiki.org/
#
# Copyright (C) 1999-2003 Peter Thoeny, peter@thoeny.com
#
# For licensing info read license.txt file in the TWiki root.
# 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, published at 
# http://www.gnu.org/copyleft/gpl.html

# Set library paths in @INC, at compile time
BEGIN { unshift @INC, '.'; require 'setlib.cfg'; }

# 'Use locale' for internationalisation of Perl regexes - 
# main locale settings are done in TWiki::setupLocale
BEGIN {
    # Do a dynamic 'use locale' for this module
    if( $TWiki::useLocale ) {
        require locale;
	import locale ();
    }
}

use CGI::Carp qw(fatalsToBrowser);
use CGI;
use File::Copy; # FIXME remove
use TWiki;

&main();


# =========================
# code fragment to extract pixel size from images
# taken from http://www.tardis.ed.ac.uk/~ark/wwwis/
# subroutines: imgsize, gifsize, OLDgifsize, gif_blockskip,
#              NEWgifsize, jpegsize
#
# looking at the filename really sucks I should be using the first 4 bytes
# of the image. If I ever do it these are the numbers.... (from chris@w3.org)
#  PNG 89 50 4e 47
#  GIF 47 49 46 38
#  JPG ff d8 ff e0
#  XBM 23 64 65 66


# =========================
sub imgsize {
  my( $file ) = shift @_;
  my( $x, $y) = ( 0, 0 );

  if( defined( $file ) && open( STRM, "<$file" ) ) {
    binmode( STRM ); # for crappy MS OSes - Win/Dos/NT use is NOT SUPPORTED
    if( $file =~ /\.jpg$/i || $file =~ /\.jpeg$/i ) {
      ( $x, $y ) = &jpegsize( \*STRM );
    } elsif( $file =~ /\.gif$/i ) {
      ( $x, $y ) = &gifsize(\*STRM);
    }
    close( STRM );
  }
  return( $x, $y );
}


# =========================
sub gifsize
{
  my( $GIF ) = @_;
  if( 0 ) {
    return &NEWgifsize( $GIF );
  } else {
    return &OLDgifsize( $GIF );
  }
}


# =========================
sub OLDgifsize {
  my( $GIF ) = @_;
  my( $type, $a, $b, $c, $d, $s ) = ( 0, 0, 0, 0, 0, 0 );

  if( defined( $GIF )              &&
      read( $GIF, $type, 6 )       &&
      $type =~ /GIF8[7,9]a/        &&
      read( $GIF, $s, 4 ) == 4     ) {
    ( $a, $b, $c, $d ) = unpack( "C"x4, $s );
    return( $b<<8|$a, $d<<8|$c );
  }
  return( 0, 0 );
}


# =========================
# part of NEWgifsize
sub gif_blockskip {
  my ( $GIF, $skip, $type ) = @_;
  my ( $s ) = 0;
  my ( $dummy ) = '';

  read( $GIF, $dummy, $skip );       # Skip header (if any)
  while( 1 ) {
    if( eof( $GIF ) ) {
      #warn "Invalid/Corrupted GIF (at EOF in GIF $type)\n";
      return "";
    }
    read( $GIF, $s, 1 );             # Block size
    last if ord( $s ) == 0;          # Block terminator
    read( $GIF, $dummy, ord( $s ) ); # Skip data
  }
}


# =========================
# this code by "Daniel V. Klein" <dvk@lonewolf.com>
sub NEWgifsize {
  my( $GIF ) = @_;
  my( $cmapsize, $a, $b, $c, $d, $e ) = 0;
  my( $type, $s ) = ( 0, 0 );
  my( $x, $y ) = ( 0, 0 );
  my( $dummy ) = '';

  return( $x,$y ) if( !defined $GIF );

  read( $GIF, $type, 6 );
  if( $type !~ /GIF8[7,9]a/ || read( $GIF, $s, 7 ) != 7 ) {
    #warn "Invalid/Corrupted GIF (bad header)\n";
    return( $x, $y );
  }
  ( $e ) = unpack( "x4 C", $s );
  if( $e & 0x80 ) {
    $cmapsize = 3 * 2**(($e & 0x07) + 1);
    if( !read( $GIF, $dummy, $cmapsize ) ) {
      #warn "Invalid/Corrupted GIF (global color map too small?)\n";
      return( $x, $y );
    }
  }
 FINDIMAGE:
  while( 1 ) {
    if( eof( $GIF ) ) {
      #warn "Invalid/Corrupted GIF (at EOF w/o Image Descriptors)\n";
      return( $x, $y );
    }
    read( $GIF, $s, 1 );
    ( $e ) = unpack( "C", $s );
    if( $e == 0x2c ) {           # Image Descriptor (GIF87a, GIF89a 20.c.i)
      if( read( $GIF, $s, 8 ) != 8 ) {
        #warn "Invalid/Corrupted GIF (missing image header?)\n";
        return( $x, $y );
      }
      ( $a, $b, $c, $d ) = unpack( "x4 C4", $s );
      $x = $b<<8|$a;
      $y = $d<<8|$c;
      return( $x, $y );
    }
    if( $type eq "GIF89a" ) {
      if( $e == 0x21 ) {         # Extension Introducer (GIF89a 23.c.i)
        read( $GIF, $s, 1 );
        ( $e ) = unpack( "C", $s );
        if( $e == 0xF9 ) {       # Graphic Control Extension (GIF89a 23.c.ii)
          read( $GIF, $dummy, 6 );        # Skip it
          next FINDIMAGE;       # Look again for Image Descriptor
        } elsif( $e == 0xFE ) {  # Comment Extension (GIF89a 24.c.ii)
          &gif_blockskip( $GIF, 0, "Comment" );
          next FINDIMAGE;       # Look again for Image Descriptor
        } elsif( $e == 0x01 ) {  # Plain Text Label (GIF89a 25.c.ii)
          &gif_blockskip( $GIF, 12, "text data" );
          next FINDIMAGE;       # Look again for Image Descriptor
        } elsif( $e == 0xFF ) {  # Application Extension Label (GIF89a 26.c.ii)
          &gif_blockskip( $GIF, 11, "application data" );
          next FINDIMAGE;       # Look again for Image Descriptor
        } else {
          #printf STDERR "Invalid/Corrupted GIF (Unknown extension %#x)\n", $e;
          return( $x, $y );
        }
      } else {
        #printf STDERR "Invalid/Corrupted GIF (Unknown code %#x)\n", $e;
        return( $x, $y );
      }
    } else {
      #warn "Invalid/Corrupted GIF (missing GIF87a Image Descriptor)\n";
      return( $x, $y );
    }
  }
}


# =========================
# jpegsize : gets the width and height (in pixels) of a jpeg file
# Andrew Tong, werdna@ugcs.caltech.edu           February 14, 1995
# modified slightly by alex@ed.ac.uk
sub jpegsize {
  my( $JPEG ) = @_;
  my( $done ) = 0;
  my( $c1, $c2, $ch, $s, $length, $dummy ) = ( 0, 0, 0, 0, 0, 0 );
  my( $a, $b, $c, $d );

  if( defined( $JPEG )             &&
      read( $JPEG, $c1, 1 )        &&
      read( $JPEG, $c2, 1 )        &&
      ord( $c1 ) == 0xFF           &&
      ord( $c2 ) == 0xD8           ) {
    while ( ord( $ch ) != 0xDA && !$done ) {
      # Find next marker (JPEG markers begin with 0xFF)
      # This can hang the program!!
      while( ord( $ch ) != 0xFF ) {
        return( 0, 0 ) unless read( $JPEG, $ch, 1 );
      }
      # JPEG markers can be padded with unlimited 0xFF's
      while( ord( $ch ) == 0xFF ) {
        return( 0, 0 ) unless read( $JPEG, $ch, 1 );
      }
      # Now, $ch contains the value of the marker.
      if( ( ord( $ch ) >= 0xC0 ) && ( ord( $ch ) <= 0xC3 ) ) {
        return( 0, 0 ) unless read( $JPEG, $dummy, 3 );
        return( 0, 0 ) unless read( $JPEG, $s, 4 );
        ( $a, $b, $c, $d ) = unpack( "C"x4, $s );
        return( $c<<8|$d, $a<<8|$b );
      } else {
        # We **MUST** skip variables, since FF's within variable names are
        # NOT valid JPEG markers
        return( 0, 0 ) unless read( $JPEG, $s, 2 );
        ( $c1, $c2 ) = unpack( "C"x2, $s );
        $length = $c1<<8|$c2;
        last if( !defined( $length ) || $length < 2 );
        read( $JPEG, $dummy, $length-2 );
      }
    }
  }
  return( 0, 0 );
}


# =========================
sub addLinkToEndOfTopic
{
    my ( $text, $pathFilename, $fileName, $fileComment ) = @_;
    my $fileLink = "";
    my $imgSize = "";

    if( $fileName =~ /\.(gif|jpg|jpeg|png)$/i ) {
        # inline image
        $fileComment = $fileName if( ! $fileComment );
        my( $nx, $ny ) = &imgsize( $pathFilename );
        if( ( $nx > 0 ) && ( $ny > 0 ) ) {
            $imgSize = " width=\"$nx\" height=\"$ny\" ";
        }
        $fileLink = &TWiki::Prefs::getPreferencesValue( "ATTACHEDIMAGEFORMAT" )
                  || '   * $comment: <br />'
                   . ' <img src="%ATTACHURLPATH%/$name" alt="$name"$size />';
    } else {
        # normal attached file
        $fileLink = &TWiki::Prefs::getPreferencesValue( "ATTACHEDFILELINKFORMAT" )
                 || '   * [[%ATTACHURL%/$name][$name]]: $comment';
    }

    $fileLink =~ s/^      /\t\t/go;
    $fileLink =~ s/^   /\t/go;
    $fileLink =~ s/\$name/$fileName/g;
    $fileLink =~ s/\$comment/$fileComment/g;
    $fileLink =~ s/\$size/$imgSize/g;
    $fileLink =~ s/\\t/\t/go;
    $fileLink =~ s/\\n/\n/go;
    $fileLink =~ s/([^\n])$/$1\n/;

    return "$text$fileLink";
}

# =========================
sub handleError
{
    my( $noredirect, $message, $query, $theWeb, $theTopic, 
        $theOopsTemplate, $oopsArg1, $oopsArg2 ) = @_;
    
    if( $noredirect ) {
        $oopsArg1 = "" if( ! $oopsArg1 );
        $oopsArg2 = "" if( ! $oopsArg2 );
        &TWiki::writeHeader( $query );
        print "ERROR $theWeb.$theTopic $message $oopsArg1 $oopsArg2\n";
    } else {
	my $url = &TWiki::getOopsUrl( $theWeb, $theTopic, $theOopsTemplate, $oopsArg1, $oopsArg2 );
	TWiki::redirect( $query, $url );
    }
}


# =========================
sub main
{
    my $query = new CGI;

    ##### for debug only: Remove next 3 comments (but redirect does not work)
    #open(STDERR,'>&STDOUT'); # redirect error to browser
    #$| = 1;                  # no buffering
    #TWiki::writeHeader( $query );

    my $thePathInfo = $query->path_info(); 
    my $theRemoteUser = $query->remote_user();
    my $theTopic = $query->param( 'topic' );
    my $theUrl = $query->url;
    my $doChangeProperties = $query->param( 'changeproperties' );
    my $hideFile = $query->param( 'hidefile' ) || "";
    my $noredirect = $query->param( 'noredirect' ) || "";
    
    ( $topic, $webName, $dummy, $userName ) = 
	&TWiki::initialize( $thePathInfo, $theRemoteUser, $theTopic, $theUrl, $query );
    $dummy = "";  # to suppress warning

    my $wikiUserName = &TWiki::userToWikiName( $userName );

    if( ! &TWiki::Store::webExists( $webName ) ) {
        handleError( $noredirect, "Missing Web", $query, $webName, $topic, "oopsnoweb" );
        return;
    }

    my( $mirrorSiteName, $mirrorViewURL ) = &TWiki::readOnlyMirrorWeb( $webName );
    if( $mirrorSiteName ) {
        handleError( $noredirect, "This is a readonly mirror", 
               $query, $webName, $topic, "oopsmirror", $mirrorSiteName, $mirrorViewURL );
        return;
    }

    # check access permission
    if( ! &TWiki::Access::checkAccessPermission( "change", $wikiUserName, "", $topic, $webName ) ) {
        handleError( $noredirect, "No change permission", $query, $webName, $topic, "oopsaccesschange" );
        return;
    }

    my $filePath = $query->param( 'filepath' ) || "";
    my $fileName = $query->param( 'filename' ) || "";
    if ( $filePath && ! $fileName ) {
        $filePath =~ m|([^/\\]*$)|;
        $fileName = $1;
    }
    my $tmpFilename = $query->tmpFileName( $filePath ) || "";
    my $fileComment = $query->param( 'filecomment' ) || "";
    my $createLink = $query->param( 'createlink' ) || "";
    
    close $filePath if( $TWiki::OS eq "WINDOWS");

    # JET need to change windows path to unix path
    $tmpFilename =~ s@\\@/@go;
    $tmpFilename =~ /(.*)/;
    $tmpFilename = $1;
    #&TWiki::writeDebug( "upload: tmpFilename $tmpFilename" );
    
    my( $fileSize, $fileUser, $fileDate, $fileVersion ) = "";

    if( ! $doChangeProperties ) {
	# check if file exists and has non zero size
	my $size = -s $tmpFilename;
	if( ! -e $tmpFilename || ! $size ) {
	    handleError( $noredirect, "File missing or zero size", 
	           $query, $webName, $topic, "oopsupload", $fileName );
	    return;
	}

	# cut path from filepath name (Windows "\" and Unix "/" format)
	my @pathz = ( split( /\\/, $filePath ) );
	my $filetemp = $pathz[$#pathz];
	my @pathza = ( split( '/', $filetemp ) );
	$fileName = $pathza[$#pathza];

	# Delete unwanted characters from filename, with I18N
	my $nonAlphaNum = "[^${TWiki::mixedAlphaNum}" . '\._-]+';
        $fileName =~ s/${nonAlphaNum}//go;
        $fileName =~ s/$TWiki::uploadFilter/$1\.txt/goi;  # apply security filter

	# Update
	my $text1 = "";
	my $saveCmd = "";
	my $doNotLogChanges = 1;
	my $doUnlock = 0;
	my $dontNotify = "";
	my $error = &TWiki::Store::saveAttachment( $webName, $topic, $text1, $saveCmd,
                                                   $fileName, $doNotLogChanges, $doUnlock, 
						   $dontNotify, $fileComment, $tmpFilename );

	if ( $error ) {
	    handleError( $noredirect, "Save attachment error", $query, $webName, $topic,
	           "oopssaveerr", $error );
	    return;
	}

	# get user name
	$fileUser = $userName;

	# get time stamp and file size of uploaded file:
	my( $tmp1,$tmp2,$tmp3,$tmp4,$tmp5,$tmp6,$tmp7,$tmp9,
	    $mtime,$tmp11,$tmp12,$tmp13 ) = "";
	( $tmp1,$tmp2,$tmp3,$tmp4,$tmp5,$tmp6,$tmp7,$fileSize,$tmp9,
	  $mtime,$tmp11,$tmp12,$tmp13 ) = stat $tmpFilename;
	$fileDate = $mtime;

        $fileVersion = TWiki::Store::getRevisionNumber( $webName, $topic, $fileName );

	if( $TWiki::doLogTopicUpload ) {
	    # write log entry
	    &TWiki::Store::writeLog( "upload", "$webName.$topic", $fileName );
	    #FIXE also do log for change property?
	}
    }
    
    
    # update topic
    my( $meta, $text ) = &TWiki::Store::readTopic( $webName, $topic );
    
    if( $doChangeProperties ) {
        TWiki::Attach::updateProperties( $fileName, $hideFile, $fileComment, $meta );
    } else {
        TWiki::Attach::updateAttachment( 
                $fileVersion, $fileName, $filePath, $fileSize,
                $fileDate, $fileUser, $fileComment, $hideFile, $meta );
    }
    
    if( $createLink ) {
        my $filePath = &TWiki::Store::getFileName( $webName, $topic, $fileName );
        $text = addLinkToEndOfTopic( $text, $filePath, $fileName, $fileComment );
    }

    my $error = &TWiki::Store::saveTopic( $webName, $topic, $text, $meta, "", 1 );
    if( $error ) {
    	handleError( $noredirect, "Save topic error", $query, $webName, $topic,
    	       "oopssaveerr", $error );
    } else {
        # and finally display topic
        if( $noredirect ) {
            &TWiki::writeHeader( $query );
            my $message = ( $doChangeProperties ) ? "properties changed" : "$fileName uploaded";
            print( "OK $message\n" );
        } else {
            TWiki::redirect( $query, &TWiki::getViewUrl( "", $topic ) );
        }
    }
}

# EOF