]> git.uio.no Git - usit-rt.git/blame - share/html/REST/1.0/dhandler
Upgrade to 4.2.2
[usit-rt.git] / share / html / REST / 1.0 / dhandler
CommitLineData
84fb5b46
MKG
1%# BEGIN BPS TAGGED BLOCK {{{
2%#
3%# COPYRIGHT:
4%#
320f0092 5%# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
84fb5b46
MKG
6%# <sales@bestpractical.com>
7%#
8%# (Except where explicitly superseded by other copyright notices)
9%#
10%#
11%# LICENSE:
12%#
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
16%# from www.gnu.org.
17%#
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.
22%#
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.
28%#
29%#
30%# CONTRIBUTION SUBMISSION POLICY:
31%#
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.)
37%#
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.
46%#
47%# END BPS TAGGED BLOCK }}}
48%# REST/1.0/dhandler
49%#
50<%ARGS>
51@id => ()
52$fields => undef
53$format => undef
54$content => undef
55</%ARGS>
56<%INIT>
57use RT::Interface::REST;
58
59my $output = "";
60my $status = "200 Ok";
61my $object = $m->dhandler_arg;
62
63my $name = qr{[\w.-]+};
64my $list = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+';
403d7b0b 65my $label = '[^,\\/]+';
84fb5b46
MKG
66my $field = RT::Interface::REST->field_spec;
67my $labels = "(?:$label,)*$label";
68
69# We must handle requests such as the following:
70#
71# 1. http://.../REST/1.0/show (with a list of object specifications).
72# 2. http://.../REST/1.0/edit (with a self-contained list of forms).
73# 3. http://.../REST/1.0/ticket/show (implicit type specification).
74# http://.../REST/1.0/ticket/edit
75# 4. http://.../REST/1.0/ticket/nn (all possibly with a single form).
76# http://.../REST/1.0/ticket/nn/history
77# http://.../REST/1.0/ticket/nn/comment
78# http://.../REST/1.0/ticket/nn/attachment/1
79#
80# Objects are specified by their type, and either a unique numeric ID,
81# or a unique name (e.g. ticket/1, queue/foo). Multiple objects of the
82# same type may be specified by a comma-separated list of identifiers
83# (e.g., user/ams,rai or ticket/1-3,5-7).
84#
85# Ultimately, we want a list of object specifications to operate upon.
86# The URLs in (4) provide enough information to identify an object. We
87# will assemble submitted information into that format in other cases.
88#
89my (@objects, $forms);
90my $utype;
91
92if ($object eq 'show' || # $REST/show
93 (($utype) = ($object =~ m{^($name)/show$}))) # $REST/ticket/show
94{
95 # We'll convert type/range specifications ("ticket/1-3,7-9/history")
96 # into a list of singular object specifications ("ticket/1/history").
97 # If the URL specifies a type, we'll accept only that one.
98 foreach my $id (@id) {
99 $id =~ s|^(?:$utype/)?|$utype/| if $utype;
100 if (my ($type, $oids, $extra) =
101 ($id =~ m#^($name)/($list|$labels)(?:(/.*))?$#o))
102 {
af59614d
MKG
103 $extra ||= '';
104 my ($attr, $args) = $extra =~ m{^(?:/($name)(?:/(.*))?)?$}o;
105 my $tids;
106 if ($attr and $attr eq 'history' and $args) {
107 ($tids) = $args =~ m#id/(\d.*)#o;
108 }
109 # expand transaction and attachment range specifications
110 # (if applicable)
111 foreach my $oid (expand_list($oids)) {
112 if ($tids) {
113 push(@objects, "$type/$oid/$attr/id/$_") for expand_list($tids);
114 } else {
115 push(@objects, "$type/$oid$extra");
116 }
117 }
118 }
84fb5b46
MKG
119 else {
120 $status = "400 Bad Request";
121 $output = "Invalid object ID specified: '$id'";
122 goto OUTPUT;
123 }
124 }
125}
126elsif ($object eq 'edit' || # $REST/edit
127 (($utype) = ($object =~ m{^($name)/edit$}))) # $REST/ticket/edit
128{
129 # We'll make sure each of the submitted forms is syntactically valid
130 # and sufficiently identifies an object to operate upon, then add to
131 # the object list as above.
132 my @output;
133
134 $forms = form_parse($content);
135 foreach my $form (@$forms) {
136 my ($c, $o, $k, $e) = @$form;
137
138 if ($e) {
139 push @output, [ "# Syntax error.", $o, $k, $e ];
140 }
141 else {
142 my ($type, $id);
143
144 # Look for matching types in the ID, form, and URL.
145 $type = $utype || $k->{id};
146 $type =~ s|^([^/]+)/\d+$|$1| if !$utype;
147 $type =~ s|^(?:$utype)?|$utype/| if $utype;
148 $type =~ s|/$|| if $type;
149
150 if (exists $k->{id}) {
151 $id = $k->{id};
152 $id =~ s|^(?:$type/)?|$type/| if $type;
153
154 if ($id =~ m#^$name/(?:$label|\d+)(?:/.*)?#o) {
155 push @objects, $id;
156 }
157 else {
158 push @output, [ "# Invalid object ID: '$id'", $o, $k, $e ];
159 }
160 }
161 else {
162 push @output, [ "# No object ID specified.", $o, $k, $e ];
163 }
164 }
165 }
166 # If we saw any errors at this stage, we won't process any part of
167 # the submitted data.
168 if (@output) {
169 unshift @output, [ "# Please resubmit with errors corrected." ];
170 $status = "409 Syntax Error";
171 $output = form_compose(\@output);
172 goto OUTPUT;
173 }
174}
175else {
176 # We'll assume that this is in the correct format already. Otherwise
177 # it will be caught by the loop below.
178 push @objects, $object;
179
180 if ($content) {
181 $forms = form_parse($content);
182
183 if (@$forms > 1) {
184 $status = "400 Bad Request";
185 $output = "You may submit only one form to this object.";
186 goto OUTPUT;
187 }
188
189 my ($c, $o, $k, $e) = @{ $forms->[0] };
190 if ($e) {
191 $status = "409 Syntax Error";
192 $output = form_compose([ ["# Syntax error.", $o, $k, $e] ]);
193 goto OUTPUT;
194 }
195 }
196}
197
198# Make sure we have something to do.
199unless (@objects) {
200 $status = "400 Bad Request";
201 $output = "No objects specified.";
202 goto OUTPUT;
203}
204
205# Parse and validate any field specifications.
206my (%fields, @fields);
207if ($fields) {
208 unless ($fields =~ /^(?:$field,)*$field$/) {
209 $status = "400 Bad Request";
210 $output = "Invalid field specification: $fields";
211 goto OUTPUT;
212 }
213 @fields = map lc, split /\s*,\s*/, $fields;
214 @fields{@fields} = ();
215 unless (exists $fields{id}) {
216 unshift @fields, "id";
217 $fields{id} = ();
218 }
219
220 # canonicalize cf-foo to cf.{foo}
221 for my $field (@fields) {
222 if ($field =~ /^(c(?:ustom)?f(?:ield)?)-(.+)/) {
223 $fields{"cf.{$2}"} = delete $fields{"$1-$2"};
224
225 # overwrite the element in @fields
226 $field = "cf.{$2}";
227 }
228 }
229}
230
231my (@comments, @output);
232
233foreach $object (@objects) {
234 my ($handler, $type, $id, $attr, $args);
235 my ($c, $o, $k, $e) = ("", ["id"], {id => $object}, 0);
236
237 my $i = 0;
238 if ($object =~ m{^($name)/(\d+|$label)(?:/($name)(?:/(.*))?)?$}o ||
239 $object =~ m{^($name)/(new)$}o)
240 {
241 ($type, $id, $attr, $args) = ($1, $2, ($3 || 'default'), $4);
242 $handler = "Forms/$type/$attr";
243
244 unless ($m->comp_exists($handler)) {
403d7b0b 245 $args = defined $args ? "$attr/$args" : $attr;
84fb5b46
MKG
246 $handler = "Forms/$type/default";
247
248 unless ($m->comp_exists($handler)) {
249 $i = 2;
250 $c = "# Unknown object type: $type";
251 }
252 }
253 elsif ($id ne 'new' && $id !~ /^\d+$/) {
254 my $ns = "Forms/$type/ns";
255
256 # Can we resolve named objects?
257 unless ($m->comp_exists($ns)) {
258 $i = 3;
259 $c = "# Objects of type $type must be specified by numeric id.";
260 }
261 else {
262 my ($n, $s) = $m->comp("Forms/$type/ns", id => $id);
263 if ($n <= 0) { $i = 4; $c = "# $s"; }
264 else { $i = 0; $id = $n; }
265 }
266 }
267 else {
268 $i = 0;
269 }
270 }
271 else {
272 $i = 1;
273 $c = "# Invalid object specification: '$object'";
274 }
275
276 if ($i != 0) {
277 if ($content) {
278 (undef, $o, $k, $e) = @{ shift @$forms };
279 }
280 push @output, [ $c, $o, $k ];
281 next;
282 }
283
284 unless ($content) {
285 my $d = $m->comp($handler, id => $id, args => $args, format => $format, fields => \%fields);
286 my ($c, $o, $k, $e) = @$d;
287
288 if (!$e && @$o && keys %fields) {
289 my %lk = map { lc $_ => $_ } keys %$k;
290 @$o = map { $lk{$_} } @fields;
291 foreach my $key (keys %$k) {
292 delete $k->{$key} unless exists $fields{lc $key};
293 }
294 }
295 push(@output, [ $c, $o, $k ]) if ($c || @$o || keys %$k);
296 }
297 else {
298 my ($c, $o, $k, $e) = @{ shift @$forms };
299 my $d = $m->comp($handler, id => $id, args => $args, format => $format,
300 changes => $k);
301 ($c, $o, $k, $e) = @$d;
302
303 # We won't pass $e through to compose, trusting instead that the
304 # handler added suitable comments for the user.
305 if ($e) {
306 if (@$o) {
307 $status = "409 Syntax Error";
308 }
309 else {
310 $status = "400 Bad Request";
311 }
312 push @output, [ $c, $o, $k ];
313 }
314 else {
315 push @comments, $c;
316 }
317 }
318}
319
320unshift(@output, [ join "\n", @comments ]) if @comments;
321$output = form_compose(\@output);
322
323OUTPUT:
324$m->out("RT/".$RT::VERSION ." ".$status ."\n\n$output\n") if ($output || $status !~ /^200/);
325return;
326</%INIT>