###############################################################################
# Name: FetchYahoo
# Purpose: retrieves messages from Yahoo! Mail, saving them to a local spool
# Description:  FetchYahoo is a Perl script that downloads mail from a Yahoo! 
#               webmail account to a local mail spool. It is meant to replace
#               fetchmail for people using Yahoo! mail since Yahoo!'s POP service
#               is no longer free. It downloads messages to a local mail spool, 
#               including all parts and attachments . It then deletes messages
#               unless requested not to.
# Author:  Ravi Ramkissoon
# Author's E-mail: ravi_ramkissoon@yahoo.com
# License: Gnu Public License
# Created: 04.12.02
# Modified: 04.22.03
# Version: .0.8
# Credit: Vadim Zeitlin <vadim@wxwindows.org>, author of yahoo2mbox.pl
# For: Structure and ideas (and doing the hard work for me :)
# Credit: David Symonds <ds@seul.org>, author of NAMG for inspiration.
###############################################################################

# Installation instructions
#  1. download this fetchyahoo.pl (you probably already have)
#  2. chmod a+rx fetchyahoo.pl
#  3. edit fetchyahoo.pl, using your username and password
#  4. edit fetchyahoo.pl, putting in your mail spool or an mbox file
#       (usually /var/spool/mail/username )
#  5. run ./fetchyahoo.pl  (--newonly to download new msgs only)
#                          (--nodelete to not delete messages after downloading)
#  6. use your mail client to get mail from the mail spool
#  7. e-mail ravi_ramkissoon@yahoo.com with bugs and feature requests

# Procmail instructions
#  1. use "/path/to/procmail" as your mail spool name
#  2. edit the $spool declaration line to read
#       my $spool = "|" . $spoolname ;
 
# TODO/Changelog:
#  1. Get login working - DONE
#  2. Download summary page - DONE
#  3. Parse summary page for number of messages - DONE
#  4. Code main loop to parse each msgid from yahoo - DONE
#  5. Parse message page for raw message text - DONE
#  6. Send message to spool - DONE
#  7. Delete successfully grabbed msgs - DONE
#  8. Get options working, option to d/load new messages only - DONE
#  9. Remove use of the specific us.f149 webmail server - DONE
# 10. Include directions for using procmail - DONE
# 11. Make sure (internal and header) From_ lines are RFC 822 compliant - DONE
# 12. Add a --nodelete option which specifies no deleting of messages - DONE
# 13. Make parsing of the From_ line more rigorous (make one if necessary) - DONE
# 14. Better error messages for wrong username/password - DONE
# 15. Get attachments working (download or include in message) - DONE
#     15.1 Cycle through bodyParts grabbing each one in turn
#     15.2 Make a mail up from pieces and then spool that (MIME::Entity)
# 16. Progress indicator - DONE
# 17. Grab more known fields - x-apparently-to, x-mailer, x-priority - DONE
# 18. Add header field parsing for German - DONE
# 19. If there is only one part, create a single part message - DONE
# 20. Forward to another address  - TODO
# 21. Fix to work with other languages - TODO
# 22. Grab all message headers, not only recognized ones. - TODO

use strict;
use IO::Socket;

use Getopt::Long ();
use HTML::Entities ();
use HTML::HeadParser ();
use HTML::TokeParser ();
use HTTP::Request::Common qw(GET POST);
use HTTP::Cookies ();
use LWP::UserAgent ();
use LWP::Simple ();
use MIME::Entity ();
sub GetRedirectUrl($);
sub PopulateMap();
		   
my $username;
my $password;
my $spoolname = "c:\\temp\\yahoomail";

############################### Create listening server ##################################
my $port = 110; # or can be something else if you already have a popper running on ur machine
my $serversock = new IO::Socket::INET(Listen => 1, LocalPort => $port) 
				or die "Could not create socket on localhost:$port";

