1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
6 # <sales@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 # General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
30 # CONTRIBUTION SUBMISSION POLICY:
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
47 # END BPS TAGGED BLOCK }}}
55 This module should never be instantiated directly by client code. it's an internal
56 module which should only be instantiated through exported APIs in Ticket, Queue and other
66 package RT::Attachment;
67 use base 'RT::Record';
69 sub Table {'Attachments'}
80 use MIME::QuotedPrint;
82 use RT::Util 'mime_recommended_filename';
84 sub _OverlayAccessible {
86 TransactionId => { 'read'=>1, 'public'=>1, 'write' => 0 },
87 MessageId => { 'read'=>1, 'write' => 0 },
88 Parent => { 'read'=>1, 'write' => 0 },
89 ContentType => { 'read'=>1, 'write' => 0 },
90 Subject => { 'read'=>1, 'write' => 0 },
91 Content => { 'read'=>1, 'write' => 0 },
92 ContentEncoding => { 'read'=>1, 'write' => 0 },
93 Headers => { 'read'=>1, 'write' => 0 },
94 Filename => { 'read'=>1, 'write' => 0 },
95 Creator => { 'read'=>1, 'auto'=>1, },
96 Created => { 'read'=>1, 'auto'=>1, },
102 Create a new attachment. Takes a paramhash:
104 'Attachment' Should be a single MIME body with optional subparts
105 'Parent' is an optional id of the parent attachment
106 'TransactionId' is the mandatory id of the transaction this attachment is associated with.;
112 my %args = ( id => 0,
118 # For ease of reference
119 my $Attachment = $args{'Attachment'};
121 # if we didn't specify a ticket, we need to bail
122 unless ( $args{'TransactionId'} ) {
123 $RT::Logger->crit( "RT::Attachment->Create couldn't, as you didn't specify a transaction" );
127 # If we possibly can, collapse it to a singlepart
128 $Attachment->make_singlepart;
131 my $Subject = $Attachment->head->get( 'subject', 0 );
132 $Subject = '' unless defined $Subject;
134 utf8::decode( $Subject ) unless utf8::is_utf8( $Subject );
137 my $MessageId = $Attachment->head->get( 'Message-ID', 0 );
138 defined($MessageId) or $MessageId = '';
140 $MessageId =~ s/^<(.*?)>$/$1/o;
144 my $Filename = mime_recommended_filename($Attachment);
147 $Filename =~ s!.*/!! if $Filename;
149 # MIME::Head doesn't support perl strings well and can return
150 # octets which later will be double encoded in low-level code
151 my $head = $Attachment->head->as_string;
152 utf8::decode( $head ) unless utf8::is_utf8( $head );
154 # If a message has no bodyhandle, that means that it has subparts (or appears to)
155 # and we should act accordingly.
156 unless ( defined $Attachment->bodyhandle ) {
157 my ($id) = $self->SUPER::Create(
158 TransactionId => $args{'TransactionId'},
159 Parent => $args{'Parent'},
160 ContentType => $Attachment->mime_type,
162 MessageId => $MessageId,
167 $RT::Logger->crit("Attachment insert failed - ". $RT::Handle->dbh->errstr);
170 foreach my $part ( $Attachment->parts ) {
171 my $SubAttachment = RT::Attachment->new( $self->CurrentUser );
172 my ($id) = $SubAttachment->Create(
173 TransactionId => $args{'TransactionId'},
178 $RT::Logger->crit("Attachment insert failed: ". $RT::Handle->dbh->errstr);
184 #If it's not multipart
187 my ($ContentEncoding, $Body, $ContentType, $Filename) = $self->_EncodeLOB(
188 $Attachment->bodyhandle->as_string,
189 $Attachment->mime_type,
193 my $id = $self->SUPER::Create(
194 TransactionId => $args{'TransactionId'},
195 ContentType => $ContentType,
196 ContentEncoding => $ContentEncoding,
197 Parent => $args{'Parent'},
201 Filename => $Filename,
202 MessageId => $MessageId,
206 $RT::Logger->crit("Attachment insert failed: ". $RT::Handle->dbh->errstr);
214 Create an attachment exactly as specified in the named parameters.
220 my %args = ( ContentEncoding => 'none', @_ );
222 ( $args{'ContentEncoding'}, $args{'Content'} ) =
223 $self->_EncodeLOB( $args{'Content'}, $args{'MimeType'} );
225 return ( $self->SUPER::Create(%args) );
228 =head2 TransactionObj
230 Returns the transaction object asscoiated with this attachment.
237 unless ( $self->{_TransactionObj} ) {
238 $self->{_TransactionObj} = RT::Transaction->new( $self->CurrentUser );
239 $self->{_TransactionObj}->Load( $self->TransactionId );
242 unless ($self->{_TransactionObj}->Id) {
243 $RT::Logger->crit( "Attachment ". $self->id
244 ." can't find transaction ". $self->TransactionId
245 ." which it is ostensibly part of. That's bad");
247 return $self->{_TransactionObj};
252 Returns a parent's L<RT::Attachment> object if this attachment
253 has a parent, otherwise returns undef.
259 return undef unless $self->Parent;
261 my $parent = RT::Attachment->new( $self->CurrentUser );
262 $parent->LoadById( $self->Parent );
268 Returns an L<RT::Attachments> object which is preloaded with
269 all attachments objects with this attachment's Id as their
277 my $kids = RT::Attachments->new( $self->CurrentUser );
278 $kids->ChildrenOf( $self->Id );
284 Returns the attachment's content. if it's base64 encoded, decode it
291 return $self->_DecodeLOB(
293 $self->ContentEncoding,
294 $self->_Value('Content', decode_utf8 => 0),
298 =head2 OriginalContent
300 Returns the attachment's content as octets before RT's mangling.
301 Generally this just means restoring text content back to its
304 If the attachment has a C<message/*> Content-Type, its children attachments
305 are reconstructed and returned as a string.
309 sub OriginalContent {
312 # message/* content types represent raw messages. Since we break them
313 # apart when they come in, we'll reconstruct their child attachments when
314 # you ask for the OriginalContent of the message/ part.
315 if ($self->IsMessageContentType) {
316 # There shouldn't be more than one "subpart" to a message/* attachment
317 my $child = $self->Children->First;
318 return $self->Content unless $child and $child->id;
319 return $child->ContentAsMIME(Children => 1)->as_string;
322 return $self->Content unless RT::I18N::IsTextualContentType($self->ContentType);
323 my $enc = $self->OriginalEncoding;
326 if ( !$self->ContentEncoding || $self->ContentEncoding eq 'none' ) {
327 $content = $self->_Value('Content', decode_utf8 => 0);
328 } elsif ( $self->ContentEncoding eq 'base64' ) {
329 $content = MIME::Base64::decode_base64($self->_Value('Content', decode_utf8 => 0));
330 } elsif ( $self->ContentEncoding eq 'quoted-printable' ) {
331 $content = MIME::QuotedPrint::decode($self->_Value('Content', decode_utf8 => 0));
333 return( $self->loc("Unknown ContentEncoding [_1]", $self->ContentEncoding));
336 # Turn *off* the SvUTF8 bits here so decode_utf8 and from_to below can work.
338 Encode::_utf8_off($content);
340 if (!$enc || $enc eq '' || $enc eq 'utf8' || $enc eq 'utf-8') {
341 # If we somehow fail to do the decode, at least push out the raw bits
342 eval { return( Encode::decode_utf8($content)) } || return ($content);
345 eval { Encode::from_to($content, 'utf8' => $enc) } if $enc;
347 $RT::Logger->error("Could not convert attachment from assumed utf8 to '$enc' :".$@);
352 =head2 OriginalEncoding
354 Returns the attachment's original encoding.
358 sub OriginalEncoding {
360 return $self->GetHeader('X-RT-Original-Encoding');
365 Returns length of L</Content> in bytes.
372 return undef unless $self->TransactionObj->CurrentUserCanSee;
374 my $len = $self->GetHeader('Content-Length');
375 unless ( defined $len ) {
377 no warnings 'uninitialized';
378 $len = length($self->Content) || 0;
379 $self->SetHeader('Content-Length' => $len);
390 my %args=(Reply=>undef, # Prefilled reply (i.e. from the KB/FAQ system)
393 my ($quoted_content, $body, $headers);
396 # TODO: Handle Multipart/Mixed (eventually fix the link in the
397 # ShowHistory web template?)
398 if (RT::I18N::IsTextualContentType($self->ContentType)) {
399 $body=$self->Content;
401 # Do we need any preformatting (wrapping, that is) of the message?
403 # Remove quoted signature.
404 $body =~ s/\n-- \n(.*)$//s;
406 # What's the longest line like?
407 foreach (split (/\n/,$body)) {
408 $max=length if ( length > $max);
412 require Text::Wrapper;
413 my $wrapper = Text::Wrapper->new
416 body_start => ($max > 70*3 ? ' ' : ''),
419 $body=$wrapper->wrap($body);
424 $body = '[' . $self->TransactionObj->CreatorObj->Name() . ' - ' . $self->TransactionObj->CreatedAsString()
429 $body = "[Non-text message not quoted]\n\n";
436 return (\$body, $max);
439 =head2 ContentAsMIME [Children => 1]
441 Returns MIME entity built from this attachment.
443 If the optional parameter C<Children> is set to a true value, the children are
444 recursively added to the entity.
455 my $entity = MIME::Entity->new();
456 foreach my $header ($self->SplitHeaders) {
457 my ($h_key, $h_val) = split /:/, $header, 2;
458 $entity->head->add( $h_key, RT::Interface::Email::EncodeToMIME( String => $h_val ) );
461 # since we want to return original content, let's use original encoding
462 $entity->head->mime_attr(
463 "Content-Type.charset" => $self->OriginalEncoding )
464 if $self->OriginalEncoding;
467 MIME::Body::Scalar->new( $self->OriginalContent )
470 if ($opts{'Children'} and not $self->IsMessageContentType) {
471 my $children = $self->Children;
472 while (my $child = $children->Next) {
473 $entity->make_multipart unless $entity->is_multipart;
474 $entity->add_part( $child->ContentAsMIME(%opts) );
481 =head2 IsMessageContentType
483 Returns a boolean indicating if the Content-Type of this attachment is a
488 sub IsMessageContentType {
490 return $self->ContentType =~ m{^\s*message/}i ? 1 : 0;
495 Returns a hashref of all addresses related to this attachment.
496 The keys of the hash are C<From>, C<To>, C<Cc>, C<Bcc>, C<RT-Send-Cc>
497 and C<RT-Send-Bcc>. The values are references to lists of
498 L<Email::Address> objects.
502 our @ADDRESS_HEADERS = qw(From To Cc Bcc RT-Send-Cc RT-Send-Bcc);
508 my $current_user_address = lc $self->CurrentUser->EmailAddress;
509 foreach my $hdr (@ADDRESS_HEADERS) {
511 my $line = $self->GetHeader($hdr);
513 foreach my $AddrObj ( Email::Address->parse( $line )) {
514 my $address = $AddrObj->address;
515 $address = lc RT::User->CanonicalizeEmailAddress($address);
516 next if $current_user_address eq $address;
517 next if RT::EmailParser->IsRTAddress($address);
518 push @Addresses, $AddrObj ;
520 $data{$hdr} = \@Addresses;
527 Returns a multi-line string of the To, From, Cc, Date and Subject headers.
534 my @hdrs = $self->_SplitHeaders;
535 while (my $str = shift @hdrs) {
536 next unless $str =~ /^(To|From|RT-Send-Cc|Cc|Bcc|Date|Subject):/i;
537 $hdrs .= $str . "\n";
538 $hdrs .= shift( @hdrs ) . "\n" while ($hdrs[0] =~ /^[ \t]+/);
545 Returns this object's headers as a string. This method specifically
546 removes the RT-Send-Bcc: header, so as to never reveal to whom RT sent a Bcc.
547 We need to record the RT-Send-Cc and RT-Send-Bcc values so that we can actually send
548 out mail. The mailing rules are separated from the ticket update code by
549 an abstraction barrier that makes it impossible to pass this data directly.
554 return join("\n", $_[0]->SplitHeaders);
557 =head2 EncodedHeaders
559 Takes encoding as argument and returns the attachment's headers as octets in encoded
562 This is not protection using quoted printable or base64 encoding.
568 my $encoding = shift || 'utf8';
569 return Encode::encode( $encoding, $self->Headers );
572 =head2 GetHeader $TAG
574 Returns the value of the header Tag as a string. This bypasses the weeding out
575 done in Headers() above.
582 foreach my $line ($self->_SplitHeaders) {
583 next unless $line =~ /^\Q$tag\E:\s+(.*)$/si;
585 #if we find the header, return its value
589 # we found no header. return an empty string
593 =head2 DelHeader $TAG
595 Delete a field from the attachment's headers.
604 foreach my $line ($self->_SplitHeaders) {
605 next if $line =~ /^\Q$tag\E:\s+/i;
606 $newheader .= "$line\n";
608 return $self->__Set( Field => 'Headers', Value => $newheader);
611 =head2 AddHeader $TAG, $VALUE, ...
613 Add one or many fields to the attachment's headers.
620 my $newheader = $self->__Value( 'Headers' );
621 while ( my ($tag, $value) = splice @_, 0, 2 ) {
622 $value = $self->_CanonicalizeHeaderValue($value);
623 $newheader .= "$tag: $value\n";
625 return $self->__Set( Field => 'Headers', Value => $newheader);
628 =head2 SetHeader ( 'Tag', 'Value' )
630 Replace or add a Header to the attachment's headers.
637 my $value = $self->_CanonicalizeHeaderValue(shift);
641 foreach my $line ( $self->_SplitHeaders ) {
642 if ( $line =~ /^\Q$tag\E:\s+/i ) {
643 # replace first instance, skip all the rest
645 $newheader .= "$tag: $value\n";
649 $newheader .= "$line\n";
653 $newheader .= "$tag: $value\n" unless $replaced;
654 $self->__Set( Field => 'Headers', Value => $newheader);
657 sub _CanonicalizeHeaderValue {
661 $value = '' unless defined $value;
663 $value =~ s/\r*\n/\n /g;
670 Returns an array of this attachment object's headers, with one header
671 per array entry. Multiple lines are folded.
673 B<Never> returns C<RT-Send-Bcc> field.
679 return (grep !/^RT-Send-Bcc/i, $self->_SplitHeaders(@_) );
684 Returns an array of this attachment object's headers, with one header
685 per array entry. multiple lines are folded.
692 my $headers = (shift || $self->_Value('Headers'));
694 # XXX TODO: splitting on \n\w is _wrong_ as it treats \n[ as a valid
695 # continuation, which it isn't. The correct split pattern, per RFC 2822,
696 # is /\n(?=[^ \t]|\z)/. That is, only "\n " or "\n\t" is a valid
697 # continuation. Older values of X-RT-GnuPG-Status contain invalid
698 # continuations and rely on this bogus split pattern, however, so it is
699 # left as-is for now.
700 for (split(/\n(?=\w|\z)/,$headers)) {
711 my $txn = $self->TransactionObj;
712 return (0, $self->loc('Permission Denied')) unless $txn->CurrentUserCanSee;
713 return (0, $self->loc('Permission Denied'))
714 unless $txn->TicketObj->CurrentUserHasRight('ModifyTicket');
715 return (0, $self->loc('GnuPG integration is disabled'))
716 unless RT->Config->Get('GnuPG')->{'Enable'};
717 return (0, $self->loc('Attachments encryption is disabled'))
718 unless RT->Config->Get('GnuPG')->{'AllowEncryptDataInDB'};
720 require RT::Crypt::GnuPG;
722 my $type = $self->ContentType;
723 if ( $type =~ /^x-application-rt\/gpg-encrypted/i ) {
724 return (1, $self->loc('Already encrypted'));
725 } elsif ( $type =~ /^multipart\//i ) {
726 return (1, $self->loc('No need to encrypt'));
728 $type = qq{x-application-rt\/gpg-encrypted; original-type="$type"};
731 my $queue = $txn->TicketObj->QueueObj;
733 foreach my $address ( grep $_,
734 $queue->CorrespondAddress,
735 $queue->CommentAddress,
736 RT->Config->Get('CorrespondAddress'),
737 RT->Config->Get('CommentAddress'),
739 my %res = RT::Crypt::GnuPG::GetKeysInfo( $address, 'private' );
740 next if $res{'exit_code'} || !$res{'info'};
741 %res = RT::Crypt::GnuPG::GetKeysForEncryption( $address );
742 next if $res{'exit_code'} || !$res{'info'};
743 $encrypt_for = $address;
745 unless ( $encrypt_for ) {
746 return (0, $self->loc('No key suitable for encryption'));
749 $self->__Set( Field => 'ContentType', Value => $type );
750 $self->SetHeader( 'Content-Type' => $type );
752 my $content = $self->Content;
753 my %res = RT::Crypt::GnuPG::SignEncryptContent(
754 Content => \$content,
757 Recipients => [ $encrypt_for ],
759 if ( $res{'exit_code'} ) {
760 return (0, $self->loc('GnuPG error. Contact with administrator'));
763 my ($status, $msg) = $self->__Set( Field => 'Content', Value => $content );
765 return ($status, $self->loc("Couldn't replace content with encrypted data: [_1]", $msg));
767 return (1, $self->loc('Successfuly encrypted data'));
773 my $txn = $self->TransactionObj;
774 return (0, $self->loc('Permission Denied')) unless $txn->CurrentUserCanSee;
775 return (0, $self->loc('Permission Denied'))
776 unless $txn->TicketObj->CurrentUserHasRight('ModifyTicket');
777 return (0, $self->loc('GnuPG integration is disabled'))
778 unless RT->Config->Get('GnuPG')->{'Enable'};
780 require RT::Crypt::GnuPG;
782 my $type = $self->ContentType;
783 if ( $type =~ /^x-application-rt\/gpg-encrypted/i ) {
784 ($type) = ($type =~ /original-type="(.*)"/i);
785 $type ||= 'application/octet-stream';
787 return (1, $self->loc('Is not encrypted'));
789 $self->__Set( Field => 'ContentType', Value => $type );
790 $self->SetHeader( 'Content-Type' => $type );
792 my $content = $self->Content;
793 my %res = RT::Crypt::GnuPG::DecryptContent( Content => \$content, );
794 if ( $res{'exit_code'} ) {
795 return (0, $self->loc('GnuPG error. Contact with administrator'));
798 my ($status, $msg) = $self->__Set( Field => 'Content', Value => $content );
800 return ($status, $self->loc("Couldn't replace content with decrypted data: [_1]", $msg));
802 return (1, $self->loc('Successfuly decrypted data'));
807 Takes the name of a table column.
808 Returns its value as a string, if the user passes an ACL check
816 #if the field is public, return it.
817 if ( $self->_Accessible( $field, 'public' ) ) {
818 return ( $self->__Value( $field, @_ ) );
821 return undef unless $self->TransactionObj->CurrentUserCanSee;
822 return $self->__Value( $field, @_ );
825 # Transactions don't change. by adding this cache congif directiove,
826 # we don't lose pathalogically on long tickets.
830 'fast_update_p' => 1,
831 'cache_for_sec' => 180,
840 Returns the current value of id.
841 (In the database, id is stored as int(11).)
849 Returns the current value of TransactionId.
850 (In the database, TransactionId is stored as int(11).)
854 =head2 SetTransactionId VALUE
857 Set TransactionId to VALUE.
858 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
859 (In the database, TransactionId will be stored as a int(11).)
867 Returns the current value of Parent.
868 (In the database, Parent is stored as int(11).)
872 =head2 SetParent VALUE
876 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
877 (In the database, Parent will be stored as a int(11).)
885 Returns the current value of MessageId.
886 (In the database, MessageId is stored as varchar(160).)
890 =head2 SetMessageId VALUE
893 Set MessageId to VALUE.
894 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
895 (In the database, MessageId will be stored as a varchar(160).)
903 Returns the current value of Subject.
904 (In the database, Subject is stored as varchar(255).)
908 =head2 SetSubject VALUE
911 Set Subject to VALUE.
912 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
913 (In the database, Subject will be stored as a varchar(255).)
921 Returns the current value of Filename.
922 (In the database, Filename is stored as varchar(255).)
926 =head2 SetFilename VALUE
929 Set Filename to VALUE.
930 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
931 (In the database, Filename will be stored as a varchar(255).)
939 Returns the current value of ContentType.
940 (In the database, ContentType is stored as varchar(80).)
944 =head2 SetContentType VALUE
947 Set ContentType to VALUE.
948 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
949 (In the database, ContentType will be stored as a varchar(80).)
955 =head2 ContentEncoding
957 Returns the current value of ContentEncoding.
958 (In the database, ContentEncoding is stored as varchar(80).)
962 =head2 SetContentEncoding VALUE
965 Set ContentEncoding to VALUE.
966 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
967 (In the database, ContentEncoding will be stored as a varchar(80).)
975 Returns the current value of Content.
976 (In the database, Content is stored as longblob.)
980 =head2 SetContent VALUE
983 Set Content to VALUE.
984 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
985 (In the database, Content will be stored as a longblob.)
993 Returns the current value of Headers.
994 (In the database, Headers is stored as longtext.)
998 =head2 SetHeaders VALUE
1001 Set Headers to VALUE.
1002 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1003 (In the database, Headers will be stored as a longtext.)
1011 Returns the current value of Creator.
1012 (In the database, Creator is stored as int(11).)
1020 Returns the current value of Created.
1021 (In the database, Created is stored as datetime.)
1028 sub _CoreAccessible {
1032 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
1034 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
1036 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1038 {read => 1, write => 1, sql_type => 12, length => 160, is_blob => 0, is_numeric => 0, type => 'varchar(160)', default => ''},
1040 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
1042 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
1044 {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''},
1046 {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''},
1048 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longblob', default => ''},
1050 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longtext', default => ''},
1052 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1054 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
1059 RT::Base->_ImportOverlays();