rt 4.2.13 ticket#13852
[freeside.git] / rt / lib / RT / Interface / REST.pm
index e7689f4..24c4bba 100644 (file)
@@ -1,40 +1,40 @@
 # BEGIN BPS TAGGED BLOCK {{{
-# 
+#
 # COPYRIGHT:
-# 
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-#                                          <jesse@bestpractical.com>
-# 
+#
+# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+#                                          <sales@bestpractical.com>
+#
 # (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
 # been provided with this software, but in any event can be snarfed
 # from www.gnu.org.
-# 
+#
 # This work is distributed in the hope that it will be useful, but
 # WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
-# 
+#
 # 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/licenses/old-licenses/gpl-2.0.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
 # 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 BPS TAGGED BLOCK }}}
-
-# lib/RT/Interface/REST.pm
 #
+# END BPS TAGGED BLOCK }}}
 
 package RT::Interface::REST;
+use LWP::MediaTypes qw(guess_media_type);
 use strict;
+use warnings;
 use RT;
 
-BEGIN {
-    use base 'Exporter';
-    use vars qw($VERSION @EXPORT);
-
-    $VERSION = do { my @r = (q$Revision: 1.1.1.8 $ =~ /\d+/g); sprintf "%d."."%02d"x$#r, @r };
-
-    @EXPORT = qw(expand_list form_parse form_compose vpush vsplit);
-}
+use base 'Exporter';
+our @EXPORT = qw(expand_list form_parse form_compose vpush vsplit process_attachments);
 
 sub custom_field_spec {
     my $self    = shift;
     my $capture = shift;
 
-    my $CF_char = '[\sa-z0-9_ :()/-]';
-    my $CF_name = $CF_char . '+';
+    my $CF_name = '[^,]+';
     $CF_name = '(' . $CF_name . ')' if $capture;
 
     my $new_style = 'CF\.\{'.$CF_name.'\}';
@@ -94,7 +86,7 @@ sub expand_list {
     my ($list) = @_;
 
     my @elts;
-    foreach (split /,/, $list) {
+    foreach (split /\s*,\s*/, $list) {
         push @elts, /^(\d+)-(\d+)$/? ($1..$2): $_;
     }
 
@@ -160,8 +152,8 @@ sub form_parse {
                 pop @v while (@v && $v[-1] eq '');
 
                 # Strip longest common leading indent from text.
-                my ($ws, $ls) = ("");
-                foreach $ls (map {/^(\s+)/} @v[1..$#v]) {
+                my $ws = ("");
+                foreach my $ls (map {/^(\s+)/} @v[1..$#v]) {
                     $ws = $ls if (!$ws || length($ls) < length($ws));
                 }
                 s/^$ws// foreach @v;
@@ -189,8 +181,7 @@ sub form_parse {
     }
     push(@forms, [ $c, $o, $k, $e ]) if ($e || $c || @$o);
 
-    my $l;
-    foreach $l (keys %$k) {
+    foreach my $l (keys %$k) {
         $k->{$l} = vsplit($k->{$l}) if (ref $k->{$l} eq 'ARRAY');
     }
 
@@ -200,9 +191,9 @@ sub form_parse {
 # Returns text representing a set of forms.
 sub form_compose {
     my ($forms) = @_;
-    my (@text, $form);
+    my (@text);
 
-    foreach $form (@$forms) {
+    foreach my $form (@$forms) {
         my ($c, $o, $k, $e) = @$form;
         my $text = "";
 
@@ -214,10 +205,10 @@ sub form_compose {
             $text .= $e;
         }
         elsif ($o) {
-            my (@lines, $key);
+            my (@lines);
 
-            foreach $key (@$o) {
-                my ($line, $sp, $v);
+            foreach my $key (@$o) {
+                my ($line, $sp);
                 my @values = (ref $k->{$key} eq 'ARRAY') ?
                                @{ $k->{$key} } :
                                   $k->{$key};
@@ -225,7 +216,7 @@ sub form_compose {
                 $sp = " "x(length("$key: "));
                 $sp = " "x4 if length($sp) > 16;
 
-                foreach $v (@values) {
+                foreach my $v (@values) {
                     $v = '' unless defined $v;
                     if ( $v =~ /\n/) {
                         $v =~ s/^/$sp/gm;
@@ -292,29 +283,101 @@ sub vpush {
 
 # "Normalise" a hash key that's known to be multi-valued.
 sub vsplit {
-    my ($val) = @_;
-    my ($line, $word, @words);
-
-    foreach $line (map {split /\n/} (ref $val eq 'ARRAY') ? @$val : ($val||''))
-    {
-        # XXX: This should become a real parser, ? la Text::ParseWords.
-        $line =~ s/^\s+//;
-        $line =~ s/\s+$//;
-        push @words, split /\s*,\s*/, $line;
+    my ($val, $strip) = @_;
+    my @words;
+    my @values = map {split /\n/} (ref $val eq 'ARRAY' ? @$val : $val);
+
+    foreach my $line (@values) {
+        while ($line =~ /\S/) {
+            $line =~ s/^
+                       \s*   # Trim leading whitespace
+                       (?:
+                           (")   # Quoted string
+                           ((?>[^\\"]*(?:\\.[^\\"]*)*))"
+                       |
+                           (')   # Single-quoted string
+                           ((?>[^\\']*(?:\\.[^\\']*)*))'
+                       |
+                           q\{(.*?)\}  # A perl-ish q{} string; this does
+                                     # no paren balancing, however, and
+                                     # only exists for back-compat
+                       |
+                           (.*?)     # Anything else, until the next comma
+                       )
+                       \s*   # Trim trailing whitespace
+                       (?:
+                           \Z  # Finish at end-of-line
+                       |
+                           ,   # Or a comma
+                       )
+                      //xs or last; # There should be no way this match
+                                    # fails, but add a failsafe to
+                                    # prevent infinite-looping if it
+                                    # somehow does.
+            my ($quote, $quoted) = ($1 ? ($1, $2) : $3 ? ($3, $4) : ('', $5 || $6));
+            # Only unquote the quote character, or the backslash -- and
+            # only if we were originally quoted..
+            if ($5) {
+                $quoted =~ s/([\\'])/\\$1/g;
+                $quote = "'";
+            }
+            if ($strip) {
+                $quoted =~ s/\\([\\$quote])/$1/g if $quote;
+                push @words, $quoted;
+            } else {
+                push @words, "$quote$quoted$quote";
+            }
+        }
     }
-
     return \@words;
 }
 
-eval "require RT::Interface::REST_Vendor";
-if ($@ && $@ !~ qr{^Can't locate RT/Interface/REST_Vendor.pm}) {
-    die $@;
-};
+sub process_attachments {
+    my $entity = shift;
+    my @list = @_;
+    return 1 unless @list;
+
+    my $m = $HTML::Mason::Commands::m;
+    my $cgi = $m->cgi_object;
+
+    my $i = 1;
+    foreach my $e ( @list ) {
+
+        my $fh = $cgi->upload("attachment_$i");
+        return (0, "No attachment for $e") unless $fh;
+
+        local $/=undef;
+
+        my $file = $e;
+        $file =~ s#^.*[\\/]##;
+
+        my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
+
+        my $buf;
+        while (sysread($fh, $buf, 8192)) {
+            syswrite($tmp_fh, $buf);
+        }
+
+        my $info = $cgi->uploadInfo($fh);
+        # If Content-ID exists for attachment then we need multipart/related
+        # to be able to refer to this Content-Id in core of mime message
+        if($info->{'Content-ID'}) {
+            $entity->head->set('Content-Type', 'multipart/related');
+        }
+        my $new_entity = $entity->attach(
+            Path => $tmp_fn,
+            Type => $info->{'Content-Type'} || guess_media_type($tmp_fn),
+            Filename => $file,
+            Disposition => $info->{'Content-Disposition'} || "attachment",
+            'Content-ID' => $info->{'Content-ID'},
+        );
+        $new_entity->bodyhandle->{'_dirty_hack_to_save_a_ref_tmp_fh'} = $tmp_fh;
+        $i++;
+    }
+    return (1);
+}
 
-eval "require RT::Interface::REST_Local";
-if ($@ && $@ !~ qr{^Can't locate RT/Interface/REST_Local.pm}) {
-    die $@;
-};
+RT::Base->_ImportOverlays();
 
 1;