my $clientsock = $serversock->accept();	
my $sockbuff;
my %data;
if ($clientsock)
{
		print "\nClient connected!\n";
		print $clientsock "+OK POP3 Yahoo pop3 proxy ready to rawk\n";
		$sockbuff = <$clientsock>;   # expect user auth. eg. "USER noorix"
		chop $sockbuff;  # remove trailing '\n'
		%data = split( ' ', $sockbuff );
		if ($data{USER}) 
		{
			# if a valid USER command sent, store username in variable :)
			$username = $data{USER};
			print "received login id\n";
			%data = {};
			print $clientsock "+OK User name accepted, password please\n";
			$sockbuff = <$clientsock>;
			chop $sockbuff; 
			%data = split (' ', $sockbuff);
			
			# if valid PASS command sent, store password 
			if ($data{PASS})
			{
				print "received password\n";
				$password = $data{PASS};
				
			}
			else
			{
				print $clientsock "-ERR Unknown AUTHORIZATION state command\n";
				die;
			}
			
		}
		else
		{
			print $clientsock "-ERR Unknown AUTHORIZATION state command\n";
			die;
		}
}
	
			




# edit this ( ">>" -> "|" ) for procmail
my $spool = ">>" . $spoolname ;

# May need to edit these in future
my $homesuff = "/ym/ShowFolder?box=Inbox";
my $msgsuff = "/ym/ShowLetter?box=Inbox&PRINT=1&Nhead=f&toc=1&MsgId=";
my $versionString = "FetchYahoo Version .0.8\n";

# turn this on for tons of debugging messages
# use LWP::Debug qw(+);

my $usage = <<EOF
$0 [options] 

Retrieves messages from the inbox of a Yahoo user using the web interface
and stores them in the specified local spool/mbox file. Deletes the messages
downloaded unless requested not to.

--version       print the version and exit
--help          give the usage message showing the program options
--newonly       only download new (i.e. unread) messages
--nodelete      do not delete messages after downloading them

NOTE: Username, password and path to mail spool need to be set
by editing fetchyahoo.pl
EOF
;


# START

# flag indicating whether to download all or just new messages
my $newOnly = 0;

# flag indicating whether to delete or not
my $noDelete = 0;

# flag for help and version
my $help = 0;
my $version = 0;

# mapping extensions to mime types
my %map  = (); 
PopulateMap();
		   
# parse input options
Getopt::Long::GetOptions
(
 'newOnly'     => \$newOnly,
 'help'        => \$help,
 'version'     => \$version,
 'noDelete'    => \$noDelete);

# unbuffer STDOUT
select((select(STDOUT), $| = 1)[0]);

# check if help or version was requested
if ($help) { print $versionString . "\n" . $usage; exit; }
if ($version) { print $versionString; exit; }

# grab login cookies
my $ua = LWP::UserAgent->new;
$ua->agent("FetchYahoo/.0.8");
my $cookie_jar = HTTP::Cookies->new();
$ua->cookie_jar($cookie_jar);
my $request = POST 'http://login.yahoo.com/config/login',
    [
     ".tries" => '1',
     #".done"  => 'URL to go to later',
     ".src"   => 'ym',
     ".intl"  => 'us',
     "login"  => $username,
     "passwd" => $password,
     ];

$request->content_type('application/x-www-form-urlencoded');
$request->header('Accept' => '*/*');
$request->header('Allowed' => 'GET HEAD PUT');
my $response = $ua->simple_request($request);
my $url;
while ( $response->is_redirect ) {
    $cookie_jar->extract_cookies($response);
    $url = GetRedirectUrl($response);
    $request = GET $url;
    $response = $ua->simple_request($request);
}

if ( !$response->is_success ) { 
	print "Failed!\n"; die "Couldn't log in\n"; 
	print $clientsock "-ERR Bad login, PERHAPS YAHOO IMAGE LOGIN REQUIRED, OR SIMPLY WRONG USER-PASS\n"; 
    die;
}

if ( ($response->content) =~ /Invalid Password/ ) {
	print "Yahoo returned \'Invalid Password\'\n";
    print $clientsock "-ERR Bad login, PERHAPS YAHOO IMAGE LOGIN REQUIRED, OR SIMPLY WRONG USER-PASS\n"; 
    die;
}

if ( ($response->content) =~ /ID does not exist/ ) {
    print "Yahoo returned \'ID does not exist\'\n";
    print $clientsock "-ERR Bad login, PERHAPS YAHOO IMAGE LOGIN REQUIRED, OR SIMPLY WRONG USER-PASS\n"; 
    die; 
}



# setup URLs
$url =~ /(http:\/\/.*?)\// ;
my $baseurl = $1;
my $homeurl = $baseurl . $homesuff ;
my $msgurl = $baseurl . $msgsuff ; 
my $delurl = $homeurl . "\&DEL=Delete";

# get all message IDs
my $msgcount = 0;
my $pagecount = 0;
my $numMsgs ;
my $startMsg ;
my $endMsg ;
my @msgids ;
my $crumb ;

