#
# COPYRIGHT:
#
-# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
# <sales@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
# If we possibly can, collapse it to a singlepart
$Attachment->make_singlepart;
+ my $head = $Attachment->head;
+
# Get the subject
- my $Subject = $Attachment->head->get( 'subject', 0 );
+ my $Subject = $head->get( 'subject', 0 );
$Subject = '' unless defined $Subject;
chomp $Subject;
utf8::decode( $Subject ) unless utf8::is_utf8( $Subject );
#Get the Message-ID
- my $MessageId = $Attachment->head->get( 'Message-ID', 0 );
+ my $MessageId = $head->get( 'Message-ID', 0 );
defined($MessageId) or $MessageId = '';
chomp ($MessageId);
$MessageId =~ s/^<(.*?)>$/$1/o;
#Get the filename
-
my $Filename = mime_recommended_filename($Attachment);
# remove path part.
$Filename =~ s!.*/!! if $Filename;
+ my $content;
+ unless ( $head->get('Content-Length') ) {
+ my $length = 0;
+ if ( defined $Attachment->bodyhandle ) {
+ $content = $Attachment->bodyhandle->as_string;
+ utf8::encode( $content ) if utf8::is_utf8( $content );
+ $length = length $content;
+ }
+ $head->replace( 'Content-Length' => $length );
+ }
+ $head = $head->as_string;
+
# MIME::Head doesn't support perl strings well and can return
# octets which later will be double encoded in low-level code
- my $head = $Attachment->head->as_string;
utf8::decode( $head ) unless utf8::is_utf8( $head );
# If a message has no bodyhandle, that means that it has subparts (or appears to)
unless ($id) {
$RT::Logger->crit("Attachment insert failed - ". $RT::Handle->dbh->errstr);
+ return ($id);
}
foreach my $part ( $Attachment->parts ) {
);
unless ($id) {
$RT::Logger->crit("Attachment insert failed: ". $RT::Handle->dbh->errstr);
+ return ($id);
}
}
return ($id);
#If it's not multipart
else {
- my ($ContentEncoding, $Body, $ContentType, $Filename) = $self->_EncodeLOB(
+ my ($encoding, $type);
+ ($encoding, $content, $type, $Filename) = $self->_EncodeLOB(
$Attachment->bodyhandle->as_string,
$Attachment->mime_type,
$Filename
my $id = $self->SUPER::Create(
TransactionId => $args{'TransactionId'},
- ContentType => $ContentType,
- ContentEncoding => $ContentEncoding,
+ ContentType => $type,
+ ContentEncoding => $encoding,
Parent => $args{'Parent'},
Headers => $head,
Subject => $Subject,
- Content => $Body,
+ Content => $content,
Filename => $Filename,
MessageId => $MessageId,
);
}
}
-=head2 Import
-
-Create an attachment exactly as specified in the named parameters.
-
-=cut
-
-sub Import {
- my $self = shift;
- my %args = ( ContentEncoding => 'none', @_ );
-
- ( $args{'ContentEncoding'}, $args{'Content'} ) =
- $self->_EncodeLOB( $args{'Content'}, $args{'MimeType'} );
-
- return ( $self->SUPER::Create(%args) );
-}
-
=head2 TransactionObj
Returns the transaction object asscoiated with this attachment.
return $parent;
}
+=head2 Closest
+
+Takes a MIME type as a string or regex. Returns an L<RT::Attachment> object
+for the nearest containing part with a matching L</ContentType>. Strings must
+match exactly and all matches are done case insensitively. Strings ending in a
+C</> must only match the first part of the MIME type. For example:
+
+ # Find the nearest multipart/* container
+ my $container = $attachment->Closest("multipart/");
+
+Returns undef if no such object is found.
+
+=cut
+
+sub Closest {
+ my $self = shift;
+ my $type = shift;
+ my $part = $self->ParentObj or return undef;
+
+ $type = qr/^\Q$type\E$/
+ unless ref $type eq "REGEX";
+
+ while (lc($part->ContentType) !~ $type) {
+ $part = $part->ParentObj or last;
+ }
+
+ return ($part and $part->id) ? $part : undef;
+}
+
=head2 Children
Returns an L<RT::Attachments> object which is preloaded with
return($kids);
}
+=head2 Siblings
+
+Returns an L<RT::Attachments> object containing all the attachments sharing
+the same immediate parent as the current object, excluding the current
+attachment itself.
+
+If the current attachment is a top-level part (i.e. Parent == 0) then a
+guaranteed empty L<RT::Attachments> object is returned.
+
+=cut
+
+sub Siblings {
+ my $self = shift;
+ my $siblings = RT::Attachments->new( $self->CurrentUser );
+ if ($self->Parent) {
+ $siblings->ChildrenOf( $self->Parent );
+ $siblings->Limit( FIELD => 'id', OPERATOR => '!=', VALUE => $self->Id );
+ } else {
+ # Ensure emptiness
+ $siblings->Limit( SUBCLAUSE => 'empty', FIELD => 'id', VALUE => 0 );
+ }
+ return $siblings;
+}
+
=head2 Content
Returns the attachment's content. if it's base64 encoded, decode it
return $len;
}
-=head2 Quote
-
-=cut
+=head2 FriendlyContentLength
-sub Quote {
- my $self=shift;
- my %args=(Reply=>undef, # Prefilled reply (i.e. from the KB/FAQ system)
- @_);
+Returns L</ContentLength> in bytes, kilobytes, or megabytes as most
+appropriate. The size is suffixed with C<M>, C<k>, and C<b> and the returned
+string is localized.
- my ($quoted_content, $body, $headers);
- my $max=0;
+Returns the empty string if the L</ContentLength> is 0 or undefined.
- # TODO: Handle Multipart/Mixed (eventually fix the link in the
- # ShowHistory web template?)
- if (RT::I18N::IsTextualContentType($self->ContentType)) {
- $body=$self->Content;
-
- # Do we need any preformatting (wrapping, that is) of the message?
-
- # Remove quoted signature.
- $body =~ s/\n-- \n(.*)$//s;
-
- # What's the longest line like?
- foreach (split (/\n/,$body)) {
- $max=length if ( length > $max);
- }
-
- if ($max>76) {
- require Text::Wrapper;
- my $wrapper = Text::Wrapper->new
- (
- columns => 70,
- body_start => ($max > 70*3 ? ' ' : ''),
- par_start => ''
- );
- $body=$wrapper->wrap($body);
- }
-
- $body =~ s/^/> /gm;
+=cut
- $body = '[' . $self->TransactionObj->CreatorObj->Name() . ' - ' . $self->TransactionObj->CreatedAsString()
- . "]:\n\n"
- . $body . "\n\n";
+sub FriendlyContentLength {
+ my $self = shift;
+ my $size = $self->ContentLength;
+ return '' unless $size;
- } else {
- $body = "[Non-text message not quoted]\n\n";
+ my $res = '';
+ if ( $size > 1024*1024 ) {
+ $res = $self->loc( "[_1]M", int( $size / 1024 / 102.4 ) / 10 );
}
-
- $max=60 if $max<60;
- $max=70 if $max>78;
- $max+=2;
-
- return (\$body, $max);
+ elsif ( $size > 1024 ) {
+ $res = $self->loc( "[_1]k", int( $size / 102.4 ) / 10 );
+ }
+ else {
+ $res = $self->loc( "[_1]b", $size );
+ }
+ return $res;
}
=head2 ContentAsMIME [Children => 1]
my $hdrs = "";
my @hdrs = $self->_SplitHeaders;
while (my $str = shift @hdrs) {
- next unless $str =~ /^(To|From|RT-Send-Cc|Cc|Bcc|Date|Subject):/i;
- $hdrs .= $str . "\n";
- $hdrs .= shift( @hdrs ) . "\n" while ($hdrs[0] =~ /^[ \t]+/);
+ next unless $str =~ /^(To|From|RT-Send-Cc|Cc|Bcc|Date|Subject):/i;
+ $hdrs .= $str . "\n";
+ $hdrs .= shift( @hdrs ) . "\n" while ($hdrs[0] =~ /^[ \t]+/);
}
return $hdrs;
}
return (0, $self->loc('Permission Denied')) unless $txn->CurrentUserCanSee;
return (0, $self->loc('Permission Denied'))
unless $txn->TicketObj->CurrentUserHasRight('ModifyTicket');
- return (0, $self->loc('GnuPG integration is disabled'))
- unless RT->Config->Get('GnuPG')->{'Enable'};
+ return (0, $self->loc('Cryptography is disabled'))
+ unless RT->Config->Get('Crypt')->{'Enable'};
return (0, $self->loc('Attachments encryption is disabled'))
- unless RT->Config->Get('GnuPG')->{'AllowEncryptDataInDB'};
-
- require RT::Crypt::GnuPG;
+ unless RT->Config->Get('Crypt')->{'AllowEncryptDataInDB'};
my $type = $self->ContentType;
- if ( $type =~ /^x-application-rt\/gpg-encrypted/i ) {
+ if ( $type =~ /^x-application-rt\/[^-]+-encrypted/i ) {
return (1, $self->loc('Already encrypted'));
} elsif ( $type =~ /^multipart\//i ) {
return (1, $self->loc('No need to encrypt'));
- } else {
- $type = qq{x-application-rt\/gpg-encrypted; original-type="$type"};
}
my $queue = $txn->TicketObj->QueueObj;
RT->Config->Get('CorrespondAddress'),
RT->Config->Get('CommentAddress'),
) {
- my %res = RT::Crypt::GnuPG::GetKeysInfo( $address, 'private' );
+ my %res = RT::Crypt->GetKeysInfo( Key => $address, Type => 'private' );
next if $res{'exit_code'} || !$res{'info'};
- %res = RT::Crypt::GnuPG::GetKeysForEncryption( $address );
+ %res = RT::Crypt->GetKeysForEncryption( $address );
next if $res{'exit_code'} || !$res{'info'};
$encrypt_for = $address;
}
return (0, $self->loc('No key suitable for encryption'));
}
- $self->__Set( Field => 'ContentType', Value => $type );
- $self->SetHeader( 'Content-Type' => $type );
-
my $content = $self->Content;
- my %res = RT::Crypt::GnuPG::SignEncryptContent(
+ my %res = RT::Crypt->SignEncryptContent(
Content => \$content,
Sign => 0,
Encrypt => 1,
Recipients => [ $encrypt_for ],
);
if ( $res{'exit_code'} ) {
- return (0, $self->loc('GnuPG error. Contact with administrator'));
+ return (0, $self->loc('Encryption error; contact the administrator'));
}
my ($status, $msg) = $self->__Set( Field => 'Content', Value => $content );
unless ( $status ) {
return ($status, $self->loc("Couldn't replace content with encrypted data: [_1]", $msg));
}
+
+ $type = qq{x-application-rt\/$res{'Protocol'}-encrypted; original-type="$type"};
+ $self->__Set( Field => 'ContentType', Value => $type );
+ $self->SetHeader( 'Content-Type' => $type );
+
return (1, $self->loc('Successfuly encrypted data'));
}
return (0, $self->loc('Permission Denied')) unless $txn->CurrentUserCanSee;
return (0, $self->loc('Permission Denied'))
unless $txn->TicketObj->CurrentUserHasRight('ModifyTicket');
- return (0, $self->loc('GnuPG integration is disabled'))
- unless RT->Config->Get('GnuPG')->{'Enable'};
-
- require RT::Crypt::GnuPG;
+ return (0, $self->loc('Cryptography is disabled'))
+ unless RT->Config->Get('Crypt')->{'Enable'};
my $type = $self->ContentType;
- if ( $type =~ /^x-application-rt\/gpg-encrypted/i ) {
+ my $protocol;
+ if ( $type =~ /^x-application-rt\/([^-]+)-encrypted/i ) {
+ $protocol = $1;
+ $protocol =~ s/gpg/gnupg/; # backwards compatibility
($type) = ($type =~ /original-type="(.*)"/i);
$type ||= 'application/octet-stream';
} else {
return (1, $self->loc('Is not encrypted'));
}
- $self->__Set( Field => 'ContentType', Value => $type );
- $self->SetHeader( 'Content-Type' => $type );
+
+ my $queue = $txn->TicketObj->QueueObj;
+ my @addresses =
+ $queue->CorrespondAddress,
+ $queue->CommentAddress,
+ RT->Config->Get('CorrespondAddress'),
+ RT->Config->Get('CommentAddress')
+ ;
my $content = $self->Content;
- my %res = RT::Crypt::GnuPG::DecryptContent( Content => \$content, );
+ my %res = RT::Crypt->DecryptContent(
+ Protocol => $protocol,
+ Content => \$content,
+ Recipients => \@addresses,
+ );
if ( $res{'exit_code'} ) {
- return (0, $self->loc('GnuPG error. Contact with administrator'));
+ return (0, $self->loc('Decryption error; contact the administrator'));
}
my ($status, $msg) = $self->__Set( Field => 'Content', Value => $content );
unless ( $status ) {
return ($status, $self->loc("Couldn't replace content with decrypted data: [_1]", $msg));
}
+ $self->__Set( Field => 'ContentType', Value => $type );
+ $self->SetHeader( 'Content-Type' => $type );
+
return (1, $self->loc('Successfuly decrypted data'));
}
{
id =>
- {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
+ {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
TransactionId =>
- {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
+ {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
Parent =>
- {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
+ {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
MessageId =>
- {read => 1, write => 1, sql_type => 12, length => 160, is_blob => 0, is_numeric => 0, type => 'varchar(160)', default => ''},
+ {read => 1, write => 1, sql_type => 12, length => 160, is_blob => 0, is_numeric => 0, type => 'varchar(160)', default => ''},
Subject =>
- {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
+ {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
Filename =>
- {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
+ {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
ContentType =>
- {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''},
+ {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''},
ContentEncoding =>
- {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''},
+ {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''},
Content =>
- {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longblob', default => ''},
+ {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longblob', default => ''},
Headers =>
- {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longtext', default => ''},
+ {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longtext', default => ''},
Creator =>
- {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
+ {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
Created =>
- {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
+ {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
}
};
+sub FindDependencies {
+ my $self = shift;
+ my ($walker, $deps) = @_;
+
+ $self->SUPER::FindDependencies($walker, $deps);
+ $deps->Add( out => $self->TransactionObj );
+}
+
RT::Base->_ImportOverlays();
1;