First pass at adding key files
This commit is contained in:
		
							
								
								
									
										1282
									
								
								site/glist/lib/GT/Mail/BulkMail.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1282
									
								
								site/glist/lib/GT/Mail/BulkMail.pm
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										524
									
								
								site/glist/lib/GT/Mail/Editor.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										524
									
								
								site/glist/lib/GT/Mail/Editor.pm
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,524 @@
 | 
			
		||||
# ==================================================================
 | 
			
		||||
# Gossamer Threads Module Library - http://gossamer-threads.com/
 | 
			
		||||
#
 | 
			
		||||
#   GT::Mail::Editor
 | 
			
		||||
#
 | 
			
		||||
#   Author: Jason Rhinelander
 | 
			
		||||
#   Revision: $Id: Editor.pm,v 1.24 2005/01/18 23:06:40 bao Exp $
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
# ==================================================================
 | 
			
		||||
#
 | 
			
		||||
# The backend to a web-based e-mail template editor. See the pod for
 | 
			
		||||
# instructions. This is designed the be used primarily from templates.
 | 
			
		||||
# This module respects local directories on saving, and both local and
 | 
			
		||||
# inheritance directories when loading.
 | 
			
		||||
#
 | 
			
		||||
# Also, any subclasses must be (something)::Editor
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
package GT::Mail::Editor;
 | 
			
		||||
 | 
			
		||||
use strict;
 | 
			
		||||
use vars qw($VERSION $DEBUG $ERRORS @ISA $ATTRIBS);
 | 
			
		||||
 | 
			
		||||
use GT::Base;
 | 
			
		||||
use GT::Template;
 | 
			
		||||
 | 
			
		||||
@ISA     = 'GT::Base';
 | 
			
		||||
$DEBUG   = 0;
 | 
			
		||||
$VERSION = sprintf "%d.%03d", q$Revision: 1.24 $ =~ /(\d+)\.(\d+)/;
 | 
			
		||||
 | 
			
		||||
