#!/usr/bin/perl -W 
###############################################################################
# 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.23.03
# Version: .0.9
# Credits: 
#    Vadim Zeitlin <vadim@wxwindows.org>, author of yahoo2mbox.pl 
#       for structure and ideas (and doing the hard work for me :)
#    David Symonds <ds@seul.org>, author of NAMG for inspiration.
#    Zainul Charbiwala - Patches to identify single-part html mail and use a webproxy
#    Paul (pknopp), Kyle Wilson, Patrick Shanahan, Michael Strauss, Murtaza Nooruddin
#       for assorted suggestions/support
#    Erwan Mas - Patches to add a config file, spoolMode configuration, forward mail
#       using /usr/lib/sendmail - http://www.mas.nom.fr/fetchyahoo/
###############################################################################
# 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.
################################################################################
# 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. Patch to correctly specify single-part text/html messages - DONE
# 21. Patch to use a web proxy - DONE
# 22. Add configuration of spoolMode - DONE
# 23. Forward to another e-mail address  - DONE
# 24. --verbose otion, --maxsize option - TODO
# 25. Fix to work with other languages - TODO
# 26. Grab all message headers, not only recognized ones. - TODO
####################################################################################

use strict;

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();
		   
# MUST configure these
my $username = 'yahoousername';
my $password = 'yahoopassword';
my $spoolname = '/var/spool/mail/localusername';

# may want to configure these
my $useProxy = 0;                     # set this to 1 to enable use of a web proxy
my $proxyHost = 'proxy.hostname.com';
my $proxyPort = 80;
my $spoolMode = 'append';             # either "append", "pipe" or "overwrite"
                                      # use "pipe" for procmail or another filter

my $forward = 0;                      # set this to 1 to enable mail forwarding
my $mailHost = 'outgoing.mail.com';   # set this to your smtp outgoing mail server
my $sendToAddress = 'me@myhost.com';  # the e-mail address you want mail forwarded to
my $sendFromAddress = 'me@myhost.com'; # the e-mail address used as the from address
                                       # this should probably be at the same ISP as
		                       # the outgoing smtp mailhost specified above

# both of these below defaults can be overridden from the commandline
my $newOnly = 0;           # download all (0) or just new (1) messages
my $noDelete = 0;          # to not delete messages set this to 1

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


# I 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.9\n';

# other variables used
my $spool;
if ($spoolMode eq 'append') {
    $spool = '>>' . $spoolname ; }
elsif ($spoolMode eq 'pipe') {
    $spool = '|' . $spoolname ; }
elsif ($spoolMode eq 'overwrite') {
    $spool = '>' . $spoolname ; }
else { $spool = '>>' . $spoolname ; }     # the default is to append

my $proxyURL = 'http://' . $proxyHost . ':' . $proxyPort . '/' ;
my $smtp;

if ($forward) {
    use Net::SMTP;
    $smtp = Net::SMTP->new($mailHost);
}

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

my %map  = ();     # hash for extension->MIMEtype mappings

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
;

# S T A R T   M A I N   P R O G R A M

# mapping extensions to mime types
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.9');
if ($useProxy) {
    $ua->proxy('http', $proxyURL); }
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"; }

if ( ($response->content) =~ /Invalid Password/ ) {
    print "Failed!\n"; die "Wrong password entered for $username\n"; }

if ( ($response->content) =~ /ID does not exist/ ) {
    print "Failed!\n"; die "Yahoo user $username does not exist\n"; }


print "Successfully logged in as $username.\n"; 

# 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 "Got $msgcount Message IDs\n";
my $delCount = 0;
my $downloadCount = 0;

# 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;
    my $noDelete = 0;
    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" ;
		$noDelete = 1;
		next ; 
	    }
	    
	    my $rawPart = $response->content ;
	    
	    if ($rawPart =~ /<head><title>Yahoo\!\s*-\s*404 Not Found<\/title>/s ) {
		print " \nFailed to get attachment $filenames[$i]. " 
		    . "Skipping attachment, message will not be deleted.\n" ;
		$noDelete = 1;
		next ; 
	    }
	    
            # 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 {  # this is a single part message (either text/plain or text/html)

	# 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 ;

	if ($msgBody =~ /<head><title>Yahoo\!\s*-\s*404 Not Found<\/title>/s ) {
	    print " \nFailed to get message body. " 
		. "Message will be skipped and will not be deleted.\n" ;
	    next ; 
	}

	my $contentType;                    # file.txt can be plain or html
       	if ( ($msgBody =~ /^\s*<html>/i) or ($msgBody =~ /^\s*<\!DOCTYPE HTML/i) ) {
	    $contentType = "text/html"; }
 	else { 
	    $contentType = "text/plain"; }

	
	$msg = build MIME::Entity            # build single-part message
	    Type           => $contentType,
	    '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
    }
    if (! $noDelete) {
	$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    
    open SPOOL, "$spool" or
	die "Can't open output file: $spoolname";
    print SPOOL $fromString ; 
    $msg->print(\*SPOOL);
    print SPOOL "\n" ;
    close SPOOL;

    # mail fowarding stuff goes here
    if ($forward) {
	$smtp->mail($sendFromAddress);
	$smtp->to($sendToAddress);	
	$smtp->data();
	$smtp->datasend($msg->stringify);
	$smtp->dataend();	
	$smtp->quit;  
    }

    $downloadCount = $downloadCount +1 ;
    print $downloadCount%10 ;         # output one digit for every completed message
    
}

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

if ( ! $noDelete) {    
    $delurl = $delurl . "\&.crumb=$crumb";
    $request = GET  $delurl ;
    $response = $ua->simple_request($request) ||
	die "Failed to delete messages.\n";
    
    # if we fail try again once
    if (($response->content) =~ /<head><title>Yahoo\!\s*-\s*404 Not Found<\/title>/s)
    {
	$response = $ua->simple_request($request) ||
	    die "Failed to delete messages.\n";
	
	if (($response->content) 
	    =~ /<head><title>Yahoo\!\s*-\s*404 Not Found<\/title>/s )
	{ 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" ;
}