if ( $newOnly ) {
    print "Only retrieving new messages\n";
    $homeurl = $homeurl . "\&Nview=u";
}

# loop over all inbox summary pages
do {
    
    # get summary page
    my $tmpurl = $homeurl . "\&Npos=$pagecount" ; 
    $request = GET  $tmpurl ;
    $response = $ua->simple_request($request);
    while ( $response->is_redirect ) {
	$cookie_jar->extract_cookies($response); # manually extract cookies
	my $url = GetRedirectUrl($response);     # get new page
	$request = GET $url;                     # go to the new page 
	$response = $ua->simple_request($request);
    }
    
    if ( !$response->is_success ) { print "Failed!\n";
				    die "Couldn't get Inbox listing.\n"; }
    
    #parse for number of messages
    my $mainPage = $response->content;
    if ($mainPage =~ /showing (\d+)-(\d+) of (\d+)/ ) { 
	$startMsg = $1 ;
	$endMsg = $2 ;
	$numMsgs = $3;  }
    elsif ($mainPage =~ /werden (\d+)-(\d+) von (\d+)/ ) {  # for German page
	$startMsg = $1 ;
	$endMsg = $2 ;
	$numMsgs = $3;  }
    elsif ($mainPage =~ /Folder\s*Inbox\s*has\s*no\s+/) {
	print "There are no messages to retrieve.\n"; exit; }
    else { print "Failed!\n"; die "Can't retrieve number of messages.\n"; }
    
    $mainPage =~ /name=\".crumb\" value=\"(.*?)\"/ ;
    $crumb = $1;
    print "Getting Message IDs for messages $startMsg - $endMsg.\n";
    
    # parse summary page for message IDs
    foreach my $word (split ' ', $mainPage) {
	if ($word =~ /ShowLetter\?MsgId=([0-9_]+)/ ) { 
	    $msgcount = $msgcount + 1;
	    $msgids[$msgcount-1] = $1 ;
	}
    }
    $pagecount = $pagecount+1 ;            # next summary page
} until $numMsgs == $endMsg ; 

print "success loggin in with $msgcount messages\n";
print $clientsock "+OK Mailbox open, $msgcount messages\n"; 

#$sockbuff = <$clientsock>;
#chop $sockbuff;chop $sockbuff;
#print "Debug [$sockbuff]\n";
#if ($sockbuff eq 'UIDL 1' || $sockbuff eq 'UIDL' ) 
#{
#	# who cares about UIDL, its optional in POP3 RFC anyway :P
#	print $clientsock "-ERR Unknown TRANSACTION state command\n";
#}





#print "Got $msgcount Message IDs\n";
my $delcount = 0;