$ERRORS = {
 | 
			
		||||
    PARSE           => "An error occured while parsing: %s",
 | 
			
		||||
    NODIR           => "Template directory not specified",
 | 
			
		||||
    BADDIR          => "Template directory '%s' does not exist or has the permissions set incorrectly",
 | 
			
		||||
    NOFILE          => "No template filename specified",
 | 
			
		||||
    CANT_CREATE_DIR => "Unable to create directory '%s': %s",
 | 
			
		||||
    BADFILE         => "Template '%s' does not exist or is not readable",
 | 
			
		||||
    SAVEERROR       => "Unable to open '%s' for writing: %s",
 | 
			
		||||
    LOADERROR       => "Unable to open '%s' for reading: %s",
 | 
			
		||||
    RECURSION       => "Recursive inheritance detected and interrupted: '%s'",
 | 
			
		||||
    INVALIDDIR      => "Invalid template directory %s",
 | 
			
		||||
    INVALIDTPL      => "Invalid template %s",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
$ATTRIBS = {
 | 
			
		||||
    dir           => '',
 | 
			
		||||
    template      => '',
 | 
			
		||||
    file          => '',
 | 
			
		||||
    headers       => undef,
 | 
			
		||||
    extra_headers => '',
 | 
			
		||||
    body          => ''
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
# GT::Mail::Editor::tpl_save(header => To => $header_to, header => From => $header_from, ..., extra_headers => $extra_headers)
 | 
			
		||||
# ($extra_headers will be parsed). Everything is optional, but you should give something to build headers from.
 | 
			
		||||
# It is not necessary to use To, From, etc. - you can enter them directly in the "extra_headers" field.
 | 
			
		||||
sub tpl_save {
 | 
			
		||||
    # Have to extract the three-argument arguments BEFORE getting $self
 | 
			
		||||
    my @headers;
 | 
			
		||||
    for (my $i = 0; $i < @_; $i++) {
 | 
			
		||||
        if ($_[$i] eq 'header') {
 | 
			
		||||
            push @headers, (splice @_, $i, 3)[1,2];
 | 
			
		||||
            redo;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    my $self = &_get_self;
 | 
			
		||||
    for (my $i = 0; $i < @headers; $i += 2) {
 | 
			
		||||
        $self->{headers}->{$headers[$i]} = $headers[$i+1];
 | 
			
		||||
    }
 | 
			
		||||
    if ($self->{extra_headers}) {
 | 
			
		||||
        for (split /\s*\n\s*/, $self->{extra_headers}) { # This will weed out any blank lines
 | 
			
		||||
            my ($key, $value) = split /\s*:\s*/, $_, 2;
 | 
			
		||||
            $self->{headers}->{$key} = $value if $key and $value;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    my $dir;
 | 
			
		||||
    if ($self->{dir} and $self->{template}) {
 | 
			
		||||
        $dir = "$self->{dir}/$self->{template}/local";
 | 
			
		||||
        if (!-d $dir) {
 | 
			
		||||
            # Attempt to create the "local" subdirectory
 | 
			
		||||
            mkdir($dir, 0777) or return $self->error(CANT_CREATE_DIR => 'FATAL' => $dir => "$!");
 | 
			
		||||
            chmod(0777, $dir);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    elsif ($self->{dir}) {
 | 
			
		||||
        $dir = $self->{dir};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    local *FILE;
 | 
			
		||||
    $self->{_error} = [];
 | 
			
		||||
    if (not $dir) {
 | 
			
		||||
        $self->error(NODIR => 'WARN');
 | 
			
		||||
    }
 | 
			
		||||
    elsif (not -d $dir or not -w $dir) {
 | 
			
		||||
        $self->error(BADDIR => WARN => $dir);
 | 
			
		||||
    }
 | 
			
		||||
    elsif (not $self->{file}) {
 | 
			
		||||
        $self->error(NOFILE => 'WARN');
 | 
			
		||||
    }
 | 
			
		||||
    elsif (-f "$dir/$self->{file}" and not -w _) {
 | 
			
		||||
        $self->error(BADFILE => WARN => "$dir/$self->{file}");
 | 
			
		||||
    }
 | 
			
		||||
    elsif (not open FILE, "> $dir/$self->{file}") {
 | 
			
		||||
        $self->error(SAVEERROR => WARN => "$dir/$self->{file}", "$!");
 | 
			
		||||
    }
 | 
			
		||||
    else { # Everything is good, now we have FILE open to the file.
 | 
			
		||||
        $self->debug("Saving $dir/$self->{file}");
 | 
			
		||||
        my $headers;
 | 
			
		||||
        while (my ($key, $val) = each %{$self->{headers}}) {
 | 
			
		||||
            next unless $key and $val;
 | 
			
		||||
            $key =~ s/\r?\n//g; $val =~ s/\r?\n//g; # Just in case...
 | 
			
		||||
            $headers .= "$key: $val\n";
 | 
			
		||||
        }
 | 
			
		||||
        print FILE $headers;
 | 
			
		||||
        print FILE "" . "\n"; # Blank line
 | 
			
		||||
        $self->{body} =~ s/\r\n/\n/g;
 | 
			
		||||
        print FILE $self->{body};
 | 
			
		||||
        close FILE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (@{$self->{_error}}) {
 | 
			
		||||
        return { error => join("<br>\n", @{$self->{_error}}) };
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        return { success => 1, error => '' };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# GT::Mail::Editor::tpl_load(header => To, header => From, header => Subject)
 | 
			
		||||
# In this case, "To", "From" and "Subject" will come to you as header_To,
 | 
			
		||||
# header_From, and header_Subject.
 | 
			
		||||
# What you get back is a hash reference, with either "error" set to an error
 | 
			
		||||
# if something bad happened, or "success" set to 1, and the following template
 | 
			
		||||
# variables:
 | 
			
		||||
#
 | 
			
		||||
# header_To, header_From, header_Subject, header_...
 | 
			
		||||
#               => The value of the To, From, Subject, etc. field.
 | 
			
		||||
#               -> Only present for individual headers that are requested with "header"
 | 
			
		||||
# extra_headers => A loop of all the other headers with { name => To, From, etc., value => value }
 | 
			
		||||
# body => The body of the e-mail. This will eventually change as this module
 | 
			
		||||
#      -> becomes capable of creating e-mails with multiple parts.
 | 
			
		||||
sub tpl_load {
 | 
			
		||||
    my $self = &_get_self;
 | 
			
		||||
    my %sep_headers;
 | 
			
		||||
    for (my $i = 0; $i < @_; $i++) {
 | 
			
		||||
        if (lc $_[$i] eq 'header') {
 | 
			
		||||
            $sep_headers{$_[++$i]} = 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    my $dir;
 | 
			
		||||
    if ($self->{dir} and $self->{template} and $self->{file}
 | 
			
		||||
        and $self->{template} !~ m[[\\/\x00-\x1f]] and $self->{template} ne '..'
 | 
			
		||||
        and $self->{file} !~ m[[\\/\x00-\x1f]]) {
 | 
			
		||||
        $dir = "$self->{dir}/$self->{template}";
 | 
			
		||||
        if (-f "$dir/local/$self->{file}") {
 | 
			
		||||
            $dir .= "/local";
 | 
			
		||||
        }
 | 
			
		||||
        elsif (!-f "$dir/$self->{file}") {
 | 
			
		||||
            my ($tplinfo, %tplinfo);
 | 
			
		||||
            while ($tplinfo = GT::Template->load_tplinfo($dir) and my $inherit = $tplinfo->{inheritance}) {
 | 
			
		||||
                if ($inherit =~ m!^(?:[a-zA-Z]:)?[\\/]!) { # Absolute inheritance path
 | 
			
		||||
                    $dir = $inherit;
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    $dir .= "/$inherit";
 | 
			
		||||
                }
 | 
			
		||||
                if (-f "$dir/local/$self->{file}") {
 | 
			
		||||
                    $dir .= "/local";
 | 
			
		||||
                    last;
 | 
			
		||||
                }
 | 
			
		||||
                elsif (-f "$dir/$self->{file}") {
 | 
			
		||||
                    last;
 | 
			
		||||
                }
 | 
			
		||||
                if (length $dir > 150 or $tplinfo{$dir}++) { # We've already looked at that directory, or we just have too many relative paths tacked on the end
 | 
			
		||||
                    $self->error(RECURSION => WARN => $dir);
 | 
			
		||||
                    last; # End the loop - there is no more inheritance since we would just be recursing over what we already have
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    my $fh = \do { local *FILE; *FILE };
 | 
			
		||||
    $self->{_error} = [];
 | 
			
		||||
    my $return = { success => 0, error => '' };
 | 
			
		||||
    if ($self->{template} =~ m[[\\/\x00-\x1f]] or $self->{template} eq '..') {
 | 
			
		||||
        $self->error(INVALIDDIR => WARN => $self->{template});
 | 
			
		||||
    }
 | 
			
		||||
    elsif ($self->{file} =~ m[[\\/\x00-\x1f]]) {
 | 
			
		||||
        $self->error(INVALIDTPL => WARN => $self->{file});
 | 
			
		||||
    }
 | 
			
		||||
    elsif (not $dir) {
 | 
			
		||||
        $self->error(NODIR => 'WARN');
 | 
			
		||||
    }
 | 
			
		||||
    elsif (not -d $dir) {
 | 
			
		||||
        $self->error(BADDIR => WARN => $dir);
 | 
			
		||||
    }
 | 
			
		||||
    elsif (not $self->{file}) {
 | 
			
		||||
        $self->error(NOFILE => 'WARN');
 | 
			
		||||
    }
 | 
			
		||||
    elsif (not -r "$dir/$self->{file}") {
 | 
			
		||||
        $self->error(BADFILE => WARN => "$dir/$self->{file}");
 | 
			
		||||
    }
 | 
			
		||||
    elsif (not open $fh, "< $dir/$self->{file}") {
 | 
			
		||||
        $self->error(LOADERROR => WARN => "$dir/$self->{file}");
 | 
			
		||||
    }
 | 
			
		||||
    else { # Everything is good, now we have $fh open to the file.
 | 
			
		||||
        $return->{success} = 1;
 | 
			
		||||
        $self->load($fh);
 | 
			
		||||
        while (my ($name, $val) = each %{$self->{headers}}) {
 | 
			
		||||
            if ($sep_headers{$name}) {
 | 
			
		||||
                $return->{"header_$name"} = $val;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                push @{$return->{extra_headers}}, { name => $name, value => $val };
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $return->{body} = $self->{body};
 | 
			
		||||
    }
 | 
			
		||||
    if ($self->{_error}) {
 | 
			
		||||
        $return->{error} = join "<br>\n", @{$self->{_error}};
 | 
			
		||||
    }
 | 
			
		||||
    return $return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub tpl_delete {
 | 
			
		||||
    my $self = &_get_self;
 | 
			
		||||
 | 
			
		||||
    if ($self->{dir} and $self->{template} and $self->{file}
 | 
			
		||||
        and $self->{template} !~ m[[\\/\x00-\x1f]] and $self->{template} ne '..'
 | 
			
		||||
        and $self->{file} !~ m[[\\/\x00-\x1f]]) {
 | 
			
		||||
        my $tpl = "$self->{dir}/$self->{template}/local/$self->{file}";
 | 
			
		||||
        if (-f $tpl and not unlink $tpl) {
 | 
			
		||||
            return { error => "Unable to remove $tpl: $!" };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return { success => 1, error => '' };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Loads a template from a filehandle or a file.
 | 
			
		||||
# You must pass in a GLOB reference as a filehandle to be read from.
 | 
			
		||||
# Otherwise, this method will attempt to open the file passed in and then read from it.
 | 
			
		||||
# (the file opened will have directory and template prepended to it).
 | 
			
		||||
sub load {
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $fh;
 | 
			
		||||
    my $file = shift;
 | 
			
		||||
    if (ref $file eq 'GLOB' or ref $file eq 'SCALAR' or ref $file eq 'LVALUE') {
 | 
			
		||||
        $fh = $file;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $fh = \do { local *FILE; *FILE };
 | 
			
		||||
        my $dir;
 | 
			
		||||
        if ($self->{template}) {
 | 
			
		||||
            $dir = "$self->{dir}/$self->{template}";
 | 
			
		||||
            if (-f "$dir/local/$file") {
 | 
			
		||||
                $dir .= "/local";
 | 
			
		||||
            }
 | 
			
		||||
            elsif (!-f "$dir/$file") {
 | 
			
		||||
                my ($tplinfo, %tplinfo);
 | 
			
		||||
                while ($tplinfo = GT::Template->load_tplinfo($dir) and my $inherit = $tplinfo->{inheritance}) {
 | 
			
		||||
                    if ($inherit =~ m!^(?:[a-zA-Z]:)?[\\/]!) { # Absolute inheritance path
 | 
			
		||||
                        $dir = $inherit;
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        $dir .= "/$inherit";
 | 
			
		||||
                    }
 | 
			
		||||
                    if (-f "$dir/local/$file") {
 | 
			
		||||
                        $dir .= "/local";
 | 
			
		||||
                        last;
 | 
			
		||||
                    }
 | 
			
		||||
                    elsif (-f "$dir/$file") {
 | 
			
		||||
                        last;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (length $dir > 150 or $tplinfo{$dir}++) { # We've already looked at that directory, or we just have too many relative paths tacked on the end
 | 
			
		||||
                        $self->error(RECURSION => WARN => $dir);
 | 
			
		||||
                        last; # End the loop - there is no more inheritance since we would just be recursing over what we already have
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $file = "$dir/$file";
 | 
			
		||||
 | 
			
		||||
        open $fh, "< $file" or return $self->error(BADFILE => WARN => $file);
 | 
			
		||||
    }
 | 
			
		||||
    if (ref $fh eq 'GLOB') {
 | 
			
		||||
        while (<$fh>) { # The header
 | 
			
		||||
            s/\r?\n$//;
 | 
			
		||||
            last if not $_; # An empty line is the end of the headers
 | 
			
		||||
            my ($field, $value) = split /:\s*/, $_, 2;
 | 
			
		||||
            $self->{headers}->{$field} = $value;
 | 
			
		||||
        }
 | 
			
		||||
        while (<$fh>) { # The body
 | 
			
		||||
            $self->{body} .= $_;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        (my $header, $self->{body}) = split /\r?\n\r?\n/, $$fh, 2;
 | 
			
		||||
        my @h = split /\r?\n/, $header;
 | 
			
		||||
        for (@h) {
 | 
			
		||||
            my ($field, $value) = split /:\s*/, $_, 2;
 | 
			
		||||
            $self->{headers}->{$field} = $value;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Creates and returns a $self object. Looks at $_[0] to see if it is already
 | 
			
		||||
# an editor object, and if so uses that. Otherwise it calls new() with @_.
 | 
			
		||||
# Should be called as &_get_self; If called as a class method, the first
 | 
			
		||||
# argument will be removed. So, instead of: 'my $self = shift;' you should
 | 
			
		||||
# use: 'my $self = &_get_self;'
 | 
			
		||||
sub _get_self {
 | 
			
		||||
    my $self;
 | 
			
		||||
    if (ref $_[0] and substr(ref $_[0], -8) eq '::Editor') { # This will allow any subclass as long as it is something::Editor
 | 
			
		||||
        $self = shift;
 | 
			
		||||
    }
 | 
			
		||||
    elsif (@_ and substr($_[0], -8) eq '::Editor') { # Class methods
 | 
			
		||||
        my $class = shift;
 | 
			
		||||
        $self = $class->new(@_);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $self = __PACKAGE__->new(@_);
 | 
			
		||||
    }
 | 
			
		||||
    return $self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub init {
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    $self->set(@_);
 | 
			
		||||
    tie %{$self->{headers}}, __PACKAGE__ . '::Ordered';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
package GT::Mail::Editor::Ordered;
 | 
			
		||||
# Implements a hash that retains the order elements are inserted into it.
 | 
			
		||||
 | 
			
		||||
sub TIEHASH { bless { o => [], h => {}, p => 0 }, $_[0] }
 | 
			
		||||
 | 
			
		||||
sub STORE {
 | 
			
		||||
    my ($self, $key, $val) = @_;
 | 
			
		||||
    $self->DELETE($key) if exists $self->{h}->{$key};
 | 
			
		||||
    $self->{h}->{$key} = $val;
 | 
			
		||||
    push @{$self->{o}}, $key;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub FETCH { $_[0]->{h}->{$_[1]} }
 | 
			
		||||
 | 
			
		||||
sub FIRSTKEY {
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    $self->{p} = 0;
 | 
			
		||||
    $self->{o}->[$self->{p}++]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub NEXTKEY { $_[0]->{o}->[$_[0]->{p}++] }
 | 
			
		||||
 | 
			
		||||
sub EXISTS { exists $_[0]->{h}->{$_[1]} }
 | 
			
		||||
 | 
			
		||||
sub DELETE {
 | 
			
		||||
    my ($self, $key) = @_;
 | 
			
		||||
    for (0 .. $#{$self->{o}}) {
 | 
			
		||||
        if ($self->{o}->[$_] eq $key) {
 | 
			
		||||
            splice @{$self->{o}}, $_, 1;
 | 
			
		||||
            last;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    delete $self->{h}->{$key};
 | 
			
		||||
}
 | 
			
		||||
sub CLEAR { $_[0] = { o => [], h => {}, p => 0 }; () }
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
 | 
			
		||||
__END__
 | 
			
		||||
 | 
			
		||||
=head1 NAME
 | 
			
		||||
 | 
			
		||||
GT::Mail::Editor - E-mail template editor
 | 
			
		||||
 | 
			
		||||
=head1 SYNOPSIS
 | 
			
		||||
 | 
			
		||||
Generally used from templates:
 | 
			
		||||
 | 
			
		||||
    <%GT::Mail::Editor::tpl_load(
 | 
			
		||||
        dir => $template_root,
 | 
			
		||||
        template => $template_set,
 | 
			
		||||
        file => $filename,
 | 
			
		||||
        header => From,
 | 
			
		||||
        header => To,
 | 
			
		||||
        header => Subject
 | 
			
		||||
    )%>
 | 
			
		||||
 | 
			
		||||
    <%if error%>
 | 
			
		||||
        Unable to load e-mail template: <%error%>
 | 
			
		||||
    <%else%>
 | 
			
		||||
        From: <input type=text name=header_From value="<%header_From%>">
 | 
			
		||||
        To: <input type=text name=header_To value="<%header_To%>">
 | 
			
		||||
        Subject: <input type=text name=header_Subject value="<%header_Subject%>">
 | 
			
		||||
        Other headers:<br>
 | 
			
		||||
        <textarea name=extra_headers>
 | 
			
		||||
        <%loop extra_headers%><%name%>: <%value%>
 | 
			
		||||
        <%endloop%>
 | 
			
		||||
    <%endif%>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    - or -
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <%GT::Mail::Editor::save(
 | 
			
		||||
        dir => $template_root,
 | 
			
		||||
        template => $template_set,
 | 
			
		||||
        file => $filename,
 | 
			
		||||
        header => To => $header_To,
 | 
			
		||||
        header => From => $header_From,
 | 
			
		||||
        header => Subject => $header_Subject,
 | 
			
		||||
        extra_headers => $extra_headers
 | 
			
		||||
    )%>
 | 
			
		||||
    <%if error%>Unable to save e-mail template: <%error%>
 | 
			
		||||
        ... Display the above form in here ...
 | 
			
		||||
    <%endif%>
 | 
			
		||||
 | 
			
		||||
=head1 DESCRIPTION
 | 
			
		||||
 | 
			
		||||
GT::Mail::Editor is designed to provide a template interface to creating and
 | 
			
		||||
editing a wide variety of e-mail templates. Although not currently supported,
 | 
			
		||||
eventually attachments, HTML, etc. will be supported.
 | 
			
		||||
 | 
			
		||||
=head2 tpl_load - Loads a template (from the templates)
 | 
			
		||||
 | 
			
		||||
Calling GT::Mail::Editor::tpl_load from a template returns variables required to
 | 
			
		||||
display a form to edit the template passed in.
 | 
			
		||||
 | 
			
		||||
=over 4
 | 
			
		||||
 | 
			
		||||
=item dir
 | 
			
		||||
 | 
			
		||||
Defines the base directory of templates.
 | 
			
		||||
 | 
			
		||||
=item template
 | 
			
		||||
 | 
			
		||||
This defines a template set. This is optional. If present, this directory will
 | 
			
		||||
be tacked onto the end of 'dir'. This is simply to provide a more flexible way
 | 
			
		||||
to specify the template directory. For example, if you have 'dir' set to '/a/b'
 | 
			
		||||
and template set to 'c', then the directory '/a/b/c' will be used to save and
 | 
			
		||||
load e-mail templates.
 | 
			
		||||
 | 
			
		||||
=item file
 | 
			
		||||
 | 
			
		||||
Specify the filename of the template inside the directory already specified with
 | 
			
		||||
'dir' and 'template'
 | 
			
		||||
 | 
			
		||||
=item header
 | 
			
		||||
 | 
			
		||||
Multiple "special" headers can be requested with this. The argument following
 | 
			
		||||
each 'header' should be the name of a header, such as "To". Then, in the
 | 
			
		||||
variables returned from tpl_load(), you will have a variable such as 'header_To'
 | 
			
		||||
available, containing the value of the To: field.
 | 
			
		||||
 | 
			
		||||
=back
 | 
			
		||||
 | 
			
		||||
=head2 tpl_save - Save a template
 | 
			
		||||
 | 
			
		||||
=over 4
 | 
			
		||||
 | 
			
		||||
=item dir template file
 | 
			
		||||
 | 
			
		||||
See the entries in L<"tpl_load">
 | 
			
		||||
 | 
			
		||||
=item header
 | 
			
		||||
 | 
			
		||||
Specifies that the two following arguments are the field and value of a header
 | 
			
		||||
field. For example, header => To => "abc@example.com" would specify that the To
 | 
			
		||||
field should be "abc@example.com" (To: abc@example.com).
 | 
			
		||||
 | 
			
		||||
=item extra_headers
 | 
			
		||||
 | 
			
		||||
The value to extra_headers should be a newline-delimited list of headers other
 | 
			
		||||
than those specified with header. These will be parsed, and blank lines skipped.
 | 
			
		||||
 | 
			
		||||
=item body
 | 
			
		||||
 | 
			
		||||
The body of the message. Need I say more? MIME messages are possible by
 | 
			
		||||
inserting them directly into the body, however currently MIME messages cannot
 | 
			
		||||
be created using this editor.
 | 
			
		||||
 | 
			
		||||
=back
 | 
			
		||||
 | 
			
		||||
=head2 load
 | 
			
		||||
 | 
			
		||||
Attempts to load a GT::Mail::Editor object with data passed in. This can take
 | 
			
		||||
either a file handle or a filename. If passing a filename, dir and template
 | 
			
		||||
will be used (if available). You should construct an object with new() prior
 | 
			
		||||
to calling this method.
 | 
			
		||||
 | 
			
		||||
=head2 new
 | 
			
		||||
 | 
			
		||||
Constructs a new GT::Mail::Editor object. This will be done automatically when
 | 
			
		||||
using the template methods L<"tpl_load"> and L<"tpl_save">. Takes the following
 | 
			
		||||
arguments:
 | 
			
		||||
 | 
			
		||||
=over 4
 | 
			
		||||
 | 
			
		||||
=item dir
 | 
			
		||||
 | 
			
		||||
Defines the base directory of templates.
 | 
			
		||||
 | 
			
		||||
=item template
 | 
			
		||||
 | 
			
		||||
This defines a template set. This is optional. If present, this directory will
 | 
			
		||||
be tacked onto the end of 'dir'. This is simply to provide a more flexible way
 | 
			
		||||
to specify the template directory. For example, if you have 'dir' set to '/a/b'
 | 
			
		||||
and template set to 'c', then the directory '/a/b/c' will be used to save and
 | 
			
		||||
load e-mail templates.
 | 
			
		||||
 | 
			
		||||
=back
 | 
			
		||||
 | 
			
		||||
=head1 COPYRIGHT
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
http://www.gossamer-threads.com/
 | 
			
		||||
 | 
			
		||||
=head1 VERSION
 | 
			
		||||
 | 
			
		||||
Revision: $Id: Editor.pm,v 1.24 2005/01/18 23:06:40 bao Exp $
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										267
									
								
								site/glist/lib/GT/Mail/Editor/HTML.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										267
									
								
								site/glist/lib/GT/Mail/Editor/HTML.pm
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,267 @@
 | 
			
		||||
 | 
			
		||||
package GT::Mail::Editor::HTML;
 | 
			
		||||
 | 
			
		||||
use vars qw/$ERROR_MESSAGE/;
 | 
			
		||||
use strict;
 | 
			
		||||
use bases 'GT::Mail::Editor' => '';
 | 
			
		||||
 | 
			
		||||
$ERROR_MESSAGE = 'GT::Mail::Editor';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sub display {
 | 
			
		||||
# ----------------------------------------------------------------
 | 
			
		||||
    my ( $self, $tags ) = @_;
 | 
			
		||||
    my $page = $self->{html_tpl_name};
 | 
			
		||||
 | 
			
		||||
    if ( $self->{fields}{page} and $self->{fields}{page} =~ /^(?:editor|email)_/ ) {
 | 
			
		||||
        $page = $self->{fields}{page};
 | 
			
		||||
    }
 | 
			
		||||
    my $ret = $self->print_page( $page, $tags );
 | 
			
		||||
    $self->{displayed} = 1;
 | 
			
		||||
    return $ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub message_from_input {
 | 
			
		||||
# ----------------------------------------------------------------
 | 
			
		||||
    my ( $self ) = @_;
 | 
			
		||||
    $self->set_headers;
 | 
			
		||||
 | 
			
		||||
# If we have a part ID, this isn't a new text part
 | 
			
		||||
    my ( $part, $id );
 | 
			
		||||
    $part = $self->{part};
 | 
			
		||||
    $part->set( 'content-type' => 'text/html; charset="'.( $self->{fields}{charset} || 'US-ASCII' ).'"' );
 | 
			
		||||
    if ( exists( $self->{fields}{msg} ) ) {
 | 
			
		||||
        my $msg = $self->{fields}{msg};
 | 
			
		||||
        $self->urls_to_inlines( $self->{part}, \$msg );
 | 
			
		||||
        $part->body_data( $msg );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub munge_message {
 | 
			
		||||
# ----------------------------------------------------------------
 | 
			
		||||
    my ( $self ) = @_;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    my $root_part = $self->{message}->root_part;
 | 
			
		||||
 | 
			
		||||
# Simple case if the message is not multipart
 | 
			
		||||
    if ( !$root_part->is_multipart ) {
 | 
			
		||||
        $self->munge_non_multipart( $root_part );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# We have a multipart. First thing we do is look for an alternative part
 | 
			
		||||
# to use.
 | 
			
		||||
    elsif ( my ( $alt ) = $self->{message}->find_multipart( 'alternative' ) ) {
 | 
			
		||||
        $self->munge_alternative( $alt );
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $self->munge_other;
 | 
			
		||||
    }
 | 
			
		||||
    $self->fix_alt_parts;
 | 
			
		||||
    $self->fix_related_parts;
 | 
			
		||||
    $self->delete_empty_multiparts;
 | 
			
		||||
    my ( $alt_part ) = $self->{message}->find_multipart( 'alternative' );
 | 
			
		||||
    my @skip = $alt_part->parts;
 | 
			
		||||
    $self->find_attachments( @skip );
 | 
			
		||||
    $self->{alt_part} = $alt_part;
 | 
			
		||||
    $self->{part} = $skip[1];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub html_part {
 | 
			
		||||
# ----------------------------------------------------------------
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    return $self->{alt_part}->parts->[1];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub text_part {
 | 
			
		||||
# ----------------------------------------------------------------
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    return $self->{alt_part}->parts->[0];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub munge_non_multipart {
 | 
			
		||||
# ----------------------------------------------------------------
 | 
			
		||||
    my ( $self, $root_part ) = @_;
 | 
			
		||||
 | 
			
		||||
# We need to munge the message into a multipart
 | 
			
		||||
    my $new_alt = $self->alt_part(
 | 
			
		||||
        html         => $root_part,
 | 
			
		||||
        charset      => $root_part->mime_attr( 'content-type.charset' ),
 | 
			
		||||
        headers_part => $root_part
 | 
			
		||||
    );
 | 
			
		||||
    $root_part->set( 'content-type' => 'multipart/mixed' );
 | 
			
		||||
    $root_part->parts( $new_alt );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub munge_alternative {
 | 
			
		||||
# ----------------------------------------------------------------
 | 
			
		||||
    my ( $self, $alt_part ) = @_;
 | 
			
		||||
    my $root_part = $self->{message}->root_part;
 | 
			
		||||
 | 
			
		||||
# Make anything we can not view an attachment
 | 
			
		||||
    $self->{message}->move_parts_last(
 | 
			
		||||
        $root_part,
 | 
			
		||||
        grep {
 | 
			
		||||
            $_->content_type ne 'text/plain' and $_->content_type ne 'text/html'
 | 
			
		||||
        } $alt_part->parts
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
# Anything left is either text or html
 | 
			
		||||
    my ( $html_part, $text_part );
 | 
			
		||||
    for ( $alt_part->parts ) {
 | 
			
		||||
        if ( $_->content_type eq 'text/html' ) {
 | 
			
		||||
            $html_part = $_;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            $text_part = $_;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
# If we do not have an editble part we need to make an empty html one
 | 
			
		||||
    if ( !defined( $text_part ) and !defined( $html_part ) ) {
 | 
			
		||||
        $html_part = $self->{message}->new_part(
 | 
			
		||||
            'content-type' => 'text/html; charset="'.( $self->{fields}{charset} || 'US-ASCII' ).'"',
 | 
			
		||||
            -body_data     => '<html><body></body></html>'
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    my $new_alt = $self->alt_part(
 | 
			
		||||
        html    => $html_part,
 | 
			
		||||
        text    => $text_part,
 | 
			
		||||
        charset => $self->{fields}{charset}
 | 
			
		||||
    );
 | 
			
		||||
    if ( $alt_part == $root_part ) {
 | 
			
		||||
        $root_part->set( 'content-type' => 'multipart/mixed' );
 | 
			
		||||
        $self->{message}->delete_parts( $root_part->parts );
 | 
			
		||||
        $root_part->parts( $new_alt );
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $self->{message}->replace_part( $alt_part, $new_alt );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub munge_other {
 | 
			
		||||
# ----------------------------------------------------------------
 | 
			
		||||
    my ( $self ) = @_;
 | 
			
		||||
 | 
			
		||||
# Else we need to search through the parts to find the displayable parts
 | 
			
		||||
    my ( $html_part, $text_part );
 | 
			
		||||
    for my $part ( $self->{message}->all_parts ) {
 | 
			
		||||
        if ( !$html_part and $part->content_type eq 'text/html' and $part->mime_attr( 'content-disposition' ) ne 'attachment' ) {
 | 
			
		||||
            $html_part = $part;
 | 
			
		||||
        }
 | 
			
		||||
        elsif ( !$text_part and $part->content_type eq 'text/plain' and $part->mime_attr( 'content-disposition' ) ne 'attachment' ) {
 | 
			
		||||
            $text_part = $part;
 | 
			
		||||
        }
 | 
			
		||||
        last if $html_part and $text_part;
 | 
			
		||||
    }
 | 
			
		||||
# If we do not have an editble part we need to make an empty html one
 | 
			
		||||
    if ( !defined( $text_part ) and !defined( $html_part ) ) {
 | 
			
		||||
        $html_part = $self->{message}->new_part(
 | 
			
		||||
            'content-type' => 'text/html; charset="'.( $self->{fields}{charset} || 'US-ASCII' ).'"',
 | 
			
		||||
            -body_data     => '<html><body></body></html>'
 | 
			
		||||
        );
 | 
			
		||||
        my $new_alt = $self->alt_part(
 | 
			
		||||
            html    => $html_part,
 | 
			
		||||
            text    => $text_part,
 | 
			
		||||
            charset => $self->{fields}{charset}
 | 
			
		||||
        );
 | 
			
		||||
        $self->{message}->add_parts_start( $self->{message}->root_part, $new_alt );
 | 
			
		||||
        my $parent = $self->{message}->parent_part( $new_alt );
 | 
			
		||||
        if ( $parent and $parent->content_type eq 'multipart/related' ) {
 | 
			
		||||
            $parent->set( 'content-type' => 'multipart/mixed' );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        my $new_alt = $self->alt_part(
 | 
			
		||||
            html    => $html_part,
 | 
			
		||||
            text    => $text_part,
 | 
			
		||||
            charset => $self->{fields}{charset}
 | 
			
		||||
        );
 | 
			
		||||
        my $parent_part = $self->{message}->parent_part( $html_part );
 | 
			
		||||
        if ( !$parent_part ) { $parent_part = $self->{message}->parent_part( $text_part ) }
 | 
			
		||||
        if ( $parent_part and $parent_part->content_type eq 'multipart/related' ) {
 | 
			
		||||
            if ( !$html_part ) {
 | 
			
		||||
                $parent_part->set( 'content-type' => 'multipart/mixed' );
 | 
			
		||||
                $self->{message}->add_parts_start( $parent_part, $new_alt );
 | 
			
		||||
                if ( $text_part ) {
 | 
			
		||||
                    $self->{message}->delete_part( $text_part );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                $self->{message}->replace_part( $parent_part->parts->[0], $new_alt );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            if ( $text_part ) {
 | 
			
		||||
                $self->{message}->delete_part( $text_part );
 | 
			
		||||
            }
 | 
			
		||||
            if ( $html_part ) {
 | 
			
		||||
                $self->{message}->delete_part( $html_part );
 | 
			
		||||
            }
 | 
			
		||||
            $self->{message}->add_parts_start( $self->{message}->root_part, $new_alt );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub alt_part {
 | 
			
		||||
# ----------------------------------------------------------------
 | 
			
		||||
    my ( $self, %opts ) = @_;
 | 
			
		||||
    my ( $text, $html, $header_from, $charset ) = @opts{qw/text html headers_part charset/};
 | 
			
		||||
 | 
			
		||||
    my $text_type = 'text/plain; charset="'.( $self->{fields}{charset} || 'US-ASCII' ).'"';
 | 
			
		||||
    my $html_type = 'text/html; charset="'.( $self->{fields}{charset} || 'US-ASCII' ).'"';
 | 
			
		||||
 | 
			
		||||
    if ( defined( $text ) ) {
 | 
			
		||||
        $text = $self->new_part_from( $text, $text_type );
 | 
			
		||||
    }
 | 
			
		||||
    elsif ( defined( $html ) ) {
 | 
			
		||||
        $text = $self->{message}->new_part(
 | 
			
		||||
            'content-type' => $text_type,
 | 
			
		||||
            -body_data     => $self->html_to_text( ref( $html ) ? $html->body_data : $html )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $self->fatal( BADARGS => "Either text or html must be defined" );
 | 
			
		||||
    }
 | 
			
		||||
    if ( defined( $html ) ) {
 | 
			
		||||
        $html = $self->new_part_from( $html, $html_type );
 | 
			
		||||
    }
 | 
			
		||||
    elsif ( defined( $text ) ) {
 | 
			
		||||
        $html = $self->{message}->new_part(
 | 
			
		||||
            'content-type' => $html_type,
 | 
			
		||||
            -body_data     => $self->text_to_html( $text->body_data )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    # logic error, one must be defined
 | 
			
		||||
    else {
 | 
			
		||||
        $self->fatal( BADARGS => "Either text or html must be defined" );
 | 
			
		||||
    }
 | 
			
		||||
    my @header = ();
 | 
			
		||||
    if ( $header_from ) {
 | 
			
		||||
        @header = map { $_ => [$header_from->get( $_ )] } $header_from->get;
 | 
			
		||||
    }
 | 
			
		||||
    return $self->{message}->new_part(
 | 
			
		||||
        @header,
 | 
			
		||||
        'content-type' => 'multipart/alternative',
 | 
			
		||||
        -parts         => [$text, $html]
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub new_part_from {
 | 
			
		||||
# ----------------------------------------------------------------
 | 
			
		||||
    my ( $self, $from, $type ) = @_;
 | 
			
		||||
    if ( !ref( $from ) ) {
 | 
			
		||||
        return $self->{message}->new_part(
 | 
			
		||||
            'content-type' => $type,
 | 
			
		||||
            -body_data     => $from
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    elsif ( ref( $from ) ) {
 | 
			
		||||
        return $self->{message}->new_part(
 | 
			
		||||
            'content-type' => $type,
 | 
			
		||||
            -body_data     => $from->body_data
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    
 | 
			
		||||
1;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										147
									
								
								site/glist/lib/GT/Mail/Editor/Text.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								site/glist/lib/GT/Mail/Editor/Text.pm
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
			
		||||
 | 
			
		||||
package GT::Mail::Editor::Text;
 | 
			
		||||
 | 
			
		||||
use vars qw/$ERROR_MESSAGE/;
 | 
			
		||||
use strict;
 | 
			
		||||
use bases 'GT::Mail::Editor' => '';
 | 
			
		||||
 | 
			
		||||
$ERROR_MESSAGE = 'GT::Mail::Editor';
 | 
			
		||||
 | 
			
		||||
sub display {
 | 
			
		||||
# ----------------------------------------------------------------
 | 
			
		||||
    my ( $self, $tags ) = @_;
 | 
			
		||||
    my $page = $self->{text_tpl_name};
 | 
			
		||||
 | 
			
		||||
    if ( $self->{fields}{page} and $self->{fields}{page} =~ /^(?:editor|email)_/ ) {
 | 
			
		||||
        $page = $self->{fields}{page};
 | 
			
		||||
    }
 | 
			
		||||
    my $ret = $self->print_page( $page, $tags );
 | 
			
		||||
    $self->{displayed} = 1;
 | 
			
		||||
    return $ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub message_from_input {
 | 
			
		||||
# ----------------------------------------------------------------
 | 
			
		||||
    my ( $self ) = @_;
 | 
			
		||||
    $self->set_headers;
 | 
			
		||||
 | 
			
		||||
# If we have a part ID, this isn't a new text part
 | 
			
		||||
    my ( $part, $id );
 | 
			
		||||
    $part = $self->{part};
 | 
			
		||||
    $part->set( 'content-type' => 'text/plain; charset="'.( $self->{fields}{charset} || 'US-ASCII' ).'"' );
 | 
			
		||||
    if ( exists( $self->{fields}{msg} ) ) {
 | 
			
		||||
        $part->body_data( $self->{fields}{msg} );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub munge_message {
 | 
			
		||||
# ----------------------------------------------------------------
 | 
			
		||||
    my ( $self ) = @_;
 | 
			
		||||
    
 | 
			
		||||
    my $root_part = $self->{message}->root_part;
 | 
			
		||||
 | 
			
		||||
# Simple case if the message is not multipart
 | 
			
		||||
    my ( $text_part, $html_part, $related_part, $alt_part );
 | 
			
		||||
    if ( !$root_part->is_multipart ) {
 | 
			
		||||
        $text_part = $root_part;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# We have a multipart. First thing we do is look for an alternative part
 | 
			
		||||
# to use.
 | 
			
		||||
    else {
 | 
			
		||||
    
 | 
			
		||||
# First we look for the proper alternative mime parts
 | 
			
		||||
        $alt_part = ($self->{message}->find_multipart( 'alternative' ))[0];
 | 
			
		||||
        if ( $alt_part ) {
 | 
			
		||||
            my @alt_parts = $alt_part->parts;
 | 
			
		||||
            for ( @alt_parts ) {
 | 
			
		||||
                if ( $_->content_type eq 'text/plain' ) {
 | 
			
		||||
                    $text_part = $self->{message}->delete_part( $_ );
 | 
			
		||||
                }
 | 
			
		||||
                elsif ( $_->content_type eq 'text/html' ) {
 | 
			
		||||
                    $html_part = $self->{message}->delete_part( $_ );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if ( !$text_part and $html_part ) {
 | 
			
		||||
                $text_part = $self->{message}->new_part(
 | 
			
		||||
                    'content-type' => 'text/plain',
 | 
			
		||||
                    -body_data     => $self->html_to_text( $html_part->body_data )
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            elsif ( !$text_part ) {
 | 
			
		||||
                $text_part = $self->{message}->new_part(
 | 
			
		||||
                    'content-type' => 'text/plain',
 | 
			
		||||
                    -body_data     => ''
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
# Make anything we can not view an attachment
 | 
			
		||||
            $self->{message}->move_parts_last(
 | 
			
		||||
                $root_part,
 | 
			
		||||
                map {
 | 
			
		||||
                    unless ( $_->is_multipart ) {
 | 
			
		||||
                        $_->set( 'content-disposition' => 'attachment' );
 | 
			
		||||
                    }
 | 
			
		||||
                    $_;
 | 
			
		||||
                } $alt_part->parts
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            if ( $alt_part == $root_part ) {
 | 
			
		||||
                $alt_part->set( 'content-type' => 'multipart/mixed' );
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                $self->{message}->delete_part( $alt_part );
 | 
			
		||||
            }
 | 
			
		||||
            $self->{message}->add_parts_start( $self->{message}->root_part, $text_part );
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
 | 
			
		||||
# Else we can just stick the text part at the beginning
 | 
			
		||||
            for my $part ( $self->{message}->all_parts ) {
 | 
			
		||||
                my $disp = $part->mime_attr( 'content-disposition' );
 | 
			
		||||
                next if $disp and $disp eq 'attachment';
 | 
			
		||||
                if ( $part->content_type eq 'text/plain' ) {
 | 
			
		||||
                    $text_part = $self->{message}->delete_part( $part );
 | 
			
		||||
                }
 | 
			
		||||
                elsif ( $part->content_type eq 'text/html' ) {
 | 
			
		||||
                    $html_part = $self->{message}->delete_part( $part );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if ( !$text_part and $html_part ) {
 | 
			
		||||
                $text_part = $self->{message}->new_part(
 | 
			
		||||
                    'content-type' => 'text/plain',
 | 
			
		||||
                    -body_data     => $self->html_to_text( $html_part->body_data )
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            elsif ( !$text_part ) {
 | 
			
		||||
                $text_part = $self->{message}->new_part(
 | 
			
		||||
                    'content-type' => 'text/plain',
 | 
			
		||||
                    -body_data     => ''
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            $self->{message}->add_parts_start( $self->{message}->root_part, $text_part );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    my $parent = $self->{message}->parent_part( $text_part );
 | 
			
		||||
    if ( $parent and $parent->content_type eq 'multipart/related' ) {
 | 
			
		||||
        $parent->set( 'content-type' => 'multipart/mixed' );
 | 
			
		||||
    }
 | 
			
		||||
    $self->fix_alt_parts;
 | 
			
		||||
    $self->fix_related_parts;
 | 
			
		||||
    $self->delete_empty_multiparts;
 | 
			
		||||
    $self->find_attachments( $text_part );
 | 
			
		||||
 | 
			
		||||
    if ( @{[$self->{message}->all_parts]} == 1 and $self->{message}->root_part->is_multipart ) {
 | 
			
		||||
        $self->{message}->delete_part( $text_part );
 | 
			
		||||
        my $root_part = $self->{message}->root_part;
 | 
			
		||||
        $root_part->set( 'content-type' => 'text/plain' );
 | 
			
		||||
        $root_part->body_data( $text_part->body_data );
 | 
			
		||||
    }
 | 
			
		||||
    $self->{part} = $text_part;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub html_part { return }
 | 
			
		||||
sub text_part { return shift()->{part} }
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										429
									
								
								site/glist/lib/GT/Mail/Encoder.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										429
									
								
								site/glist/lib/GT/Mail/Encoder.pm
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,429 @@
 | 
			
		||||
# ==================================================================
 | 
			
		||||
# Gossamer Threads Module Library - http://gossamer-threads.com/
 | 
			
		||||
#
 | 
			
		||||
#   GT::Mail::Encoder
 | 
			
		||||
#   Author  : Scott Beck
 | 
			
		||||
#   CVS Info :                          
 | 
			
		||||
#   $Id: Encoder.pm,v 1.40 2004/01/13 01:35:17 jagerman Exp $
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
# ==================================================================
 | 
			
		||||
#
 | 
			
		||||
# Description: A general purpose perl interface for encoding data.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
package GT::Mail::Encoder;
 | 
			
		||||
# ==================================================================
 | 
			
		||||
# If MIME::Base64 is installed use it - must eval before hand or 5.004_04
 | 
			
		||||
# wipes our ISA.
 | 
			
		||||
my $have_b64 = eval {
 | 
			
		||||
    local $SIG{__DIE__};
 | 
			
		||||
    require MIME::Base64;
 | 
			
		||||
    import MIME::Base64;
 | 
			
		||||
    if ($] < 5.005) { local $^W; encode_base64('brok'); }
 | 
			
		||||
    1;
 | 
			
		||||
};
 | 
			
		||||
$have_b64 or *encode_base64 = \>_old_encode_base64;
 | 
			
		||||
my $use_encode_qp;
 | 
			
		||||
if ($have_b64 and
 | 
			
		||||
    $MIME::Base64::VERSION >= 2.16 and
 | 
			
		||||
    defined &MIME::QuotedPrint::encode_qp and (
 | 
			
		||||
        not defined &MIME::QuotedPrint::old_encode_qp or
 | 
			
		||||
        \&MIME::QuotedPrint::encode_qp != \&MIME::QuotedPrint::old_encode_qp
 | 
			
		||||
    )
 | 
			
		||||
) {
 | 
			
		||||
    $use_encode_qp = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Pragmas
 | 
			
		||||
use strict;
 | 
			
		||||
use vars qw($VERSION $DEBUG @ISA %EncodeFor $CRLF);
 | 
			
		||||
 | 
			
		||||
$VERSION = sprintf "%d.%03d", q$Revision: 1.40 $ =~ /(\d+)\.(\d+)/;
 | 
			
		||||
$CRLF    = "\015\012";
 | 
			
		||||
$DEBUG   = 0;
 | 
			
		||||
@ISA     = qw(GT::Base);
 | 
			
		||||
 | 
			
		||||
my %EncoderFor = (
 | 
			
		||||
    # Standard...
 | 
			
		||||
    '7bit'       => sub { NBit('7bit', @_) },
 | 
			
		||||
    '8bit'       => sub { NBit('8bit', @_) },
 | 
			
		||||
    'base64'     => \&Base64,
 | 
			
		||||
    'binary'     => \&Binary,
 | 
			
		||||
    'none'       => \&Binary,
 | 
			
		||||
    'quoted-printable' => \&QuotedPrint,
 | 
			
		||||
 | 
			
		||||
    # Non-standard...
 | 
			
		||||
    'x-uu'       => \&UU,
 | 
			
		||||
    'x-uuencode' => \&UU,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
sub new {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    my $this = shift;
 | 
			
		||||
    my $class = ref $this || $this;
 | 
			
		||||
    my $self = bless {}, $class;
 | 
			
		||||
    $self->init(@_);
 | 
			
		||||
    my $encoding = lc($self->{encoding} || '');
 | 
			
		||||
    defined $EncoderFor{$encoding} or return or return $self->error("NOENCODING", "FATAL");
 | 
			
		||||
    $self->debug("Set encoding to $encoding") if ($self->{_debug});
 | 
			
		||||
    $self->{encoding} = $EncoderFor{$encoding};
 | 
			
		||||
    return $self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub init { 
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# $obj->init (%opts);
 | 
			
		||||
# -------------------
 | 
			
		||||
#   Sets the options for the current object.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $opt = {};
 | 
			
		||||
    if (@_ == 1 and ref $_[0] eq 'HASH') { $opt = shift }
 | 
			
		||||
    elsif (defined $_[0] and not @_ % 2) { $opt = {@_} }
 | 
			
		||||
    else { return $self->error("BADARGS", "FATAL", "init") }
 | 
			
		||||
    
 | 
			
		||||
    $self->{_debug} = exists($opt->{debug}) ? $opt->{debug} : $DEBUG;
 | 
			
		||||
    for my $m (qw(encoding in out)) {
 | 
			
		||||
        $self->{$m} = $opt->{$m} if defined $opt->{$m};
 | 
			
		||||
    }
 | 
			
		||||
    return $self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub gt_encode {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    if (!ref $self or ref $self ne 'GT::Mail::Encoder') {
 | 
			
		||||
        $self = GT::Mail::Encoder->new(@_) or return;
 | 
			
		||||
    }
 | 
			
		||||
    $self->{encoding} or return $self->error("NOENCODING", "FATAL");;
 | 
			
		||||
    return $self->{encoding}->($self->{in}, $self->{out});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub supported { return exists $EncoderFor{pop()} }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sub Base64 {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    my ($in, $out) = @_;
 | 
			
		||||
    my $encoded;
 | 
			
		||||
 | 
			
		||||
    my $nread;
 | 
			
		||||
    my $buf = '';
 | 
			
		||||
 | 
			
		||||
# Reading multiples of 57 bytes is recommended by MIME::Base64 as it comes out
 | 
			
		||||
# to a line of exactly 76 characters (the max).  We use 2299*57 (131043 bytes)
 | 
			
		||||
# because it comes out to about 128KB (131072 bytes).  Admittedly, this number
 | 
			
		||||
# is fairly arbitrary, but should work well for both large and small files, and
 | 
			
		||||
# shouldn't be too memory intensive.
 | 
			
		||||
    my $read_size = 2299 * 57;
 | 
			
		||||
 | 
			
		||||
    if (not ref $in) {
 | 
			
		||||
        while (1) {
 | 
			
		||||
            last unless length $in;
 | 
			
		||||
            $buf = substr($in, 0, $read_size);
 | 
			
		||||
            substr($in, 0, $read_size) = '';
 | 
			
		||||
 | 
			
		||||
            $encoded = encode_base64($buf, $CRLF);
 | 
			
		||||
 | 
			
		||||
# Encoding to send over SMTP
 | 
			
		||||
            $encoded .= $CRLF unless $encoded =~ /$CRLF\Z/; # ensure newline!
 | 
			
		||||
            $out->($encoded);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    elsif (fileno $in) {
 | 
			
		||||
        while ($nread = read($in, $buf, $read_size)) {
 | 
			
		||||
            $encoded = encode_base64($buf, $CRLF);
 | 
			
		||||
 | 
			
		||||
            $encoded .= $CRLF unless $encoded =~ /$CRLF\Z/; # ensure newline!
 | 
			
		||||
            $out->($encoded);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    elsif (ref $in eq 'GLOB') {
 | 
			
		||||
        die "Glob reference passed in is not an open filehandle";
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        die "Bad arguments passed to Base64, first argument must be a scalar or a filehandle";
 | 
			
		||||
    }
 | 
			
		||||
    1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub Binary {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    my ($in, $out) = @_;
 | 
			
		||||
 | 
			
		||||
    if (not ref $in) {
 | 
			
		||||
        $in =~ s/\015?\012/$CRLF/g;
 | 
			
		||||
        $out->($in);
 | 
			
		||||
    }
 | 
			
		||||
    elsif (fileno $in) {
 | 
			
		||||
        my ($buf, $nread) = ('', 0);
 | 
			
		||||
        while ($nread = read($in, $buf, 4096)) {
 | 
			
		||||
            $buf =~ s/\015?\012/$CRLF/g;
 | 
			
		||||
            $out->($buf);
 | 
			
		||||
        }
 | 
			
		||||
        defined ($nread) or return;      # check for error
 | 
			
		||||
    }
 | 
			
		||||
    elsif (ref $in eq 'GLOB') {
 | 
			
		||||
        die "Glob reference passed in is not an open filehandle";
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        die "Bad arguments passed to Binary, first argument must be a scalar or a filehandle";
 | 
			
		||||
    }
 | 
			
		||||
    1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub UU {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    my ($in, $out, $file) = @_;
 | 
			
		||||
 | 
			
		||||
    my $buf = '';
 | 
			
		||||
    my $fname = ($file || '');
 | 
			
		||||
    $out->("begin 644 $fname\n");
 | 
			
		||||
    if (not ref $in) {
 | 
			
		||||
        while (1) {
 | 
			
		||||
            last unless length $in;
 | 
			
		||||
            $buf = substr($in, 0, 45);
 | 
			
		||||
            substr($in, 0, 45) = '';
 | 
			
		||||
            $out->(pack('u', $buf));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    elsif (fileno $in) {
 | 
			
		||||
        while (read($in, $buf, 45)) {
 | 
			
		||||
            $buf =~ s/\015?\012/$CRLF/g;
 | 
			
		||||
            $out->(pack('u', $buf)) 
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    elsif (ref $in eq 'GLOB') {
 | 
			
		||||
        die "Glob reference passed in is not an open filehandle";
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        die "Bad arguments passed to UU, first argument must be a scalar or a filehandle";
 | 
			
		||||
    }
 | 
			
		||||
    $out->("end\n");
 | 
			
		||||
    1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub NBit {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    my ($enc, $in, $out) = @_;
 | 
			
		||||
 | 
			
		||||
    if (not ref $in) {
 | 
			
		||||
        $in =~ s/\015?\012/$CRLF/g;
 | 
			
		||||
        $out->($in);
 | 
			
		||||
    }
 | 
			
		||||
    elsif (fileno $in) {
 | 
			
		||||
        while (<$in>) {
 | 
			
		||||
            s/\015?\012/$CRLF/g;
 | 
			
		||||
            $out->($_);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    elsif (ref $in eq 'GLOB') {
 | 
			
		||||
        die "Glob reference passed in is not an open filehandle";
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        die "Bad arguments passed to NBit, first argument must be a scalar or a filehandle";
 | 
			
		||||
    }
 | 
			
		||||
    1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub QuotedPrint {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    my ($in, $out) = @_;
 | 
			
		||||
 | 
			
		||||
    local $_;
 | 
			
		||||
    my $ref = ref $in;
 | 
			
		||||
    if ($ref and !fileno($in)) {
 | 
			
		||||
        if ($ref eq 'GLOB') {
 | 
			
		||||
            die "Glob reference passed in is not an open filehandle";
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            die "Bad arguments passed to QuotedPrint, first argument must be a scalar or a filehandle";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    $in =~ s/\015?\012/\n/g unless $ref;
 | 
			
		||||
 | 
			
		||||
    while () {
 | 
			
		||||
        local $_;
 | 
			
		||||
        if ($ref) {
 | 
			
		||||
# Try to get around 32KB at once.  This could end up being much larger than
 | 
			
		||||
# 32KB if there is a very very long line - up to the length of the line + 32700
 | 
			
		||||
# bytes.
 | 
			
		||||
            $_ = <$in>;
 | 
			
		||||
            while (my $line = <$in>) {
 | 
			
		||||
                $_ .= $line;
 | 
			
		||||
                last if length > 32_700; # Not exactly 32KB, but close enough.
 | 
			
		||||
            }
 | 
			
		||||
            last unless defined;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
# Grab up to just shy of 32KB of the string, plus the following line.  As
 | 
			
		||||
# above, this could be much longer than 32KB if there is one or more very long
 | 
			
		||||
# lines involved.
 | 
			
		||||
            $in =~ s/^(.{0,32700}.*?(?:\n|\Z))//ms; # Encode somewhere around 32KB at a time
 | 
			
		||||
            $_ = $1;
 | 
			
		||||
            last unless defined and length;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($use_encode_qp) {
 | 
			
		||||
            $_ = MIME::QuotedPrint::encode_qp($_, $CRLF);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            s/([^ \t\n!-<>-~])/sprintf("=%02X", ord($1))/eg;  # rule #2,#3
 | 
			
		||||
            s/([ \t]+)$/
 | 
			
		||||
              join('', map { sprintf("=%02X", ord($_)) }
 | 
			
		||||
                   split('', $1)
 | 
			
		||||
              )/egm;                        # rule #3 (encode whitespace at eol)
 | 
			
		||||
 | 
			
		||||
            # rule #5 (lines must be shorter than 76 chars, but we are not allowed
 | 
			
		||||
            # to break =XX escapes.  This makes things complicated :-( )
 | 
			
		||||
            my $brokenlines = "";
 | 
			
		||||
            $brokenlines .= "$1=\n"
 | 
			
		||||
                while s/(.*?^[^\n]{73} (?:
 | 
			
		||||
                     [^=\n]{2} (?! [^=\n]{0,1} $) # 75 not followed by .?\n
 | 
			
		||||
                    |[^=\n]    (?! [^=\n]{0,2} $) # 74 not followed by .?.?\n
 | 
			
		||||
                    |          (?! [^=\n]{0,3} $) # 73 not followed by .?.?.?\n
 | 
			
		||||
                ))//xsm;
 | 
			
		||||
 | 
			
		||||
            $_ = "$brokenlines$_";
 | 
			
		||||
 | 
			
		||||
            s/\015?\012/$CRLF/g;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
# Escape 'From ' at the beginning of the line.  This is fairly easy - if the
 | 
			
		||||
# line is currently 73 or fewer characters, we simply change the F to =46,
 | 
			
		||||
# making the line 75 characters long (the max).  If the line is longer than 73,
 | 
			
		||||
# we escape the F, follow it with "=$CRLF", and put the 'rom ' and the rest of
 | 
			
		||||
# the line on the next line - meaning one line of 4 characters, and one of 73
 | 
			
		||||
# or 74.
 | 
			
		||||
        s/^From (.*)/
 | 
			
		||||
            length($1) <= 68 ? "=46rom $1" : "=46=${CRLF}rom $1"
 | 
			
		||||
        /emg; # Escape 'From' at the beginning of a line
 | 
			
		||||
# The '.' at the beginning of the line is more difficult.  The easy case is
 | 
			
		||||
# when the line is 73 or fewer characters - just escape the initial . and we're
 | 
			
		||||
# done.  If the line is longer, the fun starts.  First, we escape the initial .
 | 
			
		||||
# to =2E.  Then we look for the first = in the line; if it is found within the
 | 
			
		||||
# first 3 characters, we split two characters after it (to catch the "12" in
 | 
			
		||||
# "=12") otherwise we split after the third character.  We then add "=$CRLF" to
 | 
			
		||||
# the current line, and look at the next line; if it starts with 'From ' or a
 | 
			
		||||
# ., we escape it - and since the second line will always be less than 73
 | 
			
		||||
# characters long (since we remove at least three for the first line), we can
 | 
			
		||||
# just escape it without worrying about splitting the line up again.
 | 
			
		||||
        s/^\.([^$CRLF]*)/
 | 
			
		||||
            if (length($1) <= 72) {
 | 
			
		||||
                "=2E$1"
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                my $ret = "=2E";
 | 
			
		||||
                my $match = $1;
 | 
			
		||||
                my $index = index($match, '=');
 | 
			
		||||
                my $len = $index >= 2 ? 2 : $index + 3;
 | 
			
		||||
                $ret .= substr($match, 0, $len);
 | 
			
		||||
                substr($match, 0, $len) = '';
 | 
			
		||||
                $ret .= "=$CRLF";
 | 
			
		||||
                substr($match, 0, 1) = "=46" if substr($match, 0, 5) eq 'From ';
 | 
			
		||||
                substr($match, 0, 1) = "=2E" if substr($match, 0, 1) eq '.';
 | 
			
		||||
                $ret .= $match;
 | 
			
		||||
                $ret
 | 
			
		||||
            }
 | 
			
		||||
        /emg;
 | 
			
		||||
 | 
			
		||||
        $out->($_);
 | 
			
		||||
 | 
			
		||||
        last unless $ref or length $in;
 | 
			
		||||
    }
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub gt_old_encode_base64 {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    my $eol = $_[1];
 | 
			
		||||
    $eol = "\n" unless defined $eol;
 | 
			
		||||
 | 
			
		||||
    my $res = pack("u", $_[0]);
 | 
			
		||||
    $res =~ s/^.//mg; # Remove first character of each line
 | 
			
		||||
    $res =~ tr/\n//d; # Remove newlines
 | 
			
		||||
 | 
			
		||||
    $res =~ tr|` -_|AA-Za-z0-9+/|;
 | 
			
		||||
 | 
			
		||||
    # Fix padding at the end
 | 
			
		||||
    my $padding = (3 - length($_[0]) % 3) % 3;
 | 
			
		||||
    $res =~ s/.{$padding}$/'=' x $padding/e if $padding;
 | 
			
		||||
 | 
			
		||||
    # Break encoded string into lines of no more than 76 characters each
 | 
			
		||||
    if (length $eol) {
 | 
			
		||||
        $res =~ s/(.{1,76})/$1$eol/g;
 | 
			
		||||
    }
 | 
			
		||||
    $res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
 | 
			
		||||
__END__
 | 
			
		||||
 | 
			
		||||
=head1 NAME
 | 
			
		||||
 | 
			
		||||
GT::Mail::Encoder - MIME Encoder
 | 
			
		||||
 | 
			
		||||
=head1 SYNOPSIS
 | 
			
		||||
 | 
			
		||||
        open IN, 'decoded.txt' or die $!;
 | 
			
		||||
        open OUT, '>encoded.txt' or die $!;
 | 
			
		||||
        if (GT::Mail::Encoder->supported ('7bit')) {
 | 
			
		||||
            GT::Mail::Encoder->decode (
 | 
			
		||||
                                    debug    => 1,
 | 
			
		||||
                                    encoding => '7bit',
 | 
			
		||||
                                    in       => \*IN,
 | 
			
		||||
                                    out      => sub { print OUT $_[0] }
 | 
			
		||||
                                ) or die $GT::Mail::Encoder::error;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            die "Unsupported encoding";
 | 
			
		||||
        }
 | 
			
		||||
        close IN;
 | 
			
		||||
        close OUT;
 | 
			
		||||
 | 
			
		||||
=head1 DESCRIPTION
 | 
			
		||||
 | 
			
		||||
GT::Mail::Encoder is a MIME Encoder implemented in perl. It will try to use
 | 
			
		||||
the C extension for encoding Base64. If the extension is not there 
 | 
			
		||||
it will do it in perl (slow!).
 | 
			
		||||
 | 
			
		||||
=head2 Encoding a stream
 | 
			
		||||
 | 
			
		||||
The new() constructor and the supported() class method are the only methods that 
 | 
			
		||||
are public in the interface. The new() constructor takes a hash of params.
 | 
			
		||||
The supported() method takes a single string, the name of the encoding you want
 | 
			
		||||
to encode and returns true if the encoding is supported and false otherwise.
 | 
			
		||||
 | 
			
		||||
=over 4
 | 
			
		||||
 | 
			
		||||
=item debug
 | 
			
		||||
 | 
			
		||||
Set debugging level. 1 or 0.
 | 
			
		||||
 | 
			
		||||
=item encoding
 | 
			
		||||
 | 
			
		||||
Sets the encoding used to encode.
 | 
			
		||||
 | 
			
		||||
=item in
 | 
			
		||||
 | 
			
		||||
Set to a file handle or IO handle.
 | 
			
		||||
 | 
			
		||||
=item out
 | 
			
		||||
 | 
			
		||||
Set to a code reference, the decoded stream will be passed in at the first
 | 
			
		||||
argument for each chunk encoded.
 | 
			
		||||
 | 
			
		||||
=back
 | 
			
		||||
 | 
			
		||||
=head1 COPYRIGHT
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
http://www.gossamer-threads.com/
 | 
			
		||||
 | 
			
		||||
=head1 VERSION
 | 
			
		||||
 | 
			
		||||
Revision: $Id: Encoder.pm,v 1.40 2004/01/13 01:35:17 jagerman Exp $
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										672
									
								
								site/glist/lib/GT/Mail/Message.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										672
									
								
								site/glist/lib/GT/Mail/Message.pm
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,672 @@
 | 
			
		||||
# ====================================================================
 | 
			
		||||
# Gossamer Threads Module Library - http://gossamer-threads.com/
 | 
			
		||||
#
 | 
			
		||||
#   GT::Mail::Message
 | 
			
		||||
#   Author: Scott Beck
 | 
			
		||||
#   CVS Info :                          
 | 
			
		||||
#   $Id: Message.pm,v 1.14 2004/01/13 01:35:17 jagerman Exp $
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
# ====================================================================
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
package GT::Mail::Message;
 | 
			
		||||
 | 
			
		||||
use strict;
 | 
			
		||||
use vars qw/$ATTRIBS $CRLF/;
 | 
			
		||||
use bases 'GT::Base' => '';
 | 
			
		||||
 | 
			
		||||
$ATTRIBS = {
 | 
			
		||||
    root_part => undef,
 | 
			
		||||
    debug     => 0
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
$CRLF = "\012";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sub init {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Init called from GT::Base
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
 | 
			
		||||
    $self->set( @_ );
 | 
			
		||||
 | 
			
		||||
    if ( !defined( $self->{root_part} ) ) {
 | 
			
		||||
        $self->{root_part} = new GT::Mail::Parts;
 | 
			
		||||
    }
 | 
			
		||||
    $self->{parts} = _get_parts( $self->{root_part} );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sub delete_part {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Deletes the given part from the email
 | 
			
		||||
#
 | 
			
		||||
    my ( $self, $part ) = @_;
 | 
			
		||||
 | 
			
		||||
    die "Can't delete top level part" if $part == $self->{root_part};
 | 
			
		||||
    $self->_link;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# We must remove it from the flat list of parts
 | 
			
		||||
    $self->_delete_part( $part );
 | 
			
		||||
 | 
			
		||||
# Now we must relink our list
 | 
			
		||||
    $self->_link;
 | 
			
		||||
 | 
			
		||||
    return $part;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub move_part_before {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Move a part before another part. The first argument is the part to move 
 | 
			
		||||
# before, the second is the part to move. No moving the top level part.
 | 
			
		||||
#
 | 
			
		||||
    my ( $self, $before_part, $part ) = @_;
 | 
			
		||||
    die "Can't move part before the top part" if $before_part == $self->{root_part};
 | 
			
		||||
    die "Can't move top part" if $part == $self->{root_part};
 | 
			
		||||
    if ( !$self->_part_in_message( $before_part ) or !$self->_part_in_message( $part ) ) {
 | 
			
		||||
        die "All parts specified must be in the MIME message";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# First remove the part
 | 
			
		||||
    $self->_delete_part( $part );
 | 
			
		||||
 | 
			
		||||
# Now we add
 | 
			
		||||
    $self->add_part_before( $before_part, $part );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub move_part_after {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Move a part after another part. The first argument is the part to move 
 | 
			
		||||
# after, the second is the part to move. No moving the top level part.
 | 
			
		||||
#
 | 
			
		||||
    my ( $self, $after_part, $part ) = @_;
 | 
			
		||||
    die "Can't move part after the top part" if $after_part == $self->{root_part};
 | 
			
		||||
    die "Can't move top part" if $part == $self->{root_part};
 | 
			
		||||
    if ( !$self->_part_in_message( $after_part ) or !$self->_part_in_message( $part ) ) {
 | 
			
		||||
        die "All parts specified must be in the MIME message";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# First remove the part
 | 
			
		||||
    $self->_delete_part( $part );
 | 
			
		||||
 | 
			
		||||
# Now we add
 | 
			
		||||
    $self->add_part_after( $after_part, $part );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub move_part_end {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Move a part to the end of a multipart part. The first part is the
 | 
			
		||||
# multipart part to move it to the end of. The second argument is the part 
 | 
			
		||||
# to move. No moving the top level part.
 | 
			
		||||
#
 | 
			
		||||
    my ( $self, $parent_part, $part ) = @_;
 | 
			
		||||
    die "Can't move top part" if $part == $self->{root_part};
 | 
			
		||||
    if ( !$self->_part_in_message( $parent_part ) or !$self->_part_in_message( $part ) ) {
 | 
			
		||||
        die "All parts specified must be in the MIME message";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# First remove the part to be moved
 | 
			
		||||
    $self->_delete_part( $part );
 | 
			
		||||
 | 
			
		||||
# Then we add it back in
 | 
			
		||||
    $self->add_part_end( $parent_part, $part );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub move_part_beginning {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Move a part to the beginning of a multipart part. The first part is the
 | 
			
		||||
# multipart part to move it to the beginning of. The second argument is the
 | 
			
		||||
# part to move. No moving the top level part.
 | 
			
		||||
#
 | 
			
		||||
    my ( $self, $parent_part, $part ) = @_;
 | 
			
		||||
    die "Can't move top part" if $part == $self->{root_part};
 | 
			
		||||
    if ( !$self->_part_in_message( $parent_part ) or !$self->_part_in_message( $part ) ) {
 | 
			
		||||
        die "All parts specified must be in the MIME message";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# First remove the part to be moved
 | 
			
		||||
    $self->_delete_part( $part );
 | 
			
		||||
 | 
			
		||||
# Then we add it back in
 | 
			
		||||
    $self->add_part_beginning( $parent_part, $part );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub replace_part {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Replace a part with another part
 | 
			
		||||
#
 | 
			
		||||
    my ( $self, $old_part, $new_part ) = @_;
 | 
			
		||||
    $self->_link;
 | 
			
		||||
    splice( @{$self->{parts}}, $old_part->{id}, 1, $new_part );
 | 
			
		||||
    $self->_link;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub add_part_before {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Adds a part before the given part. The first argument is the part object
 | 
			
		||||
# to add the part before. the second argument is the part to add.
 | 
			
		||||
#
 | 
			
		||||
    my ( $self, $before_part, $part ) = @_;
 | 
			
		||||
    $self->_link;
 | 
			
		||||
    die "Can't add part before the top level part" if $before_part == $self->{root_part};
 | 
			
		||||
    my $parent_id = $before_part->{parent_id};
 | 
			
		||||
 | 
			
		||||
    if ( !defined $parent_id or !$self->{parts}[$parent_id]->is_multipart ) {
 | 
			
		||||
        die "The part's parent must exist and must be a multipart";
 | 
			
		||||
    }
 | 
			
		||||
    splice( @{$self->{parts}}, $before_part->{id}, 0, $part );
 | 
			
		||||
    my $parent_part = $self->{parts}[$parent_id];
 | 
			
		||||
    $parent_part->add_parts_before( $before_part->{id}, $part );
 | 
			
		||||
    $self->_link;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub add_part_after {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Adds a part after the given part. The first argument is the part object
 | 
			
		||||
# to add the part after. the second argument is the part to add.
 | 
			
		||||
#
 | 
			
		||||
    my ( $self, $after_part, $part ) = @_;
 | 
			
		||||
    $self->_link;
 | 
			
		||||
    die "Can't add part after the top level part" if $after_part == $self->{root_part};
 | 
			
		||||
    my $parent_id = $after_part->{parent_id};
 | 
			
		||||
    
 | 
			
		||||
    if ( !defined $parent_id or !$self->{parts}[$parent_id]->is_multipart ) {
 | 
			
		||||
        die "The part's parent must exist and must be a multipart";
 | 
			
		||||
    }
 | 
			
		||||
    splice( @{$self->{parts}}, $after_part->{id} + 1, 0, $part );
 | 
			
		||||
    my $parent_part = $self->{parts}[$parent_id];
 | 
			
		||||
    $parent_part->add_parts_after( $after_part->{id}, $part );
 | 
			
		||||
    $self->_link;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub add_part_beginning {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Adds a part at the beginning of the given multipart part. The first
 | 
			
		||||
# argument is the part object to add the part before. the second argument is
 | 
			
		||||
# the part to add.
 | 
			
		||||
#
 | 
			
		||||
    my ( $self, $parent_part, $part ) = @_;
 | 
			
		||||
    $self->_link;
 | 
			
		||||
    my $parent_id = $parent_part->{id};
 | 
			
		||||
    
 | 
			
		||||
    if ( !$self->{parts}[$parent_id]->is_multipart ) {
 | 
			
		||||
        die "The parent part must be a multipart";
 | 
			
		||||
    }
 | 
			
		||||
    splice( @{$self->{parts}}, $parent_id + 1, 0, $part );
 | 
			
		||||
    $parent_part->add_part_before( $part->{parts}[0]{id}, $part );
 | 
			
		||||
    $self->_link;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub add_part_end {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Adds a part at the end of the given multipart part. The first argument is
 | 
			
		||||
# the part object to add the part at the end of. the second argument is the
 | 
			
		||||
# part to add. The first argument must be a multipart part or a fatal error
 | 
			
		||||
# occurs.
 | 
			
		||||
#
 | 
			
		||||
    my ( $self, $parent_part, $part ) = @_;
 | 
			
		||||
    $self->_link;
 | 
			
		||||
    my $parent_id = $parent_part->{id};
 | 
			
		||||
    
 | 
			
		||||
    if ( !$self->{parts}[$parent_id]->is_multipart ) {
 | 
			
		||||
        die "The parent part must be a multipart";
 | 
			
		||||
    }
 | 
			
		||||
    splice( @{$self->{parts}}, $parent_id + @parts, 0, $part );
 | 
			
		||||
    $parent_part->parts( $part );
 | 
			
		||||
    $self->_link;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub move_part_to_position {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Move a part to a position within another multipart part. The first
 | 
			
		||||
# argument is the part to move within, the second argument is the part to
 | 
			
		||||
# move and the final argument is the position within those parts to move it
 | 
			
		||||
# in.
 | 
			
		||||
#
 | 
			
		||||
    my ( $self, $parent_part, $part, $pos ) = @_;
 | 
			
		||||
    die "Can't move top part" if $part == $self->{root_part};
 | 
			
		||||
    if ( !$self->_part_in_message( $parent_part ) or !$self->_part_in_message( $part ) ) {
 | 
			
		||||
        die "All parts specified must be in the MIME message";
 | 
			
		||||
    }
 | 
			
		||||
    $self->_link;
 | 
			
		||||
    my $parent_id = $parent_part->{id};
 | 
			
		||||
    
 | 
			
		||||
    if ( !$self->{parts}[$parent_id]->is_multipart ) {
 | 
			
		||||
        die "The parent part must be a multipart";
 | 
			
		||||
    }
 | 
			
		||||
    splice( @{$self->{parts}}, $parent_id + $pos, $part );
 | 
			
		||||
    $self->_link;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub get_part_by_id {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Method to retrieve a part object by it's id
 | 
			
		||||
#
 | 
			
		||||
    my ( $self, $id ) = @_;
 | 
			
		||||
 | 
			
		||||
    return $self->{parts}[$id];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub new_part {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Method to easily create a part object. All the header fields can be passed
 | 
			
		||||
# in as a hash. If the key "body_data" the value will be set as the parts
 | 
			
		||||
# body rather than a header field.
 | 
			
		||||
#
 | 
			
		||||
    my ( $self, @opts ) = @_;
 | 
			
		||||
    my $part = new GT::Mail::Parts;
 | 
			
		||||
    while ( my ( $key, $val ) = ( shift( @opts ), shift( @opts ) ) ) {
 | 
			
		||||
        if ( $key eq 'body_data' ) {
 | 
			
		||||
            $part->body_data( $val );
 | 
			
		||||
        }
 | 
			
		||||
        elsif ( $key eq 'body_handle' ) {
 | 
			
		||||
            $part->body_handle( $val );
 | 
			
		||||
        }
 | 
			
		||||
        elsif ( $key eq 'body_path' ) {
 | 
			
		||||
            $part->body_path( $val );
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            $part->set( $key => $val );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return $part;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub all_parts {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# my @parts = $obj->all_parts;
 | 
			
		||||
# ----------------------------
 | 
			
		||||
#   Returns a list of all the part object for the current parsed email.
 | 
			
		||||
#   If the email is not multipart this will be just the header part.
 | 
			
		||||
#
 | 
			
		||||
    return @{shift()->{parts}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub size {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Returns the total size of an email. Call this method after the email has 
 | 
			
		||||
# been parsed.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    (@{$self->{parts}} > 0) or return;
 | 
			
		||||
    my $size = 0;
 | 
			
		||||
    foreach (@{$self->{parts}}) {
 | 
			
		||||
        $size += $_->size;
 | 
			
		||||
    }
 | 
			
		||||
    return $size;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub as_string {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Returns the entire email as a sting.
 | 
			
		||||
#
 | 
			
		||||
    my ( $self ) = @_;
 | 
			
		||||
    $GT::Mail::Encoder::CRLF = $CRLF;
 | 
			
		||||
 | 
			
		||||
    my $out;
 | 
			
		||||
    $$out = ' ' x 50*1024;
 | 
			
		||||
    $self->debug ("\n\t--------------> Creating email") if $self->{_debug};
 | 
			
		||||
 | 
			
		||||
# Need the head to contiue
 | 
			
		||||
    $self->{root_part} or die "No root part!";
 | 
			
		||||
    $self->{root_part}->set( 'MIME-Version' => '1.0' ) unless $self->{root_part}->get( 'MIME-Version' );
 | 
			
		||||
 | 
			
		||||
    my $bound = $self->{root_part}->multipart_boundary;
 | 
			
		||||
 | 
			
		||||
# If the message has parts
 | 
			
		||||
 | 
			
		||||
    if ( @{$self->{root_part}->{parts}} > 0 ) {
 | 
			
		||||
        $self->debug( "Creating multipart email." ) if $self->{_debug};
 | 
			
		||||
        $self->_build_multipart_head( $out );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Else we are single part and have either a body IO handle or the body is in memory
 | 
			
		||||
    else {
 | 
			
		||||
        $self->debug( "Creating singlepart email." ) if $self->{_debug};
 | 
			
		||||
        $self->_build_singlepart_head( $out );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# If we have parts go through all of them and add them.
 | 
			
		||||
    if ( @{$self->{root_part}->{parts}} > 0 ) {
 | 
			
		||||
        my $num_parts = $#{$self->{root_part}->{parts}};
 | 
			
		||||
        for my $num ( 0 .. $num_parts ) {
 | 
			
		||||
            next unless $self->{root_part}->{parts}->[$num];
 | 
			
		||||
            $self->debug( "Creating part ($num)." ) if $self->{_debug};
 | 
			
		||||
            $self->_build_parts( $out, $self->{root_part}->{parts}->[$num] );
 | 
			
		||||
            if ( $num_parts == $num ) {
 | 
			
		||||
                $self->debug( "Boundary\n\t--$bound--" ) if $self->{_debug};
 | 
			
		||||
                $$out .= $CRLF . '--' . $bound . '--' . $CRLF;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                $self->debug( "Boundary\n\t--$bound" ) if $self->{_debug};
 | 
			
		||||
                $$out .= $CRLF . '--' . $bound . $CRLF;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Add the epilogue if we are multipart
 | 
			
		||||
    if ( @{$self->{root_part}->{parts}} > 0 ) {
 | 
			
		||||
        my $epilogue = join( '', @{$self->{root_part}->epilogue || []} ) || '';
 | 
			
		||||
        $epilogue =~ s/\015?\012//g;
 | 
			
		||||
        $self->debug( "Setting epilogue to ($epilogue)" ) if $self->{_debug};
 | 
			
		||||
        $$out .= $epilogue . $CRLF . $CRLF if $epilogue;
 | 
			
		||||
    }
 | 
			
		||||
    $self->debug( "\n\t<-------------- Email created." ) if $self->{_debug};
 | 
			
		||||
    return $$out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _build_multipart_head {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Private method to build a multipart header.
 | 
			
		||||
#
 | 
			
		||||
    my ( $self, $out ) = @_;
 | 
			
		||||
    my $bound = $self->{root_part}->multipart_boundary;
 | 
			
		||||
    my $encoding = $self->{root_part}->suggest_encoding;
 | 
			
		||||
    $self->debug( "Setting encoding to ($encoding)." ) if ( $self->{debug} );
 | 
			
		||||
    $self->{root_part}->set( 'Content-Transfer-Encoding' => $encoding );
 | 
			
		||||
    $bound or $bound = "---------=_" . scalar (time) . "-$$-" . int(rand(time)/2);
 | 
			
		||||
 | 
			
		||||
# Set the content boundary unless it has already been set
 | 
			
		||||
    my $c = $self->{root_part}->get( 'Content-Type' );
 | 
			
		||||
    if ( $c !~ /\Q$bound/i ) {
 | 
			
		||||
        if ( $c and lc( $c ) !~ /boundary=/ ) {
 | 
			
		||||
            $c =~ /multipart/ or $c = 'multipart/mixed';
 | 
			
		||||
            $self->debug( qq|Setting content type to ($c; boundary="$bound")| ) if $self->{debug};
 | 
			
		||||
            $self->{root_part}->set( 'Content-Type' => $c . qq|; boundary="$bound"| );
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            $self->debug( "Setting multipart boundary to ($bound)." ) if $self->{_debug};
 | 
			
		||||
            $self->{root_part}->set( 'Content-Type' =>  qq!multipart/mixed; boundary="$bound"! ) 
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    my $preamble = join( '', @{$self->{root_part}->preamble || []} ) || "This is a multi-part message in MIME format.";
 | 
			
		||||
    $preamble =~ s/\015?\012//g;
 | 
			
		||||
    $self->debug( "Setting preamble to ($preamble)." ) if ( $self->{_debug} );
 | 
			
		||||
    ( my $head = $self->{root_part}->header_as_string ) =~ s/\015?\012/$CRLF/g;
 | 
			
		||||
    $self->debug( "Boundary\n\t--$bound" ) if $self->{_debug};
 | 
			
		||||
    $$out .= $head . $CRLF . $preamble . $CRLF  . $CRLF . '--' . $bound . $CRLF;
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _build_singlepart_head {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Private method to build a single part header.
 | 
			
		||||
#
 | 
			
		||||
    my ( $self, $out ) = @_;
 | 
			
		||||
    my $encoding = $self->{root_part}->suggest_encoding;
 | 
			
		||||
    $self->debug( "Setting encoding to ($encoding)." ) if $self->{_debug};
 | 
			
		||||
    $self->{root_part}->set( 'Content-Transfer-Encoding' => $encoding );
 | 
			
		||||
    ( my $head = $self->{root_part}->header_as_string ) =~ s/\015?\012/$CRLF/g;
 | 
			
		||||
    $$out .= $head . $CRLF;
 | 
			
		||||
    $self->debug( "Encoding body with ($encoding)." ) if $self->{_debug};
 | 
			
		||||
    GT::Mail::Encoder->gt_encode (
 | 
			
		||||
        debug    => $self->{_debug},
 | 
			
		||||
        encoding => $encoding,
 | 
			
		||||
        in       => $self->{root_part}->body_as_string,
 | 
			
		||||
        out      => $out
 | 
			
		||||
    ) or return;
 | 
			
		||||
 | 
			
		||||
# Must seek to the beginning for additional calles
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _build_parts {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Private method that builds the parts for the email.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $out, $part) = @_;
 | 
			
		||||
 | 
			
		||||
# Need the head to contiue
 | 
			
		||||
    $self->{root_part} or die "No root part!";
 | 
			
		||||
 | 
			
		||||
    my ( $body, $encoding, $bound );
 | 
			
		||||
    $bound = $part->multipart_boundary;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Find the encoding for the part and set it.
 | 
			
		||||
    $encoding = $part->suggest_encoding;
 | 
			
		||||
    $self->debug( "Setting encoding to ($encoding)." ) if $self->{_debug};
 | 
			
		||||
    $part->set( 'Content-Transfer-Encoding' => $encoding );
 | 
			
		||||
 | 
			
		||||
# If the message has parts and has a multipart boundary
 | 
			
		||||
    if ( @{$part->{parts}} > 0 and $bound ) {
 | 
			
		||||
        $self->debug( "Part is multpart." ) if $self->{_debug};
 | 
			
		||||
 | 
			
		||||
# Set the multipart boundary
 | 
			
		||||
        $self->debug( "Setting boundary to ($bound)." ) if $self->{_debug};
 | 
			
		||||
 | 
			
		||||
# Set the content boundary unless it has already been set
 | 
			
		||||
        my $c = $part->get( 'Content-Type' );
 | 
			
		||||
        if ( $c ) {
 | 
			
		||||
            $self->debug( qq|Setting content type to ($c; boundary="$bound")| ) if $self->{_debug};
 | 
			
		||||
            $part->set( 'Content-Type' => $c . qq|; boundary="$bound"| );
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            $self->debug( "Setting multipart boundary to ($bound)." ) if $self->{_debug};
 | 
			
		||||
            $part->set( 'Content-Type' =>  qq!multipart/mixed; boundary="$bound"! );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        my $preamble = join( '' => @{ $part->preamble || [] } ) || "This is a multi-part message in MIME format.";
 | 
			
		||||
        $preamble =~ s/\015?\012//g;
 | 
			
		||||
        $self->debug( "Setting preamble to ($preamble)." ) if $self->{_debug};
 | 
			
		||||
        ( my $head = $part->header_as_string ) =~ s/\015?\012/$CRLF/g;
 | 
			
		||||
        $self->debug( "Boundary\n\t--$bound" ) if $self->{_debug};
 | 
			
		||||
        $$out .= $head . $CRLF . $preamble  . $CRLF . '--' . $bound . $CRLF;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $self->debug( "Part is single part." ) if $self->{_debug};
 | 
			
		||||
        ( my $head = $part->header_as_string ) =~ s/\015?\012/$CRLF/g;
 | 
			
		||||
        $$out .= $head . $CRLF;
 | 
			
		||||
 | 
			
		||||
# Set the body only if we have one. We would not have one on the head an multipart
 | 
			
		||||
        $self->debug( "Encoding body with ($encoding)." ) if $self->{_debug};
 | 
			
		||||
        GT::Mail::Encoder->gt_encode(
 | 
			
		||||
            encoding => $encoding,
 | 
			
		||||
            debug    => $self->{_debug},
 | 
			
		||||
            in       => $part->body_as_string,
 | 
			
		||||
            out      => $out
 | 
			
		||||
        ) or return;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Add the rest of the parts
 | 
			
		||||
    if ( @{$part->{parts}} > 0 ) {
 | 
			
		||||
        $self->debug( "Part has parts." ) if $self->{_debug};
 | 
			
		||||
        my $num_parts = $#{$part->{parts}};
 | 
			
		||||
        for my $num ( 0 .. $num_parts ) {
 | 
			
		||||
            next unless $part->{parts}->[$num];
 | 
			
		||||
            $self->debug( "Creating part ($num)." ) if $self->{_debug};
 | 
			
		||||
            $self->_build_parts( $out, $part->{parts}->[$num] ) or return;
 | 
			
		||||
            if ( $bound ) {
 | 
			
		||||
                if ( $num_parts == $num ) {
 | 
			
		||||
                    $self->debug( "Boundary\n\t--$bound--" ) if $self->{_debug};
 | 
			
		||||
                    $$out .= $CRLF . '--' . $bound . '--' . $CRLF;
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    $self->debug( "Boundary\n\t--$bound" ) if $self->{_debug};
 | 
			
		||||
                    $$out .= $CRLF . '--' . $bound . $CRLF;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Maybe done!
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _delete_part {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Internal method to delete a part
 | 
			
		||||
    my ( $self, $part ) = @_;
 | 
			
		||||
 | 
			
		||||
# We must remove it from it's parent
 | 
			
		||||
    my $parent = $self->{parts}[$part->{parent_id}];
 | 
			
		||||
    for ( 0 .. $#{$parent->{parts}} ) {
 | 
			
		||||
        if ( $parent->{parts}[$_]{id} == $part->{id} ) {
 | 
			
		||||
            splice( @{$parent->{parts}}, $_, 1 );
 | 
			
		||||
            last;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# We must remove it from the flat list of parts
 | 
			
		||||
    return splice( @{$self->{parts}}, $part->{id}, 1 );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _part_in_message {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Internal method to find out weather a part is in the current message
 | 
			
		||||
    my ( $self, $part ) = @_;
 | 
			
		||||
    for ( @{$self->{parts}} ) {
 | 
			
		||||
        return 1 if $_ == $part;
 | 
			
		||||
    }
 | 
			
		||||
    return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _link {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Creats part ids and links the children to the parrents. Called
 | 
			
		||||
# When parts arer modified
 | 
			
		||||
#
 | 
			
		||||
    my ( $self ) = @_;
 | 
			
		||||
 | 
			
		||||
# Creates ids to keep track of parts with.
 | 
			
		||||
    for ( 0 .. $#{$self->{parts}} ) {
 | 
			
		||||
        $self->{parts}[$_]{id} = $_;
 | 
			
		||||
    }
 | 
			
		||||
    _link_ids( $self->{root_part} );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _links_ids {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Internal function to link all children to their parents with the parent id.
 | 
			
		||||
# RECURSIVE
 | 
			
		||||
#
 | 
			
		||||
    my ( $part, $parent_id ) = @_;
 | 
			
		||||
    for ( @{$part->{parts}} ) {
 | 
			
		||||
        _link_ids( $_, $part->{id} );
 | 
			
		||||
    }
 | 
			
		||||
    $part->{parent_id} = $parent_id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _get_parts {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Recursive function to get a flat list of all the parts in a part structure
 | 
			
		||||
#
 | 
			
		||||
    my ( $part, $parts ) = @_;
 | 
			
		||||
    $parts ||= [];
 | 
			
		||||
 | 
			
		||||
    for ( @{$part->{parts}} ) {
 | 
			
		||||
        push @$parts, @{_get_parts( $_, $parts )};
 | 
			
		||||
    }
 | 
			
		||||
    return $parts;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
 | 
			
		||||
__END__
 | 
			
		||||
 | 
			
		||||
=head1 NAME
 | 
			
		||||
 | 
			
		||||
GT::Mail::Message - Encapsolates an email message.
 | 
			
		||||
 | 
			
		||||
=head1 SYNOPSIS
 | 
			
		||||
 | 
			
		||||
    use GT::Mail::Message;
 | 
			
		||||
 
 | 
			
		||||
    # Get a GT::Mail::Message object from the parser
 | 
			
		||||
    use GT::Mail::Parse;
 | 
			
		||||
 | 
			
		||||
    my $parser = new GT::Mail::Parse( in_file => "myemail.eml" );
 | 
			
		||||
    my $message = $parser->parse;
 | 
			
		||||
 | 
			
		||||
    # Get the top level part
 | 
			
		||||
    my $root_part = $message->root_part;
 | 
			
		||||
 | 
			
		||||
    # Replace the first part with a new part
 | 
			
		||||
    $message->replace_part( $root_part, $message->new_part(
 | 
			
		||||
        to => 'scott@gossamer-threads.com',
 | 
			
		||||
        from => 'alex@gossamer-threads.com',
 | 
			
		||||
        'content-type' => 'text/plain',
 | 
			
		||||
        body_data => 'Hi Scott, how are you?!'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    # Add a part at the end
 | 
			
		||||
    my $end_part = $message->new_part(
 | 
			
		||||
        'content-type' => 'image/gif',
 | 
			
		||||
        body_path      => 'myimage.jpg'
 | 
			
		||||
    );
 | 
			
		||||
    $message->add_part_end( $root_part, $end_part );
 | 
			
		||||
 | 
			
		||||
    # Move the first part in the top part to after the end part
 | 
			
		||||
    $message->move_part_after( $root_part->parts->[0], $end_part );
 | 
			
		||||
 | 
			
		||||
    # Print the mime message
 | 
			
		||||
    print $message->to_string;
 | 
			
		||||
 | 
			
		||||
=head1 DESCRIPTION
 | 
			
		||||
 | 
			
		||||
GT::Mail::Message encapsolates a mime message which consists of 
 | 
			
		||||
L<GT::Mail::Parts> object. This module provides methods to change,
 | 
			
		||||
move, remove, and access these parts.
 | 
			
		||||
 | 
			
		||||
=head2 Creating a new GT::Mail::Message object
 | 
			
		||||
 | 
			
		||||
Usually you will get a GT::Mail::Message object by call the parse method
 | 
			
		||||
in L<GT::Mail::Parse>.
 | 
			
		||||
 | 
			
		||||
    my $message = $parser->parse;
 | 
			
		||||
 | 
			
		||||
You may also call new on this class specifying the top level part and or
 | 
			
		||||
a debug level.
 | 
			
		||||
 | 
			
		||||
    my $message = new GT::Mail::Message(
 | 
			
		||||
        root_part => $part,
 | 
			
		||||
        debug    => 1
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
=head2 Creating a new Part
 | 
			
		||||
 | 
			
		||||
You can create a part by calling new on L<GT::Mail::Parts> directly
 | 
			
		||||
 | 
			
		||||
    my $part = new GT::Mail::Parts;
 | 
			
		||||
    $part->set( 'content-type' => 'image/gif' );
 | 
			
		||||
    $part->body_path( 'myimage.gif' );
 | 
			
		||||
 | 
			
		||||
or you can call a method in this module to get a new part
 | 
			
		||||
 | 
			
		||||
    my $part = $message->new_part(
 | 
			
		||||
        'content-type' => 'image/gif',
 | 
			
		||||
        body_path      => 'myimage.gif'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
This method is a wraper on a combination of new() and some other
 | 
			
		||||
supporting methods in L<GT::Mail::Parts> such as body_path(). Anything
 | 
			
		||||
that is not B<body_path>, B<body_data>, or B<body_handle> is treated
 | 
			
		||||
as header values.
 | 
			
		||||
 | 
			
		||||
=head2 Manipulating Parts
 | 
			
		||||
 | 
			
		||||
A MIME message is just a format for storing a tree structure. We provide
 | 
			
		||||
tree-like methods to manipulate parts. All the method for manipulating
 | 
			
		||||
parts take the part object(s) as arguments. We do this so you do not need
 | 
			
		||||
to know how the tree is tracked internally.
 | 
			
		||||
 | 
			
		||||
=head2 Accessing Parts
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
More to come!
 | 
			
		||||
 | 
			
		||||
=head1 COPYRIGHT
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
http://www.gossamer-threads.com/
 | 
			
		||||
 | 
			
		||||
=head1 VERSION
 | 
			
		||||
 | 
			
		||||
Revision: $Id: Message.pm,v 1.14 2004/01/13 01:35:17 jagerman Exp $
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										829
									
								
								site/glist/lib/GT/Mail/POP3.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										829
									
								
								site/glist/lib/GT/Mail/POP3.pm
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,829 @@
 | 
			
		||||
# ==================================================================
 | 
			
		||||
# Gossamer Threads Module Library - http://gossamer-threads.com/
 | 
			
		||||
#
 | 
			
		||||
#   GT::Mail::POP3
 | 
			
		||||
#   Author: Scott Beck
 | 
			
		||||
#   CVS Info :                          
 | 
			
		||||
#   $Id: POP3.pm,v 1.56 2004/03/19 00:36:16 brewt Exp $
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
# ==================================================================
 | 
			
		||||
#
 | 
			
		||||
# Description: A general purpose perl interface to a POP3 server.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
package GT::Mail::POP3;
 | 
			
		||||
# ==================================================================
 | 
			
		||||
 | 
			
		||||
# Pragmas
 | 
			
		||||
use strict;
 | 
			
		||||
use vars qw!$VERSION $DEBUG $ERROR $CRLF @ISA $ERRORS $ATTRIBS!;
 | 
			
		||||
 | 
			
		||||
# Constants
 | 
			
		||||
use constants TIMEOUT => 0.01; # The timeout used on selects.
 | 
			
		||||
 | 
			
		||||
# Internal modules
 | 
			
		||||
use GT::Base;
 | 
			
		||||
use GT::Socket::Client;
 | 
			
		||||
use GT::Mail::Parts;
 | 
			
		||||
use GT::Mail::Parse;
 | 
			
		||||
 | 
			
		||||
# System modules
 | 
			
		||||
use Fcntl qw/O_NONBLOCK F_SETFL F_GETFL/;
 | 
			
		||||
use POSIX qw/EAGAIN EINTR/;
 | 
			
		||||
 | 
			
		||||
# Silence warnings
 | 
			
		||||
$GT::Mail::Parse::error = '';
 | 
			
		||||
 | 
			
		||||
@ISA   = qw(GT::Base);
 | 
			
		||||
$DEBUG = 0;
 | 
			
		||||
$CRLF  = "\r\n";
 | 
			
		||||
$|     = 1;
 | 
			
		||||
 | 
			
		||||
$ATTRIBS = {
 | 
			
		||||
    host      => undef,
 | 
			
		||||
    port      => undef,
 | 
			
		||||
    user      => undef,
 | 
			
		||||
    pass      => undef,
 | 
			
		||||
    auth_mode => 'PASS',
 | 
			
		||||
    debug     => 0,
 | 
			
		||||
    blocking  => 0,
 | 
			
		||||
    ssl       => 0,
 | 
			
		||||
    timeout   => 30, # The connection timeout (passed to GT::Socket::Client)
 | 
			
		||||
    data_timeout => 5, # The timeout to read/write data from/to the connected socket
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
$ERRORS = {
 | 
			
		||||
    NOTCONNECTED => "You are calling %s and you have not connected yet!",
 | 
			
		||||
    CANTCONNECT  => "Could not connect to POP3 server: %s",
 | 
			
		||||
    READ         => "Unble to read from socket, reason (%s). Read: (%s)",
 | 
			
		||||
    WRITE        => "Unable to write %s length to socket. Wrote %s, Error(%s)",
 | 
			
		||||
    NOEOF        => "No EOF or EOL found. Socket locked.",
 | 
			
		||||
    ACTION       => "Could not %s. Server said: %s",
 | 
			
		||||
    NOMD5        => "Unable to load GT::MD5 (required for APOP authentication): %s",
 | 
			
		||||
    PARSE        => "An error occured while parsing an email: %s",
 | 
			
		||||
    LOGIN        => "An error occured while logging in: %s",
 | 
			
		||||
    OPEN         => "Could not open (%s) for read and write. Reason: %s",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
sub head_part {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# my $head = $obj->head_part($num);
 | 
			
		||||
# ---------------------------------
 | 
			
		||||
#   This method takes one argument, the number message to
 | 
			
		||||
#   parse. It returns a GT::Mail::Parts object that has
 | 
			
		||||
#   only the top level head part parsed.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $num) = @_;
 | 
			
		||||
    $num and $num =~ /^\d+$/ or return $self->error("BADARGS", "FATAL", '$obj->head_part ($msg_num)');
 | 
			
		||||
    my $io = '';
 | 
			
		||||
    $self->top($num, sub { $io .= $_[0] }) or return;
 | 
			
		||||
    return GT::Mail::Parse->new(debug  => $self->{_debug}, crlf => $CRLF)->parse_head(\$io);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub all_head_parts {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# my @heads = $obj->all_head_parts;
 | 
			
		||||
# ---------------------------------
 | 
			
		||||
#   This does much the same as head_part() but returns an
 | 
			
		||||
#   array of GT::Mail::Parts objects, each one only having
 | 
			
		||||
#   the head of the message parsed.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my @head_parts;
 | 
			
		||||
    for (1 .. $self->stat) {
 | 
			
		||||
        my $part = $self->head_part($_) or return;
 | 
			
		||||
        push(@head_parts, $part);
 | 
			
		||||
    }
 | 
			
		||||
    return wantarray ? @head_parts : \@head_parts;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub parse_message {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# my $mail = $obj->parse_message($num);
 | 
			
		||||
# -------------------------------------
 | 
			
		||||
#   This method returns a GT::Mail object. It calles parse
 | 
			
		||||
#   for the message number specified before returning the
 | 
			
		||||
#   object. You can retrieve the different parts of the
 | 
			
		||||
#   message through the GT::Mail object. If this method
 | 
			
		||||
#   fails you should check $GT::Mail::error.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $num) = @_;
 | 
			
		||||
    $num and $num =~ /^\d+$/ or return $self->error("BADARGS", "FATAL", '$obj->parse_message($msg_num)');
 | 
			
		||||
    my $io = $self->retr($num) or return;
 | 
			
		||||
    my $parser = new GT::Mail::Parse(debug => $self->{_debug}, in_string => $io, crlf => $CRLF);
 | 
			
		||||
    $parser->parse or return $self->error("PARSE", "WARN", $GT::Mail::Parse::error);
 | 
			
		||||
    return $parser;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub init {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Initilize the POP box object.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
 | 
			
		||||
    $self->set(@_);
 | 
			
		||||
 | 
			
		||||
    for (qw/user pass host/) {
 | 
			
		||||
        (defined($self->{$_})) or return $self->error('BADARGS', 'FATAL', "CLASS->new(%ARGS); The '$_' key in the hash must exists");
 | 
			
		||||
    }
 | 
			
		||||
    $self->{_debug} = exists($self->{debug}) ? delete($self->{debug}) : $DEBUG;
 | 
			
		||||
 | 
			
		||||
# Can be either PASS or APOP depending on login type.
 | 
			
		||||
    $self->{auth_mode} ||= 'PASS';
 | 
			
		||||
 | 
			
		||||
    return $self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub send {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Send a message to the server.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $msg) = @_;
 | 
			
		||||
 | 
			
		||||
    unless (defined $msg and length $msg) {
 | 
			
		||||
        $self->debug("Sending blank message!") if $self->{_debug};
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Get the socket and end of line.
 | 
			
		||||
    my $s = $self->{sock};
 | 
			
		||||
    defined($s) and defined fileno($s) or return $self->error("NOTCONNECTED", "WARN", "send()");
 | 
			
		||||
 | 
			
		||||
# Print the message.
 | 
			
		||||
    $self->debug("--> $msg") if $self->{_debug};
 | 
			
		||||
 | 
			
		||||
    $s->write($msg . $CRLF);
 | 
			
		||||
 | 
			
		||||
    $self->getline(my $line) or return;
 | 
			
		||||
 | 
			
		||||
    $line =~ s/$CRLF//o if $line;
 | 
			
		||||
    $line ||= 'Nothing sent back';
 | 
			
		||||
    $self->{message} = $line;
 | 
			
		||||
    $self->debug("<-- $line") if $self->{_debug};
 | 
			
		||||
 | 
			
		||||
    return $line;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub getline {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Read a line of input from the server.
 | 
			
		||||
#
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    my $got_cr;
 | 
			
		||||
    my $safety;
 | 
			
		||||
    my $s = $self->{sock};
 | 
			
		||||
    $s->readline($_[1]);
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub getall {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Get all pending output from the server.
 | 
			
		||||
#
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
    $_[1] = '';
 | 
			
		||||
    my $l = 0;
 | 
			
		||||
    my $safety;
 | 
			
		||||
    my $s = $self->{sock};
 | 
			
		||||
    if ($self->{blocking}) {
 | 
			
		||||
        while (<$s>) {
 | 
			
		||||
            last if /^\.$CRLF/o;
 | 
			
		||||
            s/^\.//; # Lines starting with a . are doubled up in POP3
 | 
			
		||||
            $_[1] .= $_;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        my $save = $s->read_size;
 | 
			
		||||
        $s->read_size(1048576);
 | 
			
		||||
        $s->readalluntil("\n.$CRLF", $_[1], ".$CRLF");
 | 
			
		||||
        $s->read_size($save);
 | 
			
		||||
 | 
			
		||||
        $_[1] =~ s/\n\.\r?\n$/\n/; # Remove the final . at the end of the e-mail
 | 
			
		||||
        $_[1] =~ s/^\.//mg; # Remove the initial '.' from any lines beginning with .
 | 
			
		||||
    }
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub connect {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Connect to the server.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my ($s, $iaddr, $msg, $paddr, $proto);
 | 
			
		||||
 | 
			
		||||
    $self->debug("Attempting to connect .. ") if ($self->{_debug});
 | 
			
		||||
 | 
			
		||||
    $self->{blocking} = 1 if $self->{ssl};
 | 
			
		||||
    $self->{port} ||= $self->{ssl} ? 995 : 110;
 | 
			
		||||
 | 
			
		||||
# If there was an existing connection, it'll be closed here when we reassign
 | 
			
		||||
    $self->{sock} = GT::Socket::Client->open(
 | 
			
		||||
        port         => $self->{port},
 | 
			
		||||
        host         => $self->{host},
 | 
			
		||||
        max_down     => 0,
 | 
			
		||||
        timeout      => $self->{timeout},
 | 
			
		||||
        non_blocking => !$self->{blocking},
 | 
			
		||||
        select_time  => TIMEOUT,
 | 
			
		||||
        read_wait    => $self->{data_timeout},
 | 
			
		||||
        ssl          => $self->{ssl},
 | 
			
		||||
        debug        => $self->{_debug}
 | 
			
		||||
    ) or return $self->error("CANTCONNECT", "WARN", GT::Socket::Client->error);
 | 
			
		||||
 | 
			
		||||
    $self->debug('Connected to ' . $self->{host} . ' on port ' . $self->{port} . ($self->{ssl} ? ' via SSL' : '')) if $self->{_debug};
 | 
			
		||||
 | 
			
		||||
# Get server welcoming.
 | 
			
		||||
    $self->getline($msg) or return;
 | 
			
		||||
 | 
			
		||||
# Store this - it's needed for APOP authentication
 | 
			
		||||
    $self->{msg_id}= $1 if ($msg =~ /(<[\w\d\-\.]+\@[\w\d\-\.]+>)/);
 | 
			
		||||
 | 
			
		||||
    $self->debug("Going to login") if $self->{_debug};
 | 
			
		||||
    return $self->login();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub login {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Login either using APOP or regular.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    ($self->{auth_mode} eq 'APOP' && $self->{msg_id}) ? $self->login_apop : $self->login_pass;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub login_apop {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Login using APOP.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my ($hash, $count, $line);
 | 
			
		||||
    {
 | 
			
		||||
        local $SIG{__DIE__};
 | 
			
		||||
        eval { require GT::MD5; 1 } or return $self->error('NOMD5', 'WARN', $@);
 | 
			
		||||
    }
 | 
			
		||||
    $self->debug("Attempting to log in via APOP ... ") if $self->{_debug};
 | 
			
		||||
    $hash = GT::MD5::md5_hex($self->{msg_id} . $self->{pass});
 | 
			
		||||
 | 
			
		||||
    local ($_) = $self->send('APOP ' . $self->{user} . ' ' . $hash) or return;
 | 
			
		||||
    substr($_, 0, 1) eq '+' or return $self->error("LOGIN", "WARN", "APOP Login failed: $_");
 | 
			
		||||
    if (/^\+OK \S+ has (\d+) /i) {
 | 
			
		||||
        $self->{count} = $1;
 | 
			
		||||
    }
 | 
			
		||||
    elsif (uc substr($_, 0, 3) ne '+OK') {
 | 
			
		||||
        return $self->error('LOGIN', 'WARN', $_);
 | 
			
		||||
    }
 | 
			
		||||
    $self->{state} = 'TRANSACTION';
 | 
			
		||||
    $self->stat() or return;
 | 
			
		||||
 | 
			
		||||
    $self->debug("APOP Login successful.") if $self->{_debug};
 | 
			
		||||
    return (($self->{count} == 0) ? '0E0' : $self->{count});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub login_pass {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Login using clear text authentication.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my ($line);
 | 
			
		||||
 | 
			
		||||
    $self->debug("Attempting to log in via clear text ... ") if $self->{_debug};
 | 
			
		||||
 | 
			
		||||
# Enter username.
 | 
			
		||||
    local($_) = $self->send('USER ' . $self->{user}) or return;
 | 
			
		||||
    substr($_, 0, 1) eq '+' or return $self->error('LOGIN', 'WARN', "USER POP Login failed: $_");
 | 
			
		||||
 | 
			
		||||
# Enter password.
 | 
			
		||||
    $_ = $self->send('PASS ' . $self->{pass}) or return;
 | 
			
		||||
    substr($_, 0, 1) eq '+' or return $self->error('LOGIN', 'WARN', "PASS POP Login failed: $_");
 | 
			
		||||
 | 
			
		||||
# Ok, get total number of message, and pop box status.
 | 
			
		||||
    if (/^\+OK \S+ has (\d+) /i) {
 | 
			
		||||
        $self->{count} = $1;
 | 
			
		||||
    }
 | 
			
		||||
    elsif (uc substr($_, 0, 3) ne '+OK') {
 | 
			
		||||
        return $self->error('LOGIN', 'WARN', $_);
 | 
			
		||||
    }
 | 
			
		||||
    $self->stat() or return;
 | 
			
		||||
 | 
			
		||||
    $self->debug("Login successful.") if $self->{_debug};
 | 
			
		||||
    return $self->{count} == 0 ? '0E0' : $self->{count};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub top {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Get the header of a message and the next x lines (optional).
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $num, $code)  = @_;
 | 
			
		||||
    defined($num) or return $self->error('BADARGS', 'FATAL', '$obj->head($msg_num);. No message number passed to head.');
 | 
			
		||||
    $self->debug("Getting head of message $num ... ") if $self->{_debug};
 | 
			
		||||
 | 
			
		||||
    local($_)  = $self->send("TOP $num 0") or return;
 | 
			
		||||
    uc substr($_, 0, 3) eq '+OK' or return $self->error("ACTION", "WARN", "TOP $num 0", "($_)");
 | 
			
		||||
 | 
			
		||||
    my ($tp, $header);
 | 
			
		||||
    $self->getall($header);
 | 
			
		||||
    if (substr($header, 0, 1) eq '>') {
 | 
			
		||||
        substr($header, 0, index($header, $CRLF) + 2) = '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Support broken headers which given unix linefeeds.
 | 
			
		||||
    if ($header =~ /[^\r]\n/) {
 | 
			
		||||
        $header =~ s/\r?\n/$CRLF/g;
 | 
			
		||||
    }
 | 
			
		||||
    $self->debug("Top of message $num retrieved.") if $self->{_debug};
 | 
			
		||||
    if ($code and ref $code eq 'CODE') {
 | 
			
		||||
        $code->($header);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        return wantarray ? split(/$CRLF/o, $header) : $header;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub retr {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Get the entire message.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $num, $code) = @_;
 | 
			
		||||
    defined($num) or return $self->error('BADARGS', 'FATAL', '$obj->retr ($msg_numm, $code);');
 | 
			
		||||
 | 
			
		||||
    $self->debug("Getting message $num ... ") if ($self->{_debug});
 | 
			
		||||
 | 
			
		||||
# Get the size of the message
 | 
			
		||||
    local ($_) = $self->send("RETR $num") or return;
 | 
			
		||||
    uc substr($_, 0, 3) eq '+OK' or return $self->error('ACTION', 'WARN', "RETR $num", $_);
 | 
			
		||||
 | 
			
		||||
# Retrieve the entire email
 | 
			
		||||
    my $body = '';
 | 
			
		||||
    $self->getall($body);
 | 
			
		||||
 | 
			
		||||
# Qmail puts this wierd header as the first line
 | 
			
		||||
    if (substr($body, 0, 1) eq '>') {
 | 
			
		||||
        substr($body, 0, index($body, $CRLF) + 2) = '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Support broken pop servers that send us unix linefeeds.
 | 
			
		||||
    if ($body =~ /[^\r]\n/) {
 | 
			
		||||
        $body =~ s/\r?\n/$CRLF/g;
 | 
			
		||||
    }
 | 
			
		||||
    $self->debug("Message $num retrieved.") if $self->{_debug};
 | 
			
		||||
    if ($code and ref $code eq 'CODE') {
 | 
			
		||||
        $code->($body);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        return \$body;
 | 
			
		||||
    }
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub last {
 | 
			
		||||
    my ($self) = @_;
 | 
			
		||||
 | 
			
		||||
    local($_) = $self->send("LAST") or return;
 | 
			
		||||
    uc substr($_, 0, 3) eq '+OK' or return $self->error("ACTION", "WARN", "LAST", $_);
 | 
			
		||||
    s/^\+OK\s*//i;
 | 
			
		||||
    return $_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub message_save {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Get a message and save it to a file rather then returning.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $num, $file) = @_;
 | 
			
		||||
 | 
			
		||||
# Check arguments.
 | 
			
		||||
    $num  or return $self->error("BADARGS", "FATAL", '$obj->message_save ($msg_num, $IO);');
 | 
			
		||||
    $file or return $self->error("BADARGS", "FATAL", '$obj->message_save ($msg_num, $IO);');
 | 
			
		||||
 | 
			
		||||
    my $io;
 | 
			
		||||
    if (ref $file) {
 | 
			
		||||
        $io = $file;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $file =~ /^\s*(.+?)\s*$/ and $file = $1;
 | 
			
		||||
        $io = \do { local *FH; *FH };
 | 
			
		||||
        open $io, ">$file" or return $self->error("OPENWRITE", "FATAL", $file, "$!");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Get the entire message body.
 | 
			
		||||
    $self->retr($num, sub { print $io $_[0] });
 | 
			
		||||
    $self->debug("Message $num saved to '$file'.") if $self->{_debug};
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub stat {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Handle a stat command, get the number of messages and size.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
 | 
			
		||||
    local($_) = $self->send("STAT") or return;
 | 
			
		||||
    uc substr($_, 0, 3) eq '+OK' or return $self->error('ACTION', 'WARN', 'STAT', $_);
 | 
			
		||||
    if (/^\+OK (\d+) (\d+)/i) {
 | 
			
		||||
        $self->{count} = $1;
 | 
			
		||||
        $self->{size}  = $2;
 | 
			
		||||
        $self->debug("STAT successful - count: $1 size: $2") if $self->{_debug};
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $self->debug("STAT failed, can't determine count.") if $self->{_debug};
 | 
			
		||||
    }
 | 
			
		||||
    return $self->{count} || "0E0";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub list {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Return a list of messages available.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $num  = shift || '';
 | 
			
		||||
    my @messages;
 | 
			
		||||
 | 
			
		||||
# Broken pop servers that don't like 'LIST '.
 | 
			
		||||
    my $cmd = ($num eq '') ? 'LIST' : "LIST $num";
 | 
			
		||||
 | 
			
		||||
    local($_) = $self->send($cmd) or return;
 | 
			
		||||
    uc substr($_, 0, 3) eq '+OK' or return $self->error("ACTION", "WARN", "LIST $num", $_);
 | 
			
		||||
    if ($num) {
 | 
			
		||||
        s/^\+OK\s*//i;
 | 
			
		||||
        return $_;
 | 
			
		||||
    }
 | 
			
		||||
    my $msg = '';
 | 
			
		||||
    $self->getall($msg);
 | 
			
		||||
    @messages = split /$CRLF/o => $msg;
 | 
			
		||||
    $self->debug(@messages . " messages listed.") if ($self->{_debug});
 | 
			
		||||
    if (@messages) {
 | 
			
		||||
        return wantarray ? @messages : join("", @messages);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub rset {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Reset deletion stat.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    local($_) = $self->send("RSET") or return;
 | 
			
		||||
    uc substr($_, 0, 3) eq '+OK' or return $self->error("ACTION", "WARN", "RSET", $_);
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub dele {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Delete a given message.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $num)  = @_;
 | 
			
		||||
    $num and $num =~ /^\d+$/ or return $self->error("BADARGS", "FATAL", '$obj->dele ($msg_num)');
 | 
			
		||||
    local($_) = $self->send("DELE $num") or return;
 | 
			
		||||
    uc substr($_, 0, 3) eq '+OK' or return $self->error("ACTION", "WARN", "DELE $num", $_);
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub quit {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Close the socket.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    $self->send("QUIT") or return;
 | 
			
		||||
    close $self->{sock};
 | 
			
		||||
    $self->{sock} = undef;
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub uidl {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Returns a list of uidls from the remote server
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $num  = shift;
 | 
			
		||||
    local $_;
 | 
			
		||||
    if ($num and !ref $num) {
 | 
			
		||||
        $_ = $self->send("UIDL $num") or return;
 | 
			
		||||
        /^\+OK \d+ (.+)$/i or return $self->error("ACTION", "WARN", "UIDL $num", $_);
 | 
			
		||||
        return $1;
 | 
			
		||||
    }
 | 
			
		||||
    my $ret = {};
 | 
			
		||||
    $_ = $self->send("UIDL") or return;
 | 
			
		||||
    uc substr($_, 0, 3) eq '+OK' or return $self->error("ACTION", "WARN", "UIDL $num", $_);
 | 
			
		||||
    my $list = '';
 | 
			
		||||
    $self->getall($list);
 | 
			
		||||
    for (split /$CRLF/o => $list) {
 | 
			
		||||
        if ($num and ref($num) eq 'CODE') {
 | 
			
		||||
            $num->($_);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            /^(\d+) (.+)/ and $ret->{$1} = $2;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return wantarray ? %{$ret} : $ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub count {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Accessor for number of messages waiting.
 | 
			
		||||
#
 | 
			
		||||
    return $_[0]->{count};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub size {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Accessor for size of messages waiting.
 | 
			
		||||
#
 | 
			
		||||
    return $_[0]->{count};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub last_message {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Accessor for last server message.
 | 
			
		||||
 | 
			
		||||
    @_ == 2 and $_[0]->{message} = $_[1];
 | 
			
		||||
    return $_[0]->{message};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub DESTROY {
 | 
			
		||||
# --------------------------------------------------------
 | 
			
		||||
# Auto close the socket.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    if ($self->{sock} and defined fileno($self->{sock})) {
 | 
			
		||||
        $self->send("QUIT");
 | 
			
		||||
        close $self->{sock};
 | 
			
		||||
        $self->{sock} = undef;
 | 
			
		||||
    }
 | 
			
		||||
    $self->debug("POP Object destroyed.") if ($self->{_debug} > 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
 | 
			
		||||
__END__
 | 
			
		||||
 | 
			
		||||
=head1 NAME
 | 
			
		||||
 | 
			
		||||
GT::Mail::POP3 - Receieve email through POP3 protocal
 | 
			
		||||
 | 
			
		||||
=head1 SYNOPSIS
 | 
			
		||||
 | 
			
		||||
    use GT::Mail::POP3;
 | 
			
		||||
 | 
			
		||||
    my $pop = GT::Mail::POP3->new(
 | 
			
		||||
        host      => 'mail.gossamer-threads.com',
 | 
			
		||||
        port      => 110,
 | 
			
		||||
        user      => 'someusername',
 | 
			
		||||
        pass      => 'somepassword',
 | 
			
		||||
        auth_mode => 'PASS',
 | 
			
		||||
        timeout   => 30,
 | 
			
		||||
        debug     => 1
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    my $count = $pop->connect or die $GT::Mail::POP3::error;
 | 
			
		||||
 | 
			
		||||
    for my $num (1 .. $count) {
 | 
			
		||||
        my $top = $pop->parse_head($num);
 | 
			
		||||
 | 
			
		||||
        my @to = $top->split_field;
 | 
			
		||||
 | 
			
		||||
        if (grep /myfriend\@gossamer-threads\.com/, @to) {
 | 
			
		||||
            $pop->message_save($num, '/keep/email.txt');
 | 
			
		||||
            last;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
=head1 DESCRIPTION
 | 
			
		||||
 | 
			
		||||
GT::Mail::POP3 is a module to check an email account using the POP3 protocol.
 | 
			
		||||
Many of the methods are integrated with L<GT::Mail::Parse>.
 | 
			
		||||
 | 
			
		||||
=head2 new - constructor method
 | 
			
		||||
 | 
			
		||||
This method is inherited from L<GT::Base>. The argument to this method can be
 | 
			
		||||
in the form of a hash or hash ref. As a minimum 'user', 'pass', and 'host' must
 | 
			
		||||
be specified.
 | 
			
		||||
 | 
			
		||||
=over 4
 | 
			
		||||
 | 
			
		||||
=item debug
 | 
			
		||||
 | 
			
		||||
Sets the debugging level for this instance of GT::Mail::POP3.
 | 
			
		||||
 | 
			
		||||
=item host
 | 
			
		||||
 | 
			
		||||
Sets the host to connect to for checking a POP account. This argument must be
 | 
			
		||||
provided.
 | 
			
		||||
 | 
			
		||||
=item port
 | 
			
		||||
 | 
			
		||||
Sets the port on the POP server to attempt to connect to. This defaults to 110,
 | 
			
		||||
unless using SSL, for which the default is 995.
 | 
			
		||||
 | 
			
		||||
=item ssl
 | 
			
		||||
 | 
			
		||||
Establishes the connection using SSL.  Note that this requires Net::SSLeay of
 | 
			
		||||
at least version 1.06.
 | 
			
		||||
 | 
			
		||||
=item user
 | 
			
		||||
 | 
			
		||||
Sets the user name to login with when connecting to the POP server. This must
 | 
			
		||||
be specified.
 | 
			
		||||
 | 
			
		||||
=item pass
 | 
			
		||||
 | 
			
		||||
Sets the password to login with when connection to the POP server. This must be
 | 
			
		||||
specified.
 | 
			
		||||
 | 
			
		||||
=item auth_mode
 | 
			
		||||
 | 
			
		||||
Sets the authentication type for this connection. This can be one of two
 | 
			
		||||
values.  PASS (the default) or APOP. If set to APOP, GT::Mail::POP3 will use
 | 
			
		||||
APOP to login to the remote server.
 | 
			
		||||
 | 
			
		||||
=item timeout
 | 
			
		||||
 | 
			
		||||
Sets the connection timeout.  This isn't entirely reliable as it uses alarm(),
 | 
			
		||||
which isn't supported on all systems.  That aside, this normally isn't needed
 | 
			
		||||
if you want a timeout - it defaults to 30 on alarm()-supporting systems.  The
 | 
			
		||||
main purpose is to provide a value of 0 to disable the alarm() timeout.
 | 
			
		||||
 | 
			
		||||
=back
 | 
			
		||||
 | 
			
		||||
=head2 connect - Connect to the POP account
 | 
			
		||||
 | 
			
		||||
    $obj->connect or die $GT::Mail::POP3::error;
 | 
			
		||||
 | 
			
		||||
This method performs the connection to the POP server. Returns the count of
 | 
			
		||||
messages on the server on success, and undefined on failure. Takes no arguments
 | 
			
		||||
and called before you can perform any actions on the POP server.
 | 
			
		||||
 | 
			
		||||
=head2 head_part - Access the email header
 | 
			
		||||
 | 
			
		||||
    # Get a parsed header part object for the first email in the list.
 | 
			
		||||
    my $top_part = $obj->head_part(1);
 | 
			
		||||
 | 
			
		||||
Instance method. The only argument to this method is the message number to get.
 | 
			
		||||
Returns a L<GT::Mail::Parts> object containing only the parsed header of the
 | 
			
		||||
specified message.
 | 
			
		||||
 | 
			
		||||
=head2 all_head_parts - Access all email headers
 | 
			
		||||
 | 
			
		||||
    # Get all the head parts from all messages
 | 
			
		||||
    my @headers = $obj->all_head_parts;
 | 
			
		||||
 | 
			
		||||
Instance method. Gets all the headers of all the email's on the remote server.
 | 
			
		||||
Returns an array of the L<GT::Mail::Parts> object. One object for each
 | 
			
		||||
email. None of the email's bodies are retrieved, only the head.
 | 
			
		||||
 | 
			
		||||
=head2 parse_message - Access an email
 | 
			
		||||
 | 
			
		||||
    # Parse an email and get the GT::Mail object
 | 
			
		||||
    my $mail = $obj->parse_message (1);
 | 
			
		||||
 | 
			
		||||
Instance method. Pass in the number of the email to retrieve. This method
 | 
			
		||||
retrieves the specified email and returns the parsed GT::Mail object. If this
 | 
			
		||||
method fails you should check $GT::Mail::error for the error message.
 | 
			
		||||
 | 
			
		||||
=head2 message_save - Save an email
 | 
			
		||||
 | 
			
		||||
    open FH, '/path/to/email.txt' or die $!;
 | 
			
		||||
 | 
			
		||||
    # Save message 2 to file
 | 
			
		||||
    $obj->message_save (2, \*FH);
 | 
			
		||||
    close FH;
 | 
			
		||||
 | 
			
		||||
- or -
 | 
			
		||||
 | 
			
		||||
    $obj->message_save (2, '/path/to/email.txt') or die $GT::Mail::POP3::error;
 | 
			
		||||
 | 
			
		||||
Instance method. This method takes the message number as it's first argument,
 | 
			
		||||
and either a file path or a file handle ref as it's second argument. If a file
 | 
			
		||||
path is provided the file will be opened to truncate. The email is then
 | 
			
		||||
retrieved from the server and written to the file.
 | 
			
		||||
 | 
			
		||||
=head2 stat - Do a STAT command
 | 
			
		||||
 | 
			
		||||
    # Get the number of messages on the server
 | 
			
		||||
    my $count = $obj->stat;
 | 
			
		||||
 | 
			
		||||
Instance method. Does a STAT command on the remote server. It stores the total
 | 
			
		||||
size and returns the count of messages on the server, if successful. Otherwise
 | 
			
		||||
returns undef.
 | 
			
		||||
 | 
			
		||||
=head2 list - Do a LIST command
 | 
			
		||||
 | 
			
		||||
    # At a list of messages on the server
 | 
			
		||||
    my @messages = $obj->list;
 | 
			
		||||
 | 
			
		||||
Instance method. Does a LIST command on the remote server. Returns an array of
 | 
			
		||||
the lines in list context and a single scalar that contains all the lines in
 | 
			
		||||
scalar context.
 | 
			
		||||
 | 
			
		||||
=head2 rset - Do an RSET command
 | 
			
		||||
 | 
			
		||||
    # Tell the server to ignore any dele commands we have issued in this
 | 
			
		||||
    # session
 | 
			
		||||
    $obj->rset;
 | 
			
		||||
 | 
			
		||||
Instance method. Does an RSET command. This command resets the servers
 | 
			
		||||
knowledge of what should be deleted when QUIT is called. Returns 1 on success.
 | 
			
		||||
 | 
			
		||||
=head2 dele - Do a DELE command
 | 
			
		||||
 | 
			
		||||
    # Delete message 4
 | 
			
		||||
    $obj->dele (4);
 | 
			
		||||
 | 
			
		||||
Instance method. Does a DELE command. The only argument is the message number
 | 
			
		||||
to delete.  Returns 1 on success.
 | 
			
		||||
 | 
			
		||||
=head2 quit - Quit the connection
 | 
			
		||||
 | 
			
		||||
    # Close our connection
 | 
			
		||||
    $obj->quit;
 | 
			
		||||
 | 
			
		||||
Instance method. Sends the QUIT command to the server. The should should
 | 
			
		||||
disconnect soon after this. No more actions can be taken on this connection
 | 
			
		||||
until connect is called again.
 | 
			
		||||
 | 
			
		||||
=head2 uidl - Do a UIDL command
 | 
			
		||||
 | 
			
		||||
    # Get the uidl for message 1
 | 
			
		||||
    my $uidl = $obj->uidl (1);
 | 
			
		||||
 | 
			
		||||
    # Get a list of all the uidl's and print them
 | 
			
		||||
    $obj->uidl (sub { print @_ });
 | 
			
		||||
 | 
			
		||||
    # Get an array of all the uidl's
 | 
			
		||||
    my @uidl = $obj->uidl;
 | 
			
		||||
 | 
			
		||||
Instance method. Attempts to do a UIDL command on the remote server. Please be
 | 
			
		||||
aware support for the UIDL command is not very wide spread. This method can
 | 
			
		||||
take the message number as it's first argument. If the message number is given,
 | 
			
		||||
the UIDL for that message is returned. If the first argument is a code
 | 
			
		||||
reference, a UIDL command is done with no message specified and the code
 | 
			
		||||
reference is called for each line returned from the remote server. If no second
 | 
			
		||||
argument is given, a UIDL command is done, and the results are returned in a
 | 
			
		||||
has of message number to UIDL.
 | 
			
		||||
 | 
			
		||||
=head2 count - Get the number of messages
 | 
			
		||||
 | 
			
		||||
    # Get the count from the last STAT
 | 
			
		||||
    my $count = $obj->count;
 | 
			
		||||
 | 
			
		||||
This method returns the number of messages on the server from the last STAT
 | 
			
		||||
command. A STAT is done on connect.
 | 
			
		||||
 | 
			
		||||
=head2 size - Get the size of all messages
 | 
			
		||||
 | 
			
		||||
    # Get the total size of all messages on the server
 | 
			
		||||
    my $size = $obj->size;
 | 
			
		||||
 | 
			
		||||
This method returns the size of all messages in the server as returned by the
 | 
			
		||||
last STAT command sent to the server.
 | 
			
		||||
 | 
			
		||||
=head2 send - Send a raw command
 | 
			
		||||
 | 
			
		||||
    # Send a raw command to the server
 | 
			
		||||
    my $ret = $obj->send ("HELO");
 | 
			
		||||
 | 
			
		||||
This method sends the specified raw command to the POP server. The one line
 | 
			
		||||
return from the server is returned. Do not call this method if you are
 | 
			
		||||
expecting more than a one line response.
 | 
			
		||||
 | 
			
		||||
=head2 top - Retrieve the header
 | 
			
		||||
 | 
			
		||||
    # Get the header of message 2 in an array.  New lines are stripped
 | 
			
		||||
    my @header = $obj->top (2);
 | 
			
		||||
 | 
			
		||||
    # Get the header as a string
 | 
			
		||||
    my $header = $obj->top (2);
 | 
			
		||||
 | 
			
		||||
Instance method to retrieve the top of an email on the POP server. The only
 | 
			
		||||
argument should be the message number to retrieve. Returns a scalar containing
 | 
			
		||||
the header in scalar context and an array, which is the scalar split on
 | 
			
		||||
\015?\012, in list context.
 | 
			
		||||
 | 
			
		||||
=head2 retr - Retrieve an email
 | 
			
		||||
 | 
			
		||||
    # Get message 3 from the remote server in an array.  New lines are stripped
 | 
			
		||||
    my @email = $obj->retr (3);
 | 
			
		||||
 | 
			
		||||
    # Get it as a string
 | 
			
		||||
    my $email = $obj->retr (3);
 | 
			
		||||
 | 
			
		||||
Instance method to retrieve an email from the POP server. The first argument to
 | 
			
		||||
this method should be the message number to retrieve. The second argument is an
 | 
			
		||||
optional code ref to call for each line of the message that is retrieved. If no
 | 
			
		||||
code ref is specified, this method will put the email in a scalar and return
 | 
			
		||||
the scalar in scalar context and return the scalar split on \015?\012 in list
 | 
			
		||||
context.
 | 
			
		||||
 | 
			
		||||
=head1 REQUIREMENTS
 | 
			
		||||
 | 
			
		||||
L<GT::Socket::Client>
 | 
			
		||||
L<GT::Base>
 | 
			
		||||
L<GT::MD5> (for APOP authentication)
 | 
			
		||||
 | 
			
		||||
=head1 COPYRIGHT
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
http://www.gossamer-threads.com/
 | 
			
		||||
 | 
			
		||||
=head1 VERSION
 | 
			
		||||
 | 
			
		||||
Revision: $Id: POP3.pm,v 1.56 2004/03/19 00:36:16 brewt Exp $
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										788
									
								
								site/glist/lib/GT/Mail/Parse.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										788
									
								
								site/glist/lib/GT/Mail/Parse.pm
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,788 @@
 | 
			
		||||
# ==================================================================
 | 
			
		||||
# Gossamer Threads Module Library - http://gossamer-threads.com/
 | 
			
		||||
#
 | 
			
		||||
#   GT::Mail::Parse
 | 
			
		||||
#   Author  : Scott Beck
 | 
			
		||||
#   CVS Info :                          
 | 
			
		||||
#   $Id: Parse.pm,v 1.79 2004/10/23 02:16:39 brewt Exp $
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
# ==================================================================
 | 
			
		||||
 | 
			
		||||
package GT::Mail::Parse;
 | 
			
		||||
# =============================================================================
 | 
			
		||||
# If MIME::Base64 is installed use it - must eval before hand or 5.004_04 wipes
 | 
			
		||||
# our ISA.
 | 
			
		||||
my $have_b64 = eval {
 | 
			
		||||
    local $SIG{__DIE__};
 | 
			
		||||
    require MIME::Base64;
 | 
			
		||||
    import MIME::Base64;
 | 
			
		||||
    if ($] < 5.005) { local $^W; decode_base64('brok'); }
 | 
			
		||||
    1;
 | 
			
		||||
};
 | 
			
		||||
$have_b64 or *decode_base64 = \>_old_decode_base64;
 | 
			
		||||
my $use_decode_qp;
 | 
			
		||||
if ($have_b64 and
 | 
			
		||||
    $MIME::Base64::VERSION >= 2.16 and # Prior versions had decoding bugs
 | 
			
		||||
    defined &MIME::QuotedPrint::decode_qp and (
 | 
			
		||||
        not defined &MIME::QuotedPrint::old_decode_qp or
 | 
			
		||||
        \&MIME::QuotedPrint::decode_qp != \&MIME::QuotedPrint::old_decode_qp
 | 
			
		||||
    )
 | 
			
		||||
) {
 | 
			
		||||
    $use_decode_qp = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Pragmas
 | 
			
		||||
use strict;
 | 
			
		||||
use vars qw($VERSION $DEBUG $ERRORS $CRLF $CR_LN @ISA);
 | 
			
		||||
 | 
			
		||||
# System modules
 | 
			
		||||
use Fcntl;
 | 
			
		||||
 | 
			
		||||
# Internal modules
 | 
			
		||||
use GT::Mail::Parts;
 | 
			
		||||
use GT::Base;
 | 
			
		||||
 | 
			
		||||
# Inherent from GT::Base for errors and debug
 | 
			
		||||
@ISA = qw(GT::Base);
 | 
			
		||||
 | 
			
		||||
# Debugging mode
 | 
			
		||||
$DEBUG = 0;
 | 
			
		||||
 | 
			
		||||
# The package version, both in 1.23 style *and* usable by MakeMaker:
 | 
			
		||||
$VERSION = substr q$Revision: 1.79 $, 10;
 | 
			
		||||
 | 
			
		||||
# The CRLF sequence:
 | 
			
		||||
$CRLF = "\n";
 | 
			
		||||
 | 
			
		||||
# The length of a crlf
 | 
			
		||||
$CR_LN = 1;
 | 
			
		||||
 | 
			
		||||
# Error messages
 | 
			
		||||
$ERRORS = {
 | 
			
		||||
    PARSE     => "An error occured while parsing: %s",
 | 
			
		||||
    DECODE    => "An error occured while decoding: %s",
 | 
			
		||||
    NOPARTS   => "Email has no parts!",
 | 
			
		||||
    DEEPPARTS => "Deep recursion dected, email appears to have more than 50 parts!",
 | 
			
		||||
    MALFORMED => "Found (%s) before finding the start of the boundary. Message malformed"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
my %DecoderFor = (
 | 
			
		||||
  # Standard...
 | 
			
		||||
    '7bit'             => 'NBit',
 | 
			
		||||
    '8bit'             => 'NBit',
 | 
			
		||||
    'base64'           => 'Base64',
 | 
			
		||||
    'binary'           => 'Binary',
 | 
			
		||||
    'none'             => 'Binary',
 | 
			
		||||
    'quoted-printable' => 'QuotedPrint',
 | 
			
		||||
 | 
			
		||||
  # Non-standard...
 | 
			
		||||
    'x-uu'             => 'UU',
 | 
			
		||||
    'x-uuencode'       => 'UU',
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
sub new {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# CLASS->new (
 | 
			
		||||
#           naming  => \&naming,
 | 
			
		||||
#           in_file => '/path/to/file/to/parse',
 | 
			
		||||
#           handle  => \*FH
 | 
			
		||||
#       );
 | 
			
		||||
# ----------------------------------------------
 | 
			
		||||
#  Class method to get a new object. Calles init if there are any additional
 | 
			
		||||
#  argument. To set the arguments that are passed to naming call naming
 | 
			
		||||
#  directly.
 | 
			
		||||
#
 | 
			
		||||
    my $this = shift;
 | 
			
		||||
    my $class = ref $this || $this;
 | 
			
		||||
    my $self = bless {
 | 
			
		||||
        file_handle    => undef,
 | 
			
		||||
        parts          => [],
 | 
			
		||||
        head_part      => undef,
 | 
			
		||||
        headers_intact => 1,
 | 
			
		||||
        _debug         => $DEBUG,
 | 
			
		||||
    }, $class;
 | 
			
		||||
    $self->init(@_) if @_;
 | 
			
		||||
    $self->debug("Created new object ($self).") if $self->{_debug} > 1;
 | 
			
		||||
    return $self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub init {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# $obj->init (%opts);
 | 
			
		||||
# -------------------
 | 
			
		||||
#   Sets the options for the current object.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $opt = {};
 | 
			
		||||
    if (@_ == 1 and ref $_[0] eq 'HASH') { $opt = shift }
 | 
			
		||||
    elsif (defined $_[0] and not @_ % 2) { $opt = {@_} }
 | 
			
		||||
    else { return $self->error("BADARGS", "FATAL", "init") }
 | 
			
		||||
 | 
			
		||||
    $self->{_debug} = exists($opt->{debug}) ? $opt->{debug} : $DEBUG;
 | 
			
		||||
    $self->{headers_intact} = exists($opt->{headers_intact}) ? $opt->{headers_intact} : 1;
 | 
			
		||||
    for my $m (qw(crlf in_file in_handle in_string attach_rfc822)) {
 | 
			
		||||
        $self->$m($opt->{$m}) if defined $opt->{$m};
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub attach_rfc822 {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    if (@_) {
 | 
			
		||||
        $self->{attach_rfc822} = shift;
 | 
			
		||||
    }
 | 
			
		||||
    return $self->{attach_rfc822};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub crlf {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    $CRLF = pop || return $CRLF;
 | 
			
		||||
    $CR_LN = length($CRLF);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub parse {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# my $top = $obj->parse;
 | 
			
		||||
# ----------------------
 | 
			
		||||
#   Parses the email set in new or init. Also calls init if there are any
 | 
			
		||||
#   arguments passed in.
 | 
			
		||||
#   Returns the top level part object.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, @opts) = @_;
 | 
			
		||||
 | 
			
		||||
# Any additional arguments goto init
 | 
			
		||||
    $self->init(@opts) if @opts;
 | 
			
		||||
 | 
			
		||||
    ($self->{string} and ref($self->{string}) eq 'SCALAR')
 | 
			
		||||
        or return $self->error('BADARGS', 'FATAL', "No input was given to parse before parse() was called");
 | 
			
		||||
 | 
			
		||||
# Recursive function to parse
 | 
			
		||||
    $self->_parse_part(undef, $self->{string});  # parse!
 | 
			
		||||
 | 
			
		||||
# Return top part
 | 
			
		||||
    return $self->{head_part};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub parse_head {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# my $head = $obj->parse_head;
 | 
			
		||||
# ----------------------------
 | 
			
		||||
#   Passes any additional arguments to init. Parses only the top level header.
 | 
			
		||||
#   This saves some overhead if for example all you need to do it find out who
 | 
			
		||||
#   an email is to on a POP3 server.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $in, @opts) = @_;
 | 
			
		||||
 | 
			
		||||
    unless (ref $self) {
 | 
			
		||||
        $self = $self->new(@opts);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $in ||= $self->{string};
 | 
			
		||||
    $in || return $self->error("BADARGS", "FATAL", "No string to parse set!");
 | 
			
		||||
 | 
			
		||||
# Parse the head
 | 
			
		||||
    return $self->_parse_head($in);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#--------------------------------------------
 | 
			
		||||
# Access
 | 
			
		||||
#--------------------------------------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sub in_handle {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# $obj->in_handle (\*FH);
 | 
			
		||||
# --------------------
 | 
			
		||||
#   Pass in a file handle to parse from when parse is called.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $value) = @_;
 | 
			
		||||
    if (@_ > 1 and ref $value and defined fileno $value) {
 | 
			
		||||
        read $value, ${$self->{string}}, -s $value;
 | 
			
		||||
    }
 | 
			
		||||
    return $self->{string};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub in_file {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# $obj->in_file ('/path/to/file');
 | 
			
		||||
# --------------------------------
 | 
			
		||||
#   Pass in the path to a file to parse when parse is called
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $file = shift;
 | 
			
		||||
    my $io = \do { local *FH; *FH };
 | 
			
		||||
    open $io, "<$file" or return $self->error("READOPEN", "FATAL", $file, $!);
 | 
			
		||||
    return $self->in_handle($io);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub in_string {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    my ($self, $string) = @_;
 | 
			
		||||
    return $self->{string} unless (@_ > 1);
 | 
			
		||||
    if (ref($string) eq 'SCALAR') {
 | 
			
		||||
        $self->{string} = $string;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $self->{string} = \$string;
 | 
			
		||||
    }
 | 
			
		||||
    return $self->{string};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub size {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# my $email_size = $obj->size;
 | 
			
		||||
# ----------------------------
 | 
			
		||||
#   Returns the total size of an email. Call this method after the email has
 | 
			
		||||
#   been parsed.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    (@{$self->{parts}} > 0) or return $self->error("NOPARTS", "WARN");
 | 
			
		||||
    my $size = 0;
 | 
			
		||||
    foreach (@{$self->{parts}}) {
 | 
			
		||||
        $size += $_->size;
 | 
			
		||||
    }
 | 
			
		||||
    return $size;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub all_parts {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# my @parts = $obj->all_parts;
 | 
			
		||||
# ----------------------------
 | 
			
		||||
#   Returns a list of all the part object for the current parsed email.  If the
 | 
			
		||||
#   email is not multipart this will be just the header part.
 | 
			
		||||
#
 | 
			
		||||
    return @{shift()->{parts}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub top_part {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    return ${shift()->{parts}}[0];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#---------------------------------------------
 | 
			
		||||
# Internal Methods
 | 
			
		||||
#---------------------------------------------
 | 
			
		||||
 | 
			
		||||
sub _parse_head {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Internal Method
 | 
			
		||||
# ---------------
 | 
			
		||||
# Parse just the head. Returns the part object.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $in) = @_;
 | 
			
		||||
 | 
			
		||||
    # Get a new part object
 | 
			
		||||
    my $part = GT::Mail::Parts->new(headers_intact => $self->{headers_intact});
 | 
			
		||||
 | 
			
		||||
    if (ref $in eq 'ARRAY') {
 | 
			
		||||
        $part->extract($in) or return $self->error("PARSE", "WARN", "Couldn't parse head!");
 | 
			
		||||
        return $part;
 | 
			
		||||
    }
 | 
			
		||||
    $part->extract([map { $_ . $CRLF } split($CRLF => $$in)]) or return $self->error($GT::Mail::Parts::error, 'WARN');
 | 
			
		||||
    return $part;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _parse_part {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Internal Method
 | 
			
		||||
# ---------------
 | 
			
		||||
#   Parses all the parts of an email and stores them in there parts object.
 | 
			
		||||
#   This function is recursive.
 | 
			
		||||
# 
 | 
			
		||||
    my ($self, $outer_bound, $in, $part) = @_;
 | 
			
		||||
    my $state = 'OK';
 | 
			
		||||
 | 
			
		||||
# First part is going to be the top level part
 | 
			
		||||
    if (!$part) {
 | 
			
		||||
        $part = GT::Mail::Parts->new(headers_intact => $self->{headers_intact});
 | 
			
		||||
        $self->{head_part} = $part;
 | 
			
		||||
    }
 | 
			
		||||
    push @{$self->{parts}}, $part;
 | 
			
		||||
 | 
			
		||||
# Get the header for this part
 | 
			
		||||
    my $indx;
 | 
			
		||||
    if (($indx = index($$in, $CRLF)) == 0) {
 | 
			
		||||
        substr($$in, 0, $CR_LN) = '';
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $indx = index($$in, ($CRLF . $CRLF));
 | 
			
		||||
        if ($indx == -1) {
 | 
			
		||||
            $self->debug('Message has no body.') if $self->{_debug};
 | 
			
		||||
            $indx = length($$in);
 | 
			
		||||
        }
 | 
			
		||||
        $part->extract([map { $_ . $CRLF } split($CRLF => substr($$in, 0, $indx))])
 | 
			
		||||
            or return $self->error($GT::Mail::Parts::error, 'WARN');
 | 
			
		||||
        substr($$in, 0, $indx + ($CR_LN * 2)) = '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Get the mime type
 | 
			
		||||
    my ($type, $subtype) = split('/', $part->mime_type);
 | 
			
		||||
    $type    ||= 'text';
 | 
			
		||||
    $subtype ||= 'plain';
 | 
			
		||||
    if ($self->{_debug}) {
 | 
			
		||||
        my $name = $part->recommended_filename || '[unnamed]';
 | 
			
		||||
        $self->debug("Type is '$type/$subtype' ($name)");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Deal with the multipart type with some recursion
 | 
			
		||||
    if ($type eq 'multipart') {
 | 
			
		||||
        my $retype = (($subtype eq 'digest') ? 'message/rfc822' : '');
 | 
			
		||||
 | 
			
		||||
# Find the multipart boundary
 | 
			
		||||
        my $inner_bound = $part->multipart_boundary;
 | 
			
		||||
        $self->debug("Boundary is $inner_bound") if $self->{_debug} > 1;
 | 
			
		||||
        defined $inner_bound             or return $self->error("PARSE", "WARN", "No multipart boundary in multipart message.");
 | 
			
		||||
        index($inner_bound, $CRLF) == -1 or return $self->error("PARSE", "WARN", "CR or LF in multipart boundary.");
 | 
			
		||||
 | 
			
		||||
# Parse the Preamble
 | 
			
		||||
        $self->debug("Parsing preamble.") if $self->{_debug} > 1;
 | 
			
		||||
        $state = $self->_parse_preamble($inner_bound, $in, $part) or return;
 | 
			
		||||
        chomp($part->preamble->[-1]) if @{$part->preamble};
 | 
			
		||||
 | 
			
		||||
# Get all the parts of the multipart message
 | 
			
		||||
        my $partno = 0;
 | 
			
		||||
        my $parts;
 | 
			
		||||
        while (1) {
 | 
			
		||||
            ++$partno < 200 or return $self->error('DEEPPARTS', 'WARN');
 | 
			
		||||
            $self->debug("Parsing part $partno.") if $self->{_debug};
 | 
			
		||||
 | 
			
		||||
            ($parts, $state) = $self->_parse_part($inner_bound, $in, GT::Mail::Parts->new(headers_intact => $self->{headers_intact})) or return;
 | 
			
		||||
            ($state eq 'EOF') and return $self->error('PARSE', 'WARN', 'Unexpected EOF before close.');
 | 
			
		||||
 | 
			
		||||
            $parts->mime_type($retype) if $retype;
 | 
			
		||||
            push(@{$part->{parts}}, $parts);
 | 
			
		||||
 | 
			
		||||
            last if $state eq 'CLOSE';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
# Parse the epilogue
 | 
			
		||||
        $self->debug("Parsing epilogue.") if $self->{_debug} > 1;
 | 
			
		||||
        $state = $self->_parse_epilogue($outer_bound, $in, $part) or return;
 | 
			
		||||
        chomp($part->epilogue->[-1]) if @{$part->epilogue} and $state ne 'EOF';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# We are on a single part
 | 
			
		||||
    else {
 | 
			
		||||
        $self->debug("Decoding single part.") if $self->{_debug} > 1;
 | 
			
		||||
 | 
			
		||||
# Find the encoding for the body of the part
 | 
			
		||||
        my $encoding = $part->mime_encoding || 'binary';
 | 
			
		||||
        if (!exists($DecoderFor{lc($encoding)})) {
 | 
			
		||||
            $self->debug("Unsupported encoding '$encoding': using 'binary'... \n" .
 | 
			
		||||
                "The entity will have an effective MIME type of \n" .
 | 
			
		||||
                "application/octet-stream, as per RFC-2045.")
 | 
			
		||||
                if $self->{_debug};
 | 
			
		||||
            $part->effective_type('application/octet-stream');
 | 
			
		||||
            $encoding = 'binary';
 | 
			
		||||
        }
 | 
			
		||||
        my $reparse;
 | 
			
		||||
        $reparse = ("$type/$subtype" eq "message/rfc822") unless $self->{attach_rfc822};
 | 
			
		||||
        my $encoded = "";
 | 
			
		||||
 | 
			
		||||
# If we have boundaries we parse the body to the boundary
 | 
			
		||||
        if (defined $outer_bound) {
 | 
			
		||||
            $self->debug("Parsing to boundary.") if $self->{_debug} > 1;
 | 
			
		||||
            $state = $self->_parse_to_bound($outer_bound, $in, \$encoded) or return;
 | 
			
		||||
        }
 | 
			
		||||
# Else we would parse the rest of the input stream as the rest of the message
 | 
			
		||||
        else {
 | 
			
		||||
            $self->debug("No Boundries.") if $self->{_debug} > 1;
 | 
			
		||||
            $encoded = $$in;
 | 
			
		||||
            $state = 'EOF';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
# Normal part so we get the body and decode it.
 | 
			
		||||
        if (!$reparse) {
 | 
			
		||||
            $self->debug("Not reparsing.") if $self->{_debug} > 1;
 | 
			
		||||
            $part->{body_in} = 'MEMORY';
 | 
			
		||||
 | 
			
		||||
            my $decoder = $DecoderFor{lc($encoding)};
 | 
			
		||||
            $self->debug("Decoding part using: " . lc($encoding)) if $self->{_debug};
 | 
			
		||||
            $part->{data} = '';
 | 
			
		||||
            my $out = '';
 | 
			
		||||
            my $res = $self->$decoder(\$encoded, \$out);
 | 
			
		||||
            undef $encoded;
 | 
			
		||||
            $res or return;
 | 
			
		||||
            $part->{data} = $out;
 | 
			
		||||
            undef $out;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
# If have an embeded email we reparse it.
 | 
			
		||||
            $self->debug("Reparsing enclosed message.") if $self->{_debug};
 | 
			
		||||
            my $out = '';
 | 
			
		||||
 | 
			
		||||
            my $decoder = $DecoderFor{lc($encoding)};
 | 
			
		||||
            $self->debug("Decoding " . lc($encoding)) if $self->{_debug};
 | 
			
		||||
            my $res = $self->$decoder(\$encoded, \$out);
 | 
			
		||||
            undef $encoded;
 | 
			
		||||
            $res or return;
 | 
			
		||||
            my $p = GT::Mail::Parts->new(headers_intact => $self->{headers_intact});
 | 
			
		||||
            push @{$part->{parts}}, $p;
 | 
			
		||||
            $self->_parse_part(undef, \$out, $p) or return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return ($part, $state);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _parse_to_bound {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# This method takes a boundary ($bound), an input string ref ($in), and an
 | 
			
		||||
# output string ref ($out). It will place into $$out the data contained by
 | 
			
		||||
# $bound, and remove the entire region (including boundary) from $$in.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $bound, $in, $out) = @_;
 | 
			
		||||
 | 
			
		||||
# Set up strings for faster checking:
 | 
			
		||||
    my ($delim, $close) = ("--$bound", "--$bound--");
 | 
			
		||||
    $self->debug("Parsing bounds. Skip until\n\tdelim ($delim)\n\tclose ($close)") if $self->{_debug} > 1;
 | 
			
		||||
    my ($pos, $ret);
 | 
			
		||||
 | 
			
		||||
# Place our part in $$out.    
 | 
			
		||||
    $$out = undef;
 | 
			
		||||
    if (defined($pos = index($$in, "$CRLF$delim$CRLF")) and $pos != -1) {
 | 
			
		||||
        $$out = substr($$in, 0, $pos);
 | 
			
		||||
        substr($$in, 0, $pos + length("$CRLF$delim$CRLF")) = "";
 | 
			
		||||
        $ret = 'DELIM';
 | 
			
		||||
    }
 | 
			
		||||
    elsif (index($$in, "$delim$CRLF") == 0) {
 | 
			
		||||
        substr($$in, 0, length("$delim$CRLF")) = "";
 | 
			
		||||
        $$out = "";
 | 
			
		||||
        $ret = 'DELIM';
 | 
			
		||||
    }
 | 
			
		||||
    elsif (defined($pos = index($$in, "$CRLF$close$CRLF")) and $pos != -1) {
 | 
			
		||||
        $$out = $$in;
 | 
			
		||||
        substr($$out, -(length($$out) - $pos)) = '';
 | 
			
		||||
        my $len = (length($$in) - (length("$CRLF$close$CRLF") + $pos)) * -1;
 | 
			
		||||
        if ($len == 0) {
 | 
			
		||||
            $$in = '';
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            $$in = substr($$in, $len);
 | 
			
		||||
        }
 | 
			
		||||
        $ret = 'CLOSE';
 | 
			
		||||
    }
 | 
			
		||||
    elsif (index($$in, "$CRLF$close") == (length($$in) - length("$CRLF$close"))) {
 | 
			
		||||
        $$out = substr($$in, 0, length($$in) - length("$CRLF$close"));
 | 
			
		||||
        $$in  = "";
 | 
			
		||||
        $ret  = 'CLOSE';
 | 
			
		||||
    }
 | 
			
		||||
    elsif (index($$in, "$close$CRLF") == 0) {
 | 
			
		||||
        $$out = "";
 | 
			
		||||
        substr($$in, 0, length("$close$CRLF")) = "";
 | 
			
		||||
        $ret = 'CLOSE';
 | 
			
		||||
    }
 | 
			
		||||
    elsif (index($$in, $close) == 0 and (length($$in) == length($close))) {
 | 
			
		||||
        $$out = "";
 | 
			
		||||
        $$in = "";
 | 
			
		||||
        $ret = 'CLOSE';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (defined $$out) {
 | 
			
		||||
        return $ret;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
# Broken Email, retype to text/plain
 | 
			
		||||
        $self->{parts}->[$#{$self->{parts}}]->set('content-type' => 'text/plain');
 | 
			
		||||
        $$out = $$in;
 | 
			
		||||
        return 'CLOSE';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _parse_preamble {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Internal Method
 | 
			
		||||
# ---------------
 | 
			
		||||
#   Parses preamble and sets it in part.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $inner_bound, $in, $part) = @_;
 | 
			
		||||
 | 
			
		||||
    my $loc;
 | 
			
		||||
    my ($delim, $close) = ("--$inner_bound", "--$inner_bound--");
 | 
			
		||||
 | 
			
		||||
    $self->debug("Parsing preamble. Skip until\n\tdelim ($delim)\n\tclose ($close)") if $self->{_debug} > 1;
 | 
			
		||||
    my @saved;
 | 
			
		||||
    $part->preamble(\@saved);
 | 
			
		||||
 | 
			
		||||
    my ($data, $pos, $len);
 | 
			
		||||
    if (index($$in, "$delim$CRLF") == 0) {
 | 
			
		||||
        $data = '';
 | 
			
		||||
        substr($$in, 0, length("$delim$CRLF")) = '';
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $pos = index($$in, "$CRLF$delim$CRLF");
 | 
			
		||||
        if ($pos >= 0) {
 | 
			
		||||
            $data = substr($$in, 0, $pos);
 | 
			
		||||
            substr($$in, 0, $pos + length("$CRLF$delim$CRLF")) = '';
 | 
			
		||||
        }
 | 
			
		||||
        elsif ($pos == -1) {
 | 
			
		||||
            return $self->error('PARSE', 'WARN', "Unable to find opening boundary: " .
 | 
			
		||||
                "$delim\n" .
 | 
			
		||||
                "Message is probably corrupt.");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    push @saved, split $CRLF => $data;
 | 
			
		||||
    undef $data;
 | 
			
		||||
    return 'DELIM';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _parse_epilogue {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
# Internal Method
 | 
			
		||||
# ---------------
 | 
			
		||||
#   Parses epilogue and sets it in part.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $outer_bound, $in, $part) = @_;
 | 
			
		||||
    my ($delim, $close, $loc);
 | 
			
		||||
 | 
			
		||||
    ($delim, $close) = ("--$outer_bound", "--$outer_bound--") if defined $outer_bound;
 | 
			
		||||
 | 
			
		||||
    $self->debug("Parsing epilogue. Skip until\n\tdelim (" . ($delim || '') .
 | 
			
		||||
        ")\n\tclose (" . ($close || '') . ")")
 | 
			
		||||
        if $self->{_debug} > 1;
 | 
			
		||||
    my @saved;
 | 
			
		||||
    $part->epilogue(\@saved);
 | 
			
		||||
    if (defined $outer_bound) {
 | 
			
		||||
        if ($$in =~ s/(.*?)(?:\A|$CRLF)\Q$delim\E$CRLF//s) {
 | 
			
		||||
            push(@saved, split($CRLF => $1));
 | 
			
		||||
            $self->debug("Found delim($delim)") if $self->{_debug};
 | 
			
		||||
            return 'DELIM'
 | 
			
		||||
        }
 | 
			
		||||
        elsif ($$in =~ s/(.*?)(?:\A|$CRLF)\Q$close\E(?:\Z|$CRLF)//s) {
 | 
			
		||||
            push(@saved, split($CRLF => $1));
 | 
			
		||||
            $self->debug("Found close($close)") if $self->{_debug};
 | 
			
		||||
            return 'CLOSE'
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    push(@saved, split($CRLF => $$in));
 | 
			
		||||
    $$in = '';
 | 
			
		||||
    $self->debug("EOF: epilogue is " . length(join '', @saved) . " bytes") if $self->{_debug};
 | 
			
		||||
    return 'EOF';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sub Base64 {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    my ($self, $in, $out) = @_;
 | 
			
		||||
 | 
			
		||||
# Remove any non base64 characters.
 | 
			
		||||
    $$in =~ tr{A-Za-z0-9+/}{}cd;
 | 
			
		||||
 | 
			
		||||
# Must pass multiple of 4 to decode_base64. Store any remainder in $rem_str and 
 | 
			
		||||
# pad it with trailing equal signs.
 | 
			
		||||
    my $rem = length($$in) % 4;
 | 
			
		||||
    my ($rem_str);
 | 
			
		||||
    if ($rem) {
 | 
			
		||||
        my $pad   = '=' x (4 - $rem);
 | 
			
		||||
        $rem_str  = substr($$in, length($$in) - $rem);
 | 
			
		||||
        $rem_str .= $pad;
 | 
			
		||||
        substr($$in, $rem * -1) = '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $$out = decode_base64($$in);
 | 
			
		||||
    if ($rem) {
 | 
			
		||||
        $$out .= decode_base64($rem_str);
 | 
			
		||||
    }
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub Binary {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    my ($self, $in, $out) = @_;
 | 
			
		||||
    $$out = $$in;
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub NBit {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    my ($self, $in, $out) = @_;
 | 
			
		||||
    $$out = $$in;
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub QuotedPrint {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    my ($self, $in, $out) = @_;
 | 
			
		||||
    if ($use_decode_qp) {
 | 
			
		||||
        $$out = MIME::QuotedPrint::decode_qp($$in);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $$out = $$in;
 | 
			
		||||
        $$out =~ s/\r\n/\n/g;      # normalize newlines
 | 
			
		||||
        $$out =~ s/[ \t]+\n/\n/g;  # rule #3 (trailing whitespace must be deleted)
 | 
			
		||||
        $$out =~ s/=\n//g;         # rule #5 (soft line breaks)
 | 
			
		||||
        $$out =~ s/=([\da-fA-F]{2})/chr hex $1/ge;
 | 
			
		||||
    }
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub UU {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    my ($self, $in, $out) = @_;
 | 
			
		||||
    my ($mode, $file);
 | 
			
		||||
 | 
			
		||||
    # Find beginning...
 | 
			
		||||
    while ($$in =~ s/^(.+$CRLF)//o) {
 | 
			
		||||
        local $_ = $1;
 | 
			
		||||
        last if ($mode, $file) = /^begin\s*(\d*)\s*(\S*)/;
 | 
			
		||||
    }
 | 
			
		||||
    return $self->error("uu decoding: no begin found", 'WARN') if (!defined($_));
 | 
			
		||||
 | 
			
		||||
    # Decode:
 | 
			
		||||
    while ($$in =~ s/^(.+$CRLF)//o) {
 | 
			
		||||
        local $_ = $1;
 | 
			
		||||
        last if /^end/;
 | 
			
		||||
        next if /[a-z]/;
 | 
			
		||||
        next unless int((((ord() - 32) & 077) + 2) / 3) == int(length($_) / 4);
 | 
			
		||||
        $$out .= unpack('u', $_);
 | 
			
		||||
    }
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub gt_old_decode_base64 {
 | 
			
		||||
# --------------------------------------------------------------------------
 | 
			
		||||
    my $str = shift;
 | 
			
		||||
    my $res = "";
 | 
			
		||||
 | 
			
		||||
    $str =~ tr|A-Za-z0-9+=/||cd;
 | 
			
		||||
 | 
			
		||||
    $str =~ s/=+$//;
 | 
			
		||||
    $str =~ tr|A-Za-z0-9+/| -_|;
 | 
			
		||||
    return "" unless length $str;
 | 
			
		||||
 | 
			
		||||
    my $uustr = '';
 | 
			
		||||
    my ($i, $l);
 | 
			
		||||
    $l = length($str) - 60;
 | 
			
		||||
    for ($i = 0; $i <= $l; $i += 60) {
 | 
			
		||||
        $uustr .= "M" . substr($str, $i, 60);
 | 
			
		||||
    }
 | 
			
		||||
    $str = substr($str, $i);
 | 
			
		||||
    # and any leftover chars
 | 
			
		||||
    if ($str ne "") {
 | 
			
		||||
        $uustr .= chr(32 + length($str)*3/4) . $str;
 | 
			
		||||
    }
 | 
			
		||||
    return unpack("u", $uustr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
 | 
			
		||||
__END__
 | 
			
		||||
 | 
			
		||||
=head1 NAME
 | 
			
		||||
 | 
			
		||||
GT::Mail::Parse - MIME Parse
 | 
			
		||||
 | 
			
		||||
=head1 SYNOPSIS
 | 
			
		||||
 | 
			
		||||
    use GT::Mail::Parse
 | 
			
		||||
    
 | 
			
		||||
    my $parser = new GT::Mail::Parse (
 | 
			
		||||
        naming  => \&name_files,
 | 
			
		||||
        in_file => '/path/to/file.eml',
 | 
			
		||||
        debug   => 1
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    my $top = $parser->parse or die $GT::Mail::Parse::error;
 | 
			
		||||
 | 
			
		||||
    - or -
 | 
			
		||||
 | 
			
		||||
    my $parser = new GT::Mail::Parse;
 | 
			
		||||
    
 | 
			
		||||
    open FH, '/path/to/file.eml' or die $!;
 | 
			
		||||
    my $top = $parser->parse (
 | 
			
		||||
        naming  => \&name_files,
 | 
			
		||||
        handle  => \*FH,
 | 
			
		||||
        debug   => 1
 | 
			
		||||
    ) or die $GT::Mail::Parse::error;
 | 
			
		||||
    close FH;
 | 
			
		||||
 | 
			
		||||
    - or -
 | 
			
		||||
 | 
			
		||||
    my $parser = new GT::Mail::Parse;
 | 
			
		||||
 | 
			
		||||
    my $top_head = $parser->parse_head (
 | 
			
		||||
        naming  => \&name_files,
 | 
			
		||||
        in_file => '/path/to/file.eml',
 | 
			
		||||
        debug   => 1
 | 
			
		||||
    ) or die $GT::Mail::Parse::error;
 | 
			
		||||
 | 
			
		||||
=head1 DESCRIPTION
 | 
			
		||||
 | 
			
		||||
GT::Mail::Parse is a 100% rfc822 email MIME parser that supports unlimited 
 | 
			
		||||
nested levels of MIME. Emails are parsed into L<GT::Mail::Parts> objects. Each 
 | 
			
		||||
part knows where it's body is and each part contains it's sub parts. See
 | 
			
		||||
L<GT::Mail::Parts> for details on parts methods.
 | 
			
		||||
 | 
			
		||||
=head2 new - Constructor method
 | 
			
		||||
 | 
			
		||||
This is the constructor method to get a GT::Mail::Parse object, which you
 | 
			
		||||
need to access all the methods (there are no Class methods). new() takes
 | 
			
		||||
a hash or hash ref as it's arguments. Each key has an accessor method by the
 | 
			
		||||
same name except debug, which can only be set by passing debug to new(), parse()
 | 
			
		||||
or parse_head().
 | 
			
		||||
 | 
			
		||||
=over 4
 | 
			
		||||
 | 
			
		||||
=item debug
 | 
			
		||||
 | 
			
		||||
Sets the debug level for this insance of the class.
 | 
			
		||||
 | 
			
		||||
=item naming
 | 
			
		||||
 | 
			
		||||
Specify a code reference to use as a naming convention for each part of the 
 | 
			
		||||
email being parsed. This is useful to keep file IO down when you want the emails
 | 
			
		||||
seperated into each part as a file. If this is not specified GT::Mail::Parse
 | 
			
		||||
uses a default naming, which is to start at one and incriment that number for each
 | 
			
		||||
attachment. The attachments would go in the current working directory.
 | 
			
		||||
 | 
			
		||||
=item in_file
 | 
			
		||||
 | 
			
		||||
Specify the path to the file that contains the email to be parsed. One of in_file
 | 
			
		||||
and handle must be specified.
 | 
			
		||||
 | 
			
		||||
=item handle
 | 
			
		||||
 | 
			
		||||
Specify the file handle or IO stream that contains the email to be parsed.
 | 
			
		||||
 | 
			
		||||
=back
 | 
			
		||||
 | 
			
		||||
=item attach_rfc822
 | 
			
		||||
 | 
			
		||||
By default, the parser will decode any embeded emails, and flatten out all the 
 | 
			
		||||
parts. If you prefer to leave embeded emails unparsed, pass in 1 to this option
 | 
			
		||||
and the parser will treat it as an attachment.
 | 
			
		||||
 | 
			
		||||
=back
 | 
			
		||||
 | 
			
		||||
=head2 parse - Parse an email
 | 
			
		||||
 | 
			
		||||
Instance method. Parses the email specified by either in_file or handle. Returns
 | 
			
		||||
the top level L<GT::Mail::Parts> object. Any additional parameters passed in are
 | 
			
		||||
treated the same as if they were passed to the constuctor.
 | 
			
		||||
 | 
			
		||||
=head2 parse_head - Parse just the header of the email
 | 
			
		||||
 | 
			
		||||
Instance method. This method is exactly the same as parse except only the top
 | 
			
		||||
level header is parsed and it's part object returned. This is useful to keep
 | 
			
		||||
overhead down if you only need to know about the header of the email.
 | 
			
		||||
 | 
			
		||||
=head2 size - Get the size
 | 
			
		||||
 | 
			
		||||
Instance method. Returns the total size in bytes of the parsed unencoded email. This 
 | 
			
		||||
method will return undef if no email has been parsed.
 | 
			
		||||
 | 
			
		||||
=head2 all_parts - Get all parts
 | 
			
		||||
 | 
			
		||||
Instance method. Returns all the parts in the parsed email. This is a flatened
 | 
			
		||||
list of the objects. Somewhat similar to what MIME::Tools does. All the parts
 | 
			
		||||
still contain their sub parts.
 | 
			
		||||
 | 
			
		||||
=head1 COPYRIGHT
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
http://www.gossamer-threads.com/
 | 
			
		||||
 | 
			
		||||
=head1 VERSION
 | 
			
		||||
 | 
			
		||||
Revision: $Id: Parse.pm,v 1.79 2004/10/23 02:16:39 brewt Exp $
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1225
									
								
								site/glist/lib/GT/Mail/Parts.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1225
									
								
								site/glist/lib/GT/Mail/Parts.pm
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										481
									
								
								site/glist/lib/GT/Mail/Send.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										481
									
								
								site/glist/lib/GT/Mail/Send.pm
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,481 @@
 | 
			
		||||
# ==================================================================
 | 
			
		||||
# Gossamer Threads Module Library - http://gossamer-threads.com/
 | 
			
		||||
#
 | 
			
		||||
#   GT::Mail::Send
 | 
			
		||||
#   Author  : Scott Beck
 | 
			
		||||
#   CVS Info :                          
 | 
			
		||||
#   $Id: Send.pm,v 1.53 2004/08/23 20:07:44 jagerman Exp $
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
# ==================================================================
 | 
			
		||||
 | 
			
		||||
package GT::Mail::Send;
 | 
			
		||||
 | 
			
		||||
use strict;
 | 
			
		||||
use GT::Base;
 | 
			
		||||
use GT::Socket::Client;
 | 
			
		||||
use GT::Mail::POP3;
 | 
			
		||||
use GT::MD5;
 | 
			
		||||
use vars qw(@ISA $VERSION $DEBUG $ATTRIBS $ERRORS $CRLF %SENDMAIL_ERRORS $HAVE_SSL);
 | 
			
		||||
 | 
			
		||||
%SENDMAIL_ERRORS = (
 | 
			
		||||
    64 => 'EX_USAGE',
 | 
			
		||||
    65 => 'EX_DATAERR',
 | 
			
		||||
    66 => 'EX_NOINPUT',
 | 
			
		||||
    67 => 'EX_NOUSER',
 | 
			
		||||
    68 => 'EX_NOHOST',
 | 
			
		||||
    69 => 'EX_UNAVAILABLE',
 | 
			
		||||
    70 => 'EX_SOFTWARE',
 | 
			
		||||
    71 => 'EX_OSERR',
 | 
			
		||||
    72 => 'EX_OSFILE',
 | 
			
		||||
    73 => 'EX_CANTCREAT',
 | 
			
		||||
    74 => 'EX_IOERR',
 | 
			
		||||
    75 => 'EX_TEMPFAIL',
 | 
			
		||||
    76 => 'EX_PROTOCOL',
 | 
			
		||||
    77 => 'EX_NOPERM',
 | 
			
		||||
    78 => 'EX_CONFIG',
 | 
			
		||||
 | 
			
		||||
# This is for qmail-inject's version of sendmail
 | 
			
		||||
# Nice that they are different..
 | 
			
		||||
    111 => 'EX_TEMPFAIL',
 | 
			
		||||
    100 => 'EX_USAGE',
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
@ISA     = qw/GT::Base/;
 | 
			
		||||
$VERSION = sprintf "%d.%03d", q$Revision: 1.53 $ =~ /(\d+)\.(\d+)/;
 | 
			
		||||
$DEBUG   = 0;
 | 
			
		||||
$ATTRIBS = {
 | 
			
		||||
    mail          => undef,
 | 
			
		||||
    host          => undef,
 | 
			
		||||
    port          => undef,
 | 
			
		||||
    ssl           => undef,
 | 
			
		||||
    from          => undef,
 | 
			
		||||
    path          => undef,
 | 
			
		||||
    flags         => undef,
 | 
			
		||||
    rcpt          => undef,
 | 
			
		||||
    user          => undef,
 | 
			
		||||
    pass          => undef,
 | 
			
		||||
    pbs_user      => undef,
 | 
			
		||||
    pbs_pass      => undef,
 | 
			
		||||
    pbs_host      => undef,
 | 
			
		||||
    pbs_port      => undef,
 | 
			
		||||
    pbs_auth_mode => undef,
 | 
			
		||||
    pbs_ssl       => undef,
 | 
			
		||||
    debug    => 0,
 | 
			
		||||
};
 | 
			
		||||
$ERRORS = {
 | 
			
		||||
    HOSTNOTFOUND     => "SMTP: server '%s' was not found.",
 | 
			
		||||
    CONNFAILED       => "SMTP: connect() failed. reason: %s",
 | 
			
		||||
    SERVNOTAVAIL     => "SMTP: Service not available: %s",
 | 
			
		||||
    SSLNOTAVAIL      => "SMTP: SSL connections are not available: Net::SSLeay 1.06 or greater not installed.",
 | 
			
		||||
    COMMERROR        => "SMTP: Unspecified communications error: '%s'.",
 | 
			
		||||
    USERUNKNOWN      => "SMTP: Local user '%s' unknown on host '%s'. Server said: %s",
 | 
			
		||||
    TRANSFAILED      => "SMTP: Transmission of message failed: %s",
 | 
			
		||||
    AUTHFAILED       => "SMTP: Authentication failed: %s",
 | 
			
		||||
    TOEMPTY          => "No To: field specified.",
 | 
			
		||||
    NOMSG            => "No message body specified",
 | 
			
		||||
    SENDMAILNOTFOUND => "Sendmail was not defined or not found: %s",
 | 
			
		||||
    NOOPTIONS        => "No options were specified. Be sure to pass a hash ref to send()",
 | 
			
		||||
    NOTRANSPORT      => "Neither sendmail nor SMTP were specified!",
 | 
			
		||||
    SENDMAIL         => "There was a problem sending to Sendmail: (%s)",
 | 
			
		||||
    NOMAILOBJ        => "No mail object was specified.",
 | 
			
		||||
    EX_USAGE         => "Command line usage error",
 | 
			
		||||
    EX_DATAERR       => "Data format error",
 | 
			
		||||
    EX_NOINPUT       => "Cannot open input",
 | 
			
		||||
    EX_NOUSER        => "Addressee unknown",
 | 
			
		||||
    EX_NOHOST        => "Host name unknown",
 | 
			
		||||
    EX_UNAVAILABLE   => "Service unavailable",
 | 
			
		||||
    EX_SOFTWARE      => "Internal software error",
 | 
			
		||||
    EX_OSERR         => "System error (e.g., can't fork)",
 | 
			
		||||
    EX_OSFILE        => "Critical OS file missing",
 | 
			
		||||
    EX_CANTCREAT     => "Can't create (user) output file",
 | 
			
		||||
    EX_IOERR         => "Input/output error",
 | 
			
		||||
    EX_TEMPFAIL      => "Temp failure; user is invited to retry",
 | 
			
		||||
    EX_PROTOCOL      => "Remote error in protocol",
 | 
			
		||||
    EX_NOPERM        => "Permission denied",
 | 
			
		||||
    EX_CONFIG        => "Configuration error",
 | 
			
		||||
    EX_UNKNOWN       => "Sendmail exited with an unknown exit status: %s"
 | 
			
		||||
};
 | 
			
		||||
$CRLF = "\015\012";
 | 
			
		||||
 | 
			
		||||
sub init {
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    $self->set(@_);
 | 
			
		||||
 | 
			
		||||
# We need either a host or a path to sendmail and an email object
 | 
			
		||||
    $self->{host} or $self->{path} or return $self->error("NOTRANSPORT", "FATAL");
 | 
			
		||||
    exists $self->{mail}           or return $self->error("NOMAILOBJ", "FATAL");
 | 
			
		||||
 | 
			
		||||
# Set debugging
 | 
			
		||||
    $self->{_debug} = defined($self->{debug}) ? $self->{debug} : $DEBUG;
 | 
			
		||||
 | 
			
		||||
# Default port for smtp
 | 
			
		||||
    if ($self->{host} and !$self->{port}) {
 | 
			
		||||
        $self->{port} = $self->{ssl} ? 465 : 25;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Default flags for sendmail
 | 
			
		||||
    elsif ($self->{path}) {
 | 
			
		||||
        ($self->{flags}) or $self->{flags} = '-t -oi -oeq';
 | 
			
		||||
        $self->{path} =~ /^\s*(.+?)\s*$/ and $self->{path} = $1; # Untaint
 | 
			
		||||
        (-e $self->{path}) or return $self->error('SENDMAILNOTFOUND', 'FATAL', $1);
 | 
			
		||||
    }
 | 
			
		||||
    return $self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub smtp_send {
 | 
			
		||||
# ---------------------------------------------------------------
 | 
			
		||||
# 
 | 
			
		||||
    my ($self, $sock, $cmd) = @_;
 | 
			
		||||
 | 
			
		||||
    if (defined $cmd) {
 | 
			
		||||
        print $sock "$cmd$CRLF";
 | 
			
		||||
        $self->debug("SMTP Log: >> $cmd\n") if $self->{debug} > 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $_ = <$sock>;
 | 
			
		||||
    return if !$_;
 | 
			
		||||
 | 
			
		||||
    my $resp = $_;
 | 
			
		||||
    if (/^\d{3}-/) {
 | 
			
		||||
        while (defined($_ = <$sock>) and /^\d{3}-/) {
 | 
			
		||||
            $resp .= $_;
 | 
			
		||||
        }
 | 
			
		||||
        $resp .= $_;
 | 
			
		||||
    }
 | 
			
		||||
    $resp =~ s/$CRLF/\n/g;
 | 
			
		||||
    $self->debug("SMTP Log: << $resp") if $self->{debug} > 1;
 | 
			
		||||
    return $resp;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub smtp {
 | 
			
		||||
# ---------------------------------------------------------------
 | 
			
		||||
# Opens a smtp port and sends the message headers.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
 | 
			
		||||
    ref $self or $self = $self->new(@_);
 | 
			
		||||
 | 
			
		||||
    if ($self->{ssl}) {
 | 
			
		||||
        $HAVE_SSL ||= eval { require Net::SSLeay; Net::SSLeay->require_version(1.06); 1 };
 | 
			
		||||
        $HAVE_SSL or return $self->error('SSLNOTAVAIL', 'FATAL');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($self->{pbs_host}) {
 | 
			
		||||
        my $pop = GT::Mail::POP3->new(
 | 
			
		||||
            host      => $self->{pbs_host},
 | 
			
		||||
            port      => $self->{pbs_port},
 | 
			
		||||
            user      => $self->{pbs_user},
 | 
			
		||||
            pass      => $self->{pbs_pass},
 | 
			
		||||
            auth_mode => $self->{pbs_auth_mode},
 | 
			
		||||
            ssl       => $self->{pbs_ssl},
 | 
			
		||||
            debug     => $self->{debug}
 | 
			
		||||
        );
 | 
			
		||||
        my $count = $pop->connect();
 | 
			
		||||
        if (!defined($count)) {
 | 
			
		||||
            $self->debug("Couldn't connect to server for POP3 before SMTP authentication: $GT::Mail::POP3::error") if $self->{debug};
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            $pop->quit();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    my $sock = GT::Socket::Client->open(
 | 
			
		||||
        host => $self->{host},
 | 
			
		||||
        port => $self->{port},
 | 
			
		||||
        ssl => $self->{ssl}
 | 
			
		||||
    ) or return $self->error("CONNFAILED", "WARN", GT::Socket::Client->error);
 | 
			
		||||
 | 
			
		||||
    local $SIG{PIPE} = 'IGNORE';
 | 
			
		||||
    local $_;
 | 
			
		||||
 | 
			
		||||
# Get the server's greeting message
 | 
			
		||||
    my $resp = $self->smtp_send($sock) or return $self->error('COMMERROR', 'WARN');
 | 
			
		||||
    return $self->error('SERVNOTAVAIL', 'WARN', $resp) if $resp =~ /^[45]/;
 | 
			
		||||
 | 
			
		||||
    $resp = $self->smtp_send($sock, "EHLO localhost") or return $self->error('COMMERROR', 'WARN');
 | 
			
		||||
    if ($resp =~ /^[45]/) {
 | 
			
		||||
        $resp = $self->smtp_send($sock, "HELO localhost") or return $self->error('COMMERROR', 'WARN');
 | 
			
		||||
        return $self->error('SERVNOTAVAIL', 'WARN', $resp) if $resp =~ /^[45]/;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Authenticate if needed
 | 
			
		||||
    if ($resp =~ /AUTH[ =](.*)/ and $self->{user}) {
 | 
			
		||||
        my $server = uc $1;
 | 
			
		||||
        my $method = '';
 | 
			
		||||
# These are the authentication types that are supported, ordered by preference
 | 
			
		||||
        for my $m (qw/CRAM-MD5 PLAIN LOGIN/) {
 | 
			
		||||
            if ($server =~ /$m/) {
 | 
			
		||||
                $method = $m;
 | 
			
		||||
                last;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if ($method eq 'CRAM-MD5') {
 | 
			
		||||
            $resp = $self->smtp_send($sock, "AUTH CRAM-MD5") or return $self->error('COMMERROR', 'WARN');
 | 
			
		||||
            return $self->error('AUTHFAILED', 'WARN', $resp) if $resp =~ /^[45]/;
 | 
			
		||||
 | 
			
		||||
            my ($challenge) = $resp =~ /\d{3}\s+(.*)/;
 | 
			
		||||
            $challenge = decode_base64($challenge);
 | 
			
		||||
            my $auth = encode_base64("$self->{user} " . hmac_md5_hex($challenge, $self->{pass}));
 | 
			
		||||
 | 
			
		||||
            $resp = $self->smtp_send($sock, $auth) or return $self->error('COMMERROR', 'WARN');
 | 
			
		||||
            return $self->error('AUTHFAILED', 'WARN', $resp) if $resp =~ /^[45]/;
 | 
			
		||||
        }
 | 
			
		||||
        elsif ($method eq 'PLAIN') {
 | 
			
		||||
            my $auth = encode_base64("$self->{user}\0$self->{user}\0$self->{pass}");
 | 
			
		||||
            $resp = $self->smtp_send($sock, "AUTH PLAIN $auth") or return $self->error('COMMERROR', 'WARN');
 | 
			
		||||
            return $self->error('AUTHFAILED', 'WARN', $resp) if $resp =~ /^[45]/;
 | 
			
		||||
        }
 | 
			
		||||
        elsif ($method eq 'LOGIN') {
 | 
			
		||||
            $resp = $self->smtp_send($sock, "AUTH LOGIN") or return $self->error('COMMERROR', 'WARN');
 | 
			
		||||
            return $self->error('AUTHFAILED', 'WARN', $resp) if $resp =~ /^[45]/;
 | 
			
		||||
 | 
			
		||||
            $resp = $self->smtp_send($sock, encode_base64($self->{user})) or return $self->error('COMMERROR', 'WARN');
 | 
			
		||||
            return $self->error('AUTHFAILED', 'WARN', $resp) if $resp =~ /^[45]/;
 | 
			
		||||
 | 
			
		||||
            $resp = $self->smtp_send($sock, encode_base64($self->{pass})) or return $self->error('COMMERROR', 'WARN');
 | 
			
		||||
            return $self->error('AUTHFAILED', 'WARN', $resp) if $resp =~ /^[45]/;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# We use return-path so the email will bounce to who it's from, not the user
 | 
			
		||||
# doing the sending.
 | 
			
		||||
    my $from = $self->{mail}->{head}->get('return-path') || $self->{mail}->{head}->get('from');
 | 
			
		||||
    $from = $self->extract_email($from) || '';
 | 
			
		||||
 | 
			
		||||
    $self->debug("Sending from: <$from>") if $self->{debug} == 1;
 | 
			
		||||
    $resp = $self->smtp_send($sock, "MAIL FROM: <$from>") or return $self->error('COMMERROR', 'WARN');
 | 
			
		||||
    return $self->error('COMMERROR', 'WARN', $resp) if $resp =~ /^[45]/;
 | 
			
		||||
 | 
			
		||||
    my $found_valid = 0;
 | 
			
		||||
    my @tos = ($self->{mail}->{head}->split_field('to'), $self->{mail}->{head}->split_field('bcc'), $self->{mail}->{head}->split_field('cc'));
 | 
			
		||||
    for my $to (@tos) {
 | 
			
		||||
        next unless $to and my $email = $self->extract_email($to);
 | 
			
		||||
 | 
			
		||||
        $found_valid++;
 | 
			
		||||
        $self->debug("Sending RCPT TO: <$email>.") if $self->{debug} == 1;
 | 
			
		||||
        $resp = $self->smtp_send($sock, "RCPT TO: <$email>") or return $self->error('COMMERROR', 'WARN');
 | 
			
		||||
        return $self->error('USERUNKNOWN', 'WARN', $email, $self->{host}, $resp) if $resp =~ /^[45]/;
 | 
			
		||||
    }
 | 
			
		||||
    $found_valid or return $self->error('TOEMPTY', 'FATAL');
 | 
			
		||||
 | 
			
		||||
    $resp = $self->smtp_send($sock, "DATA") or return $self->error('COMMERROR', 'WARN');
 | 
			
		||||
    return $self->error('COMMERROR', 'WARN', $resp) if $resp =~ /^[45]/;
 | 
			
		||||
 | 
			
		||||
# Remove Bcc from the headers.
 | 
			
		||||
    my @bcc = $self->{mail}->{head}->delete('bcc');
 | 
			
		||||
 | 
			
		||||
    my $mail = $self->{mail}->to_string;
 | 
			
		||||
 | 
			
		||||
# SMTP needs any leading .'s to be doubled up.
 | 
			
		||||
    $mail =~ s/^\./../gm;
 | 
			
		||||
 | 
			
		||||
# Print the mail body.
 | 
			
		||||
    $resp = $self->smtp_send($sock, $mail . $CRLF . '.') or return $self->error('COMMERROR', 'WARN');
 | 
			
		||||
    return $self->error('TRANSFAILED', 'WARN', $resp) if $resp =~ /^[45]/;
 | 
			
		||||
 | 
			
		||||
# Add them back in.
 | 
			
		||||
    foreach my $bcc (@bcc) {
 | 
			
		||||
        $self->{mail}->{head}->set('bcc', $bcc);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Close the connection.
 | 
			
		||||
    $resp = $self->smtp_send($sock, "QUIT") or return $self->error('COMMERROR', 'WARN');
 | 
			
		||||
    close $sock;
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub sendmail {
 | 
			
		||||
# ---------------------------------------------------------------
 | 
			
		||||
# Sends a message using sendmail.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
 | 
			
		||||
    ref $self or $self = $self->new(@_);
 | 
			
		||||
 | 
			
		||||
# Get a filehandle, and open pipe to sendmail.
 | 
			
		||||
    my $s = \do{ local *FH; *FH };
 | 
			
		||||
 | 
			
		||||
# If the email address is safe, we set the envelope via -f so bounces are handled properly.
 | 
			
		||||
    my $from = $self->{mail}->{head}->get('return-path') || $self->{mail}->{head}->get('from');
 | 
			
		||||
    my $envelope = '';
 | 
			
		||||
    if ($from =~ /<?([\w\-\.]+\@[\w\-\.]+)>?/) {
 | 
			
		||||
        $envelope = "-f $1";
 | 
			
		||||
    }
 | 
			
		||||
    elsif ($from eq '<>' or $from eq '') {
 | 
			
		||||
        $envelope = "-f ''";
 | 
			
		||||
    }
 | 
			
		||||
    open($s, "|$self->{path} $self->{flags} $envelope 1>&2") or return $self->error("SENDMAIL", "WARN", "$!");
 | 
			
		||||
    $self->{mail}->write($s);
 | 
			
		||||
    return 1 if close $s;
 | 
			
		||||
    my $exit_value  = $? >> 8;
 | 
			
		||||
 | 
			
		||||
    my $code;
 | 
			
		||||
    if (exists $SENDMAIL_ERRORS{$exit_value}) {
 | 
			
		||||
        $code = $SENDMAIL_ERRORS{$exit_value};
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $code = 'EX_UNKNOWN';
 | 
			
		||||
    }
 | 
			
		||||
    if ($code eq 'EX_TEMPFAIL') {
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
    return $self->error($code, "WARN", $exit_value);
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub extract_email {
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
# Takes a field, returns the e-mail address contained in that field, or undef
 | 
			
		||||
# if no e-mail address could be found.
 | 
			
		||||
#
 | 
			
		||||
    shift if @_ > 1 and UNIVERSAL::isa($_[0], __PACKAGE__);
 | 
			
		||||
 | 
			
		||||
    my $to = shift;
 | 
			
		||||
 | 
			
		||||
# We're trying to get down to the actual e-mail address.  To do so, we have to
 | 
			
		||||
# remove quoted strings and comments, then extract the e-mail from whatever is
 | 
			
		||||
# left over.  
 | 
			
		||||
    $to =~ s/"(?:[^"\\]|\\.)*"//g;
 | 
			
		||||
    1 while $to =~ s/\((?:[^()\\]|\\.)*\)//sg;
 | 
			
		||||
 | 
			
		||||
    my ($email) = $to =~ /([^<>\s]+\@[\w.-]+)/;
 | 
			
		||||
 | 
			
		||||
    return $email;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub encode_base64 {
 | 
			
		||||
    my $res = '';
 | 
			
		||||
    pos($_[0]) = 0; # In case something has previously adjusted pos
 | 
			
		||||
    while ($_[0] =~ /(.{1,45})/gs) {
 | 
			
		||||
        $res .= substr(pack(u => $1), 1, -1);
 | 
			
		||||
    }
 | 
			
		||||
    $res =~ tr|` -_|AA-Za-z0-9+/|;
 | 
			
		||||
 | 
			
		||||
    my $padding = (3 - length($_[0]) % 3) % 3;
 | 
			
		||||
    $res =~ s/.{$padding}$/'=' x $padding/e if $padding;
 | 
			
		||||
    $res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub decode_base64 {
 | 
			
		||||
    my $str = shift;
 | 
			
		||||
    my $res = '';
 | 
			
		||||
 | 
			
		||||
    $str =~ tr|A-Za-z0-9+=/||cd;
 | 
			
		||||
 | 
			
		||||
    $str =~ s/=+$//;
 | 
			
		||||
    $str =~ tr|A-Za-z0-9+/| -_|;
 | 
			
		||||
    return '' unless length $str;
 | 
			
		||||
 | 
			
		||||
    my $uustr = '';
 | 
			
		||||
    my ($i, $l);
 | 
			
		||||
    $l = length($str) - 60;
 | 
			
		||||
    for ($i = 0; $i <= $l; $i += 60) {
 | 
			
		||||
        $uustr .= "M" . substr($str, $i, 60);
 | 
			
		||||
    }
 | 
			
		||||
    $str = substr($str, $i);
 | 
			
		||||
    # and any leftover chars
 | 
			
		||||
    if ($str ne "") {
 | 
			
		||||
        $uustr .= chr(32 + length($str) * 3 / 4) . $str;
 | 
			
		||||
    }
 | 
			
		||||
    return unpack("u", $uustr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub hmac_md5_hex {
 | 
			
		||||
    my ($challenge, $data) = @_;
 | 
			
		||||
 | 
			
		||||
    GT::MD5::md5($challenge) if length $challenge > 64;
 | 
			
		||||
 | 
			
		||||
    my $ipad = $data ^ (chr(0x36) x 64);
 | 
			
		||||
    my $opad = $data ^ (chr(0x5c) x 64);
 | 
			
		||||
 | 
			
		||||
    return GT::MD5::md5_hex($opad, GT::MD5::md5($ipad, $challenge));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
 | 
			
		||||
__END__
 | 
			
		||||
 | 
			
		||||
=head1 NAME
 | 
			
		||||
 | 
			
		||||
GT::Mail::Send - Module to send emails
 | 
			
		||||
 | 
			
		||||
=head1 SYNOPSIS
 | 
			
		||||
 | 
			
		||||
    use GT::Mail::Send;
 | 
			
		||||
    
 | 
			
		||||
    # $mail_object must be a GT::Mail object
 | 
			
		||||
    my $send = new GT::Mail::Send (
 | 
			
		||||
        mail  => $mail_object,
 | 
			
		||||
        host  => 'smtp.gossamer-threads.com',
 | 
			
		||||
        debug => 1
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $send->smtp or die $GT::Mail::Send::error;
 | 
			
		||||
 | 
			
		||||
=head1 DESCRIPTION
 | 
			
		||||
 | 
			
		||||
GT::Mail::Send is an object interface to sending email over either
 | 
			
		||||
SMTP or Sendmail. This module is used internally to GT::Mail.
 | 
			
		||||
 | 
			
		||||
=head2 new - Constructor method
 | 
			
		||||
 | 
			
		||||
Returns a new GT::Mail::Send object. You must specify either the smtp host
 | 
			
		||||
or a path to sendmail. This method is inherented from GT::Base. The arguments
 | 
			
		||||
can be in the form of a hash or hash ref.
 | 
			
		||||
 | 
			
		||||
=over 4
 | 
			
		||||
 | 
			
		||||
=item debug
 | 
			
		||||
 | 
			
		||||
Sets the debug level for this instance of GT::Mail::Send.
 | 
			
		||||
 | 
			
		||||
=item mail
 | 
			
		||||
 | 
			
		||||
Specify the mail object to use. This must be a GT::Mail object and must contain
 | 
			
		||||
an email, either passed in or parsed in.
 | 
			
		||||
 | 
			
		||||
=item host
 | 
			
		||||
 | 
			
		||||
Specify the host to use when sending by SMTP.
 | 
			
		||||
 | 
			
		||||
=item port
 | 
			
		||||
 | 
			
		||||
Specify the port to use when sending over SMTP. Defaults to 25.
 | 
			
		||||
 | 
			
		||||
=item path
 | 
			
		||||
 | 
			
		||||
Specify the path to sendmail when sending over sendmail. If the binary passed in
 | 
			
		||||
does not exist, undef will be returned and the error set in GT::Mail::Send::error.
 | 
			
		||||
 | 
			
		||||
=item flags
 | 
			
		||||
 | 
			
		||||
Specify the flags used to call sendmail. Defaults to -t -oi -oeq, see the Sendmail
 | 
			
		||||
guilde for sendmail for more info on the parameters to sendmail.
 | 
			
		||||
 | 
			
		||||
=back
 | 
			
		||||
 | 
			
		||||
=head2 smtp
 | 
			
		||||
 | 
			
		||||
Class or instance method. Sends the passed in email over SMTP. If called as a class
 | 
			
		||||
method, the parameters passed in will be used to call new(). Returns true on error,
 | 
			
		||||
false otherwise.
 | 
			
		||||
 | 
			
		||||
=head2 sendmail
 | 
			
		||||
 | 
			
		||||
Class or instance method. Send the passed in email to sendmail using the specified
 | 
			
		||||
path and flags. If called as a class method all additional arguments are passed to the
 | 
			
		||||
new() method. Returns true on success and false otherwise.
 | 
			
		||||
 | 
			
		||||
=head1 COPYRIGHT
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
http://www.gossamer-threads.com/
 | 
			
		||||
 | 
			
		||||
=head1 VERSION
 | 
			
		||||
 | 
			
		||||
Revision: $Id: Send.pm,v 1.53 2004/08/23 20:07:44 jagerman Exp $
 | 
			
		||||
 | 
			
		||||
=cut
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user