[Bps-public-commit] rt-extension-rest2 branch, rest2_api_attachment_handling, created. 1.07-5-g606e525
Aaron Trevena
ast at bestpractical.com
Mon Apr 6 08:31:38 EDT 2020
The branch, rest2_api_attachment_handling has been created
at 606e5256bcd32f098109b396b088a3d36082b520 (commit)
- Log -----------------------------------------------------------------
commit 551f3c870d3763945fcfa57492116572728a89e0
Author: gibus <gibus at easter-eggs.com>
Date: Thu Oct 18 13:56:49 2018 +0200
Allow attachments as JSON Array with Base64 encoded content
Add documentation for attachments as JSON Array with Base64 encoded content
Based on public github PR #15
diff --git a/README b/README
index 40138e3..0624b11 100644
--- a/README
+++ b/README
@@ -193,6 +193,133 @@ USAGE
You may of course choose to ignore the ETag header and not provide
If-Match in your requests; RT doesn't require its use.
+ Replying/Commenting Tickets
+ You can reply to or comment a ticket by POSTing to _url from the
+ correspond or comment hyperlinks that were returned when fetching the
+ ticket.
+
+ curl -X POST
+ -H "Content-Type: application/json"
+ -d '{
+ "Subject" : "response",
+ "Content" : "What is your <em>issue</em>?",
+ "ContentType": "text/html",
+ "TimeTaken" : "1"
+ }'
+ -H 'Authorization: token XX_TOKEN_XX'
+ 'XX_TICKET_URL_XX'/correspond
+
+ Replying or commenting a ticket is quite similar to a ticket creation:
+ you send a POST request, with data encoded in JSON. The difference lies
+ in the properties of the JSON data object you can pass:
+
+ Subject
+ The subject of your response/comment, optional
+
+ Content
+ The content of your response/comment, mandatory unless there is a
+ non empty AttachmentsContent property to add at least one attachment
+ to the ticket (see "Add Attachments" section below).
+
+ ContentType
+ The MIME content type of your response/comment, typically text/plain
+ or /text/html, mandatory unless there is a non empty
+ AttachmentsContent property to add at least one attachment to the
+ ticket (see "Add Attachments" section below).
+
+ TimeTaken
+ The time, in minutes, you've taken to work on your response/comment,
+ optional.
+
+ Add Attachments
+ You can attach any binary or text file to your response or comment by
+ specifying in the JSON object sent a AttachementsContents property,
+ which should be a JSON array where each item represents a file you want
+ to attach. Each item is a JSON object with the following properties:
+
+ FileName
+ The name of the file to attach to your response/comment, mandatory.
+
+ FileType
+ The MIME type of the file to attach to your response/comment,
+ mandatory.
+
+ FileContent
+ The content, *encoded in MIME Base64* of the file to attach to your
+ response/comment, mandatory.
+
+ The reason why you should encode the content of any file to MIME Base64
+ is that a JSON string value should be a sequence of zero or more Unicode
+ characters. MIME Base64 is a binary-to-text encoding scheme widely used
+ (for eg. by web browser) to send binary data when text data is required.
+ Most popular language have MIME Base64 libraries that you can use to
+ encode the content of your attached files (see MIME::Base64 for Perl).
+ Note that even text files should be MIME Base64 encoded to be passed in
+ the FileContent property.
+
+ Here's a Perl example to send an image and a plain text file attached to
+ a comment:
+
+ #!/usr/bin/perl
+ use strict;
+ use warnings;
+
+ use LWP::UserAgent;
+ use JSON;
+ use MIME::Base64;
+ use Data::Dumper;
+
+ my $url = 'http://rt.local/REST/2.0/ticket/1/comment';
+
+ my $img_path = '/tmp/my_image.png';
+ my $img_content;
+ open my $img_fh, '<', $img_path or die "Cannot read $img_path: $!\n";
+ {
+ local $/;
+ $img_content = <$img_fh>;
+ }
+ close $img_fh;
+ $img_content = MIME::Base64::encode_base64($img_content);
+
+ my $txt_path = '~/.bashrc';
+ my $txt_content;
+ open my $txt_fh, '<', glob($txt_path) or die "Cannot read $txt_path: $!\n";
+ {
+ local $/;
+ $txt_content = <$txt_fh>;
+ }
+ close $txt_fh;
+ $txt_content = MIME::Base64::encode_base64($txt_content);
+
+ my $json = JSON->new->utf8;
+ my $payload = {
+ Content => '<p>I want <b>two</b> <em>attachment</em></p>',
+ ContentType => 'text/html',
+ Subject => 'Attachments in JSON Array',
+ AttachmentsContents => [
+ {
+ FileName => 'my_image.png',
+ FileType => 'image/png',
+ FileContent => $img_content,
+ },
+ {
+ FileName => '.bashrc',
+ FileType => 'text/plain',
+ FileContent => $txt_content,
+ },
+ ],
+ };
+
+ my $req = HTTP::Request->new(POST => $url);
+ $req->header('Authorization' => 'token 6-66-66666666666666666666666666666666');
+ $req->header('Content-Type' => 'application/json' );
+ $req->header('Accept' => 'application/json' );
+ $req->content($json->encode($payload));
+
+ my $ua = LWP::UserAgent->new;
+ my $res = $ua->request($req);
+ print Dumper($json->decode($res->content)) . "\n";
+
Summary
RT's REST2 API provides the tools you need to build robust and dynamic
integrations. Tools like ETag/If-Match allow you to avoid conflicts such
diff --git a/lib/RT/Extension/REST2.pm b/lib/RT/Extension/REST2.pm
index c4b61c6..27083bc 100644
--- a/lib/RT/Extension/REST2.pm
+++ b/lib/RT/Extension/REST2.pm
@@ -224,6 +224,127 @@ adding time worked can be automatically be recalculated).
You may of course choose to ignore the C<ETag> header and not provide
C<If-Match> in your requests; RT doesn't require its use.
+=head3 Replying/Commenting Tickets
+
+You can reply to or comment a ticket by C<POST>ing to C<_url> from the C<correspond> or C<comment> hyperlinks that were returned when fetching the ticket.
+
+ curl -X POST
+ -H "Content-Type: application/json"
+ -d '{
+ "Subject" : "response",
+ "Content" : "What is your <em>issue</em>?",
+ "ContentType": "text/html",
+ "TimeTaken" : "1"
+ }'
+ -H 'Authorization: token XX_TOKEN_XX'
+ 'XX_TICKET_URL_XX'/correspond
+
+Replying or commenting a ticket is quite similar to a ticket creation: you send a C<POST> request, with data encoded in C<JSON>. The difference lies in the properties of the JSON data object you can pass:
+
+=over 4
+
+=item C<Subject>
+
+The subject of your response/comment, optional
+
+=item C<Content>
+
+The content of your response/comment, mandatory unless there is a non empty C<AttachmentsContent> property to add at least one attachment to the ticket (see L<Add Attachments> section below).
+
+=item C<ContentType>
+
+The MIME content type of your response/comment, typically C<text/plain> or C</text/html>, mandatory unless there is a non empty C<AttachmentsContent> property to add at least one attachment to the ticket (see L<Add Attachments> section below).
+
+=item C<TimeTaken>
+
+The time, in minutes, you've taken to work on your response/comment, optional.
+
+=back
+
+=head3 Add Attachments
+
+You can attach any binary or text file to your response or comment by specifying in the JSON object sent a C<AttachementsContents> property, which should be a JSON array where each item represents a file you want to attach. Each item is a JSON object with the following properties:
+
+=over 4
+
+=item C<FileName>
+
+The name of the file to attach to your response/comment, mandatory.
+
+=item C<FileType>
+
+The MIME type of the file to attach to your response/comment, mandatory.
+
+=item C<FileContent>
+
+The content, I<encoded in C<MIME Base64>> of the file to attach to your response/comment, mandatory.
+
+=back
+
+The reason why you should encode the content of any file to C<MIME Base64> is that a JSON string value should be a sequence of zero or more Unicode characters. C<MIME Base64> is a binary-to-text encoding scheme widely used (for eg. by web browser) to send binary data when text data is required. Most popular language have C<MIME Base64> libraries that you can use to encode the content of your attached files (see L<MIME::Base64> for C<Perl>). Note that even text files should be C<MIME Base64> encoded to be passed in the C<FileContent> property.
+
+Here's a Perl example to send an image and a plain text file attached to a comment:
+
+ #!/usr/bin/perl
+ use strict;
+ use warnings;
+
+ use LWP::UserAgent;
+ use JSON;
+ use MIME::Base64;
+ use Data::Dumper;
+
+ my $url = 'http://rt.local/REST/2.0/ticket/1/comment';
+
+ my $img_path = '/tmp/my_image.png';
+ my $img_content;
+ open my $img_fh, '<', $img_path or die "Cannot read $img_path: $!\n";
+ {
+ local $/;
+ $img_content = <$img_fh>;
+ }
+ close $img_fh;
+ $img_content = MIME::Base64::encode_base64($img_content);
+
+ my $txt_path = '~/.bashrc';
+ my $txt_content;
+ open my $txt_fh, '<', glob($txt_path) or die "Cannot read $txt_path: $!\n";
+ {
+ local $/;
+ $txt_content = <$txt_fh>;
+ }
+ close $txt_fh;
+ $txt_content = MIME::Base64::encode_base64($txt_content);
+
+ my $json = JSON->new->utf8;
+ my $payload = {
+ Content => '<p>I want <b>two</b> <em>attachment</em></p>',
+ ContentType => 'text/html',
+ Subject => 'Attachments in JSON Array',
+ AttachmentsContents => [
+ {
+ FileName => 'my_image.png',
+ FileType => 'image/png',
+ FileContent => $img_content,
+ },
+ {
+ FileName => '.bashrc',
+ FileType => 'text/plain',
+ FileContent => $txt_content,
+ },
+ ],
+ };
+
+ my $req = HTTP::Request->new(POST => $url);
+ $req->header('Authorization' => 'token 6-66-66666666666666666666666666666666');
+ $req->header('Content-Type' => 'application/json' );
+ $req->header('Accept' => 'application/json' );
+ $req->content($json->encode($payload));
+
+ my $ua = LWP::UserAgent->new;
+ my $res = $ua->request($req);
+ print Dumper($json->decode($res->content)) . "\n";
+
=head3 Summary
RT's REST2 API provides the tools you need to build robust and dynamic
diff --git a/lib/RT/Extension/REST2/Resource/Message.pm b/lib/RT/Extension/REST2/Resource/Message.pm
index 57c8ef1..bf4775e 100644
--- a/lib/RT/Extension/REST2/Resource/Message.pm
+++ b/lib/RT/Extension/REST2/Resource/Message.pm
@@ -4,6 +4,7 @@ use warnings;
use Moose;
use namespace::autoclean;
+use MIME::Base64;
extends 'RT::Extension::REST2::Resource';
use RT::Extension::REST2::Util qw( error_as_json );
@@ -49,12 +50,26 @@ sub from_json {
my $self = shift;
my $body = JSON::decode_json( $self->request->content );
- if (!$body->{ContentType}) {
+ if ($body->{AttachmentsContents}) {
+ foreach my $attachment (@{$body->{AttachmentsContents}}) {
+ foreach my $field ('FileName', 'FileType', 'FileContent') {
+ return error_as_json(
+ $self->response,
+ \400, "$field is a required field for each attachment in AttachmentsContents")
+ unless $attachment->{$field};
+ }
+ }
+
+ $body->{NoContent} = 1 unless $body->{Content};
+ }
+
+ if (!$body->{NoContent} && !$body->{ContentType}) {
return error_as_json(
$self->response,
\400, "ContentType is a required field for application/json");
}
+
$self->add_message(%$body);
}
@@ -64,11 +79,20 @@ sub add_message {
my $MIME = HTML::Mason::Commands::MakeMIMEEntity(
Interface => 'REST',
- Body => $args{Content} || $self->request->content,
+ $args{NoContent} ? () : (Body => $args{Content} || $self->request->content),
Type => $args{ContentType} || $self->request->content_type,
Subject => $args{Subject},
);
+ # Process attachments
+ foreach my $attachment (@{$args{AttachmentsContents}}) {
+ $MIME->attach(
+ Type => $attachment->{FileType},
+ Filename => $attachment->{FileName},
+ Data => MIME::Base64::decode_base64($attachment->{FileContent}),
+ );
+ }
+
my ( $Trans, $msg, $TransObj ) ;
if ($self->type eq 'correspond') {
commit d119220129ab8d7ed858673493cb4b48c9bfccb7
Author: gibus <gibus at easter-eggs.com>
Date: Thu Oct 18 13:57:39 2018 +0200
Add test for attachments as JSON Array with Base64 encoded content
Based on public github PR #15
diff --git a/MANIFEST b/MANIFEST
index 7ef88eb..30eb11b 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -68,8 +68,10 @@ README
TODO
xt/asset-customfields.t
xt/assets.t
+xt/attachments.t
xt/catalogs.t
xt/conflict.t
+xt/data/image.png
xt/group-members.t
xt/not_found.t
xt/organization.t
diff --git a/xt/attachments.t b/xt/attachments.t
new file mode 100644
index 0000000..68a8e0d
--- /dev/null
+++ b/xt/attachments.t
@@ -0,0 +1,150 @@
+use strict;
+use warnings;
+use lib 't/lib';
+use RT::Extension::REST2::Test tests => undef;
+use Test::Deep;
+use MIME::Base64;
+
+my $mech = RT::Extension::REST2::Test->mech;
+my $auth = RT::Extension::REST2::Test->authorization_header;
+my $rest_base_path = '/REST/2.0';
+my $user = RT::Extension::REST2::Test->user;
+
+$user->PrincipalObj->GrantRight(Right => 'CreateTicket');
+$user->PrincipalObj->GrantRight(Right => 'ReplyToTicket');
+$user->PrincipalObj->GrantRight(Right => 'CommentOnTicket');
+$user->PrincipalObj->GrantRight(Right => 'ShowTicket');
+$user->PrincipalObj->GrantRight(Right => 'ShowTicketComments');
+
+my $ticket = RT::Ticket->new($user);
+$ticket->Create(Queue => 'General', Subject => 'hello world');
+my $ticket_id = $ticket->id;
+
+my $image_name = 'image.png';
+my $image_path = RT::Test::get_relocatable_file($image_name, 'data');
+my $image_content;
+open my $fh, '<', $image_path or die "Cannot read $image_path: $!\n";
+{
+ local $/;
+ $image_content = <$fh>;
+}
+close $fh;
+
+$image_content = MIME::Base64::encode_base64($image_content);
+
+# Comment ticket with image and text attachments
+{
+ my $payload = {
+ Content => 'Have you seen this <b>image</b>',
+ ContentType => 'text/html',
+ Subject => 'HTML comment with PNG image and text file',
+ AttachmentsContents => [
+ {
+ FileName => $image_name,
+ FileType => 'image/png',
+ FileContent => $image_content,
+ },
+ {
+ FileName => 'password',
+ FileType => 'text/plain',
+ FileContent => MIME::Base64::encode_base64('Hey this is secret!'),
+ },
+ ],
+ };
+ my $res = $mech->post_json("$rest_base_path/ticket/$ticket_id/comment",
+ $payload,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 201);
+ cmp_deeply($mech->json_response, [re(qr/Comments added|Message recorded/)]);
+
+ my $transaction_id = $ticket->Transactions->Last->id;
+ my $attachments = $ticket->Attachments->ItemsArrayRef;
+
+ # 3 attachments + 1 wrapper
+ is(scalar(@$attachments), 4);
+
+ # 1st attachment is wrapper
+ is($attachments->[0]->TransactionId, $transaction_id);
+ is($attachments->[0]->Parent, 0);
+ is($attachments->[0]->Subject, 'HTML comment with PNG image and text file');
+ ok(!$attachments->[0]->Filename);
+ is($attachments->[0]->ContentType, 'multipart/mixed');
+
+ # 2nd attachment is comment's content
+ is($attachments->[1]->Parent, $attachments->[0]->id);
+ is($attachments->[1]->TransactionId, $transaction_id);
+ is($attachments->[1]->ContentType, 'text/html');
+ is($attachments->[1]->ContentEncoding, 'none');
+ is($attachments->[1]->Content, 'Have you seen this <b>image</b>');
+ ok(!$attachments->[1]->Subject);
+
+ # 3rd attachment is image
+ my $expected_encoding = ($RT::Handle->BinarySafeBLOBs) ? 'none' : 'base54';
+ is($attachments->[2]->Parent, $attachments->[0]->id);
+ is($attachments->[2]->TransactionId, $transaction_id);
+ is($attachments->[2]->ContentType, 'image/png');
+ is($attachments->[2]->ContentEncoding, $expected_encoding);
+ is($attachments->[2]->Filename, $image_name);
+ ok(!$attachments->[2]->Subject);
+
+ # 4th attachment is text file
+ is($attachments->[3]->Parent, $attachments->[0]->id);
+ is($attachments->[3]->TransactionId, $transaction_id);
+ is($attachments->[3]->ContentType, 'text/plain');
+ is($attachments->[3]->ContentEncoding, 'none');
+ is($attachments->[3]->Filename, 'password');
+ is($attachments->[3]->Content, 'Hey this is secret!');
+ ok(!$attachments->[3]->Subject);
+}
+
+#Comment ticket with image attachment and no content
+{
+ my $payload = {
+ Subject => 'No content, just an image',
+ AttachmentsContents => [
+ {
+ FileName => $image_name,
+ FileType => 'image/png',
+ FileContent => $image_content,
+ },
+ ],
+ };
+ my $res = $mech->post_json("$rest_base_path/ticket/$ticket_id/comment",
+ $payload,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 201);
+ cmp_deeply($mech->json_response, [re(qr/Comments added|Message recorded/)]);
+
+ my $transaction_id = $ticket->Transactions->Last->id;
+ my @attachments = grep { $_->TransactionId == $transaction_id } @{$ticket->Attachments->ItemsArrayRef};
+
+ # 2 attachments + 1 wrapper
+ is(scalar(@attachments), 3);
+
+ # 1st attachment is wrapper
+ is($attachments[0]->Parent, 0);
+ is($attachments[0]->Subject, 'No content, just an image');
+ ok(!$attachments[0]->Filename);
+ is($attachments[0]->ContentType, 'multipart/mixed');
+
+ # 2nd attachment is empty comment's content
+ is($attachments[1]->Parent, $attachments[0]->id);
+ is($attachments[1]->TransactionId, $transaction_id);
+ is($attachments[1]->ContentType, 'application/octet-stream');
+ ok(!$attachments[1]->ContentEncoding);
+ ok(!$attachments[1]->Content);
+ ok(!$attachments[1]->Subject);
+
+ # 3rd attachment is image
+ my $expected_encoding = ($RT::Handle->BinarySafeBLOBs) ? 'none' : 'base54';
+ is($attachments[2]->Parent, $attachments[0]->id);
+ is($attachments[2]->TransactionId, $transaction_id);
+ is($attachments[2]->ContentType, 'image/png');
+ is($attachments[2]->ContentEncoding, $expected_encoding);
+ is($attachments[2]->Filename, $image_name);
+ ok(!$attachments[2]->Subject);
+}
+
+done_testing;
diff --git a/xt/data/image.png b/xt/data/image.png
new file mode 100644
index 0000000..8a87374
Binary files /dev/null and b/xt/data/image.png differ
commit 377be7d57052db5021c4d216a47b5441f04592e7
Author: gibus <gibus at easter-eggs.com>
Date: Sat Oct 20 11:57:52 2018 +0200
Allow attachments with multipart/form-data
Add documentation for attachments with multipart/form-data
Based on public github PR #16
diff --git a/lib/RT/Extension/REST2.pm b/lib/RT/Extension/REST2.pm
index 27083bc..9eb1c94 100644
--- a/lib/RT/Extension/REST2.pm
+++ b/lib/RT/Extension/REST2.pm
@@ -226,7 +226,9 @@ C<If-Match> in your requests; RT doesn't require its use.
=head3 Replying/Commenting Tickets
-You can reply to or comment a ticket by C<POST>ing to C<_url> from the C<correspond> or C<comment> hyperlinks that were returned when fetching the ticket.
+You can reply to or comment a ticket by C<POST>ing to C<_url> from the
+C<correspond> or C<comment> hyperlinks that were returned when fetching the
+ticket.
curl -X POST
-H "Content-Type: application/json"
@@ -239,7 +241,9 @@ You can reply to or comment a ticket by C<POST>ing to C<_url> from the C<corresp
-H 'Authorization: token XX_TOKEN_XX'
'XX_TICKET_URL_XX'/correspond
-Replying or commenting a ticket is quite similar to a ticket creation: you send a C<POST> request, with data encoded in C<JSON>. The difference lies in the properties of the JSON data object you can pass:
+Replying or commenting a ticket is quite similar to a ticket creation: you send
+a C<POST> request, with data encoded in C<JSON>. The difference lies in the
+properties of the JSON data object you can pass:
=over 4
@@ -249,11 +253,16 @@ The subject of your response/comment, optional
=item C<Content>
-The content of your response/comment, mandatory unless there is a non empty C<AttachmentsContent> property to add at least one attachment to the ticket (see L<Add Attachments> section below).
+The content of your response/comment, mandatory unless there is a non empty
+C<AttachmentsContent> property to add at least one attachment to the ticket (see
+L<Add Attachments> section below).
=item C<ContentType>
-The MIME content type of your response/comment, typically C<text/plain> or C</text/html>, mandatory unless there is a non empty C<AttachmentsContent> property to add at least one attachment to the ticket (see L<Add Attachments> section below).
+The MIME content type of your response/comment, typically C<text/plain> or
+C</text/html>, mandatory unless there is a non empty C<AttachmentsContent>
+property to add at least one attachment to the ticket (see L<Add Attachments>
+section below).
=item C<TimeTaken>
@@ -263,7 +272,10 @@ The time, in minutes, you've taken to work on your response/comment, optional.
=head3 Add Attachments
-You can attach any binary or text file to your response or comment by specifying in the JSON object sent a C<AttachementsContents> property, which should be a JSON array where each item represents a file you want to attach. Each item is a JSON object with the following properties:
+You can attach any binary or text file to your response or comment by specifying
+in the JSON object sent a C<AttachementsContents> property, which should be a
+JSON array where each item represents a file you want to attach. Each item is
+a JSON object with the following properties:
=over 4
@@ -277,13 +289,21 @@ The MIME type of the file to attach to your response/comment, mandatory.
=item C<FileContent>
-The content, I<encoded in C<MIME Base64>> of the file to attach to your response/comment, mandatory.
+The content, I<encoded in C<MIME Base64>> of the file to attach to your
+response/comment, mandatory.
=back
-The reason why you should encode the content of any file to C<MIME Base64> is that a JSON string value should be a sequence of zero or more Unicode characters. C<MIME Base64> is a binary-to-text encoding scheme widely used (for eg. by web browser) to send binary data when text data is required. Most popular language have C<MIME Base64> libraries that you can use to encode the content of your attached files (see L<MIME::Base64> for C<Perl>). Note that even text files should be C<MIME Base64> encoded to be passed in the C<FileContent> property.
+The reason why you should encode the content of any file to C<MIME Base64> is
+that a JSON string value should be a sequence of zero or more Unicode
+characters. C<MIME Base64> is a binary-to-text encoding scheme widely used (for
+eg. by web browser) to send binary data when text data is required. Most popular
+language have C<MIME Base64> libraries that you can use to encode the content of
+your attached files (see L<MIME::Base64> for C<Perl>). Note that even text files
+should be C<MIME Base64> encoded to be passed in the C<FileContent> property.
-Here's a Perl example to send an image and a plain text file attached to a comment:
+Here's a Perl example to send an image and a plain text file attached to a
+comment:
#!/usr/bin/perl
use strict;
@@ -318,7 +338,7 @@ Here's a Perl example to send an image and a plain text file attached to a comme
my $json = JSON->new->utf8;
my $payload = {
- Content => '<p>I want <b>two</b> <em>attachment</em></p>',
+ Content => '<p>I want <b>two</b> <em>attachments</em></p>',
ContentType => 'text/html',
Subject => 'Attachments in JSON Array',
AttachmentsContents => [
@@ -345,6 +365,37 @@ Here's a Perl example to send an image and a plain text file attached to a comme
my $res = $ua->request($req);
print Dumper($json->decode($res->content)) . "\n";
+Encoding the content of attachments file in C<MIME Base64> has the drawback
+of adding some processing overhead and to increase the sent data size by around
+33%. RT's REST2 API provides another way to attach any binary or text file to your
+response or comment by C<POST>ing, instead of a JSON request, a
+C<multipart/form-data> request. This kind of request is similar to what the browser
+sends when you add attachments in RT's reply or comment form. As its name suggests,
+a C<multipart/form-data> request message contains a series of parts, each representing
+a form field. To reply to or comment a ticket, the request has to include a field
+named C<Json>, which, as previously, is a JSON object with C<Subject>, C<Content>,
+C<ContentType>, C<TimeTaken> properties. Files can then be attached by specifying
+a field named C<Attachment> for each of them, with the content of the file as value
+and the appropriate MIME type.
+
+The curl invocation is quite straith forward:
+
+ curl -X POST
+ -H "Content-Type: multipart/form-data"
+ -F 'Json={
+ "Subject" : "Attachments in multipart/form-data",
+ "Content" : "<p>I want <b>two</b> <em>attachments</em></p>",
+ "ContentType": "text/html",
+ "TimeTaken" : "1"
+ };type=application/json'
+ -F 'Attachment_1=@/tmp/my_image.png;type=image/png'
+ -F 'Attachment_2=@~/.bashrc;type=text/plain'
+ -H 'Authorization: token XX_TOKEN_XX'
+ 'XX_TICKET_URL_XX'/comment
+
+As a sidenote, fields for attached files can also be named C<attachment_1>,
+C<attachment_2>, etc. since such names were used in RT's REST 1.0 API.
+
=head3 Summary
RT's REST2 API provides the tools you need to build robust and dynamic
@@ -439,7 +490,8 @@ Below are some examples using the endpoints above.
# Create an Asset
curl -X POST -H "Content-Type: application/json" -u 'root:password'
- -d '{"Name" : "Asset From Rest", "Catalog" : "General assets", "Content" : "Some content"}'
+ -d '{"Name" : "Asset From Rest",
+ "Catalog" : "General assets", "Content" : "Some content"}'
'https://myrt.com/REST/2.0/asset'
# Search Assets
diff --git a/lib/RT/Extension/REST2/Resource/Message.pm b/lib/RT/Extension/REST2/Resource/Message.pm
index bf4775e..d47deda 100644
--- a/lib/RT/Extension/REST2/Resource/Message.pm
+++ b/lib/RT/Extension/REST2/Resource/Message.pm
@@ -44,11 +44,51 @@ sub allowed_methods { ['POST'] }
sub charsets_provided { [ 'utf-8' ] }
sub default_charset { 'utf-8' }
sub content_types_provided { [ { 'application/json' => sub {} } ] }
-sub content_types_accepted { [ { 'text/plain' => 'add_message' }, { 'text/html' => 'add_message' }, { 'application/json' => 'from_json' } ] }
+sub content_types_accepted { [ { 'text/plain' => 'add_message' }, { 'text/html' => 'add_message' }, { 'application/json' => 'from_json' }, { 'multipart/form-data' => 'from_multipart' } ] }
+
+sub from_multipart {
+ my $self = shift;
+ my $json_str = $self->request->parameters->{Json};
+ return error_as_json(
+ $self->response,
+ \400, "Json is a required field for multipart/form-data")
+ unless $json_str;
+
+ my $json = JSON::decode_json($json_str);
+
+ my @attachments = $self->request->upload;
+ if (@attachments && $attachments[0] =~ /^Attachment[_\d]*$/i) {
+ $json->{AttachmentsContents} = ()
+ unless $json->{AttachmentsContents};
+
+ foreach my $attach_field (sort @attachments) {
+ next unless $attach_field =~ /^Attachment[_\d]*$/i;
+
+ my $attachment = $self->request->upload($attach_field);
+ open my $filehandle, '<', $attachment->tempname;
+ if (defined $filehandle && length $filehandle) {
+ my ( @content, $buffer );
+ while ( my $bytesread = read( $filehandle, $buffer, 72*57 ) ) {
+ push @content, MIME::Base64::encode_base64($buffer);
+ }
+ close $filehandle;
+
+ push @{$json->{AttachmentsContents}},
+ {
+ FileName => $attachment->filename,
+ FileType => $attachment->headers->{'content-type'},
+ FileContent => join("\n", @content),
+ };
+ }
+ }
+ }
+
+ return $self->from_json($json);
+}
sub from_json {
my $self = shift;
- my $body = JSON::decode_json( $self->request->content );
+ my $body = shift || JSON::decode_json( $self->request->content );
if ($body->{AttachmentsContents}) {
foreach my $attachment (@{$body->{AttachmentsContents}}) {
commit 606e5256bcd32f098109b396b088a3d36082b520
Author: gibus <gibus at easter-eggs.com>
Date: Sat Oct 20 13:57:18 2018 +0200
Add test for attachments with multipart/form-data
Based on public github PR #16
diff --git a/xt/attachments.t b/xt/attachments.t
index 68a8e0d..29cb037 100644
--- a/xt/attachments.t
+++ b/xt/attachments.t
@@ -32,7 +32,7 @@ close $fh;
$image_content = MIME::Base64::encode_base64($image_content);
-# Comment ticket with image and text attachments
+# Comment ticket with image and text attachments through JSON Base64
{
my $payload = {
Content => 'Have you seen this <b>image</b>',
@@ -98,7 +98,7 @@ $image_content = MIME::Base64::encode_base64($image_content);
ok(!$attachments->[3]->Subject);
}
-#Comment ticket with image attachment and no content
+# Comment ticket with image attachment and no content through JSON Base64
{
my $payload = {
Subject => 'No content, just an image',
@@ -147,4 +147,113 @@ $image_content = MIME::Base64::encode_base64($image_content);
ok(!$attachments[2]->Subject);
}
+my $json = JSON->new->utf8;
+
+# Comment ticket with image and text attachments through multipart/form-data
+{
+ my $payload = {
+ Content => 'Have you seen this <b>image</b>',
+ ContentType => 'text/html',
+ Subject => 'HTML comment with PNG image and text file',
+ };
+
+ $HTTP::Request::Common::DYNAMIC_FILE_UPLOAD = 1;
+ my $res = $mech->post("$rest_base_path/ticket/$ticket_id/comment",
+ 'Authorization' => $auth,
+ 'Content_Type' => 'form-data',
+ 'Content' => [
+ 'Json' => $json->encode($payload),
+ 'Attachment_1' => [$image_path, $image_name, 'Content-Type' => 'image/png'],
+ 'Attachment_2' => [undef, 'password', 'Content-Type' => 'text/plain', Content => 'Hey this is secret!']]);
+
+ is($res->code, 201);
+ cmp_deeply($mech->json_response, [re(qr/Comments added|Message recorded/)]);
+
+ my $transaction_id = $ticket->Transactions->Last->id;
+ my @attachments = grep { $_->TransactionId == $transaction_id } @{$ticket->Attachments->ItemsArrayRef};
+
+ # 3 attachments + 1 wrapper
+ is(scalar(@attachments), 4);
+
+ # 1st attachment is wrapper
+ is($attachments[0]->TransactionId, $transaction_id);
+ is($attachments[0]->Parent, 0);
+ is($attachments[0]->Subject, 'HTML comment with PNG image and text file');
+ ok(!$attachments[0]->Filename);
+ is($attachments[0]->ContentType, 'multipart/mixed');
+
+ # 2nd attachment is comment's content
+ is($attachments[1]->Parent, $attachments[0]->id);
+ is($attachments[1]->TransactionId, $transaction_id);
+ is($attachments[1]->ContentType, 'text/html');
+ is($attachments[1]->ContentEncoding, 'none');
+ is($attachments[1]->Content, 'Have you seen this <b>image</b>');
+ ok(!$attachments[1]->Subject);
+
+ # 3rd attachment is image
+ my $expected_encoding = ($RT::Handle->BinarySafeBLOBs) ? 'none' : 'base54';
+ is($attachments[2]->Parent, $attachments[0]->id);
+ is($attachments[2]->TransactionId, $transaction_id);
+ is($attachments[2]->ContentType, 'image/png');
+ is($attachments[2]->ContentEncoding, $expected_encoding);
+ is($attachments[2]->Filename, $image_name);
+ ok(!$attachments[2]->Subject);
+
+ # 4th attachment is text file
+ is($attachments[3]->Parent, $attachments[0]->id);
+ is($attachments[3]->TransactionId, $transaction_id);
+ is($attachments[3]->ContentType, 'text/plain');
+ is($attachments[3]->ContentEncoding, 'none');
+ is($attachments[3]->Filename, 'password');
+ is($attachments[3]->Content, 'Hey this is secret!');
+ ok(!$attachments[3]->Subject);
+}
+
+# Comment ticket with image attachment and no content through multipart/form-data
+{
+ my $payload = {
+ Subject => 'No content, just an image',
+ };
+
+ $HTTP::Request::Common::DYNAMIC_FILE_UPLOAD = 1;
+ my $res = $mech->post("$rest_base_path/ticket/$ticket_id/comment",
+ 'Authorization' => $auth,
+ 'Content_Type' => 'form-data',
+ 'Content' => [
+ 'Json' => $json->encode($payload),
+ 'Attachment1' => [$image_path, $image_name, 'Content-Type' => 'image/png']]);
+
+ is($res->code, 201);
+ cmp_deeply($mech->json_response, [re(qr/Comments added|Message recorded/)]);
+
+ my $transaction_id = $ticket->Transactions->Last->id;
+ my @attachments = grep { $_->TransactionId == $transaction_id } @{$ticket->Attachments->ItemsArrayRef};
+
+ # 2 attachments + 1 wrapper
+ is(scalar(@attachments), 3);
+
+ # 1st attachment is wrapper
+ is($attachments[0]->Parent, 0);
+ is($attachments[0]->Subject, 'No content, just an image');
+ ok(!$attachments[0]->Filename);
+ is($attachments[0]->ContentType, 'multipart/mixed');
+
+ # 2nd attachment is empty comment's content
+ is($attachments[1]->Parent, $attachments[0]->id);
+ is($attachments[1]->TransactionId, $transaction_id);
+ is($attachments[1]->ContentType, 'application/octet-stream');
+ ok(!$attachments[1]->ContentEncoding);
+ ok(!$attachments[1]->Content);
+ ok(!$attachments[1]->Subject);
+
+ # 3rd attachment is image
+ my $expected_encoding = ($RT::Handle->BinarySafeBLOBs) ? 'none' : 'base54';
+ is($attachments[2]->Parent, $attachments[0]->id);
+ is($attachments[2]->TransactionId, $transaction_id);
+ is($attachments[2]->ContentType, 'image/png');
+ is($attachments[2]->ContentEncoding, $expected_encoding);
+ is($attachments[2]->Filename, $image_name);
+ ok(!$attachments[2]->Subject);
+}
+
done_testing;
-----------------------------------------------------------------------
More information about the Bps-public-commit
mailing list