diff options
Diffstat (limited to 'contrib/subversion/subversion/libsvn_ra_serf/multistatus.c')
-rw-r--r-- | contrib/subversion/subversion/libsvn_ra_serf/multistatus.c | 752 |
1 files changed, 752 insertions, 0 deletions
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/multistatus.c b/contrib/subversion/subversion/libsvn_ra_serf/multistatus.c new file mode 100644 index 0000000000000..5b4c8fd0c0f51 --- /dev/null +++ b/contrib/subversion/subversion/libsvn_ra_serf/multistatus.c @@ -0,0 +1,752 @@ +/* + * multistatus.c : parse multistatus (error) responses. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <assert.h> + +#include <apr.h> + +#include <serf.h> +#include <serf_bucket_types.h> + +#include "svn_private_config.h" +#include "svn_hash.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_string.h" +#include "svn_xml.h" +#include "svn_props.h" +#include "svn_dirent_uri.h" + +#include "private/svn_dep_compat.h" +#include "private/svn_fspath.h" + +#include "ra_serf.h" + +/* The current state of our XML parsing. */ +typedef enum iprops_state_e { + INITIAL = XML_STATE_INITIAL, + MS_MULTISTATUS, + + MS_RESPONSE, + MS_RESPONSE_HREF, + + MS_PROPSTAT, + MS_PROPSTAT_PROP, + MS_PROPSTAT_PROP_NAME, + MS_PROPSTAT_STATUS, + MS_PROPSTAT_RESPONSEDESCRIPTION, + MS_PROPSTAT_ERROR, + MS_PROPSTAT_ERROR_HUMANREADABLE, + + MS_RESPONSE_STATUS, + MS_RESPONSE_RESPONSEDESCRIPTION, + MS_RESPONSE_ERROR, + MS_RESPONSE_ERROR_HUMANREADABLE, + + MS_MULTISTATUS_RESPONSEDESCRIPTION, + + D_ERROR, + S_ERROR, + M_ERROR_HUMANREADABLE +} iprops_state_e; + +/* + <D:multistatus xmlns:D="DAV:"> + <D:response> + <D:href>http://something</D:href> + <!-- Possibly multiple D:href elements --> + <D:status>HTTP/1.1 500 failed</D:status> + <D:error> + <S:human-readable code="12345"> + Some Subversion error + </S:human-readable> + </D:error> + <D:responsedescription> + Human readable description + </D:responsedescription> + <D:location>http://redirected</D:location> + </D:response> + ... + </D:multistatus> + + Or for property operations: + + <D:multistatus xmlns:D="DAV:"> + <D:response> + <D:href>http://somewhere-else</D:href> + <D:propstat> + <D:propname><C:myprop /></D:propname> + <D:status>HTTP/1.1 499 failed</D:status> + <D:error> + <S:human-readable code="12345"> + Some Subversion error + </S:human-readable> + </D:error> + <D:responsedescription> + Human readable description + </D:responsedescription> + </D:propstat> + <D:status>HTTP/1.1 499 failed</D:status> + <D:error> + <S:human-readable code="12345"> + Some Subversion error + </S:human-readable> + </D:error> + <D:responsedescription> + Human readable description + </D:responsedescription> + <D:location>http://redirected</D:location> + <D:responsedescription> + Global description + <D:responsedescription> + </D:multistatus> + + Or on request failures + <D:error> + <X:some-error xmlns="QQ" /> + <D:human-readable code="12345"> + Some Subversion error + </D:human-readable> + </D:error> + */ + +#define D_ "DAV:" +#define S_ SVN_XML_NAMESPACE +#define M_ "http://apache.org/dav/xmlns" +static const svn_ra_serf__xml_transition_t multistatus_ttable[] = { + { INITIAL, D_, "multistatus", MS_MULTISTATUS, + FALSE, { NULL }, FALSE }, + + { MS_MULTISTATUS, D_, "responsedescription", MS_MULTISTATUS_RESPONSEDESCRIPTION, + TRUE, { NULL }, TRUE }, + + /* <response> */ + { MS_MULTISTATUS, D_, "response", MS_RESPONSE, + FALSE, { NULL }, TRUE }, + + { MS_RESPONSE, D_, "href", MS_RESPONSE_HREF, + TRUE, { NULL }, TRUE }, + + /* <propstat> */ + { MS_RESPONSE, D_, "propstat", MS_PROPSTAT, + FALSE, { NULL }, TRUE }, + + { MS_PROPSTAT, D_, "prop", MS_PROPSTAT_PROP, + FALSE, { NULL }, FALSE }, + + { MS_PROPSTAT_PROP, "", "*", MS_PROPSTAT_PROP_NAME, + FALSE, { NULL }, FALSE }, + + { MS_PROPSTAT, D_, "status", MS_PROPSTAT_STATUS, + TRUE, { NULL }, TRUE }, + + { MS_PROPSTAT, D_, "responsedescription", MS_PROPSTAT_RESPONSEDESCRIPTION, + TRUE, { NULL }, TRUE }, + + { MS_PROPSTAT, D_, "error", MS_PROPSTAT_ERROR, + FALSE, { NULL }, FALSE }, + + { MS_PROPSTAT_ERROR, M_, "human-readable", MS_PROPSTAT_ERROR_HUMANREADABLE, + TRUE, { "?errcode", NULL }, TRUE }, + /* </propstat> */ + + + { MS_RESPONSE, D_, "status", MS_RESPONSE_STATUS, + TRUE, { NULL }, TRUE }, + + { MS_RESPONSE, D_, "responsedescription", MS_RESPONSE_RESPONSEDESCRIPTION, + TRUE, { NULL }, TRUE }, + + { MS_RESPONSE, D_, "error", MS_RESPONSE_ERROR, + FALSE, { NULL }, TRUE }, + + { MS_RESPONSE_ERROR, M_, "human-readable", MS_RESPONSE_ERROR_HUMANREADABLE, + TRUE, { "?errcode", NULL }, TRUE }, + + /* </response> */ + + { MS_MULTISTATUS, D_, "responsedescription", MS_MULTISTATUS_RESPONSEDESCRIPTION, + TRUE, { NULL }, TRUE }, + + + { INITIAL, D_, "error", D_ERROR, + FALSE, { NULL }, TRUE }, + + { D_ERROR, S_, "error", S_ERROR, + FALSE, { NULL }, FALSE }, + + { D_ERROR, M_, "human-readable", M_ERROR_HUMANREADABLE, + TRUE, { "?errcode", NULL }, TRUE }, + + { 0 } +}; + +/* Given a string like "HTTP/1.1 500 (status)" in BUF, parse out the numeric + status code into *STATUS_CODE_OUT. Ignores leading whitespace. */ +static svn_error_t * +parse_status_line(int *status_code_out, + const char **reason, + const char *status_line, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + const char *token; + char *tok_status; + svn_stringbuf_t *temp_buf = svn_stringbuf_create(status_line, scratch_pool); + + svn_stringbuf_strip_whitespace(temp_buf); + token = apr_strtok(temp_buf->data, " \t\r\n", &tok_status); + if (token) + token = apr_strtok(NULL, " \t\r\n", &tok_status); + if (!token) + return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Malformed DAV:status '%s'"), + status_line); + err = svn_cstring_atoi(status_code_out, token); + if (err) + return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, err, + _("Malformed DAV:status '%s'"), + status_line); + + token = apr_strtok(NULL, " \t\r\n", &tok_status); + + *reason = apr_pstrdup(result_pool, token); + + return SVN_NO_ERROR; +} + + +typedef struct error_item_t +{ + const char *path; + const char *propname; + + int http_status; + const char *http_reason; + apr_status_t apr_err; + + const char *message; +} error_item_t; + +static svn_error_t * +multistatus_opened(svn_ra_serf__xml_estate_t *xes, + void *baton, + int entered_state, + const svn_ra_serf__dav_props_t *tag, + apr_pool_t *scratch_pool) +{ + /*struct svn_ra_serf__server_error_t *server_error = baton;*/ + const char *propname; + + switch (entered_state) + { + case MS_PROPSTAT_PROP_NAME: + if (strcmp(tag->xmlns, SVN_DAV_PROP_NS_SVN) == 0) + propname = apr_pstrcat(scratch_pool, SVN_PROP_PREFIX, tag->name, + SVN_VA_NULL); + else + propname = tag->name; + svn_ra_serf__xml_note(xes, MS_PROPSTAT, "propname", propname); + break; + case S_ERROR: + /* This toggles an has error boolean in libsvn_ra_neon in 1.7 */ + break; + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +multistatus_closed(svn_ra_serf__xml_estate_t *xes, + void *baton, + int leaving_state, + const svn_string_t *cdata, + apr_hash_t *attrs, + apr_pool_t *scratch_pool) +{ + struct svn_ra_serf__server_error_t *server_error = baton; + const char *errcode; + const char *status; + + switch (leaving_state) + { + case MS_RESPONSE_HREF: + { + apr_status_t result; + apr_uri_t uri; + + result = apr_uri_parse(scratch_pool, cdata->data, &uri); + if (result) + return svn_ra_serf__wrap_err(result, NULL); + svn_ra_serf__xml_note(xes, MS_RESPONSE, "path", + svn_urlpath__canonicalize(uri.path, scratch_pool)); + } + break; + case MS_RESPONSE_STATUS: + svn_ra_serf__xml_note(xes, MS_RESPONSE, "status", cdata->data); + break; + case MS_RESPONSE_ERROR_HUMANREADABLE: + svn_ra_serf__xml_note(xes, MS_RESPONSE, "human-readable", cdata->data); + errcode = svn_hash_gets(attrs, "errcode"); + if (errcode) + svn_ra_serf__xml_note(xes, MS_RESPONSE, "errcode", errcode); + break; + case MS_RESPONSE: + if ((status = svn_hash_gets(attrs, "status")) != NULL) + { + error_item_t *item; + + item = apr_pcalloc(server_error->pool, sizeof(*item)); + + item->path = apr_pstrdup(server_error->pool, + svn_hash_gets(attrs, "path")); + + SVN_ERR(parse_status_line(&item->http_status, + &item->http_reason, + status, + server_error->pool, + scratch_pool)); + + /* Do we have a mod_dav specific message? */ + item->message = svn_hash_gets(attrs, "human-readable"); + + if (item->message) + { + if ((errcode = svn_hash_gets(attrs, "errcode")) != NULL) + { + apr_int64_t val; + + SVN_ERR(svn_cstring_atoi64(&val, errcode)); + item->apr_err = (apr_status_t)val; + } + + item->message = apr_pstrdup(server_error->pool, item->message); + } + else + item->message = apr_pstrdup(server_error->pool, + svn_hash_gets(attrs, "description")); + + APR_ARRAY_PUSH(server_error->items, error_item_t *) = item; + } + break; + + + case MS_PROPSTAT_STATUS: + svn_ra_serf__xml_note(xes, MS_PROPSTAT, "status", cdata->data); + break; + case MS_PROPSTAT_ERROR_HUMANREADABLE: + svn_ra_serf__xml_note(xes, MS_PROPSTAT, "human-readable", cdata->data); + errcode = svn_hash_gets(attrs, "errcode"); + if (errcode) + svn_ra_serf__xml_note(xes, MS_PROPSTAT, "errcode", errcode); + break; + case MS_PROPSTAT_RESPONSEDESCRIPTION: + svn_ra_serf__xml_note(xes, MS_PROPSTAT, "description", + cdata->data); + break; + + case MS_PROPSTAT: + if ((status = svn_hash_gets(attrs, "status")) != NULL) + { + apr_hash_t *response_attrs; + error_item_t *item; + + response_attrs = svn_ra_serf__xml_gather_since(xes, MS_RESPONSE); + item = apr_pcalloc(server_error->pool, sizeof(*item)); + + item->path = apr_pstrdup(server_error->pool, + svn_hash_gets(response_attrs, "path")); + item->propname = apr_pstrdup(server_error->pool, + svn_hash_gets(attrs, "propname")); + + SVN_ERR(parse_status_line(&item->http_status, + &item->http_reason, + status, + server_error->pool, + scratch_pool)); + + /* Do we have a mod_dav specific message? */ + item->message = svn_hash_gets(attrs, "human-readable"); + + if (item->message) + { + if ((errcode = svn_hash_gets(attrs, "errcode")) != NULL) + { + apr_int64_t val; + + SVN_ERR(svn_cstring_atoi64(&val, errcode)); + item->apr_err = (apr_status_t)val; + } + + item->message = apr_pstrdup(server_error->pool, item->message); + } + else + item->message = apr_pstrdup(server_error->pool, + svn_hash_gets(attrs, "description")); + + + APR_ARRAY_PUSH(server_error->items, error_item_t *) = item; + } + break; + + case M_ERROR_HUMANREADABLE: + svn_ra_serf__xml_note(xes, D_ERROR, "human-readable", cdata->data); + errcode = svn_hash_gets(attrs, "errcode"); + if (errcode) + svn_ra_serf__xml_note(xes, D_ERROR, "errcode", errcode); + break; + + case D_ERROR: + { + error_item_t *item; + + item = apr_pcalloc(server_error->pool, sizeof(*item)); + + item->http_status = server_error->handler->sline.code; + + /* Do we have a mod_dav specific message? */ + item->message = svn_hash_gets(attrs, "human-readable"); + + if (item->message) + { + if ((errcode = svn_hash_gets(attrs, "errcode")) != NULL) + { + apr_int64_t val; + + SVN_ERR(svn_cstring_atoi64(&val, errcode)); + item->apr_err = (apr_status_t)val; + } + + item->message = apr_pstrdup(server_error->pool, item->message); + } + else + item->message = apr_pstrdup(server_error->pool, + svn_hash_gets(attrs, "description")); + + + APR_ARRAY_PUSH(server_error->items, error_item_t *) = item; + } + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_serf__server_error_create(svn_ra_serf__handler_t *handler, + apr_pool_t *scratch_pool) +{ + svn_ra_serf__server_error_t *server_error = handler->server_error; + svn_error_t *err = NULL; + int i; + + for (i = 0; i < server_error->items->nelts; i++) + { + const error_item_t *item; + apr_status_t status; + const char *message; + svn_error_t *new_err; + + item = APR_ARRAY_IDX(server_error->items, i, error_item_t *); + + if (!item->apr_err && item->http_status == 200) + { + continue; /* Success code */ + } + else if (!item->apr_err && item->http_status == 424 && item->propname) + { + continue; /* Failed because other PROPPATCH operations failed */ + } + + if (item->apr_err) + status = item->apr_err; + else + switch (item->http_status) + { + case 0: + continue; /* Not an error */ + case 301: + case 302: + case 303: + case 307: + case 308: + status = SVN_ERR_RA_DAV_RELOCATED; + break; + case 403: + status = SVN_ERR_RA_DAV_FORBIDDEN; + break; + case 404: + status = SVN_ERR_FS_NOT_FOUND; + break; + case 409: + status = SVN_ERR_FS_CONFLICT; + break; + case 412: + status = SVN_ERR_RA_DAV_PRECONDITION_FAILED; + break; + case 423: + status = SVN_ERR_FS_NO_LOCK_TOKEN; + break; + case 500: + status = SVN_ERR_RA_DAV_REQUEST_FAILED; + break; + case 501: + status = SVN_ERR_UNSUPPORTED_FEATURE; + break; + default: + if (err) + status = err->apr_err; /* Just use previous */ + else + status = SVN_ERR_RA_DAV_REQUEST_FAILED; + break; + } + + if (item->message && *item->message) + { + svn_stringbuf_t *sb = svn_stringbuf_create(item->message, + scratch_pool); + + svn_stringbuf_strip_whitespace(sb); + message = sb->data; + } + else if (item->propname) + { + message = apr_psprintf(scratch_pool, + _("Property operation on '%s' failed"), + item->propname); + } + else + { + /* Yuck: Older servers sometimes assume that we get convertable + apr error codes, while mod_dav_svn just produces a blank + text error, because err->message is NULL. */ + serf_status_line sline; + svn_error_t *tmp_err; + + memset(&sline, 0, sizeof(sline)); + sline.code = item->http_status; + sline.reason = item->http_reason; + + tmp_err = svn_ra_serf__error_on_status(sline, item->path, NULL); + + message = (tmp_err && tmp_err->message) + ? apr_pstrdup(scratch_pool, tmp_err->message) + : _("<blank error>"); + svn_error_clear(tmp_err); + } + + SVN_ERR_ASSERT(status > 0); + new_err = svn_error_create(status, NULL, message); + + if (item->propname) + new_err = svn_error_createf(new_err->apr_err, new_err, + _("While handling the '%s' property on '%s':"), + item->propname, item->path); + else if (item->path) + new_err = svn_error_createf(new_err->apr_err, new_err, + _("While handling the '%s' path:"), + item->path); + + err = svn_error_compose_create( + err, + new_err); + } + + /* Theoretically a 207 status can have a 'global' description without a + global STATUS that summarizes the final result of property/href + operations. + + We should wrap that around the existing errors if there is one. + + But currently I don't see how mod_dav ever sets that value */ + + if (!err) + { + /* We should fail.... but why... Who installed us? */ + err = svn_error_trace(svn_ra_serf__unexpected_status(handler)); + } + + return err; +} + + +svn_error_t * +svn_ra_serf__setup_error_parsing(svn_ra_serf__server_error_t **server_err, + svn_ra_serf__handler_t *handler, + svn_boolean_t expect_207_only, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_ra_serf__server_error_t *ms_baton; + svn_ra_serf__handler_t *tmp_handler; + + int *expected_status = apr_pcalloc(result_pool, + 2 * sizeof(expected_status[0])); + + expected_status[0] = handler->sline.code; + + ms_baton = apr_pcalloc(result_pool, sizeof(*ms_baton)); + ms_baton->pool = result_pool; + + ms_baton->items = apr_array_make(result_pool, 4, sizeof(error_item_t *)); + ms_baton->handler = handler; + + ms_baton->xmlctx = svn_ra_serf__xml_context_create(multistatus_ttable, + multistatus_opened, + multistatus_closed, + NULL, + ms_baton, + ms_baton->pool); + + tmp_handler = svn_ra_serf__create_expat_handler(handler->session, + ms_baton->xmlctx, + expected_status, + result_pool); + + /* Ugly way to obtain expat_handler() */ + tmp_handler->sline = handler->sline; + ms_baton->response_handler = tmp_handler->response_handler; + ms_baton->response_baton = tmp_handler->response_baton; + + *server_err = ms_baton; + return SVN_NO_ERROR; +} + + + +/* Implements svn_ra_serf__response_handler_t */ +svn_error_t * +svn_ra_serf__handle_multistatus_only(serf_request_t *request, + serf_bucket_t *response, + void *baton, + apr_pool_t *scratch_pool) +{ + svn_ra_serf__handler_t *handler = baton; + + /* This function is just like expect_empty_body() except for the + XML parsing callbacks. We are looking for very limited pieces of + the multistatus response. */ + + /* We should see this just once, in order to initialize SERVER_ERROR. + At that point, the core error processing will take over. If we choose + not to parse an error, then we'll never return here (because we + change the response handler). */ + SVN_ERR_ASSERT(handler->server_error == NULL); + + { + serf_bucket_t *hdrs; + const char *val; + + hdrs = serf_bucket_response_get_headers(response); + val = serf_bucket_headers_get(hdrs, "Content-Type"); + if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) + { + svn_ra_serf__server_error_t *server_err; + + SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, + handler, + TRUE, + handler->handler_pool, + handler->handler_pool)); + + handler->server_error = server_err; + } + else + { + /* The body was not text/xml, so we don't know what to do with it. + Toss anything that arrives. */ + handler->discard_body = TRUE; + } + } + + /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it + to call the response handler again. That will start up the XML parsing, + or it will be dropped on the floor (per the decision above). */ + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_serf__handle_server_error(svn_ra_serf__server_error_t *server_error, + svn_ra_serf__handler_t *handler, + serf_request_t *request, + serf_bucket_t *response, + apr_status_t *serf_status, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + + err = server_error->response_handler(request, response, + server_error->response_baton, + scratch_pool); + /* If we do not receive an error or it is a non-transient error, return + immediately. + + APR_EOF will be returned when parsing is complete. + + APR_EAGAIN & WAIT_CONN may be intermittently returned as we proceed through + parsing and the network has no more data right now. If we receive that, + clear the error and return - allowing serf to wait for more data. + */ + if (!err || SERF_BUCKET_READ_ERROR(err->apr_err)) + return svn_error_trace(err); + + if (!APR_STATUS_IS_EOF(err->apr_err)) + { + *serf_status = err->apr_err; + svn_error_clear(err); + return SVN_NO_ERROR; + } + + /* Clear the EOF. We don't need it as subversion error. */ + svn_error_clear(err); + *serf_status = APR_EOF; + + /* On PROPPATCH we always get status 207, which may or may not imply an + error status, but let's keep it generic and just do the check for + any multistatus */ + if (handler->sline.code == 207 /* MULTISTATUS */) + { + svn_boolean_t have_error = FALSE; + int i; + + for (i = 0; i < server_error->items->nelts; i++) + { + const error_item_t *item; + item = APR_ARRAY_IDX(server_error->items, i, error_item_t *); + + if (!item->apr_err && item->http_status == 200) + { + continue; /* Success code */ + } + + have_error = TRUE; + break; + } + + if (! have_error) + handler->server_error = NULL; /* We didn't have a server error */ + } + + return SVN_NO_ERROR; +} |