Followup to r29625: fix getopt tests.
[svn.git] / subversion / libsvn_ra_neon / util.c
blob157e252e2fd157f735635d836bdc92ce569d9201
1 /*
2 * util.c : utility functions for the RA/DAV library
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 #include <apr_pools.h>
21 #define APR_WANT_STRFUNC
22 #include <apr_want.h>
24 #include <apr_uri.h>
26 #include <ne_alloc.h>
27 #include <ne_compress.h>
28 #include <ne_basic.h>
30 #include "svn_pools.h"
31 #include "svn_path.h"
32 #include "svn_string.h"
33 #include "svn_utf.h"
34 #include "svn_xml.h"
36 #include "svn_private_config.h"
38 #include "ra_neon.h"
39 #include <assert.h>
43 static apr_status_t
44 xml_parser_cleanup(void *baton)
46 ne_xml_destroy(baton);
48 return APR_SUCCESS;
51 static ne_xml_parser *
52 xml_parser_create(svn_ra_neon__request_t *req)
54 ne_xml_parser *p = ne_xml_create();
56 /* ### HACK: Set the parser's error to the empty string. Someday we
57 hope neon will let us have an easy way to tell the difference
58 between XML parsing errors, and errors that occur while handling
59 the XML tags that we get. Until then, trust that whenever neon
60 has an error somewhere below the API, it sets its own error to
61 something non-empty (the API promises non-NULL, at least). */
62 ne_xml_set_error(p, "");
64 apr_pool_cleanup_register(req->pool, p,
65 xml_parser_cleanup,
66 apr_pool_cleanup_null);
68 return p;
73 /* Simple multi-status parser
75 * For the purpose of 'simple' requests which - if it weren't
76 * for our custom error parser - could use the ne_basic.h interfaces.
79 static const svn_ra_neon__xml_elm_t multistatus_elements[] =
80 { { "DAV:", "multistatus", ELEM_multistatus, 0 },
81 { "DAV:", "response", ELEM_response, 0 },
82 { "DAV:", "responsedescription", ELEM_responsedescription,
83 SVN_RA_NEON__XML_CDATA },
84 { "DAV:", "status", ELEM_status, SVN_RA_NEON__XML_CDATA },
85 { "DAV:", "href", ELEM_href, SVN_RA_NEON__XML_CDATA },
86 { "DAV:", "propstat", ELEM_propstat, SVN_RA_NEON__XML_CDATA },
87 { "DAV:", "prop", ELEM_prop, SVN_RA_NEON__XML_CDATA },
89 /* We start out basic and are not interested in other elements */
90 { "", "", ELEM_unknown, 0 },
92 { NULL }
96 static const int multistatus_nesting_table[][5] =
97 { { ELEM_root, ELEM_multistatus, SVN_RA_NEON__XML_INVALID },
98 { ELEM_multistatus, ELEM_response, ELEM_responsedescription,
99 SVN_RA_NEON__XML_DECLINE },
100 { ELEM_responsedescription, SVN_RA_NEON__XML_INVALID },
101 { ELEM_response, ELEM_href, ELEM_status, ELEM_propstat,
102 SVN_RA_NEON__XML_DECLINE },
103 { ELEM_status, SVN_RA_NEON__XML_INVALID },
104 { ELEM_href, SVN_RA_NEON__XML_INVALID },
105 { ELEM_propstat, ELEM_prop, ELEM_status, ELEM_responsedescription,
106 SVN_RA_NEON__XML_INVALID },
107 { ELEM_prop, SVN_RA_NEON__XML_DECLINE },
108 { SVN_RA_NEON__XML_DECLINE },
112 static int
113 validate_element(int parent, int child)
115 int i = 0;
116 int j = 0;
118 while (parent != multistatus_nesting_table[i][0]
119 && (multistatus_nesting_table[i][0] > 0 || i == 0))
120 i++;
122 if (parent == multistatus_nesting_table[i][0])
123 while (multistatus_nesting_table[i][++j] != child
124 && multistatus_nesting_table[i][j] > 0)
127 return multistatus_nesting_table[i][j];
130 typedef struct
132 svn_stringbuf_t *want_cdata;
133 svn_stringbuf_t *cdata;
135 svn_boolean_t in_propstat;
136 svn_boolean_t propstat_has_error;
137 svn_stringbuf_t *propname;
138 svn_stringbuf_t *propstat_description;
140 svn_ra_neon__request_t *req;
141 svn_stringbuf_t *description;
142 svn_boolean_t contains_error;
143 } multistatus_baton_t;
145 static svn_error_t *
146 start_207_element(int *elem, void *baton, int parent,
147 const char *nspace, const char *name, const char **atts)
149 multistatus_baton_t *b = baton;
150 const svn_ra_neon__xml_elm_t *elm =
151 svn_ra_neon__lookup_xml_elem(multistatus_elements, nspace, name);
152 *elem = elm ? validate_element(parent, elm->id) : SVN_RA_NEON__XML_DECLINE;
155 if (parent == ELEM_prop)
157 svn_stringbuf_setempty(b->propname);
158 if (strcmp(nspace, SVN_DAV_PROP_NS_DAV) == 0)
159 svn_stringbuf_set(b->propname, SVN_PROP_PREFIX);
160 else if (strcmp(nspace, "DAV:") == 0)
161 svn_stringbuf_set(b->propname, "DAV:");
163 svn_stringbuf_appendcstr(b->propname, name);
166 if (*elem < 1) /* ! > 0 */
167 return SVN_NO_ERROR;
169 switch (*elem)
171 case ELEM_propstat:
172 b->in_propstat = TRUE;
173 b->propstat_has_error = FALSE;
174 break;
176 default:
177 break;
180 /* We're guaranteed to have ELM now: SVN_RA_NEON__XML_DECLINE < 1 */
181 if (elm->flags & SVN_RA_NEON__XML_CDATA)
183 svn_stringbuf_setempty(b->cdata);
184 b->want_cdata = b->cdata;
187 return SVN_NO_ERROR;
190 static svn_error_t *
191 end_207_element(void *baton, int state,
192 const char *nspace, const char *name)
194 multistatus_baton_t *b = baton;
196 switch (state)
198 case ELEM_multistatus:
199 if (b->contains_error)
201 if (svn_stringbuf_isempty(b->description))
202 return svn_error_create(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
203 _("The request response contained at least "
204 "one error"));
205 else
206 return svn_error_create(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
207 b->description->data);
209 break;
211 case ELEM_responsedescription:
212 if (b->in_propstat)
213 svn_stringbuf_set(b->propstat_description, b->cdata->data);
214 else
216 if (! svn_stringbuf_isempty(b->description))
217 svn_stringbuf_appendcstr(b->description, "\n");
218 svn_stringbuf_appendstr(b->description, b->cdata);
220 break;
222 case ELEM_status:
224 ne_status status;
226 if (ne_parse_statusline(b->cdata->data, &status) == 0)
228 /*### I wanted ||=, but I guess the end result is the same */
229 if (! b->in_propstat)
230 b->contains_error |= (status.klass != 2);
231 else
232 b->propstat_has_error = (status.klass != 2);
234 free(status.reason_phrase);
236 else
237 return svn_error_create(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
238 _("The response contains a non-conforming "
239 "HTTP status line"));
241 break;
243 case ELEM_propstat:
244 b->in_propstat = FALSE;
245 b->contains_error |= b->propstat_has_error;
246 svn_stringbuf_appendcstr(b->description,
247 apr_psprintf(b->req->pool,
248 _("Error setting property '%s': "),
249 b->propname->data));
250 svn_stringbuf_appendstr(b->description,
251 b->propstat_description);
253 default:
254 /* do nothing */
255 break;
258 /* When we have an element which wants cdata,
259 we'll set it all up in start_207_element() again */
260 b->want_cdata = NULL;
262 return SVN_NO_ERROR;
266 static ne_xml_parser *
267 multistatus_parser_create(svn_ra_neon__request_t *req)
269 multistatus_baton_t *b = apr_pcalloc(req->pool, sizeof(*b));
270 ne_xml_parser *multistatus_parser =
271 svn_ra_neon__xml_parser_create(req, ne_accept_207,
272 start_207_element,
273 svn_ra_neon__xml_collect_cdata,
274 end_207_element, b);
275 b->cdata = svn_stringbuf_create("", req->pool);
276 b->description = svn_stringbuf_create("", req->pool);
277 b->req = req;
279 b->propname = svn_stringbuf_create("", req->pool);
280 b->propstat_description = svn_stringbuf_create("", req->pool);
282 return multistatus_parser;
288 /* Neon request management */
290 /* Forward declare */
291 static apr_status_t
292 dav_request_cleanup(void *baton);
294 static apr_status_t
295 dav_request_sess_cleanup(void *baton)
297 svn_ra_neon__request_t *req = baton;
299 /* Make sure we don't run the 'child' cleanup anymore:
300 the pool it refers to probably doesn't exist anymore when it
301 finally does get run if it hasn't by now. */
302 apr_pool_cleanup_kill(req->pool, req, dav_request_cleanup);
304 if (req->ne_req)
305 ne_request_destroy(req->ne_req);
307 return APR_SUCCESS;
310 static apr_status_t
311 dav_request_cleanup(void *baton)
313 svn_ra_neon__request_t *req = baton;
314 apr_pool_cleanup_run(req->sess->pool, req, dav_request_sess_cleanup);
316 return APR_SUCCESS;
320 /* Return a path-absolute relative URL, given a URL reference (which may
321 be absolute or relative). */
322 static const char *
323 path_from_url(const char *url)
325 const char *p;
327 /* Look for the scheme/authority separator. Stop if we see a path
328 separator - that indicates that this definitely isn't an absolute URL. */
329 for (p = url; *p; p++)
330 if (*p == ':' || *p == '/')
331 break;
333 /* Check whether we found the scheme/authority separator. */
334 if (*p++ != ':' || *p++ != '/' || *p++ != '/')
336 /* No separator, so it must already be relative. */
337 return url;
340 /* Find the end of the authority section, indicated by the start of
341 a path, query, or fragment section. */
342 for (; *p; p++)
343 if (*p == '/' || *p == '?' || *p == '#')
344 break;
346 /* Return a pointer to the rest of the URL, or to the empty string if there
347 was no next section. */
348 return p;
351 svn_ra_neon__request_t *
352 svn_ra_neon__request_create(svn_ra_neon__session_t *sess,
353 const char *method, const char *url,
354 apr_pool_t *pool)
356 apr_pool_t *reqpool = svn_pool_create(pool);
357 svn_ra_neon__request_t *req = apr_pcalloc(reqpool, sizeof(*req));
359 /* We never want to send Neon an absolute URL, since that can cause
360 problems with some servers (for example, those that may be accessed
361 using different server names from different locations, or those that
362 want to rewrite the incoming URL). If the URL passed in is absolute,
363 convert it to a path-absolute relative URL. */
364 const char *path = path_from_url(url);
366 req->ne_sess = sess->main_session_busy ? sess->ne_sess2 : sess->ne_sess;
367 req->ne_req = ne_request_create(req->ne_sess, method, path);
368 req->sess = sess;
369 req->pool = reqpool;
370 req->iterpool = svn_pool_create(req->pool);
371 req->method = apr_pstrdup(req->pool, method);
372 req->url = apr_pstrdup(req->pool, url);
373 req->rv = -1;
375 /* Neon resources may be NULL on out-of-memory */
376 assert(req->ne_req != NULL);
377 apr_pool_cleanup_register(sess->pool, req,
378 dav_request_sess_cleanup,
379 apr_pool_cleanup_null);
380 apr_pool_cleanup_register(reqpool, req,
381 dav_request_cleanup,
382 apr_pool_cleanup_null);
384 return req;
387 static apr_status_t
388 compressed_body_reader_cleanup(void *baton)
390 if (baton)
391 ne_decompress_destroy(baton);
393 return APR_SUCCESS;
396 static void
397 attach_ne_body_reader(svn_ra_neon__request_t *req,
398 ne_accept_response accpt,
399 ne_block_reader reader,
400 void *userdata)
402 if (req->sess->compression)
404 ne_decompress *decompress =
405 ne_decompress_reader(req->ne_req, accpt, reader, userdata);
407 apr_pool_cleanup_register(req->pool,
408 decompress,
409 compressed_body_reader_cleanup,
410 apr_pool_cleanup_null);
412 else
413 ne_add_response_body_reader(req->ne_req, accpt, reader, userdata);
417 typedef struct
419 svn_ra_neon__request_t *req;
420 svn_ra_neon__block_reader real_reader;
421 void *real_baton;
422 } body_reader_wrapper_baton_t;
424 static int
425 body_reader_wrapper(void *userdata, const char *data, size_t len)
427 body_reader_wrapper_baton_t *b = userdata;
429 if (b->req->err)
430 /* We already had an error? Bail out. */
431 return 1;
433 SVN_RA_NEON__REQ_ERR
434 (b->req,
435 b->real_reader(b->real_baton, data, len));
437 if (b->req->err)
438 return 1;
440 return 0;
443 void
444 svn_ra_neon__add_response_body_reader(svn_ra_neon__request_t *req,
445 ne_accept_response accpt,
446 svn_ra_neon__block_reader reader,
447 void *userdata)
449 body_reader_wrapper_baton_t *b = apr_palloc(req->pool, sizeof(*b));
451 b->req = req;
452 b->real_baton = userdata;
453 b->real_reader = reader;
455 attach_ne_body_reader(req, accpt, body_reader_wrapper, b);
460 const svn_ra_neon__xml_elm_t *
461 svn_ra_neon__lookup_xml_elem(const svn_ra_neon__xml_elm_t *table,
462 const char *nspace,
463 const char *name)
465 /* placeholder for `unknown' element if it's present */
466 const svn_ra_neon__xml_elm_t *elem_unknown = NULL;
467 const svn_ra_neon__xml_elm_t *elem;
469 for(elem = table; elem->nspace; ++elem)
471 if (strcmp(elem->nspace, nspace) == 0
472 && strcmp(elem->name, name) == 0)
473 return elem;
475 /* Use a single loop to save CPU cycles.
477 * Maybe this element is defined as `unknown'? */
478 if (elem->id == ELEM_unknown)
479 elem_unknown = elem;
482 /* ELEM_unknown position in the table or NULL */
483 return elem_unknown;
486 svn_error_t *
487 svn_ra_neon__xml_collect_cdata(void *baton, int state,
488 const char *cdata, size_t len)
490 svn_stringbuf_t **b = baton;
492 if (*b)
493 svn_stringbuf_appendbytes(*b, cdata, len);
495 return SVN_NO_ERROR;
500 svn_error_t *
501 svn_ra_neon__copy_href(svn_stringbuf_t *dst, const char *src,
502 apr_pool_t *pool)
504 /* parse the PATH element out of the URL and store it.
506 ### do we want to verify the rest matches the current session?
508 Note: mod_dav does not (currently) use an absolute URL, but simply a
509 server-relative path (i.e. this uri_parse is effectively a no-op).
512 apr_uri_t uri;
513 apr_status_t apr_status
514 = apr_uri_parse(pool, src, &uri);
516 if (apr_status != APR_SUCCESS)
517 return svn_error_wrap_apr(apr_status,
518 _("Unable to parse URL '%s'"),
519 src);
521 svn_stringbuf_setempty(dst);
522 svn_stringbuf_appendcstr(dst, uri.path);
524 return SVN_NO_ERROR;
527 static svn_error_t *
528 generate_error(svn_ra_neon__request_t *req, apr_pool_t *pool)
530 int errcode = SVN_ERR_RA_DAV_REQUEST_FAILED;
531 const char *context =
532 apr_psprintf(req->pool, _("%s of '%s'"), req->method, req->url);
533 const char *msg;
534 const char *hostport;
536 /* Convert the return codes. */
537 switch (req->rv)
539 case NE_OK:
540 switch (req->code)
542 case 404:
543 return svn_error_create(SVN_ERR_RA_DAV_PATH_NOT_FOUND, NULL,
544 apr_psprintf(pool, _("'%s' path not found"),
545 req->url));
547 case 301:
548 case 302:
549 return svn_error_create
550 (SVN_ERR_RA_DAV_RELOCATED, NULL,
551 apr_psprintf(pool,
552 (req->code == 301)
553 ? _("Repository moved permanently to '%s';"
554 " please relocate")
555 : _("Repository moved temporarily to '%s';"
556 " please relocate"),
557 svn_ra_neon__request_get_location(req, pool)));
559 default:
560 return svn_error_create
561 (errcode, NULL,
562 apr_psprintf(pool,
563 _("Server sent unexpected return value (%d %s) "
564 "in response to %s request for '%s'"), req->code,
565 req->code_desc, req->method, req->url));
567 case NE_AUTH:
568 errcode = SVN_ERR_RA_NOT_AUTHORIZED;
569 #ifdef SVN_NEON_0_27
570 /* neon >= 0.27 gives a descriptive error message after auth
571 * failure; expose this since it's a useful diagnostic e.g. for
572 * an unsupported challenge scheme, or a local GSSAPI error due
573 * to an expired ticket. */
574 SVN_ERR(svn_utf_cstring_to_utf8(&msg, ne_get_error(req->ne_sess), pool));
575 msg = apr_psprintf(pool, _("authorization failed: %s"), msg);
576 #else
577 msg = _("authorization failed");
578 #endif
579 break;
581 case NE_CONNECT:
582 msg = _("could not connect to server");
583 break;
585 case NE_TIMEOUT:
586 msg = _("timed out waiting for server");
587 break;
589 default:
590 /* Get the error string from neon and convert to UTF-8. */
591 SVN_ERR(svn_utf_cstring_to_utf8(&msg, ne_get_error(req->ne_sess), pool));
592 break;
595 /* The hostname may contain non-ASCII characters, so convert it to UTF-8. */
596 SVN_ERR(svn_utf_cstring_to_utf8(&hostport,
597 ne_get_server_hostport(req->ne_sess), pool));
599 /*### This is a translation nightmare. Make sure to compose full strings
600 and mark those for translation. */
601 return svn_error_createf(errcode, NULL, "%s: %s (%s://%s)",
602 context, msg, ne_get_scheme(req->ne_sess),
603 hostport);
607 /** Error parsing **/
610 /* Custom function of type ne_accept_response. */
611 static int ra_neon_error_accepter(void *userdata,
612 ne_request *req,
613 const ne_status *st)
615 /* Before, this function was being run for *all* responses including
616 the 401 auth challenge. In neon 0.24.x that was harmless. But
617 in neon 0.25.0, trying to parse a 401 response as XML using
618 ne_xml_parse_v aborts the response; so the auth hooks never got a
619 chance. */
620 ne_content_type ctype;
622 /* Only accept non-2xx responses with text/xml content-type */
623 if (st->klass != 2 && ne_get_content_type(req, &ctype) == 0)
625 int is_xml =
626 (strcmp(ctype.type, "text") == 0 && strcmp(ctype.subtype, "xml") == 0);
627 ne_free(ctype.value);
628 return is_xml;
630 else
631 return 0;
635 static const svn_ra_neon__xml_elm_t error_elements[] =
637 { "DAV:", "error", ELEM_error, 0 },
638 { "svn:", "error", ELEM_svn_error, 0 },
639 { "http://apache.org/dav/xmlns", "human-readable",
640 ELEM_human_readable, SVN_RA_NEON__XML_CDATA },
642 /* ### our validator doesn't yet recognize the rich, specific
643 <D:some-condition-failed/> objects as defined by DeltaV.*/
645 { NULL }
649 static int validate_error_elements(svn_ra_neon__xml_elmid parent,
650 svn_ra_neon__xml_elmid child)
652 switch (parent)
654 case ELEM_root:
655 if (child == ELEM_error)
656 return child;
657 else
658 return SVN_RA_NEON__XML_INVALID;
660 case ELEM_error:
661 if (child == ELEM_svn_error
662 || child == ELEM_human_readable)
663 return child;
664 else
665 return SVN_RA_NEON__XML_DECLINE; /* ignore if something else
666 was in there */
668 default:
669 return SVN_RA_NEON__XML_DECLINE;
672 /* NOTREACHED */
676 static int
677 collect_error_cdata(void *baton, int state,
678 const char *cdata, size_t len)
680 svn_stringbuf_t **b = baton;
682 if (*b)
683 svn_stringbuf_appendbytes(*b, cdata, len);
685 return 0;
688 typedef struct error_parser_baton
690 svn_stringbuf_t *want_cdata;
691 svn_stringbuf_t *cdata;
693 svn_error_t **dst_err;
694 svn_error_t *tmp_err;
695 svn_boolean_t *marshalled_error;
696 } error_parser_baton_t;
699 static int
700 start_err_element(void *baton, int parent,
701 const char *nspace, const char *name, const char **atts)
703 const svn_ra_neon__xml_elm_t *elm
704 = svn_ra_neon__lookup_xml_elem(error_elements, nspace, name);
705 int acc = elm
706 ? validate_error_elements(parent, elm->id) : SVN_RA_NEON__XML_DECLINE;
707 error_parser_baton_t *b = baton;
708 svn_error_t **err = &(b->tmp_err);
710 if (acc < 1) /* ! > 0 */
711 return acc;
713 switch (elm->id)
715 case ELEM_svn_error:
717 /* allocate the svn_error_t. Hopefully the value will be
718 overwritten by the <human-readable> tag, or even someday by
719 a <D:failed-precondition/> tag. */
720 *err = svn_error_create(APR_EGENERAL, NULL,
721 "General svn error from server");
722 break;
724 case ELEM_human_readable:
726 /* get the errorcode attribute if present */
727 const char *errcode_str =
728 svn_xml_get_attr_value("errcode", /* ### make constant in
729 some mod_dav header? */
730 atts);
732 if (errcode_str && *err)
733 (*err)->apr_err = atoi(errcode_str);
735 break;
738 default:
739 break;
742 switch (elm->id)
744 case ELEM_human_readable:
745 b->want_cdata = b->cdata;
746 svn_stringbuf_setempty(b->want_cdata);
747 break;
749 default:
750 b->want_cdata = NULL;
751 break;
754 return elm->id;
757 static int
758 end_err_element(void *baton, int state, const char *nspace, const char *name)
760 error_parser_baton_t *b = baton;
761 svn_error_t **err = &(b->tmp_err);
763 switch (state)
765 case ELEM_human_readable:
767 if (b->cdata->data && *err)
769 /* On the server dav_error_response_tag() will add a leading
770 and trailing newline if DEBUG_CR is defined in mod_dav.h,
771 so remove any such characters here. */
772 apr_size_t len;
773 const char *cd = b->cdata->data;
774 if (*cd == '\n')
775 ++cd;
776 len = strlen(cd);
777 if (len > 0 && cd[len-1] == '\n')
778 --len;
780 (*err)->message = apr_pstrmemdup((*err)->pool, cd, len);
782 break;
785 case ELEM_error:
787 if (*(b->dst_err))
788 svn_error_clear(b->tmp_err);
789 else if (b->tmp_err)
791 *(b->dst_err) = b->tmp_err;
792 if (b->marshalled_error)
793 *(b->marshalled_error) = TRUE;
795 b->tmp_err = NULL;
796 break;
799 default:
800 break;
803 return 0;
806 static apr_status_t
807 error_parser_baton_cleanup(void *baton)
809 error_parser_baton_t *b = baton;
811 if (b->tmp_err)
812 svn_error_clear(b->tmp_err);
814 return APR_SUCCESS;
817 static ne_xml_parser *
818 error_parser_create(svn_ra_neon__request_t *req)
820 error_parser_baton_t *b = apr_palloc(req->pool, sizeof(*b));
821 ne_xml_parser *error_parser;
823 b->dst_err = &(req->err);
824 b->marshalled_error = &(req->marshalled_error);
825 b->tmp_err = NULL;
827 b->want_cdata = NULL;
828 b->cdata = svn_stringbuf_create("", req->pool);
830 /* attach a standard <D:error> body parser to the request */
831 error_parser = xml_parser_create(req);
832 ne_xml_push_handler(error_parser,
833 start_err_element,
834 collect_error_cdata,
835 end_err_element, b);
837 apr_pool_cleanup_register(req->pool, b,
838 error_parser_baton_cleanup,
839 apr_pool_cleanup_null);
841 /* Register the "error" accepter and body-reader with the request --
842 the one to use when HTTP status is *not* 2XX */
843 attach_ne_body_reader(req, ra_neon_error_accepter,
844 ne_xml_parse_v, error_parser);
846 return error_parser;
850 /* A body provider for ne_set_request_body_provider that pulls data
851 * from an APR file. See ne_request.h for a description of the
852 * interface.
855 typedef struct
857 svn_ra_neon__request_t *req;
858 apr_file_t *body_file;
859 } body_provider_baton_t;
861 static ssize_t ra_neon_body_provider(void *userdata,
862 char *buffer,
863 size_t buflen)
865 body_provider_baton_t *b = userdata;
866 svn_ra_neon__request_t *req = b->req;
867 apr_file_t *body_file = b->body_file;
869 if (req->sess->callbacks &&
870 req->sess->callbacks->cancel_func)
871 SVN_RA_NEON__REQ_ERR
872 (req, (req->sess->callbacks->cancel_func)(req->sess->callback_baton));
874 if (req->err)
875 return -1;
877 svn_pool_clear(req->iterpool);
878 if (buflen == 0)
880 /* This is the beginning of a new body pull. Rewind the file. */
881 apr_off_t offset = 0;
882 SVN_RA_NEON__REQ_ERR
883 (b->req,
884 svn_io_file_seek(body_file, APR_SET, &offset, req->iterpool));
885 return (req->err ? -1 : 0);
887 else
889 apr_size_t nbytes = buflen;
890 svn_error_t *err = svn_io_file_read(body_file, buffer, &nbytes,
891 req->iterpool);
892 if (err)
894 if (APR_STATUS_IS_EOF(err->apr_err))
896 svn_error_clear(err);
897 return 0;
900 SVN_RA_NEON__REQ_ERR(req, err);
901 return -1;
903 else
904 return nbytes;
909 svn_error_t *svn_ra_neon__set_neon_body_provider(svn_ra_neon__request_t *req,
910 apr_file_t *body_file)
912 apr_status_t status;
913 apr_finfo_t finfo;
914 body_provider_baton_t *b = apr_palloc(req->pool, sizeof(*b));
916 status = apr_file_info_get(&finfo, APR_FINFO_SIZE, body_file);
917 if (status)
918 return svn_error_wrap_apr(status,
919 _("Can't calculate the request body size"));
921 b->body_file = body_file;
922 b->req = req;
924 ne_set_request_body_provider(req->ne_req, (size_t) finfo.size,
925 ra_neon_body_provider, b);
926 return SVN_NO_ERROR;
930 typedef struct spool_reader_baton_t
932 const char *spool_file_name;
933 apr_file_t *spool_file;
934 svn_ra_neon__request_t *req;
935 } spool_reader_baton_t;
938 /* This implements the svn_ra_neon__block_reader() callback interface. */
939 static svn_error_t *
940 spool_reader(void *userdata,
941 const char *buf,
942 size_t len)
944 spool_reader_baton_t *baton = userdata;
946 SVN_ERR(svn_io_file_write_full(baton->spool_file, buf,
947 len, NULL, baton->req->iterpool));
948 svn_pool_clear(baton->req->iterpool);
950 return SVN_NO_ERROR;
954 static svn_error_t *
955 parse_spool_file(svn_ra_neon__session_t *ras,
956 const char *spool_file_name,
957 ne_xml_parser *success_parser,
958 apr_pool_t *pool)
960 apr_file_t *spool_file;
961 svn_stream_t *spool_stream;
962 char *buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
963 apr_size_t len;
965 SVN_ERR(svn_io_file_open(&spool_file, spool_file_name,
966 (APR_READ | APR_BUFFERED), APR_OS_DEFAULT, pool));
967 spool_stream = svn_stream_from_aprfile(spool_file, pool);
968 while (1)
970 if (ras->callbacks &&
971 ras->callbacks->cancel_func)
972 SVN_ERR((ras->callbacks->cancel_func)(ras->callback_baton));
974 len = SVN__STREAM_CHUNK_SIZE;
975 SVN_ERR(svn_stream_read(spool_stream, buf, &len));
976 if (len > 0)
977 if (ne_xml_parse(success_parser, buf, len) != 0)
978 /* The parse encountered an error or
979 was aborted by a user defined callback */
980 break;
982 if (len != SVN__STREAM_CHUNK_SIZE)
983 break;
985 return SVN_NO_ERROR;
989 /* A baton that is used along with a set of Neon ne_startelm_cb,
990 * ne_cdata_cb, and ne_endelm_cb callbacks to handle conversion
991 * from Subversion style errors to Neon style errors.
993 * The underlying Subversion callbacks are called, and if errors
994 * are returned they are stored in this baton and a Neon level
995 * error code is returned to the parser.
997 typedef struct {
998 svn_ra_neon__request_t *req;
999 ne_xml_parser *parser;
1001 void *baton;
1002 svn_ra_neon__startelm_cb_t startelm_cb;
1003 svn_ra_neon__cdata_cb_t cdata_cb;
1004 svn_ra_neon__endelm_cb_t endelm_cb;
1005 } parser_wrapper_baton_t;
1007 static int
1008 wrapper_startelm_cb(void *baton,
1009 int parent,
1010 const char *nspace,
1011 const char *name,
1012 const char **atts)
1014 parser_wrapper_baton_t *pwb = baton;
1015 int elem = SVN_RA_NEON__XML_DECLINE;
1017 if (pwb->startelm_cb)
1018 SVN_RA_NEON__REQ_ERR
1019 (pwb->req,
1020 pwb->startelm_cb(&elem, pwb->baton, parent, nspace, name, atts));
1022 if (elem == SVN_RA_NEON__XML_INVALID)
1023 SVN_RA_NEON__REQ_ERR
1024 (pwb->req,
1025 svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL));
1027 if (pwb->req->err)
1028 return NE_XML_ABORT;
1030 return elem;
1033 static int
1034 wrapper_cdata_cb(void *baton, int state, const char *cdata, size_t len)
1036 parser_wrapper_baton_t *pwb = baton;
1038 if (pwb->cdata_cb)
1039 SVN_RA_NEON__REQ_ERR
1040 (pwb->req,
1041 pwb->cdata_cb(pwb->baton, state, cdata, len));
1043 if (pwb->req->err)
1044 return NE_XML_ABORT;
1046 return 0;
1049 static int
1050 wrapper_endelm_cb(void *baton,
1051 int state,
1052 const char *nspace,
1053 const char *name)
1055 parser_wrapper_baton_t *pwb = baton;
1057 if (pwb->endelm_cb)
1058 SVN_RA_NEON__REQ_ERR
1059 (pwb->req,
1060 pwb->endelm_cb(pwb->baton, state, nspace, name));
1062 if (pwb->req->err)
1063 return NE_XML_ABORT;
1065 return 0;
1068 static int
1069 wrapper_reader_cb(void *baton, const char *data, size_t len)
1071 parser_wrapper_baton_t *pwb = baton;
1072 svn_ra_neon__session_t *sess = pwb->req->sess;
1074 if (pwb->req->err)
1075 return 1;
1077 if (sess->callbacks->cancel_func)
1078 SVN_RA_NEON__REQ_ERR
1079 (pwb->req,
1080 (sess->callbacks->cancel_func)(sess->callback_baton));
1082 if (pwb->req->err)
1083 return 1;
1085 return ne_xml_parse(pwb->parser, data, len);
1088 ne_xml_parser *
1089 svn_ra_neon__xml_parser_create(svn_ra_neon__request_t *req,
1090 ne_accept_response accpt,
1091 svn_ra_neon__startelm_cb_t startelm_cb,
1092 svn_ra_neon__cdata_cb_t cdata_cb,
1093 svn_ra_neon__endelm_cb_t endelm_cb,
1094 void *baton)
1096 ne_xml_parser *p = xml_parser_create(req);
1097 parser_wrapper_baton_t *pwb = apr_palloc(req->pool, sizeof(*pwb));
1099 pwb->req = req;
1100 pwb->parser = p;
1101 pwb->baton = baton;
1102 pwb->startelm_cb = startelm_cb;
1103 pwb->cdata_cb = cdata_cb;
1104 pwb->endelm_cb = endelm_cb;
1106 ne_xml_push_handler(p,
1107 wrapper_startelm_cb,
1108 wrapper_cdata_cb,
1109 wrapper_endelm_cb, pwb);
1111 if (accpt)
1112 attach_ne_body_reader(req, accpt, wrapper_reader_cb, pwb);
1114 return p;
1118 typedef struct cancellation_baton_t
1120 ne_block_reader real_cb;
1121 void *real_userdata;
1122 svn_ra_neon__request_t *req;
1123 } cancellation_baton_t;
1125 static int
1126 cancellation_callback(void *userdata, const char *block, size_t len)
1128 cancellation_baton_t *b = userdata;
1129 svn_ra_neon__session_t *ras = b->req->sess;
1131 if (ras->callbacks->cancel_func)
1132 SVN_RA_NEON__REQ_ERR
1133 (b->req,
1134 (ras->callbacks->cancel_func)(ras->callback_baton));
1136 if (b->req->err)
1137 return 1;
1138 else
1139 return (b->real_cb)(b->real_userdata, block, len);
1143 static cancellation_baton_t *
1144 get_cancellation_baton(svn_ra_neon__request_t *req,
1145 ne_block_reader real_cb,
1146 void *real_userdata,
1147 apr_pool_t *pool)
1149 cancellation_baton_t *b = apr_palloc(pool, sizeof(*b));
1151 b->real_cb = real_cb;
1152 b->real_userdata = real_userdata;
1153 b->req = req;
1155 return b;
1158 /* See doc string for svn_ra_neon__parsed_request. */
1159 static svn_error_t *
1160 parsed_request(svn_ra_neon__request_t *req,
1161 svn_ra_neon__session_t *ras,
1162 const char *method,
1163 const char *url,
1164 const char *body,
1165 apr_file_t *body_file,
1166 void set_parser(ne_xml_parser *parser,
1167 void *baton),
1168 svn_ra_neon__startelm_cb_t startelm_cb,
1169 svn_ra_neon__cdata_cb_t cdata_cb,
1170 svn_ra_neon__endelm_cb_t endelm_cb,
1171 void *baton,
1172 apr_hash_t *extra_headers,
1173 int *status_code,
1174 svn_boolean_t spool_response,
1175 apr_pool_t *pool)
1177 ne_xml_parser *success_parser = NULL;
1178 const char *msg;
1179 spool_reader_baton_t spool_reader_baton;
1181 if (body == NULL)
1182 SVN_ERR(svn_ra_neon__set_neon_body_provider(req, body_file));
1184 /* ### use a symbolic name somewhere for this MIME type? */
1185 ne_add_request_header(req->ne_req, "Content-Type", "text/xml");
1187 /* create a parser to read the normal response body */
1188 success_parser = svn_ra_neon__xml_parser_create(req, NULL,
1189 startelm_cb, cdata_cb,
1190 endelm_cb, baton);
1192 /* if our caller is interested in having access to this parser, call
1193 the SET_PARSER callback with BATON. */
1194 if (set_parser != NULL)
1195 set_parser(success_parser, baton);
1197 /* Register the "main" accepter and body-reader with the request --
1198 the one to use when the HTTP status is 2XX. If we are spooling
1199 the response to disk first, we use our custom spool reader. */
1200 if (spool_response)
1202 const char *tmpfile_path;
1203 SVN_ERR(svn_io_temp_dir(&tmpfile_path, pool));
1205 tmpfile_path = svn_path_join(tmpfile_path, "dav-spool", pool);
1206 /* Blow the temp-file away as soon as we eliminate the entire request */
1207 SVN_ERR(svn_io_open_unique_file2(&spool_reader_baton.spool_file,
1208 &spool_reader_baton.spool_file_name,
1209 tmpfile_path, "",
1210 svn_io_file_del_on_pool_cleanup,
1211 req->pool));
1212 spool_reader_baton.req = req;
1214 svn_ra_neon__add_response_body_reader(req, ne_accept_2xx, spool_reader,
1215 &spool_reader_baton);
1217 else
1218 attach_ne_body_reader(req, ne_accept_2xx, cancellation_callback,
1219 get_cancellation_baton(req, ne_xml_parse_v,
1220 success_parser, pool));
1222 /* run the request and get the resulting status code. */
1223 SVN_ERR(svn_ra_neon__request_dispatch(status_code,
1224 req, extra_headers, body,
1225 (strcmp(method, "PROPFIND") == 0)
1226 ? 207 : 200,
1227 0, /* not used */
1228 pool));
1230 if (spool_response)
1232 /* All done with the temporary file we spooled the response into. */
1233 (void) apr_file_close(spool_reader_baton.spool_file);
1235 /* The success parser may set an error value in req->err */
1236 SVN_RA_NEON__REQ_ERR
1237 (req, parse_spool_file(ras, spool_reader_baton.spool_file_name,
1238 success_parser, req->pool));
1239 if (req->err)
1241 svn_error_compose(req->err, svn_error_createf
1242 (SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1243 _("Error reading spooled %s request response"),
1244 method));
1245 return req->err;
1249 /* was there an XML parse error somewhere? */
1250 msg = ne_xml_get_error(success_parser);
1251 if (msg != NULL && *msg != '\0')
1252 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1253 _("The %s request returned invalid XML "
1254 "in the response: %s (%s)"),
1255 method, msg, url);
1257 return SVN_NO_ERROR;
1261 svn_error_t *
1262 svn_ra_neon__parsed_request(svn_ra_neon__session_t *sess,
1263 const char *method,
1264 const char *url,
1265 const char *body,
1266 apr_file_t *body_file,
1267 void set_parser(ne_xml_parser *parser,
1268 void *baton),
1269 svn_ra_neon__startelm_cb_t startelm_cb,
1270 svn_ra_neon__cdata_cb_t cdata_cb,
1271 svn_ra_neon__endelm_cb_t endelm_cb,
1272 void *baton,
1273 apr_hash_t *extra_headers,
1274 int *status_code,
1275 svn_boolean_t spool_response,
1276 apr_pool_t *pool)
1278 /* create/prep the request */
1279 svn_ra_neon__request_t* req = svn_ra_neon__request_create(sess, method, url,
1280 pool);
1281 svn_error_t *err = parsed_request(req,
1282 sess, method, url, body, body_file,
1283 set_parser,
1284 startelm_cb, cdata_cb, endelm_cb,
1285 baton, extra_headers, status_code,
1286 spool_response, pool);
1287 svn_ra_neon__request_destroy(req);
1288 return err;
1292 svn_error_t *
1293 svn_ra_neon__simple_request(int *code,
1294 svn_ra_neon__session_t *ras,
1295 const char *method,
1296 const char *url,
1297 apr_hash_t *extra_headers,
1298 const char *body,
1299 int okay_1, int okay_2, apr_pool_t *pool)
1301 svn_ra_neon__request_t *req =
1302 svn_ra_neon__request_create(ras, method, url, pool);
1303 svn_error_t *err;
1305 /* we don't need the status parser: it's attached to the request
1306 and detected errors will be returned there... */
1307 (void) multistatus_parser_create(req);
1309 /* svn_ra_neon__request_dispatch() adds the custom error response
1310 reader. Neon will take care of the Content-Length calculation */
1311 err = svn_ra_neon__request_dispatch(code, req, extra_headers,
1312 body ? body : "",
1313 okay_1, okay_2, pool);
1314 svn_ra_neon__request_destroy(req);
1316 return err;
1319 void
1320 svn_ra_neon__add_depth_header(apr_hash_t *extra_headers, int depth)
1322 /* assert(extra_headers != NULL);
1323 assert(depth == SVN_RA_NEON__DEPTH_ZERO
1324 || depth == SVN_RA_NEON__DEPTH_ONE
1325 || depth == SVN_RA_NEON__DEPTH_INFINITE); */
1326 apr_hash_set(extra_headers, "Depth", APR_HASH_KEY_STRING,
1327 (depth == SVN_RA_NEON__DEPTH_INFINITE)
1328 ? "infinity" : (depth == SVN_RA_NEON__DEPTH_ZERO) ? "0" : "1");
1330 return;
1334 svn_error_t *
1335 svn_ra_neon__copy(svn_ra_neon__session_t *ras,
1336 svn_boolean_t overwrite,
1337 int depth,
1338 const char *src,
1339 const char *dst,
1340 apr_pool_t *pool)
1342 const char *abs_dst;
1343 apr_hash_t *extra_headers = apr_hash_make(pool);
1345 abs_dst = apr_psprintf(pool, "%s://%s%s", ne_get_scheme(ras->ne_sess),
1346 ne_get_server_hostport(ras->ne_sess), dst);
1347 apr_hash_set(extra_headers, "Destination", APR_HASH_KEY_STRING, abs_dst);
1348 apr_hash_set(extra_headers, "Overwrite", APR_HASH_KEY_STRING,
1349 overwrite ? "T" : "F");
1350 svn_ra_neon__add_depth_header(extra_headers, depth);
1352 return svn_ra_neon__simple_request(NULL, ras, "COPY", src, extra_headers,
1353 NULL, 201, 204, pool);
1358 svn_error_t *
1359 svn_ra_neon__maybe_store_auth_info(svn_ra_neon__session_t *ras,
1360 apr_pool_t *pool)
1362 /* No auth_baton? Never mind. */
1363 if (! ras->callbacks->auth_baton)
1364 return SVN_NO_ERROR;
1366 /* If we ever got credentials, ask the iter_baton to save them. */
1367 SVN_ERR(svn_auth_save_credentials(ras->auth_iterstate,
1368 pool));
1370 return SVN_NO_ERROR;
1374 svn_error_t *
1375 svn_ra_neon__maybe_store_auth_info_after_result(svn_error_t *err,
1376 svn_ra_neon__session_t *ras,
1377 apr_pool_t *pool)
1379 if (! err || (err->apr_err != SVN_ERR_RA_NOT_AUTHORIZED))
1381 svn_error_t *err2 = svn_ra_neon__maybe_store_auth_info(ras, pool);
1382 if (err2 && ! err)
1383 return err2;
1384 else if (err)
1386 svn_error_clear(err2);
1387 return err;
1391 return err;
1395 svn_error_t *
1396 svn_ra_neon__request_dispatch(int *code_p,
1397 svn_ra_neon__request_t *req,
1398 apr_hash_t *extra_headers,
1399 const char *body,
1400 int okay_1,
1401 int okay_2,
1402 apr_pool_t *pool)
1404 ne_xml_parser *error_parser;
1405 const ne_status *statstruct;
1407 /* add any extra headers passed in by caller. */
1408 if (extra_headers != NULL)
1410 apr_hash_index_t *hi;
1411 for (hi = apr_hash_first(pool, extra_headers);
1412 hi; hi = apr_hash_next(hi))
1414 const void *key;
1415 void *val;
1416 apr_hash_this(hi, &key, NULL, &val);
1417 ne_add_request_header(req->ne_req,
1418 (const char *) key, (const char *) val);
1422 if (body)
1423 ne_set_request_body_buffer(req->ne_req, body, strlen(body));
1425 /* attach a standard <D:error> body parser to the request */
1426 error_parser = error_parser_create(req);
1428 if (req->ne_sess == req->sess->ne_sess) /* We're consuming 'session 1' */
1429 req->sess->main_session_busy = TRUE;
1430 /* run the request, see what comes back. */
1431 req->rv = ne_request_dispatch(req->ne_req);
1432 if (req->ne_sess == req->sess->ne_sess) /* We're done consuming 'session 1' */
1433 req->sess->main_session_busy = FALSE;
1435 /* Save values from the request */
1436 statstruct = ne_get_status(req->ne_req);
1437 req->code_desc = apr_pstrdup(pool, statstruct->reason_phrase);
1438 req->code = statstruct->code;
1440 if (code_p)
1441 *code_p = req->code;
1443 if (!req->marshalled_error)
1444 SVN_ERR(req->err);
1446 /* If the status code was one of the two that we expected, then go
1447 ahead and return now. IGNORE any marshalled error. */
1448 if (req->rv == NE_OK && (req->code == okay_1 || req->code == okay_2))
1449 return SVN_NO_ERROR;
1451 /* Any other errors? Report them */
1452 SVN_ERR(req->err);
1454 /* We either have a neon error, or some other error
1455 that we didn't expect. */
1456 return generate_error(req, pool);
1460 const char *
1461 svn_ra_neon__request_get_location(svn_ra_neon__request_t *request,
1462 apr_pool_t *pool)
1464 const char *val = ne_get_response_header(request->ne_req, "Location");
1465 return val ? apr_pstrdup(pool, val) : NULL;