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
;
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
);
577 msg
= _("authorization failed");
582 msg
= _("could not connect to server");
586 msg
= _("timed out waiting for server");
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
));
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
),
607 /** Error parsing **/
610 /* Custom function of type ne_accept_response. */
611 static int ra_neon_error_accepter(void *userdata
,
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
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)
626 (strcmp(ctype
.type
, "text") == 0 && strcmp(ctype
.subtype
, "xml") == 0);
627 ne_free(ctype
.value
);
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.*/
649 static int validate_error_elements(svn_ra_neon__xml_elmid parent
,
650 svn_ra_neon__xml_elmid child
)
655 if (child
== ELEM_error
)
658 return SVN_RA_NEON__XML_INVALID
;
661 if (child
== ELEM_svn_error
662 || child
== ELEM_human_readable
)
665 return SVN_RA_NEON__XML_DECLINE
; /* ignore if something else
669 return SVN_RA_NEON__XML_DECLINE
;
677 collect_error_cdata(void *baton
, int state
,
678 const char *cdata
, size_t len
)
680 svn_stringbuf_t
**b
= baton
;
683 svn_stringbuf_appendbytes(*b
, cdata
, len
);
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
;
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
);
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 */
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");
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? */
732 if (errcode_str
&& *err
)
733 (*err
)->apr_err
= atoi(errcode_str
);
744 case ELEM_human_readable
:
745 b
->want_cdata
= b
->cdata
;
746 svn_stringbuf_setempty(b
->want_cdata
);
750 b
->want_cdata
= NULL
;
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
);
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. */
773 const char *cd
= b
->cdata
->data
;
777 if (len
> 0 && cd
[len
-1] == '\n')
780 (*err
)->message
= apr_pstrmemdup((*err
)->pool
, cd
, len
);
788 svn_error_clear(b
->tmp_err
);
791 *(b
->dst_err
) = b
->tmp_err
;
792 if (b
->marshalled_error
)
793 *(b
->marshalled_error
) = TRUE
;
807 error_parser_baton_cleanup(void *baton
)
809 error_parser_baton_t
*b
= baton
;
812 svn_error_clear(b
->tmp_err
);
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
);
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
,
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
);
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
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
,
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
)
872 (req
, (req
->sess
->callbacks
->cancel_func
)(req
->sess
->callback_baton
));
877 svn_pool_clear(req
->iterpool
);
880 /* This is the beginning of a new body pull. Rewind the file. */
881 apr_off_t offset
= 0;
884 svn_io_file_seek(body_file
, APR_SET
, &offset
, req
->iterpool
));
885 return (req
->err
? -1 : 0);
889 apr_size_t nbytes
= buflen
;
890 svn_error_t
*err
= svn_io_file_read(body_file
, buffer
, &nbytes
,
894 if (APR_STATUS_IS_EOF(err
->apr_err
))
896 svn_error_clear(err
);
900 SVN_RA_NEON__REQ_ERR(req
, err
);
909 svn_error_t
*svn_ra_neon__set_neon_body_provider(svn_ra_neon__request_t
*req
,
910 apr_file_t
*body_file
)
914 body_provider_baton_t
*b
= apr_palloc(req
->pool
, sizeof(*b
));
916 status
= apr_file_info_get(&finfo
, APR_FINFO_SIZE
, body_file
);
918 return svn_error_wrap_apr(status
,
919 _("Can't calculate the request body size"));
921 b
->body_file
= body_file
;
924 ne_set_request_body_provider(req
->ne_req
, (size_t) finfo
.size
,
925 ra_neon_body_provider
, b
);
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. */
940 spool_reader(void *userdata
,
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
);
955 parse_spool_file(svn_ra_neon__session_t
*ras
,
956 const char *spool_file_name
,
957 ne_xml_parser
*success_parser
,
960 apr_file_t
*spool_file
;
961 svn_stream_t
*spool_stream
;
962 char *buf
= apr_palloc(pool
, SVN__STREAM_CHUNK_SIZE
);
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
);
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
));
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 */
982 if (len
!= SVN__STREAM_CHUNK_SIZE
)
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.
998 svn_ra_neon__request_t
*req
;
999 ne_xml_parser
*parser
;
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
;
1008 wrapper_startelm_cb(void *baton
,
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
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
1025 svn_error_create(SVN_ERR_XML_MALFORMED
, NULL
, NULL
));
1028 return NE_XML_ABORT
;
1034 wrapper_cdata_cb(void *baton
, int state
, const char *cdata
, size_t len
)
1036 parser_wrapper_baton_t
*pwb
= baton
;
1039 SVN_RA_NEON__REQ_ERR
1041 pwb
->cdata_cb(pwb
->baton
, state
, cdata
, len
));
1044 return NE_XML_ABORT
;
1050 wrapper_endelm_cb(void *baton
,
1055 parser_wrapper_baton_t
*pwb
= baton
;
1058 SVN_RA_NEON__REQ_ERR
1060 pwb
->endelm_cb(pwb
->baton
, state
, nspace
, name
));
1063 return NE_XML_ABORT
;
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
;
1077 if (sess
->callbacks
->cancel_func
)
1078 SVN_RA_NEON__REQ_ERR
1080 (sess
->callbacks
->cancel_func
)(sess
->callback_baton
));
1085 return ne_xml_parse(pwb
->parser
, data
, len
);
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
,
1096 ne_xml_parser
*p
= xml_parser_create(req
);
1097 parser_wrapper_baton_t
*pwb
= apr_palloc(req
->pool
, sizeof(*pwb
));
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
,
1109 wrapper_endelm_cb
, pwb
);
1112 attach_ne_body_reader(req
, accpt
, wrapper_reader_cb
, pwb
);
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
;
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
1134 (ras
->callbacks
->cancel_func
)(ras
->callback_baton
));
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
,
1149 cancellation_baton_t
*b
= apr_palloc(pool
, sizeof(*b
));
1151 b
->real_cb
= real_cb
;
1152 b
->real_userdata
= real_userdata
;
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
,
1165 apr_file_t
*body_file
,
1166 void set_parser(ne_xml_parser
*parser
,
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
,
1172 apr_hash_t
*extra_headers
,
1174 svn_boolean_t spool_response
,
1177 ne_xml_parser
*success_parser
= NULL
;
1179 spool_reader_baton_t spool_reader_baton
;
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
,
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. */
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
,
1210 svn_io_file_del_on_pool_cleanup
,
1212 spool_reader_baton
.req
= req
;
1214 svn_ra_neon__add_response_body_reader(req
, ne_accept_2xx
, spool_reader
,
1215 &spool_reader_baton
);
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)
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
));
1241 svn_error_compose(req
->err
, svn_error_createf
1242 (SVN_ERR_RA_DAV_REQUEST_FAILED
, NULL
,
1243 _("Error reading spooled %s request response"),
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)"),
1257 return SVN_NO_ERROR
;
1262 svn_ra_neon__parsed_request(svn_ra_neon__session_t
*sess
,
1266 apr_file_t
*body_file
,
1267 void set_parser(ne_xml_parser
*parser
,
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
,
1273 apr_hash_t
*extra_headers
,
1275 svn_boolean_t spool_response
,
1278 /* create/prep the request */
1279 svn_ra_neon__request_t
* req
= svn_ra_neon__request_create(sess
, method
, url
,
1281 svn_error_t
*err
= parsed_request(req
,
1282 sess
, method
, url
, body
, body_file
,
1284 startelm_cb
, cdata_cb
, endelm_cb
,
1285 baton
, extra_headers
, status_code
,
1286 spool_response
, pool
);
1287 svn_ra_neon__request_destroy(req
);
1293 svn_ra_neon__simple_request(int *code
,
1294 svn_ra_neon__session_t
*ras
,
1297 apr_hash_t
*extra_headers
,
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
);
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
,
1313 okay_1
, okay_2
, pool
);
1314 svn_ra_neon__request_destroy(req
);
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");
1335 svn_ra_neon__copy(svn_ra_neon__session_t
*ras
,
1336 svn_boolean_t overwrite
,
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
);
1359 svn_ra_neon__maybe_store_auth_info(svn_ra_neon__session_t
*ras
,
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
,
1370 return SVN_NO_ERROR
;
1375 svn_ra_neon__maybe_store_auth_info_after_result(svn_error_t
*err
,
1376 svn_ra_neon__session_t
*ras
,
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
);
1386 svn_error_clear(err2
);
1396 svn_ra_neon__request_dispatch(int *code_p
,
1397 svn_ra_neon__request_t
*req
,
1398 apr_hash_t
*extra_headers
,
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
))
1416 apr_hash_this(hi
, &key
, NULL
, &val
);
1417 ne_add_request_header(req
->ne_req
,
1418 (const char *) key
, (const char *) val
);
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
;
1441 *code_p
= req
->code
;
1443 if (!req
->marshalled_error
)
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 */
1454 /* We either have a neon error, or some other error
1455 that we didn't expect. */
1456 return generate_error(req
, pool
);
1461 svn_ra_neon__request_get_location(svn_ra_neon__request_t
*request
,
1464 const char *val
= ne_get_response_header(request
->ne_req
, "Location");
1465 return val
? apr_pstrdup(pool
, val
) : NULL
;