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