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
27 #include <ne_compress.h>
30 #include "svn_pools.h"
32 #include "svn_string.h"
36 #include "svn_private_config.h"
44 xml_parser_cleanup(void *baton
)
46 ne_xml_destroy(baton
);
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
,
66 apr_pool_cleanup_null
);
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 },
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
},
113 validate_element(int parent
, int child
)
118 while (parent
!= multistatus_nesting_table
[i
][0]
119 && (multistatus_nesting_table
[i
][0] > 0 || i
== 0))
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
];
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
;
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 */
172 b
->in_propstat
= TRUE
;
173 b
->propstat_has_error
= FALSE
;
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
;
191 end_207_element(void *baton
, int state
,
192 const char *nspace
, const char *name
)
194 multistatus_baton_t
*b
= baton
;
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 "
206 return svn_error_create(SVN_ERR_RA_DAV_REQUEST_FAILED
, NULL
,
207 b
->description
->data
);
211 case ELEM_responsedescription
:
213 svn_stringbuf_set(b
->propstat_description
, b
->cdata
->data
);
216 if (! svn_stringbuf_isempty(b
->description
))
217 svn_stringbuf_appendcstr(b
->description
, "\n");
218 svn_stringbuf_appendstr(b
->description
, b
->cdata
);
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);
232 b
->propstat_has_error
= (status
.klass
!= 2);
234 free(status
.reason_phrase
);
237 return svn_error_create(SVN_ERR_RA_DAV_REQUEST_FAILED
, NULL
,
238 _("The response contains a non-conforming "
239 "HTTP status line"));
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': "),
250 svn_stringbuf_appendstr(b
->description
,
251 b
->propstat_description
);
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
;
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
,
273 svn_ra_neon__xml_collect_cdata
,
275 b
->cdata
= svn_stringbuf_create("", req
->pool
);
276 b
->description
= svn_stringbuf_create("", req
->pool
);
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 */
292 dav_request_cleanup(void *baton
);
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
);
305 ne_request_destroy(req
->ne_req
);
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
);
320 /* Return a path-absolute relative URL, given a URL reference (which may
321 be absolute or relative). */
323 path_from_url(const char *url
)
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
== '/')
333 /* Check whether we found the scheme/authority separator. */
334 if (*p
++ != ':' || *p
++ != '/' || *p
++ != '/')
336 /* No separator, so it must already be relative. */
340 /* Find the end of the authority section, indicated by the start of
341 a path, query, or fragment section. */
343 if (*p
== '/' || *p
== '?' || *p
== '#')
346 /* Return a pointer to the rest of the URL, or to the empty string if there
347 was no next section. */
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
,
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
);
370 req
->iterpool
= svn_pool_create(req
->pool
);
371 req
->method
= apr_pstrdup(req
->pool
, method
);
372 req
->url
= apr_pstrdup(req
->pool
, url
);
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
,
382 apr_pool_cleanup_null
);
388 compressed_body_reader_cleanup(void *baton
)
391 ne_decompress_destroy(baton
);
397 attach_ne_body_reader(svn_ra_neon__request_t
*req
,
398 ne_accept_response accpt
,
399 ne_block_reader reader
,
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
,
409 compressed_body_reader_cleanup
,
410 apr_pool_cleanup_null
);
413 ne_add_response_body_reader(req
->ne_req
, accpt
, reader
, userdata
);
419 svn_ra_neon__request_t
*req
;
420 svn_ra_neon__block_reader real_reader
;
422 } body_reader_wrapper_baton_t
;
425 body_reader_wrapper(void *userdata
, const char *data
, size_t len
)
427 body_reader_wrapper_baton_t
*b
= userdata
;
430 /* We already had an error? Bail out. */
435 b
->real_reader(b
->real_baton
, data
, len
));
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
,
449 body_reader_wrapper_baton_t
*b
= apr_palloc(req
->pool
, sizeof(*b
));
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
,
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)
475 /* Use a single loop to save CPU cycles.
477 * Maybe this element is defined as `unknown'? */
478 if (elem
->id
== ELEM_unknown
)
482 /* ELEM_unknown position in the table or NULL */
487 svn_ra_neon__xml_collect_cdata(void *baton
, int state
,
488 const char *cdata
, size_t len
)
490 svn_stringbuf_t
**b
= baton
;
493 svn_stringbuf_appendbytes(*b
, cdata
, len
);
501 svn_ra_neon__copy_href(svn_stringbuf_t
*dst
, const char *src
,
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).
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'"),
521 svn_stringbuf_setempty(dst
);
522 svn_stringbuf_appendcstr(dst
, uri
.path
);
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
);
534 const char *hostport
;
536 /* Convert the return codes. */
543 return svn_error_create(SVN_ERR_RA_DAV_PATH_NOT_FOUND
, NULL
,
544 apr_psprintf(pool
, _("'%s' path not found"),
549 return svn_error_create
550 (SVN_ERR_RA_DAV_RELOCATED
, NULL
,
553 ? _("Repository moved permanently to '%s';"
555 : _("Repository moved temporarily to '%s';"
557 svn_ra_neon__request_get_location(req
, pool
)));
560 return svn_error_create
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
));
568 errcode
= SVN_ERR_RA_NOT_AUTHORIZED
;
569 msg
= _("authorization failed");
573 msg
= _("could not connect to server");
577 msg
= _("timed out waiting for server");
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
));
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
),
598 /** Error parsing **/
601 /* Custom function of type ne_accept_response. */
602 static int ra_neon_error_accepter(void *userdata
,
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
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)
617 (strcmp(ctype
.type
, "text") == 0 && strcmp(ctype
.subtype
, "xml") == 0);
618 ne_free(ctype
.value
);
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.*/
640 static int validate_error_elements(svn_ra_neon__xml_elmid parent
,
641 svn_ra_neon__xml_elmid child
)
646 if (child
== ELEM_error
)
649 return SVN_RA_NEON__XML_INVALID
;
652 if (child
== ELEM_svn_error
653 || child
== ELEM_human_readable
)
656 return SVN_RA_NEON__XML_DECLINE
; /* ignore if something else
660 return SVN_RA_NEON__XML_DECLINE
;
668 collect_error_cdata(void *baton
, int state
,
669 const char *cdata
, size_t len
)
671 svn_stringbuf_t
**b
= baton
;
674 svn_stringbuf_appendbytes(*b
, cdata
, len
);
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
;
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
);
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 */
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");
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? */
723 if (errcode_str
&& *err
)
724 (*err
)->apr_err
= atoi(errcode_str
);
735 case ELEM_human_readable
:
736 b
->want_cdata
= b
->cdata
;
737 svn_stringbuf_setempty(b
->want_cdata
);
741 b
->want_cdata
= NULL
;
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
);
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. */
764 const char *cd
= b
->cdata
->data
;
768 if (len
> 0 && cd
[len
-1] == '\n')
771 (*err
)->message
= apr_pstrmemdup((*err
)->pool
, cd
, len
);
779 svn_error_clear(b
->tmp_err
);
782 *(b
->dst_err
) = b
->tmp_err
;
783 if (b
->marshalled_error
)
784 *(b
->marshalled_error
) = TRUE
;
798 error_parser_baton_cleanup(void *baton
)
800 error_parser_baton_t
*b
= baton
;
803 svn_error_clear(b
->tmp_err
);
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
);
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
,
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
);
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
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
,
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
)
863 (req
, (req
->sess
->callbacks
->cancel_func
)(req
->sess
->callback_baton
));
868 svn_pool_clear(req
->iterpool
);
871 /* This is the beginning of a new body pull. Rewind the file. */
872 apr_off_t offset
= 0;
875 svn_io_file_seek(body_file
, APR_SET
, &offset
, req
->iterpool
));
876 return (req
->err
? -1 : 0);
880 apr_size_t nbytes
= buflen
;
881 svn_error_t
*err
= svn_io_file_read(body_file
, buffer
, &nbytes
,
885 if (APR_STATUS_IS_EOF(err
->apr_err
))
887 svn_error_clear(err
);
891 SVN_RA_NEON__REQ_ERR(req
, err
);
900 svn_error_t
*svn_ra_neon__set_neon_body_provider(svn_ra_neon__request_t
*req
,
901 apr_file_t
*body_file
)
905 body_provider_baton_t
*b
= apr_palloc(req
->pool
, sizeof(*b
));
907 status
= apr_file_info_get(&finfo
, APR_FINFO_SIZE
, body_file
);
909 return svn_error_wrap_apr(status
,
910 _("Can't calculate the request body size"));
912 b
->body_file
= body_file
;
915 ne_set_request_body_provider(req
->ne_req
, (size_t) finfo
.size
,
916 ra_neon_body_provider
, b
);
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. */
931 spool_reader(void *userdata
,
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
);
946 parse_spool_file(svn_ra_neon__session_t
*ras
,
947 const char *spool_file_name
,
948 ne_xml_parser
*success_parser
,
951 apr_file_t
*spool_file
;
952 svn_stream_t
*spool_stream
;
953 char *buf
= apr_palloc(pool
, SVN__STREAM_CHUNK_SIZE
);
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
);
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
));
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 */
973 if (len
!= SVN__STREAM_CHUNK_SIZE
)
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.
989 svn_ra_neon__request_t
*req
;
990 ne_xml_parser
*parser
;
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
;
999 wrapper_startelm_cb(void *baton
,
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
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
1016 svn_error_create(SVN_ERR_XML_MALFORMED
, NULL
, NULL
));
1019 return NE_XML_ABORT
;
1025 wrapper_cdata_cb(void *baton
, int state
, const char *cdata
, size_t len
)
1027 parser_wrapper_baton_t
*pwb
= baton
;
1030 SVN_RA_NEON__REQ_ERR
1032 pwb
->cdata_cb(pwb
->baton
, state
, cdata
, len
));
1035 return NE_XML_ABORT
;
1041 wrapper_endelm_cb(void *baton
,
1046 parser_wrapper_baton_t
*pwb
= baton
;
1049 SVN_RA_NEON__REQ_ERR
1051 pwb
->endelm_cb(pwb
->baton
, state
, nspace
, name
));
1054 return NE_XML_ABORT
;
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
;
1068 if (sess
->callbacks
->cancel_func
)
1069 SVN_RA_NEON__REQ_ERR
1071 (sess
->callbacks
->cancel_func
)(sess
->callback_baton
));
1076 return ne_xml_parse(pwb
->parser
, data
, len
);
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
,
1087 ne_xml_parser
*p
= xml_parser_create(req
);
1088 parser_wrapper_baton_t
*pwb
= apr_palloc(req
->pool
, sizeof(*pwb
));
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
,
1100 wrapper_endelm_cb
, pwb
);
1103 attach_ne_body_reader(req
, accpt
, wrapper_reader_cb
, pwb
);
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
;
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
1125 (ras
->callbacks
->cancel_func
)(ras
->callback_baton
));
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
,
1140 cancellation_baton_t
*b
= apr_palloc(pool
, sizeof(*b
));
1142 b
->real_cb
= real_cb
;
1143 b
->real_userdata
= real_userdata
;
1149 /* See doc string for svn_ra_neon__parsed_request. */
1150 static svn_error_t
*
1151 parsed_request(svn_ra_neon__session_t
*ras
,
1155 apr_file_t
*body_file
,
1156 void set_parser(ne_xml_parser
*parser
,
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
,
1162 apr_hash_t
*extra_headers
,
1164 svn_boolean_t spool_response
,
1167 svn_ra_neon__request_t
*req
;
1168 ne_xml_parser
*success_parser
= NULL
;
1170 spool_reader_baton_t spool_reader_baton
;
1172 /* create/prep the request */
1173 req
= svn_ra_neon__request_create(ras
, method
, url
, pool
);
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
,
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. */
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
,
1204 svn_io_file_del_on_pool_cleanup
,
1206 spool_reader_baton
.req
= req
;
1208 svn_ra_neon__add_response_body_reader(req
, ne_accept_2xx
, spool_reader
,
1209 &spool_reader_baton
);
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)
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
));
1235 svn_error_compose(req
->err
, svn_error_createf
1236 (SVN_ERR_RA_DAV_REQUEST_FAILED
, NULL
,
1237 _("Error reading spooled %s request response"),
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)"),
1251 svn_ra_neon__request_destroy(req
);
1253 return SVN_NO_ERROR
;
1258 svn_ra_neon__parsed_request(svn_ra_neon__session_t
*sess
,
1262 apr_file_t
*body_file
,
1263 void set_parser(ne_xml_parser
*parser
,
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
,
1269 apr_hash_t
*extra_headers
,
1271 svn_boolean_t spool_response
,
1274 SVN_ERR_W(parsed_request(sess
, method
, url
, body
, body_file
,
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
;
1286 svn_ra_neon__simple_request(int *code
,
1287 svn_ra_neon__session_t
*ras
,
1290 apr_hash_t
*extra_headers
,
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
,
1305 okay_1
, okay_2
, pool
));
1306 svn_ra_neon__request_destroy(req
);
1308 return SVN_NO_ERROR
;
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");
1327 svn_ra_neon__copy(svn_ra_neon__session_t
*ras
,
1328 svn_boolean_t overwrite
,
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
);
1351 svn_ra_neon__maybe_store_auth_info(svn_ra_neon__session_t
*ras
,
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
,
1362 return SVN_NO_ERROR
;
1367 svn_ra_neon__maybe_store_auth_info_after_result(svn_error_t
*err
,
1368 svn_ra_neon__session_t
*ras
,
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
);
1378 svn_error_clear(err2
);
1388 svn_ra_neon__request_dispatch(int *code_p
,
1389 svn_ra_neon__request_t
*req
,
1390 apr_hash_t
*extra_headers
,
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
))
1408 apr_hash_this(hi
, &key
, NULL
, &val
);
1409 ne_add_request_header(req
->ne_req
,
1410 (const char *) key
, (const char *) val
);
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
;
1433 *code_p
= req
->code
;
1435 if (!req
->marshalled_error
)
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 */
1446 /* We either have a neon error, or some other error
1447 that we didn't expect. */
1448 return generate_error(req
, pool
);
1453 svn_ra_neon__request_get_location(svn_ra_neon__request_t
*request
,
1456 const char *val
= ne_get_response_header(request
->ne_req
, "Location");
1457 return val
? apr_pstrdup(pool
, val
) : NULL
;