如何通过SSL下载IMAP邮件附件并使用Perl在本地保存?

时间:2010-03-16 10:29:22

标签: perl email ssl imap attachment

我需要有关如何从我的IMAP邮件中下载附件的建议,这些邮件包含主题行的附件和当前日期,即YYYYMMDD格式,并将附件保存到本地路径。

我浏览了Perl模块Mail::IMAPClient并能够连接到IMAP邮件服务器,但需要其他任务的帮助。还有一点需要注意,我的IMAP服务器需要SSL身份验证。

附件也可以是gz,tar或tar.gz文件。

4 个答案:

答案 0 :(得分:5)

一个简单的程序可以满足您的需求。

#! /usr/bin/perl

use warnings;
use strict;

Email::MIME的最低版本适用于引入walk_parts的时间。

use Email::MIME 1.901;
use IO::Socket::SSL;
use Mail::IMAPClient;
use POSIX qw/ strftime /;
use Term::ReadKey;

您不想在程序中对密码进行硬编码,是吗?

sub read_password {
  local $| = 1;
  print "Enter password: ";

  ReadMode "noecho";
  my $password = <STDIN>;
  ReadMode "restore";

  die "$0: unexpected end of input"
    unless defined $password;

  print "\n";
  chomp $password; 
  $password;
}

使用SSL连接。我们应该能够使用构造函数的简单Ssl参数来完成此操作,但是有些供应商选择在它们的包中打破它。

my $pw = read_password;
my $imap = Mail::IMAPClient->new(
 #Debug    => 1,
  User     => "you\@domain.com",
  Password => $pw,
  Uid      => 1,
  Peek     => 1,  # don't set \Seen flag
  Socket   => IO::Socket::SSL->new(
                Proto    => 'tcp',
                PeerAddr => 'imap.domain.com',
                PeerPort => 993,
              ),
);

die "$0: connect: $@" if defined $@;

如果您想要收件箱以外的其他文件夹,请进行更改。

$imap->select("INBOX")
  or die "$0: select INBOX: ", $imap->LastError, "\n";

使用IMAP搜索,我们会查找主题包含YYYYMMDD格式的今天日期的所有邮件。日期可以是主题中的任何位置,因此,例如,“foo bar baz 20100316”的主题今天将匹配。

my $today = strftime "%Y%m%d", localtime $^T;
my @messages = $imap->search(SUBJECT => $today);
die "$0: search: $@" if defined $@;

对于每条此类消息,请将其附件写入当前目录中的文件。我们编写最外层的附件,不要挖掘嵌套附件。在其内容类型中具有name参数的部分(如在image/jpeg; name="foo.jpg"中)被假定为附件,并且我们忽略所有其他部分。已保存附件的名称是由-分隔的以下组件:今天的日期,其IMAP消息ID,其在消息中的位置的从一开始的索引及其名称。

foreach my $id (@messages) {
  die "$0: funky ID ($id)" unless $id =~ /\A\d+\z/;

  my $str = $imap->message_string($id)
    or die "$0: message_string: $@";

  my $n = 1;
  Email::MIME->new($str)->walk_parts(sub {
    my($part) = @_;
    return unless ($part->content_type =~ /\bname=([^"]+)/ 
                or $part->content_type =~ /\bname="([^"]+)"/); # " grr...

    my $name = "./$today-$id-" . $n++ . "-$1";
    print "$0: writing $name...\n";
    open my $fh, ">", $name
      or die "$0: open $name: $!";
    print $fh $part->content_type =~ m!^text/!
                ? $part->body_str
                : $part->body
      or die "$0: print $name: $!";
    close $fh
      or warn "$0: close $name: $!";
  });
}

答案 1 :(得分:3)

如果您想坚持使用Mail::IMAPClient,可以告诉use SSL

或者,Net::IMAP::Simple::SSL也可以帮助您。界面与Net::IMAP::Simple提供的界面相同。

收到消息后,Parsing emails with attachments会显示如何提取附件。我没有尝试过,但我的预感是使用Email::MIME::walk_parts可以用来显着简化PerlMonks文章中显示的脚本。

答案 2 :(得分:1)

我已经改变了一点从@Greg下载附件的方法,因为它显示下载SAP XML附件不可靠。他们没有遵循Content-Type: application/pdf; name=XXXXX标准,所以它给了我很多问题。例如:

Content-ID: <payload-xxxxxxxxxxxxx@sap.com>
Content-Disposition: attachment;
    filename="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.xml"
Content-Type: application/xml
Content-Descripton: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.xml

该计划的其余部分几乎保持不变。不同之处在于,我现在正在使用MIME::Parser来检索所有消息,并且我扔掉了与身体和图像相关的所有内容。我还删除了Peek => 1,因为我想在下载后将消息标记为已读(并且只导航未读消息)。 Log::Logger帮助创建了一个集中日志:

--- Snippet 1 --- Libs

#! /usr/bin/perl
use warnings;
use strict;
use Mail::IMAPClient; #IMAP connection
use Log::Logger; #Logging facility
use MIME::Parser; #Mime "slicer"
use DateTime; #Date
use File::Copy; #File manipulation
use File::Path qw( mkpath );

--- Snippet 2 ---日志初始化

$log_script = new Log::Logger;
$log_script->open_append("/var/log/downloader.log");
my $dt = DateTime->now;
$dt->set_time_zone('America/Sao_Paulo');
$hour = (join ' ', $dt->ymd, $dt->hms);

--- Snippet 3 ---邮件下载

$imap->select($remote_dir) or ($log_script->log("$hour: Account $account, Dir $remote_dir. Check if this folder exists") and next);
# Select unseen messages only
my @mails = ($imap->unseen);
foreach my $id (@mails) {
  my $subject = $imap->subject($id);
  my $str = $imap->message_string($id) or ($log_script->log("$hour: Account $account, Email \<$subject\> with problems. Crawling through next email") and next);
  my $parser = MIME::Parser->new();
  $parser->output_dir( $temp_dir );
  $parser->parse_data( $str );
  opendir(DIR, $temp_dir);
  foreach $file (readdir(DIR)) {
    next unless (-f "$temp_dir/$file");
    if ("$file" =~ /^msg/i){ # ignores body
      $body .= "$file ";
      unlink "$temp_dir/$file";
    } elsif (("$file" =~ /jpg$/i) # ignores signature images
          or ("$file" =~ /gif$/i)
          or ("$file" =~ /png$/i)) {
      $body .= "$file ";
      unlink "$temp_dir/$file";
    } else { # move attachments to destination dir
      $log_script->log("$hour: Account: $account, File $file, Email \<$subject\>, saved $local_dir");
      move "$temp_dir/$file", "$local_dir";
    };
 };
  $log_script->log("$hour: Files from email \<$subject\> ignored as they are body related stuff: $body") if $body;

答案 3 :(得分:1)

我更喜欢Greg概述的Mail::IMAPClient方法,但 binmode()输出文件句柄必不可少,即阻止Windows假设0x0A字节为换行符并替换为CRLF等二进制文件无效。 我很抱歉将此视为答案,评论是合适的,但我现在还没有任何声誉。