my (@messages,$mailboxsize);
# loop over all Message IDs
foreach my $msgid (@msgids) { 
   
   
   
   
    my $tmpurl = $msgurl . $msgid ; 
    my $request = GET  $tmpurl ;
    my $response;

    if ( ! ($response = $ua->simple_request($request))) {
	print "\nFailed to get body of message $msgid. It will be "
	    . "skipped and not deleted.\n";
	next ; }
    my $msgText = $response->content ;

    if ($msgText =~ /Printable\&nbsp;View/ ) {  # sometimes we get a 
	$tmpurl = $msgurl . $msgid ;            # non-printable view
	$request = GET  $tmpurl ;
	if ( ! ($response = $ua->simple_request($request) )) {
	    print "\nFailed to get body of message $msgid. It will be "
		. "skipped and not deleted.\n";
	    next ; }
	$msgText = $response->content ;
    }
    
    # loop over all the message parts, getting their URLs and filenames
    my $partcount = 0 ; 
    my @parturls;
    my @filenames;
    
    foreach my $word (split ' ', $msgText) {
	if ($word =~ /filename=(.*?)&download=1/ ) {
	    $filenames[$partcount] = $1 ;
	    my $parturl = $word ; 
	    $parturl =~ s/href=\"// ;
	    $parturl =~ s/\">// ;
	    $partcount = $partcount + 1;
	    $parturls[$partcount-1] = $parturl ;
	}
    }

    # message mangling goes here :)
    # need to convert to ascii chars so we can parse To, From and Subject.
   
    $msgText =~ s/<b>X-Apparently/\nX-Apparently/ ;  # insert a break b4 1st header
    $msgText =~ s/<.*?>//gs ;     # strip all raw html tags
    $msgText =~ s/\&\#34;/\"/g ;  # convert html character codes 
    $msgText =~ s/\&\#39;/\'/g ;
    $msgText =~ s/\&\#147;/\"/g ;
    $msgText =~ s/\&\#148;/\"/g ;
    $msgText =~ s/\&\#183;/./g ;
    $msgText =~ s/\&\#8217;/\'/g ;
    $msgText =~ s/\&\#8220;/\"/g ;
    $msgText =~ s/\&\#8221;/\"/g ;
    $msgText =~ s/\&\#8230;/.../g ;
    $msgText =~ s/\&nbsp;/ /gi ;
    $msgText =~ s/\&lt;/</gi ; 
    $msgText =~ s/\&gt;/>/gi ; 
    $msgText =~ s/\&amp;/\&/gi ;   
    $msgText =~ s/\&quot;/\"/gi ;   

    # Declare defaults for common message header fields
    my $toString = "_UNKNOWN_";
    my $fromString = "_UNKNOWN_";
    my $subjectString = "_UNKNOWN_";
    my $ccString = "" ; 
    my $dateString = scalar localtime ; 
    my $replyToString = "" ;
    my $senderString = "";
    my $returnPathString = "" ;
    my $messageIdString = "" ;
    my $priorityString = "" ; 
    my $mailerString = "" ;
    my $inReplyToString = "" ;
    my $importanceString = "" ;
    my $apparentlyToString = "" ;
    my $recvdString = "" ;

    # parse common message header fields
    if ( $msgText =~ /^To:\s*(.*?)\s*\n/m ||
	 $msgText =~ /^An:\s*(.*?)\s*\n/m ) { $toString = $1 ; }    
    if ( $msgText =~ /^From:\s*(.*?)\s*\n/m ||
	 $msgText =~ /^Von:\s*(.*?)\s*\n/m ) { $fromString = $1 ; }   
    if ( $msgText =~ /^Subject:\s*(.*?)\s*\n/m ||
	 $msgText =~ /^Betreff:\s*(.*?)\s*\n/m ) { $subjectString = $1 ; }
    if ( $msgText =~ /^CC:\s*(.*?)\s*\n/m ) { $ccString = $1 ; }
    if ( $msgText =~ /^Date:\s*(.*?)\s*\n/m ||
	 $msgText =~ /^Datum:\s*(.*?)\s*\n/m ) { $dateString = $1 ; }
    if ( $msgText =~ /^Reply-To:\s*(.*?)\s*\n/m ) { $replyToString = $1 ; }
    if ( $msgText =~ /^Sender:\s*(.*?)\s*\n/m ) { $senderString = $1 ; }
    if ( $msgText =~ /^Return-Path:\s*(.*?)\s*\n/m ) { $returnPathString = $1 ; }
    if ( $msgText =~ /^Message-ID:\s*(.*?)\s*\n/m ) { $messageIdString = $1 ; }
    if ( $msgText =~ /^X-Priority:\s*(.*?)\s*\n/m ) { $priorityString = $1 ; }
    if ( $msgText =~ /^X-Mailer:\s*(.*?)\s*\n/m ||
	 $msgText =~ /^User-Agent:\s*(.*?)\s*\n/m) { $mailerString = $1 ; }
    if ( $msgText =~ /^In-Reply-To:\s*(.*?)\s*\n/m ) { $inReplyToString = $1 ; }
    if ( $msgText =~ /^Importance:\s*(.*?)\s*\n/m ) { $importanceString = $1 ; }
    if ( $msgText =~ /X-Apparently-To:\s*(.*?)\s*\n/m ) { $apparentlyToString=$1;}
    if ( $msgText =~ /^Received:\s*(.*?)\s*\n/m ) { $recvdString = $1;}

    # if we can't parse To, From, or Subject, assume this has failed
    if ( ($toString =~ "_UNKNOWN_") && ($fromString =~ "_UNKNOWN_") 
	 && ($subjectString = "_UNKNOWN_") ) {
	print "\nCan't find message $msgid. It will be skipped and not deleted.\n";
	next;
    }
    my $msg;
    if ($partcount>1) {
	# start building a message to spool
	$msg = build MIME::Entity  
	    Type          => "multipart/mixed",
	    'X-Apparently-To:'=>$apparentlyToString,
	    'Return-Path' => $returnPathString,
	    Received      => $recvdString,
	    From          => $fromString,
	    To            => $toString,
	    Cc            => $ccString,
	    Subject       => $subjectString,
	    Date          => $dateString,
	    'Reply-To'    => $replyToString,
	    Sender        => $senderString,
	    'Message-ID'  => $messageIdString,
	    'X-Priority'  => $priorityString,
	    'X-Mailer'    => $mailerString,
	    'In-Reply-To:' => $inReplyToString,
	    'Importance:'  => $importanceString;
	
	# loop over all message parts
	for (my $i = 0; $i < $partcount; $i++) {
	    
	    # get one part of the message
	    $tmpurl = $baseurl . $parturls[$i] ; 
	    $request = GET  $tmpurl ;
	    
	    if ( ! ($response = $ua->simple_request($request) ) ) {
		print " \nFailed to get attachment $filenames[$i]. " 
		    . "Skipping attachment, message will not be deleted.\n" ;
		next ; 
	    }
	    
	    my $rawPart = $response->content ;
	    
	    # get extension and derive type and disposition from that
	    $filenames[$i] =~ /.*\.(.*)/ ;
	    my $fileExt = "qqq"; 
	    if (defined ($1) ) { $fileExt = $1 ; } 
	    my $type = $map{lc($fileExt)};
	    if ( ! defined ($type) ) { $type = "text/plain" ; } 
	    my $disp ; 
	    if ( $type =~ "text/plain"  && !($fileExt =~ "qqq")) { $disp = "inline";}
	    else { $disp = "attachment" ; }
	    
	    # attach this part to the message
	    attach $msg Data=>$rawPart,
	    Disposition => $disp,
	    Filename =>$filenames[$i],
	    Type => $type;
	    print "." ;               # output one "." for every part
	}
    }
    else {

	# get message body
	$tmpurl = $baseurl . $parturls[0] ; 
	$request = GET  $tmpurl ;	
	if ( ! ($response = $ua->simple_request($request) ) ) {
	    print " \nFailed to get message body. " 
		. "Message will be skipped and will not be deleted.\n" ;
	    next ; 
	} 	
	my $msgBody = $response->content ;

	$msg = build MIME::Entity            # build single-part message
	    Type           => "text/plain",
	    'X-Apparently-To:'=>$apparentlyToString,
	    'Return-Path'  => $returnPathString,
	    Received       => $recvdString,
	    From           => $fromString,
	    To             => $toString,
	    Cc             => $ccString,
	    Subject        => $subjectString,
	    Date           => $dateString,
	    'Reply-To'     => $replyToString,
	    Sender         => $senderString,
	    'Message-ID'   => $messageIdString,
	    'X-Priority'   => $priorityString,
	    'X-Mailer'     => $mailerString,
	    'In-Reply-To:' => $inReplyToString,
	    'Importance:'  => $importanceString,
	    Data           => $msgBody;
	print "." ;               # output one "." for every part
    }
    
    $delurl = $delurl . "\&Mid=$msgid";    # add message to deletion list
    $delcount = $delcount+1;
    
    # create a proper From_ line
    $fromString =~ s/ /_/g ;
    $fromString = "From " . $fromString . " " . scalar localtime() . "\n" ; 
    
    # send From_line and created multipart message to the specified spool/file    
    
    my $email =$msg->stringify;
    #open SPOOL, "$spool" or
	#die "Can't open output file: $spoolname";
    #print SPOOL $fromString ; 
    #$msg->print(\*SPOOL);
    #print SPOOL "\n" ;
    #close SPOOL;
    push @messages, $email;
    $mailboxsize += length($email);
    
    print $delcount%10 ;               # output one "-" for every completed message
}

