Mailcap parsing library

bcutter@pdn.paradyne.com
Wed, 27 Jul 94 13:29:09 EDT


Included below is a mailcap library I've been working on... It's like
Mosaic in that it doesn't support the full MIME mailcap spec, but
unlike Mosaic it does recognize the 'test' field.  (Useful for
using different mailcap lines on different OS's, etc..)

The mailcap library also does not currently support line continuation via the \
character.  This is the next thing I'll fix..

There are two external/public routines that are used... &exists_handler
determines if there is a program defined to handle the desired type,
and &view to execute a the program specified by the view field..

-Brooks
bcutter@paradyne.com or bcutter@stuff.com

----- Begin Included Message -----

# $Id$
# ---------------------------------------------------------------------------
# wwwmime_mailcap.pl: This library implements routines for parsing a 
#                     MIME mailcap file and executing commands based on
#                     a file's MIME Content-type
#
# The MIME mailcap file is defined in the (Work in progress) mailcap draft:
# ftp://venera.isi.edu/internet-drafts/draft-borenstein-mailcap-00.ps
# ftp://venera.isi.edu/internet-drafts/draft-borenstein-mailcap-00.txt
#
# This package currently parses mailcap lines, and uses the test field
# to determine if the mailcap line should be used.  It will save the
# 'view' command associated with each MIME Content-type.
#
# Public/External routines:
#
# &exists_handler($mime_type,$cmd)    - is a mime type handler defined?
#  Returns a 1 if there exists a definition for $mime_type in mailcap
#  Returns 0 if there is no handler for the specified $mime_type
#   $mime_type is of the form "type/subtype" like "text/html"
#   $cmd is optional, and defaults to 'view' if not specified
# &view($mime_type,$fn)               - exec view program for $mime_type
#   $mime_type is of the form "type/subtype" like "text/html"
#   $fn is the name of the file to pass to the program
#
# Private/Internal routines:
#  - You don't need to call (or know about) these routines.
#    the first time you call &view or &exists_handler, &init
#    is called and parses the mailcap files..
#
# &init                 - load defaults and search for mailcap files
# &load_mailcap_default - load mailcap from internal defaults
# &load_mailcap_file    - local mailcap from external file
# &parse_mailcap(@_)    - parse mailcap lines intro internal assoc array
#
#----------------------------------------------------------------------
# Todo:
#      This package recognizes the 'view' and 'test' fields, however there
#      are a number of other fields that should be recognized, and the values
#      (if any) should be saved and accessible through the library API.
#
# The following key/value fields or flags are not currently recognized:
#
# key/value fields: compose, composetyped, edit, print,  description,
#                   textualnewlines, x11bitmap, nametemplate
# flags: needsterminal, copiousoutput, needsx11
#
# Also needs to be done:
# - rewrite parse_mailcap so it recognizes lines that end with \ (backslash)
# - recognize attribute/variable quoting and pass other needed MIME headers.
#----------------------------------------------------------------------
#
# This package has been developed by Brooks Cutter <bcutter@stuff.com>.
# It is distributed under the Artistic License (included with your Perl
# distribution files and with the standard distribution of this package).
#
# 26 Jul 1994 (BBC): Initial version
#
# If you have any suggestions, bug reports, fixes, or enhancements,
# send them to Roy Fielding at <fielding@ics.uci.edu> or to the libwww-perl
# mailing list at <libwww-perl@ics.uci.edu>.
# ---------------------------------------------------------------------------
#
#


package wwwmime_mailcap;

# This is Mosaic's default mailcap ...
$gl_default_mailcap = <<EOF;
audio/*; showaudio %s
image/xwd; xwud -in %s
image/x-xwd; xwud -in %s
image/x-xwindowdump; xwud -in %s
image/*; xv %s
video/mpeg; mpeg_play %s
application/postscript; ghostview %s
application/x-dvi; xdvi %d
message/rfc822; xterm -e metamail %s
EOF

# As defined in draft-bornstein-mailcap-00.txt 5/94
# If Environment variable MAILCAPS isn't set, use the default search path
$gl_mailcap_path = $ENV{'MAILCAPS'} || 
"$ENV{'HOME'}/.mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap";


# ===========================================================================
# exists_handler(): returns 1 if there is a "handler" for $mime_type (1st arg)
#                   otherwise returns 0
#                   $cmd indicates the MIME mailcap command to execute
#                   if $cmd isn't specified, it defaults to 'view'
#
# $ok = &exists_handler($mime_type,$cmd)
#
# WHERE,
#
#        $ok: 1 if there is a mailcap entry for $mime_type and command $cmd
#
# $mime_type: A MIME Content-type, like "text/html"
#
#       $cmd: Either null (defaults to 'view') or a MIME command
#             Currently the only supported command is 'view'
#
# Example
#
# $url = 'http://www.host.dom/dir/file.html';
# $response = &www'request('GET',$url,*headers,*content,$timeout);
# if ($headers{'content-type'} =~ m!^text/(plain|html)$!i) {
#   print $content; # display it on the screen 
# } elsif (&wwwmime_mailcap'exists_handler($headers{'content-type'},'view')) {
#   local($fn) = "/tmp/file.$$.".time;
#   open(OUT,">$fn"); print OUT $content; close(OUT);
#   &wwwmime_mailcap'view($headers{'content-type'},$fn);
# } else {
#   print "Unable to handle MIME type $headers{'content-type'}\n";
# }
#
sub exists_handler {
  local($mime_type,$cmd) = @_;
  &init unless (%gl_mailcap);
  local($type,$subtype) = split(/\//,$mime_type,2);
  $cmd = 'view' unless(@_);
  if (($gl_mailcap{$type,$subtype,$cmd})
  ||  ($gl_mailcap{$type,'*',$cmd})
  ||  ($gl_mailcap{'*','*',$cmd})) {
    return(1);
  }
  return(0);
}


# ===========================================================================
# view(): select the 'view' program for $mime_type and execute with $fn
#
# &exists_handler($mime_type,$fn)
#
# WHERE,
#
# $mime_type: A MIME Content-type, like "text/html"
#
#        $fn: The name of the file to pass to the MIME mailcap 'view' program
#
# Note: For an example including &view, see &exists_handler
#
sub view {
  local($mime_type,$fn) = @_;
  &init unless (%gl_mailcap);
  # %s - replace with name of file (otherwise pass data by stdin)
  # %t - replace with content-type (type/subtype)
  local($type,$subtype) = split(/\//,$mime_type,2);
  local($cmd)         = $gl_mailcap{$type,$subtype,'view'};
                     || $gl_mailcap{$type,'*','view'}
                     || $gl_mailcap{'*','*','view'};
  return unless($cmd); # exit rather than return since already forked
  return if (fork);
  if ($cmd =~ /%s/) { 
    $cmd =~ s/%s/$fn/g;
    if ($cmd =~ /%t/) { $cmd =~ s!%t!$type/$subtype!g; }
    exec "$cmd ; rm -f $fn"; # calls sh -c ..
  } else {
    exec "cat $fn | $cmd ; rm -f $fn"; # calls sh -c ..
  }
  exit;
}


# ===========================================================================
# init(): load mailcap files and default mailcap
#
# &init (no arguments)
#
sub init {
  return if (%gl_mailcap);
  local($_);
  for (split(/:/,$gl_mailcap_path)) {
    next unless(-f $_);
    &load_mailcap_file($_);
  }
  &load_mailcap_default;
}

# ===========================================================================
# load_mailcap_default(): load defaults from global var $gl_default_mailcap
#
# &load_mailcap_default (no arguments)
#
sub load_mailcap_default {
  return(-1) unless($gl_default_mailcap);
  &parse_mailcap(split(/\n/,$gl_default_mailcap));
  return(0);
}

# ===========================================================================
# load_mailcap_file(): load mailcap data from external file $fn
#
# &load_mailcap_file($fn)
#
# WHERE,
#
# $fn: Name of mailcap file to load
#
sub load_mailcap_file {
  local($fn) = @_;
  open(IN,$fn) || return(-1);
  &parse_mailcap(<IN>);
  close(IN);
  return(0);
}

# ===========================================================================
# parse_mailcap(): parse mailcap lines and store in var $gl_default_mailcap
#
# &parse_mailcap(@mailcap_lines)
#
# WHERE,
#
# @mailcap_lines: One or more mailcap lines.  Each element of @mailcap_lines
#                 is a complete entry and has any newlines and/or backslahes
#                 stripped out.
#
sub parse_mailcap {
  local($_,$type,$view,@types,$line,$key,$val,$types,$subtype);

  for (@_) {
    tr/\x00-\x1f\x7f-\xff//d;
    @types = split(/\s*;\s*/);
    $types = shift(@types);
    ($type,$subtype) = split(/\//,$types,2);
    $view = shift(@types);
    for $line (@types) {
      $line =~ s/^\s+//;
      $line =~ s/\s+$//;
      ($key,$val) = split(/\s*=\s*/,$line,2);
      $key =~ tr/A-Z/a-z/;
      if (($key eq 'test') && ($val)) {
        local(@ret) = `sh -c '$val'`;
        local($ret) = ($? >> 8); # value returned by exec'ing $val..
        # if test field returns a non-zero value, ignore the entry
        next if ($ret);
      } 
    }
    if (($view) && (!$gl_mailcap{$type,$subtype,'view'})) {
      $gl_mailcap{$type,$subtype,'view'} = $view;
    }
  }
}

package main;

1;


----- End Included Message -----