In the command-line client, forbid
[svn.git] / subversion / libsvn_ra_neon / util.c
blob2a2854815a053f4f2f5c217cd6003642cdce1714
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 msg = _("authorization failed");
570 break;
572 case NE_CONNECT:
573 msg = _("could not connect to server");
574 break;
576 case NE_TIMEOUT:
577 msg = _("timed out waiting for server");
578 break;
580 default:
581 /* Get the error string from neon and convert to UTF-8. */
582 SVN_ERR(svn_utf_cstring_to_utf8(&msg, ne_get_error(req->ne_sess), pool));
583 break;
586 /* The hostname may contain non-ASCII characters, so convert it to UTF-8. */
587 SVN_ERR(svn_utf_cstring_to_utf8(&hostport,
588 ne_get_server_hostport(req->ne_sess), pool));
590 /*### This is a translation nightmare. Make sure to compose full strings
591 and mark those for translation. */
592 return svn_error_createf(errcode, NULL, "%s: %s (%s://%s)",
593 context, msg, ne_get_scheme(req->ne_sess),
594 hostport);
598 /** Error parsing **/
601 /* Custom function of type ne_accept_response. */
602 static int ra_neon_error_accepter(void *userdata,
603 ne_request *req,
604 const ne_status *st)
606 /* Before, this function was being run for *all* responses including
607 the 401 auth challenge. In neon 0.24.x that was harmless. But
608 in neon 0.25.0, trying to parse a 401 response as XML using
609 ne_xml_parse_v aborts the response; so the auth hooks never got a
610 chance. */
611 ne_content_type ctype;
613 /* Only accept non-2xx responses with text/xml content-type */
614 if (st->klass != 2 && ne_get_content_type(req, &ctype) == 0)
616 int is_xml =
617 (strcmp(ctype.type, "text") == 0 && strcmp(ctype.subtype, "xml") == 0);
618 ne_free(ctype.value);
619 return is_xml;
621 else
622 return 0;
626 static const svn_ra_neon__xml_elm_t error_elements[] =
628 { "DAV:", "error", ELEM_error, 0 },
629 { "svn:", "error", ELEM_svn_error, 0 },
630 { "http://apache.org/dav/xmlns", "human-readable",
631 ELEM_human_readable, SVN_RA_NEON__XML_CDATA },
633 /* ### our validator doesn't yet recognize the rich, specific
634 <D:some-condition-failed/> objects as defined by DeltaV.*/
636 { NULL }
640 static int validate_error_elements(svn_ra_neon__xml_elmid parent,
641 svn_ra_neon__xml_elmid child)
643 switch (parent)
645 case ELEM_root:
646 if (child == ELEM_error)
647 return child;
648 else
649 return SVN_RA_NEON__XML_INVALID;
651 case ELEM_error:
652 if (child == ELEM_svn_error
653 || child == ELEM_human_readable)
654 return child;
655 else
656 return SVN_RA_NEON__XML_DECLINE; /* ignore if something else
657 was in there */
659 default:
660 return SVN_RA_NEON__XML_DECLINE;
663 /* NOTREACHED */
667 static int
668 collect_error_cdata(void *baton, int state,
669 const char *cdata, size_t len)
671 svn_stringbuf_t **b = baton;
673 if (*b)
674 svn_stringbuf_appendbytes(*b, cdata, len);
676 return 0;
679 typedef struct error_parser_baton
681 svn_stringbuf_t *want_cdata;
682 svn_stringbuf_t *cdata;
684 svn_error_t **dst_err;
685 svn_error_t *tmp_err;
686 svn_boolean_t *marshalled_error;
687 } error_parser_baton_t;
690 static int
691 start_err_element(void *baton, int parent,
692 const char *nspace, const char *name, const char **atts)
694 const svn_ra_neon__xml_elm_t *elm
695 = svn_ra_neon__lookup_xml_elem(error_elements, nspace, name);
696 int acc = elm
697 ? validate_error_elements(parent, elm->id) : SVN_RA_NEON__XML_DECLINE;
698 error_parser_baton_t *b = baton;
699 svn_error_t **err = &(b->tmp_err);
701 if (acc < 1) /* ! > 0 */
702 return acc;
704 switch (elm->id)
706 case ELEM_svn_error:
708 /* allocate the svn_error_t. Hopefully the value will be
709 overwritten by the <human-readable> tag, or even someday by
710 a <D:failed-precondition/> tag. */
711 *err = svn_error_create(APR_EGENERAL, NULL,
712 "General svn error from server");
713 break;
715 case ELEM_human_readable:
717 /* get the errorcode attribute if present */
718 const char *errcode_str =
719 svn_xml_get_attr_value("errcode", /* ### make constant in
720 some mod_dav header? */
721 atts);
723 if (errcode_str && *err)
724 (*err)->apr_err = atoi(errcode_str);
726 break;
729 default:
730 break;
733 switch (elm->id)
735 case ELEM_human_readable:
736 b->want_cdata = b->cdata;
737 svn_stringbuf_setempty(b->want_cdata);
738 break;
740 default:
741 b->want_cdata = NULL;
742 break;
745 return elm->id;
748 static int
749 end_err_element(void *baton, int state, const char *nspace, const char *name)
751 error_parser_baton_t *b = baton;
752 svn_error_t **err = &(b->tmp_err);
754 switch (state)
756 case ELEM_human_readable:
758 if (b->cdata->data && *err)
760 /* On the server dav_error_response_tag() will add a leading
761 and trailing newline if DEBUG_CR is defined in mod_dav.h,
762 so remove any such characters here. */
763 apr_size_t len;
764 const char *cd = b->cdata->data;
765 if (*cd == '\n')
766 ++cd;
767 len = strlen(cd);
768 if (len > 0 && cd[len-1] == '\n')
769 --len;
771 (*err)->message = apr_pstrmemdup((*err)->pool, cd, len);
773 break;
776 case ELEM_error:
778 if (*(b->dst_err))
779 svn_error_clear(b->tmp_err);
780 else if (b->tmp_err)
782 *(b->dst_err) = b->tmp_err;
783 if (b->marshalled_error)
784 *(b->marshalled_error) = TRUE;
786 b->tmp_err = NULL;
787 break;
790 default:
791 break;
794 return 0;
797 static apr_status_t
798 error_parser_baton_cleanup(void *baton)
800 error_parser_baton_t *b = baton;
802 if (b->tmp_err)
803 svn_error_clear(b->tmp_err);
805 return APR_SUCCESS;
808 static ne_xml_parser *
809 error_parser_create(svn_ra_neon__request_t *req)
811 error_parser_baton_t *b = apr_palloc(req->pool, sizeof(*b));
812 ne_xml_parser *error_parser;
814 b->dst_err = &(req->err);
815 b->marshalled_error = &(req->marshalled_error);
816 b->tmp_err = NULL;
818 b->want_cdata = NULL;
819 b->cdata = svn_stringbuf_create("", req->pool);
821 /* attach a standard <D:error> body parser to the request */
822 error_parser = xml_parser_create(req);
823 ne_xml_push_handler(error_parser,
824 start_err_element,
825 collect_error_cdata,
826 end_err_element, b);
828 apr_pool_cleanup_register(req->pool, b,
829 error_parser_baton_cleanup,
830 apr_pool_cleanup_null);
832 /* Register the "error" accepter and body-reader with the request --
833 the one to use when HTTP status is *not* 2XX */
834 attach_ne_body_reader(req, ra_neon_error_accepter,
835 ne_xml_parse_v, error_parser);
837 return error_parser;
841 /* A body provider for ne_set_request_body_provider that pulls data
842 * from an APR file. See ne_request.h for a description of the
843 * interface.
846 typedef struct
848 svn_ra_neon__request_t *req;
849 apr_file_t *body_file;
850 } body_provider_baton_t;
852 static ssize_t ra_neon_body_provider(void *userdata,
853 char *buffer,
854 size_t buflen)
856 body_provider_baton_t *b = userdata;
857 svn_ra_neon__request_t *req = b->req;
858 apr_file_t *body_file = b->body_file;
860 if (req->sess->callbacks &&
861 req->sess->callbacks->cancel_func)
862 SVN_RA_NEON__REQ_ERR
863 (req, (req->sess->callbacks->cancel_func)(req->sess->callback_baton));
865 if (req->err)
866 return -1;
868 svn_pool_clear(req->iterpool);
869 if (buflen == 0)
871 /* This is the beginning of a new body pull. Rewind the file. */
872 apr_off_t offset = 0;
873 SVN_RA_NEON__REQ_ERR
874 (b->req,
875 svn_io_file_seek(body_file, APR_SET, &offset, req->iterpool));
876 return (req->err ? -1 : 0);
878 else
880 apr_size_t nbytes = buflen;
881 svn_error_t *err = svn_io_file_read(body_file, buffer, &nbytes,
882 req->iterpool);
883 if (err)
885 if (APR_STATUS_IS_EOF(err->apr_err))
887 svn_error_clear(err);
888 return 0;
891 SVN_RA_NEON__REQ_ERR(req, err);
892 return -1;
894 else
895 return nbytes;
900 svn_error_t *svn_ra_neon__set_neon_body_provider(svn_ra_neon__request_t *req,
901 apr_file_t *body_file)
903 apr_status_t status;
904 apr_finfo_t finfo;
905 body_provider_baton_t *b = apr_palloc(req->pool, sizeof(*b));
907 status = apr_file_info_get(&finfo, APR_FINFO_SIZE, body_file);
908 if (status)
909 return svn_error_wrap_apr(status,
910 _("Can't calculate the request body size"));
912 b->body_file = body_file;
913 b->req = req;
915 ne_set_request_body_provider(req->ne_req, (size_t) finfo.size,
916 ra_neon_body_provider, b);
917 return SVN_NO_ERROR;
921 typedef struct spool_reader_baton_t
923 const char *spool_file_name;
924 apr_file_t *spool_file;
925 svn_ra_neon__request_t *req;
926 } spool_reader_baton_t;
929 /* This implements the svn_ra_neon__block_reader() callback interface. */
930 static svn_error_t *
931 spool_reader(void *userdata,
932 const char *buf,
933 size_t len)
935 spool_reader_baton_t *baton = userdata;
937 SVN_ERR(svn_io_file_write_full(baton->spool_file, buf,
938 len, NULL, baton->req->iterpool));
939 svn_pool_clear(baton->req->iterpool);
941 return SVN_NO_ERROR;
945 static svn_error_t *
946 parse_spool_file(svn_ra_neon__session_t *ras,
947 const char *spool_file_name,
948 ne_xml_parser *success_parser,
949 apr_pool_t *pool)
951 apr_file_t *spool_file;
952 svn_stream_t *spool_stream;
953 char *buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
954 apr_size_t len;
956 SVN_ERR(svn_io_file_open(&spool_file, spool_file_name,
957 (APR_READ | APR_BUFFERED), APR_OS_DEFAULT, pool));
958 spool_stream = svn_stream_from_aprfile(spool_file, pool);
959 while (1)
961 if (ras->callbacks &&
962 ras->callbacks->cancel_func)
963 SVN_ERR((ras->callbacks->cancel_func)(ras->callback_baton));
965 len = SVN__STREAM_CHUNK_SIZE;
966 SVN_ERR(svn_stream_read(spool_stream, buf, &len));
967 if (len > 0)
968 if (ne_xml_parse(success_parser, buf, len) != 0)
969 /* The parse encountered an error or
970 was aborted by a user defined callback */
971 break;
973 if (len != SVN__STREAM_CHUNK_SIZE)
974 break;
976 return SVN_NO_ERROR;
980 /* A baton that is used along with a set of Neon ne_startelm_cb,
981 * ne_cdata_cb, and ne_endelm_cb callbacks to handle conversion
982 * from Subversion style errors to Neon style errors.
984 * The underlying Subversion callbacks are called, and if errors
985 * are returned they are stored in this baton and a Neon level
986 * error code is returned to the parser.
988 typedef struct {
989 svn_ra_neon__request_t *req;
990 ne_xml_parser *parser;
992 void *baton;
993 svn_ra_neon__startelm_cb_t startelm_cb;
994 svn_ra_neon__cdata_cb_t cdata_cb;
995 svn_ra_neon__endelm_cb_t endelm_cb;
996 } parser_wrapper_baton_t;
998 static int
999 wrapper_startelm_cb(void *baton,
1000 int parent,
1001 const char *nspace,
1002 const char *name,
1003 const char **atts)
1005 parser_wrapper_baton_t *pwb = baton;
1006 int elem = SVN_RA_NEON__XML_DECLINE;
1008 if (pwb->startelm_cb)
1009 SVN_RA_NEON__REQ_ERR
1010 (pwb->req,
1011 pwb->startelm_cb(&elem, pwb->baton, parent, nspace, name, atts));
1013 if (elem == SVN_RA_NEON__XML_INVALID)
1014 SVN_RA_NEON__REQ_ERR
1015 (pwb->req,
1016 svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL));
1018 if (pwb->req->err)
1019 return NE_XML_ABORT;
1021 return elem;
1024 static int
1025 wrapper_cdata_cb(void *baton, int state, const char *cdata, size_t len)
1027 parser_wrapper_baton_t *pwb = baton;
1029 if (pwb->cdata_cb)
1030 SVN_RA_NEON__REQ_ERR
1031 (pwb->req,
1032 pwb->cdata_cb(pwb->baton, state, cdata, len));
1034 if (pwb->req->err)
1035 return NE_XML_ABORT;
1037 return 0;
1040 static int
1041 wrapper_endelm_cb(void *baton,
1042 int state,
1043 const char *nspace,
1044 const char *name)
1046 parser_wrapper_baton_t *pwb = baton;
1048 if (pwb->endelm_cb)
1049 SVN_RA_NEON__REQ_ERR
1050 (pwb->req,
1051 pwb->endelm_cb(pwb->baton, state, nspace, name));
1053 if (pwb->req->err)
1054 return NE_XML_ABORT;
1056 return 0;
1059 static int
1060 wrapper_reader_cb(void *baton, const char *data, size_t len)
1062 parser_wrapper_baton_t *pwb = baton;
1063 svn_ra_neon__session_t *sess = pwb->req->sess;
1065 if (pwb->req->err)
1066 return 1;
1068 if (sess->callbacks->cancel_func)
1069 SVN_RA_NEON__REQ_ERR
1070 (pwb->req,
1071 (sess->callbacks->cancel_func)(sess->callback_baton));
1073 if (pwb->req->err)
1074 return 1;
1076 return ne_xml_parse(pwb->parser, data, len);
1079 ne_xml_parser *
1080 svn_ra_neon__xml_parser_create(svn_ra_neon__request_t *req,
1081 ne_accept_response accpt,
1082 svn_ra_neon__startelm_cb_t startelm_cb,
1083 svn_ra_neon__cdata_cb_t cdata_cb,
1084 svn_ra_neon__endelm_cb_t endelm_cb,
1085 void *baton)
1087 ne_xml_parser *p = xml_parser_create(req);
1088 parser_wrapper_baton_t *pwb = apr_palloc(req->pool, sizeof(*pwb));
1090 pwb->req = req;
1091 pwb->parser = p;
1092 pwb->baton = baton;
1093 pwb->startelm_cb = startelm_cb;
1094 pwb->cdata_cb = cdata_cb;
1095 pwb->endelm_cb = endelm_cb;
1097 ne_xml_push_handler(p,
1098 wrapper_startelm_cb,
1099 wrapper_cdata_cb,
1100 wrapper_endelm_cb, pwb);
1102 if (accpt)
1103 attach_ne_body_reader(req, accpt, wrapper_reader_cb, pwb);
1105 return p;
1109 typedef struct cancellation_baton_t
1111 ne_block_reader real_cb;
1112 void *real_userdata;
1113 svn_ra_neon__request_t *req;
1114 } cancellation_baton_t;
1116 static int
1117 cancellation_callback(void *userdata, const char *block, size_t len)
1119 cancellation_baton_t *b = userdata;
1120 svn_ra_neon__session_t *ras = b->req->sess;
1122 if (ras->callbacks->cancel_func)
1123 SVN_RA_NEON__REQ_ERR
1124 (b->req,
1125 (ras->callbacks->cancel_func)(ras->callback_baton));
1127 if (b->req->err)
1128 return 1;
1129 else
1130 return (b->real_cb)(b->real_userdata, block, len);
1134 static cancellation_baton_t *
1135 get_cancellation_baton(svn_ra_neon__request_t *req,
1136 ne_block_reader real_cb,
1137 void *real_userdata,
1138 apr_pool_t *pool)
1140 cancellation_baton_t *b = apr_palloc(pool, sizeof(*b));
1142 b->real_cb = real_cb;
1143 b->real_userdata = real_userdata;
1144 b->req = req;
1146 return b;
1149 /* See doc string for svn_ra_neon__parsed_request. */
1150 static svn_error_t *
1151 parsed_request(svn_ra_neon__session_t *ras,
1152 const char *method,
1153 const char *url,
1154 const char *body,
1155 apr_file_t *body_file,
1156 void set_parser(ne_xml_parser *parser,
1157 void *baton),
1158 svn_ra_neon__startelm_cb_t startelm_cb,
1159 svn_ra_neon__cdata_cb_t cdata_cb,
1160 svn_ra_neon__endelm_cb_t endelm_cb,
1161 void *baton,
1162 apr_hash_t *extra_headers,
1163 int *status_code,
1164 svn_boolean_t spool_response,
1165 apr_pool_t *pool)
1167 svn_ra_neon__request_t *req;
1168 ne_xml_parser *success_parser = NULL;
1169 const char *msg;
1170 spool_reader_baton_t spool_reader_baton;
1172 /* create/prep the request */
1173 req = svn_ra_neon__request_create(ras, method, url, pool);
1175 if (body == NULL)
1176 SVN_ERR(svn_ra_neon__set_neon_body_provider(req, body_file));
1178 /* ### use a symbolic name somewhere for this MIME type? */
1179 ne_add_request_header(req->ne_req, "Content-Type", "text/xml");
1181 /* create a parser to read the normal response body */
1182 success_parser = svn_ra_neon__xml_parser_create(req, NULL,
1183 startelm_cb, cdata_cb,
1184 endelm_cb, baton);
1186 /* if our caller is interested in having access to this parser, call
1187 the SET_PARSER callback with BATON. */
1188 if (set_parser != NULL)
1189 set_parser(success_parser, baton);
1191 /* Register the "main" accepter and body-reader with the request --
1192 the one to use when the HTTP status is 2XX. If we are spooling
1193 the response to disk first, we use our custom spool reader. */
1194 if (spool_response)
1196 const char *tmpfile_path;
1197 SVN_ERR(svn_io_temp_dir(&tmpfile_path, pool));
1199 tmpfile_path = svn_path_join(tmpfile_path, "dav-spool", pool);
1200 /* Blow the temp-file away as soon as we eliminate the entire request */
1201 SVN_ERR(svn_io_open_unique_file2(&spool_reader_baton.spool_file,
1202 &spool_reader_baton.spool_file_name,
1203 tmpfile_path, "",
1204 svn_io_file_del_on_pool_cleanup,
1205 req->pool));
1206 spool_reader_baton.req = req;
1208 svn_ra_neon__add_response_body_reader(req, ne_accept_2xx, spool_reader,
1209 &spool_reader_baton);
1211 else
1212 attach_ne_body_reader(req, ne_accept_2xx, cancellation_callback,
1213 get_cancellation_baton(req, ne_xml_parse_v,
1214 success_parser, pool));
1216 /* run the request and get the resulting status code. */
1217 SVN_ERR(svn_ra_neon__request_dispatch(status_code,
1218 req, extra_headers, body,
1219 (strcmp(method, "PROPFIND") == 0)
1220 ? 207 : 200,
1221 0, /* not used */
1222 pool));
1224 if (spool_response)
1226 /* All done with the temporary file we spooled the response into. */
1227 (void) apr_file_close(spool_reader_baton.spool_file);
1229 /* The success parser may set an error value in req->err */
1230 SVN_RA_NEON__REQ_ERR
1231 (req, parse_spool_file(ras, spool_reader_baton.spool_file_name,
1232 success_parser, req->pool));
1233 if (req->err)
1235 svn_error_compose(req->err, svn_error_createf
1236 (SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1237 _("Error reading spooled %s request response"),
1238 method));
1239 return req->err;
1243 /* was there an XML parse error somewhere? */
1244 msg = ne_xml_get_error(success_parser);
1245 if (msg != NULL && *msg != '\0')
1246 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1247 _("The %s request returned invalid XML "
1248 "in the response: %s (%s)"),
1249 method, msg, url);
1251 svn_ra_neon__request_destroy(req);
1253 return SVN_NO_ERROR;
1257 svn_error_t *
1258 svn_ra_neon__parsed_request(svn_ra_neon__session_t *sess,
1259 const char *method,
1260 const char *url,
1261 const char *body,
1262 apr_file_t *body_file,
1263 void set_parser(ne_xml_parser *parser,
1264 void *baton),
1265 svn_ra_neon__startelm_cb_t startelm_cb,
1266 svn_ra_neon__cdata_cb_t cdata_cb,
1267 svn_ra_neon__endelm_cb_t endelm_cb,
1268 void *baton,
1269 apr_hash_t *extra_headers,
1270 int *status_code,
1271 svn_boolean_t spool_response,
1272 apr_pool_t *pool)
1274 SVN_ERR_W(parsed_request(sess, method, url, body, body_file,
1275 set_parser,
1276 startelm_cb, cdata_cb, endelm_cb,
1277 baton, extra_headers, status_code,
1278 spool_response, pool),
1279 apr_psprintf(pool,_("%s request failed on '%s'"), method, url));
1281 return SVN_NO_ERROR;
1285 svn_error_t *
1286 svn_ra_neon__simple_request(int *code,
1287 svn_ra_neon__session_t *ras,
1288 const char *method,
1289 const char *url,
1290 apr_hash_t *extra_headers,
1291 const char *body,
1292 int okay_1, int okay_2, apr_pool_t *pool)
1294 svn_ra_neon__request_t *req =
1295 svn_ra_neon__request_create(ras, method, url, pool);
1297 /* we don't need the status parser: it's attached to the request
1298 and detected errors will be returned there... */
1299 (void) multistatus_parser_create(req);
1301 /* svn_ra_neon__request_dispatch() adds the custom error response
1302 reader. Neon will take care of the Content-Length calculation */
1303 SVN_ERR(svn_ra_neon__request_dispatch(code, req, extra_headers,
1304 body ? body : "",
1305 okay_1, okay_2, pool));
1306 svn_ra_neon__request_destroy(req);
1308 return SVN_NO_ERROR;
1311 void
1312 svn_ra_neon__add_depth_header(apr_hash_t *extra_headers, int depth)
1314 /* assert(extra_headers != NULL);
1315 assert(depth == SVN_RA_NEON__DEPTH_ZERO
1316 || depth == SVN_RA_NEON__DEPTH_ONE
1317 || depth == SVN_RA_NEON__DEPTH_INFINITE); */
1318 apr_hash_set(extra_headers, "Depth", APR_HASH_KEY_STRING,
1319 (depth == SVN_RA_NEON__DEPTH_INFINITE)
1320 ? "infinity" : (depth == SVN_RA_NEON__DEPTH_ZERO) ? "0" : "1");
1322 return;
1326 svn_error_t *
1327 svn_ra_neon__copy(svn_ra_neon__session_t *ras,
1328 svn_boolean_t overwrite,
1329 int depth,
1330 const char *src,
1331 const char *dst,
1332 apr_pool_t *pool)
1334 const char *abs_dst;
1335 apr_hash_t *extra_headers = apr_hash_make(pool);
1337 abs_dst = apr_psprintf(pool, "%s://%s%s", ne_get_scheme(ras->ne_sess),
1338 ne_get_server_hostport(ras->ne_sess), dst);
1339 apr_hash_set(extra_headers, "Destination", APR_HASH_KEY_STRING, abs_dst);
1340 apr_hash_set(extra_headers, "Overwrite", APR_HASH_KEY_STRING,
1341 overwrite ? "T" : "F");
1342 svn_ra_neon__add_depth_header(extra_headers, depth);
1344 return svn_ra_neon__simple_request(NULL, ras, "COPY", src, extra_headers,
1345 NULL, 201, 204, pool);
1350 svn_error_t *
1351 svn_ra_neon__maybe_store_auth_info(svn_ra_neon__session_t *ras,
1352 apr_pool_t *pool)
1354 /* No auth_baton? Never mind. */
1355 if (! ras->callbacks->auth_baton)
1356 return SVN_NO_ERROR;
1358 /* If we ever got credentials, ask the iter_baton to save them. */
1359 SVN_ERR(svn_auth_save_credentials(ras->auth_iterstate,
1360 pool));
1362 return SVN_NO_ERROR;
1366 svn_error_t *
1367 svn_ra_neon__maybe_store_auth_info_after_result(svn_error_t *err,
1368 svn_ra_neon__session_t *ras,
1369 apr_pool_t *pool)
1371 if (! err || (err->apr_err != SVN_ERR_RA_NOT_AUTHORIZED))
1373 svn_error_t *err2 = svn_ra_neon__maybe_store_auth_info(ras, pool);
1374 if (err2 && ! err)
1375 return err2;
1376 else if (err)
1378 svn_error_clear(err2);
1379 return err;
1383 return err;
1387 svn_error_t *
1388 svn_ra_neon__request_dispatch(int *code_p,
1389 svn_ra_neon__request_t *req,
1390 apr_hash_t *extra_headers,
1391 const char *body,
1392 int okay_1,
1393 int okay_2,
1394 apr_pool_t *pool)
1396 ne_xml_parser *error_parser;
1397 const ne_status *statstruct;
1399 /* add any extra headers passed in by caller. */
1400 if (extra_headers != NULL)
1402 apr_hash_index_t *hi;
1403 for (hi = apr_hash_first(pool, extra_headers);
1404 hi; hi = apr_hash_next(hi))
1406 const void *key;
1407 void *val;
1408 apr_hash_this(hi, &key, NULL, &val);
1409 ne_add_request_header(req->ne_req,
1410 (const char *) key, (const char *) val);
1414 if (body)
1415 ne_set_request_body_buffer(req->ne_req, body, strlen(body));
1417 /* attach a standard <D:error> body parser to the request */
1418 error_parser = error_parser_create(req);
1420 if (req->ne_sess == req->sess->ne_sess) /* We're consuming 'session 1' */
1421 req->sess->main_session_busy = TRUE;
1422 /* run the request, see what comes back. */
1423 req->rv = ne_request_dispatch(req->ne_req);
1424 if (req->ne_sess == req->sess->ne_sess) /* We're done consuming 'session 1' */
1425 req->sess->main_session_busy = FALSE;
1427 /* Save values from the request */
1428 statstruct = ne_get_status(req->ne_req);
1429 req->code_desc = apr_pstrdup(pool, statstruct->reason_phrase);
1430 req->code = statstruct->code;
1432 if (code_p)
1433 *code_p = req->code;
1435 if (!req->marshalled_error)
1436 SVN_ERR(req->err);
1438 /* If the status code was one of the two that we expected, then go
1439 ahead and return now. IGNORE any marshalled error. */
1440 if (req->rv == NE_OK && (req->code == okay_1 || req->code == okay_2))
1441 return SVN_NO_ERROR;
1443 /* Any other errors? Report them */
1444 SVN_ERR(req->err);
1446 /* We either have a neon error, or some other error
1447 that we didn't expect. */
1448 return generate_error(req, pool);
1452 const char *
1453 svn_ra_neon__request_get_location(svn_ra_neon__request_t *request,
1454 apr_pool_t *pool)
1456 const char *val = ne_get_response_header(request->ne_req, "Location");
1457 return val ? apr_pstrdup(pool, val) : NULL;