my $emailsize;
$sockbuff = <$clientsock>;
chop $sockbuff;chop $sockbuff;
print "Debug [$sockbuff]\n";
if ( $sockbuff eq 'STAT')
{
	print $clientsock "+OK $msgcount $mailboxsize\n";
}
else
{
	print $clientsock "-ERR Unknown TRANSACTION state command\n";

}

$sockbuff = <$clientsock>;
chop $sockbuff;chop $sockbuff;
print "Debug [$sockbuff]\n";

if( $sockbuff eq 'LIST')
{
	print $clientsock "+OK $msgcount messages\n";
	for(my $i=1; $i<= scalar @messages; $i++){
		$emailsize = length $messages[$i-1];
		print $clientsock "$i $emailsize\n";
		print "Debug [$i $emailsize]\n";
	}
	print $clientsock ".\n";
}
else
{
	print $clientsock "-ERR Unknown TRANSACTION state command\n";
	
}


foreach my $mail ( @messages)
{

	## Outlook Express should ask for RETR 1... n
	%data = {};
	$sockbuff = <$clientsock>;
	chop $sockbuff; chop $sockbuff;
	%data = split (' ', $sockbuff);
	print "Debug [$sockbuff]\n";
	$clientsock->autoflush;
	if ($data{RETR})
	{

		my $emailsize = length $mail ;
		print "Debug Email size [$emailsize]\n";
		print $clientsock "+OK $emailsize octets\n";
		$clientsock->send( "$mail\r\n.\r\n" );
		
									
	}
	else
	{
		print $clientsock "-ERR Unknown TRANSACTION state command\n";	
		die;
	}
}

