1/* 2 * multistatus.c : parse multistatus (error) responses. 3 * 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 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 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 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25 26#include <assert.h> 27 28#include <apr.h> 29 30#include <serf.h> 31#include <serf_bucket_types.h> 32 33#include "svn_private_config.h" 34#include "svn_hash.h" 35#include "svn_dirent_uri.h" 36#include "svn_path.h" 37#include "svn_string.h" 38#include "svn_xml.h" 39#include "svn_props.h" 40#include "svn_dirent_uri.h" 41 42#include "private/svn_dep_compat.h" 43#include "private/svn_fspath.h" 44 45#include "ra_serf.h" 46 47/* The current state of our XML parsing. */ 48typedef enum iprops_state_e { 49 INITIAL = XML_STATE_INITIAL, 50 MS_MULTISTATUS, 51 52 MS_RESPONSE, 53 MS_RESPONSE_HREF, 54 55 MS_PROPSTAT, 56 MS_PROPSTAT_PROP, 57 MS_PROPSTAT_PROP_NAME, 58 MS_PROPSTAT_STATUS, 59 MS_PROPSTAT_RESPONSEDESCRIPTION, 60 MS_PROPSTAT_ERROR, 61 MS_PROPSTAT_ERROR_HUMANREADABLE, 62 63 MS_RESPONSE_STATUS, 64 MS_RESPONSE_RESPONSEDESCRIPTION, 65 MS_RESPONSE_ERROR, 66 MS_RESPONSE_ERROR_HUMANREADABLE, 67 68 MS_MULTISTATUS_RESPONSEDESCRIPTION, 69 70 D_ERROR, 71 S_ERROR, 72 M_ERROR_HUMANREADABLE 73} iprops_state_e; 74 75/* 76 <D:multistatus xmlns:D="DAV:"> 77 <D:response> 78 <D:href>http://something</D:href> 79 <!-- Possibly multiple D:href elements --> 80 <D:status>HTTP/1.1 500 failed</D:status> 81 <D:error> 82 <S:human-readable code="12345"> 83 Some Subversion error 84 </S:human-readable> 85 </D:error> 86 <D:responsedescription> 87 Human readable description 88 </D:responsedescription> 89 <D:location>http://redirected</D:location> 90 </D:response> 91 ... 92 </D:multistatus> 93 94 Or for property operations: 95 96 <D:multistatus xmlns:D="DAV:"> 97 <D:response> 98 <D:href>http://somewhere-else</D:href> 99 <D:propstat> 100 <D:propname><C:myprop /></D:propname> 101 <D:status>HTTP/1.1 499 failed</D:status> 102 <D:error> 103 <S:human-readable code="12345"> 104 Some Subversion error 105 </S:human-readable> 106 </D:error> 107 <D:responsedescription> 108 Human readable description 109 </D:responsedescription> 110 </D:propstat> 111 <D:status>HTTP/1.1 499 failed</D:status> 112 <D:error> 113 <S:human-readable code="12345"> 114 Some Subversion error 115 </S:human-readable> 116 </D:error> 117 <D:responsedescription> 118 Human readable description 119 </D:responsedescription> 120 <D:location>http://redirected</D:location> 121 <D:responsedescription> 122 Global description 123 <D:responsedescription> 124 </D:multistatus> 125 126 Or on request failures 127 <D:error> 128 <X:some-error xmlns="QQ" /> 129 <D:human-readable code="12345"> 130 Some Subversion error 131 </D:human-readable> 132 </D:error> 133 */ 134 135#define D_ "DAV:" 136#define S_ SVN_XML_NAMESPACE 137#define M_ "http://apache.org/dav/xmlns" 138static const svn_ra_serf__xml_transition_t multistatus_ttable[] = { 139 { INITIAL, D_, "multistatus", MS_MULTISTATUS, 140 FALSE, { NULL }, FALSE }, 141 142 { MS_MULTISTATUS, D_, "responsedescription", MS_MULTISTATUS_RESPONSEDESCRIPTION, 143 TRUE, { NULL }, TRUE }, 144 145 /* <response> */ 146 { MS_MULTISTATUS, D_, "response", MS_RESPONSE, 147 FALSE, { NULL }, TRUE }, 148 149 { MS_RESPONSE, D_, "href", MS_RESPONSE_HREF, 150 TRUE, { NULL }, TRUE }, 151 152 /* <propstat> */ 153 { MS_RESPONSE, D_, "propstat", MS_PROPSTAT, 154 FALSE, { NULL }, TRUE }, 155 156 { MS_PROPSTAT, D_, "prop", MS_PROPSTAT_PROP, 157 FALSE, { NULL }, FALSE }, 158 159 { MS_PROPSTAT_PROP, "", "*", MS_PROPSTAT_PROP_NAME, 160 FALSE, { NULL }, FALSE }, 161 162 { MS_PROPSTAT, D_, "status", MS_PROPSTAT_STATUS, 163 TRUE, { NULL }, TRUE }, 164 165 { MS_PROPSTAT, D_, "responsedescription", MS_PROPSTAT_RESPONSEDESCRIPTION, 166 TRUE, { NULL }, TRUE }, 167 168 { MS_PROPSTAT, D_, "error", MS_PROPSTAT_ERROR, 169 FALSE, { NULL }, FALSE }, 170 171 { MS_PROPSTAT_ERROR, M_, "human-readable", MS_PROPSTAT_ERROR_HUMANREADABLE, 172 TRUE, { "?errcode", NULL }, TRUE }, 173 /* </propstat> */ 174 175 176 { MS_RESPONSE, D_, "status", MS_RESPONSE_STATUS, 177 TRUE, { NULL }, TRUE }, 178 179 { MS_RESPONSE, D_, "responsedescription", MS_RESPONSE_RESPONSEDESCRIPTION, 180 TRUE, { NULL }, TRUE }, 181 182 { MS_RESPONSE, D_, "error", MS_RESPONSE_ERROR, 183 FALSE, { NULL }, TRUE }, 184 185 { MS_RESPONSE_ERROR, M_, "human-readable", MS_RESPONSE_ERROR_HUMANREADABLE, 186 TRUE, { "?errcode", NULL }, TRUE }, 187 188 /* </response> */ 189 190 { MS_MULTISTATUS, D_, "responsedescription", MS_MULTISTATUS_RESPONSEDESCRIPTION, 191 TRUE, { NULL }, TRUE }, 192 193 194 { INITIAL, D_, "error", D_ERROR, 195 FALSE, { NULL }, TRUE }, 196 197 { D_ERROR, S_, "error", S_ERROR, 198 FALSE, { NULL }, FALSE }, 199 200 { D_ERROR, M_, "human-readable", M_ERROR_HUMANREADABLE, 201 TRUE, { "?errcode", NULL }, TRUE }, 202 203 { 0 } 204}; 205 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. */ 208static svn_error_t * 209parse_status_line(int *status_code_out, 210 const char **reason, 211 const char *status_line, 212 apr_pool_t *result_pool, 213 apr_pool_t *scratch_pool) 214{ 215 svn_error_t *err; 216 const char *token; 217 char *tok_status; 218 svn_stringbuf_t *temp_buf = svn_stringbuf_create(status_line, scratch_pool); 219 220 svn_stringbuf_strip_whitespace(temp_buf); 221 token = apr_strtok(temp_buf->data, " \t\r\n", &tok_status); 222 if (token) 223 token = apr_strtok(NULL, " \t\r\n", &tok_status); 224 if (!token) 225 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 226 _("Malformed DAV:status '%s'"), 227 status_line); 228 err = svn_cstring_atoi(status_code_out, token); 229 if (err) 230 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, err, 231 _("Malformed DAV:status '%s'"), 232 status_line); 233 234 token = apr_strtok(NULL, " \t\r\n", &tok_status); 235 236 *reason = apr_pstrdup(result_pool, token); 237 238 return SVN_NO_ERROR; 239} 240 241 242typedef struct error_item_t 243{ 244 const char *path; 245 const char *propname; 246 247 int http_status; 248 const char *http_reason; 249 apr_status_t apr_err; 250 251 const char *message; 252} error_item_t; 253 254static svn_error_t * 255multistatus_opened(svn_ra_serf__xml_estate_t *xes, 256 void *baton, 257 int entered_state, 258 const svn_ra_serf__dav_props_t *tag, 259 apr_pool_t *scratch_pool) 260{ 261 /*struct svn_ra_serf__server_error_t *server_error = baton;*/ 262 const char *propname; 263 264 switch (entered_state) 265 { 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, 269 SVN_VA_NULL); 270 else 271 propname = tag->name; 272 svn_ra_serf__xml_note(xes, MS_PROPSTAT, "propname", propname); 273 break; 274 case S_ERROR: 275 /* This toggles an has error boolean in libsvn_ra_neon in 1.7 */ 276 break; 277 } 278 279 return SVN_NO_ERROR; 280} 281 282static svn_error_t * 283multistatus_closed(svn_ra_serf__xml_estate_t *xes, 284 void *baton, 285 int leaving_state, 286 const svn_string_t *cdata, 287 apr_hash_t *attrs, 288 apr_pool_t *scratch_pool) 289{ 290 struct svn_ra_serf__server_error_t *server_error = baton; 291 const char *errcode; 292 const char *status; 293 294 switch (leaving_state) 295 { 296 case MS_RESPONSE_HREF: 297 { 298 apr_status_t result; 299 apr_uri_t uri; 300 301 result = apr_uri_parse(scratch_pool, cdata->data, &uri); 302 if (result) 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)); 306 } 307 break; 308 case MS_RESPONSE_STATUS: 309 svn_ra_serf__xml_note(xes, MS_RESPONSE, "status", cdata->data); 310 break; 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"); 314 if (errcode) 315 svn_ra_serf__xml_note(xes, MS_RESPONSE, "errcode", errcode); 316 break; 317 case MS_RESPONSE: 318 if ((status = svn_hash__get_cstring(attrs, "status", NULL)) != NULL) 319 { 320 error_item_t *item; 321 322 item = apr_pcalloc(server_error->pool, sizeof(*item)); 323 324 item->path = apr_pstrdup(server_error->pool, 325 svn_hash_gets(attrs, "path")); 326 327 SVN_ERR(parse_status_line(&item->http_status, 328 &item->http_reason, 329 status, 330 server_error->pool, 331 scratch_pool)); 332 333 /* Do we have a mod_dav specific message? */ 334 item->message = svn_hash_gets(attrs, "human-readable"); 335 336 if (item->message) 337 { 338 if ((errcode = svn_hash_gets(attrs, "errcode")) != NULL) 339 { 340 apr_int64_t val; 341 342 SVN_ERR(svn_cstring_atoi64(&val, errcode)); 343 item->apr_err = (apr_status_t)val; 344 } 345 346 item->message = apr_pstrdup(server_error->pool, item->message); 347 } 348 else 349 item->message = apr_pstrdup(server_error->pool, 350 svn_hash_gets(attrs, "description")); 351 352 APR_ARRAY_PUSH(server_error->items, error_item_t *) = item; 353 } 354 break; 355 356 357 case MS_PROPSTAT_STATUS: 358 svn_ra_serf__xml_note(xes, MS_PROPSTAT, "status", cdata->data); 359 break; 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"); 363 if (errcode) 364 svn_ra_serf__xml_note(xes, MS_PROPSTAT, "errcode", errcode); 365 break; 366 case MS_PROPSTAT_RESPONSEDESCRIPTION: 367 svn_ra_serf__xml_note(xes, MS_PROPSTAT, "description", 368 cdata->data); 369 break; 370 371 case MS_PROPSTAT: 372 if ((status = svn_hash__get_cstring(attrs, "status", NULL)) != NULL) 373 { 374 apr_hash_t *response_attrs; 375 error_item_t *item; 376 377 response_attrs = svn_ra_serf__xml_gather_since(xes, MS_RESPONSE); 378 item = apr_pcalloc(server_error->pool, sizeof(*item)); 379 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")); 384 385 SVN_ERR(parse_status_line(&item->http_status, 386 &item->http_reason, 387 status, 388 server_error->pool, 389 scratch_pool)); 390 391 /* Do we have a mod_dav specific message? */ 392 item->message = svn_hash_gets(attrs, "human-readable"); 393 394 if (item->message) 395 { 396 if ((errcode = svn_hash_gets(attrs, "errcode")) != NULL) 397 { 398 apr_int64_t val; 399 400 SVN_ERR(svn_cstring_atoi64(&val, errcode)); 401 item->apr_err = (apr_status_t)val; 402 } 403 404 item->message = apr_pstrdup(server_error->pool, item->message); 405 } 406 else 407 item->message = apr_pstrdup(server_error->pool, 408 svn_hash_gets(attrs, "description")); 409 410 411 APR_ARRAY_PUSH(server_error->items, error_item_t *) = item; 412 } 413 break; 414 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"); 418 if (errcode) 419 svn_ra_serf__xml_note(xes, D_ERROR, "errcode", errcode); 420 break; 421 422 case D_ERROR: 423 { 424 error_item_t *item; 425 426 item = apr_pcalloc(server_error->pool, sizeof(*item)); 427 428 item->http_status = server_error->handler->sline.code; 429 430 /* Do we have a mod_dav specific message? */ 431 item->message = svn_hash__get_cstring(attrs, "human-readable", 432 NULL); 433 434 if (item->message) 435 { 436 if ((errcode = svn_hash_gets(attrs, "errcode")) != NULL) 437 { 438 apr_int64_t val; 439 440 SVN_ERR(svn_cstring_atoi64(&val, errcode)); 441 item->apr_err = (apr_status_t)val; 442 } 443 444 item->message = apr_pstrdup(server_error->pool, item->message); 445 } 446 447 448 APR_ARRAY_PUSH(server_error->items, error_item_t *) = item; 449 } 450 } 451 return SVN_NO_ERROR; 452} 453 454svn_error_t * 455svn_ra_serf__server_error_create(svn_ra_serf__handler_t *handler, 456 apr_pool_t *scratch_pool) 457{ 458 svn_ra_serf__server_error_t *server_error = handler->server_error; 459 svn_error_t *err = NULL; 460 int i; 461 462 for (i = 0; i < server_error->items->nelts; i++) 463 { 464 const error_item_t *item; 465 apr_status_t status; 466 const char *message; 467 svn_error_t *new_err; 468 469 item = APR_ARRAY_IDX(server_error->items, i, error_item_t *); 470 471 if (!item->apr_err && item->http_status == 200) 472 { 473 continue; /* Success code */ 474 } 475 else if (!item->apr_err && item->http_status == 424 && item->propname) 476 { 477 continue; /* Failed because other PROPPATCH operations failed */ 478 } 479 480 if (item->apr_err) 481 status = item->apr_err; 482 else 483 switch (item->http_status) 484 { 485 case 0: 486 continue; /* Not an error */ 487 case 301: 488 case 302: 489 case 303: 490 case 307: 491 case 308: 492 status = SVN_ERR_RA_DAV_RELOCATED; 493 break; 494 case 403: 495 status = SVN_ERR_RA_DAV_FORBIDDEN; 496 break; 497 case 404: 498 status = SVN_ERR_FS_NOT_FOUND; 499 break; 500 case 409: 501 status = SVN_ERR_FS_CONFLICT; 502 break; 503 case 412: 504 status = SVN_ERR_RA_DAV_PRECONDITION_FAILED; 505 break; 506 case 423: 507 status = SVN_ERR_FS_NO_LOCK_TOKEN; 508 break; 509 case 500: 510 status = SVN_ERR_RA_DAV_REQUEST_FAILED; 511 break; 512 case 501: 513 status = SVN_ERR_UNSUPPORTED_FEATURE; 514 break; 515 default: 516 if (err) 517 status = err->apr_err; /* Just use previous */ 518 else 519 status = SVN_ERR_RA_DAV_REQUEST_FAILED; 520 break; 521 } 522 523 if (item->message && *item->message) 524 { 525 svn_stringbuf_t *sb = svn_stringbuf_create(item->message, 526 scratch_pool); 527 528 svn_stringbuf_strip_whitespace(sb); 529 message = sb->data; 530 } 531 else if (item->propname) 532 { 533 message = apr_psprintf(scratch_pool, 534 _("Property operation on '%s' failed"), 535 item->propname); 536 } 537 else 538 { 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; 544 545 memset(&sline, 0, sizeof(sline)); 546 sline.code = item->http_status; 547 sline.reason = item->http_reason; 548 549 tmp_err = svn_ra_serf__error_on_status(sline, item->path, NULL); 550 551 message = (tmp_err && tmp_err->message) 552 ? apr_pstrdup(scratch_pool, tmp_err->message) 553 : _("<blank error>"); 554 svn_error_clear(tmp_err); 555 } 556 557 SVN_ERR_ASSERT(status > 0); 558 new_err = svn_error_create(status, NULL, message); 559 560 if (item->propname) 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); 564 else if (item->path) 565 new_err = svn_error_createf(new_err->apr_err, new_err, 566 _("While handling the '%s' path:"), 567 item->path); 568 569 err = svn_error_compose_create( 570 err, 571 new_err); 572 } 573 574 /* Theoretically a 207 status can have a 'global' description without a 575 global STATUS that summarizes the final result of property/href 576 operations. 577 578 We should wrap that around the existing errors if there is one. 579 580 But currently I don't see how mod_dav ever sets that value */ 581 582 if (!err) 583 { 584 /* We should fail.... but why... Who installed us? */ 585 err = svn_error_trace(svn_ra_serf__unexpected_status(handler)); 586 } 587 588 return err; 589} 590 591 592svn_error_t * 593svn_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) 598{ 599 svn_ra_serf__server_error_t *ms_baton; 600 svn_ra_serf__handler_t *tmp_handler; 601 602 int *expected_status = apr_pcalloc(result_pool, 603 2 * sizeof(expected_status[0])); 604 605 expected_status[0] = handler->sline.code; 606 607 ms_baton = apr_pcalloc(result_pool, sizeof(*ms_baton)); 608 ms_baton->pool = result_pool; 609 610 ms_baton->items = apr_array_make(result_pool, 4, sizeof(error_item_t *)); 611 ms_baton->handler = handler; 612 613 ms_baton->xmlctx = svn_ra_serf__xml_context_create(multistatus_ttable, 614 multistatus_opened, 615 multistatus_closed, 616 NULL, 617 ms_baton, 618 ms_baton->pool); 619 620 tmp_handler = svn_ra_serf__create_expat_handler(handler->session, 621 ms_baton->xmlctx, 622 expected_status, 623 result_pool); 624 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; 629 630 *server_err = ms_baton; 631 return SVN_NO_ERROR; 632} 633 634 635 636/* Implements svn_ra_serf__response_handler_t */ 637svn_error_t * 638svn_ra_serf__handle_multistatus_only(serf_request_t *request, 639 serf_bucket_t *response, 640 void *baton, 641 apr_pool_t *scratch_pool) 642{ 643 svn_ra_serf__handler_t *handler = baton; 644 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. */ 648 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); 654 655 { 656 serf_bucket_t *hdrs; 657 const char *val; 658 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) 662 { 663 svn_ra_serf__server_error_t *server_err; 664 665 SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, 666 handler, 667 TRUE, 668 handler->handler_pool, 669 handler->handler_pool)); 670 671 handler->server_error = server_err; 672 } 673 else 674 { 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; 678 } 679 } 680 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). */ 684 return SVN_NO_ERROR; 685} 686 687svn_error_t * 688svn_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) 694{ 695 svn_error_t *err; 696 697 err = server_error->response_handler(request, response, 698 server_error->response_baton, 699 scratch_pool); 700 /* If we do not receive an error or it is a non-transient error, return 701 immediately. 702 703 APR_EOF will be returned when parsing is complete. 704 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. 708 */ 709 if (!err || SERF_BUCKET_READ_ERROR(err->apr_err)) 710 { 711 /* Perhaps we already parsed some server generated message. Let's pass 712 all information we can get.*/ 713 if (err) 714 err = svn_error_compose_create( 715 svn_ra_serf__server_error_create(handler, scratch_pool), 716 err); 717 718 return svn_error_trace(err); 719 } 720 721 if (!APR_STATUS_IS_EOF(err->apr_err)) 722 { 723 *serf_status = err->apr_err; 724 svn_error_clear(err); 725 return SVN_NO_ERROR; 726 } 727 728 /* Clear the EOF. We don't need it as subversion error. */ 729 svn_error_clear(err); 730 *serf_status = APR_EOF; 731 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 734 any multistatus */ 735 if (handler->sline.code == 207 /* MULTISTATUS */) 736 { 737 svn_boolean_t have_error = FALSE; 738 int i; 739 740 for (i = 0; i < server_error->items->nelts; i++) 741 { 742 const error_item_t *item; 743 item = APR_ARRAY_IDX(server_error->items, i, error_item_t *); 744 745 if (!item->apr_err && item->http_status == 200) 746 { 747 continue; /* Success code */ 748 } 749 750 have_error = TRUE; 751 break; 752 } 753 754 if (! have_error) 755 handler->server_error = NULL; /* We didn't have a server error */ 756 } 757 758 return SVN_NO_ERROR; 759} 760