# rfc1522.pm
# Author: Noah Friedman <friedman@splode.com>
# Created: 1997-04-03
# Public domain

# $Id: rfc1522.pm,v 1.7 2000/01/30 09:06:59 friedman Exp $

# Commentary:

# Parse names of the form =?ISO-8859-1?Q?Fran=E7ois?= Pinard
#                      or =?ISO-8859-1?Q?Fran=E7ois_Pinard?=
#                      or =?ISO-8859-1?B?RnJhbudvaXMgUGluYXJk?=
# See RFC1522 for more details.

# Code:

package NF::rfc1522;

use Exporter ();
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
$VERSION     = 1.00;
@ISA         = qw(Exporter);
@EXPORT      = qw(parse_rfc1522);
@EXPORT_OK   = qw(base64_decode qp_decode);

use strict;


my @base64_decode_vector;

sub base64_decode ($)
{
  return $_[0] unless (length ($_[0]) % 4 == 0);

  if (!defined @base64_decode_vector)
    {
      my $i = 0;
      my $s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
            . "abcdefghijklmnopqrstuvwxyz"
            . "0123456789"
            . "+/";
      map { $base64_decode_vector[ord $_] = $i++ } split (//, $s);
    }

  my @input = split (//, $_[0]);
  my $result = "";
  my $c = 0;
  my $n = 0;
  while (scalar @input > 0)
    {
      if ($input[0] eq '=')
        {
          $result .= chr ($n >> 10), last if ($c == 2);
          # $c == 3 if we get to this point.
          $result .= chr ($n >> 16);
          $result .= chr (($n >> 8) & 0xff);
          last;
        }
      $n += $base64_decode_vector[ord shift @input];
      if (++$c == 4)
        {
          $result .= chr ($n >> 16);
          $result .= chr (($n >> 8) & 0xff);
          $result .= chr ($n & 0xff);
          $n = $c = 0;
          next;
        }
      $n <<= 6;
    }
  return $result;
}

sub qp_decode ($)
{
  my $data = shift;

  $data =~ y/_/ /;
  my $p = $[;
  while (1)
    {
      $p = index ($data, "=", $p);
      last if ($p < $[);
      # Convert "=XX" (where XX is the hexidecimal representation
      # of an ascii character) to ascii.
      substr ($data, $p, 3) = chr hex substr ($data, $p+1, 2);
      $p++;
    }
  return $data;
}

my $rfc1522_charset = join ("|", "iso-8859-1", "windows-1254");

sub parse_rfc1522 ($)
{
  my $input = shift;
  my $result = "";

  # Perl regexp notes:
  #   *?     provides non-greedy matching
  #   (?:re) provides grouping without creating a saved register
  while ($input =~ m/(.*?)=\?(?:$rfc1522_charset)\?([^?]).*?\?([^?]*)\?=/gcio)
    {
      $result .= $1;
      my $encoding = lc ($2);
      $result .= qp_decode ($3)     if ($encoding eq 'q');
      $result .= base64_decode ($3) if ($encoding eq 'b');
    }
  return $input if ($result eq "");
  return $result . substr ($input, pos $input);
}

1;
