2 * multistatus.c : parse multistatus (error) responses.
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
31 #include <serf_bucket_types.h>
33 #include "svn_private_config.h"
35 #include "svn_dirent_uri.h"
37 #include "svn_string.h"
39 #include "svn_props.h"
40 #include "svn_dirent_uri.h"
42 #include "private/svn_dep_compat.h"
43 #include "private/svn_fspath.h"
47 /* The current state of our XML parsing. */
48 typedef enum iprops_state_e {
49 INITIAL = XML_STATE_INITIAL,
57 MS_PROPSTAT_PROP_NAME,
59 MS_PROPSTAT_RESPONSEDESCRIPTION,
61 MS_PROPSTAT_ERROR_HUMANREADABLE,
64 MS_RESPONSE_RESPONSEDESCRIPTION,
66 MS_RESPONSE_ERROR_HUMANREADABLE,
68 MS_MULTISTATUS_RESPONSEDESCRIPTION,
76 <D:multistatus xmlns:D="DAV:">
78 <D:href>http://something</D:href>
79 <!-- Possibly multiple D:href elements -->
80 <D:status>HTTP/1.1 500 failed</D:status>
82 <S:human-readable code="12345">
86 <D:responsedescription>
87 Human readable description
88 </D:responsedescription>
89 <D:location>http://redirected</D:location>
94 Or for property operations:
96 <D:multistatus xmlns:D="DAV:">
98 <D:href>http://somewhere-else</D:href>
100 <D:propname><C:myprop /></D:propname>
101 <D:status>HTTP/1.1 499 failed</D:status>
103 <S:human-readable code="12345">
104 Some Subversion error
107 <D:responsedescription>
108 Human readable description
109 </D:responsedescription>
111 <D:status>HTTP/1.1 499 failed</D:status>
113 <S:human-readable code="12345">
114 Some Subversion error
117 <D:responsedescription>
118 Human readable description
119 </D:responsedescription>
120 <D:location>http://redirected</D:location>
121 <D:responsedescription>
123 <D:responsedescription>
126 Or on request failures
128 <X:some-error xmlns="QQ" />
129 <D:human-readable code="12345">
130 Some Subversion error
136 #define S_ SVN_XML_NAMESPACE
137 #define M_ "http://apache.org/dav/xmlns"
138 static const svn_ra_serf__xml_transition_t multistatus_ttable[] = {
139 { INITIAL, D_, "multistatus", MS_MULTISTATUS,
140 FALSE, { NULL }, FALSE },
142 { MS_MULTISTATUS, D_, "responsedescription", MS_MULTISTATUS_RESPONSEDESCRIPTION,
143 TRUE, { NULL }, TRUE },
146 { MS_MULTISTATUS, D_, "response", MS_RESPONSE,
147 FALSE, { NULL }, TRUE },
149 { MS_RESPONSE, D_, "href", MS_RESPONSE_HREF,
150 TRUE, { NULL }, TRUE },
153 { MS_RESPONSE, D_, "propstat", MS_PROPSTAT,
154 FALSE, { NULL }, TRUE },
156 { MS_PROPSTAT, D_, "prop", MS_PROPSTAT_PROP,
157 FALSE, { NULL }, FALSE },
159 { MS_PROPSTAT_PROP, "", "*", MS_PROPSTAT_PROP_NAME,
160 FALSE, { NULL }, FALSE },
162 { MS_PROPSTAT, D_, "status", MS_PROPSTAT_STATUS,
163 TRUE, { NULL }, TRUE },
165 { MS_PROPSTAT, D_, "responsedescription", MS_PROPSTAT_RESPONSEDESCRIPTION,
166 TRUE, { NULL }, TRUE },
168 { MS_PROPSTAT, D_, "error", MS_PROPSTAT_ERROR,
169 FALSE, { NULL }, FALSE },
171 { MS_PROPSTAT_ERROR, M_, "human-readable", MS_PROPSTAT_ERROR_HUMANREADABLE,
172 TRUE, { "?errcode", NULL }, TRUE },
176 { MS_RESPONSE, D_, "status", MS_RESPONSE_STATUS,
177 TRUE, { NULL }, TRUE },
179 { MS_RESPONSE, D_, "responsedescription", MS_RESPONSE_RESPONSEDESCRIPTION,
180 TRUE, { NULL }, TRUE },
182 { MS_RESPONSE, D_, "error", MS_RESPONSE_ERROR,
183 FALSE, { NULL }, TRUE },
185 { MS_RESPONSE_ERROR, M_, "human-readable", MS_RESPONSE_ERROR_HUMANREADABLE,
186 TRUE, { "?errcode", NULL }, TRUE },
190 { MS_MULTISTATUS, D_, "responsedescription", MS_MULTISTATUS_RESPONSEDESCRIPTION,
191 TRUE, { NULL }, TRUE },
194 { INITIAL, D_, "error", D_ERROR,
195 FALSE, { NULL }, TRUE },
197 { D_ERROR, S_, "error", S_ERROR,
198 FALSE, { NULL }, FALSE },
200 { D_ERROR, M_, "human-readable", M_ERROR_HUMANREADABLE,
201 TRUE, { "?errcode", NULL }, TRUE },
206 /* Given a string like "HTTP/1.1 500 (status)" in BUF, parse out the numeric
207 status code into *STATUS_CODE_OUT. Ignores leading whitespace. */
209 parse_status_line(int *status_code_out,
211 const char *status_line,
212 apr_pool_t *result_pool,
213 apr_pool_t *scratch_pool)
218 svn_stringbuf_t *temp_buf = svn_stringbuf_create(status_line, scratch_pool);
220 svn_stringbuf_strip_whitespace(temp_buf);
221 token = apr_strtok(temp_buf->data, " \t\r\n", &tok_status);
223 token = apr_strtok(NULL, " \t\r\n", &tok_status);
225 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
226 _("Malformed DAV:status '%s'"),
228 err = svn_cstring_atoi(status_code_out, token);
230 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
231 _("Malformed DAV:status '%s'"),
234 token = apr_strtok(NULL, " \t\r\n", &tok_status);
236 *reason = apr_pstrdup(result_pool, token);
242 typedef struct error_item_t
245 const char *propname;
248 const char *http_reason;
249 apr_status_t apr_err;
255 multistatus_opened(svn_ra_serf__xml_estate_t *xes,
258 const svn_ra_serf__dav_props_t *tag,
259 apr_pool_t *scratch_pool)
261 /*struct svn_ra_serf__server_error_t *server_error = baton;*/
262 const char *propname;
264 switch (entered_state)
266 case MS_PROPSTAT_PROP_NAME:
267 if (strcmp(tag->xmlns, SVN_DAV_PROP_NS_SVN) == 0)
268 propname = apr_pstrcat(scratch_pool, SVN_PROP_PREFIX, tag->name,
271 propname = tag->name;
272 svn_ra_serf__xml_note(xes, MS_PROPSTAT, "propname", propname);
275 /* This toggles an has error boolean in libsvn_ra_neon in 1.7 */
283 multistatus_closed(svn_ra_serf__xml_estate_t *xes,
286 const svn_string_t *cdata,
288 apr_pool_t *scratch_pool)
290 struct svn_ra_serf__server_error_t *server_error = baton;
294 switch (leaving_state)
296 case MS_RESPONSE_HREF:
301 result = apr_uri_parse(scratch_pool, cdata->data, &uri);
303 return svn_ra_serf__wrap_err(result, NULL);
304 svn_ra_serf__xml_note(xes, MS_RESPONSE, "path",
305 svn_urlpath__canonicalize(uri.path, scratch_pool));
308 case MS_RESPONSE_STATUS:
309 svn_ra_serf__xml_note(xes, MS_RESPONSE, "status", cdata->data);
311 case MS_RESPONSE_ERROR_HUMANREADABLE:
312 svn_ra_serf__xml_note(xes, MS_RESPONSE, "human-readable", cdata->data);
313 errcode = svn_hash_gets(attrs, "errcode");
315 svn_ra_serf__xml_note(xes, MS_RESPONSE, "errcode", errcode);
318 if ((status = svn_hash__get_cstring(attrs, "status", NULL)) != NULL)
322 item = apr_pcalloc(server_error->pool, sizeof(*item));
324 item->path = apr_pstrdup(server_error->pool,
325 svn_hash_gets(attrs, "path"));
327 SVN_ERR(parse_status_line(&item->http_status,
333 /* Do we have a mod_dav specific message? */
334 item->message = svn_hash_gets(attrs, "human-readable");
338 if ((errcode = svn_hash_gets(attrs, "errcode")) != NULL)
342 SVN_ERR(svn_cstring_atoi64(&val, errcode));
343 item->apr_err = (apr_status_t)val;
346 item->message = apr_pstrdup(server_error->pool, item->message);
349 item->message = apr_pstrdup(server_error->pool,
350 svn_hash_gets(attrs, "description"));
352 APR_ARRAY_PUSH(server_error->items, error_item_t *) = item;
357 case MS_PROPSTAT_STATUS:
358 svn_ra_serf__xml_note(xes, MS_PROPSTAT, "status", cdata->data);
360 case MS_PROPSTAT_ERROR_HUMANREADABLE:
361 svn_ra_serf__xml_note(xes, MS_PROPSTAT, "human-readable", cdata->data);
362 errcode = svn_hash_gets(attrs, "errcode");
364 svn_ra_serf__xml_note(xes, MS_PROPSTAT, "errcode", errcode);
366 case MS_PROPSTAT_RESPONSEDESCRIPTION:
367 svn_ra_serf__xml_note(xes, MS_PROPSTAT, "description",
372 if ((status = svn_hash__get_cstring(attrs, "status", NULL)) != NULL)
374 apr_hash_t *response_attrs;
377 response_attrs = svn_ra_serf__xml_gather_since(xes, MS_RESPONSE);
378 item = apr_pcalloc(server_error->pool, sizeof(*item));
380 item->path = apr_pstrdup(server_error->pool,
381 svn_hash_gets(response_attrs, "path"));
382 item->propname = apr_pstrdup(server_error->pool,
383 svn_hash_gets(attrs, "propname"));
385 SVN_ERR(parse_status_line(&item->http_status,
391 /* Do we have a mod_dav specific message? */
392 item->message = svn_hash_gets(attrs, "human-readable");
396 if ((errcode = svn_hash_gets(attrs, "errcode")) != NULL)
400 SVN_ERR(svn_cstring_atoi64(&val, errcode));
401 item->apr_err = (apr_status_t)val;
404 item->message = apr_pstrdup(server_error->pool, item->message);
407 item->message = apr_pstrdup(server_error->pool,
408 svn_hash_gets(attrs, "description"));
411 APR_ARRAY_PUSH(server_error->items, error_item_t *) = item;
415 case M_ERROR_HUMANREADABLE:
416 svn_ra_serf__xml_note(xes, D_ERROR, "human-readable", cdata->data);
417 errcode = svn_hash_gets(attrs, "errcode");
419 svn_ra_serf__xml_note(xes, D_ERROR, "errcode", errcode);
426 item = apr_pcalloc(server_error->pool, sizeof(*item));
428 item->http_status = server_error->handler->sline.code;
430 /* Do we have a mod_dav specific message? */
431 item->message = svn_hash__get_cstring(attrs, "human-readable",
436 if ((errcode = svn_hash_gets(attrs, "errcode")) != NULL)
440 SVN_ERR(svn_cstring_atoi64(&val, errcode));
441 item->apr_err = (apr_status_t)val;
444 item->message = apr_pstrdup(server_error->pool, item->message);
448 APR_ARRAY_PUSH(server_error->items, error_item_t *) = item;
455 svn_ra_serf__server_error_create(svn_ra_serf__handler_t *handler,
456 apr_pool_t *scratch_pool)
458 svn_ra_serf__server_error_t *server_error = handler->server_error;
459 svn_error_t *err = NULL;
462 for (i = 0; i < server_error->items->nelts; i++)
464 const error_item_t *item;
467 svn_error_t *new_err;
469 item = APR_ARRAY_IDX(server_error->items, i, error_item_t *);
471 if (!item->apr_err && item->http_status == 200)
473 continue; /* Success code */
475 else if (!item->apr_err && item->http_status == 424 && item->propname)
477 continue; /* Failed because other PROPPATCH operations failed */
481 status = item->apr_err;
483 switch (item->http_status)
486 continue; /* Not an error */
492 status = SVN_ERR_RA_DAV_RELOCATED;
495 status = SVN_ERR_RA_DAV_FORBIDDEN;
498 status = SVN_ERR_FS_NOT_FOUND;
501 status = SVN_ERR_FS_CONFLICT;
504 status = SVN_ERR_RA_DAV_PRECONDITION_FAILED;
507 status = SVN_ERR_FS_NO_LOCK_TOKEN;
510 status = SVN_ERR_RA_DAV_REQUEST_FAILED;
513 status = SVN_ERR_UNSUPPORTED_FEATURE;
517 status = err->apr_err; /* Just use previous */
519 status = SVN_ERR_RA_DAV_REQUEST_FAILED;
523 if (item->message && *item->message)
525 svn_stringbuf_t *sb = svn_stringbuf_create(item->message,
528 svn_stringbuf_strip_whitespace(sb);
531 else if (item->propname)
533 message = apr_psprintf(scratch_pool,
534 _("Property operation on '%s' failed"),
539 /* Yuck: Older servers sometimes assume that we get convertable
540 apr error codes, while mod_dav_svn just produces a blank
541 text error, because err->message is NULL. */
542 serf_status_line sline;
543 svn_error_t *tmp_err;
545 memset(&sline, 0, sizeof(sline));
546 sline.code = item->http_status;
547 sline.reason = item->http_reason;
549 tmp_err = svn_ra_serf__error_on_status(sline, item->path, NULL);
551 message = (tmp_err && tmp_err->message)
552 ? apr_pstrdup(scratch_pool, tmp_err->message)
553 : _("<blank error>");
554 svn_error_clear(tmp_err);
557 SVN_ERR_ASSERT(status > 0);
558 new_err = svn_error_create(status, NULL, message);
561 new_err = svn_error_createf(new_err->apr_err, new_err,
562 _("While handling the '%s' property on '%s':"),
563 item->propname, item->path);
565 new_err = svn_error_createf(new_err->apr_err, new_err,
566 _("While handling the '%s' path:"),
569 err = svn_error_compose_create(
574 /* Theoretically a 207 status can have a 'global' description without a
575 global STATUS that summarizes the final result of property/href
578 We should wrap that around the existing errors if there is one.
580 But currently I don't see how mod_dav ever sets that value */
584 /* We should fail.... but why... Who installed us? */
585 err = svn_error_trace(svn_ra_serf__unexpected_status(handler));
593 svn_ra_serf__setup_error_parsing(svn_ra_serf__server_error_t **server_err,
594 svn_ra_serf__handler_t *handler,
595 svn_boolean_t expect_207_only,
596 apr_pool_t *result_pool,
597 apr_pool_t *scratch_pool)
599 svn_ra_serf__server_error_t *ms_baton;
600 svn_ra_serf__handler_t *tmp_handler;
602 int *expected_status = apr_pcalloc(result_pool,
603 2 * sizeof(expected_status[0]));
605 expected_status[0] = handler->sline.code;
607 ms_baton = apr_pcalloc(result_pool, sizeof(*ms_baton));
608 ms_baton->pool = result_pool;
610 ms_baton->items = apr_array_make(result_pool, 4, sizeof(error_item_t *));
611 ms_baton->handler = handler;
613 ms_baton->xmlctx = svn_ra_serf__xml_context_create(multistatus_ttable,
620 tmp_handler = svn_ra_serf__create_expat_handler(handler->session,
625 /* Ugly way to obtain expat_handler() */
626 tmp_handler->sline = handler->sline;
627 ms_baton->response_handler = tmp_handler->response_handler;
628 ms_baton->response_baton = tmp_handler->response_baton;
630 *server_err = ms_baton;
636 /* Implements svn_ra_serf__response_handler_t */
638 svn_ra_serf__handle_multistatus_only(serf_request_t *request,
639 serf_bucket_t *response,
641 apr_pool_t *scratch_pool)
643 svn_ra_serf__handler_t *handler = baton;
645 /* This function is just like expect_empty_body() except for the
646 XML parsing callbacks. We are looking for very limited pieces of
647 the multistatus response. */
649 /* We should see this just once, in order to initialize SERVER_ERROR.
650 At that point, the core error processing will take over. If we choose
651 not to parse an error, then we'll never return here (because we
652 change the response handler). */
653 SVN_ERR_ASSERT(handler->server_error == NULL);
659 hdrs = serf_bucket_response_get_headers(response);
660 val = serf_bucket_headers_get(hdrs, "Content-Type");
661 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
663 svn_ra_serf__server_error_t *server_err;
665 SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err,
668 handler->handler_pool,
669 handler->handler_pool));
671 handler->server_error = server_err;
675 /* The body was not text/xml, so we don't know what to do with it.
676 Toss anything that arrives. */
677 handler->discard_body = TRUE;
681 /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
682 to call the response handler again. That will start up the XML parsing,
683 or it will be dropped on the floor (per the decision above). */
688 svn_ra_serf__handle_server_error(svn_ra_serf__server_error_t *server_error,
689 svn_ra_serf__handler_t *handler,
690 serf_request_t *request,
691 serf_bucket_t *response,
692 apr_status_t *serf_status,
693 apr_pool_t *scratch_pool)
697 err = server_error->response_handler(request, response,
698 server_error->response_baton,
700 /* If we do not receive an error or it is a non-transient error, return
703 APR_EOF will be returned when parsing is complete.
705 APR_EAGAIN & WAIT_CONN may be intermittently returned as we proceed through
706 parsing and the network has no more data right now. If we receive that,
707 clear the error and return - allowing serf to wait for more data.
709 if (!err || SERF_BUCKET_READ_ERROR(err->apr_err))
711 /* Perhaps we already parsed some server generated message. Let's pass
712 all information we can get.*/
714 err = svn_error_compose_create(
715 svn_ra_serf__server_error_create(handler, scratch_pool),
718 return svn_error_trace(err);
721 if (!APR_STATUS_IS_EOF(err->apr_err))
723 *serf_status = err->apr_err;
724 svn_error_clear(err);
728 /* Clear the EOF. We don't need it as subversion error. */
729 svn_error_clear(err);
730 *serf_status = APR_EOF;
732 /* On PROPPATCH we always get status 207, which may or may not imply an
733 error status, but let's keep it generic and just do the check for
735 if (handler->sline.code == 207 /* MULTISTATUS */)
737 svn_boolean_t have_error = FALSE;
740 for (i = 0; i < server_error->items->nelts; i++)
742 const error_item_t *item;
743 item = APR_ARRAY_IDX(server_error->items, i, error_item_t *);
745 if (!item->apr_err && item->http_status == 200)
747 continue; /* Success code */
755 handler->server_error = NULL; /* We didn't have a server error */