# read and ignore delete message requests            
while($sockbuff = <$clientsock>)
{          
chop $sockbuff; chop $sockbuff;            
print "Debug [$sockbuff]\n"; 
print $clientsock "+OK\r\n";
}
close $clientsock;


print "\nFinished downloading $msgcount messages.\n";

if ( ! $noDelete) {    
    $delurl = $delurl . "\&.crumb=$crumb";
    $request = GET  $delurl ;
    $response = $ua->simple_request($request) ||
	die "Failed to delete messages.\n";
    print $delcount . " message(s) have been deleted.\n";
} else { print "Messages have not been deleted.\n"; }


###############################################################################
# Subroutines
###############################################################################

# return the URL we're redirected to
sub GetRedirectUrl($) {
    my $response = $_[0];
    my $url = $response->header('Location') || return undef;
    
    # the Location URL is sometimes non-absolute which is not allowed, fix it
    local $URI::ABS_ALLOW_RELATIVE_SCHEME = 1;
    my $base = $response->base;
    $url = $HTTP::URI_CLASS->new($url, $base)->abs($base);
    
    return $url;
}

sub PopulateMap() {

$map{af}  =  "audio/aiff" ;
$map{ai}  =  "application/postscript" ;
$map{aiff}  =  "audio/aiff" ;
$map{asc}  =  "text/plain" ;
$map{au}  =  "audio/basic" ;
$map{au}  =  "audio/x-pn-au" ;
$map{avi}  =  "video/x-msvideo" ;
$map{bcpio}  =  "application/x-bcpio" ;
$map{bin}  =  "application/octet-stream" ;
$map{cdf}  =  "application/x-netcdf" ;
$map{cpio}  =  "application/x-cpio" ;
$map{cpt}  =  "application/mac-compactpro" ;
$map{csh}  =  "application/x-csh" ;
$map{css}  =  "text/css" ;
$map{dcr}  =  "application/x-director" ;
$map{dir}  =  "application/x-director" ;
$map{dms}  =  "application/octet-stream" ;
$map{doc}  =  "application/msword" ;
$map{dvi}  =  "application/x-dvi" ;
$map{dxr}  =  "application/x-director" ;
$map{eps}  =  "application/postscript" ;
$map{etx}  =  "text/x-setext" ;
$map{exe}  =  "application/octet-stream" ;
$map{ez}  =  "application/andrew-inset" ;
$map{gif}  =  "image/gif" ;
$map{gtar}  =  "application/x-gtar" ;
$map{hdf}  =  "application/x-hdf" ;
$map{hqx}  =  "application/mac-binhex40" ;
$map{html}  =  "text/html" ;
$map{htm}  =  "text/html" ;
$map{ice}  =  "x-conference/x-cooltalk" ;
$map{ief}  =  "image/ief" ;
$map{iges}  =  "model/iges" ;
$map{igs}  =  "model/iges" ;
$map{jpeg}  =  "image/jpeg" ;
$map{jpe}  =  "image/jpeg" ;
$map{jpg}  =  "image/jpeg" ;
$map{js}  =  "application/x-javascript" ;
$map{kar}  =  "audio/midi" ;
$map{latex}  =  "application/x-latex" ;
$map{lha}  =  "application/octet-stream" ;
$map{lzh}  =  "application/octet-stream" ;
$map{man}  =  "application/x-troff-man" ;
$map{me}  =  "application/x-troff-me" ;
$map{mesh}  =  "model/mesh" ;
$map{mid}  =  "audio/midi" ;
$map{midi}  =  "audio/midi" ;
$map{mif}  =  "application/vnd.mif" ;
$map{movie}  =  "video/x-sgi-movie" ;
$map{mov}  =  "video/quicktime" ;
$map{mp2}  =  "audio/mpeg" ;
$map{mp3}  =  "audio/mpeg" ;
$map{mpeg}  =  "video/mpeg" ;
$map{mpe}  =  "video/mpeg" ;
$map{mpga}  =  "audio/mpeg" ;
$map{mpg}  =  "video/mpeg" ;
$map{ms}  =  "application/x-troff-ms" ;
$map{msh}  =  "model/mesh" ;
$map{nc}  =  "application/x-netcdf" ;
$map{oda}  =  "application/oda" ;
$map{pbm}  =  "image/x-portable-bitmap" ;
$map{pdb}  =  "chemical/x-pdb" ;
$map{pdf}  =  "application/pdf" ;
$map{pgm}  =  "image/x-portable-graymap" ;
$map{pgn}  =  "application/x-chess-pgn" ;
$map{png}  =  "image/png" ;
$map{pnm}  =  "image/x-portable-anymap" ;
$map{ppm}  =  "image/x-portable-pixmap" ;
$map{ppt}  =  "application/vnd.ms-powerpoint" ;
$map{ps}  =  "application/postscript" ;
$map{qt}  =  "video/quicktime" ;
$map{ra}  =  "audio/x-realaudio" ;
$map{ram}  =  "audio/x-pn-realaudio" ;
$map{ras}  =  "image/x-cmu-raster" ;
$map{rf}  =  "image/vnd.rn-realflash" ;
$map{rgb}  =  "image/x-rgb" ;
$map{rm}  =  "application/vnd.rn-realmedia" ;
$map{rmm}  =  "audio/x-pn-realaudio" ;
$map{roff}  =  "application/x-troff" ;
$map{rp}  =  "image/vnd.rn-realpix" ;
$map{rtf}  =  "text/rtf" ;
$map{rt}  =  "text/vnd.rn-realtext" ;
$map{rtx}  =  "text/richtext" ;
$map{rv}  =  "video/vnd.rn-realvideo" ;
$map{sdp}  =  "application/sdp" ;
$map{sgml}  =  "text/sgml" ;
$map{sgm}  =  "text/sgml" ;
$map{sh}  =  "application/x-sh" ;
$map{shar}  =  "application/x-shar" ;
$map{silo}  =  "model/mesh" ;
$map{sit}  =  "application/x-stuffit" ;
$map{skd}  =  "application/x-koan" ;
$map{skm}  =  "application/x-koan" ;
$map{skp}  =  "application/x-koan" ;
$map{skt}  =  "application/x-koan" ;
$map{smi}  =  "application/smil" ;
$map{smil}  =  "application/smil" ;
$map{spl}  =  "application/x-futuresplash" ;
$map{src}  =  "application/x-wais-source" ;
$map{sv4cpio}  =  "application/x-sv4cpio" ;
$map{sv4crc}  =  "application/x-sv4crc" ;
$map{swf}  =  "application/x-shockwave-flash" ;
$map{t}  =  "application/x-troff" ;
$map{tar}  =  "application/x-tar" ;
$map{tcl}  =  "application/x-tcl" ;
$map{tex}  =  "application/x-tex" ;
$map{texi}  =  "application/x-texinfo" ;
$map{texinfo}  =  "application/x-texinfo" ;
$map{tiff}  =  "image/tiff" ;
$map{tif}  =  "image/tiff" ;
$map{tr}  =  "application/x-troff" ;
$map{tsv}  =  "text/tab-separated-values" ;
$map{txt}  =  "text/plain" ;
$map{ustar}  =  "application/x-ustar" ;
$map{vcd}  =  "application/x-cdlink" ;
$map{vrml}  =  "model/vrml" ;
$map{wav}  =  "audio/wav" ;
$map{wdf}  =  "text/x-wdf" ;
$map{wrl}  =  "model/vrml" ;
$map{xbm}  =  "image/x-xbitmap" ;
$map{xml}  =  "text/xml" ;
$map{xpm}  =  "image/x-xpixmap" ;
$map{xwd}  =  "image/x-xwindowdump" ;
$map{xyz}  =  "chemical/x-pdb" ;
$map{zip}  =  "application/zip" ;
}

