-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
#
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
#
-# (Except where explictly superceded by other copyright notices)
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
#
# This work is made available to you under the terms of Version 2 of
# the GNU General Public License. A copy of that license should have
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
#
-# END LICENSE BLOCK
+# END BPS TAGGED BLOCK }}}
=head1 SYNOPSIS
use RT::Attachment;
=cut
+
+package RT::Attachment;
+
use strict;
no warnings qw(redefine);
use MIME::Base64;
use MIME::QuotedPrint;
-# {{{ sub _ClassAccessible
-sub _ClassAccessible {
+
+# {{{ sub _OverlayAccessible
+sub _OverlayAccessible {
{
- TransactionId => { 'read'=>1, 'public'=>1, },
- MessageId => { 'read'=>1, },
- Parent => { 'read'=>1, },
- ContentType => { 'read'=>1, },
- Subject => { 'read'=>1, },
- Content => { 'read'=>1, },
- ContentEncoding => { 'read'=>1, },
- Headers => { 'read'=>1, },
- Filename => { 'read'=>1, },
+ TransactionId => { 'read'=>1, 'public'=>1, 'write' => 0 },
+ MessageId => { 'read'=>1, 'write' => 0 },
+ Parent => { 'read'=>1, 'write' => 0 },
+ ContentType => { 'read'=>1, 'write' => 0 },
+ Subject => { 'read'=>1, 'write' => 0 },
+ Content => { 'read'=>1, 'write' => 0 },
+ ContentEncoding => { 'read'=>1, 'write' => 0 },
+ Headers => { 'read'=>1, 'write' => 0 },
+ Filename => { 'read'=>1, 'write' => 0 },
Creator => { 'read'=>1, 'auto'=>1, },
Created => { 'read'=>1, 'auto'=>1, },
};
$self->{_TransactionObj}=RT::Transaction->new($self->CurrentUser);
$self->{_TransactionObj}->Load($self->TransactionId);
}
+ unless ($self->{_TransactionObj}->Id) {
+ $RT::Logger->crit("Attachment ".$self->id." can't find transaction ".$self->TransactionId." which it is ostensibly part of. That's bad");
+ }
return $self->{_TransactionObj};
}
Create a new attachment. Takes a paramhash:
'Attachment' Should be a single MIME body with optional subparts
- 'Parent' is an optional Parent RT::Attachment object
- 'TransactionId' is the mandatory id of the Transaction this attachment is associated with.;
+ 'Parent' is an optional id of the parent attachment
+ 'TransactionId' is the mandatory id of the transaction this attachment is associated with.;
=cut
sub Create {
my $self = shift;
- my ($id);
my %args = ( id => 0,
TransactionId => 0,
Parent => 0,
#For ease of reference
my $Attachment = $args{'Attachment'};
- #if we didn't specify a ticket, we need to bail
- if ( $args{'TransactionId'} == 0 ) {
+ #if we didn't specify a ticket, we need to bail
+ if ( $args{'TransactionId'} == 0 ) {
$RT::Logger->crit( "RT::Attachment->Create couldn't, as you didn't specify a transaction\n" );
return (0);
defined($Subject) or $Subject = '';
chomp($Subject);
+ #Get the Message-ID
+ my $MessageId = $Attachment->head->get( 'Message-ID', 0 );
+ defined($MessageId) or $MessageId = '';
+ chomp ($MessageId);
+ $MessageId =~ s/^<(.*)>$/$1/go;
+
+
#Get the filename
- my $Filename = $Attachment->head->recommended_filename || eval {
- ${ $Attachment->head->{mail_hdr_hash}{'Content-Disposition'}[0] }
- =~ /^.*\bfilename="(.*)"$/ ? $1 : ''
- };
+ my $Filename = $Attachment->head->recommended_filename;
# If a message has no bodyhandle, that means that it has subparts (or appears to)
# and we should act accordingly.
unless ( defined $Attachment->bodyhandle ) {
- $id = $self->SUPER::Create(
+
+ my $id = $self->SUPER::Create(
TransactionId => $args{'TransactionId'},
Parent => 0,
ContentType => $Attachment->mime_type,
- Headers => $Attachment->head->as_string,
- Subject => $Subject);
+ Headers => $Attachment->head->as_string,
+ MessageId => $MessageId,
+ Subject => $Subject
+ );
+
+ unless ($id) {
+ $RT::Logger->crit("Attachment insert failed - ".$RT::Handle->dbh->errstr);
+ }
foreach my $part ( $Attachment->parts ) {
my $SubAttachment = new RT::Attachment( $self->CurrentUser );
- $SubAttachment->Create(
+ my ($id) = $SubAttachment->Create(
TransactionId => $args{'TransactionId'},
Parent => $id,
Attachment => $part,
- ContentType => $Attachment->mime_type,
- Headers => $Attachment->head->as_string(),
-
);
+ unless ($id) {
+ $RT::Logger->crit("Attachment insert failed - ".$RT::Handle->dbh->errstr);
+ }
}
return ($id);
}
#If it's not multipart
else {
- my $ContentEncoding = 'none';
-
- my $Body = $Attachment->bodyhandle->as_string;
-
- #get the max attachment length from RT
- my $MaxSize = $RT::MaxAttachmentSize;
-
- #if the current attachment contains nulls and the
- #database doesn't support embedded nulls
-
- if ( $RT::AlwaysUseBase64 or
- ( !$RT::Handle->BinarySafeBLOBs ) && ( $Body =~ /\x00/ ) ) {
-
- # set a flag telling us to mimencode the attachment
- $ContentEncoding = 'base64';
-
- #cut the max attchment size by 25% (for mime-encoding overhead.
- $RT::Logger->debug("Max size is $MaxSize\n");
- $MaxSize = $MaxSize * 3 / 4;
- # Some databases (postgres) can't handle non-utf8 data
- } elsif ( !$RT::Handle->BinarySafeBLOBs
- && $Attachment->mime_type !~ /text\/plain/gi
- && !Encode::is_utf8( $Body, 1 ) ) {
- $ContentEncoding = 'quoted-printable';
- }
-
- #if the attachment is larger than the maximum size
- if ( ($MaxSize) and ( $MaxSize < length($Body) ) ) {
-
- # if we're supposed to truncate large attachments
- if ($RT::TruncateLongAttachments) {
-
- # truncate the attachment to that length.
- $Body = substr( $Body, 0, $MaxSize );
-
- }
-
- # elsif we're supposed to drop large attachments on the floor,
- elsif ($RT::DropLongAttachments) {
-
- # drop the attachment on the floor
- $RT::Logger->info( "$self: Dropped an attachment of size " . length($Body) . "\n" . "It started: " . substr( $Body, 0, 60 ) . "\n" );
- return (undef);
- }
- }
-
- # if we need to mimencode the attachment
- if ( $ContentEncoding eq 'base64' ) {
-
- # base64 encode the attachment
- Encode::_utf8_off($Body);
- $Body = MIME::Base64::encode_base64($Body);
-
- } elsif ($ContentEncoding eq 'quoted-printable') {
- Encode::_utf8_off($Body);
- $Body = MIME::QuotedPrint::encode($Body);
+ my ($ContentEncoding, $Body) = $self->_EncodeLOB( $Attachment->bodyhandle->as_string,
+ $Attachment->mime_type
+ );
+ my $id = $self->SUPER::Create(
+ TransactionId => $args{'TransactionId'},
+ ContentType => $Attachment->mime_type,
+ ContentEncoding => $ContentEncoding,
+ Parent => $args{'Parent'},
+ Headers => $Attachment->head->as_string,
+ Subject => $Subject,
+ Content => $Body,
+ Filename => $Filename,
+ MessageId => $MessageId,
+ );
+ unless ($id) {
+ $RT::Logger->crit("Attachment insert failed - ".$RT::Handle->dbh->errstr);
}
-
- my $id = $self->SUPER::Create( TransactionId => $args{'TransactionId'},
- ContentType => $Attachment->mime_type,
- ContentEncoding => $ContentEncoding,
- Parent => $args{'Parent'},
- Headers => $Attachment->head->as_string,
- Subject => $Subject,
- Content => $Body,
- Filename => $Filename, );
return ($id);
}
}
my %args = ( ContentEncoding => 'none',
@_ );
- return($self->SUPER::Create(@_));
+
+
+ ($args{'ContentEncoding'}, $args{'Content'}) = $self->_EncodeLOB($args{'Content'}, $args{'MimeType'});
+
+ return($self->SUPER::Create(%args));
}
# {{{ sub Content
sub Content {
my $self = shift;
- my $decode_utf8 = (($self->ContentType eq 'text/plain') ? 1 : 0);
-
- if ( $self->ContentEncoding eq 'none' || ! $self->ContentEncoding ) {
- return $self->_Value(
- 'Content',
- decode_utf8 => $decode_utf8,
- );
- } elsif ( $self->ContentEncoding eq 'base64' ) {
- return ( $decode_utf8
- ? Encode::decode_utf8(MIME::Base64::decode_base64($self->_Value('Content')))
- : MIME::Base64::decode_base64($self->_Value('Content'))
- );
- } elsif ( $self->ContentEncoding eq 'quoted-printable' ) {
- return ( $decode_utf8
- ? Encode::decode_utf8(MIME::QuotedPrint::decode($self->_Value('Content')))
- : MIME::QuotedPrint::decode($self->_Value('Content'))
- );
- } else {
- return( $self->loc("Unknown ContentEncoding [_1]", $self->ContentEncoding));
- }
+ $self->_DecodeLOB($self->ContentType, $self->ContentEncoding, $self->_Value('Content', decode_utf8 => 0));
}
=head2 OriginalContent
Returns the attachment's content as octets before RT's mangling.
-Currently, this just means restoring text/plain content back to its
+Currently, this just means restoring text content back to its
original encoding.
=cut
sub OriginalContent {
my $self = shift;
- return $self->Content unless $self->ContentType eq 'text/plain';
+ return $self->Content unless RT::I18N::IsTextualContentType($self->ContentType);
+
my $enc = $self->OriginalEncoding;
my $content;
} elsif ( $self->ContentEncoding eq 'base64' ) {
$content = MIME::Base64::decode_base64($self->_Value('Content', decode_utf8 => 0));
} elsif ( $self->ContentEncoding eq 'quoted-printable' ) {
- return MIME::QuotedPrint::decode($self->_Value('Content', decode_utf8 => 0));
+ $content = MIME::QuotedPrint::decode($self->_Value('Content', decode_utf8 => 0));
} else {
return( $self->loc("Unknown ContentEncoding [_1]", $self->ContentEncoding));
}
- # Encode::_utf8_on($content);
+ # Turn *off* the SvUTF8 bits here so decode_utf8 and from_to below can work.
+ local $@;
+ Encode::_utf8_off($content);
+
if (!$enc || $enc eq '' || $enc eq 'utf8' || $enc eq 'utf-8') {
# If we somehow fail to do the decode, at least push out the raw bits
eval {return( Encode::decode_utf8($content))} || return ($content);
}
- eval { Encode::from_to($content, 'utf8' => $enc);};
+ eval { Encode::from_to($content, 'utf8' => $enc) } if $enc;
if ($@) {
$RT::Logger->error("Could not convert attachment from assumed utf8 to '$enc' :".$@);
}
# TODO: Handle Multipart/Mixed (eventually fix the link in the
# ShowHistory web template?)
- if ($self->ContentType =~ m{^(text/plain|message)}i) {
+ if (RT::I18N::IsTextualContentType($self->ContentType)) {
$body=$self->Content;
# Do we need any preformatting (wrapping, that is) of the message?
=head2 NiceHeaders
-Returns the To, From, Cc, Date and Subject headers.
-
-It is a known issue that this breaks if any of these headers are not
-properly unfolded.
+Returns a multi-line string of the To, From, Cc, Date and Subject headers.
=cut
sub NiceHeaders {
my $self = shift;
my $hdrs = "";
- my @hdrs = split(/\n/,$self->Headers);
+ my @hdrs = $self->_SplitHeaders;
while (my $str = shift @hdrs) {
- next unless $str =~ /^(To|From|RT-Send-Cc|Cc|Date|Subject): /i;
+ next unless $str =~ /^(To|From|RT-Send-Cc|Cc|Bcc|Date|Subject):/i;
$hdrs .= $str . "\n";
$hdrs .= shift( @hdrs ) . "\n" while ($hdrs[0] =~ /^[ \t]+/);
}
sub Headers {
my $self = shift;
my $hdrs="";
- for ($self->_SplitHeaders) {
- $hdrs.="$_\n" unless /^(RT-Send-Bcc):/i
- }
- return $hdrs;
+ my @headers = grep { !/^RT-Send-Bcc/i } $self->_SplitHeaders;
+ return join("\n",@headers);
+
}
=cut
-sub _Value {
+sub _Value {
- my $self = shift;
+ my $self = shift;
my $field = shift;
-
-
+
#if the field is public, return it.
- if ($self->_Accessible($field, 'public')) {
- #$RT::Logger->debug("Skipping ACL check for $field\n");
- return($self->__Value($field, @_));
-
+ if ( $self->_Accessible( $field, 'public' ) ) {
+ return ( $self->__Value( $field, @_ ) );
}
-
+
#If it's a comment, we need to be extra special careful
- elsif ( (($self->TransactionObj->CurrentUserHasRight('ShowTicketComments')) and
- ($self->TransactionObj->Type eq 'Comment') ) or
- ($self->TransactionObj->CurrentUserHasRight('ShowTicket'))) {
- return($self->__Value($field, @_));
+ elsif ( $self->TransactionObj->Type =~ /^Comment/ ) {
+ if ( $self->TransactionObj->CurrentUserHasRight('ShowTicketComments') )
+ {
+ return ( $self->__Value( $field, @_ ) );
+ }
}
+ elsif ( $self->TransactionObj->CurrentUserHasRight('ShowTicket') ) {
+ return ( $self->__Value( $field, @_ ) );
+ }
+
#if they ain't got rights to see, don't let em
else {
- return(undef);
- }
-
-
+ return (undef);
+ }
+
}
# }}}
=head2 _SplitHeaders
Returns an array of this attachment object's headers, with one header
-per array entry. multiple lines are folded
+per array entry. multiple lines are folded.
=begin testing