summaryrefslogtreecommitdiff
path: root/subversion/libsvn_ra_serf
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@baserock.org>2015-03-18 13:33:26 +0000
committer <>2015-07-08 14:41:01 +0000
commitbb0ef45f7c46b0ae221b26265ef98a768c33f820 (patch)
tree98bae10dde41c746c51ae97ec4f879e330415aa7 /subversion/libsvn_ra_serf
parent239dfafe71711b2f4c43d7b90a1228d7bdc5195e (diff)
downloadsubversion-tarball-bb0ef45f7c46b0ae221b26265ef98a768c33f820.tar.gz
Imported from /home/lorry/working-area/delta_subversion-tarball/subversion-1.8.13.tar.gz.subversion-1.8.13
Diffstat (limited to 'subversion/libsvn_ra_serf')
-rw-r--r--subversion/libsvn_ra_serf/blame.c450
-rw-r--r--subversion/libsvn_ra_serf/blncache.c4
-rw-r--r--subversion/libsvn_ra_serf/commit.c1216
-rw-r--r--subversion/libsvn_ra_serf/get_deleted_rev.c143
-rw-r--r--subversion/libsvn_ra_serf/getdate.c156
-rw-r--r--subversion/libsvn_ra_serf/getlocations.c177
-rw-r--r--subversion/libsvn_ra_serf/getlocationsegments.c164
-rw-r--r--subversion/libsvn_ra_serf/getlocks.c338
-rw-r--r--subversion/libsvn_ra_serf/inherited_props.c285
-rw-r--r--subversion/libsvn_ra_serf/locks.c633
-rw-r--r--subversion/libsvn_ra_serf/log.c599
-rw-r--r--subversion/libsvn_ra_serf/merge.c483
-rw-r--r--subversion/libsvn_ra_serf/mergeinfo.c212
-rw-r--r--subversion/libsvn_ra_serf/options.c611
-rw-r--r--subversion/libsvn_ra_serf/property.c979
-rw-r--r--subversion/libsvn_ra_serf/ra_serf.h924
-rw-r--r--subversion/libsvn_ra_serf/replay.c243
-rw-r--r--subversion/libsvn_ra_serf/sb_bucket.c185
-rw-r--r--subversion/libsvn_ra_serf/serf.c448
-rw-r--r--subversion/libsvn_ra_serf/update.c1769
-rw-r--r--subversion/libsvn_ra_serf/util.c2259
-rw-r--r--subversion/libsvn_ra_serf/util_error.c100
-rw-r--r--subversion/libsvn_ra_serf/xml.c587
23 files changed, 7542 insertions, 5423 deletions
diff --git a/subversion/libsvn_ra_serf/blame.c b/subversion/libsvn_ra_serf/blame.c
index c166ee7..b6f136a 100644
--- a/subversion/libsvn_ra_serf/blame.c
+++ b/subversion/libsvn_ra_serf/blame.c
@@ -22,11 +22,9 @@
*/
#include <apr_uri.h>
-
-#include <expat.h>
-
#include <serf.h>
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_ra.h"
#include "svn_dav.h"
@@ -39,6 +37,8 @@
#include "svn_private_config.h"
+#include "private/svn_string_private.h"
+
#include "ra_serf.h"
#include "../libsvn_ra/ra_loader.h"
@@ -47,7 +47,7 @@
* This enum represents the current state of our XML parsing for a REPORT.
*/
typedef enum blame_state_e {
- NONE = 0,
+ INITIAL = 0,
FILE_REVS_REPORT,
FILE_REV,
REV_PROP,
@@ -57,43 +57,6 @@ typedef enum blame_state_e {
TXDELTA
} blame_state_e;
-typedef struct blame_info_t {
- /* Current pool. */
- apr_pool_t *pool;
-
- /* our suspicious file */
- const char *path;
-
- /* the intended suspect */
- svn_revnum_t rev;
-
- /* Hashtable of revision properties */
- apr_hash_t *rev_props;
-
- /* Added and removed properties (svn_prop_t*'s) */
- apr_array_header_t *prop_diffs;
-
- /* txdelta */
- svn_txdelta_window_handler_t txdelta;
- void *txdelta_baton;
-
- /* returned txdelta stream */
- svn_stream_t *stream;
-
- /* Is this property base64-encoded? */
- svn_boolean_t prop_base64;
-
- /* The currently collected value as we build it up */
- const char *prop_name;
- const char *prop_attr;
- apr_size_t prop_attr_len;
-
- svn_string_t *prop_string;
-
- /* Merged revision flag */
- svn_boolean_t merged_revision;
-
-} blame_info_t;
typedef struct blame_context_t {
/* pool passed to get_file_revs */
@@ -105,281 +68,211 @@ typedef struct blame_context_t {
svn_revnum_t end;
svn_boolean_t include_merged_revisions;
- /* are we done? */
- svn_boolean_t done;
-
/* blame handler and baton */
svn_file_rev_handler_t file_rev;
void *file_rev_baton;
+
+ /* As we parse each FILE_REV, we collect data in these variables:
+ property changes and new content. STREAM is valid when we're
+ in the TXDELTA state, processing the incoming cdata. */
+ apr_hash_t *rev_props;
+ apr_array_header_t *prop_diffs;
+ apr_pool_t *state_pool; /* put property stuff in here */
+
+ svn_stream_t *stream;
+
} blame_context_t;
-
-static blame_info_t *
-push_state(svn_ra_serf__xml_parser_t *parser,
- blame_context_t *blame_ctx,
- blame_state_e state)
-{
- svn_ra_serf__xml_push_state(parser, state);
- if (state == FILE_REV)
- {
- blame_info_t *info;
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t blame_ttable[] = {
+ { INITIAL, S_, "file-revs-report", FILE_REVS_REPORT,
+ FALSE, { NULL }, FALSE },
- info = apr_palloc(parser->state->pool, sizeof(*info));
+ { FILE_REVS_REPORT, S_, "file-rev", FILE_REV,
+ FALSE, { "path", "rev", NULL }, TRUE },
- info->pool = parser->state->pool;
+ { FILE_REV, S_, "rev-prop", REV_PROP,
+ TRUE, { "name", "?encoding", NULL }, TRUE },
- info->rev = SVN_INVALID_REVNUM;
- info->path = NULL;
+ { FILE_REV, S_, "set-prop", SET_PROP,
+ TRUE, { "name", "?encoding", NULL }, TRUE },
- info->rev_props = apr_hash_make(info->pool);
- info->prop_diffs = apr_array_make(info->pool, 0, sizeof(svn_prop_t));
+ { FILE_REV, S_, "remove-prop", REMOVE_PROP,
+ FALSE, { "name", NULL }, TRUE },
- info->stream = NULL;
- info->merged_revision = FALSE;
+ { FILE_REV, S_, "merged-revision", MERGED_REVISION,
+ FALSE, { NULL }, TRUE },
- parser->state->private = info;
- }
+ { FILE_REV, S_, "txdelta", TXDELTA,
+ FALSE, { NULL }, TRUE },
+
+ { 0 }
+};
- return parser->state->private;
-}
-static const svn_string_t *
-create_propval(blame_info_t *info)
+/* Conforms to svn_ra_serf__xml_opened_t */
+static svn_error_t *
+blame_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)
{
- const svn_string_t *s;
+ blame_context_t *blame_ctx = baton;
- if (!info->prop_attr)
+ if (entered_state == FILE_REV)
{
- return svn_string_create("", info->pool);
+ apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
+
+ /* Child elements will store properties in these structures. */
+ blame_ctx->rev_props = apr_hash_make(state_pool);
+ blame_ctx->prop_diffs = apr_array_make(state_pool,
+ 5, sizeof(svn_prop_t));
+ blame_ctx->state_pool = state_pool;
+
+ /* Clear this, so we can detect the absence of a TXDELTA. */
+ blame_ctx->stream = NULL;
}
- else
+ else if (entered_state == TXDELTA)
{
- info->prop_attr = apr_pmemdup(info->pool, info->prop_attr,
- info->prop_attr_len + 1);
+ apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
+ apr_hash_t *gathered = svn_ra_serf__xml_gather_since(xes, FILE_REV);
+ const char *path;
+ const char *rev;
+ const char *merged_revision;
+ svn_txdelta_window_handler_t txdelta;
+ void *txdelta_baton;
+
+ path = svn_hash_gets(gathered, "path");
+ rev = svn_hash_gets(gathered, "rev");
+ merged_revision = svn_hash_gets(gathered, "merged-revision");
+
+ SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
+ path, SVN_STR_TO_REV(rev),
+ blame_ctx->rev_props,
+ merged_revision != NULL,
+ &txdelta, &txdelta_baton,
+ blame_ctx->prop_diffs,
+ state_pool));
+
+ blame_ctx->stream = svn_base64_decode(svn_txdelta_parse_svndiff(
+ txdelta, txdelta_baton,
+ TRUE /* error_on_early_close */,
+ state_pool),
+ state_pool);
}
- s = svn_string_ncreate(info->prop_attr, info->prop_attr_len, info->pool);
- if (info->prop_base64)
- {
- s = svn_base64_decode_string(s, info->pool);
- }
- return s;
+ return SVN_NO_ERROR;
}
+
+/* Conforms to svn_ra_serf__xml_closed_t */
static svn_error_t *
-start_blame(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name,
- const char **attrs)
+blame_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)
{
- blame_context_t *blame_ctx = userData;
- blame_state_e state;
+ blame_context_t *blame_ctx = baton;
- state = parser->state->current_state;
+ if (leaving_state == FILE_REV)
+ {
+ /* Note that we test STREAM, but any pointer is currently invalid.
+ It was closed when left the TXDELTA state. */
+ if (blame_ctx->stream == NULL)
+ {
+ const char *path;
+ const char *rev;
+
+ path = svn_hash_gets(attrs, "path");
+ rev = svn_hash_gets(attrs, "rev");
- if (state == NONE && strcmp(name.name, "file-revs-report") == 0)
+ /* Send a "no content" notification. */
+ SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
+ path, SVN_STR_TO_REV(rev),
+ blame_ctx->rev_props,
+ FALSE /* result_of_merge */,
+ NULL, NULL, /* txdelta / baton */
+ blame_ctx->prop_diffs,
+ scratch_pool));
+ }
+ }
+ else if (leaving_state == MERGED_REVISION)
{
- push_state(parser, blame_ctx, FILE_REVS_REPORT);
+ svn_ra_serf__xml_note(xes, FILE_REV, "merged-revision", "*");
}
- else if (state == FILE_REVS_REPORT &&
- strcmp(name.name, "file-rev") == 0)
+ else if (leaving_state == TXDELTA)
{
- blame_info_t *info;
-
- info = push_state(parser, blame_ctx, FILE_REV);
-
- info->path = apr_pstrdup(info->pool,
- svn_xml_get_attr_value("path", attrs));
- info->rev = SVN_STR_TO_REV(svn_xml_get_attr_value("rev", attrs));
+ SVN_ERR(svn_stream_close(blame_ctx->stream));
}
- else if (state == FILE_REV)
+ else
{
- blame_info_t *info;
- const char *enc;
+ const char *name;
+ const svn_string_t *value;
- info = parser->state->private;
+ SVN_ERR_ASSERT(leaving_state == REV_PROP
+ || leaving_state == SET_PROP
+ || leaving_state == REMOVE_PROP);
- if (strcmp(name.name, "rev-prop") == 0)
- {
- push_state(parser, blame_ctx, REV_PROP);
- }
- else if (strcmp(name.name, "set-prop") == 0)
- {
- push_state(parser, blame_ctx, SET_PROP);
- }
- if (strcmp(name.name, "remove-prop") == 0)
- {
- push_state(parser, blame_ctx, REMOVE_PROP);
- }
- else if (strcmp(name.name, "merged-revision") == 0)
+ name = apr_pstrdup(blame_ctx->state_pool,
+ svn_hash_gets(attrs, "name"));
+
+ if (leaving_state == REMOVE_PROP)
{
- push_state(parser, blame_ctx, MERGED_REVISION);
+ value = NULL;
}
- else if (strcmp(name.name, "txdelta") == 0)
+ else
{
- SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
- info->path, info->rev,
- info->rev_props, info->merged_revision,
- &info->txdelta, &info->txdelta_baton,
- info->prop_diffs, info->pool));
+ const char *encoding = svn_hash_gets(attrs, "encoding");
- info->stream = svn_base64_decode
- (svn_txdelta_parse_svndiff(info->txdelta, info->txdelta_baton,
- TRUE, info->pool), info->pool);
-
- push_state(parser, blame_ctx, TXDELTA);
- }
-
- state = parser->state->current_state;
-
- switch (state)
- {
- case REV_PROP:
- case SET_PROP:
- case REMOVE_PROP:
- info->prop_name = apr_pstrdup(info->pool,
- svn_xml_get_attr_value("name", attrs));
- info->prop_attr = NULL;
- info->prop_attr_len = 0;
-
- enc = svn_xml_get_attr_value("encoding", attrs);
- if (enc && strcmp(enc, "base64") == 0)
- {
- info->prop_base64 = TRUE;
- }
+ if (encoding && strcmp(encoding, "base64") == 0)
+ value = svn_base64_decode_string(cdata, blame_ctx->state_pool);
else
- {
- info->prop_base64 = FALSE;
- }
- break;
- case MERGED_REVISION:
- info->merged_revision = TRUE;
- break;
- default:
- break;
+ value = svn_string_dup(cdata, blame_ctx->state_pool);
}
- }
- return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-end_blame(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name)
-{
- blame_context_t *blame_ctx = userData;
- blame_state_e state;
- blame_info_t *info;
-
- state = parser->state->current_state;
- info = parser->state->private;
-
- if (state == NONE)
- {
- return SVN_NO_ERROR;
- }
-
- if (state == FILE_REVS_REPORT &&
- strcmp(name.name, "file-revs-report") == 0)
- {
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == FILE_REV &&
- strcmp(name.name, "file-rev") == 0)
- {
- /* no file changes. */
- if (!info->stream)
+ if (leaving_state == REV_PROP)
{
- SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
- info->path, info->rev,
- info->rev_props, FALSE,
- NULL, NULL,
- info->prop_diffs, info->pool));
+ svn_hash_sets(blame_ctx->rev_props, name, value);
}
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == REV_PROP &&
- strcmp(name.name, "rev-prop") == 0)
- {
- apr_hash_set(info->rev_props,
- info->prop_name, APR_HASH_KEY_STRING,
- create_propval(info));
-
- svn_ra_serf__xml_pop_state(parser);
- }
- else if ((state == SET_PROP &&
- strcmp(name.name, "set-prop") == 0) ||
- (state == REMOVE_PROP &&
- strcmp(name.name, "remove-prop") == 0))
- {
- svn_prop_t *prop = apr_array_push(info->prop_diffs);
- prop->name = info->prop_name;
- prop->value = create_propval(info);
-
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == MERGED_REVISION &&
- strcmp(name.name, "merged-revision") == 0)
- {
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == TXDELTA &&
- strcmp(name.name, "txdelta") == 0)
- {
- SVN_ERR(svn_stream_close(info->stream));
+ else
+ {
+ svn_prop_t *prop = apr_array_push(blame_ctx->prop_diffs);
- svn_ra_serf__xml_pop_state(parser);
+ prop->name = name;
+ prop->value = value;
+ }
}
return SVN_NO_ERROR;
}
+
+/* Conforms to svn_ra_serf__xml_cdata_t */
static svn_error_t *
-cdata_blame(svn_ra_serf__xml_parser_t *parser,
- void *userData,
+blame_cdata(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int current_state,
const char *data,
- apr_size_t len)
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
{
- blame_context_t *blame_ctx = userData;
- blame_state_e state;
- blame_info_t *info;
-
- UNUSED_CTX(blame_ctx);
-
- state = parser->state->current_state;
- info = parser->state->private;
-
- if (state == NONE)
- {
- return SVN_NO_ERROR;
- }
+ blame_context_t *blame_ctx = baton;
- switch (state)
+ if (current_state == TXDELTA)
{
- case REV_PROP:
- case SET_PROP:
- svn_ra_serf__expand_string(&info->prop_attr, &info->prop_attr_len,
- data, len, parser->state->pool);
- break;
- case TXDELTA:
- if (info->stream)
- {
- apr_size_t ret_len;
-
- ret_len = len;
-
- SVN_ERR(svn_stream_write(info->stream, data, &ret_len));
- }
- break;
- default:
- break;
+ SVN_ERR(svn_stream_write(blame_ctx->stream, data, &len));
+ /* Ignore the returned LEN value. */
}
return SVN_NO_ERROR;
}
+
/* Implements svn_ra_serf__request_body_delegate_t */
static svn_error_t *
create_file_revs_body(serf_bucket_t **body_bkt,
@@ -436,9 +329,8 @@ svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session,
blame_context_t *blame_ctx;
svn_ra_serf__session_t *session = ra_session->priv;
svn_ra_serf__handler_t *handler;
- svn_ra_serf__xml_parser_t *parser_ctx;
- const char *relative_url, *basecoll_url, *req_url;
- int status_code;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url;
svn_error_t *err;
blame_ctx = apr_pcalloc(pool, sizeof(*blame_ctx));
@@ -449,14 +341,19 @@ svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session,
blame_ctx->start = start;
blame_ctx->end = end;
blame_ctx->include_merged_revisions = include_merged_revisions;
- blame_ctx->done = FALSE;
- SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url, session,
- NULL, session->session_url.path,
- end, NULL, pool));
- req_url = svn_path_url_add_component2(basecoll_url, relative_url, pool);
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, end,
+ pool, pool));
- handler = apr_pcalloc(pool, sizeof(*handler));
+ xmlctx = svn_ra_serf__xml_context_create(blame_ttable,
+ blame_opened,
+ blame_closed,
+ blame_cdata,
+ blame_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
handler->method = "REPORT";
handler->path = req_url;
@@ -466,27 +363,12 @@ svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session,
handler->conn = session->conns[0];
handler->session = session;
- parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
-
- parser_ctx->pool = pool;
- parser_ctx->user_data = blame_ctx;
- parser_ctx->start = start_blame;
- parser_ctx->end = end_blame;
- parser_ctx->cdata = cdata_blame;
- parser_ctx->done = &blame_ctx->done;
- parser_ctx->status_code = &status_code;
-
- handler->response_handler = svn_ra_serf__handle_xml_parser;
- handler->response_baton = parser_ctx;
-
- svn_ra_serf__request_create(handler);
-
- err = svn_ra_serf__context_run_wait(&blame_ctx->done, session, pool);
+ err = svn_ra_serf__context_run_one(handler, pool);
err = svn_error_compose_create(
- svn_ra_serf__error_on_status(status_code,
+ svn_ra_serf__error_on_status(handler->sline,
handler->path,
- parser_ctx->location),
+ handler->location),
err);
return svn_error_trace(err);
diff --git a/subversion/libsvn_ra_serf/blncache.c b/subversion/libsvn_ra_serf/blncache.c
index fe39284..d6abcdf 100644
--- a/subversion/libsvn_ra_serf/blncache.c
+++ b/subversion/libsvn_ra_serf/blncache.c
@@ -23,6 +23,7 @@
#include <apr_pools.h>
+#include "svn_hash.h"
#include "svn_dirent_uri.h"
#include "svn_types.h"
#include "svn_pools.h"
@@ -161,8 +162,7 @@ svn_ra_serf__blncache_get_baseline_info(const char **bc_url_p,
const char *baseline_url,
apr_pool_t *pool)
{
- baseline_info_t *info = apr_hash_get(blncache->baseline_info, baseline_url,
- APR_HASH_KEY_STRING);
+ baseline_info_t *info = svn_hash_gets(blncache->baseline_info, baseline_url);
if (info)
{
*bc_url_p = apr_pstrdup(pool, info->bc_url);
diff --git a/subversion/libsvn_ra_serf/commit.c b/subversion/libsvn_ra_serf/commit.c
index 25aefb3..9d48d41 100644
--- a/subversion/libsvn_ra_serf/commit.c
+++ b/subversion/libsvn_ra_serf/commit.c
@@ -22,11 +22,9 @@
*/
#include <apr_uri.h>
-
-#include <expat.h>
-
#include <serf.h>
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_ra.h"
#include "svn_dav.h"
@@ -41,23 +39,11 @@
#include "svn_private_config.h"
#include "private/svn_dep_compat.h"
#include "private/svn_fspath.h"
+#include "private/svn_skel.h"
#include "ra_serf.h"
#include "../libsvn_ra/ra_loader.h"
-
-/* Structure associated with a CHECKOUT request. */
-typedef struct checkout_context_t {
-
- apr_pool_t *pool;
-
- const char *activity_url;
- const char *checkout_url;
- const char *resource_url;
-
- svn_ra_serf__simple_request_context_t progress;
-
-} checkout_context_t;
/* Baton passed back with the commit editor. */
typedef struct commit_context_t {
@@ -82,7 +68,7 @@ typedef struct commit_context_t {
/* HTTP v1 stuff (only valid when 'txn_url' is NULL) */
const char *activity_url; /* activity base URL... */
- checkout_context_t *baseline; /* checkout for the baseline */
+ const char *baseline_url; /* the working-baseline resource */
const char *checked_in_url; /* checked-in root to base CHECKOUTs from */
const char *vcc_url; /* vcc url */
@@ -110,19 +96,14 @@ typedef struct proppatch_context_t {
/* In HTTP v2, this is the file/directory version we think we're changing. */
svn_revnum_t base_revision;
- svn_ra_serf__simple_request_context_t progress;
} proppatch_context_t;
typedef struct delete_context_t {
- const char *path;
+ const char *relpath;
svn_revnum_t revision;
- const char *lock_token;
- apr_hash_t *lock_token_hash;
- svn_boolean_t keep_locks;
-
- svn_ra_serf__simple_request_context_t progress;
+ commit_context_t *commit;
} delete_context_t;
/* Represents a directory. */
@@ -162,10 +143,9 @@ typedef struct dir_context_t {
apr_hash_t *changed_props;
apr_hash_t *removed_props;
- /* The checked out context for this directory. May be NULL; if so
+ /* The checked-out working resource for this directory. May be NULL; if so
call checkout_dir() first. */
- checkout_context_t *checkout;
-
+ const char *working_url;
} dir_context_t;
/* Represents a file to be committed. */
@@ -184,8 +164,8 @@ typedef struct file_context_t {
const char *relpath;
const char *name;
- /* The checked out context for this file. */
- checkout_context_t *checkout;
+ /* The checked-out working resource for this file. */
+ const char *working_url;
/* The base revision of the file. */
svn_revnum_t base_revision;
@@ -219,24 +199,29 @@ typedef struct file_context_t {
/* Setup routines and handlers for various requests we'll invoke. */
static svn_error_t *
-return_response_err(svn_ra_serf__handler_t *handler,
- svn_ra_serf__simple_request_context_t *ctx)
+return_response_err(svn_ra_serf__handler_t *handler)
{
svn_error_t *err;
+ /* We should have captured SLINE and LOCATION in the HANDLER. */
+ SVN_ERR_ASSERT(handler->handler_pool != NULL);
+
/* Ye Olde Fallback Error */
err = svn_error_compose_create(
- ctx->server_error.error,
+ handler->server_error != NULL
+ ? handler->server_error->error
+ : SVN_NO_ERROR,
svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
_("%s of '%s': %d %s"),
handler->method, handler->path,
- ctx->status, ctx->reason));
+ handler->sline.code, handler->sline.reason));
/* Try to return one of the standard errors for 301, 404, etc.,
then look for an error embedded in the response. */
- return svn_error_compose_create(svn_ra_serf__error_on_status(ctx->status,
- handler->path,
- ctx->location),
+ return svn_error_compose_create(svn_ra_serf__error_on_status(
+ handler->sline,
+ handler->path,
+ handler->location),
err);
}
@@ -247,7 +232,7 @@ create_checkout_body(serf_bucket_t **bkt,
serf_bucket_alloc_t *alloc,
apr_pool_t *pool)
{
- checkout_context_t *ctx = baton;
+ const char *activity_url = baton;
serf_bucket_t *body_bkt;
body_bkt = serf_bucket_aggregate_create(alloc);
@@ -259,8 +244,10 @@ create_checkout_body(serf_bucket_t **bkt,
svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:activity-set", NULL);
svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", NULL);
+ SVN_ERR_ASSERT(activity_url != NULL);
svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc,
- ctx->activity_url, strlen(ctx->activity_url));
+ activity_url,
+ strlen(activity_url));
svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href");
svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:activity-set");
@@ -271,137 +258,185 @@ create_checkout_body(serf_bucket_t **bkt,
return SVN_NO_ERROR;
}
-/* Implements svn_ra_serf__response_handler_t */
+
+/* Using the HTTPv1 protocol, perform a CHECKOUT of NODE_URL within the
+ given COMMIT_CTX. The resulting working resource will be returned in
+ *WORKING_URL, allocated from RESULT_POOL. All temporary allocations
+ are performed in SCRATCH_POOL.
+
+ ### are these URLs actually repos relpath values? or fspath? or maybe
+ ### the abspath portion of the full URL.
+
+ This function operates synchronously.
+
+ Strictly speaking, we could perform "all" of the CHECKOUT requests
+ when the commit starts, and only block when we need a specific
+ answer. Or, at a minimum, send off these individual requests async
+ and block when we need the answer (eg PUT or PROPPATCH).
+
+ However: the investment to speed this up is not worthwhile, given
+ that CHECKOUT (and the related round trip) is completely obviated
+ in HTTPv2.
+*/
static svn_error_t *
-handle_checkout(serf_request_t *request,
- serf_bucket_t *response,
- void *handler_baton,
- apr_pool_t *pool)
+checkout_node(const char **working_url,
+ const commit_context_t *commit_ctx,
+ const char *node_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- checkout_context_t *ctx = handler_baton;
+ svn_ra_serf__handler_t handler = { 0 };
+ apr_status_t status;
+ apr_uri_t uri;
- svn_error_t *err = svn_ra_serf__handle_status_only(request, response,
- &ctx->progress, pool);
+ /* HANDLER_POOL is the scratch pool since we don't need to remember
+ anything from the handler. We just want the working resource. */
+ handler.handler_pool = scratch_pool;
+ handler.session = commit_ctx->session;
+ handler.conn = commit_ctx->conn;
- /* These handler functions are supposed to return an APR_EOF status
- wrapped in a svn_error_t to indicate to serf that the response was
- completely read. While we have to return this status code to our
- caller, we should treat it as the normal case for now. */
- if (err && ! APR_STATUS_IS_EOF(err->apr_err))
- return err;
+ handler.body_delegate = create_checkout_body;
+ handler.body_delegate_baton = (/* const */ void *)commit_ctx->activity_url;
+ handler.body_type = "text/xml";
- /* Get the resulting location. */
- if (ctx->progress.done && ctx->progress.status == 201)
- {
- serf_bucket_t *hdrs;
- apr_uri_t uri;
- const char *location;
- apr_status_t status;
+ handler.response_handler = svn_ra_serf__expect_empty_body;
+ handler.response_baton = &handler;
- hdrs = serf_bucket_response_get_headers(response);
- location = serf_bucket_headers_get(hdrs, "Location");
- if (!location)
- return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
- _("No Location header received"));
+ handler.method = "CHECKOUT";
+ handler.path = node_url;
- status = apr_uri_parse(pool, location, &uri);
+ SVN_ERR(svn_ra_serf__context_run_one(&handler, scratch_pool));
- if (status)
- err = svn_error_compose_create(svn_error_wrap_apr(status, NULL), err);
+ if (handler.sline.code != 201)
+ return svn_error_trace(return_response_err(&handler));
+
+ if (handler.location == NULL)
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("No Location header received"));
+
+ /* We only want the path portion of the Location header.
+ (code.google.com sometimes returns an 'http:' scheme for an
+ 'https:' transaction ... we'll work around that by stripping the
+ scheme, host, and port here and re-adding the correct ones
+ later. */
+ status = apr_uri_parse(scratch_pool, handler.location, &uri);
+ if (status)
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Error parsing Location header value"));
+
+ *working_url = svn_urlpath__canonicalize(uri.path, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This is a wrapper around checkout_node() (which see for
+ documentation) which simply retries the CHECKOUT request when it
+ fails due to an SVN_ERR_APMOD_BAD_BASELINE error return from the
+ server.
- ctx->resource_url = svn_urlpath__canonicalize(uri.path, ctx->pool);
+ See http://subversion.tigris.org/issues/show_bug.cgi?id=4127 for
+ details.
+*/
+static svn_error_t *
+retry_checkout_node(const char **working_url,
+ const commit_context_t *commit_ctx,
+ const char *node_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ int retry_count = 5; /* Magic, arbitrary number. */
+
+ do
+ {
+ svn_error_clear(err);
+
+ err = checkout_node(working_url, commit_ctx, node_url,
+ result_pool, scratch_pool);
+
+ /* There's a small chance of a race condition here if Apache is
+ experiencing heavy commit concurrency or if the network has
+ long latency. It's possible that the value of HEAD changed
+ between the time we fetched the latest baseline and the time
+ we try to CHECKOUT that baseline. If that happens, Apache
+ will throw us a BAD_BASELINE error (deltaV says you can only
+ checkout the latest baseline). We just ignore that specific
+ error and retry a few times, asking for the latest baseline
+ again. */
+ if (err && (err->apr_err != SVN_ERR_APMOD_BAD_BASELINE))
+ return err;
}
+ while (err && retry_count--);
return err;
}
+
static svn_error_t *
-checkout_dir(dir_context_t *dir)
+checkout_dir(dir_context_t *dir,
+ apr_pool_t *scratch_pool)
{
- checkout_context_t *checkout_ctx;
- svn_ra_serf__handler_t *handler;
svn_error_t *err;
dir_context_t *p_dir = dir;
+ const char *checkout_url;
+ const char **working;
- if (dir->checkout)
+ if (dir->working_url)
{
return SVN_NO_ERROR;
}
- /* Is this directory or one of our parent dirs newly added?
+ /* Is this directory or one of our parent dirs newly added?
* If so, we're already implicitly checked out. */
while (p_dir)
{
if (p_dir->added)
{
- /* Implicitly checkout this dir now. */
- dir->checkout = apr_pcalloc(dir->pool, sizeof(*dir->checkout));
- dir->checkout->pool = dir->pool;
- dir->checkout->progress.pool = dir->pool;
- dir->checkout->activity_url = dir->commit->activity_url;
- dir->checkout->resource_url =
- svn_path_url_add_component2(dir->parent_dir->checkout->resource_url,
- dir->name, dir->pool);
+ /* Calculate the working_url by skipping the shared ancestor bewteen
+ * the parent->relpath and dir->relpath. This is safe since an
+ * add is guaranteed to have a parent that is checked out. */
+ dir_context_t *parent = p_dir->parent_dir;
+ const char *relpath = svn_relpath_skip_ancestor(parent->relpath,
+ dir->relpath);
+ /* Implicitly checkout this dir now. */
+ SVN_ERR_ASSERT(parent->working_url);
+ dir->working_url = svn_path_url_add_component2(
+ parent->working_url,
+ relpath, dir->pool);
return SVN_NO_ERROR;
}
p_dir = p_dir->parent_dir;
}
- /* Checkout our directory into the activity URL now. */
- handler = apr_pcalloc(dir->pool, sizeof(*handler));
- handler->session = dir->commit->session;
- handler->conn = dir->commit->conn;
-
- checkout_ctx = apr_pcalloc(dir->pool, sizeof(*checkout_ctx));
- checkout_ctx->pool = dir->pool;
- checkout_ctx->progress.pool = dir->pool;
-
- checkout_ctx->activity_url = dir->commit->activity_url;
-
/* We could be called twice for the root: once to checkout the baseline;
* once to checkout the directory itself if we need to do so.
+ * Note: CHECKOUT_URL should live longer than HANDLER.
*/
- if (!dir->parent_dir && !dir->commit->baseline)
+ if (!dir->parent_dir && !dir->commit->baseline_url)
{
- checkout_ctx->checkout_url = dir->commit->vcc_url;
- dir->commit->baseline = checkout_ctx;
+ checkout_url = dir->commit->vcc_url;
+ working = &dir->commit->baseline_url;
}
else
{
- checkout_ctx->checkout_url = dir->url;
- dir->checkout = checkout_ctx;
+ checkout_url = dir->url;
+ working = &dir->working_url;
}
- handler->body_delegate = create_checkout_body;
- handler->body_delegate_baton = checkout_ctx;
- handler->body_type = "text/xml";
-
- handler->response_handler = handle_checkout;
- handler->response_baton = checkout_ctx;
-
- handler->method = "CHECKOUT";
- handler->path = checkout_ctx->checkout_url;
-
- svn_ra_serf__request_create(handler);
-
- err = svn_ra_serf__context_run_wait(&checkout_ctx->progress.done,
- dir->commit->session,
- dir->pool);
+ /* Checkout our directory into the activity URL now. */
+ err = retry_checkout_node(working, dir->commit, checkout_url,
+ dir->pool, scratch_pool);
if (err)
{
if (err->apr_err == SVN_ERR_FS_CONFLICT)
- SVN_ERR_W(err, apr_psprintf(dir->pool,
+ SVN_ERR_W(err, apr_psprintf(scratch_pool,
_("Directory '%s' is out of date; try updating"),
- svn_dirent_local_style(dir->relpath, dir->pool)));
+ svn_dirent_local_style(dir->relpath, scratch_pool)));
return err;
}
- if (checkout_ctx->progress.status != 201)
- {
- return return_response_err(handler, &checkout_ctx->progress);
- }
-
return SVN_NO_ERROR;
}
@@ -419,16 +454,17 @@ checkout_dir(dir_context_t *dir)
* BASE_REVISION, and set *CHECKED_IN_URL to the concatenation of that
* with RELPATH.
*
- * Allocate the result in POOL, and use POOL for temporary allocation.
+ * Allocate the result in RESULT_POOL, and use SCRATCH_POOL for
+ * temporary allocation.
*/
static svn_error_t *
get_version_url(const char **checked_in_url,
svn_ra_serf__session_t *session,
- svn_ra_serf__connection_t *conn,
const char *relpath,
svn_revnum_t base_revision,
const char *parent_vsn_url,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
const char *root_checkout;
@@ -436,15 +472,16 @@ get_version_url(const char **checked_in_url,
{
const svn_string_t *current_version;
- SVN_ERR(session->wc_callbacks->get_wc_prop(session->wc_callback_baton,
- relpath,
- SVN_RA_SERF__WC_CHECKED_IN_URL,
- &current_version, pool));
+ SVN_ERR(session->wc_callbacks->get_wc_prop(
+ session->wc_callback_baton,
+ relpath,
+ SVN_RA_SERF__WC_CHECKED_IN_URL,
+ &current_version, scratch_pool));
if (current_version)
{
*checked_in_url =
- svn_urlpath__canonicalize(current_version->data, pool);
+ svn_urlpath__canonicalize(current_version->data, result_pool);
return SVN_NO_ERROR;
}
}
@@ -455,63 +492,53 @@ get_version_url(const char **checked_in_url,
}
else
{
- svn_ra_serf__propfind_context_t *propfind_ctx;
- apr_hash_t *props;
const char *propfind_url;
-
- props = apr_hash_make(pool);
+ svn_ra_serf__connection_t *conn = session->conns[0];
if (SVN_IS_VALID_REVNUM(base_revision))
{
- const char *bc_url, *bc_relpath;
-
/* mod_dav_svn can't handle the "Label:" header that
svn_ra_serf__deliver_props() is going to try to use for
this lookup, so we'll do things the hard(er) way, by
looking up the version URL from a resource in the
baseline collection. */
- SVN_ERR(svn_ra_serf__get_baseline_info(&bc_url, &bc_relpath,
- session, conn,
- session->session_url.path,
- base_revision, NULL, pool));
- propfind_url = svn_path_url_add_component2(bc_url, bc_relpath, pool);
+ /* ### conn==NULL for session->conns[0]. same as CONN. */
+ SVN_ERR(svn_ra_serf__get_stable_url(&propfind_url,
+ NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, base_revision,
+ scratch_pool, scratch_pool));
}
else
{
propfind_url = session->session_url.path;
}
- /* ### switch to svn_ra_serf__retrieve_props */
- SVN_ERR(svn_ra_serf__deliver_props(&propfind_ctx, props, session, conn,
- propfind_url, base_revision, "0",
- checked_in_props, NULL, pool));
- SVN_ERR(svn_ra_serf__wait_for_props(propfind_ctx, session, pool));
-
- /* We wouldn't get here if the url wasn't found (404), so the checked-in
- property should have been set. */
- root_checkout =
- svn_ra_serf__get_ver_prop(props, propfind_url,
- base_revision, "DAV:", "checked-in");
-
+ SVN_ERR(svn_ra_serf__fetch_dav_prop(&root_checkout,
+ conn, propfind_url, base_revision,
+ "checked-in",
+ scratch_pool, scratch_pool));
if (!root_checkout)
return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
_("Path '%s' not present"),
session->session_url.path);
- root_checkout = svn_urlpath__canonicalize(root_checkout, pool);
+ root_checkout = svn_urlpath__canonicalize(root_checkout, scratch_pool);
}
- *checked_in_url = svn_path_url_add_component2(root_checkout, relpath, pool);
+ *checked_in_url = svn_path_url_add_component2(root_checkout, relpath,
+ result_pool);
return SVN_NO_ERROR;
}
static svn_error_t *
-checkout_file(file_context_t *file)
+checkout_file(file_context_t *file,
+ apr_pool_t *scratch_pool)
{
- svn_ra_serf__handler_t *handler;
svn_error_t *err;
dir_context_t *parent_dir = file->parent_dir;
+ const char *checkout_url;
/* Is one of our parent dirs newly added? If so, we're already
* implicitly checked out.
@@ -521,69 +548,33 @@ checkout_file(file_context_t *file)
if (parent_dir->added)
{
/* Implicitly checkout this file now. */
- file->checkout = apr_pcalloc(file->pool, sizeof(*file->checkout));
- file->checkout->pool = file->pool;
- file->checkout->progress.pool = file->pool;
- file->checkout->activity_url = file->commit->activity_url;
- file->checkout->resource_url =
- svn_path_url_add_component2(parent_dir->checkout->resource_url,
- svn_relpath__is_child(parent_dir->relpath,
- file->relpath,
- file->pool),
- file->pool);
+ file->working_url = svn_path_url_add_component2(
+ parent_dir->working_url,
+ svn_relpath_skip_ancestor(
+ parent_dir->relpath, file->relpath),
+ file->pool);
return SVN_NO_ERROR;
}
parent_dir = parent_dir->parent_dir;
}
- /* Checkout our file into the activity URL now. */
- handler = apr_pcalloc(file->pool, sizeof(*handler));
- handler->session = file->commit->session;
- handler->conn = file->commit->conn;
-
- file->checkout = apr_pcalloc(file->pool, sizeof(*file->checkout));
- file->checkout->pool = file->pool;
- file->checkout->progress.pool = file->pool;
-
- file->checkout->activity_url = file->commit->activity_url;
-
- SVN_ERR(get_version_url(&(file->checkout->checkout_url),
- file->commit->session, file->commit->conn,
+ SVN_ERR(get_version_url(&checkout_url,
+ file->commit->session,
file->relpath, file->base_revision,
- NULL, file->pool));
-
- handler->body_delegate = create_checkout_body;
- handler->body_delegate_baton = file->checkout;
- handler->body_type = "text/xml";
-
- handler->response_handler = handle_checkout;
- handler->response_baton = file->checkout;
+ NULL, scratch_pool, scratch_pool));
- handler->method = "CHECKOUT";
- handler->path = file->checkout->checkout_url;
-
- svn_ra_serf__request_create(handler);
-
- /* There's no need to wait here as we only need this when we start the
- * PROPPATCH or PUT of the file.
- */
- err = svn_ra_serf__context_run_wait(&file->checkout->progress.done,
- file->commit->session,
- file->pool);
+ /* Checkout our file into the activity URL now. */
+ err = retry_checkout_node(&file->working_url, file->commit, checkout_url,
+ file->pool, scratch_pool);
if (err)
{
if (err->apr_err == SVN_ERR_FS_CONFLICT)
- SVN_ERR_W(err, apr_psprintf(file->pool,
+ SVN_ERR_W(err, apr_psprintf(scratch_pool,
_("File '%s' is out of date; try updating"),
- svn_dirent_local_style(file->relpath, file->pool)));
+ svn_dirent_local_style(file->relpath, scratch_pool)));
return err;
}
- if (file->checkout->progress.status != 201)
- {
- return return_response_err(handler, &file->checkout->progress);
- }
-
return SVN_NO_ERROR;
}
@@ -788,6 +779,54 @@ proppatch_walker(void *baton,
return SVN_NO_ERROR;
}
+/* Possible add the lock-token "If:" precondition header to HEADERS if
+ an examination of COMMIT_CTX and RELPATH indicates that this is the
+ right thing to do.
+
+ Generally speaking, if the client provided a lock token for
+ RELPATH, it's the right thing to do. There is a notable instance
+ where this is not the case, however. If the file at RELPATH was
+ explicitly deleted in this commit already, then mod_dav removed its
+ lock token when it fielded the DELETE request, so we don't want to
+ set the lock precondition again. (See
+ http://subversion.tigris.org/issues/show_bug.cgi?id=3674 for details.)
+*/
+static svn_error_t *
+maybe_set_lock_token_header(serf_bucket_t *headers,
+ commit_context_t *commit_ctx,
+ const char *relpath,
+ apr_pool_t *pool)
+{
+ const char *token;
+
+ if (! (relpath && commit_ctx->lock_tokens))
+ return SVN_NO_ERROR;
+
+ if (! svn_hash_gets(commit_ctx->deleted_entries, relpath))
+ {
+ token = svn_hash_gets(commit_ctx->lock_tokens, relpath);
+ if (token)
+ {
+ const char *token_header;
+ const char *token_uri;
+ apr_uri_t uri = commit_ctx->session->session_url;
+
+ /* Supplying the optional URI affects apache response when
+ the lock is broken, see issue 4369. When present any URI
+ must be absolute (RFC 2518 9.4). */
+ uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
+ pool);
+ token_uri = apr_uri_unparse(pool, &uri, 0);
+
+ token_header = apr_pstrcat(pool, "<", token_uri, "> (<", token, ">)",
+ (char *)NULL);
+ serf_bucket_headers_set(headers, "If", token_header);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
static svn_error_t *
setup_proppatch_headers(serf_bucket_t *headers,
void *baton,
@@ -802,22 +841,8 @@ setup_proppatch_headers(serf_bucket_t *headers,
proppatch->base_revision));
}
- if (proppatch->relpath && proppatch->commit->lock_tokens)
- {
- const char *token;
-
- token = apr_hash_get(proppatch->commit->lock_tokens, proppatch->relpath,
- APR_HASH_KEY_STRING);
-
- if (token)
- {
- const char *token_header;
-
- token_header = apr_pstrcat(pool, "(<", token, ">)", (char *)NULL);
-
- serf_bucket_headers_set(headers, "If", token_header);
- }
- }
+ SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit,
+ proppatch->relpath, pool));
return SVN_NO_ERROR;
}
@@ -921,6 +946,7 @@ proppatch_resource(proppatch_context_t *proppatch,
struct proppatch_body_baton_t pbb;
handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
handler->method = "PROPPATCH";
handler->path = proppatch->path;
handler->conn = commit->conn;
@@ -935,20 +961,19 @@ proppatch_resource(proppatch_context_t *proppatch,
handler->body_delegate_baton = &pbb;
handler->response_handler = svn_ra_serf__handle_multistatus_only;
- handler->response_baton = &proppatch->progress;
+ handler->response_baton = handler;
- svn_ra_serf__request_create(handler);
+ SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
- /* If we don't wait for the response, our pool will be gone! */
- SVN_ERR(svn_ra_serf__context_run_wait(&proppatch->progress.done,
- commit->session, pool));
-
- if (proppatch->progress.status != 207 ||
- proppatch->progress.server_error.error)
+ if (handler->sline.code != 207
+ || (handler->server_error != NULL
+ && handler->server_error->error != NULL))
{
- return svn_error_create(SVN_ERR_RA_DAV_PROPPATCH_FAILED,
- return_response_err(handler, &proppatch->progress),
- _("At least one property change failed; repository is unchanged"));
+ return svn_error_create(
+ SVN_ERR_RA_DAV_PROPPATCH_FAILED,
+ return_response_err(handler),
+ _("At least one property change failed; repository"
+ " is unchanged"));
}
return SVN_NO_ERROR;
@@ -1020,22 +1045,8 @@ setup_put_headers(serf_bucket_t *headers,
ctx->result_checksum);
}
- if (ctx->commit->lock_tokens)
- {
- const char *token;
-
- token = apr_hash_get(ctx->commit->lock_tokens, ctx->relpath,
- APR_HASH_KEY_STRING);
-
- if (token)
- {
- const char *token_header;
-
- token_header = apr_pstrcat(pool, "(<", token, ">)", (char *)NULL);
-
- serf_bucket_headers_set(headers, "If", token_header);
- }
- }
+ SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit,
+ ctx->relpath, pool));
return APR_SUCCESS;
}
@@ -1056,13 +1067,103 @@ setup_copy_file_headers(serf_bucket_t *headers,
serf_bucket_headers_set(headers, "Destination", absolute_uri);
- serf_bucket_headers_set(headers, "Depth", "0");
- serf_bucket_headers_set(headers, "Overwrite", "T");
+ serf_bucket_headers_setn(headers, "Depth", "0");
+ serf_bucket_headers_setn(headers, "Overwrite", "T");
return SVN_NO_ERROR;
}
static svn_error_t *
+setup_if_header_recursive(svn_boolean_t *added,
+ serf_bucket_t *headers,
+ commit_context_t *commit_ctx,
+ const char *rq_relpath,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *sb = NULL;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = NULL;
+
+ if (!commit_ctx->lock_tokens)
+ {
+ *added = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ /* We try to create a directory, so within the Subversion world that
+ would imply that there is nothing here, but mod_dav_svn still sees
+ locks on the old nodes here as in DAV it is perfectly legal to lock
+ something that is not there...
+
+ Let's make mod_dav, mod_dav_svn and the DAV RFC happy by providing
+ the locks we know of with the request */
+
+ for (hi = apr_hash_first(pool, commit_ctx->lock_tokens);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *relpath = svn__apr_hash_index_key(hi);
+ apr_uri_t uri;
+
+ if (!svn_relpath_skip_ancestor(rq_relpath, relpath))
+ continue;
+ else if (svn_hash_gets(commit_ctx->deleted_entries, relpath))
+ {
+ /* When a path is already explicit deleted then its lock
+ will be removed by mod_dav. But mod_dav doesn't remove
+ locks on descendants */
+ continue;
+ }
+
+ if (!iterpool)
+ iterpool = svn_pool_create(pool);
+ else
+ svn_pool_clear(iterpool);
+
+ if (sb == NULL)
+ sb = svn_stringbuf_create("", pool);
+ else
+ svn_stringbuf_appendbyte(sb, ' ');
+
+ uri = commit_ctx->session->session_url;
+ uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
+ iterpool);
+
+ svn_stringbuf_appendbyte(sb, '<');
+ svn_stringbuf_appendcstr(sb, apr_uri_unparse(iterpool, &uri, 0));
+ svn_stringbuf_appendcstr(sb, "> (<");
+ svn_stringbuf_appendcstr(sb, svn__apr_hash_index_val(hi));
+ svn_stringbuf_appendcstr(sb, ">)");
+ }
+
+ if (iterpool)
+ svn_pool_destroy(iterpool);
+
+ if (sb)
+ {
+ serf_bucket_headers_set(headers, "If", sb->data);
+ *added = TRUE;
+ }
+ else
+ *added = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+setup_add_dir_common_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ dir_context_t *dir = baton;
+ svn_boolean_t added;
+
+ return svn_error_trace(
+ setup_if_header_recursive(&added, headers, dir->commit, dir->relpath,
+ pool));
+}
+
+static svn_error_t *
setup_copy_dir_headers(serf_bucket_t *headers,
void *baton,
apr_pool_t *pool)
@@ -1081,24 +1182,20 @@ setup_copy_dir_headers(serf_bucket_t *headers,
else
{
uri.path = (char *)svn_path_url_add_component2(
- dir->parent_dir->checkout->resource_url,
+ dir->parent_dir->working_url,
dir->name, pool);
}
absolute_uri = apr_uri_unparse(pool, &uri, 0);
serf_bucket_headers_set(headers, "Destination", absolute_uri);
- serf_bucket_headers_set(headers, "Depth", "infinity");
- serf_bucket_headers_set(headers, "Overwrite", "T");
+ serf_bucket_headers_setn(headers, "Depth", "infinity");
+ serf_bucket_headers_setn(headers, "Overwrite", "T");
/* Implicitly checkout this dir now. */
- dir->checkout = apr_pcalloc(dir->pool, sizeof(*dir->checkout));
- dir->checkout->pool = dir->pool;
- dir->checkout->progress.pool = dir->pool;
- dir->checkout->activity_url = dir->commit->activity_url;
- dir->checkout->resource_url = apr_pstrdup(dir->checkout->pool, uri.path);
+ dir->working_url = apr_pstrdup(dir->pool, uri.path);
- return SVN_NO_ERROR;
+ return svn_error_trace(setup_add_dir_common_headers(headers, baton, pool));
}
static svn_error_t *
@@ -1106,55 +1203,22 @@ setup_delete_headers(serf_bucket_t *headers,
void *baton,
apr_pool_t *pool)
{
- delete_context_t *ctx = baton;
+ delete_context_t *del = baton;
+ svn_boolean_t added;
serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
- apr_ltoa(pool, ctx->revision));
-
- if (ctx->lock_token_hash)
- {
- ctx->lock_token = apr_hash_get(ctx->lock_token_hash, ctx->path,
- APR_HASH_KEY_STRING);
-
- if (ctx->lock_token)
- {
- const char *token_header;
+ apr_ltoa(pool, del->revision));
- token_header = apr_pstrcat(pool, "<", ctx->path, "> (<",
- ctx->lock_token, ">)", (char *)NULL);
+ SVN_ERR(setup_if_header_recursive(&added, headers, del->commit,
+ del->relpath, pool));
- serf_bucket_headers_set(headers, "If", token_header);
-
- if (ctx->keep_locks)
- serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
- SVN_DAV_OPTION_KEEP_LOCKS);
- }
- }
+ if (added && del->commit->keep_locks)
+ serf_bucket_headers_setn(headers, SVN_DAV_OPTIONS_HEADER,
+ SVN_DAV_OPTION_KEEP_LOCKS);
return SVN_NO_ERROR;
}
-/* Implements svn_ra_serf__request_body_delegate_t */
-static svn_error_t *
-create_delete_body(serf_bucket_t **body_bkt,
- void *baton,
- serf_bucket_alloc_t *alloc,
- apr_pool_t *pool)
-{
- delete_context_t *ctx = baton;
- serf_bucket_t *body;
-
- body = serf_bucket_aggregate_create(alloc);
-
- svn_ra_serf__add_xml_header_buckets(body, alloc);
-
- svn_ra_serf__merge_lock_token_list(ctx->lock_token_hash, ctx->path,
- body, alloc, pool);
-
- *body_bkt = body;
- return SVN_NO_ERROR;
-}
-
/* Helper function to write the svndiff stream to temporary file. */
static svn_error_t *
svndiff_stream_write(void *file_baton,
@@ -1182,7 +1246,26 @@ create_txn_post_body(serf_bucket_t **body_bkt,
serf_bucket_alloc_t *alloc,
apr_pool_t *pool)
{
- *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc);
+ apr_hash_t *revprops = baton;
+ svn_skel_t *request_skel;
+ svn_stringbuf_t *skel_str;
+
+ request_skel = svn_skel__make_empty_list(pool);
+ if (revprops)
+ {
+ svn_skel_t *proplist_skel;
+
+ SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, revprops, pool));
+ svn_skel__prepend(proplist_skel, request_skel);
+ svn_skel__prepend_str("create-txn-with-props", request_skel, pool);
+ skel_str = svn_skel__unparse(request_skel, pool);
+ *body_bkt = SERF_BUCKET_SIMPLE_STRING(skel_str->data, alloc);
+ }
+ else
+ {
+ *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc);
+ }
+
return SVN_NO_ERROR;
}
@@ -1206,7 +1289,7 @@ setup_post_headers(serf_bucket_t *headers,
/* Handler baton for POST request. */
typedef struct post_response_ctx_t
{
- svn_ra_serf__simple_request_context_t *request_ctx;
+ svn_ra_serf__handler_t *handler;
commit_context_t *commit_ctx;
} post_response_ctx_t;
@@ -1251,14 +1334,15 @@ post_headers_iterator_callback(void *baton,
/* A custom serf_response_handler_t which is mostly a wrapper around
- svn_ra_serf__handle_status_only -- it just notices POST response
+ svn_ra_serf__expect_empty_body -- it just notices POST response
headers, too.
+
Implements svn_ra_serf__response_handler_t */
static svn_error_t *
post_response_handler(serf_request_t *request,
serf_bucket_t *response,
void *baton,
- apr_pool_t *pool)
+ apr_pool_t *scratch_pool)
{
post_response_ctx_t *prc = baton;
serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
@@ -1267,8 +1351,8 @@ post_response_handler(serf_request_t *request,
serf_bucket_headers_do(hdrs, post_headers_iterator_callback, prc);
/* Execute the 'real' response handler to XML-parse the repsonse body. */
- return svn_ra_serf__handle_status_only(request, response,
- prc->request_ctx, pool);
+ return svn_ra_serf__expect_empty_body(request, response,
+ prc->handler, scratch_pool);
}
@@ -1286,45 +1370,44 @@ open_root(void *edit_baton,
proppatch_context_t *proppatch_ctx;
dir_context_t *dir;
apr_hash_index_t *hi;
- const char *proppatch_target;
+ const char *proppatch_target = NULL;
if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->session))
{
- svn_ra_serf__simple_request_context_t *post_ctx;
post_response_ctx_t *prc;
const char *rel_path;
+ svn_boolean_t post_with_revprops
+ = (NULL != svn_hash_gets(ctx->session->supported_posts,
+ "create-txn-with-props"));
/* Create our activity URL now on the server. */
handler = apr_pcalloc(ctx->pool, sizeof(*handler));
+ handler->handler_pool = ctx->pool;
handler->method = "POST";
handler->body_type = SVN_SKEL_MIME_TYPE;
handler->body_delegate = create_txn_post_body;
- handler->body_delegate_baton = NULL;
+ handler->body_delegate_baton =
+ post_with_revprops ? ctx->revprop_table : NULL;
handler->header_delegate = setup_post_headers;
handler->header_delegate_baton = NULL;
handler->path = ctx->session->me_resource;
handler->conn = ctx->session->conns[0];
handler->session = ctx->session;
- post_ctx = apr_pcalloc(ctx->pool, sizeof(*post_ctx));
- post_ctx->pool = ctx->pool;
-
prc = apr_pcalloc(ctx->pool, sizeof(*prc));
- prc->request_ctx = post_ctx;
+ prc->handler = handler;
prc->commit_ctx = ctx;
handler->response_handler = post_response_handler;
handler->response_baton = prc;
- svn_ra_serf__request_create(handler);
+ SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool));
- SVN_ERR(svn_ra_serf__context_run_wait(&post_ctx->done, ctx->session,
- ctx->pool));
-
- if (post_ctx->status != 201)
+ if (handler->sline.code != 201)
{
apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
- switch(post_ctx->status)
+
+ switch (handler->sline.code)
{
case 403:
status = SVN_ERR_RA_DAV_FORBIDDEN;
@@ -1337,7 +1420,7 @@ open_root(void *edit_baton,
return svn_error_createf(status, NULL,
_("%s of '%s': %d %s (%s://%s)"),
handler->method, handler->path,
- post_ctx->status, post_ctx->reason,
+ handler->sline.code, handler->sline.reason,
ctx->session->session_url.scheme,
ctx->session->session_url.hostinfo);
}
@@ -1366,28 +1449,32 @@ open_root(void *edit_baton,
dir->removed_props = apr_hash_make(dir->pool);
dir->url = apr_pstrdup(dir->pool, ctx->txn_root_url);
- proppatch_target = ctx->txn_url;
+ /* If we included our revprops in the POST, we need not
+ PROPPATCH them. */
+ proppatch_target = post_with_revprops ? NULL : ctx->txn_url;
}
else
{
- svn_ra_serf__options_context_t *opt_ctx;
- svn_ra_serf__simple_request_context_t *mkact_ctx;
- const char *activity_str;
+ const char *activity_str = ctx->session->activity_collection_url;
- SVN_ERR(svn_ra_serf__create_options_req(&opt_ctx, ctx->session,
- ctx->session->conns[0],
- ctx->session->session_url.path,
- ctx->pool));
-
- SVN_ERR(svn_ra_serf__context_run_wait(
- svn_ra_serf__get_options_done_ptr(opt_ctx),
- ctx->session, ctx->pool));
-
- activity_str = svn_ra_serf__options_get_activity_collection(opt_ctx);
if (!activity_str)
- return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
- _("The OPTIONS response did not include the "
- "requested activity-collection-set value"));
+ SVN_ERR(svn_ra_serf__v1_get_activity_collection(&activity_str,
+ ctx->session->conns[0],
+ ctx->pool,
+ ctx->pool));
+
+ /* Cache the result. */
+ if (activity_str)
+ {
+ ctx->session->activity_collection_url =
+ apr_pstrdup(ctx->session->pool, activity_str);
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
+ _("The OPTIONS response did not include the "
+ "requested activity-collection-set value"));
+ }
ctx->activity_url =
svn_path_url_add_component2(activity_str, svn_uuid_generate(ctx->pool),
@@ -1395,26 +1482,22 @@ open_root(void *edit_baton,
/* Create our activity URL now on the server. */
handler = apr_pcalloc(ctx->pool, sizeof(*handler));
+ handler->handler_pool = ctx->pool;
handler->method = "MKACTIVITY";
handler->path = ctx->activity_url;
handler->conn = ctx->session->conns[0];
handler->session = ctx->session;
- mkact_ctx = apr_pcalloc(ctx->pool, sizeof(*mkact_ctx));
- mkact_ctx->pool = ctx->pool;
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
- handler->response_handler = svn_ra_serf__handle_status_only;
- handler->response_baton = mkact_ctx;
+ SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool));
- svn_ra_serf__request_create(handler);
-
- SVN_ERR(svn_ra_serf__context_run_wait(&mkact_ctx->done, ctx->session,
- ctx->pool));
-
- if (mkact_ctx->status != 201)
+ if (handler->sline.code != 201)
{
apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
- switch(mkact_ctx->status)
+
+ switch (handler->sline.code)
{
case 403:
status = SVN_ERR_RA_DAV_FORBIDDEN;
@@ -1427,7 +1510,7 @@ open_root(void *edit_baton,
return svn_error_createf(status, NULL,
_("%s of '%s': %d %s (%s://%s)"),
handler->method, handler->path,
- mkact_ctx->status, mkact_ctx->reason,
+ handler->sline.code, handler->sline.reason,
ctx->session->session_url.scheme,
ctx->session->session_url.hostinfo);
}
@@ -1448,57 +1531,55 @@ open_root(void *edit_baton,
dir->removed_props = apr_hash_make(dir->pool);
SVN_ERR(get_version_url(&dir->url, dir->commit->session,
- dir->commit->conn, dir->relpath,
+ dir->relpath,
dir->base_revision, ctx->checked_in_url,
- dir->pool));
+ dir->pool, dir->pool /* scratch_pool */));
ctx->checked_in_url = dir->url;
/* Checkout our root dir */
- SVN_ERR(checkout_dir(dir));
+ SVN_ERR(checkout_dir(dir, dir->pool /* scratch_pool */));
- proppatch_target = ctx->baseline->resource_url;
+ proppatch_target = ctx->baseline_url;
}
-
- /* PROPPATCH our revprops and pass them along. */
- proppatch_ctx = apr_pcalloc(ctx->pool, sizeof(*proppatch_ctx));
- proppatch_ctx->pool = dir_pool;
- proppatch_ctx->progress.pool = dir_pool;
- proppatch_ctx->commit = ctx;
- proppatch_ctx->path = proppatch_target;
- proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
- proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
- proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
-
- for (hi = apr_hash_first(ctx->pool, ctx->revprop_table); hi;
- hi = apr_hash_next(hi))
+ /* Unless this is NULL -- which means we don't need to PROPPATCH the
+ transaction with our revprops -- then, you know, PROPPATCH the
+ transaction with our revprops. */
+ if (proppatch_target)
{
- const void *key;
- void *val;
- const char *name;
- svn_string_t *value;
- const char *ns;
-
- apr_hash_this(hi, &key, NULL, &val);
- name = key;
- value = val;
+ proppatch_ctx = apr_pcalloc(ctx->pool, sizeof(*proppatch_ctx));
+ proppatch_ctx->pool = dir_pool;
+ proppatch_ctx->commit = ctx;
+ proppatch_ctx->path = proppatch_target;
+ proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
+ proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
+ proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
- if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
- {
- ns = SVN_DAV_PROP_NS_SVN;
- name += sizeof(SVN_PROP_PREFIX) - 1;
- }
- else
+ for (hi = apr_hash_first(ctx->pool, ctx->revprop_table); hi;
+ hi = apr_hash_next(hi))
{
- ns = SVN_DAV_PROP_NS_CUSTOM;
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_string_t *value = svn__apr_hash_index_val(hi);
+ const char *ns;
+
+ if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
+ {
+ ns = SVN_DAV_PROP_NS_SVN;
+ name += sizeof(SVN_PROP_PREFIX) - 1;
+ }
+ else
+ {
+ ns = SVN_DAV_PROP_NS_CUSTOM;
+ }
+
+ svn_ra_serf__set_prop(proppatch_ctx->changed_props,
+ proppatch_ctx->path,
+ ns, name, value, proppatch_ctx->pool);
}
- svn_ra_serf__set_prop(proppatch_ctx->changed_props, proppatch_ctx->path,
- ns, name, value, proppatch_ctx->pool);
+ SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, ctx->pool));
}
- SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, ctx->pool));
-
*root_baton = dir;
return SVN_NO_ERROR;
@@ -1514,7 +1595,6 @@ delete_entry(const char *path,
delete_context_t *delete_ctx;
svn_ra_serf__handler_t *handler;
const char *delete_target;
- svn_error_t *err;
if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
{
@@ -1524,8 +1604,8 @@ delete_entry(const char *path,
else
{
/* Ensure our directory has been checked out */
- SVN_ERR(checkout_dir(dir));
- delete_target = svn_path_url_add_component2(dir->checkout->resource_url,
+ SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
+ delete_target = svn_path_url_add_component2(dir->working_url,
svn_relpath_basename(path,
NULL),
pool);
@@ -1533,18 +1613,17 @@ delete_entry(const char *path,
/* DELETE our entry */
delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
- delete_ctx->progress.pool = pool;
- delete_ctx->path = apr_pstrdup(pool, path);
+ delete_ctx->relpath = apr_pstrdup(pool, path);
delete_ctx->revision = revision;
- delete_ctx->lock_token_hash = dir->commit->lock_tokens;
- delete_ctx->keep_locks = dir->commit->keep_locks;
+ delete_ctx->commit = dir->commit;
handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
handler->session = dir->commit->session;
handler->conn = dir->commit->conn;
- handler->response_handler = svn_ra_serf__handle_status_only;
- handler->response_baton = &delete_ctx->progress;
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
handler->header_delegate = setup_delete_headers;
handler->header_delegate_baton = delete_ctx;
@@ -1552,48 +1631,16 @@ delete_entry(const char *path,
handler->method = "DELETE";
handler->path = delete_target;
- svn_ra_serf__request_create(handler);
-
- err = svn_ra_serf__context_run_wait(&delete_ctx->progress.done,
- dir->commit->session, pool);
-
- if (err &&
- (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN ||
- err->apr_err == SVN_ERR_FS_NO_LOCK_TOKEN ||
- err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH ||
- err->apr_err == SVN_ERR_FS_PATH_ALREADY_LOCKED))
- {
- svn_error_clear(err);
-
- /* An error has been registered on the connection. Reset the thing
- so that we can use it again. */
- serf_connection_reset(handler->conn->conn);
-
- handler->body_delegate = create_delete_body;
- handler->body_delegate_baton = delete_ctx;
- handler->body_type = "text/xml";
-
- svn_ra_serf__request_create(handler);
-
- delete_ctx->progress.done = 0;
-
- SVN_ERR(svn_ra_serf__context_run_wait(&delete_ctx->progress.done,
- dir->commit->session, pool));
- }
- else if (err)
- {
- return err;
- }
+ SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
/* 204 No Content: item successfully deleted */
- if (delete_ctx->progress.status != 204)
+ if (handler->sline.code != 204)
{
- return return_response_err(handler, &delete_ctx->progress);
+ return svn_error_trace(return_response_err(handler));
}
- apr_hash_set(dir->commit->deleted_entries,
- apr_pstrdup(dir->commit->pool, path), APR_HASH_KEY_STRING,
- (void*)1);
+ svn_hash_sets(dir->commit->deleted_entries,
+ apr_pstrdup(dir->commit->pool, path), (void *)1);
return SVN_NO_ERROR;
}
@@ -1609,7 +1656,6 @@ add_directory(const char *path,
dir_context_t *parent = parent_baton;
dir_context_t *dir;
svn_ra_serf__handler_t *handler;
- svn_ra_serf__simple_request_context_t *add_dir_ctx;
apr_status_t status;
const char *mkcol_target;
@@ -1621,7 +1667,7 @@ add_directory(const char *path,
dir->added = TRUE;
dir->base_revision = SVN_INVALID_REVNUM;
dir->copy_revision = copyfrom_revision;
- dir->copy_path = copyfrom_path;
+ dir->copy_path = apr_pstrdup(dir->pool, copyfrom_path);
dir->relpath = apr_pstrdup(dir->pool, path);
dir->name = svn_relpath_basename(dir->relpath, NULL);
dir->changed_props = apr_hash_make(dir->pool);
@@ -1636,33 +1682,34 @@ add_directory(const char *path,
else
{
/* Ensure our parent is checked out. */
- SVN_ERR(checkout_dir(parent));
+ SVN_ERR(checkout_dir(parent, dir->pool /* scratch_pool */));
dir->url = svn_path_url_add_component2(parent->commit->checked_in_url,
dir->name, dir->pool);
mkcol_target = svn_path_url_add_component2(
- parent->checkout->resource_url,
+ parent->working_url,
dir->name, dir->pool);
}
handler = apr_pcalloc(dir->pool, sizeof(*handler));
+ handler->handler_pool = dir->pool;
handler->conn = dir->commit->conn;
handler->session = dir->commit->session;
- add_dir_ctx = apr_pcalloc(dir->pool, sizeof(*add_dir_ctx));
- add_dir_ctx->pool = dir->pool;
-
- handler->response_handler = svn_ra_serf__handle_status_only;
- handler->response_baton = add_dir_ctx;
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
if (!dir->copy_path)
{
handler->method = "MKCOL";
handler->path = mkcol_target;
+
+ handler->header_delegate = setup_add_dir_common_headers;
+ handler->header_delegate_baton = dir;
}
else
{
apr_uri_t uri;
- const char *rel_copy_path, *basecoll_url, *req_url;
+ const char *req_url;
status = apr_uri_parse(dir->pool, dir->copy_path, &uri);
if (status)
@@ -1672,13 +1719,12 @@ add_directory(const char *path,
dir->copy_path);
}
- SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &rel_copy_path,
- dir->commit->session,
- dir->commit->conn,
- uri.path, dir->copy_revision,
- NULL, dir_pool));
- req_url = svn_path_url_add_component2(basecoll_url, rel_copy_path,
- dir->pool);
+ /* ### conn==NULL for session->conns[0]. same as commit->conn. */
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ dir->commit->session,
+ NULL /* conn */,
+ uri.path, dir->copy_revision,
+ dir_pool, dir_pool));
handler->method = "COPY";
handler->path = req_url;
@@ -1687,29 +1733,24 @@ add_directory(const char *path,
handler->header_delegate_baton = dir;
}
- svn_ra_serf__request_create(handler);
-
- SVN_ERR(svn_ra_serf__context_run_wait(&add_dir_ctx->done,
- dir->commit->session, dir->pool));
+ SVN_ERR(svn_ra_serf__context_run_one(handler, dir->pool));
- switch (add_dir_ctx->status)
+ switch (handler->sline.code)
{
case 201: /* Created: item was successfully copied */
case 204: /* No Content: item successfully replaced an existing target */
break;
case 403:
- SVN_ERR(add_dir_ctx->server_error.error);
return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
_("Access to '%s' forbidden"),
handler->path);
default:
- SVN_ERR(add_dir_ctx->server_error.error);
return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
_("Adding directory failed: %s on %s "
"(%d %s)"),
handler->method, handler->path,
- add_dir_ctx->status, add_dir_ctx->reason);
+ handler->sline.code, handler->sline.reason);
}
*child_baton = dir;
@@ -1749,9 +1790,10 @@ open_directory(const char *path,
else
{
SVN_ERR(get_version_url(&dir->url,
- dir->commit->session, dir->commit->conn,
+ dir->commit->session,
dir->relpath, dir->base_revision,
- dir->commit->checked_in_url, dir->pool));
+ dir->commit->checked_in_url,
+ dir->pool, dir->pool /* scratch_pool */));
}
*child_baton = dir;
@@ -1776,9 +1818,9 @@ change_dir_prop(void *dir_baton,
else
{
/* Ensure we have a checked out dir. */
- SVN_ERR(checkout_dir(dir));
+ SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
- proppatch_target = dir->checkout->resource_url;
+ proppatch_target = dir->working_url;
}
name = apr_pstrdup(dir->pool, name);
@@ -1800,7 +1842,7 @@ change_dir_prop(void *dir_baton,
}
else
{
- value = svn_string_create("", dir->pool);
+ value = svn_string_create_empty(dir->pool);
svn_ra_serf__set_prop(dir->removed_props, proppatch_target,
ns, name, value, dir->pool);
}
@@ -1826,7 +1868,6 @@ close_directory(void *dir_baton,
proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
proppatch_ctx->pool = pool;
- proppatch_ctx->progress.pool = pool;
proppatch_ctx->commit = dir->commit;
proppatch_ctx->relpath = dir->relpath;
proppatch_ctx->changed_props = dir->changed_props;
@@ -1839,7 +1880,7 @@ close_directory(void *dir_baton,
}
else
{
- proppatch_ctx->path = dir->checkout->resource_url;
+ proppatch_ctx->path = dir->working_url;
}
SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, dir->pool));
@@ -1871,7 +1912,7 @@ add_file(const char *path,
new_file->name = svn_relpath_basename(new_file->relpath, NULL);
new_file->added = TRUE;
new_file->base_revision = SVN_INVALID_REVNUM;
- new_file->copy_path = copy_path;
+ new_file->copy_path = apr_pstrdup(new_file->pool, copy_path);
new_file->copy_revision = copy_revision;
new_file->changed_props = apr_hash_make(new_file->pool);
new_file->removed_props = apr_hash_make(new_file->pool);
@@ -1887,17 +1928,16 @@ add_file(const char *path,
else
{
/* Ensure our parent directory has been checked out */
- SVN_ERR(checkout_dir(dir));
+ SVN_ERR(checkout_dir(dir, new_file->pool /* scratch_pool */));
new_file->url =
- svn_path_url_add_component2(dir->checkout->resource_url,
+ svn_path_url_add_component2(dir->working_url,
new_file->name, new_file->pool);
}
while (deleted_parent && deleted_parent[0] != '\0')
{
- if (apr_hash_get(dir->commit->deleted_entries,
- deleted_parent, APR_HASH_KEY_STRING))
+ if (svn_hash_gets(dir->commit->deleted_entries, deleted_parent))
{
break;
}
@@ -1907,30 +1947,35 @@ add_file(const char *path,
if (! ((dir->added && !dir->copy_path) ||
(deleted_parent && deleted_parent[0] != '\0')))
{
- svn_ra_serf__simple_request_context_t *head_ctx;
svn_ra_serf__handler_t *handler;
- head_ctx = apr_pcalloc(new_file->pool, sizeof(*head_ctx));
- head_ctx->pool = new_file->pool;
-
handler = apr_pcalloc(new_file->pool, sizeof(*handler));
+ handler->handler_pool = new_file->pool;
handler->session = new_file->commit->session;
handler->conn = new_file->commit->conn;
handler->method = "HEAD";
handler->path = svn_path_url_add_component2(
dir->commit->session->session_url.path,
path, new_file->pool);
- handler->response_handler = svn_ra_serf__handle_status_only;
- handler->response_baton = head_ctx;
- svn_ra_serf__request_create(handler);
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
- SVN_ERR(svn_ra_serf__context_run_wait(&head_ctx->done,
- new_file->commit->session,
- new_file->pool));
+ SVN_ERR(svn_ra_serf__context_run_one(handler, new_file->pool));
- if (head_ctx->status != 404)
+ if (handler->sline.code != 404)
{
- return svn_error_createf(SVN_ERR_RA_DAV_ALREADY_EXISTS, NULL,
+ if (handler->sline.code != 200)
+ {
+ svn_error_t *err;
+
+ err = svn_ra_serf__error_on_status(handler->sline,
+ handler->path,
+ handler->location);
+
+ SVN_ERR(err);
+ }
+
+ return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
_("File '%s' already exists"), path);
}
}
@@ -1972,9 +2017,9 @@ open_file(const char *path,
else
{
/* CHECKOUT the file into our activity. */
- SVN_ERR(checkout_file(new_file));
+ SVN_ERR(checkout_file(new_file, new_file->pool /* scratch_pool */));
- new_file->url = new_file->checkout->resource_url;
+ new_file->url = new_file->working_url;
}
*file_baton = new_file;
@@ -2011,7 +2056,8 @@ apply_textdelta(void *file_baton,
ctx->stream = svn_stream_create(ctx, pool);
svn_stream_set_write(ctx->stream, svndiff_stream_write);
- svn_txdelta_to_svndiff2(handler, handler_baton, ctx->stream, 0, pool);
+ svn_txdelta_to_svndiff3(handler, handler_baton, ctx->stream, 0,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
if (base_checksum)
ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum);
@@ -2048,7 +2094,7 @@ change_file_prop(void *file_baton,
}
else
{
- value = svn_string_create("", file->pool);
+ value = svn_string_create_empty(file->pool);
svn_ra_serf__set_prop(file->removed_props, file->url,
ns, name, value, file->pool);
@@ -2060,7 +2106,7 @@ change_file_prop(void *file_baton,
static svn_error_t *
close_file(void *file_baton,
const char *text_checksum,
- apr_pool_t *pool)
+ apr_pool_t *scratch_pool)
{
file_context_t *ctx = file_baton;
svn_boolean_t put_empty_file = FALSE;
@@ -2071,11 +2117,10 @@ close_file(void *file_baton,
if (ctx->copy_path)
{
svn_ra_serf__handler_t *handler;
- svn_ra_serf__simple_request_context_t *copy_ctx;
apr_uri_t uri;
- const char *rel_copy_path, *basecoll_url, *req_url;
+ const char *req_url;
- status = apr_uri_parse(pool, ctx->copy_path, &uri);
+ status = apr_uri_parse(scratch_pool, ctx->copy_path, &uri);
if (status)
{
return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
@@ -2083,36 +2128,31 @@ close_file(void *file_baton,
ctx->copy_path);
}
- SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &rel_copy_path,
- ctx->commit->session,
- ctx->commit->conn,
- uri.path, ctx->copy_revision,
- NULL, pool));
- req_url = svn_path_url_add_component2(basecoll_url, rel_copy_path, pool);
+ /* ### conn==NULL for session->conns[0]. same as commit->conn. */
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ ctx->commit->session,
+ NULL /* conn */,
+ uri.path, ctx->copy_revision,
+ scratch_pool, scratch_pool));
- handler = apr_pcalloc(pool, sizeof(*handler));
+ handler = apr_pcalloc(scratch_pool, sizeof(*handler));
+ handler->handler_pool = scratch_pool;
handler->method = "COPY";
handler->path = req_url;
handler->conn = ctx->commit->conn;
handler->session = ctx->commit->session;
- copy_ctx = apr_pcalloc(pool, sizeof(*copy_ctx));
- copy_ctx->pool = pool;
-
- handler->response_handler = svn_ra_serf__handle_status_only;
- handler->response_baton = copy_ctx;
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
handler->header_delegate = setup_copy_file_headers;
handler->header_delegate_baton = ctx;
- svn_ra_serf__request_create(handler);
-
- SVN_ERR(svn_ra_serf__context_run_wait(&copy_ctx->done,
- ctx->commit->session, pool));
+ SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
- if (copy_ctx->status != 201 && copy_ctx->status != 204)
+ if (handler->sline.code != 201 && handler->sline.code != 204)
{
- return return_response_err(handler, copy_ctx);
+ return svn_error_trace(return_response_err(handler));
}
}
@@ -2126,19 +2166,16 @@ close_file(void *file_baton,
if (ctx->stream || put_empty_file)
{
svn_ra_serf__handler_t *handler;
- svn_ra_serf__simple_request_context_t *put_ctx;
- handler = apr_pcalloc(pool, sizeof(*handler));
+ handler = apr_pcalloc(scratch_pool, sizeof(*handler));
+ handler->handler_pool = scratch_pool;
handler->method = "PUT";
handler->path = ctx->url;
handler->conn = ctx->commit->conn;
handler->session = ctx->commit->session;
- put_ctx = apr_pcalloc(pool, sizeof(*put_ctx));
- put_ctx->pool = pool;
-
- handler->response_handler = svn_ra_serf__handle_status_only;
- handler->response_baton = put_ctx;
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
if (put_empty_file)
{
@@ -2150,25 +2187,22 @@ close_file(void *file_baton,
{
handler->body_delegate = create_put_body;
handler->body_delegate_baton = ctx;
- handler->body_type = "application/vnd.svn-svndiff";
+ handler->body_type = SVN_SVNDIFF_MIME_TYPE;
}
handler->header_delegate = setup_put_headers;
handler->header_delegate_baton = ctx;
- svn_ra_serf__request_create(handler);
+ SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
- SVN_ERR(svn_ra_serf__context_run_wait(&put_ctx->done,
- ctx->commit->session, pool));
-
- if (put_ctx->status != 204 && put_ctx->status != 201)
+ if (handler->sline.code != 204 && handler->sline.code != 201)
{
- return return_response_err(handler, put_ctx);
+ return svn_error_trace(return_response_err(handler));
}
}
if (ctx->svndiff)
- SVN_ERR(svn_io_file_close(ctx->svndiff, pool));
+ SVN_ERR(svn_io_file_close(ctx->svndiff, scratch_pool));
/* If we had any prop changes, push them via PROPPATCH. */
if (apr_hash_count(ctx->changed_props) ||
@@ -2178,7 +2212,6 @@ close_file(void *file_baton,
proppatch = apr_pcalloc(ctx->pool, sizeof(*proppatch));
proppatch->pool = ctx->pool;
- proppatch->progress.pool = pool;
proppatch->relpath = ctx->relpath;
proppatch->path = ctx->url;
proppatch->commit = ctx->commit;
@@ -2197,61 +2230,61 @@ close_edit(void *edit_baton,
apr_pool_t *pool)
{
commit_context_t *ctx = edit_baton;
- svn_ra_serf__merge_context_t *merge_ctx;
- svn_ra_serf__simple_request_context_t *delete_ctx;
- svn_ra_serf__handler_t *handler;
- svn_boolean_t *merge_done;
const char *merge_target =
ctx->activity_url ? ctx->activity_url : ctx->txn_url;
+ const svn_commit_info_t *commit_info;
+ int response_code;
+ svn_error_t *err = NULL;
/* MERGE our activity */
- SVN_ERR(svn_ra_serf__merge_create_req(&merge_ctx, ctx->session,
- ctx->session->conns[0],
- merge_target,
- ctx->lock_tokens,
- ctx->keep_locks,
- pool));
-
- merge_done = svn_ra_serf__merge_get_done_ptr(merge_ctx);
-
- SVN_ERR(svn_ra_serf__context_run_wait(merge_done, ctx->session, pool));
+ SVN_ERR(svn_ra_serf__run_merge(&commit_info, &response_code,
+ ctx->session,
+ ctx->session->conns[0],
+ merge_target,
+ ctx->lock_tokens,
+ ctx->keep_locks,
+ pool, pool));
- if (svn_ra_serf__merge_get_status(merge_ctx) != 200)
+ if (response_code != 200)
{
return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
_("MERGE request failed: returned %d "
"(during commit)"),
- svn_ra_serf__merge_get_status(merge_ctx));
+ response_code);
}
+ ctx->txn_url = NULL; /* If HTTPv2, the txn is now done */
+
/* Inform the WC that we did a commit. */
if (ctx->callback)
- SVN_ERR(ctx->callback(svn_ra_serf__merge_get_commit_info(merge_ctx),
- ctx->callback_baton, pool));
+ err = ctx->callback(commit_info, ctx->callback_baton, pool);
/* If we're using activities, DELETE our completed activity. */
if (ctx->activity_url)
{
+ svn_ra_serf__handler_t *handler;
+
handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
handler->method = "DELETE";
handler->path = ctx->activity_url;
handler->conn = ctx->conn;
handler->session = ctx->session;
- delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
- delete_ctx->pool = pool;
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
- handler->response_handler = svn_ra_serf__handle_status_only;
- handler->response_baton = delete_ctx;
+ ctx->activity_url = NULL; /* Don't try again in abort_edit() on fail */
- svn_ra_serf__request_create(handler);
+ SVN_ERR(svn_error_compose_create(
+ err,
+ svn_ra_serf__context_run_one(handler, pool)));
- SVN_ERR(svn_ra_serf__context_run_wait(&delete_ctx->done, ctx->session,
- pool));
-
- SVN_ERR_ASSERT(delete_ctx->status == 204);
+ SVN_ERR_ASSERT(handler->sline.code == 204);
}
+ SVN_ERR(err);
+
return SVN_NO_ERROR;
}
@@ -2261,7 +2294,6 @@ abort_edit(void *edit_baton,
{
commit_context_t *ctx = edit_baton;
svn_ra_serf__handler_t *handler;
- svn_ra_serf__simple_request_context_t *delete_ctx;
/* If an activity or transaction wasn't even created, don't bother
trying to delete it. */
@@ -2274,35 +2306,32 @@ abort_edit(void *edit_baton,
/* DELETE our aborted activity */
handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
handler->method = "DELETE";
handler->conn = ctx->session->conns[0];
handler->session = ctx->session;
- delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
- delete_ctx->pool = pool;
-
- handler->response_handler = svn_ra_serf__handle_status_only;
- handler->response_baton = delete_ctx;
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
if (USING_HTTPV2_COMMIT_SUPPORT(ctx)) /* HTTP v2 */
handler->path = ctx->txn_url;
else
handler->path = ctx->activity_url;
- svn_ra_serf__request_create(handler);
-
- SVN_ERR(svn_ra_serf__context_run_wait(&delete_ctx->done, ctx->session,
- pool));
+ SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
/* 204 if deleted,
403 if DELETE was forbidden (indicates MKACTIVITY was forbidden too),
404 if the activity wasn't found. */
- if (delete_ctx->status != 204 &&
- delete_ctx->status != 403 &&
- delete_ctx->status != 404
+ if (handler->sline.code != 204
+ && handler->sline.code != 403
+ && handler->sline.code != 404
)
{
- SVN_ERR_MALFUNCTION();
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("DELETE returned unexpected status: %d"),
+ handler->sline.code);
}
return SVN_NO_ERROR;
@@ -2322,7 +2351,9 @@ svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
svn_ra_serf__session_t *session = ra_session->priv;
svn_delta_editor_t *editor;
commit_context_t *ctx;
- apr_hash_index_t *hi;
+ const char *repos_root;
+ const char *base_relpath;
+ svn_boolean_t supports_ephemeral_props;
ctx = apr_pcalloc(pool, sizeof(*ctx));
@@ -2331,22 +2362,28 @@ svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
ctx->session = session;
ctx->conn = session->conns[0];
- ctx->revprop_table = apr_hash_make(pool);
- for (hi = apr_hash_first(pool, revprop_table); hi; hi = apr_hash_next(hi))
- {
- const void *key;
- apr_ssize_t klen;
- void *val;
+ ctx->revprop_table = svn_prop_hash_dup(revprop_table, pool);
- apr_hash_this(hi, &key, &klen, &val);
- apr_hash_set(ctx->revprop_table, apr_pstrdup(pool, key), klen,
- svn_string_dup(val, pool));
+ /* If the server supports ephemeral properties, add some carrying
+ interesting version information. */
+ SVN_ERR(svn_ra_serf__has_capability(ra_session, &supports_ephemeral_props,
+ SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
+ pool));
+ if (supports_ephemeral_props)
+ {
+ svn_hash_sets(ctx->revprop_table,
+ apr_pstrdup(pool, SVN_PROP_TXN_CLIENT_COMPAT_VERSION),
+ svn_string_create(SVN_VER_NUMBER, pool));
+ svn_hash_sets(ctx->revprop_table,
+ apr_pstrdup(pool, SVN_PROP_TXN_USER_AGENT),
+ svn_string_create(session->useragent, pool));
}
ctx->callback = callback;
ctx->callback_baton = callback_baton;
- ctx->lock_tokens = lock_tokens;
+ ctx->lock_tokens = (lock_tokens && apr_hash_count(lock_tokens))
+ ? lock_tokens : NULL;
ctx->keep_locks = keep_locks;
ctx->deleted_entries = apr_hash_make(ctx->pool);
@@ -2369,6 +2406,14 @@ svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
*ret_editor = editor;
*edit_baton = ctx;
+ SVN_ERR(svn_ra_serf__get_repos_root(ra_session, &repos_root, pool));
+ base_relpath = svn_uri_skip_ancestor(repos_root, session->session_url_str,
+ pool);
+
+ SVN_ERR(svn_editor__insert_shims(ret_editor, edit_baton, *ret_editor,
+ *edit_baton, repos_root, base_relpath,
+ session->shim_callbacks, pool, pool));
+
return SVN_NO_ERROR;
}
@@ -2383,8 +2428,8 @@ svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
svn_ra_serf__session_t *session = ra_session->priv;
proppatch_context_t *proppatch_ctx;
commit_context_t *commit;
- const char *vcc_url, *proppatch_target, *ns;
- apr_hash_t *props;
+ const char *proppatch_target;
+ const char *ns;
svn_error_t *err;
if (old_value_p)
@@ -2411,21 +2456,15 @@ svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
}
else
{
- svn_ra_serf__propfind_context_t *propfind_ctx;
+ const char *vcc_url;
SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, commit->session,
commit->conn, pool));
- props = apr_hash_make(pool);
-
- /* ### switch to svn_ra_serf__retrieve_props */
- SVN_ERR(svn_ra_serf__deliver_props(&propfind_ctx, props, commit->session,
- commit->conn, vcc_url, rev, "0",
- checked_in_props, NULL, pool));
- SVN_ERR(svn_ra_serf__wait_for_props(propfind_ctx, commit->session, pool));
-
- proppatch_target = svn_ra_serf__get_ver_prop(props, vcc_url, rev,
- "DAV:", "href");
+ SVN_ERR(svn_ra_serf__fetch_dav_prop(&proppatch_target,
+ commit->conn, vcc_url, rev,
+ "href",
+ pool, pool));
}
if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
@@ -2441,7 +2480,6 @@ svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
/* PROPPATCH our log message and pass it along. */
proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
proppatch_ctx->pool = pool;
- proppatch_ctx->progress.pool = pool;
proppatch_ctx->commit = commit;
proppatch_ctx->path = proppatch_target;
proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
@@ -2461,7 +2499,7 @@ svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
}
else if (old_value_p)
{
- svn_string_t *dummy_value = svn_string_create("", proppatch_ctx->pool);
+ svn_string_t *dummy_value = svn_string_create_empty(proppatch_ctx->pool);
svn_ra_serf__set_prop(proppatch_ctx->previous_removed_props,
proppatch_ctx->path,
@@ -2475,7 +2513,7 @@ svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
}
else
{
- value = svn_string_create("", proppatch_ctx->pool);
+ value = svn_string_create_empty(proppatch_ctx->pool);
svn_ra_serf__set_prop(proppatch_ctx->removed_props, proppatch_ctx->path,
ns, name, value, proppatch_ctx->pool);
diff --git a/subversion/libsvn_ra_serf/get_deleted_rev.c b/subversion/libsvn_ra_serf/get_deleted_rev.c
index 09db22c..40f6b1d 100644
--- a/subversion/libsvn_ra_serf/get_deleted_rev.c
+++ b/subversion/libsvn_ra_serf/get_deleted_rev.c
@@ -35,14 +35,13 @@
/*
* This enum represents the current state of our XML parsing for a REPORT.
*/
-typedef enum drev_state_e {
- NONE = 0,
+enum drev_state_e {
+ INITIAL = 0,
+ REPORT,
VERSION_NAME
-} drev_state_e;
+};
typedef struct drev_context_t {
- apr_pool_t *pool;
-
const char *path;
svn_revnum_t peg_revision;
svn_revnum_t end_revision;
@@ -51,90 +50,40 @@ typedef struct drev_context_t {
the range PEG_REVISION-END-END_REVISION? */
svn_revnum_t *revision_deleted;
- /* are we done? */
- svn_boolean_t done;
-
} drev_context_t;
-
-static void
-push_state(svn_ra_serf__xml_parser_t *parser,
- drev_context_t *drev_ctx,
- drev_state_e state)
-{
- svn_ra_serf__xml_push_state(parser, state);
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t getdrev_ttable[] = {
+ { INITIAL, S_, "get-deleted-rev-report", REPORT,
+ FALSE, { NULL }, FALSE },
- if (state == VERSION_NAME)
- parser->state->private = NULL;
-}
+ { REPORT, D_, SVN_DAV__VERSION_NAME, VERSION_NAME,
+ TRUE, { NULL }, TRUE },
-static svn_error_t *
-start_getdrev(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name,
- const char **attrs)
-{
- drev_context_t *drev_ctx = userData;
- drev_state_e state;
-
- state = parser->state->current_state;
-
- if (state == NONE &&
- strcmp(name.name, SVN_DAV__VERSION_NAME) == 0)
- {
- push_state(parser, drev_ctx, VERSION_NAME);
- }
-
- return SVN_NO_ERROR;
-}
+ { 0 }
+};
+
+/* Conforms to svn_ra_serf__xml_closed_t */
static svn_error_t *
-end_getdrev(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name)
+getdrev_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)
{
- drev_context_t *drev_ctx = userData;
- drev_state_e state;
- svn_string_t *info;
+ drev_context_t *drev_ctx = baton;
- state = parser->state->current_state;
- info = parser->state->private;
+ SVN_ERR_ASSERT(leaving_state == VERSION_NAME);
+ SVN_ERR_ASSERT(cdata != NULL);
- if (state == VERSION_NAME &&
- strcmp(name.name, SVN_DAV__VERSION_NAME) == 0 &&
- info)
- {
- *drev_ctx->revision_deleted = SVN_STR_TO_REV(info->data);
- svn_ra_serf__xml_pop_state(parser);
- }
+ *drev_ctx->revision_deleted = SVN_STR_TO_REV(cdata->data);
return SVN_NO_ERROR;
}
-static svn_error_t *
-cdata_getdrev(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- const char *data,
- apr_size_t len)
-{
- drev_context_t *drev_ctx = userData;
- drev_state_e state;
-
- UNUSED_CTX(drev_ctx);
-
- state = parser->state->current_state;
- switch (state)
- {
- case VERSION_NAME:
- parser->state->private = svn_string_ncreate(data, len,
- parser->state->pool);
- break;
- default:
- break;
- }
-
- return SVN_NO_ERROR;
-}
/* Implements svn_ra_serf__request_body_delegate_t */
static svn_error_t *
@@ -186,54 +135,44 @@ svn_ra_serf__get_deleted_rev(svn_ra_session_t *session,
drev_context_t *drev_ctx;
svn_ra_serf__session_t *ras = session->priv;
svn_ra_serf__handler_t *handler;
- svn_ra_serf__xml_parser_t *parser_ctx;
- const char *relative_url, *basecoll_url, *req_url;
- int status_code = 0;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url;
svn_error_t *err;
drev_ctx = apr_pcalloc(pool, sizeof(*drev_ctx));
drev_ctx->path = path;
drev_ctx->peg_revision = peg_revision;
drev_ctx->end_revision = end_revision;
- drev_ctx->pool = pool;
drev_ctx->revision_deleted = revision_deleted;
- drev_ctx->done = FALSE;
-
- SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url,
- ras, NULL, NULL, peg_revision, NULL,
- pool));
- req_url = svn_path_url_add_component2(basecoll_url, relative_url, pool);
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ ras, NULL /* conn */,
+ NULL /* url */, peg_revision,
+ pool, pool));
- parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
- parser_ctx->pool = pool;
- parser_ctx->user_data = drev_ctx;
- parser_ctx->start = start_getdrev;
- parser_ctx->end = end_getdrev;
- parser_ctx->cdata = cdata_getdrev;
- parser_ctx->done = &drev_ctx->done;
- parser_ctx->status_code = &status_code;
+ xmlctx = svn_ra_serf__xml_context_create(getdrev_ttable,
+ NULL, getdrev_closed, NULL,
+ drev_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
- handler = apr_pcalloc(pool, sizeof(*handler));
handler->method = "REPORT";
handler->path = req_url;
handler->body_type = "text/xml";
- handler->response_handler = svn_ra_serf__handle_xml_parser;
handler->body_delegate = create_getdrev_body;
handler->body_delegate_baton = drev_ctx;
handler->conn = ras->conns[0];
handler->session = ras;
- handler->response_baton = parser_ctx;
- svn_ra_serf__request_create(handler);
-
- err = svn_ra_serf__context_run_wait(&drev_ctx->done, ras, pool);
+ err = svn_ra_serf__context_run_one(handler, pool);
/* Map status 501: Method Not Implemented to our not implemented error.
1.5.x servers and older don't support this report. */
- if (status_code == 501)
+ if (handler->sline.code == 501)
return svn_error_createf(SVN_ERR_RA_NOT_IMPLEMENTED, err,
- _("'%s' REPORT not implemented"), "get-deleted-rev");
+ _("'%s' REPORT not implemented"),
+ "get-deleted-rev");
SVN_ERR(err);
+
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_ra_serf/getdate.c b/subversion/libsvn_ra_serf/getdate.c
index 3899ccf..cc1014e 100644
--- a/subversion/libsvn_ra_serf/getdate.c
+++ b/subversion/libsvn_ra_serf/getdate.c
@@ -24,9 +24,6 @@
#include <apr_uri.h>
-
-#include <expat.h>
-
#include <serf.h>
#include "svn_pools.h"
@@ -46,120 +43,54 @@
/*
* This enum represents the current state of our XML parsing for a REPORT.
*/
-typedef enum date_state_e {
- NONE = 0,
+enum date_state_e {
+ INITIAL = 0,
+ REPORT,
VERSION_NAME
-} date_state_e;
+};
-typedef struct date_info_t {
- /* The currently collected value as we build it up */
- const char *tmp;
- apr_size_t tmp_len;
-} date_info_t;
typedef struct date_context_t {
- apr_pool_t *pool;
-
/* The time asked about. */
apr_time_t time;
/* What was the youngest revision at that time? */
svn_revnum_t *revision;
- /* are we done? */
- svn_boolean_t done;
-
} date_context_t;
-
-static date_info_t *
-push_state(svn_ra_serf__xml_parser_t *parser,
- date_context_t *date_ctx,
- date_state_e state)
-{
- svn_ra_serf__xml_push_state(parser, state);
-
- if (state == VERSION_NAME)
- {
- date_info_t *info;
-
- info = apr_pcalloc(parser->state->pool, sizeof(*info));
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t date_ttable[] = {
+ { INITIAL, S_, "dated-rev-report", REPORT,
+ FALSE, { NULL }, FALSE },
- parser->state->private = info;
- }
+ { REPORT, D_, SVN_DAV__VERSION_NAME, VERSION_NAME,
+ TRUE, { NULL }, TRUE },
- return parser->state->private;
-}
-
-static svn_error_t *
-start_getdate(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name,
- const char **attrs)
-{
- date_context_t *date_ctx = userData;
- date_state_e state;
+ { 0 }
+};
- state = parser->state->current_state;
-
- if (state == NONE &&
- strcmp(name.name, SVN_DAV__VERSION_NAME) == 0)
- {
- push_state(parser, date_ctx, VERSION_NAME);
- }
-
- return SVN_NO_ERROR;
-}
+/* Conforms to svn_ra_serf__xml_closed_t */
static svn_error_t *
-end_getdate(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name)
+date_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)
{
- date_context_t *date_ctx = userData;
- date_state_e state;
- date_info_t *info;
+ date_context_t *date_ctx = baton;
- state = parser->state->current_state;
- info = parser->state->private;
+ SVN_ERR_ASSERT(leaving_state == VERSION_NAME);
+ SVN_ERR_ASSERT(cdata != NULL);
- if (state == VERSION_NAME &&
- strcmp(name.name, SVN_DAV__VERSION_NAME) == 0)
- {
- *date_ctx->revision = SVN_STR_TO_REV(info->tmp);
- svn_ra_serf__xml_pop_state(parser);
- }
+ *date_ctx->revision = SVN_STR_TO_REV(cdata->data);
return SVN_NO_ERROR;
}
-static svn_error_t *
-cdata_getdate(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- const char *data,
- apr_size_t len)
-{
- date_context_t *date_ctx = userData;
- date_state_e state;
- date_info_t *info;
-
- UNUSED_CTX(date_ctx);
-
- state = parser->state->current_state;
- info = parser->state->private;
-
- switch (state)
- {
- case VERSION_NAME:
- svn_ra_serf__expand_string(&info->tmp, &info->tmp_len,
- data, len, parser->state->pool);
- break;
- default:
- break;
- }
-
- return SVN_NO_ERROR;
-}
/* Implements svn_ra_serf__request_body_delegate_t */
static svn_error_t *
@@ -198,19 +129,21 @@ svn_ra_serf__get_dated_revision(svn_ra_session_t *ra_session,
date_context_t *date_ctx;
svn_ra_serf__session_t *session = ra_session->priv;
svn_ra_serf__handler_t *handler;
- svn_ra_serf__xml_parser_t *parser_ctx;
+ svn_ra_serf__xml_context_t *xmlctx;
const char *report_target;
- int status_code;
+ svn_error_t *err;
- date_ctx = apr_pcalloc(pool, sizeof(*date_ctx));
- date_ctx->pool = pool;
+ date_ctx = apr_palloc(pool, sizeof(*date_ctx));
date_ctx->time = tm;
date_ctx->revision = revision;
- date_ctx->done = FALSE;
SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool));
- handler = apr_pcalloc(pool, sizeof(*handler));
+ xmlctx = svn_ra_serf__xml_context_create(date_ttable,
+ NULL, date_closed, NULL,
+ date_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
handler->method = "REPORT";
handler->path = report_target;
@@ -218,25 +151,20 @@ svn_ra_serf__get_dated_revision(svn_ra_session_t *ra_session,
handler->conn = session->conns[0];
handler->session = session;
- parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
-
- parser_ctx->pool = pool;
- parser_ctx->user_data = date_ctx;
- parser_ctx->start = start_getdate;
- parser_ctx->end = end_getdate;
- parser_ctx->cdata = cdata_getdate;
- parser_ctx->done = &date_ctx->done;
- parser_ctx->status_code = &status_code;
-
handler->body_delegate = create_getdate_body;
handler->body_delegate_baton = date_ctx;
- handler->response_handler = svn_ra_serf__handle_xml_parser;
- handler->response_baton = parser_ctx;
+ *date_ctx->revision = SVN_INVALID_REVNUM;
- svn_ra_serf__request_create(handler);
+ err = svn_ra_serf__context_run_one(handler, pool);
- *date_ctx->revision = SVN_INVALID_REVNUM;
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline,
+ report_target,
+ handler->location),
+ err));
+
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(*revision));
- return svn_ra_serf__context_run_wait(&date_ctx->done, session, pool);
+ return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_ra_serf/getlocations.c b/subversion/libsvn_ra_serf/getlocations.c
index 6d3fa97..1ae4f82 100644
--- a/subversion/libsvn_ra_serf/getlocations.c
+++ b/subversion/libsvn_ra_serf/getlocations.c
@@ -27,6 +27,7 @@
#include <serf.h>
+#include "svn_hash.h"
#include "svn_path.h"
#include "svn_pools.h"
#include "svn_ra.h"
@@ -41,18 +42,11 @@
/*
* This enum represents the current state of our XML parsing for a REPORT.
*/
-typedef enum loc_state_e {
+enum loc_state_e {
+ INITIAL = 0,
REPORT,
LOCATION
-} loc_state_e;
-
-typedef struct loc_state_list_t {
- /* The current state that we are in now. */
- loc_state_e state;
-
- /* The previous state we were in. */
- struct loc_state_list_t *prev;
-} loc_state_list_t;
+};
typedef struct loc_context_t {
/* pool to allocate memory from */
@@ -66,115 +60,50 @@ typedef struct loc_context_t {
/* Returned location hash */
apr_hash_t *paths;
- /* Current state we're in */
- loc_state_list_t *state;
- loc_state_list_t *free_state;
-
- int status_code;
-
- svn_boolean_t done;
} loc_context_t;
-
-static void
-push_state(loc_context_t *loc_ctx, loc_state_e state)
-{
- loc_state_list_t *new_state;
-
- if (!loc_ctx->free_state)
- {
- new_state = apr_palloc(loc_ctx->pool, sizeof(*loc_ctx->state));
- }
- else
- {
- new_state = loc_ctx->free_state;
- loc_ctx->free_state = loc_ctx->free_state->prev;
- }
- new_state->state = state;
-
- /* Add it to the state chain. */
- new_state->prev = loc_ctx->state;
- loc_ctx->state = new_state;
-}
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t getloc_ttable[] = {
+ { INITIAL, S_, "get-locations-report", REPORT,
+ FALSE, { NULL }, FALSE },
-static void pop_state(loc_context_t *loc_ctx)
-{
- loc_state_list_t *free_state;
- free_state = loc_ctx->state;
- /* advance the current state */
- loc_ctx->state = loc_ctx->state->prev;
- free_state->prev = loc_ctx->free_state;
- loc_ctx->free_state = free_state;
-}
+ { REPORT, S_, "location", LOCATION,
+ FALSE, { "?rev", "?path", NULL }, TRUE },
-static svn_error_t *
-start_getloc(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name,
- const char **attrs)
-{
- loc_context_t *loc_ctx = userData;
-
- if (!loc_ctx->state && strcmp(name.name, "get-locations-report") == 0)
- {
- push_state(loc_ctx, REPORT);
- }
- else if (loc_ctx->state &&
- loc_ctx->state->state == REPORT &&
- strcmp(name.name, "location") == 0)
- {
- svn_revnum_t rev = SVN_INVALID_REVNUM;
- const char *revstr, *path;
-
- revstr = svn_xml_get_attr_value("rev", attrs);
- if (revstr)
- {
- rev = SVN_STR_TO_REV(revstr);
- }
-
- path = svn_xml_get_attr_value("path", attrs);
-
- if (SVN_IS_VALID_REVNUM(rev) && path)
- {
- apr_hash_set(loc_ctx->paths,
- apr_pmemdup(loc_ctx->pool, &rev, sizeof(rev)),
- sizeof(rev),
- apr_pstrdup(loc_ctx->pool, path));
- }
- }
-
- return SVN_NO_ERROR;
-}
+ { 0 }
+};
+
+/* Conforms to svn_ra_serf__xml_closed_t */
static svn_error_t *
-end_getloc(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name)
+getloc_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)
{
- loc_context_t *loc_ctx = userData;
- loc_state_list_t *cur_state;
-
- if (!loc_ctx->state)
- {
- return SVN_NO_ERROR;
- }
+ loc_context_t *loc_ctx = baton;
+ const char *revstr;
+ const char *path;
- cur_state = loc_ctx->state;
+ SVN_ERR_ASSERT(leaving_state == LOCATION);
- if (cur_state->state == REPORT &&
- strcmp(name.name, "get-locations-report") == 0)
- {
- pop_state(loc_ctx);
- }
- else if (cur_state->state == LOCATION &&
- strcmp(name.name, "location") == 0)
+ revstr = svn_hash_gets(attrs, "rev");
+ path = svn_hash_gets(attrs, "path");
+ if (revstr != NULL && path != NULL)
{
- pop_state(loc_ctx);
+ svn_revnum_t rev = SVN_STR_TO_REV(revstr);
+ apr_hash_set(loc_ctx->paths,
+ apr_pmemdup(loc_ctx->pool, &rev, sizeof(rev)), sizeof(rev),
+ apr_pstrdup(loc_ctx->pool, path));
}
return SVN_NO_ERROR;
}
+
/* Implements svn_ra_serf__request_body_delegate_t */
static svn_error_t *
create_get_locations_body(serf_bucket_t **body_bkt,
@@ -228,8 +157,8 @@ svn_ra_serf__get_locations(svn_ra_session_t *ra_session,
loc_context_t *loc_ctx;
svn_ra_serf__session_t *session = ra_session->priv;
svn_ra_serf__handler_t *handler;
- svn_ra_serf__xml_parser_t *parser_ctx;
- const char *relative_url, *basecoll_url, *req_url;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url;
svn_error_t *err;
loc_ctx = apr_pcalloc(pool, sizeof(*loc_ctx));
@@ -237,18 +166,20 @@ svn_ra_serf__get_locations(svn_ra_session_t *ra_session,
loc_ctx->path = path;
loc_ctx->peg_revision = peg_revision;
loc_ctx->location_revisions = location_revisions;
- loc_ctx->done = FALSE;
loc_ctx->paths = apr_hash_make(loc_ctx->pool);
*locations = loc_ctx->paths;
- SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url, session,
- NULL, NULL, peg_revision, NULL,
- pool));
-
- req_url = svn_path_url_add_component2(basecoll_url, relative_url, pool);
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, peg_revision,
+ pool, pool));
- handler = apr_pcalloc(pool, sizeof(*handler));
+ xmlctx = svn_ra_serf__xml_context_create(getloc_ttable,
+ NULL, getloc_closed, NULL,
+ loc_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
handler->method = "REPORT";
handler->path = req_url;
@@ -258,26 +189,12 @@ svn_ra_serf__get_locations(svn_ra_session_t *ra_session,
handler->conn = session->conns[0];
handler->session = session;
- parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
-
- parser_ctx->pool = pool;
- parser_ctx->user_data = loc_ctx;
- parser_ctx->start = start_getloc;
- parser_ctx->end = end_getloc;
- parser_ctx->status_code = &loc_ctx->status_code;
- parser_ctx->done = &loc_ctx->done;
-
- handler->response_handler = svn_ra_serf__handle_xml_parser;
- handler->response_baton = parser_ctx;
-
- svn_ra_serf__request_create(handler);
-
- err = svn_ra_serf__context_run_wait(&loc_ctx->done, session, pool);
+ err = svn_ra_serf__context_run_one(handler, pool);
SVN_ERR(svn_error_compose_create(
- svn_ra_serf__error_on_status(loc_ctx->status_code,
+ svn_ra_serf__error_on_status(handler->sline,
req_url,
- parser_ctx->location),
+ handler->location),
err));
return SVN_NO_ERROR;
diff --git a/subversion/libsvn_ra_serf/getlocationsegments.c b/subversion/libsvn_ra_serf/getlocationsegments.c
index 5f2179d..e5c58a9 100644
--- a/subversion/libsvn_ra_serf/getlocationsegments.c
+++ b/subversion/libsvn_ra_serf/getlocationsegments.c
@@ -25,9 +25,9 @@
#include <apr_uri.h>
-#include <expat.h>
#include <serf.h>
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_ra.h"
#include "svn_xml.h"
@@ -50,82 +50,60 @@ typedef struct gls_context_t {
svn_location_segment_receiver_t receiver;
void *receiver_baton;
- /* subpool used only as long as a single receiver invocation */
- apr_pool_t *subpool;
+} gls_context_t;
- /* True iff we're looking at a child of the outer report tag */
- svn_boolean_t inside_report;
+enum {
+ INITIAL = 0,
+ REPORT,
+ SEGMENT
+};
- int status_code;
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t gls_ttable[] = {
+ { INITIAL, S_, "get-location-segments-report", REPORT,
+ FALSE, { NULL }, FALSE },
- svn_boolean_t done;
-} gls_context_t;
+ { REPORT, S_, "location-segment", SEGMENT,
+ FALSE, { "?path", "range-start", "range-end", NULL }, TRUE },
+
+ { 0 }
+};
+/* Conforms to svn_ra_serf__xml_closed_t */
static svn_error_t *
-start_gls(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name,
- const char **attrs)
+gls_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)
{
- gls_context_t *gls_ctx = userData;
-
- if ((! gls_ctx->inside_report)
- && strcmp(name.name, "get-location-segments-report") == 0)
- {
- gls_ctx->inside_report = TRUE;
- }
- else if (gls_ctx->inside_report
- && strcmp(name.name, "location-segment") == 0)
- {
- const char *rev_str;
- svn_revnum_t range_start = SVN_INVALID_REVNUM;
- svn_revnum_t range_end = SVN_INVALID_REVNUM;
- const char *path = NULL;
-
- path = svn_xml_get_attr_value("path", attrs);
- rev_str = svn_xml_get_attr_value("range-start", attrs);
- if (rev_str)
- range_start = SVN_STR_TO_REV(rev_str);
- rev_str = svn_xml_get_attr_value("range-end", attrs);
- if (rev_str)
- range_end = SVN_STR_TO_REV(rev_str);
-
- if (SVN_IS_VALID_REVNUM(range_start) && SVN_IS_VALID_REVNUM(range_end))
- {
- svn_location_segment_t *segment = apr_pcalloc(gls_ctx->subpool,
- sizeof(*segment));
- segment->path = path;
- segment->range_start = range_start;
- segment->range_end = range_end;
- SVN_ERR(gls_ctx->receiver(segment,
- gls_ctx->receiver_baton,
- gls_ctx->subpool));
- svn_pool_clear(gls_ctx->subpool);
- }
- else
- {
- return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Expected valid revision range"));
- }
- }
+ gls_context_t *gls_ctx = baton;
+ const char *path;
+ const char *start_str;
+ const char *end_str;
+ svn_location_segment_t segment;
- return SVN_NO_ERROR;
-}
+ SVN_ERR_ASSERT(leaving_state == SEGMENT);
-static svn_error_t *
-end_gls(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name)
-{
- gls_context_t *gls_ctx = userData;
+ path = svn_hash_gets(attrs, "path");
+ start_str = svn_hash_gets(attrs, "range-start");
+ end_str = svn_hash_gets(attrs, "range-end");
- if (strcmp(name.name, "get-location-segments-report") == 0)
- gls_ctx->inside_report = FALSE;
+ /* The transition table said these must exist. */
+ SVN_ERR_ASSERT(start_str && end_str);
+
+ segment.path = path; /* may be NULL */
+ segment.range_start = SVN_STR_TO_REV(start_str);
+ segment.range_end = SVN_STR_TO_REV(end_str);
+ SVN_ERR(gls_ctx->receiver(&segment, gls_ctx->receiver_baton, scratch_pool));
return SVN_NO_ERROR;
}
+
/* Implements svn_ra_serf__request_body_delegate_t */
static svn_error_t *
create_gls_body(serf_bucket_t **body_bkt,
@@ -182,9 +160,9 @@ svn_ra_serf__get_location_segments(svn_ra_session_t *ra_session,
gls_context_t *gls_ctx;
svn_ra_serf__session_t *session = ra_session->priv;
svn_ra_serf__handler_t *handler;
- svn_ra_serf__xml_parser_t *parser_ctx;
- const char *relative_url, *basecoll_url, *req_url;
- svn_error_t *err, *err2;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url;
+ svn_error_t *err;
gls_ctx = apr_pcalloc(pool, sizeof(*gls_ctx));
gls_ctx->path = path;
@@ -193,16 +171,17 @@ svn_ra_serf__get_location_segments(svn_ra_session_t *ra_session,
gls_ctx->end_rev = end_rev;
gls_ctx->receiver = receiver;
gls_ctx->receiver_baton = receiver_baton;
- gls_ctx->subpool = svn_pool_create(pool);
- gls_ctx->inside_report = FALSE;
- gls_ctx->done = FALSE;
-
- SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url, session,
- NULL, NULL, peg_revision, NULL, pool));
- req_url = svn_path_url_add_component2(basecoll_url, relative_url, pool);
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, peg_revision,
+ pool, pool));
- handler = apr_pcalloc(pool, sizeof(*handler));
+ xmlctx = svn_ra_serf__xml_context_create(gls_ttable,
+ NULL, gls_closed, NULL,
+ gls_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
handler->method = "REPORT";
handler->path = req_url;
@@ -212,41 +191,16 @@ svn_ra_serf__get_location_segments(svn_ra_session_t *ra_session,
handler->conn = session->conns[0];
handler->session = session;
- parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
-
- parser_ctx->pool = pool;
- parser_ctx->user_data = gls_ctx;
- parser_ctx->start = start_gls;
- parser_ctx->end = end_gls;
- parser_ctx->status_code = &gls_ctx->status_code;
- parser_ctx->done = &gls_ctx->done;
-
- handler->response_handler = svn_ra_serf__handle_xml_parser;
- handler->response_baton = parser_ctx;
+ err = svn_ra_serf__context_run_one(handler, pool);
- svn_ra_serf__request_create(handler);
-
- err = svn_ra_serf__context_run_wait(&gls_ctx->done, session, pool);
-
- if (gls_ctx->inside_report)
- err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, err,
- _("Location segment report failed on '%s'@'%ld'"),
- path, peg_revision);
-
- err2 = svn_ra_serf__error_on_status(gls_ctx->status_code,
+ err = svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline,
handler->path,
- parser_ctx->location);
- if (err2)
- {
- /* Prefer err2 to err. */
- svn_error_clear(err);
- return err2;
- }
-
- svn_pool_destroy(gls_ctx->subpool);
+ handler->location),
+ err);
if (err && (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE))
return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, NULL);
- return err;
+ return svn_error_trace(err);
}
diff --git a/subversion/libsvn_ra_serf/getlocks.c b/subversion/libsvn_ra_serf/getlocks.c
index 719e4f0..df201a7 100644
--- a/subversion/libsvn_ra_serf/getlocks.c
+++ b/subversion/libsvn_ra_serf/getlocks.c
@@ -27,6 +27,7 @@
#include <serf.h>
+#include "svn_hash.h"
#include "svn_path.h"
#include "svn_pools.h"
#include "svn_ra.h"
@@ -46,8 +47,8 @@
/*
* This enum represents the current state of our XML parsing for a REPORT.
*/
-typedef enum lock_state_e {
- NONE = 0,
+enum {
+ INITIAL = 0,
REPORT,
LOCK,
PATH,
@@ -56,19 +57,7 @@ typedef enum lock_state_e {
COMMENT,
CREATION_DATE,
EXPIRATION_DATE
-} lock_state_e;
-
-typedef struct lock_info_t {
- /* Temporary pool */
- apr_pool_t *pool;
-
- svn_lock_t *lock;
-
- /* The currently collected value as we build it up */
- const char *tmp;
- apr_size_t tmp_len;
-
-} lock_info_t;
+};
typedef struct lock_context_t {
apr_pool_t *pool;
@@ -80,107 +69,56 @@ typedef struct lock_context_t {
/* return hash */
apr_hash_t *hash;
- /* are we done? */
- svn_boolean_t done;
-
} lock_context_t;
-
-static lock_info_t *
-push_state(svn_ra_serf__xml_parser_t *parser,
- lock_context_t *lock_ctx,
- lock_state_e state)
-{
- svn_ra_serf__xml_push_state(parser, state);
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t getlocks_ttable[] = {
+ { INITIAL, S_, "get-locks-report", REPORT,
+ FALSE, { NULL }, FALSE },
- if (state == LOCK)
- {
- lock_info_t *info;
+ { REPORT, S_, "lock", LOCK,
+ FALSE, { NULL }, TRUE },
- info = apr_pcalloc(parser->state->pool, sizeof(*info));
+ { LOCK, S_, "path", PATH,
+ TRUE, { NULL }, TRUE },
- info->pool = lock_ctx->pool;
- info->lock = svn_lock_create(lock_ctx->pool);
- info->lock->path =
+ { LOCK, S_, "token", TOKEN,
+ TRUE, { NULL }, TRUE },
- parser->state->private = info;
- }
+ { LOCK, S_, "owner", OWNER,
+ TRUE, { NULL }, TRUE },
- return parser->state->private;
-}
+ { LOCK, S_, "comment", COMMENT,
+ TRUE, { NULL }, TRUE },
-static svn_error_t *
-start_getlocks(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name,
- const char **attrs)
-{
- lock_context_t *lock_ctx = userData;
- lock_state_e state;
+ { LOCK, S_, SVN_DAV__CREATIONDATE, CREATION_DATE,
+ TRUE, { NULL }, TRUE },
- state = parser->state->current_state;
+ { LOCK, S_, "expirationdate", EXPIRATION_DATE,
+ TRUE, { NULL }, TRUE },
- if (state == NONE &&
- strcmp(name.name, "get-locks-report") == 0)
- {
- push_state(parser, lock_ctx, REPORT);
- }
- else if (state == REPORT &&
- strcmp(name.name, "lock") == 0)
- {
- push_state(parser, lock_ctx, LOCK);
- }
- else if (state == LOCK)
- {
- if (strcmp(name.name, "path") == 0)
- {
- push_state(parser, lock_ctx, PATH);
- }
- else if (strcmp(name.name, "token") == 0)
- {
- push_state(parser, lock_ctx, TOKEN);
- }
- else if (strcmp(name.name, "owner") == 0)
- {
- push_state(parser, lock_ctx, OWNER);
- }
- else if (strcmp(name.name, "comment") == 0)
- {
- push_state(parser, lock_ctx, COMMENT);
- }
- else if (strcmp(name.name, SVN_DAV__CREATIONDATE) == 0)
- {
- push_state(parser, lock_ctx, CREATION_DATE);
- }
- else if (strcmp(name.name, "expirationdate") == 0)
- {
- push_state(parser, lock_ctx, EXPIRATION_DATE);
- }
- }
-
- return SVN_NO_ERROR;
-}
+ { 0 }
+};
+
+/* Conforms to svn_ra_serf__xml_closed_t */
static svn_error_t *
-end_getlocks(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name)
+getlocks_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)
{
- lock_context_t *lock_ctx = userData;
- lock_state_e state;
- lock_info_t *info;
-
- state = parser->state->current_state;
- info = parser->state->private;
+ lock_context_t *lock_ctx = baton;
- if (state == REPORT &&
- strcmp(name.name, "get-locks-report") == 0)
- {
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == LOCK &&
- strcmp(name.name, "lock") == 0)
+ if (leaving_state == LOCK)
{
+ const char *path = svn_hash_gets(attrs, "path");
+ const char *token = svn_hash_gets(attrs, "token");
+ svn_boolean_t save_lock = FALSE;
+
/* Filter out unwanted paths. Since Subversion only allows
locks on files, we can treat depth=immediates the same as
depth=files for filtering purposes. Meaning, we'll keep
@@ -191,106 +129,84 @@ end_getlocks(svn_ra_serf__xml_parser_t *parser,
c) we've asked for depth=files or depth=immediates, and this
lock is on an immediate child of our query path.
*/
- if ((strcmp(lock_ctx->path, info->lock->path) == 0)
- || (lock_ctx->requested_depth == svn_depth_infinity))
+ if (! token)
{
- apr_hash_set(lock_ctx->hash, info->lock->path,
- APR_HASH_KEY_STRING, info->lock);
+ /* A lock without a token is not a lock; just an answer that there
+ is no lock on the node. */
+ save_lock = FALSE;
}
- else if ((lock_ctx->requested_depth == svn_depth_files) ||
- (lock_ctx->requested_depth == svn_depth_immediates))
+ if (strcmp(lock_ctx->path, path) == 0
+ || lock_ctx->requested_depth == svn_depth_infinity)
{
- const char *rel_path = svn_fspath__is_child(lock_ctx->path,
- info->lock->path,
- info->pool);
- if (rel_path && (svn_path_component_count(rel_path) == 1))
- apr_hash_set(lock_ctx->hash, info->lock->path,
- APR_HASH_KEY_STRING, info->lock);
+ save_lock = TRUE;
+ }
+ else if (lock_ctx->requested_depth == svn_depth_files
+ || lock_ctx->requested_depth == svn_depth_immediates)
+ {
+ const char *relpath = svn_fspath__skip_ancestor(lock_ctx->path,
+ path);
+ if (relpath && (svn_path_component_count(relpath) == 1))
+ save_lock = TRUE;
}
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == PATH &&
- strcmp(name.name, "path") == 0)
- {
- info->lock->path = apr_pstrmemdup(info->pool, info->tmp, info->tmp_len);
- info->tmp_len = 0;
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == TOKEN &&
- strcmp(name.name, "token") == 0)
- {
- info->lock->token = apr_pstrmemdup(info->pool, info->tmp, info->tmp_len);
- info->tmp_len = 0;
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == OWNER &&
- strcmp(name.name, "owner") == 0)
- {
- info->lock->owner = apr_pstrmemdup(info->pool, info->tmp, info->tmp_len);
- info->tmp_len = 0;
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == COMMENT &&
- strcmp(name.name, "comment") == 0)
- {
- info->lock->comment = apr_pstrmemdup(info->pool,
- info->tmp, info->tmp_len);
- info->tmp_len = 0;
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == CREATION_DATE &&
- strcmp(name.name, SVN_DAV__CREATIONDATE) == 0)
- {
- SVN_ERR(svn_time_from_cstring(&info->lock->creation_date,
- info->tmp, info->pool));
- info->tmp_len = 0;
- svn_ra_serf__xml_pop_state(parser);
+ if (save_lock)
+ {
+ /* We get to put the structure on the stack rather than using
+ svn_lock_create(). Bwahahaha.... */
+ svn_lock_t lock = { 0 };
+ const char *date;
+ svn_lock_t *result_lock;
+
+ /* Note: these "attributes" came from child elements. Some of
+ them may have not been sent, so the value will be NULL. */
+
+ lock.path = path;
+ lock.token = token;
+ lock.owner = svn_hash_gets(attrs, "owner");
+ lock.comment = svn_hash_gets(attrs, "comment");
+
+ date = svn_hash_gets(attrs, SVN_DAV__CREATIONDATE);
+ if (date)
+ SVN_ERR(svn_time_from_cstring(&lock.creation_date, date,
+ scratch_pool));
+
+ date = svn_hash_gets(attrs, "expirationdate");
+ if (date)
+ SVN_ERR(svn_time_from_cstring(&lock.expiration_date, date,
+ scratch_pool));
+
+ result_lock = svn_lock_dup(&lock, lock_ctx->pool);
+ svn_hash_sets(lock_ctx->hash, result_lock->path, result_lock);
+ }
}
- else if (state == EXPIRATION_DATE &&
- strcmp(name.name, "expirationdate") == 0)
+ else
{
- SVN_ERR(svn_time_from_cstring(&info->lock->expiration_date,
- info->tmp, info->pool));
- info->tmp_len = 0;
- svn_ra_serf__xml_pop_state(parser);
+ const char *name;
+
+ SVN_ERR_ASSERT(cdata != NULL);
+
+ if (leaving_state == PATH)
+ name = "path";
+ else if (leaving_state == TOKEN)
+ name = "token";
+ else if (leaving_state == OWNER)
+ name = "owner";
+ else if (leaving_state == COMMENT)
+ name = "comment";
+ else if (leaving_state == CREATION_DATE)
+ name = SVN_DAV__CREATIONDATE;
+ else if (leaving_state == EXPIRATION_DATE)
+ name = "expirationdate";
+ else
+ SVN_ERR_MALFUNCTION();
+
+ /* Store the lock information onto the LOCK elemstate. */
+ svn_ra_serf__xml_note(xes, LOCK, name, cdata->data);
}
return SVN_NO_ERROR;
}
-static svn_error_t *
-cdata_getlocks(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- const char *data,
- apr_size_t len)
-{
- lock_context_t *lock_ctx = userData;
- lock_state_e state;
- lock_info_t *info;
-
- UNUSED_CTX(lock_ctx);
-
- state = parser->state->current_state;
- info = parser->state->private;
-
- switch (state)
- {
- case PATH:
- case TOKEN:
- case OWNER:
- case COMMENT:
- case CREATION_DATE:
- case EXPIRATION_DATE:
- svn_ra_serf__expand_string(&info->tmp, &info->tmp_len,
- data, len, parser->state->pool);
- break;
- default:
- break;
- }
-
- return SVN_NO_ERROR;
-}
/* Implements svn_ra_serf__request_body_delegate_t */
static svn_error_t *
@@ -323,9 +239,9 @@ svn_ra_serf__get_locks(svn_ra_session_t *ra_session,
lock_context_t *lock_ctx;
svn_ra_serf__session_t *session = ra_session->priv;
svn_ra_serf__handler_t *handler;
- svn_ra_serf__xml_parser_t *parser_ctx;
+ svn_ra_serf__xml_context_t *xmlctx;
const char *req_url, *rel_path;
- int status_code;
+ svn_error_t *err;
req_url = svn_path_url_add_component2(session->session_url.path, path, pool);
SVN_ERR(svn_ra_serf__get_relative_path(&rel_path, req_url, session,
@@ -336,9 +252,12 @@ svn_ra_serf__get_locks(svn_ra_session_t *ra_session,
lock_ctx->path = apr_pstrcat(pool, "/", rel_path, (char *)NULL);
lock_ctx->requested_depth = depth;
lock_ctx->hash = apr_hash_make(pool);
- lock_ctx->done = FALSE;
- handler = apr_pcalloc(pool, sizeof(*handler));
+ xmlctx = svn_ra_serf__xml_context_create(getlocks_ttable,
+ NULL, getlocks_closed, NULL,
+ lock_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
handler->method = "REPORT";
handler->path = req_url;
@@ -346,25 +265,26 @@ svn_ra_serf__get_locks(svn_ra_session_t *ra_session,
handler->conn = session->conns[0];
handler->session = session;
- parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
-
- parser_ctx->pool = pool;
- parser_ctx->user_data = lock_ctx;
- parser_ctx->start = start_getlocks;
- parser_ctx->end = end_getlocks;
- parser_ctx->cdata = cdata_getlocks;
- parser_ctx->done = &lock_ctx->done;
- parser_ctx->status_code = &status_code;
-
handler->body_delegate = create_getlocks_body;
handler->body_delegate_baton = lock_ctx;
- handler->response_handler = svn_ra_serf__handle_xml_parser;
- handler->response_baton = parser_ctx;
-
- svn_ra_serf__request_create(handler);
-
- SVN_ERR(svn_ra_serf__context_run_wait(&lock_ctx->done, session, pool));
+ err = svn_ra_serf__context_run_one(handler, pool);
+
+ /* Wrap the server generated error for an unsupported report with the
+ documented error for this ra function. */
+ if (svn_error_find_cause(err, SVN_ERR_UNSUPPORTED_FEATURE))
+ err = svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, NULL);
+
+ SVN_ERR(err);
+
+ /* We get a 404 when a path doesn't exist in HEAD, but it might
+ have existed earlier (E.g. 'svn ls http://s/svn/trunk/file@1' */
+ if (handler->sline.code != 404)
+ {
+ SVN_ERR(svn_ra_serf__error_on_status(handler->sline,
+ handler->path,
+ handler->location));
+ }
*locks = lock_ctx->hash;
diff --git a/subversion/libsvn_ra_serf/inherited_props.c b/subversion/libsvn_ra_serf/inherited_props.c
new file mode 100644
index 0000000..f1172e7
--- /dev/null
+++ b/subversion/libsvn_ra_serf/inherited_props.c
@@ -0,0 +1,285 @@
+/*
+ * inherited_props.c : ra_serf implementation of svn_ra_get_inherited_props
+ *
+ * ====================================================================
+ * 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 <apr_tables.h>
+#include <apr_xml.h>
+
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_ra.h"
+#include "svn_string.h"
+#include "svn_xml.h"
+#include "svn_props.h"
+#include "svn_base64.h"
+
+#include "private/svn_dav_protocol.h"
+#include "../libsvn_ra/ra_loader.h"
+#include "svn_private_config.h"
+#include "ra_serf.h"
+
+
+/* The current state of our XML parsing. */
+typedef enum iprops_state_e {
+ INITIAL = 0,
+ IPROPS_REPORT,
+ IPROPS_ITEM,
+ IPROPS_PATH,
+ IPROPS_PROPNAME,
+ IPROPS_PROPVAL
+} iprops_state_e;
+
+/* Struct for accumulating inherited props. */
+typedef struct iprops_context_t {
+ /* The depth-first ordered array of svn_prop_inherited_item_t *
+ structures we are building. */
+ apr_array_header_t *iprops;
+
+ /* Pool in which to allocate elements of IPROPS. */
+ apr_pool_t *pool;
+
+ /* The repository's root URL. */
+ const char *repos_root_url;
+
+ /* Current property name */
+ svn_stringbuf_t *curr_propname;
+
+ /* Current element in IPROPS. */
+ svn_prop_inherited_item_t *curr_iprop;
+
+ /* Path we are finding inherited properties for. This is relative to
+ the RA session passed to svn_ra_serf__get_inherited_props. */
+ const char *path;
+ /* The revision of PATH*/
+ svn_revnum_t revision;
+} iprops_context_t;
+
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t iprops_table[] = {
+ { INITIAL, S_, SVN_DAV__INHERITED_PROPS_REPORT, IPROPS_REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { IPROPS_REPORT, S_, SVN_DAV__IPROP_ITEM, IPROPS_ITEM,
+ FALSE, { NULL }, TRUE },
+
+ { IPROPS_ITEM, S_, SVN_DAV__IPROP_PATH, IPROPS_PATH,
+ TRUE, { NULL }, TRUE },
+
+ { IPROPS_ITEM, S_, SVN_DAV__IPROP_PROPNAME, IPROPS_PROPNAME,
+ TRUE, { NULL }, TRUE },
+
+ { IPROPS_ITEM, S_, SVN_DAV__IPROP_PROPVAL, IPROPS_PROPVAL,
+ TRUE, { "?V:encoding", NULL }, TRUE },
+
+ { 0 }
+};
+
+/* Conforms to svn_ra_serf__xml_opened_t */
+static svn_error_t *
+iprops_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)
+{
+ iprops_context_t *iprops_ctx = baton;
+
+ if (entered_state == IPROPS_ITEM)
+ {
+ svn_stringbuf_setempty(iprops_ctx->curr_propname);
+
+ iprops_ctx->curr_iprop = apr_pcalloc(iprops_ctx->pool,
+ sizeof(*iprops_ctx->curr_iprop));
+
+ iprops_ctx->curr_iprop->prop_hash = apr_hash_make(iprops_ctx->pool);
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+iprops_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)
+{
+ iprops_context_t *iprops_ctx = baton;
+
+ if (leaving_state == IPROPS_ITEM)
+ {
+ APR_ARRAY_PUSH(iprops_ctx->iprops, svn_prop_inherited_item_t *) =
+ iprops_ctx->curr_iprop;
+
+ iprops_ctx->curr_iprop = NULL;
+ }
+ else if (leaving_state == IPROPS_PATH)
+ {
+ /* Every <iprop-item> has a single <iprop-path> */
+ if (iprops_ctx->curr_iprop->path_or_url)
+ return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
+
+ iprops_ctx->curr_iprop->path_or_url =
+ svn_path_url_add_component2(iprops_ctx->repos_root_url,
+ cdata->data,
+ iprops_ctx->pool);
+ }
+ else if (leaving_state == IPROPS_PROPNAME)
+ {
+ if (iprops_ctx->curr_propname->len)
+ return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
+
+ /* Store propname for value */
+ svn_stringbuf_set(iprops_ctx->curr_propname, cdata->data);
+ }
+ else if (leaving_state == IPROPS_PROPVAL)
+ {
+ const char *encoding;
+ const svn_string_t *val_str;
+
+ if (! iprops_ctx->curr_propname->len)
+ return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
+
+ encoding = svn_hash_gets(attrs, "V:encoding");
+
+ if (encoding)
+ {
+ if (strcmp(encoding, "base64") != 0)
+ return svn_error_createf(SVN_ERR_XML_MALFORMED,
+ NULL,
+ _("Got unrecognized encoding '%s'"),
+ encoding);
+
+ /* Decode into the right pool. */
+ val_str = svn_base64_decode_string(cdata, iprops_ctx->pool);
+ }
+ else
+ {
+ /* Copy into the right pool. */
+ val_str = svn_string_dup(cdata, iprops_ctx->pool);
+ }
+
+ svn_hash_sets(iprops_ctx->curr_iprop->prop_hash,
+ apr_pstrdup(iprops_ctx->pool,
+ iprops_ctx->curr_propname->data),
+ val_str);
+ /* Clear current propname. */
+ svn_stringbuf_setempty(iprops_ctx->curr_propname);
+ }
+ else
+ SVN_ERR_MALFUNCTION(); /* Invalid transition table */
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+create_iprops_body(serf_bucket_t **bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ iprops_context_t *iprops_ctx = baton;
+ serf_bucket_t *body_bkt;
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
+ "S:" SVN_DAV__INHERITED_PROPS_REPORT,
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+ svn_ra_serf__add_tag_buckets(body_bkt,
+ "S:" SVN_DAV__REVISION,
+ apr_ltoa(pool, iprops_ctx->revision),
+ alloc);
+ svn_ra_serf__add_tag_buckets(body_bkt, "S:" SVN_DAV__PATH,
+ iprops_ctx->path, alloc);
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
+ "S:" SVN_DAV__INHERITED_PROPS_REPORT);
+ *bkt = body_bkt;
+ return SVN_NO_ERROR;
+}
+
+/* Request a inherited-props-report from the URL attached to RA_SESSION,
+ and fill the IPROPS array hash with the results. */
+svn_error_t *
+svn_ra_serf__get_inherited_props(svn_ra_session_t *ra_session,
+ apr_array_header_t **iprops,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ iprops_context_t *iprops_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url;
+
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url,
+ NULL /* latest_revnum */,
+ session,
+ NULL /* conn */,
+ NULL /* url */,
+ revision,
+ result_pool, scratch_pool));
+
+ SVN_ERR_ASSERT(session->repos_root_str);
+
+ iprops_ctx = apr_pcalloc(scratch_pool, sizeof(*iprops_ctx));
+ iprops_ctx->repos_root_url = session->repos_root_str;
+ iprops_ctx->pool = result_pool;
+ iprops_ctx->curr_propname = svn_stringbuf_create_empty(scratch_pool);
+ iprops_ctx->curr_iprop = NULL;
+ iprops_ctx->iprops = apr_array_make(result_pool, 1,
+ sizeof(svn_prop_inherited_item_t *));
+ iprops_ctx->path = path;
+ iprops_ctx->revision = revision;
+
+ xmlctx = svn_ra_serf__xml_context_create(iprops_table,
+ iprops_opened, iprops_closed, NULL,
+ iprops_ctx,
+ scratch_pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, scratch_pool);
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->conn = session->conns[0];
+ handler->session = session;
+ handler->body_delegate = create_iprops_body;
+ handler->body_delegate_baton = iprops_ctx;
+ handler->body_type = "text/xml";
+ handler->handler_pool = scratch_pool;
+
+ err = svn_ra_serf__context_run_one(handler, scratch_pool);
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline,
+ handler->path,
+ handler->location),
+ err));
+
+ *iprops = iprops_ctx->iprops;
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra_serf/locks.c b/subversion/libsvn_ra_serf/locks.c
index 2aaf2fd..252c301 100644
--- a/subversion/libsvn_ra_serf/locks.c
+++ b/subversion/libsvn_ra_serf/locks.c
@@ -24,9 +24,6 @@
#include <apr_uri.h>
-
-#include <expat.h>
-
#include <serf.h>
#include "svn_dav.h"
@@ -45,8 +42,11 @@
/*
* This enum represents the current state of our XML parsing for a REPORT.
*/
-typedef enum lock_state_e {
- NONE = 0,
+enum {
+ INITIAL = 0,
+ MULTISTATUS,
+ RESPONSE,
+ PROPSTAT,
PROP,
LOCK_DISCOVERY,
ACTIVE_LOCK,
@@ -55,13 +55,9 @@ typedef enum lock_state_e {
DEPTH,
TIMEOUT,
LOCK_TOKEN,
- COMMENT
-} lock_state_e;
-
-typedef struct lock_prop_info_t {
- const char *data;
- apr_size_t len;
-} lock_prop_info_t;
+ OWNER,
+ HREF
+};
typedef struct lock_info_t {
apr_pool_t *pool;
@@ -75,249 +71,128 @@ typedef struct lock_info_t {
svn_boolean_t read_headers;
- /* Our HTTP status code and reason. */
- int status_code;
- const char *reason;
+ svn_ra_serf__handler_t *handler;
- /* The currently collected value as we build it up */
- const char *tmp;
- apr_size_t tmp_len;
+ /* The expat handler. We wrap this to do a bit more work. */
+ svn_ra_serf__response_handler_t inner_handler;
+ void *inner_baton;
- /* are we done? */
- svn_boolean_t done;
} lock_info_t;
-
-static lock_prop_info_t*
-push_state(svn_ra_serf__xml_parser_t *parser,
- lock_info_t *lock_ctx,
- lock_state_e state)
-{
- svn_ra_serf__xml_push_state(parser, state);
- switch (state)
- {
- case LOCK_TYPE:
- case LOCK_SCOPE:
- case DEPTH:
- case TIMEOUT:
- case LOCK_TOKEN:
- case COMMENT:
- parser->state->private = apr_pcalloc(parser->state->pool,
- sizeof(lock_prop_info_t));
- break;
- default:
- break;
- }
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t locks_ttable[] = {
+ /* The INITIAL state can transition into D:prop (LOCK) or
+ to D:multistatus (PROPFIND) */
+ { INITIAL, D_, "prop", PROP,
+ FALSE, { NULL }, FALSE },
+ { INITIAL, D_, "multistatus", MULTISTATUS,
+ FALSE, { NULL }, FALSE },
- return parser->state->private;
-}
+ { MULTISTATUS, D_, "response", RESPONSE,
+ FALSE, { NULL }, FALSE },
-/*
- * Expat callback invoked on a start element tag for a PROPFIND response.
- */
-static svn_error_t *
-start_lock(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name,
- const char **attrs)
-{
- lock_info_t *ctx = userData;
- lock_state_e state;
+ { RESPONSE, D_, "propstat", PROPSTAT,
+ FALSE, { NULL }, FALSE },
- state = parser->state->current_state;
+ { PROPSTAT, D_, "prop", PROP,
+ FALSE, { NULL }, FALSE },
- if (state == NONE && strcmp(name.name, "prop") == 0)
- {
- svn_ra_serf__xml_push_state(parser, PROP);
- }
- else if (state == PROP &&
- strcmp(name.name, "lockdiscovery") == 0)
- {
- push_state(parser, ctx, LOCK_DISCOVERY);
- }
- else if (state == LOCK_DISCOVERY &&
- strcmp(name.name, "activelock") == 0)
- {
- push_state(parser, ctx, ACTIVE_LOCK);
- }
- else if (state == ACTIVE_LOCK)
- {
- if (strcmp(name.name, "locktype") == 0)
- {
- push_state(parser, ctx, LOCK_TYPE);
- }
- else if (strcmp(name.name, "lockscope") == 0)
- {
- push_state(parser, ctx, LOCK_SCOPE);
- }
- else if (strcmp(name.name, "depth") == 0)
- {
- push_state(parser, ctx, DEPTH);
- }
- else if (strcmp(name.name, "timeout") == 0)
- {
- push_state(parser, ctx, TIMEOUT);
- }
- else if (strcmp(name.name, "locktoken") == 0)
- {
- push_state(parser, ctx, LOCK_TOKEN);
- }
- else if (strcmp(name.name, "owner") == 0)
- {
- push_state(parser, ctx, COMMENT);
- }
- }
- else if (state == LOCK_TYPE)
- {
- if (strcmp(name.name, "write") == 0)
- {
- /* Do nothing. */
- }
- else
- {
- SVN_ERR_MALFUNCTION();
- }
- }
- else if (state == LOCK_SCOPE)
- {
- if (strcmp(name.name, "exclusive") == 0)
- {
- /* Do nothing. */
- }
- else
- {
- SVN_ERR_MALFUNCTION();
- }
- }
+ { PROP, D_, "lockdiscovery", LOCK_DISCOVERY,
+ FALSE, { NULL }, FALSE },
- return SVN_NO_ERROR;
-}
+ { LOCK_DISCOVERY, D_, "activelock", ACTIVE_LOCK,
+ FALSE, { NULL }, FALSE },
-/*
- * Expat callback invoked on an end element tag for a PROPFIND response.
- */
+#if 0
+ /* ### we don't really need to parse locktype/lockscope. we know what
+ ### the values are going to be. we *could* validate that the only
+ ### possible children are D:write and D:exclusive. we'd need to
+ ### modify the state transition to tell us about all children
+ ### (ie. maybe support "*" for the name) and then validate. but it
+ ### just isn't important to validate, so disable this for now... */
+
+ { ACTIVE_LOCK, D_, "locktype", LOCK_TYPE,
+ FALSE, { NULL }, FALSE },
+
+ { LOCK_TYPE, D_, "write", WRITE,
+ FALSE, { NULL }, TRUE },
+
+ { ACTIVE_LOCK, D_, "lockscope", LOCK_SCOPE,
+ FALSE, { NULL }, FALSE },
+
+ { LOCK_SCOPE, D_, "exclusive", EXCLUSIVE,
+ FALSE, { NULL }, TRUE },
+#endif /* 0 */
+
+ { ACTIVE_LOCK, D_, "timeout", TIMEOUT,
+ TRUE, { NULL }, TRUE },
+
+ { ACTIVE_LOCK, D_, "locktoken", LOCK_TOKEN,
+ FALSE, { NULL }, FALSE },
+
+ { LOCK_TOKEN, D_, "href", HREF,
+ TRUE, { NULL }, TRUE },
+
+ { ACTIVE_LOCK, D_, "owner", OWNER,
+ TRUE, { NULL }, TRUE },
+
+ /* ACTIVE_LOCK has a D:depth child, but we can ignore that. */
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
static svn_error_t *
-end_lock(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name)
+locks_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)
{
- lock_info_t *ctx = userData;
- lock_state_e state;
-
- state = parser->state->current_state;
+ lock_info_t *lock_ctx = baton;
- if (state == PROP &&
- strcmp(name.name, "prop") == 0)
- {
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == LOCK_DISCOVERY &&
- strcmp(name.name, "lockdiscovery") == 0)
- {
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == ACTIVE_LOCK &&
- strcmp(name.name, "activelock") == 0)
- {
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == LOCK_TYPE &&
- strcmp(name.name, "locktype") == 0)
- {
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == LOCK_SCOPE &&
- strcmp(name.name, "lockscope") == 0)
- {
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == DEPTH &&
- strcmp(name.name, "depth") == 0)
+ if (leaving_state == TIMEOUT)
{
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == TIMEOUT &&
- strcmp(name.name, "timeout") == 0)
- {
- lock_prop_info_t *info = parser->state->private;
-
- if (strcmp(info->data, "Infinite") == 0)
+ if (strcasecmp(cdata->data, "Infinite") == 0)
+ lock_ctx->lock->expiration_date = 0;
+ else if (strncasecmp(cdata->data, "Second-", 7) == 0)
{
- ctx->lock->expiration_date = 0;
+ unsigned n;
+ SVN_ERR(svn_cstring_atoui(&n, cdata->data+7));
+
+ lock_ctx->lock->expiration_date = apr_time_now() +
+ apr_time_from_sec(n);
}
else
- {
- SVN_ERR(svn_time_from_cstring(&ctx->lock->creation_date,
- info->data, ctx->pool));
- }
- svn_ra_serf__xml_pop_state(parser);
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Invalid LOCK timeout value '%s'"),
+ cdata->data);
}
- else if (state == LOCK_TOKEN &&
- strcmp(name.name, "locktoken") == 0)
+ else if (leaving_state == HREF)
{
- lock_prop_info_t *info = parser->state->private;
-
- if (!ctx->lock->token && info->len)
+ if (cdata->len)
{
- apr_collapse_spaces((char*)info->data, info->data);
- ctx->lock->token = apr_pstrndup(ctx->pool, info->data, info->len);
+ char *buf = apr_pstrmemdup(lock_ctx->pool, cdata->data, cdata->len);
+
+ apr_collapse_spaces(buf, buf);
+ lock_ctx->lock->token = buf;
}
- /* We don't actually need the lock token. */
- svn_ra_serf__xml_pop_state(parser);
}
- else if (state == COMMENT &&
- strcmp(name.name, "owner") == 0)
+ else if (leaving_state == OWNER)
{
- lock_prop_info_t *info = parser->state->private;
-
- if (info->len)
+ if (cdata->len)
{
- ctx->lock->comment = apr_pstrndup(ctx->pool, info->data, info->len);
+ lock_ctx->lock->comment = apr_pstrmemdup(lock_ctx->pool,
+ cdata->data, cdata->len);
}
- svn_ra_serf__xml_pop_state(parser);
- }
-
- return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-cdata_lock(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- const char *data,
- apr_size_t len)
-{
- lock_info_t *lock_ctx = userData;
- lock_state_e state;
- lock_prop_info_t *info;
-
- UNUSED_CTX(lock_ctx);
-
- state = parser->state->current_state;
- info = parser->state->private;
-
- switch (state)
- {
- case LOCK_TYPE:
- case LOCK_SCOPE:
- case DEPTH:
- case TIMEOUT:
- case LOCK_TOKEN:
- case COMMENT:
- svn_ra_serf__expand_string(&info->data, &info->len,
- data, len, parser->state->pool);
- break;
- default:
- break;
}
return SVN_NO_ERROR;
}
-static const svn_ra_serf__dav_props_t lock_props[] =
-{
- { "DAV:", "lockdiscovery" },
- { NULL }
-};
static svn_error_t *
set_lock_headers(serf_bucket_t *headers,
@@ -341,6 +216,39 @@ set_lock_headers(serf_bucket_t *headers,
return APR_SUCCESS;
}
+
+/* Register an error within the session. If something is already there,
+ then it will take precedence. */
+static svn_error_t *
+determine_error(svn_ra_serf__handler_t *handler,
+ svn_error_t *err)
+{
+ {
+ apr_status_t errcode;
+
+ if (handler->sline.code == 423)
+ errcode = SVN_ERR_FS_PATH_ALREADY_LOCKED;
+ else if (handler->sline.code == 403)
+ errcode = SVN_ERR_RA_DAV_FORBIDDEN;
+ else
+ return err;
+
+ /* Client-side or server-side error already. Return it. */
+ if (err != NULL)
+ return err;
+
+ /* The server did not send us a detailed human-readable error.
+ Provide a generic error. */
+ err = svn_error_createf(errcode, NULL,
+ _("Lock request failed: %d %s"),
+ handler->sline.code,
+ handler->sline.reason);
+ }
+
+ return err;
+}
+
+
/* Implements svn_ra_serf__response_handler_t */
static svn_error_t *
handle_lock(serf_request_t *request,
@@ -348,43 +256,25 @@ handle_lock(serf_request_t *request,
void *handler_baton,
apr_pool_t *pool)
{
- svn_ra_serf__xml_parser_t *xml_ctx = handler_baton;
- lock_info_t *ctx = xml_ctx->user_data;
- svn_error_t *err;
+ lock_info_t *ctx = handler_baton;
- if (ctx->read_headers == FALSE)
+ /* 403 (Forbidden) when a lock doesn't exist.
+ 423 (Locked) when a lock already exists. */
+ if (ctx->handler->sline.code == 403
+ || ctx->handler->sline.code == 423)
+ {
+ /* Go look in the body for a server-provided error. This will
+ reset flags for the core handler to Do The Right Thing. We
+ won't be back to this handler again. */
+ return svn_error_trace(svn_ra_serf__expect_empty_body(
+ request, response, ctx->handler, pool));
+ }
+
+ if (!ctx->read_headers)
{
serf_bucket_t *headers;
const char *val;
- serf_status_line sl;
- apr_status_t status;
-
- status = serf_bucket_response_status(response, &sl);
- if (SERF_BUCKET_READ_ERROR(status))
- {
- return svn_error_wrap_apr(status, NULL);
- }
-
- ctx->status_code = sl.code;
- ctx->reason = sl.reason;
-
- /* 423 == Locked */
- if (sl.code == 423)
- {
- /* Older servers may not give a descriptive error, so we'll
- make one of our own if we can't find one in the response. */
- err = svn_ra_serf__handle_server_error(request, response, pool);
- if (!err)
- {
- err = svn_error_createf(SVN_ERR_FS_PATH_ALREADY_LOCKED,
- NULL,
- _("Lock request failed: %d %s"),
- ctx->status_code, ctx->reason);
- }
- return err;
- }
-
headers = serf_bucket_response_get_headers(response);
val = serf_bucket_headers_get(headers, SVN_DAV_LOCK_OWNER_HEADER);
@@ -403,25 +293,7 @@ handle_lock(serf_request_t *request,
ctx->read_headers = TRUE;
}
- /* Forbidden when a lock doesn't exist. */
- if (ctx->status_code == 403)
- {
- /* If we get an "unexpected EOF" error, we'll wrap it with
- generic request failure error. */
- err = svn_ra_serf__handle_discard_body(request, response, NULL, pool);
- if (err && APR_STATUS_IS_EOF(err->apr_err))
- {
- ctx->done = TRUE;
- err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED,
- err,
- _("Lock request failed: %d %s"),
- ctx->status_code, ctx->reason);
- }
- return err;
- }
-
- return svn_ra_serf__handle_xml_parser(request, response,
- handler_baton, pool);
+ return ctx->inner_handler(request, response, ctx->inner_baton, pool);
}
/* Implements svn_ra_serf__request_body_delegate_t */
@@ -453,7 +325,7 @@ setup_getlock_headers(serf_bucket_t *headers,
void *baton,
apr_pool_t *pool)
{
- serf_bucket_headers_set(headers, "Depth", "0");
+ serf_bucket_headers_setn(headers, "Depth", "0");
return SVN_NO_ERROR;
}
@@ -499,26 +371,30 @@ svn_error_t *
svn_ra_serf__get_lock(svn_ra_session_t *ra_session,
svn_lock_t **lock,
const char *path,
- apr_pool_t *pool)
+ apr_pool_t *result_pool)
{
svn_ra_serf__session_t *session = ra_session->priv;
svn_ra_serf__handler_t *handler;
- svn_ra_serf__xml_parser_t *parser_ctx;
+ svn_ra_serf__xml_context_t *xmlctx;
+ apr_pool_t *scratch_pool = svn_pool_create(result_pool);
lock_info_t *lock_ctx;
const char *req_url;
svn_error_t *err;
- int status_code;
- req_url = svn_path_url_add_component2(session->session_url.path, path, pool);
+ req_url = svn_path_url_add_component2(session->session_url.path, path,
+ scratch_pool);
- lock_ctx = apr_pcalloc(pool, sizeof(*lock_ctx));
-
- lock_ctx->pool = pool;
+ lock_ctx = apr_pcalloc(scratch_pool, sizeof(*lock_ctx));
+ lock_ctx->pool = result_pool;
lock_ctx->path = req_url;
- lock_ctx->lock = svn_lock_create(pool);
- lock_ctx->lock->path = path;
+ lock_ctx->lock = svn_lock_create(result_pool);
+ lock_ctx->lock->path = apr_pstrdup(result_pool, path);
- handler = apr_pcalloc(pool, sizeof(*handler));
+ xmlctx = svn_ra_serf__xml_context_create(locks_ttable,
+ NULL, locks_closed, NULL,
+ lock_ctx,
+ scratch_pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, scratch_pool);
handler->method = "PROPFIND";
handler->path = req_url;
@@ -526,29 +402,23 @@ svn_ra_serf__get_lock(svn_ra_session_t *ra_session,
handler->conn = session->conns[0];
handler->session = session;
- parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
-
- parser_ctx->pool = pool;
- parser_ctx->user_data = lock_ctx;
- parser_ctx->start = start_lock;
- parser_ctx->end = end_lock;
- parser_ctx->cdata = cdata_lock;
- parser_ctx->done = &lock_ctx->done;
- parser_ctx->status_code = &status_code;
-
handler->body_delegate = create_getlock_body;
handler->body_delegate_baton = lock_ctx;
handler->header_delegate = setup_getlock_headers;
handler->header_delegate_baton = lock_ctx;
+ lock_ctx->inner_handler = handler->response_handler;
+ lock_ctx->inner_baton = handler->response_baton;
handler->response_handler = handle_lock;
- handler->response_baton = parser_ctx;
+ handler->response_baton = lock_ctx;
+
+ lock_ctx->handler = handler;
- svn_ra_serf__request_create(handler);
- err = svn_ra_serf__context_run_wait(&lock_ctx->done, session, pool);
+ err = svn_ra_serf__context_run_one(handler, scratch_pool);
+ err = determine_error(handler, err);
- if (status_code == 404)
+ if (handler->sline.code == 404)
{
return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, err,
_("Malformed URL for repository"));
@@ -560,7 +430,12 @@ svn_ra_serf__get_lock(svn_ra_session_t *ra_session,
_("Server does not support locking features"));
}
- *lock = lock_ctx->lock;
+ if (lock_ctx->lock && lock_ctx->lock->token)
+ *lock = lock_ctx->lock;
+ else
+ *lock = NULL;
+
+ svn_pool_destroy(scratch_pool);
return SVN_NO_ERROR;
}
@@ -572,42 +447,50 @@ svn_ra_serf__lock(svn_ra_session_t *ra_session,
svn_boolean_t force,
svn_ra_lock_callback_t lock_func,
void *lock_baton,
- apr_pool_t *pool)
+ apr_pool_t *scratch_pool)
{
svn_ra_serf__session_t *session = ra_session->priv;
apr_hash_index_t *hi;
- apr_pool_t *subpool;
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(scratch_pool);
- subpool = svn_pool_create(pool);
+ /* ### TODO for issue 2263: Send all the locks over the wire at once. This
+ ### loop is just a temporary shim.
+ ### an alternative, which is backwards-compat with all servers is to
+ ### pipeline these requests. ie. stop using run_wait/run_one. */
- for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
+ for (hi = apr_hash_first(scratch_pool, path_revs);
+ hi;
+ hi = apr_hash_next(hi))
{
svn_ra_serf__handler_t *handler;
- svn_ra_serf__xml_parser_t *parser_ctx;
+ svn_ra_serf__xml_context_t *xmlctx;
const char *req_url;
lock_info_t *lock_ctx;
- const void *key;
- void *val;
svn_error_t *err;
svn_error_t *new_err = NULL;
- svn_pool_clear(subpool);
+ svn_pool_clear(iterpool);
- lock_ctx = apr_pcalloc(subpool, sizeof(*lock_ctx));
+ lock_ctx = apr_pcalloc(iterpool, sizeof(*lock_ctx));
- apr_hash_this(hi, &key, NULL, &val);
- lock_ctx->pool = subpool;
- lock_ctx->path = key;
- lock_ctx->revision = *((svn_revnum_t*)val);
- lock_ctx->lock = svn_lock_create(subpool);
- lock_ctx->lock->path = key;
+ lock_ctx->pool = iterpool;
+ lock_ctx->path = svn__apr_hash_index_key(hi);
+ lock_ctx->revision = *((svn_revnum_t*)svn__apr_hash_index_val(hi));
+ lock_ctx->lock = svn_lock_create(iterpool);
+ lock_ctx->lock->path = lock_ctx->path;
lock_ctx->lock->comment = comment;
lock_ctx->force = force;
req_url = svn_path_url_add_component2(session->session_url.path,
- lock_ctx->path, subpool);
+ lock_ctx->path, iterpool);
- handler = apr_pcalloc(subpool, sizeof(*handler));
+ xmlctx = svn_ra_serf__xml_context_create(locks_ttable,
+ NULL, locks_closed, NULL,
+ lock_ctx,
+ iterpool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, iterpool);
handler->method = "LOCK";
handler->path = req_url;
@@ -615,35 +498,32 @@ svn_ra_serf__lock(svn_ra_session_t *ra_session,
handler->conn = session->conns[0];
handler->session = session;
- parser_ctx = apr_pcalloc(subpool, sizeof(*parser_ctx));
-
- parser_ctx->pool = subpool;
- parser_ctx->user_data = lock_ctx;
- parser_ctx->start = start_lock;
- parser_ctx->end = end_lock;
- parser_ctx->cdata = cdata_lock;
- parser_ctx->done = &lock_ctx->done;
-
handler->header_delegate = set_lock_headers;
handler->header_delegate_baton = lock_ctx;
handler->body_delegate = create_lock_body;
handler->body_delegate_baton = lock_ctx;
+ lock_ctx->inner_handler = handler->response_handler;
+ lock_ctx->inner_baton = handler->response_baton;
handler->response_handler = handle_lock;
- handler->response_baton = parser_ctx;
+ handler->response_baton = lock_ctx;
+
+ lock_ctx->handler = handler;
- svn_ra_serf__request_create(handler);
- err = svn_ra_serf__context_run_wait(&lock_ctx->done, session, subpool);
+ err = svn_ra_serf__context_run_one(handler, iterpool);
+ err = determine_error(handler, err);
if (lock_func)
new_err = lock_func(lock_baton, lock_ctx->path, TRUE, lock_ctx->lock,
- err, subpool);
+ err, iterpool);
svn_error_clear(err);
SVN_ERR(new_err);
}
+ svn_pool_destroy(iterpool);
+
return SVN_NO_ERROR;
}
@@ -675,43 +555,43 @@ svn_ra_serf__unlock(svn_ra_session_t *ra_session,
svn_boolean_t force,
svn_ra_lock_callback_t lock_func,
void *lock_baton,
- apr_pool_t *pool)
+ apr_pool_t *scratch_pool)
{
svn_ra_serf__session_t *session = ra_session->priv;
apr_hash_index_t *hi;
- apr_pool_t *subpool;
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(scratch_pool);
- subpool = svn_pool_create(pool);
+ /* ### TODO for issue 2263: Send all the locks over the wire at once. This
+ ### loop is just a temporary shim.
+ ### an alternative, which is backwards-compat with all servers is to
+ ### pipeline these requests. ie. stop using run_wait/run_one. */
- for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
+ for (hi = apr_hash_first(scratch_pool, path_tokens);
+ hi;
+ hi = apr_hash_next(hi))
{
svn_ra_serf__handler_t *handler;
- svn_ra_serf__simple_request_context_t *ctx;
const char *req_url, *path, *token;
- const void *key;
- void *val;
svn_lock_t *existing_lock = NULL;
struct unlock_context_t unlock_ctx;
- svn_error_t *lock_err = NULL;
+ svn_error_t *err = NULL;
+ svn_error_t *new_err = NULL;
- svn_pool_clear(subpool);
- ctx = apr_pcalloc(subpool, sizeof(*ctx));
- ctx->pool = subpool;
+ svn_pool_clear(iterpool);
- apr_hash_this(hi, &key, NULL, &val);
- path = key;
- token = val;
+ path = svn__apr_hash_index_key(hi);
+ token = svn__apr_hash_index_val(hi);
if (force && (!token || token[0] == '\0'))
{
SVN_ERR(svn_ra_serf__get_lock(ra_session, &existing_lock, path,
- subpool));
- token = existing_lock->token;
+ iterpool));
+ token = existing_lock ? existing_lock->token : NULL;
if (!token)
{
- svn_error_t *err;
-
err = svn_error_createf(SVN_ERR_RA_NOT_LOCKED, NULL,
_("'%s' is not locked in the repository"),
path);
@@ -719,23 +599,31 @@ svn_ra_serf__unlock(svn_ra_session_t *ra_session,
if (lock_func)
{
svn_error_t *err2;
- err2 = lock_func(lock_baton, path, FALSE, NULL, err, subpool);
+ err2 = lock_func(lock_baton, path, FALSE, NULL, err,
+ iterpool);
svn_error_clear(err);
+ err = NULL;
if (err2)
- return err2;
+ return svn_error_trace(err2);
+ }
+ else
+ {
+ svn_error_clear(err);
+ err = NULL;
}
continue;
}
}
unlock_ctx.force = force;
- unlock_ctx.token = apr_pstrcat(subpool, "<", token, ">", (char *)NULL);
+ unlock_ctx.token = apr_pstrcat(iterpool, "<", token, ">", (char *)NULL);
req_url = svn_path_url_add_component2(session->session_url.path, path,
- subpool);
+ iterpool);
- handler = apr_pcalloc(subpool, sizeof(*handler));
+ handler = apr_pcalloc(iterpool, sizeof(*handler));
+ handler->handler_pool = iterpool;
handler->method = "UNLOCK";
handler->path = req_url;
handler->conn = session->conns[0];
@@ -744,35 +632,38 @@ svn_ra_serf__unlock(svn_ra_session_t *ra_session,
handler->header_delegate = set_unlock_headers;
handler->header_delegate_baton = &unlock_ctx;
- handler->response_handler = svn_ra_serf__handle_status_only;
- handler->response_baton = ctx;
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
- svn_ra_serf__request_create(handler);
- SVN_ERR(svn_ra_serf__context_run_wait(&ctx->done, session, subpool));
+ SVN_ERR(svn_ra_serf__context_run_one(handler, iterpool));
- switch (ctx->status)
+ switch (handler->sline.code)
{
case 204:
break; /* OK */
case 403:
/* Api users expect this specific error code to detect failures */
- lock_err = svn_error_createf(SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
- _("Unlock request failed: %d %s"),
- ctx->status, ctx->reason);
+ err = svn_error_createf(SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
+ _("Unlock request failed: %d %s"),
+ handler->sline.code,
+ handler->sline.reason);
break;
default:
- lock_err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
- _("Unlock request failed: %d %s"),
- ctx->status, ctx->reason);
+ err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("Unlock request failed: %d %s"),
+ handler->sline.code,
+ handler->sline.reason);
}
if (lock_func)
- {
- SVN_ERR(lock_func(lock_baton, path, FALSE, existing_lock,
- lock_err, subpool));
- svn_error_clear(lock_err);
- }
+ new_err = lock_func(lock_baton, path, FALSE, existing_lock, err,
+ iterpool);
+
+ svn_error_clear(err);
+ SVN_ERR(new_err);
}
+ svn_pool_destroy(iterpool);
+
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_ra_serf/log.c b/subversion/libsvn_ra_serf/log.c
index cd6a6b2..02f2f29 100644
--- a/subversion/libsvn_ra_serf/log.c
+++ b/subversion/libsvn_ra_serf/log.c
@@ -24,11 +24,9 @@
#include <apr_uri.h>
-
-#include <expat.h>
-
#include <serf.h>
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_ra.h"
#include "svn_dav.h"
@@ -39,6 +37,8 @@
#include "svn_props.h"
#include "private/svn_dav_protocol.h"
+#include "private/svn_string_private.h"
+#include "private/svn_subr_private.h"
#include "svn_private_config.h"
#include "ra_serf.h"
@@ -48,8 +48,8 @@
/*
* This enum represents the current state of our XML parsing for a REPORT.
*/
-typedef enum log_state_e {
- NONE = 0,
+enum {
+ INITIAL = 0,
REPORT,
ITEM,
VERSION,
@@ -63,27 +63,7 @@ typedef enum log_state_e {
DELETED_PATH,
MODIFIED_PATH,
SUBTRACTIVE_MERGE
-} log_state_e;
-
-typedef struct log_info_t {
- apr_pool_t *pool;
-
- /* The currently collected value as we build it up, and its wire
- * encoding (if any).
- */
- const char *tmp;
- apr_size_t tmp_len;
- const char *tmp_encoding;
-
- /* Temporary change path - ultimately inserted into changed_paths hash. */
- svn_log_changed_path2_t *tmp_path;
-
- /* Log information */
- svn_log_entry_t *log_entry;
-
- /* Place to hold revprop name. */
- const char *revprop_name;
-} log_info_t;
+};
typedef struct log_context_t {
apr_pool_t *pool;
@@ -100,9 +80,11 @@ typedef struct log_context_t {
int nest_level; /* used to track mergeinfo nesting levels */
int count; /* only incremented when nest_level == 0 */
- /* are we done? */
- svn_boolean_t done;
- int status_code;
+ /* Collect information for storage into a log entry. Most of the entry
+ members are collected by individual states. revprops and paths are
+ N datapoints per entry. */
+ apr_hash_t *collect_revprops;
+ apr_hash_t *collect_paths;
/* log receiver function and baton */
svn_log_entry_receiver_t receiver;
@@ -114,406 +96,307 @@ typedef struct log_context_t {
svn_boolean_t want_message;
} log_context_t;
-
-static log_info_t *
-push_state(svn_ra_serf__xml_parser_t *parser,
- log_context_t *log_ctx,
- log_state_e state,
- const char **attrs)
-{
- svn_ra_serf__xml_push_state(parser, state);
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t log_ttable[] = {
+ { INITIAL, S_, "log-report", REPORT,
+ FALSE, { NULL }, FALSE },
- if (state == ITEM)
- {
- log_info_t *info;
- apr_pool_t *info_pool = svn_pool_create(parser->state->pool);
+ /* Note that we have an opener here. We need to construct a new LOG_ENTRY
+ to record multiple paths. */
+ { REPORT, S_, "log-item", ITEM,
+ FALSE, { NULL }, TRUE },
- info = apr_pcalloc(info_pool, sizeof(*info));
- info->pool = info_pool;
- info->log_entry = svn_log_entry_create(info_pool);
+ { ITEM, D_, SVN_DAV__VERSION_NAME, VERSION,
+ TRUE, { NULL }, TRUE },
- info->log_entry->revision = SVN_INVALID_REVNUM;
+ { ITEM, D_, "creator-displayname", CREATOR,
+ TRUE, { "?encoding", NULL }, TRUE },
- parser->state->private = info;
- }
+ { ITEM, S_, "date", DATE,
+ TRUE, { "?encoding", NULL }, TRUE },
- if (state == ADDED_PATH || state == REPLACED_PATH ||
- state == DELETED_PATH || state == MODIFIED_PATH)
- {
- log_info_t *info = parser->state->private;
+ { ITEM, D_, "comment", COMMENT,
+ TRUE, { "?encoding", NULL }, TRUE },
- if (!info->log_entry->changed_paths2)
- {
- info->log_entry->changed_paths2 = apr_hash_make(info->pool);
- info->log_entry->changed_paths = info->log_entry->changed_paths2;
- }
+ { ITEM, S_, "revprop", REVPROP,
+ TRUE, { "name", "?encoding", NULL }, TRUE },
- info->tmp_path = svn_log_changed_path2_create(info->pool);
- info->tmp_path->copyfrom_rev = SVN_INVALID_REVNUM;
- }
+ { ITEM, S_, "has-children", HAS_CHILDREN,
+ FALSE, { NULL }, TRUE },
- if (state == CREATOR || state == DATE || state == COMMENT
- || state == REVPROP)
- {
- log_info_t *info = parser->state->private;
+ { ITEM, S_, "subtractive-merge", SUBTRACTIVE_MERGE,
+ FALSE, { NULL }, TRUE },
- info->tmp_encoding = svn_xml_get_attr_value("encoding", attrs);
- if (info->tmp_encoding)
- info->tmp_encoding = apr_pstrdup(info->pool, info->tmp_encoding);
+ { ITEM, S_, "added-path", ADDED_PATH,
+ TRUE, { "?node-kind", "?text-mods", "?prop-mods",
+ "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
- if (!info->log_entry->revprops)
- {
- info->log_entry->revprops = apr_hash_make(info->pool);
- }
- }
+ { ITEM, S_, "replaced-path", REPLACED_PATH,
+ TRUE, { "?node-kind", "?text-mods", "?prop-mods",
+ "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
- return parser->state->private;
-}
+ { ITEM, S_, "deleted-path", DELETED_PATH,
+ TRUE, { "?node-kind", "?text-mods", "?prop-mods", NULL }, TRUE },
-/* Helper function to parse the common arguments availabe in ATTRS into CHANGE. */
-static svn_error_t *
-read_changed_path_attributes(svn_log_changed_path2_t *change, const char **attrs)
-{
- /* All these arguments are optional. The *_from_word() functions can handle
- them for us */
+ { ITEM, S_, "modified-path", MODIFIED_PATH,
+ TRUE, { "?node-kind", "?text-mods", "?prop-mods", NULL }, TRUE },
- change->node_kind = svn_node_kind_from_word(
- svn_xml_get_attr_value("node-kind", attrs));
- change->text_modified = svn_tristate__from_word(
- svn_xml_get_attr_value("text-mods", attrs));
- change->props_modified = svn_tristate__from_word(
- svn_xml_get_attr_value("prop-mods", attrs));
+ { 0 }
+};
- return SVN_NO_ERROR;
-}
+
+/* Store CDATA into REVPROPS, associated with PROPNAME. If ENCODING is not
+ NULL, then it must base "base64" and CDATA will be decoded first.
+ NOTE: PROPNAME must live longer than REVPROPS. */
static svn_error_t *
-start_log(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name,
- const char **attrs)
+collect_revprop(apr_hash_t *revprops,
+ const char *propname,
+ const svn_string_t *cdata,
+ const char *encoding)
{
- log_context_t *log_ctx = userData;
- log_state_e state;
-
- state = parser->state->current_state;
+ apr_pool_t *result_pool = apr_hash_pool_get(revprops);
+ const svn_string_t *decoded;
- if (state == NONE &&
- strcmp(name.name, "log-report") == 0)
+ if (encoding)
{
- push_state(parser, log_ctx, REPORT, attrs);
+ /* Check for a known encoding type. This is easy -- there's
+ only one. */
+ if (strcmp(encoding, "base64") != 0)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Unsupported encoding '%s'"),
+ encoding);
+ }
+
+ decoded = svn_base64_decode_string(cdata, result_pool);
}
- else if (state == REPORT &&
- strcmp(name.name, "log-item") == 0)
+ else
{
- push_state(parser, log_ctx, ITEM, attrs);
+ decoded = svn_string_dup(cdata, result_pool);
}
- else if (state == ITEM)
+
+ /* Caller has ensured PROPNAME has sufficient lifetime. */
+ svn_hash_sets(revprops, propname, decoded);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Record ACTION on the path in CDATA into PATHS. Other properties about
+ the action are pulled from ATTRS. */
+static svn_error_t *
+collect_path(apr_hash_t *paths,
+ char action,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs)
+{
+ apr_pool_t *result_pool = apr_hash_pool_get(paths);
+ svn_log_changed_path2_t *lcp;
+ const char *copyfrom_path;
+ const char *copyfrom_rev;
+ const char *path;
+
+ lcp = svn_log_changed_path2_create(result_pool);
+ lcp->action = action;
+ lcp->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ /* COPYFROM_* are only recorded for ADDED_PATH and REPLACED_PATH. */
+ copyfrom_path = svn_hash_gets(attrs, "copyfrom-path");
+ copyfrom_rev = svn_hash_gets(attrs, "copyfrom-rev");
+ if (copyfrom_path && copyfrom_rev)
{
- log_info_t *info;
+ svn_revnum_t rev = SVN_STR_TO_REV(copyfrom_rev);
- if (strcmp(name.name, SVN_DAV__VERSION_NAME) == 0)
- {
- push_state(parser, log_ctx, VERSION, attrs);
- }
- else if (strcmp(name.name, "creator-displayname") == 0)
- {
- info = push_state(parser, log_ctx, CREATOR, attrs);
- }
- else if (strcmp(name.name, "date") == 0)
- {
- info = push_state(parser, log_ctx, DATE, attrs);
- }
- else if (strcmp(name.name, "comment") == 0)
- {
- info = push_state(parser, log_ctx, COMMENT, attrs);
- }
- else if (strcmp(name.name, "revprop") == 0)
- {
- const char *revprop_name =
- svn_xml_get_attr_value("name", attrs);
- if (revprop_name == NULL)
- return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Missing name attr in revprop element"));
- info = push_state(parser, log_ctx, REVPROP, attrs);
- info->revprop_name = apr_pstrdup(info->pool, revprop_name);
- }
- else if (strcmp(name.name, "has-children") == 0)
- {
- push_state(parser, log_ctx, HAS_CHILDREN, attrs);
- }
- else if (strcmp(name.name, "subtractive-merge") == 0)
- {
- push_state(parser, log_ctx, SUBTRACTIVE_MERGE, attrs);
- }
- else if (strcmp(name.name, "added-path") == 0)
- {
- const char *copy_path, *copy_rev_str;
-
- info = push_state(parser, log_ctx, ADDED_PATH, attrs);
- info->tmp_path->action = 'A';
-
- copy_path = svn_xml_get_attr_value("copyfrom-path", attrs);
- copy_rev_str = svn_xml_get_attr_value("copyfrom-rev", attrs);
- if (copy_path && copy_rev_str)
- {
- svn_revnum_t copy_rev;
-
- copy_rev = SVN_STR_TO_REV(copy_rev_str);
- if (SVN_IS_VALID_REVNUM(copy_rev))
- {
- info->tmp_path->copyfrom_path = apr_pstrdup(info->pool,
- copy_path);
- info->tmp_path->copyfrom_rev = copy_rev;
- }
- }
-
- SVN_ERR(read_changed_path_attributes(info->tmp_path, attrs));
- }
- else if (strcmp(name.name, "replaced-path") == 0)
+ if (SVN_IS_VALID_REVNUM(rev))
{
- const char *copy_path, *copy_rev_str;
-
- info = push_state(parser, log_ctx, REPLACED_PATH, attrs);
- info->tmp_path->action = 'R';
-
- copy_path = svn_xml_get_attr_value("copyfrom-path", attrs);
- copy_rev_str = svn_xml_get_attr_value("copyfrom-rev", attrs);
- if (copy_path && copy_rev_str)
- {
- svn_revnum_t copy_rev;
-
- copy_rev = SVN_STR_TO_REV(copy_rev_str);
- if (SVN_IS_VALID_REVNUM(copy_rev))
- {
- info->tmp_path->copyfrom_path = apr_pstrdup(info->pool,
- copy_path);
- info->tmp_path->copyfrom_rev = copy_rev;
- }
- }
-
- SVN_ERR(read_changed_path_attributes(info->tmp_path, attrs));
+ lcp->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path);
+ lcp->copyfrom_rev = rev;
}
- else if (strcmp(name.name, "deleted-path") == 0)
- {
- info = push_state(parser, log_ctx, DELETED_PATH, attrs);
- info->tmp_path->action = 'D';
+ }
- SVN_ERR(read_changed_path_attributes(info->tmp_path, attrs));
- }
- else if (strcmp(name.name, "modified-path") == 0)
- {
- info = push_state(parser, log_ctx, MODIFIED_PATH, attrs);
- info->tmp_path->action = 'M';
+ lcp->node_kind = svn_node_kind_from_word(svn_hash_gets(attrs, "node-kind"));
+ lcp->text_modified = svn_tristate__from_word(svn_hash_gets(attrs,
+ "text-mods"));
+ lcp->props_modified = svn_tristate__from_word(svn_hash_gets(attrs,
+ "prop-mods"));
- SVN_ERR(read_changed_path_attributes(info->tmp_path, attrs));
- }
- }
+ path = apr_pstrmemdup(result_pool, cdata->data, cdata->len);
+ svn_hash_sets(paths, path, lcp);
return SVN_NO_ERROR;
}
-/*
- * Set *DECODED_CDATA to a copy of current CDATA being tracked in INFO,
- * decoded as necessary, and allocated from INFO->pool..
- */
+
+/* Conforms to svn_ra_serf__xml_opened_t */
static svn_error_t *
-maybe_decode_log_cdata(const svn_string_t **decoded_cdata,
- log_info_t *info)
+log_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)
{
- if (info->tmp_encoding)
- {
- svn_string_t in;
- in.data = info->tmp;
- in.len = info->tmp_len;
-
- /* Check for a known encoding type. This is easy -- there's
- only one. */
- if (strcmp(info->tmp_encoding, "base64") != 0)
- {
- return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Unsupported encoding '%s'"),
- info->tmp_encoding);
- }
+ log_context_t *log_ctx = baton;
- *decoded_cdata = svn_base64_decode_string(&in, info->pool);
- }
- else
+ if (entered_state == ITEM)
{
- *decoded_cdata = svn_string_ncreate(info->tmp, info->tmp_len,
- info->pool);
+ apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
+
+ log_ctx->collect_revprops = apr_hash_make(state_pool);
+ log_ctx->collect_paths = apr_hash_make(state_pool);
}
+
return SVN_NO_ERROR;
}
+
+/* Conforms to svn_ra_serf__xml_closed_t */
static svn_error_t *
-end_log(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name)
+log_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)
{
- log_context_t *log_ctx = userData;
- log_state_e state;
- log_info_t *info;
-
- state = parser->state->current_state;
- info = parser->state->private;
+ log_context_t *log_ctx = baton;
- if (state == REPORT &&
- strcmp(name.name, "log-report") == 0)
- {
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == ITEM &&
- strcmp(name.name, "log-item") == 0)
+ if (leaving_state == ITEM)
{
+ svn_log_entry_t *log_entry;
+ const char *rev_str;
+
if (log_ctx->limit && (log_ctx->nest_level == 0)
&& (++log_ctx->count > log_ctx->limit))
{
return SVN_NO_ERROR;
}
+ log_entry = svn_log_entry_create(scratch_pool);
+
+ /* Pick up the paths from the context. These have the same lifetime
+ as this state. That is long enough for us to pass the paths to
+ the receiver callback. */
+ if (apr_hash_count(log_ctx->collect_paths) > 0)
+ {
+ log_entry->changed_paths = log_ctx->collect_paths;
+ log_entry->changed_paths2 = log_ctx->collect_paths;
+ }
+
+ /* ... and same story for the collected revprops. */
+ log_entry->revprops = log_ctx->collect_revprops;
+
+ log_entry->has_children = svn_hash__get_bool(attrs,
+ "has-children",
+ FALSE);
+ log_entry->subtractive_merge = svn_hash__get_bool(attrs,
+ "subtractive-merge",
+ FALSE);
+
+ rev_str = svn_hash_gets(attrs, "revision");
+ if (rev_str)
+ log_entry->revision = SVN_STR_TO_REV(rev_str);
+ else
+ log_entry->revision = SVN_INVALID_REVNUM;
+
/* Give the info to the reporter */
SVN_ERR(log_ctx->receiver(log_ctx->receiver_baton,
- info->log_entry,
- info->pool));
+ log_entry,
+ scratch_pool));
- if (info->log_entry->has_children)
+ if (log_entry->has_children)
{
log_ctx->nest_level++;
}
- if (! SVN_IS_VALID_REVNUM(info->log_entry->revision))
+ if (! SVN_IS_VALID_REVNUM(log_entry->revision))
{
SVN_ERR_ASSERT(log_ctx->nest_level);
log_ctx->nest_level--;
}
- svn_pool_destroy(info->pool);
- svn_ra_serf__xml_pop_state(parser);
+ /* These hash tables are going to be unusable once this state's
+ pool is destroyed. But let's not leave stale pointers in
+ structures that have a longer life. */
+ log_ctx->collect_revprops = NULL;
+ log_ctx->collect_paths = NULL;
}
- else if (state == VERSION &&
- strcmp(name.name, SVN_DAV__VERSION_NAME) == 0)
+ else if (leaving_state == VERSION)
{
- info->log_entry->revision = SVN_STR_TO_REV(info->tmp);
- info->tmp_len = 0;
- svn_ra_serf__xml_pop_state(parser);
+ svn_ra_serf__xml_note(xes, ITEM, "revision", cdata->data);
}
- else if (state == CREATOR &&
- strcmp(name.name, "creator-displayname") == 0)
+ else if (leaving_state == CREATOR)
{
if (log_ctx->want_author)
{
- const svn_string_t *decoded_cdata;
- SVN_ERR(maybe_decode_log_cdata(&decoded_cdata, info));
- apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
- APR_HASH_KEY_STRING, decoded_cdata);
+ SVN_ERR(collect_revprop(log_ctx->collect_revprops,
+ SVN_PROP_REVISION_AUTHOR,
+ cdata,
+ svn_hash_gets(attrs, "encoding")));
}
- info->tmp_len = 0;
- svn_ra_serf__xml_pop_state(parser);
}
- else if (state == DATE &&
- strcmp(name.name, "date") == 0)
+ else if (leaving_state == DATE)
{
if (log_ctx->want_date)
{
- const svn_string_t *decoded_cdata;
- SVN_ERR(maybe_decode_log_cdata(&decoded_cdata, info));
- apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_DATE,
- APR_HASH_KEY_STRING, decoded_cdata);
+ SVN_ERR(collect_revprop(log_ctx->collect_revprops,
+ SVN_PROP_REVISION_DATE,
+ cdata,
+ svn_hash_gets(attrs, "encoding")));
}
- info->tmp_len = 0;
- svn_ra_serf__xml_pop_state(parser);
}
- else if (state == COMMENT &&
- strcmp(name.name, "comment") == 0)
+ else if (leaving_state == COMMENT)
{
if (log_ctx->want_message)
{
- const svn_string_t *decoded_cdata;
- SVN_ERR(maybe_decode_log_cdata(&decoded_cdata, info));
- apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_LOG,
- APR_HASH_KEY_STRING, decoded_cdata);
+ SVN_ERR(collect_revprop(log_ctx->collect_revprops,
+ SVN_PROP_REVISION_LOG,
+ cdata,
+ svn_hash_gets(attrs, "encoding")));
}
- info->tmp_len = 0;
- svn_ra_serf__xml_pop_state(parser);
}
- else if (state == REVPROP)
+ else if (leaving_state == REVPROP)
{
- const svn_string_t *decoded_cdata;
- SVN_ERR(maybe_decode_log_cdata(&decoded_cdata, info));
- apr_hash_set(info->log_entry->revprops, info->revprop_name,
- APR_HASH_KEY_STRING, decoded_cdata);
- info->tmp_len = 0;
- svn_ra_serf__xml_pop_state(parser);
+ apr_pool_t *result_pool = apr_hash_pool_get(log_ctx->collect_revprops);
+
+ SVN_ERR(collect_revprop(
+ log_ctx->collect_revprops,
+ apr_pstrdup(result_pool,
+ svn_hash_gets(attrs, "name")),
+ cdata,
+ svn_hash_gets(attrs, "encoding")
+ ));
}
- else if (state == HAS_CHILDREN &&
- strcmp(name.name, "has-children") == 0)
+ else if (leaving_state == HAS_CHILDREN)
{
- info->log_entry->has_children = TRUE;
- svn_ra_serf__xml_pop_state(parser);
+ svn_ra_serf__xml_note(xes, ITEM, "has-children", "yes");
}
- else if (state == SUBTRACTIVE_MERGE &&
- strcmp(name.name, "subtractive-merge") == 0)
+ else if (leaving_state == SUBTRACTIVE_MERGE)
{
- info->log_entry->subtractive_merge = TRUE;
- svn_ra_serf__xml_pop_state(parser);
+ svn_ra_serf__xml_note(xes, ITEM, "subtractive-merge", "yes");
}
- else if ((state == ADDED_PATH &&
- strcmp(name.name, "added-path") == 0) ||
- (state == DELETED_PATH &&
- strcmp(name.name, "deleted-path") == 0) ||
- (state == MODIFIED_PATH &&
- strcmp(name.name, "modified-path") == 0) ||
- (state == REPLACED_PATH &&
- strcmp(name.name, "replaced-path") == 0))
+ else
{
- char *path;
-
- path = apr_pstrmemdup(info->pool, info->tmp, info->tmp_len);
- info->tmp_len = 0;
+ char action;
+
+ if (leaving_state == ADDED_PATH)
+ action = 'A';
+ else if (leaving_state == REPLACED_PATH)
+ action = 'R';
+ else if (leaving_state == DELETED_PATH)
+ action = 'D';
+ else
+ {
+ SVN_ERR_ASSERT(leaving_state == MODIFIED_PATH);
+ action = 'M';
+ }
- apr_hash_set(info->log_entry->changed_paths2, path, APR_HASH_KEY_STRING,
- info->tmp_path);
- svn_ra_serf__xml_pop_state(parser);
+ SVN_ERR(collect_path(log_ctx->collect_paths, action, cdata, attrs));
}
return SVN_NO_ERROR;
}
-static svn_error_t *
-cdata_log(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- const char *data,
- apr_size_t len)
-{
- log_context_t *log_ctx = userData;
- log_state_e state;
- log_info_t *info;
-
- UNUSED_CTX(log_ctx);
-
- state = parser->state->current_state;
- info = parser->state->private;
-
- switch (state)
- {
- case VERSION:
- case CREATOR:
- case DATE:
- case COMMENT:
- case REVPROP:
- case ADDED_PATH:
- case REPLACED_PATH:
- case DELETED_PATH:
- case MODIFIED_PATH:
- svn_ra_serf__expand_string(&info->tmp, &info->tmp_len,
- data, len, info->pool);
- break;
- default:
- break;
- }
-
- return SVN_NO_ERROR;
-}
static svn_error_t *
create_log_body(serf_bucket_t **body_bkt,
@@ -632,11 +515,11 @@ svn_ra_serf__get_log(svn_ra_session_t *ra_session,
log_context_t *log_ctx;
svn_ra_serf__session_t *session = ra_session->priv;
svn_ra_serf__handler_t *handler;
- svn_ra_serf__xml_parser_t *parser_ctx;
+ svn_ra_serf__xml_context_t *xmlctx;
svn_boolean_t want_custom_revprops;
svn_revnum_t peg_rev;
svn_error_t *err;
- const char *relative_url, *basecoll_url, *req_url;
+ const char *req_url;
log_ctx = apr_pcalloc(pool, sizeof(*log_ctx));
log_ctx->pool = pool;
@@ -651,7 +534,6 @@ svn_ra_serf__get_log(svn_ra_session_t *ra_session,
log_ctx->include_merged_revisions = include_merged_revisions;
log_ctx->revprops = revprops;
log_ctx->nest_level = 0;
- log_ctx->done = FALSE;
want_custom_revprops = FALSE;
if (revprops)
@@ -689,14 +571,18 @@ svn_ra_serf__get_log(svn_ra_session_t *ra_session,
/* At this point, we may have a deleted file. So, we'll match ra_neon's
* behavior and use the larger of start or end as our 'peg' rev.
*/
- peg_rev = (start > end) ? start : end;
+ peg_rev = (start == SVN_INVALID_REVNUM || start > end) ? start : end;
- SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url, session,
- NULL, NULL, peg_rev, NULL, pool));
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, peg_rev,
+ pool, pool));
- req_url = svn_path_url_add_component2(basecoll_url, relative_url, pool);
-
- handler = apr_pcalloc(pool, sizeof(*handler));
+ xmlctx = svn_ra_serf__xml_context_create(log_ttable,
+ log_opened, log_closed, NULL,
+ log_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
handler->method = "REPORT";
handler->path = req_url;
@@ -706,27 +592,12 @@ svn_ra_serf__get_log(svn_ra_session_t *ra_session,
handler->conn = session->conns[0];
handler->session = session;
- parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
-
- parser_ctx->pool = pool;
- parser_ctx->user_data = log_ctx;
- parser_ctx->start = start_log;
- parser_ctx->end = end_log;
- parser_ctx->cdata = cdata_log;
- parser_ctx->done = &log_ctx->done;
- parser_ctx->status_code = &log_ctx->status_code;
-
- handler->response_handler = svn_ra_serf__handle_xml_parser;
- handler->response_baton = parser_ctx;
-
- svn_ra_serf__request_create(handler);
-
- err = svn_ra_serf__context_run_wait(&log_ctx->done, session, pool);
+ err = svn_ra_serf__context_run_one(handler, pool);
SVN_ERR(svn_error_compose_create(
- svn_ra_serf__error_on_status(log_ctx->status_code,
+ svn_ra_serf__error_on_status(handler->sline,
req_url,
- parser_ctx->location),
+ handler->location),
err));
return SVN_NO_ERROR;
diff --git a/subversion/libsvn_ra_serf/merge.c b/subversion/libsvn_ra_serf/merge.c
index 1d3bd1a..670e421 100644
--- a/subversion/libsvn_ra_serf/merge.c
+++ b/subversion/libsvn_ra_serf/merge.c
@@ -27,6 +27,7 @@
#include <serf.h>
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_ra.h"
#include "svn_dav.h"
@@ -47,7 +48,7 @@
* This enum represents the current state of our XML parsing for a MERGE.
*/
typedef enum merge_state_e {
- NONE = 0,
+ INITIAL = 0,
MERGE_RESPONSE,
UPDATED_SET,
RESPONSE,
@@ -55,41 +56,26 @@ typedef enum merge_state_e {
PROPSTAT,
PROP,
RESOURCE_TYPE,
- AUTHOR,
- NAME,
- DATE,
- IGNORE_PROP_NAME,
- NEED_PROP_NAME,
- PROP_VAL
-} merge_state_e;
-
-typedef enum resource_type_e {
- UNSET,
BASELINE,
COLLECTION,
- CHECKED_IN
-} resource_type_e;
-
-typedef struct merge_info_t {
- /* Temporary allocations here please */
- apr_pool_t *pool;
-
- resource_type_e type;
+ SKIP_HREF,
+ CHECKED_IN,
+ VERSION_NAME,
+ DATE,
+ AUTHOR,
+ POST_COMMIT_ERR,
- apr_hash_t *props;
+ PROP_VAL
+} merge_state_e;
- const char *prop_ns;
- const char *prop_name;
- const char *prop_val;
- apr_size_t prop_val_len;
-} merge_info_t;
/* Structure associated with a MERGE request. */
-struct svn_ra_serf__merge_context_t
+typedef struct merge_context_t
{
apr_pool_t *pool;
svn_ra_serf__session_t *session;
+ svn_ra_serf__handler_t *handler;
apr_hash_t *lock_tokens;
svn_boolean_t keep_locks;
@@ -97,315 +83,192 @@ struct svn_ra_serf__merge_context_t
const char *merge_resource_url; /* URL of resource to be merged. */
const char *merge_url; /* URL at which the MERGE request is aimed. */
- int status;
+ svn_commit_info_t *commit_info;
- svn_boolean_t done;
+} merge_context_t;
- svn_commit_info_t *commit_info;
-};
-
-static merge_info_t *
-push_state(svn_ra_serf__xml_parser_t *parser,
- svn_ra_serf__merge_context_t *ctx,
- merge_state_e state)
-{
- merge_info_t *info;
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t merge_ttable[] = {
+ { INITIAL, D_, "merge-response", MERGE_RESPONSE,
+ FALSE, { NULL }, FALSE },
- svn_ra_serf__xml_push_state(parser, state);
+ { MERGE_RESPONSE, D_, "updated-set", UPDATED_SET,
+ FALSE, { NULL }, FALSE },
- if (state == RESPONSE)
- {
- info = apr_palloc(parser->state->pool, sizeof(*info));
- info->pool = parser->state->pool;
- info->props = apr_hash_make(info->pool);
+ { UPDATED_SET, D_, "response", RESPONSE,
+ FALSE, { NULL }, TRUE },
- parser->state->private = info;
- }
+ { RESPONSE, D_, "href", HREF,
+ TRUE, { NULL }, TRUE },
- return parser->state->private;
-}
+ { RESPONSE, D_, "propstat", PROPSTAT,
+ FALSE, { NULL }, FALSE },
-static svn_error_t *
-start_merge(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name,
- const char **attrs)
-{
- svn_ra_serf__merge_context_t *ctx = userData;
- merge_state_e state;
- merge_info_t *info;
+#if 0
+ /* Not needed. */
+ { PROPSTAT, D_, "status", STATUS,
+ FALSE, { NULL }, FALSE },
+#endif
- state = parser->state->current_state;
+ { PROPSTAT, D_, "prop", PROP,
+ FALSE, { NULL }, FALSE },
- if (state == NONE &&
- strcmp(name.name, "merge-response") == 0)
- {
- push_state(parser, ctx, MERGE_RESPONSE);
- }
- else if (state == NONE)
- {
- /* do nothing as we haven't seen our valid start tag yet. */
- }
- else if (state == MERGE_RESPONSE &&
- strcmp(name.name, "updated-set") == 0)
- {
- push_state(parser, ctx, UPDATED_SET);
- }
- else if (state == UPDATED_SET &&
- strcmp(name.name, "response") == 0)
- {
- push_state(parser, ctx, RESPONSE);
- }
- else if (state == RESPONSE &&
- strcmp(name.name, "href") == 0)
- {
- info = push_state(parser, ctx, PROP_VAL);
+ { PROP, D_, "resourcetype", RESOURCE_TYPE,
+ FALSE, { NULL }, FALSE },
- info->prop_ns = name.namespace;
- info->prop_name = apr_pstrdup(info->pool, name.name);
- info->prop_val = NULL;
- info->prop_val_len = 0;
- }
- else if (state == RESPONSE &&
- strcmp(name.name, "propstat") == 0)
- {
- push_state(parser, ctx, PROPSTAT);
- }
- else if (state == PROPSTAT &&
- strcmp(name.name, "prop") == 0)
- {
- push_state(parser, ctx, PROP);
- }
- else if (state == PROPSTAT &&
- strcmp(name.name, "status") == 0)
- {
- /* Do nothing for now. */
- }
- else if (state == PROP &&
- strcmp(name.name, "resourcetype") == 0)
- {
- info = push_state(parser, ctx, RESOURCE_TYPE);
- info->type = UNSET;
- }
- else if (state == RESOURCE_TYPE &&
- strcmp(name.name, "baseline") == 0)
- {
- info = parser->state->private;
+ { RESOURCE_TYPE, D_, "baseline", BASELINE,
+ FALSE, { NULL }, TRUE },
- info->type = BASELINE;
- }
- else if (state == RESOURCE_TYPE &&
- strcmp(name.name, "collection") == 0)
- {
- info = parser->state->private;
+ { RESOURCE_TYPE, D_, "collection", COLLECTION,
+ FALSE, { NULL }, TRUE },
- info->type = COLLECTION;
- }
- else if (state == PROP &&
- strcmp(name.name, "checked-in") == 0)
- {
- info = push_state(parser, ctx, IGNORE_PROP_NAME);
+ { PROP, D_, "checked-in", SKIP_HREF,
+ FALSE, { NULL }, FALSE },
- info->prop_ns = name.namespace;
- info->prop_name = apr_pstrdup(info->pool, name.name);
- info->prop_val = NULL;
- info->prop_val_len = 0;
- }
- else if (state == PROP)
- {
- push_state(parser, ctx, PROP_VAL);
- }
- else if (state == IGNORE_PROP_NAME)
- {
- push_state(parser, ctx, PROP_VAL);
- }
- else if (state == NEED_PROP_NAME)
- {
- info = push_state(parser, ctx, PROP_VAL);
- info->prop_ns = name.namespace;
- info->prop_name = apr_pstrdup(info->pool, name.name);
- info->prop_val = NULL;
- info->prop_val_len = 0;
- }
- else
- {
- SVN_ERR_MALFUNCTION();
- }
+ { SKIP_HREF, D_, "href", CHECKED_IN,
+ TRUE, { NULL }, TRUE },
+
+ { PROP, D_, SVN_DAV__VERSION_NAME, VERSION_NAME,
+ TRUE, { NULL }, TRUE },
+
+ { PROP, D_, SVN_DAV__CREATIONDATE, DATE,
+ TRUE, { NULL }, TRUE },
+
+ { PROP, D_, "creator-displayname", AUTHOR,
+ TRUE, { NULL }, TRUE },
+
+ { PROP, S_, "post-commit-err", POST_COMMIT_ERR,
+ TRUE, { NULL }, TRUE },
+
+ { 0 }
+};
- return SVN_NO_ERROR;
-}
+/* Conforms to svn_ra_serf__xml_closed_t */
static svn_error_t *
-end_merge(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name)
+merge_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)
{
- svn_ra_serf__merge_context_t *ctx = userData;
- merge_state_e state;
- merge_info_t *info;
+ merge_context_t *merge_ctx = baton;
- state = parser->state->current_state;
- info = parser->state->private;
-
- if (state == NONE)
+ if (leaving_state == RESPONSE)
{
- /* nothing to close yet. */
- return SVN_NO_ERROR;
- }
+ const char *rtype;
- if (state == RESPONSE &&
- strcmp(name.name, "response") == 0)
- {
- if (info->type == BASELINE)
+ rtype = svn_hash_gets(attrs, "resourcetype");
+
+ /* rtype can only be "baseline" or "collection" (or NULL). We can
+ keep this check simple. */
+ if (rtype && *rtype == 'b')
{
- const char *str;
+ const char *rev_str;
- str = apr_hash_get(info->props, SVN_DAV__VERSION_NAME,
- APR_HASH_KEY_STRING);
- if (str)
- {
- ctx->commit_info->revision = SVN_STR_TO_REV(str);
- }
+ rev_str = svn_hash_gets(attrs, "revision");
+ if (rev_str)
+ merge_ctx->commit_info->revision = SVN_STR_TO_REV(rev_str);
else
- {
- ctx->commit_info->revision = SVN_INVALID_REVNUM;
- }
+ merge_ctx->commit_info->revision = SVN_INVALID_REVNUM;
- ctx->commit_info->date =
- apr_pstrdup(ctx->pool,
- apr_hash_get(info->props, SVN_DAV__CREATIONDATE,
- APR_HASH_KEY_STRING));
+ merge_ctx->commit_info->date =
+ apr_pstrdup(merge_ctx->pool,
+ svn_hash_gets(attrs, "date"));
- ctx->commit_info->author =
- apr_pstrdup(ctx->pool,
- apr_hash_get(info->props, "creator-displayname",
- APR_HASH_KEY_STRING));
+ merge_ctx->commit_info->author =
+ apr_pstrdup(merge_ctx->pool,
+ svn_hash_gets(attrs, "author"));
- ctx->commit_info->post_commit_err =
- apr_pstrdup(ctx->pool,
- apr_hash_get(info->props,
- "post-commit-err", APR_HASH_KEY_STRING));
+ merge_ctx->commit_info->post_commit_err =
+ apr_pstrdup(merge_ctx->pool,
+ svn_hash_gets(attrs, "post-commit-err"));
}
else
{
const char *href;
- href = apr_hash_get(info->props, "href", APR_HASH_KEY_STRING);
- if (! svn_urlpath__is_ancestor(ctx->merge_url, href))
- {
- return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
- _("A MERGE response for '%s' is not "
- "a child of the destination ('%s')"),
- href, ctx->merge_url);
- }
+ href = svn_urlpath__skip_ancestor(
+ merge_ctx->merge_url,
+ svn_hash_gets(attrs, "href"));
+
+ if (href == NULL)
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("A MERGE response for '%s' is not "
+ "a child of the destination ('%s')"),
+ href, merge_ctx->merge_url);
/* We now need to dive all the way into the WC to update the
- * base VCC url.
- */
- if ((! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->session))
- && ctx->session->wc_callbacks->push_wc_prop)
+ base VCC url. */
+ if (!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(merge_ctx->session)
+ && merge_ctx->session->wc_callbacks->push_wc_prop)
{
- svn_string_t checked_in_str;
const char *checked_in;
+ svn_string_t checked_in_str;
- /* From the above check, we know that CTX->MERGE_URL is
- an ancestor of HREF. All that remains is to
- determine of HREF is the same as CTX->MERGE_URL, or --
- if not -- is relative value as a child thereof. */
- href = svn_urlpath__is_child(ctx->merge_url, href, NULL);
- if (! href)
- href = "";
-
- checked_in = apr_hash_get(info->props, "checked-in",
- APR_HASH_KEY_STRING);
+ checked_in = svn_hash_gets(attrs, "checked-in");
checked_in_str.data = checked_in;
checked_in_str.len = strlen(checked_in);
- SVN_ERR(ctx->session->wc_callbacks->push_wc_prop(
- ctx->session->wc_callback_baton, href,
- SVN_RA_SERF__WC_CHECKED_IN_URL, &checked_in_str, info->pool));
+ SVN_ERR(merge_ctx->session->wc_callbacks->push_wc_prop(
+ merge_ctx->session->wc_callback_baton,
+ href,
+ SVN_RA_SERF__WC_CHECKED_IN_URL,
+ &checked_in_str,
+ scratch_pool));
}
}
-
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == PROPSTAT &&
- strcmp(name.name, "propstat") == 0)
- {
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == PROP &&
- strcmp(name.name, "prop") == 0)
- {
- svn_ra_serf__xml_pop_state(parser);
}
- else if (state == RESOURCE_TYPE &&
- strcmp(name.name, "resourcetype") == 0)
+ else if (leaving_state == BASELINE)
{
- svn_ra_serf__xml_pop_state(parser);
+ svn_ra_serf__xml_note(xes, RESPONSE, "resourcetype", "baseline");
}
- else if (state == IGNORE_PROP_NAME || state == NEED_PROP_NAME)
+ else if (leaving_state == COLLECTION)
{
- svn_ra_serf__xml_pop_state(parser);
+ svn_ra_serf__xml_note(xes, RESPONSE, "resourcetype", "collection");
}
- else if (state == PROP_VAL)
+ else
{
- if (!info->prop_name)
+ const char *name;
+ const char *value = cdata->data;
+
+ if (leaving_state == HREF)
{
- info->prop_name = apr_pstrdup(info->pool, name.name);
+ name = "href";
+ value = svn_urlpath__canonicalize(value, scratch_pool);
}
- info->prop_val = apr_pstrmemdup(info->pool, info->prop_val,
- info->prop_val_len);
- if (strcmp(info->prop_name, "href") == 0)
- info->prop_val = svn_urlpath__canonicalize(info->prop_val,
- info->pool);
-
- /* Set our property. */
- apr_hash_set(info->props, info->prop_name, APR_HASH_KEY_STRING,
- info->prop_val);
-
- info->prop_ns = NULL;
- info->prop_name = NULL;
- info->prop_val = NULL;
- info->prop_val_len = 0;
-
- svn_ra_serf__xml_pop_state(parser);
- }
-
- return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-cdata_merge(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- const char *data,
- apr_size_t len)
-{
- svn_ra_serf__merge_context_t *ctx = userData;
- merge_state_e state;
- merge_info_t *info;
-
- UNUSED_CTX(ctx);
-
- state = parser->state->current_state;
- info = parser->state->private;
+ else if (leaving_state == CHECKED_IN)
+ {
+ name = "checked-in";
+ value = svn_urlpath__canonicalize(value, scratch_pool);
+ }
+ else if (leaving_state == VERSION_NAME)
+ name = "revision";
+ else if (leaving_state == DATE)
+ name = "date";
+ else if (leaving_state == AUTHOR)
+ name = "author";
+ else if (leaving_state == POST_COMMIT_ERR)
+ name = "post-commit-err";
+ else
+ SVN_ERR_MALFUNCTION();
- if (state == PROP_VAL)
- {
- svn_ra_serf__expand_string(&info->prop_val, &info->prop_val_len,
- data, len, parser->state->pool);
+ svn_ra_serf__xml_note(xes, RESPONSE, name, value);
}
return SVN_NO_ERROR;
}
+
static svn_error_t *
setup_merge_headers(serf_bucket_t *headers,
void *baton,
apr_pool_t *pool)
{
- svn_ra_serf__merge_context_t *ctx = baton;
+ merge_context_t *ctx = baton;
if (!ctx->keep_locks)
{
@@ -447,7 +310,7 @@ svn_ra_serf__merge_lock_token_list(apr_hash_t *lock_tokens,
path.data = key;
path.len = klen;
- if (parent && !svn_relpath__is_ancestor(parent, key))
+ if (parent && !svn_relpath_skip_ancestor(parent, key))
continue;
svn_ra_serf__add_open_tag_buckets(body, alloc, "S:lock", NULL);
@@ -470,7 +333,7 @@ create_merge_body(serf_bucket_t **bkt,
serf_bucket_alloc_t *alloc,
apr_pool_t *pool)
{
- svn_ra_serf__merge_context_t *ctx = baton;
+ merge_context_t *ctx = baton;
serf_bucket_t *body_bkt;
body_bkt = serf_bucket_aggregate_create(alloc);
@@ -512,21 +375,23 @@ create_merge_body(serf_bucket_t **bkt,
svn_error_t *
-svn_ra_serf__merge_create_req(svn_ra_serf__merge_context_t **ret_ctx,
- svn_ra_serf__session_t *session,
- svn_ra_serf__connection_t *conn,
- const char *merge_resource_url,
- apr_hash_t *lock_tokens,
- svn_boolean_t keep_locks,
- apr_pool_t *pool)
+svn_ra_serf__run_merge(const svn_commit_info_t **commit_info,
+ int *response_code,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *merge_resource_url,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- svn_ra_serf__merge_context_t *merge_ctx;
+ merge_context_t *merge_ctx;
svn_ra_serf__handler_t *handler;
- svn_ra_serf__xml_parser_t *parser_ctx;
+ svn_ra_serf__xml_context_t *xmlctx;
- merge_ctx = apr_pcalloc(pool, sizeof(*merge_ctx));
+ merge_ctx = apr_pcalloc(scratch_pool, sizeof(*merge_ctx));
- merge_ctx->pool = pool;
+ merge_ctx->pool = result_pool;
merge_ctx->session = session;
merge_ctx->merge_resource_url = merge_resource_url;
@@ -534,11 +399,15 @@ svn_ra_serf__merge_create_req(svn_ra_serf__merge_context_t **ret_ctx,
merge_ctx->lock_tokens = lock_tokens;
merge_ctx->keep_locks = keep_locks;
- merge_ctx->commit_info = svn_create_commit_info(pool);
+ merge_ctx->commit_info = svn_create_commit_info(result_pool);
merge_ctx->merge_url = session->session_url.path;
- handler = apr_pcalloc(pool, sizeof(*handler));
+ xmlctx = svn_ra_serf__xml_context_create(merge_ttable,
+ NULL, merge_closed, NULL,
+ merge_ctx,
+ scratch_pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, scratch_pool);
handler->method = "MERGE";
handler->path = merge_ctx->merge_url;
@@ -547,43 +416,15 @@ svn_ra_serf__merge_create_req(svn_ra_serf__merge_context_t **ret_ctx,
handler->conn = conn;
handler->session = session;
- parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
-
- parser_ctx->pool = pool;
- parser_ctx->user_data = merge_ctx;
- parser_ctx->start = start_merge;
- parser_ctx->end = end_merge;
- parser_ctx->cdata = cdata_merge;
- parser_ctx->done = &merge_ctx->done;
- parser_ctx->status_code = &merge_ctx->status;
-
handler->header_delegate = setup_merge_headers;
handler->header_delegate_baton = merge_ctx;
- handler->response_handler = svn_ra_serf__handle_xml_parser;
- handler->response_baton = parser_ctx;
+ merge_ctx->handler = handler;
- svn_ra_serf__request_create(handler);
+ SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
- *ret_ctx = merge_ctx;
+ *commit_info = merge_ctx->commit_info;
+ *response_code = handler->sline.code;
return SVN_NO_ERROR;
}
-
-svn_boolean_t*
-svn_ra_serf__merge_get_done_ptr(svn_ra_serf__merge_context_t *ctx)
-{
- return &ctx->done;
-}
-
-svn_commit_info_t*
-svn_ra_serf__merge_get_commit_info(svn_ra_serf__merge_context_t *ctx)
-{
- return ctx->commit_info;
-}
-
-int
-svn_ra_serf__merge_get_status(svn_ra_serf__merge_context_t *ctx)
-{
- return ctx->status;
-}
diff --git a/subversion/libsvn_ra_serf/mergeinfo.c b/subversion/libsvn_ra_serf/mergeinfo.c
index 7584d99..bd4fcba 100644
--- a/subversion/libsvn_ra_serf/mergeinfo.c
+++ b/subversion/libsvn_ra_serf/mergeinfo.c
@@ -24,6 +24,7 @@
#include <apr_tables.h>
#include <apr_xml.h>
+#include "svn_hash.h"
#include "svn_mergeinfo.h"
#include "svn_path.h"
#include "svn_ra.h"
@@ -40,7 +41,7 @@
/* The current state of our XML parsing. */
typedef enum mergeinfo_state_e {
- NONE = 0,
+ INITIAL = 0,
MERGEINFO_REPORT,
MERGEINFO_ITEM,
MERGEINFO_PATH,
@@ -49,132 +50,87 @@ typedef enum mergeinfo_state_e {
/* Baton for accumulating mergeinfo. RESULT_CATALOG stores the final
mergeinfo catalog result we are going to hand back to the caller of
- get_mergeinfo. curr_path and curr_info contain the value of the
- CDATA from the mergeinfo items as we get them from the server. */
-
+ get_mergeinfo. */
typedef struct mergeinfo_context_t {
apr_pool_t *pool;
- svn_stringbuf_t *curr_path;
- svn_stringbuf_t *curr_info;
svn_mergeinfo_t result_catalog;
- svn_boolean_t done;
const apr_array_header_t *paths;
svn_revnum_t revision;
svn_mergeinfo_inheritance_t inherit;
svn_boolean_t include_descendants;
} mergeinfo_context_t;
-static svn_error_t *
-start_element(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name,
- const char **attrs)
-{
- mergeinfo_context_t *mergeinfo_ctx = userData;
- mergeinfo_state_e state;
- state = parser->state->current_state;
- if (state == NONE && strcmp(name.name, SVN_DAV__MERGEINFO_REPORT) == 0)
- {
- svn_ra_serf__xml_push_state(parser, MERGEINFO_REPORT);
- }
- else if (state == MERGEINFO_REPORT &&
- strcmp(name.name, SVN_DAV__MERGEINFO_ITEM) == 0)
- {
- svn_ra_serf__xml_push_state(parser, MERGEINFO_ITEM);
- svn_stringbuf_setempty(mergeinfo_ctx->curr_path);
- svn_stringbuf_setempty(mergeinfo_ctx->curr_info);
- }
- else if (state == MERGEINFO_ITEM &&
- strcmp(name.name, SVN_DAV__MERGEINFO_PATH) == 0)
- {
- svn_ra_serf__xml_push_state(parser, MERGEINFO_PATH);
- }
- else if (state == MERGEINFO_ITEM &&
- strcmp(name.name, SVN_DAV__MERGEINFO_INFO) == 0)
- {
- svn_ra_serf__xml_push_state(parser, MERGEINFO_INFO);
- }
- return SVN_NO_ERROR;
-}
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t mergeinfo_ttable[] = {
+ { INITIAL, S_, SVN_DAV__MERGEINFO_REPORT, MERGEINFO_REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { MERGEINFO_REPORT, S_, SVN_DAV__MERGEINFO_ITEM, MERGEINFO_ITEM,
+ FALSE, { NULL }, TRUE },
+
+ { MERGEINFO_ITEM, S_, SVN_DAV__MERGEINFO_PATH, MERGEINFO_PATH,
+ TRUE, { NULL }, TRUE },
+
+ { MERGEINFO_ITEM, S_, SVN_DAV__MERGEINFO_INFO, MERGEINFO_INFO,
+ TRUE, { NULL }, TRUE },
+
+ { 0 }
+};
+
+/* Conforms to svn_ra_serf__xml_closed_t */
static svn_error_t *
-end_element(svn_ra_serf__xml_parser_t *parser, void *userData,
- svn_ra_serf__dav_props_t name)
+mergeinfo_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)
{
- mergeinfo_context_t *mergeinfo_ctx = userData;
- mergeinfo_state_e state;
-
- state = parser->state->current_state;
+ mergeinfo_context_t *mergeinfo_ctx = baton;
- if (state == MERGEINFO_REPORT &&
- strcmp(name.name, SVN_DAV__MERGEINFO_REPORT) == 0)
- {
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == MERGEINFO_ITEM
- && strcmp(name.name, SVN_DAV__MERGEINFO_ITEM) == 0)
+ if (leaving_state == MERGEINFO_ITEM)
{
- if (mergeinfo_ctx->curr_info && mergeinfo_ctx->curr_path)
+ /* Placed here from the child elements. */
+ const char *path = svn_hash_gets(attrs, "path");
+ const char *info = svn_hash_gets(attrs, "info");
+
+ if (path != NULL && info != NULL)
{
svn_mergeinfo_t path_mergeinfo;
- const char *path;
- SVN_ERR_ASSERT(mergeinfo_ctx->curr_path->data);
- path = apr_pstrdup(mergeinfo_ctx->pool,
- mergeinfo_ctx->curr_path->data);
- SVN_ERR(svn_mergeinfo_parse(&path_mergeinfo,
- mergeinfo_ctx->curr_info->data,
- mergeinfo_ctx->pool));
/* Correct for naughty servers that send "relative" paths
with leading slashes! */
- apr_hash_set(mergeinfo_ctx->result_catalog,
- path[0] == '/' ? path + 1 : path,
- APR_HASH_KEY_STRING, path_mergeinfo);
- }
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == MERGEINFO_PATH
- && strcmp(name.name, SVN_DAV__MERGEINFO_PATH) == 0)
- {
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == MERGEINFO_INFO
- && strcmp(name.name, SVN_DAV__MERGEINFO_INFO) == 0)
- {
- svn_ra_serf__xml_pop_state(parser);
- }
- return SVN_NO_ERROR;
-}
-
+ if (path[0] == '/')
+ ++path;
-static svn_error_t *
-cdata_handler(svn_ra_serf__xml_parser_t *parser, void *userData,
- const char *data, apr_size_t len)
-{
- mergeinfo_context_t *mergeinfo_ctx = userData;
- mergeinfo_state_e state;
+ SVN_ERR(svn_mergeinfo_parse(&path_mergeinfo, info,
+ mergeinfo_ctx->pool));
- state = parser->state->current_state;
- switch (state)
+ svn_hash_sets(mergeinfo_ctx->result_catalog,
+ apr_pstrdup(mergeinfo_ctx->pool, path),
+ path_mergeinfo);
+ }
+ }
+ else
{
- case MERGEINFO_PATH:
- if (mergeinfo_ctx->curr_path)
- svn_stringbuf_appendbytes(mergeinfo_ctx->curr_path, data, len);
- break;
-
- case MERGEINFO_INFO:
- if (mergeinfo_ctx->curr_info)
- svn_stringbuf_appendbytes(mergeinfo_ctx->curr_info, data, len);
- break;
-
- default:
- break;
+ SVN_ERR_ASSERT(leaving_state == MERGEINFO_PATH
+ || leaving_state == MERGEINFO_INFO);
+
+ /* Stash the value onto the parent MERGEINFO_ITEM. */
+ svn_ra_serf__xml_note(xes, MERGEINFO_ITEM,
+ leaving_state == MERGEINFO_PATH
+ ? "path"
+ : "info",
+ cdata->data);
}
return SVN_NO_ERROR;
}
+
static svn_error_t *
create_mergeinfo_body(serf_bucket_t **bkt,
void *baton,
@@ -226,8 +182,6 @@ create_mergeinfo_body(serf_bucket_t **bkt,
return SVN_NO_ERROR;
}
-/* Request a mergeinfo-report from the URL attached to SESSION,
- and fill in the MERGEINFO hash with the results. */
svn_error_t *
svn_ra_serf__get_mergeinfo(svn_ra_session_t *ra_session,
svn_mergeinfo_catalog_t *catalog,
@@ -237,35 +191,33 @@ svn_ra_serf__get_mergeinfo(svn_ra_session_t *ra_session,
svn_boolean_t include_descendants,
apr_pool_t *pool)
{
- svn_error_t *err, *err2;
- int status_code;
-
+ svn_error_t *err;
mergeinfo_context_t *mergeinfo_ctx;
svn_ra_serf__session_t *session = ra_session->priv;
svn_ra_serf__handler_t *handler;
- svn_ra_serf__xml_parser_t *parser_ctx;
- const char *relative_url, *basecoll_url;
+ svn_ra_serf__xml_context_t *xmlctx;
const char *path;
*catalog = NULL;
- SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url, session,
- NULL, NULL, revision, NULL, pool));
-
- path = svn_path_url_add_component2(basecoll_url, relative_url, pool);
+ SVN_ERR(svn_ra_serf__get_stable_url(&path, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, revision,
+ pool, pool));
mergeinfo_ctx = apr_pcalloc(pool, sizeof(*mergeinfo_ctx));
mergeinfo_ctx->pool = pool;
- mergeinfo_ctx->curr_path = svn_stringbuf_create("", pool);
- mergeinfo_ctx->curr_info = svn_stringbuf_create("", pool);
- mergeinfo_ctx->done = FALSE;
mergeinfo_ctx->result_catalog = apr_hash_make(pool);
mergeinfo_ctx->paths = paths;
mergeinfo_ctx->revision = revision;
mergeinfo_ctx->inherit = inherit;
mergeinfo_ctx->include_descendants = include_descendants;
- handler = apr_pcalloc(pool, sizeof(*handler));
+ xmlctx = svn_ra_serf__xml_context_create(mergeinfo_ttable,
+ NULL, mergeinfo_closed, NULL,
+ mergeinfo_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
handler->method = "REPORT";
handler->path = path;
@@ -275,34 +227,14 @@ svn_ra_serf__get_mergeinfo(svn_ra_session_t *ra_session,
handler->body_delegate_baton = mergeinfo_ctx;
handler->body_type = "text/xml";
- parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
-
- parser_ctx->pool = pool;
- parser_ctx->user_data = mergeinfo_ctx;
- parser_ctx->start = start_element;
- parser_ctx->end = end_element;
- parser_ctx->cdata = cdata_handler;
- parser_ctx->done = &mergeinfo_ctx->done;
- parser_ctx->status_code = &status_code;
-
- handler->response_handler = svn_ra_serf__handle_xml_parser;
- handler->response_baton = parser_ctx;
-
- svn_ra_serf__request_create(handler);
-
- err = svn_ra_serf__context_run_wait(&mergeinfo_ctx->done, session, pool);
-
- err2 = svn_ra_serf__error_on_status(status_code, handler->path,
- parser_ctx->location);
- if (err2)
- {
- svn_error_clear(err);
- return err2;
- }
+ err = svn_ra_serf__context_run_one(handler, pool);
- SVN_ERR(err);
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline, handler->path,
+ handler->location),
+ err));
- if (mergeinfo_ctx->done && apr_hash_count(mergeinfo_ctx->result_catalog))
+ if (handler->done && apr_hash_count(mergeinfo_ctx->result_catalog))
*catalog = mergeinfo_ctx->result_catalog;
return SVN_NO_ERROR;
diff --git a/subversion/libsvn_ra_serf/options.c b/subversion/libsvn_ra_serf/options.c
index 8bdc8fd..5389b04 100644
--- a/subversion/libsvn_ra_serf/options.c
+++ b/subversion/libsvn_ra_serf/options.c
@@ -28,10 +28,12 @@
#include <serf.h>
#include "svn_dirent_uri.h"
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_ra.h"
#include "svn_dav.h"
#include "svn_xml.h"
+#include "svn_ctype.h"
#include "../libsvn_ra/ra_loader.h"
#include "svn_private_config.h"
@@ -48,167 +50,68 @@
/*
* This enum represents the current state of our XML parsing for an OPTIONS.
*/
-typedef enum options_state_e {
+enum options_state_e {
+ INITIAL = 0,
OPTIONS,
ACTIVITY_COLLECTION,
HREF
-} options_state_e;
-
-typedef struct options_state_list_t {
- /* The current state that we are in now. */
- options_state_e state;
-
- /* The previous state we were in. */
- struct options_state_list_t *prev;
-} options_state_list_t;
+};
-struct svn_ra_serf__options_context_t {
+typedef struct options_context_t {
/* pool to allocate memory from */
apr_pool_t *pool;
- const char *attr_val;
- apr_size_t attr_val_len;
- svn_boolean_t collect_cdata;
-
- /* Current state we're in */
- options_state_list_t *state;
- options_state_list_t *free_state;
-
- /* HTTP Status code */
- int status_code;
-
- /* are we done? */
- svn_boolean_t done;
+ /* Have we extracted options values from the headers already? */
+ svn_boolean_t headers_processed;
svn_ra_serf__session_t *session;
svn_ra_serf__connection_t *conn;
+ svn_ra_serf__handler_t *handler;
- const char *path;
+ svn_ra_serf__response_handler_t inner_handler;
+ void *inner_baton;
const char *activity_collection;
svn_revnum_t youngest_rev;
- serf_response_acceptor_t acceptor;
- serf_response_handler_t handler;
- svn_ra_serf__xml_parser_t *parser_ctx;
+} options_context_t;
-};
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t options_ttable[] = {
+ { INITIAL, D_, "options-response", OPTIONS,
+ FALSE, { NULL }, FALSE },
-static void
-push_state(svn_ra_serf__options_context_t *options_ctx, options_state_e state)
-{
- options_state_list_t *new_state;
+ { OPTIONS, D_, "activity-collection-set", ACTIVITY_COLLECTION,
+ FALSE, { NULL }, FALSE },
- if (!options_ctx->free_state)
- {
- new_state = apr_palloc(options_ctx->pool, sizeof(*options_ctx->state));
- }
- else
- {
- new_state = options_ctx->free_state;
- options_ctx->free_state = options_ctx->free_state->prev;
- }
- new_state->state = state;
-
- /* Add it to the state chain. */
- new_state->prev = options_ctx->state;
- options_ctx->state = new_state;
-}
-
-static void pop_state(svn_ra_serf__options_context_t *options_ctx)
-{
- options_state_list_t *free_state;
- free_state = options_ctx->state;
- /* advance the current state */
- options_ctx->state = options_ctx->state->prev;
- free_state->prev = options_ctx->free_state;
- options_ctx->free_state = free_state;
-}
-
-static svn_error_t *
-start_options(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name,
- const char **attrs)
-{
- svn_ra_serf__options_context_t *options_ctx = userData;
+ { ACTIVITY_COLLECTION, D_, "href", HREF,
+ TRUE, { NULL }, TRUE },
- if (!options_ctx->state && strcmp(name.name, "options-response") == 0)
- {
- push_state(options_ctx, OPTIONS);
- }
- else if (!options_ctx->state)
- {
- /* Nothing to do. */
- return SVN_NO_ERROR;
- }
- else if (options_ctx->state->state == OPTIONS &&
- strcmp(name.name, "activity-collection-set") == 0)
- {
- push_state(options_ctx, ACTIVITY_COLLECTION);
- }
- else if (options_ctx->state->state == ACTIVITY_COLLECTION &&
- strcmp(name.name, "href") == 0)
- {
- options_ctx->collect_cdata = TRUE;
- push_state(options_ctx, HREF);
- }
+ { 0 }
+};
- return SVN_NO_ERROR;
-}
+/* Conforms to svn_ra_serf__xml_closed_t */
static svn_error_t *
-end_options(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name)
+options_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)
{
- svn_ra_serf__options_context_t *options_ctx = userData;
- options_state_list_t *cur_state;
-
- if (!options_ctx->state)
- {
- return SVN_NO_ERROR;
- }
+ options_context_t *opt_ctx = baton;
- cur_state = options_ctx->state;
+ SVN_ERR_ASSERT(leaving_state == HREF);
+ SVN_ERR_ASSERT(cdata != NULL);
- if (cur_state->state == OPTIONS &&
- strcmp(name.name, "options-response") == 0)
- {
- pop_state(options_ctx);
- }
- else if (cur_state->state == ACTIVITY_COLLECTION &&
- strcmp(name.name, "activity-collection-set") == 0)
- {
- pop_state(options_ctx);
- }
- else if (cur_state->state == HREF &&
- strcmp(name.name, "href") == 0)
- {
- options_ctx->collect_cdata = FALSE;
- options_ctx->activity_collection =
- svn_urlpath__canonicalize(options_ctx->attr_val, options_ctx->pool);
- pop_state(options_ctx);
- }
+ opt_ctx->activity_collection = svn_urlpath__canonicalize(cdata->data,
+ opt_ctx->pool);
return SVN_NO_ERROR;
}
-static svn_error_t *
-cdata_options(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- const char *data,
- apr_size_t len)
-{
- svn_ra_serf__options_context_t *ctx = userData;
- if (ctx->collect_cdata)
- {
- svn_ra_serf__expand_string(&ctx->attr_val, &ctx->attr_val_len,
- data, len, ctx->pool);
- }
-
- return SVN_NO_ERROR;
-}
static svn_error_t *
create_options_body(serf_bucket_t **body_bkt,
@@ -229,47 +132,17 @@ create_options_body(serf_bucket_t **body_bkt,
return SVN_NO_ERROR;
}
-svn_boolean_t*
-svn_ra_serf__get_options_done_ptr(svn_ra_serf__options_context_t *ctx)
-{
- return &ctx->done;
-}
-
-const char *
-svn_ra_serf__options_get_activity_collection(svn_ra_serf__options_context_t *ctx)
-{
- return ctx->activity_collection;
-}
-
-svn_revnum_t
-svn_ra_serf__options_get_youngest_rev(svn_ra_serf__options_context_t *ctx)
-{
- return ctx->youngest_rev;
-}
-
-/* Context for both options_response_handler() and capabilities callback. */
-struct options_response_ctx_t {
- /* Baton for __handle_xml_parser() */
- svn_ra_serf__xml_parser_t *parser_ctx;
-
- /* Session into which we'll store server capabilities */
- svn_ra_serf__session_t *session;
-
- /* For temporary work only. */
- apr_pool_t *pool;
-};
-
/* We use these static pointers so we can employ pointer comparison
* of our capabilities hash members instead of strcmp()ing all over
* the place.
*/
/* Both server and repository support the capability. */
-static const char *capability_yes = "yes";
+static const char *const capability_yes = "yes";
/* Either server or repository does not support the capability. */
-static const char *capability_no = "no";
+static const char *const capability_no = "no";
/* Server supports the capability, but don't yet know if repository does. */
-static const char *capability_server_yes = "server-yes";
+static const char *const capability_server_yes = "server-yes";
/* This implements serf_bucket_headers_do_callback_fn_t.
@@ -279,7 +152,8 @@ capabilities_headers_iterator_callback(void *baton,
const char *key,
const char *val)
{
- struct options_response_ctx_t *orc = baton;
+ options_context_t *opt_ctx = baton;
+ svn_ra_serf__session_t *session = opt_ctx->session;
if (svn_cstring_casecmp(key, "dav") == 0)
{
@@ -287,7 +161,8 @@ capabilities_headers_iterator_callback(void *baton,
DAV: version-control,checkout,working-resource
DAV: merge,baseline,activity,version-controlled-collection
DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */
- apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE, orc->pool);
+ apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
+ opt_ctx->pool);
/* Right now we only have a few capabilities to detect, so just
seek for them directly. This could be written slightly more
@@ -296,49 +171,86 @@ capabilities_headers_iterator_callback(void *baton,
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals))
{
- apr_hash_set(orc->session->capabilities, SVN_RA_CAPABILITY_DEPTH,
- APR_HASH_KEY_STRING, capability_yes);
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_DEPTH, capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
{
/* The server doesn't know what repository we're referring
to, so it can't just say capability_yes. */
- apr_hash_set(orc->session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
- APR_HASH_KEY_STRING, capability_server_yes);
+ if (!svn_hash_gets(session->capabilities,
+ SVN_RA_CAPABILITY_MERGEINFO))
+ {
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
+ capability_server_yes);
+ }
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
{
- apr_hash_set(orc->session->capabilities,
- SVN_RA_CAPABILITY_LOG_REVPROPS,
- APR_HASH_KEY_STRING, capability_yes);
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_LOG_REVPROPS, capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals))
{
- apr_hash_set(orc->session->capabilities,
- SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
- APR_HASH_KEY_STRING, capability_yes);
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS, capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals))
{
- apr_hash_set(orc->session->capabilities,
- SVN_RA_CAPABILITY_PARTIAL_REPLAY,
- APR_HASH_KEY_STRING, capability_yes);
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_PARTIAL_REPLAY, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_INHERITED_PROPS, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS,
+ vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
+ capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INLINE_PROPS, vals))
+ {
+ session->supports_inline_props = TRUE;
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE, vals))
+ {
+ session->supports_rev_rsrc_replay = TRUE;
}
}
/* SVN-specific headers -- if present, server supports HTTP protocol v2 */
- else if (strncmp(key, "SVN", 3) == 0)
+ else if (!svn_ctype_casecmp(key[0], 'S')
+ && !svn_ctype_casecmp(key[1], 'V')
+ && !svn_ctype_casecmp(key[2], 'N'))
{
+ /* If we've not yet seen any information about supported POST
+ requests, we'll initialize the list/hash with "create-txn"
+ (which we know is supported by virtue of the server speaking
+ HTTPv2 at all. */
+ if (! session->supported_posts)
+ {
+ session->supported_posts = apr_hash_make(session->pool);
+ apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
+ }
+
if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
{
- orc->session->repos_root = orc->session->session_url;
- orc->session->repos_root.path = apr_pstrdup(orc->session->pool, val);
- orc->session->repos_root_str =
+ session->repos_root = session->session_url;
+ session->repos_root.path =
+ (char *)svn_fspath__canonicalize(val, session->pool);
+ session->repos_root_str =
svn_urlpath__canonicalize(
- apr_uri_unparse(orc->session->pool,
- &orc->session->repos_root,
- 0),
- orc->session->pool);
+ apr_uri_unparse(session->pool, &session->repos_root, 0),
+ session->pool);
}
else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0)
{
@@ -347,44 +259,73 @@ capabilities_headers_iterator_callback(void *baton,
if (!(ignore_v2_env_var
&& apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0))
- orc->session->me_resource = apr_pstrdup(orc->session->pool, val);
+ session->me_resource = apr_pstrdup(session->pool, val);
#else
- orc->session->me_resource = apr_pstrdup(orc->session->pool, val);
+ session->me_resource = apr_pstrdup(session->pool, val);
#endif
}
else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0)
{
- orc->session->rev_stub = apr_pstrdup(orc->session->pool, val);
+ session->rev_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0)
{
- orc->session->rev_root_stub = apr_pstrdup(orc->session->pool, val);
+ session->rev_root_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0)
{
- orc->session->txn_stub = apr_pstrdup(orc->session->pool, val);
+ session->txn_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0)
{
- orc->session->txn_root_stub = apr_pstrdup(orc->session->pool, val);
+ session->txn_root_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0)
{
- orc->session->vtxn_stub = apr_pstrdup(orc->session->pool, val);
+ session->vtxn_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0)
{
- orc->session->vtxn_root_stub = apr_pstrdup(orc->session->pool, val);
+ session->vtxn_root_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0)
{
- orc->session->uuid = apr_pstrdup(orc->session->pool, val);
+ session->uuid = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0)
{
- struct svn_ra_serf__options_context_t *user_data =
- orc->parser_ctx->user_data;
- user_data->youngest_rev = SVN_STR_TO_REV(val);
+ opt_ctx->youngest_rev = SVN_STR_TO_REV(val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0)
+ {
+ session->server_allows_bulk = apr_pstrdup(session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0)
+ {
+ /* May contain multiple values, separated by commas. */
+ int i;
+ apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
+ session->pool);
+
+ for (i = 0; i < vals->nelts; i++)
+ {
+ const char *post_val = APR_ARRAY_IDX(vals, i, const char *);
+
+ svn_hash_sets(session->supported_posts, post_val, (void *)1);
+ }
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0)
+ {
+ if (svn_cstring_casecmp(val, "yes") == 0)
+ {
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
+ capability_yes);
+ }
+ else if (svn_cstring_casecmp(val, "no") == 0)
+ {
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
+ capability_no);
+ }
}
}
@@ -393,7 +334,7 @@ capabilities_headers_iterator_callback(void *baton,
/* A custom serf_response_handler_t which is mostly a wrapper around
- svn_ra_serf__handle_xml_parser -- it just notices OPTIONS response
+ the expat-based response handler -- it just notices OPTIONS response
headers first, before handing off to the xml parser.
Implements svn_ra_serf__response_handler_t */
static svn_error_t *
@@ -402,86 +343,137 @@ options_response_handler(serf_request_t *request,
void *baton,
apr_pool_t *pool)
{
- struct options_response_ctx_t *orc = baton;
- serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
-
- /* Start out assuming all capabilities are unsupported. */
- apr_hash_set(orc->session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
- APR_HASH_KEY_STRING, capability_no);
- apr_hash_set(orc->session->capabilities, SVN_RA_CAPABILITY_DEPTH,
- APR_HASH_KEY_STRING, capability_no);
- apr_hash_set(orc->session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
- APR_HASH_KEY_STRING, capability_no);
- apr_hash_set(orc->session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
- APR_HASH_KEY_STRING, capability_no);
- apr_hash_set(orc->session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
- APR_HASH_KEY_STRING, capability_no);
-
- /* Then see which ones we can discover. */
- serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback, orc);
-
- /* Execute the 'real' response handler to XML-parse the repsonse body. */
- return svn_ra_serf__handle_xml_parser(request, response,
- orc->parser_ctx, pool);
+ options_context_t *opt_ctx = baton;
+
+ if (!opt_ctx->headers_processed)
+ {
+ svn_ra_serf__session_t *session = opt_ctx->session;
+ serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
+
+ /* Start out assuming all capabilities are unsupported. */
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
+ capability_no);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH,
+ capability_no);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
+ NULL);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
+ capability_no);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
+ capability_no);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS,
+ capability_no);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
+ capability_no);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
+ capability_no);
+
+ /* Then see which ones we can discover. */
+ serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
+ opt_ctx);
+
+ /* Assume mergeinfo capability unsupported, if didn't recieve information
+ about server or repository mergeinfo capability. */
+ if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO))
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
+ capability_no);
+
+ opt_ctx->headers_processed = TRUE;
+ }
+
+ /* Execute the 'real' response handler to XML-parse the response body. */
+ return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool);
}
-svn_error_t *
-svn_ra_serf__create_options_req(svn_ra_serf__options_context_t **opt_ctx,
- svn_ra_serf__session_t *session,
- svn_ra_serf__connection_t *conn,
- const char *path,
- apr_pool_t *pool)
+static svn_error_t *
+create_options_req(options_context_t **opt_ctx,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool)
{
- svn_ra_serf__options_context_t *new_ctx;
+ options_context_t *new_ctx;
+ svn_ra_serf__xml_context_t *xmlctx;
svn_ra_serf__handler_t *handler;
- svn_ra_serf__xml_parser_t *parser_ctx;
- struct options_response_ctx_t *options_response_ctx;
new_ctx = apr_pcalloc(pool, sizeof(*new_ctx));
-
new_ctx->pool = pool;
-
- new_ctx->path = path;
- new_ctx->youngest_rev = SVN_INVALID_REVNUM;
-
new_ctx->session = session;
new_ctx->conn = conn;
- handler = apr_pcalloc(pool, sizeof(*handler));
+ new_ctx->youngest_rev = SVN_INVALID_REVNUM;
+
+ xmlctx = svn_ra_serf__xml_context_create(options_ttable,
+ NULL, options_closed, NULL,
+ new_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
handler->method = "OPTIONS";
- handler->path = path;
+ handler->path = session->session_url.path;
handler->body_delegate = create_options_body;
handler->body_type = "text/xml";
handler->conn = conn;
handler->session = session;
- parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
+ new_ctx->handler = handler;
+
+ new_ctx->inner_handler = handler->response_handler;
+ new_ctx->inner_baton = handler->response_baton;
+ handler->response_handler = options_response_handler;
+ handler->response_baton = new_ctx;
+
+ *opt_ctx = new_ctx;
- parser_ctx->pool = pool;
- parser_ctx->user_data = new_ctx;
- parser_ctx->start = start_options;
- parser_ctx->end = end_options;
- parser_ctx->cdata = cdata_options;
- parser_ctx->done = &new_ctx->done;
- parser_ctx->status_code = &new_ctx->status_code;
+ return SVN_NO_ERROR;
+}
- options_response_ctx = apr_pcalloc(pool, sizeof(*options_response_ctx));
- options_response_ctx->parser_ctx = parser_ctx;
- options_response_ctx->session = session;
- options_response_ctx->pool = pool;
- handler->response_handler = options_response_handler;
- handler->response_baton = options_response_ctx;
+svn_error_t *
+svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__session_t *session = conn->session;
+ options_context_t *opt_ctx;
- svn_ra_serf__request_create(handler);
+ SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
- new_ctx->parser_ctx = parser_ctx;
+ SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool));
+ SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
+ SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline,
+ opt_ctx->handler->path,
+ opt_ctx->handler->location));
- *opt_ctx = new_ctx;
+ *youngest = opt_ctx->youngest_rev;
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(*youngest));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__v1_get_activity_collection(const char **activity_url,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__session_t *session = conn->session;
+ options_context_t *opt_ctx;
+
+ SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
+
+ SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool));
+ SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
+
+ SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline,
+ opt_ctx->handler->path,
+ opt_ctx->handler->location));
+
+ *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection);
return SVN_NO_ERROR;
+
}
@@ -493,33 +485,100 @@ svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
const char **corrected_url,
apr_pool_t *pool)
{
- svn_ra_serf__options_context_t *opt_ctx;
+ options_context_t *opt_ctx;
svn_error_t *err;
/* This routine automatically fills in serf_sess->capabilities */
- SVN_ERR(svn_ra_serf__create_options_req(&opt_ctx, serf_sess,
- serf_sess->conns[0],
- serf_sess->session_url.path, pool));
+ SVN_ERR(create_options_req(&opt_ctx, serf_sess, serf_sess->conns[0], pool));
- err = svn_ra_serf__context_run_wait(
- svn_ra_serf__get_options_done_ptr(opt_ctx), serf_sess, pool);
+ err = svn_ra_serf__context_run_one(opt_ctx->handler, pool);
/* If our caller cares about server redirections, and our response
carries such a thing, report as much. We'll disregard ERR --
it's most likely just a complaint about the response body not
successfully parsing as XML or somesuch. */
- if (corrected_url && (opt_ctx->status_code == 301))
+ if (corrected_url && (opt_ctx->handler->sline.code == 301))
{
svn_error_clear(err);
- *corrected_url = opt_ctx->parser_ctx->location;
+ *corrected_url = opt_ctx->handler->location;
return SVN_NO_ERROR;
}
- return svn_error_compose_create(
- svn_ra_serf__error_on_status(opt_ctx->status_code,
- serf_sess->session_url.path,
- opt_ctx->parser_ctx->location),
- err);
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(opt_ctx->handler->sline,
+ serf_sess->session_url.path,
+ opt_ctx->handler->location),
+ err));
+
+ /* Opportunistically cache any reported activity URL. (We don't
+ want to have to ask for this again later, potentially against an
+ unreadable commit anchor URL.) */
+ if (opt_ctx->activity_collection)
+ {
+ serf_sess->activity_collection_url =
+ apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+create_simple_options_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *body;
+ serf_bucket_t *s;
+
+ body = serf_bucket_aggregate_create(alloc);
+ svn_ra_serf__add_xml_header_buckets(body, alloc);
+
+ s = SERF_BUCKET_SIMPLE_STRING("<D:options xmlns:D=\"DAV:\" />", alloc);
+ serf_bucket_aggregate_append(body, s);
+
+ *body_bkt = body;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__handler_t *handler;
+
+ handler = apr_pcalloc(scratch_pool, sizeof(*handler));
+ handler->handler_pool = scratch_pool;
+ handler->method = "OPTIONS";
+ handler->path = serf_sess->session_url.path;
+ handler->conn = serf_sess->conns[0];
+ handler->session = serf_sess;
+
+ /* We don't care about the response body, so discard it. */
+ handler->response_handler = svn_ra_serf__handle_discard_body;
+
+ /* We need a simple body, in order to send it in chunked format. */
+ handler->body_delegate = create_simple_options_body;
+
+ /* No special headers. */
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
+ /* Some versions of nginx in reverse proxy mode will return 411. They want
+ a Content-Length header, rather than chunked requests. We can keep other
+ HTTP/1.1 features, but will disable the chunking. */
+ if (handler->sline.code == 411)
+ {
+ serf_sess->using_chunked_requests = FALSE;
+
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(svn_ra_serf__error_on_status(handler->sline,
+ handler->path,
+ handler->location));
+
+ return SVN_NO_ERROR;
}
@@ -539,21 +598,16 @@ svn_ra_serf__has_capability(svn_ra_session_t *ra_session,
return SVN_NO_ERROR;
}
- cap_result = apr_hash_get(serf_sess->capabilities,
- capability,
- APR_HASH_KEY_STRING);
+ cap_result = svn_hash_gets(serf_sess->capabilities, capability);
/* If any capability is unknown, they're all unknown, so ask. */
if (cap_result == NULL)
SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, pool));
/* Try again, now that we've fetched the capabilities. */
- cap_result = apr_hash_get(serf_sess->capabilities,
- capability, APR_HASH_KEY_STRING);
+ cap_result = svn_hash_gets(serf_sess->capabilities, capability);
- /* Some capabilities depend on the repository as well as the server.
- NOTE: svn_ra_neon__has_capability() has a very similar code block. If
- you change something here, check there as well. */
+ /* Some capabilities depend on the repository as well as the server. */
if (cap_result == capability_server_yes)
{
if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
@@ -597,9 +651,8 @@ svn_ra_serf__has_capability(svn_ra_session_t *ra_session,
else
cap_result = capability_yes;
- apr_hash_set(serf_sess->capabilities,
- SVN_RA_CAPABILITY_MERGEINFO, APR_HASH_KEY_STRING,
- cap_result);
+ svn_hash_sets(serf_sess->capabilities,
+ SVN_RA_CAPABILITY_MERGEINFO, cap_result);
}
else
{
diff --git a/subversion/libsvn_ra_serf/property.c b/subversion/libsvn_ra_serf/property.c
index 727f78b..586d38f 100644
--- a/subversion/libsvn_ra_serf/property.c
+++ b/subversion/libsvn_ra_serf/property.c
@@ -25,6 +25,7 @@
#include <serf.h>
+#include "svn_hash.h"
#include "svn_path.h"
#include "svn_base64.h"
#include "svn_xml.h"
@@ -33,6 +34,7 @@
#include "private/svn_dav_protocol.h"
#include "private/svn_fspath.h"
+#include "private/svn_string_private.h"
#include "svn_private_config.h"
#include "ra_serf.h"
@@ -40,31 +42,23 @@
/* Our current parsing state we're in for the PROPFIND response. */
typedef enum prop_state_e {
- NONE = 0,
+ INITIAL = 0,
+ MULTISTATUS,
RESPONSE,
+ HREF,
+ PROPSTAT,
+ STATUS,
PROP,
- PROPVAL
+ PROPVAL,
+ COLLECTION,
+ HREF_VALUE
} prop_state_e;
-typedef struct prop_info_t {
- apr_pool_t *pool;
-
- /* Current ns, attribute name, and value of the property we're parsing */
- const char *ns;
-
- const char *name;
-
- const char *val;
- apr_size_t val_len;
-
- const char *encoding;
-
-} prop_info_t;
/*
* This structure represents a pending PROPFIND response.
*/
-struct svn_ra_serf__propfind_context_t {
+typedef struct propfind_context_t {
/* pool to issue allocations from */
apr_pool_t *pool;
@@ -89,31 +83,267 @@ struct svn_ra_serf__propfind_context_t {
/* hash table that will be updated with the properties
*
- * This can be shared between multiple svn_ra_serf__propfind_context_t
+ * This can be shared between multiple propfind_context_t
* structures
*/
apr_hash_t *ret_props;
- /* If we're dealing with a Depth: 1 response,
- * we may be dealing with multiple paths.
+ /* hash table containing all the properties associated with the
+ * "current" <propstat> tag. These will get copied into RET_PROPS
+ * if the status code similarly associated indicates that they are
+ * "good"; otherwise, they'll get discarded.
*/
- const char *current_path;
-
- /* Returned status code. */
- int status_code;
-
- /* Are we done issuing the PROPFIND? */
- svn_boolean_t done;
-
- /* Context from XML stream */
- svn_ra_serf__xml_parser_t *parser_ctx;
+ apr_hash_t *ps_props;
/* If not-NULL, add us to this list when we're done. */
svn_ra_serf__list_t **done_list;
svn_ra_serf__list_t done_item;
+
+} propfind_context_t;
+
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t propfind_ttable[] = {
+ { INITIAL, D_, "multistatus", MULTISTATUS,
+ FALSE, { NULL }, TRUE },
+
+ { MULTISTATUS, D_, "response", RESPONSE,
+ FALSE, { NULL }, FALSE },
+
+ { RESPONSE, D_, "href", HREF,
+ TRUE, { NULL }, TRUE },
+
+ { RESPONSE, D_, "propstat", PROPSTAT,
+ FALSE, { NULL }, TRUE },
+
+ { PROPSTAT, D_, "status", STATUS,
+ TRUE, { NULL }, TRUE },
+
+ { PROPSTAT, D_, "prop", PROP,
+ FALSE, { NULL }, FALSE },
+
+ { PROP, "*", "*", PROPVAL,
+ TRUE, { "?V:encoding", NULL }, TRUE },
+
+ { PROPVAL, D_, "collection", COLLECTION,
+ FALSE, { NULL }, TRUE },
+
+ { PROPVAL, D_, "href", HREF_VALUE,
+ TRUE, { NULL }, TRUE },
+
+ { 0 }
};
+
+/* Return the HTTP status code contained in STATUS_LINE, or 0 if
+ there's a problem parsing it. */
+static int parse_status_code(const char *status_line)
+{
+ /* STATUS_LINE should be of form: "HTTP/1.1 200 OK" */
+ if (status_line[0] == 'H' &&
+ status_line[1] == 'T' &&
+ status_line[2] == 'T' &&
+ status_line[3] == 'P' &&
+ status_line[4] == '/' &&
+ (status_line[5] >= '0' && status_line[5] <= '9') &&
+ status_line[6] == '.' &&
+ (status_line[7] >= '0' && status_line[7] <= '9') &&
+ status_line[8] == ' ')
+ {
+ char *reason;
+
+ return apr_strtoi64(status_line + 8, &reason, 10);
+ }
+ return 0;
+}
+
+
+/* Conforms to svn_ra_serf__path_rev_walker_t */
+static svn_error_t *
+copy_into_ret_props(void *baton,
+ const char *path, apr_ssize_t path_len,
+ const char *ns, apr_ssize_t ns_len,
+ const char *name, apr_ssize_t name_len,
+ const svn_string_t *val,
+ apr_pool_t *pool)
+{
+ propfind_context_t *ctx = baton;
+
+ svn_ra_serf__set_ver_prop(ctx->ret_props, path, ctx->rev, ns, name,
+ val, ctx->pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_ra_serf__xml_opened_t */
+static svn_error_t *
+propfind_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)
+{
+ propfind_context_t *ctx = baton;
+
+ if (entered_state == PROPVAL)
+ {
+ svn_ra_serf__xml_note(xes, PROPVAL, "ns", tag->namespace);
+ svn_ra_serf__xml_note(xes, PROPVAL, "name", tag->name);
+ }
+ else if (entered_state == PROPSTAT)
+ {
+ ctx->ps_props = apr_hash_make(ctx->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+propfind_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)
+{
+ propfind_context_t *ctx = baton;
+
+ if (leaving_state == MULTISTATUS)
+ {
+ /* We've gathered all the data from the reponse. Add this item
+ onto the "done list". External callers will then know this
+ request has been completed (tho stray response bytes may still
+ arrive). */
+ if (ctx->done_list)
+ {
+ ctx->done_item.data = ctx->handler;
+ ctx->done_item.next = *ctx->done_list;
+ *ctx->done_list = &ctx->done_item;
+ }
+ }
+ else if (leaving_state == HREF)
+ {
+ const char *path;
+ const svn_string_t *val_str;
+
+ if (strcmp(ctx->depth, "1") == 0)
+ path = svn_urlpath__canonicalize(cdata->data, scratch_pool);
+ else
+ path = ctx->path;
+
+ svn_ra_serf__xml_note(xes, RESPONSE, "path", path);
+
+ /* Copy the value into the right pool, then save the HREF. */
+ val_str = svn_string_dup(cdata, ctx->pool);
+ svn_ra_serf__set_ver_prop(ctx->ret_props,
+ path, ctx->rev, D_, "href", val_str,
+ ctx->pool);
+ }
+ else if (leaving_state == COLLECTION)
+ {
+ svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", "collection");
+ }
+ else if (leaving_state == HREF_VALUE)
+ {
+ svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", cdata->data);
+ }
+ else if (leaving_state == STATUS)
+ {
+ /* Parse the status field, and remember if this is a property
+ that we wish to ignore. (Typically, if it's not a 200, the
+ status will be 404 to indicate that a property we
+ specifically requested from the server doesn't exist.) */
+ int status = parse_status_code(cdata->data);
+ if (status != 200)
+ svn_ra_serf__xml_note(xes, PROPSTAT, "ignore-prop", "*");
+ }
+ else if (leaving_state == PROPVAL)
+ {
+ const char *encoding = svn_hash_gets(attrs, "V:encoding");
+ const svn_string_t *val_str;
+ apr_hash_t *gathered;
+ const char *path;
+ const char *ns;
+ const char *name;
+ const char *altvalue;
+
+ if (encoding)
+ {
+ if (strcmp(encoding, "base64") != 0)
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
+ NULL,
+ _("Got unrecognized encoding '%s'"),
+ encoding);
+
+ /* Decode into the right pool. */
+ val_str = svn_base64_decode_string(cdata, ctx->pool);
+ }
+ else
+ {
+ /* Copy into the right pool. */
+ val_str = svn_string_dup(cdata, ctx->pool);
+ }
+
+ /* The current path sits on the RESPONSE state. Gather up all the
+ state from this PROPVAL to the (grandparent) RESPONSE state,
+ and grab the path from there.
+
+ Now, it would be nice if we could, at this point, know that
+ the status code for this property indicated a problem -- then
+ we could simply bail out here and ignore the property.
+ Sadly, though, we might get the status code *after* we get
+ the property value. So we'll carry on with our processing
+ here, setting the property and value as expected. Once we
+ know for sure the status code associate with the property,
+ we'll decide its fate. */
+ gathered = svn_ra_serf__xml_gather_since(xes, RESPONSE);
+
+ /* These will be dup'd into CTX->POOL, as necessary. */
+ path = svn_hash_gets(gathered, "path");
+ if (path == NULL)
+ path = ctx->path;
+
+ ns = svn_hash_gets(attrs, "ns");
+ name = apr_pstrdup(ctx->pool,
+ svn_hash_gets(attrs, "name"));
+
+ altvalue = svn_hash_gets(attrs, "altvalue");
+ if (altvalue != NULL)
+ val_str = svn_string_create(altvalue, ctx->pool);
+
+ svn_ra_serf__set_ver_prop(ctx->ps_props,
+ path, ctx->rev, ns, name, val_str,
+ ctx->pool);
+ }
+ else
+ {
+ apr_hash_t *gathered;
+
+ SVN_ERR_ASSERT(leaving_state == PROPSTAT);
+
+ gathered = svn_ra_serf__xml_gather_since(xes, PROPSTAT);
+
+ /* If we've squirreled away a note that says we want to ignore
+ these properties, we'll do so. Otherwise, we need to copy
+ them from the temporary hash into the ctx->ret_props hash. */
+ if (! svn_hash_gets(gathered, "ignore-prop"))
+ {
+ SVN_ERR(svn_ra_serf__walk_all_paths(ctx->ps_props, ctx->rev,
+ copy_into_ret_props, ctx,
+ scratch_pool));
+ }
+
+ ctx->ps_props = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
const svn_string_t *
svn_ra_serf__get_ver_prop_string(apr_hash_t *props,
const char *path,
@@ -127,14 +357,14 @@ svn_ra_serf__get_ver_prop_string(apr_hash_t *props,
ver_props = apr_hash_get(props, &rev, sizeof(rev));
if (ver_props)
{
- path_props = apr_hash_get(ver_props, path, APR_HASH_KEY_STRING);
+ path_props = svn_hash_gets(ver_props, path);
if (path_props)
{
- ns_props = apr_hash_get(path_props, ns, APR_HASH_KEY_STRING);
+ ns_props = svn_hash_gets(path_props, ns);
if (ns_props)
{
- val = apr_hash_get(ns_props, name, APR_HASH_KEY_STRING);
+ val = svn_hash_gets(ns_props, name);
}
}
}
@@ -196,28 +426,28 @@ svn_ra_serf__set_ver_prop(apr_hash_t *props,
ver_props);
}
- path_props = apr_hash_get(ver_props, path, APR_HASH_KEY_STRING);
+ path_props = svn_hash_gets(ver_props, path);
if (!path_props)
{
path_props = apr_hash_make(pool);
path = apr_pstrdup(pool, path);
- apr_hash_set(ver_props, path, APR_HASH_KEY_STRING, path_props);
+ svn_hash_sets(ver_props, path, path_props);
/* todo: we know that we'll fail the next check, but fall through
* for now for simplicity's sake.
*/
}
- ns_props = apr_hash_get(path_props, ns, APR_HASH_KEY_STRING);
+ ns_props = svn_hash_gets(path_props, ns);
if (!ns_props)
{
ns_props = apr_hash_make(pool);
ns = apr_pstrdup(pool, ns);
- apr_hash_set(path_props, ns, APR_HASH_KEY_STRING, ns_props);
+ svn_hash_sets(path_props, ns, ns_props);
}
- apr_hash_set(ns_props, name, APR_HASH_KEY_STRING, val);
+ svn_hash_sets(ns_props, name, val);
}
void
@@ -230,205 +460,14 @@ svn_ra_serf__set_prop(apr_hash_t *props,
val, pool);
}
-static prop_info_t *
-push_state(svn_ra_serf__xml_parser_t *parser,
- svn_ra_serf__propfind_context_t *propfind,
- prop_state_e state)
-{
- svn_ra_serf__xml_push_state(parser, state);
-
- if (state == PROPVAL)
- {
- prop_info_t *info;
-
- info = apr_pcalloc(parser->state->pool, sizeof(*info));
- info->pool = parser->state->pool;
-
- parser->state->private = info;
- }
-
- return parser->state->private;
-}
-
-/*
- * Expat callback invoked on a start element tag for a PROPFIND response.
- */
-static svn_error_t *
-start_propfind(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name,
- const char **attrs)
-{
- svn_ra_serf__propfind_context_t *ctx = userData;
- prop_state_e state;
- prop_info_t *info;
-
- state = parser->state->current_state;
-
- if (state == NONE && strcmp(name.name, "response") == 0)
- {
- svn_ra_serf__xml_push_state(parser, RESPONSE);
- }
- else if (state == RESPONSE && strcmp(name.name, "href") == 0)
- {
- info = push_state(parser, ctx, PROPVAL);
- info->ns = name.namespace;
- info->name = apr_pstrdup(info->pool, name.name);
- }
- else if (state == RESPONSE && strcmp(name.name, "prop") == 0)
- {
- push_state(parser, ctx, PROP);
- }
- else if (state == PROP)
- {
- info = push_state(parser, ctx, PROPVAL);
- info->ns = name.namespace;
- info->name = apr_pstrdup(info->pool, name.name);
- info->encoding = apr_pstrdup(info->pool,
- svn_xml_get_attr_value("V:encoding", attrs));
- }
-
- return SVN_NO_ERROR;
-}
-
-/*
- * Expat callback invoked on an end element tag for a PROPFIND response.
- */
-static svn_error_t *
-end_propfind(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name)
-{
- svn_ra_serf__propfind_context_t *ctx = userData;
- prop_state_e state;
- prop_info_t *info;
-
- state = parser->state->current_state;
- info = parser->state->private;
-
- if (state == RESPONSE && strcmp(name.name, "response") == 0)
- {
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == PROP && strcmp(name.name, "prop") == 0)
- {
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == PROPVAL)
- {
- const char *ns, *pname, *val;
- svn_string_t *val_str;
-
- /* if we didn't see a CDATA element, we may want the tag name
- * as long as it isn't equivalent to the property name.
- */
- if (!info->val)
- {
- if (strcmp(info->name, name.name) != 0)
- {
- info->val = name.name;
- info->val_len = strlen(info->val);
- }
- else
- {
- info->val = "";
- info->val_len = 0;
- }
- }
-
- if (parser->state->prev->current_state == RESPONSE &&
- strcmp(name.name, "href") == 0)
- {
- if (strcmp(ctx->depth, "1") == 0)
- {
- ctx->current_path =
- svn_urlpath__canonicalize(info->val, ctx->pool);
- }
- else
- {
- ctx->current_path = ctx->path;
- }
- }
- else if (info->encoding)
- {
- if (strcmp(info->encoding, "base64") == 0)
- {
- svn_string_t encoded;
- const svn_string_t *decoded;
-
- encoded.data = info->val;
- encoded.len = info->val_len;
-
- decoded = svn_base64_decode_string(&encoded, parser->state->pool);
- info->val = decoded->data;
- info->val_len = decoded->len;
- }
- else
- {
- return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
- NULL,
- _("Got unrecognized encoding '%s'"),
- info->encoding);
- }
- }
-
- ns = apr_pstrdup(ctx->pool, info->ns);
- pname = apr_pstrdup(ctx->pool, info->name);
- val = apr_pmemdup(ctx->pool, info->val, info->val_len);
- val_str = svn_string_ncreate(val, info->val_len, ctx->pool);
-
- /* set the return props and update our cache too. */
- svn_ra_serf__set_ver_prop(ctx->ret_props,
- ctx->current_path, ctx->rev,
- ns, pname, val_str,
- ctx->pool);
-
- svn_ra_serf__xml_pop_state(parser);
- }
-
- return SVN_NO_ERROR;
-}
-
-/*
- * Expat callback invoked on CDATA elements in a PROPFIND response.
- *
- * This callback can be called multiple times.
- */
-static svn_error_t *
-cdata_propfind(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- const char *data,
- apr_size_t len)
-{
- svn_ra_serf__propfind_context_t *ctx = userData;
- prop_state_e state;
- prop_info_t *info;
-
- UNUSED_CTX(ctx);
-
- state = parser->state->current_state;
- info = parser->state->private;
-
- if (state == PROPVAL)
- {
- svn_ra_serf__expand_string(&info->val, &info->val_len, data, len,
- info->pool);
- }
-
- return SVN_NO_ERROR;
-}
static svn_error_t *
setup_propfind_headers(serf_bucket_t *headers,
void *setup_baton,
apr_pool_t *pool)
{
- svn_ra_serf__propfind_context_t *ctx = setup_baton;
+ propfind_context_t *ctx = setup_baton;
- if (ctx->conn->using_compression)
- {
- serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
- }
serf_bucket_headers_setn(headers, "Depth", ctx->depth);
if (ctx->label)
{
@@ -447,7 +486,7 @@ create_propfind_body(serf_bucket_t **bkt,
serf_bucket_alloc_t *alloc,
apr_pool_t *pool)
{
- svn_ra_serf__propfind_context_t *ctx = setup_baton;
+ propfind_context_t *ctx = setup_baton;
serf_bucket_t *body_bkt, *tmp;
const svn_ra_serf__dav_props_t *prop;
@@ -487,7 +526,7 @@ create_propfind_body(serf_bucket_t **bkt,
}
/* If we're not doing an allprop, add <prop> tags. */
- if (requested_allprop == FALSE)
+ if (!requested_allprop)
{
tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>",
sizeof("<prop>")-1,
@@ -501,7 +540,7 @@ create_propfind_body(serf_bucket_t **bkt,
serf_bucket_aggregate_prepend(body_bkt, tmp);
- if (requested_allprop == FALSE)
+ if (!requested_allprop)
{
tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>",
sizeof("</prop>")-1,
@@ -520,7 +559,7 @@ create_propfind_body(serf_bucket_t **bkt,
svn_error_t *
-svn_ra_serf__deliver_props(svn_ra_serf__propfind_context_t **prop_ctx,
+svn_ra_serf__deliver_props(svn_ra_serf__handler_t **propfind_handler,
apr_hash_t *ret_props,
svn_ra_serf__session_t *sess,
svn_ra_serf__connection_t *conn,
@@ -531,9 +570,9 @@ svn_ra_serf__deliver_props(svn_ra_serf__propfind_context_t **prop_ctx,
svn_ra_serf__list_t **done_list,
apr_pool_t *pool)
{
- svn_ra_serf__propfind_context_t *new_prop_ctx;
+ propfind_context_t *new_prop_ctx;
svn_ra_serf__handler_t *handler;
- svn_ra_serf__xml_parser_t *parser_ctx;
+ svn_ra_serf__xml_context_t *xmlctx;
new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx));
@@ -542,7 +581,6 @@ svn_ra_serf__deliver_props(svn_ra_serf__propfind_context_t **prop_ctx,
new_prop_ctx->find_props = find_props;
new_prop_ctx->ret_props = ret_props;
new_prop_ctx->depth = depth;
- new_prop_ctx->done = FALSE;
new_prop_ctx->sess = sess;
new_prop_ctx->conn = conn;
new_prop_ctx->rev = rev;
@@ -557,7 +595,13 @@ svn_ra_serf__deliver_props(svn_ra_serf__propfind_context_t **prop_ctx,
new_prop_ctx->label = NULL;
}
- handler = apr_pcalloc(pool, sizeof(*handler));
+ xmlctx = svn_ra_serf__xml_context_create(propfind_ttable,
+ propfind_opened,
+ propfind_closed,
+ NULL,
+ new_prop_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
handler->method = "PROPFIND";
handler->path = path;
@@ -572,64 +616,30 @@ svn_ra_serf__deliver_props(svn_ra_serf__propfind_context_t **prop_ctx,
new_prop_ctx->handler = handler;
- parser_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx->parser_ctx));
- parser_ctx->pool = pool;
- parser_ctx->user_data = new_prop_ctx;
- parser_ctx->start = start_propfind;
- parser_ctx->end = end_propfind;
- parser_ctx->cdata = cdata_propfind;
- parser_ctx->status_code = &new_prop_ctx->status_code;
- parser_ctx->done = &new_prop_ctx->done;
- parser_ctx->done_list = new_prop_ctx->done_list;
- parser_ctx->done_item = &new_prop_ctx->done_item;
-
- new_prop_ctx->parser_ctx = parser_ctx;
-
- handler->response_handler = svn_ra_serf__handle_xml_parser;
- handler->response_baton = parser_ctx;
-
- /* create request */
- svn_ra_serf__request_create(new_prop_ctx->handler);
-
- *prop_ctx = new_prop_ctx;
+ *propfind_handler = handler;
return SVN_NO_ERROR;
}
-svn_boolean_t
-svn_ra_serf__propfind_is_done(svn_ra_serf__propfind_context_t *ctx)
-{
- return ctx->done;
-}
-
-int
-svn_ra_serf__propfind_status_code(svn_ra_serf__propfind_context_t *ctx)
-{
- return ctx->status_code;
-}
/*
* This helper function will block until the PROP_CTX indicates that is done
* or another error is returned.
*/
svn_error_t *
-svn_ra_serf__wait_for_props(svn_ra_serf__propfind_context_t *prop_ctx,
- svn_ra_serf__session_t *sess,
- apr_pool_t *pool)
+svn_ra_serf__wait_for_props(svn_ra_serf__handler_t *handler,
+ apr_pool_t *scratch_pool)
{
- svn_error_t *err, *err2;
+ svn_error_t *err;
+ svn_error_t *err2;
- err = svn_ra_serf__context_run_wait(&prop_ctx->done, sess, pool);
+ err = svn_ra_serf__context_run_one(handler, scratch_pool);
- err2 = svn_ra_serf__error_on_status(prop_ctx->status_code,
- prop_ctx->path, NULL);
- if (err2)
- {
- svn_error_clear(err);
- return err2;
- }
+ err2 = svn_ra_serf__error_on_status(handler->sline,
+ handler->path,
+ handler->location);
- return err;
+ return svn_error_compose_create(err2, err);
}
/*
@@ -646,45 +656,63 @@ svn_ra_serf__retrieve_props(apr_hash_t **results,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- svn_ra_serf__propfind_context_t *prop_ctx;
+ svn_ra_serf__handler_t *handler;
*results = apr_hash_make(result_pool);
- SVN_ERR(svn_ra_serf__deliver_props(&prop_ctx, *results, sess, conn, url,
+ SVN_ERR(svn_ra_serf__deliver_props(&handler, *results, sess, conn, url,
rev, depth, props, NULL, result_pool));
- SVN_ERR(svn_ra_serf__wait_for_props(prop_ctx, sess, result_pool));
+ SVN_ERR(svn_ra_serf__wait_for_props(handler, scratch_pool));
return SVN_NO_ERROR;
}
svn_error_t *
-svn_ra_serf__walk_all_props(apr_hash_t *props,
- const char *name,
- svn_revnum_t rev,
- svn_ra_serf__walker_visitor_t walker,
- void *baton,
- apr_pool_t *scratch_pool)
+svn_ra_serf__fetch_node_props(apr_hash_t **results,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ const svn_ra_serf__dav_props_t *which_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- apr_hash_index_t *ns_hi;
+ apr_hash_t *multiprops;
apr_hash_t *ver_props;
- apr_hash_t *path_props;
- apr_pool_t *iterpool;
- ver_props = apr_hash_get(props, &rev, sizeof(rev));
- if (!ver_props)
- {
- return SVN_NO_ERROR;
- }
+ /* Note: a couple extra hash tables and whatnot get into RESULT_POOL.
+ Not a big deal at this point. Theoretically, we could fetch all
+ props into SCRATCH_POOL, then copy just the REVISION/URL props
+ into RESULT_POOL. Too much work for too little gain... */
+ SVN_ERR(svn_ra_serf__retrieve_props(&multiprops, conn->session, conn,
+ url, revision, "0", which_props,
+ result_pool, scratch_pool));
- path_props = apr_hash_get(ver_props, name, APR_HASH_KEY_STRING);
- if (!path_props)
+ ver_props = apr_hash_get(multiprops, &revision, sizeof(revision));
+ if (ver_props != NULL)
{
- return SVN_NO_ERROR;
+ *results = svn_hash_gets(ver_props, url);
+ if (*results != NULL)
+ return SVN_NO_ERROR;
}
+ return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
+ _("The PROPFIND response did not include "
+ "the requested properties"));
+}
+
+
+svn_error_t *
+svn_ra_serf__walk_node_props(apr_hash_t *props,
+ svn_ra_serf__walker_visitor_t walker,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ apr_hash_index_t *ns_hi;
+
iterpool = svn_pool_create(scratch_pool);
- for (ns_hi = apr_hash_first(scratch_pool, path_props); ns_hi;
+ for (ns_hi = apr_hash_first(scratch_pool, props); ns_hi;
ns_hi = apr_hash_next(ns_hi))
{
void *ns_val;
@@ -718,6 +746,31 @@ svn_ra_serf__walk_all_props(apr_hash_t *props,
svn_error_t *
+svn_ra_serf__walk_all_props(apr_hash_t *props,
+ const char *name,
+ svn_revnum_t rev,
+ svn_ra_serf__walker_visitor_t walker,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *ver_props;
+ apr_hash_t *path_props;
+
+ ver_props = apr_hash_get(props, &rev, sizeof(rev));
+ if (!ver_props)
+ return SVN_NO_ERROR;
+
+ path_props = svn_hash_gets(ver_props, name);
+ if (!path_props)
+ return SVN_NO_ERROR;
+
+ return svn_error_trace(svn_ra_serf__walk_node_props(path_props,
+ walker, baton,
+ scratch_pool));
+}
+
+
+svn_error_t *
svn_ra_serf__walk_all_paths(apr_hash_t *props,
svn_revnum_t rev,
svn_ra_serf__path_rev_walker_t walker,
@@ -830,7 +883,7 @@ set_flat_props(void *baton,
prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool);
if (prop_name != NULL)
- apr_hash_set(props, prop_name, APR_HASH_KEY_STRING, value);
+ svn_hash_sets(props, prop_name, value);
return SVN_NO_ERROR;
}
@@ -839,15 +892,13 @@ set_flat_props(void *baton,
svn_error_t *
svn_ra_serf__flatten_props(apr_hash_t **flat_props,
apr_hash_t *props,
- const char *path,
- svn_revnum_t revision,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
*flat_props = apr_hash_make(result_pool);
- return svn_error_trace(svn_ra_serf__walk_all_props(
- props, path, revision,
+ return svn_error_trace(svn_ra_serf__walk_node_props(
+ props,
set_flat_props,
*flat_props /* baton */,
scratch_pool));
@@ -882,7 +933,7 @@ select_revprops(void *baton,
return SVN_NO_ERROR;
}
- apr_hash_set(revprops, prop_name, APR_HASH_KEY_STRING, val);
+ svn_hash_sets(revprops, prop_name, val);
return SVN_NO_ERROR;
}
@@ -906,7 +957,7 @@ svn_ra_serf__select_revprops(apr_hash_t **revprops,
/*
- * Contact the server (using SESSION and CONN) to calculate baseline
+ * Contact the server (using CONN) to calculate baseline
* information for BASELINE_URL at REVISION (which may be
* SVN_INVALID_REVNUM to query the HEAD revision).
*
@@ -918,199 +969,254 @@ svn_ra_serf__select_revprops(apr_hash_t **revprops,
static svn_error_t *
retrieve_baseline_info(svn_revnum_t *actual_revision,
const char **basecoll_url_p,
- svn_ra_serf__session_t *session,
svn_ra_serf__connection_t *conn,
const char *baseline_url,
svn_revnum_t revision,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
apr_hash_t *props;
+ apr_hash_t *dav_props;
const char *basecoll_url;
- const char *version_name;
-
- SVN_ERR(svn_ra_serf__retrieve_props(&props, session, conn,
- baseline_url, revision, "0",
- baseline_props,
- pool, pool));
- basecoll_url = svn_ra_serf__get_ver_prop(props, baseline_url, revision,
- "DAV:", "baseline-collection");
+ SVN_ERR(svn_ra_serf__fetch_node_props(&props, conn,
+ baseline_url, revision,
+ baseline_props,
+ scratch_pool, scratch_pool));
+ dav_props = apr_hash_get(props, "DAV:", 4);
+ /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL. */
+ basecoll_url = svn_prop_get_value(dav_props, "baseline-collection");
if (!basecoll_url)
{
return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
_("The PROPFIND response did not include "
"the requested baseline-collection value"));
}
+ *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool);
- *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, pool);
-
- version_name = svn_ra_serf__get_ver_prop(props, baseline_url, revision,
- "DAV:", SVN_DAV__VERSION_NAME);
- if (!version_name)
+ if (actual_revision)
{
- return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
- _("The PROPFIND response did not include "
- "the requested version-name value"));
+ const char *version_name;
+
+ version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME);
+ if (!version_name)
+ return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
+ _("The PROPFIND response did not include "
+ "the requested version-name value"));
+
+ *actual_revision = SVN_STR_TO_REV(version_name);
}
- if (actual_revision)
+ return SVN_NO_ERROR;
+}
+
+
+/* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest
+ revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline
+ collection URL is also returned.
+
+ Do the work over CONN.
+
+ *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All
+ temporary allocations will be made in SCRATCH_POOL. */
+static svn_error_t *
+v1_get_youngest_revnum(svn_revnum_t *youngest,
+ const char **basecoll_url,
+ svn_ra_serf__connection_t *conn,
+ const char *vcc_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *baseline_url;
+ const char *bc_url;
+
+ /* Fetching DAV:checked-in from the VCC (with no Label: to specify a
+ revision) will return the latest Baseline resource's URL. */
+ SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, conn, vcc_url,
+ SVN_INVALID_REVNUM,
+ "checked-in",
+ scratch_pool, scratch_pool));
+ if (!baseline_url)
{
- *actual_revision = SVN_STR_TO_REV(version_name);
+ return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
+ _("The OPTIONS response did not include "
+ "the requested checked-in value"));
}
+ baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool);
+
+ /* From the Baseline resource, we can fetch the DAV:baseline-collection
+ and DAV:version-name properties. The latter is the revision number,
+ which is formally the name used in Label: headers. */
+
+ /* First check baseline information cache. */
+ SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url,
+ youngest,
+ conn->session->blncache,
+ baseline_url,
+ scratch_pool));
+ if (!bc_url)
+ {
+ SVN_ERR(retrieve_baseline_info(youngest, &bc_url, conn,
+ baseline_url, SVN_INVALID_REVNUM,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_ra_serf__blncache_set(conn->session->blncache,
+ baseline_url, *youngest,
+ bc_url, scratch_pool));
+ }
+
+ if (basecoll_url != NULL)
+ *basecoll_url = apr_pstrdup(result_pool, bc_url);
return SVN_NO_ERROR;
}
+
svn_error_t *
-svn_ra_serf__get_baseline_info(const char **bc_url,
- const char **bc_relative,
- svn_ra_serf__session_t *session,
- svn_ra_serf__connection_t *conn,
- const char *url,
- svn_revnum_t revision,
- svn_revnum_t *latest_revnum,
- apr_pool_t *pool)
+svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest,
+ svn_ra_serf__session_t *session,
+ apr_pool_t *scratch_pool)
{
- const char *vcc_url, *relative_url, *basecoll_url, *baseline_url;
+ const char *vcc_url;
- /* No URL? No sweat. We'll use the session URL. */
- if (! url)
- url = session->session_url.path;
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
+ return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum(
+ youngest, session->conns[0], scratch_pool));
- /* If the caller didn't provide a specific connection for us to use,
- we'll use the default one. */
- if (! conn)
- conn = session->conns[0];
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, scratch_pool));
+
+ return svn_error_trace(v1_get_youngest_revnum(youngest, NULL,
+ session->conns[0], vcc_url,
+ scratch_pool, scratch_pool));
+}
+
+
+/* Set *BC_URL to the baseline collection url for REVISION. If REVISION
+ is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used.
+
+ *REVNUM_USED will be set to the revision used.
+ Uses the specified CONN, which is part of SESSION.
+
+ All allocations (results and temporary) are performed in POOL. */
+static svn_error_t *
+get_baseline_info(const char **bc_url,
+ svn_revnum_t *revnum_used,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
/* If we detected HTTP v2 support on the server, we can construct
the baseline collection URL ourselves, and fetch the latest
revision (if needed) with an OPTIONS request. */
if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
{
- svn_revnum_t actual_revision;
-
if (SVN_IS_VALID_REVNUM(revision))
{
- actual_revision = revision;
+ *revnum_used = revision;
}
else
{
- svn_ra_serf__options_context_t *opt_ctx;
-
- SVN_ERR(svn_ra_serf__create_options_req(&opt_ctx, session, conn,
- session->session_url.path,
- pool));
- SVN_ERR(svn_ra_serf__context_run_wait(
- svn_ra_serf__get_options_done_ptr(opt_ctx), session, pool));
-
- actual_revision = svn_ra_serf__options_get_youngest_rev(opt_ctx);
- if (! SVN_IS_VALID_REVNUM(actual_revision))
+ SVN_ERR(svn_ra_serf__v2_get_youngest_revnum(
+ revnum_used, conn, pool));
+ if (! SVN_IS_VALID_REVNUM(*revnum_used))
return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
_("The OPTIONS response did not include "
"the youngest revision"));
}
- basecoll_url = apr_psprintf(pool, "%s/%ld",
- session->rev_root_stub, actual_revision);
- if (latest_revnum)
- *latest_revnum = actual_revision;
+ *bc_url = apr_psprintf(pool, "%s/%ld",
+ session->rev_root_stub, *revnum_used);
}
/* Otherwise, we fall back to the old VCC_URL PROPFIND hunt. */
else
{
+ const char *vcc_url;
+
SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, conn, pool));
if (SVN_IS_VALID_REVNUM(revision))
{
/* First check baseline information cache. */
- SVN_ERR(svn_ra_serf__blncache_get_bc_url(&basecoll_url,
+ SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url,
session->blncache,
revision, pool));
-
- if (!basecoll_url)
+ if (!*bc_url)
{
- SVN_ERR(retrieve_baseline_info(NULL, &basecoll_url, session,
- conn, vcc_url, revision, pool));
+ SVN_ERR(retrieve_baseline_info(NULL, bc_url, conn,
+ vcc_url, revision, pool, pool));
SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL,
- revision, basecoll_url, pool));
+ revision, *bc_url, pool));
}
- if (latest_revnum)
- {
- *latest_revnum = revision;
- }
+ *revnum_used = revision;
}
else
{
- apr_hash_t *props;
- svn_revnum_t actual_revision;
-
- SVN_ERR(svn_ra_serf__retrieve_props(&props, session, conn,
- vcc_url, revision, "0",
- checked_in_props,
- pool, pool));
- baseline_url = svn_ra_serf__get_ver_prop(props, vcc_url, revision,
- "DAV:", "checked-in");
- if (!baseline_url)
- {
- return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
- _("The OPTIONS response did not include "
- "the requested checked-in value"));
- }
+ SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url,
+ conn, vcc_url,
+ pool, pool));
+ }
+ }
- baseline_url = svn_urlpath__canonicalize(baseline_url, pool);
+ return SVN_NO_ERROR;
+}
- /* First check baseline information cache. */
- SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&basecoll_url,
- &actual_revision,
- session->blncache,
- baseline_url,
- pool));
- if (!basecoll_url)
- {
- SVN_ERR(retrieve_baseline_info(&actual_revision, &basecoll_url,
- session, conn,
- baseline_url, revision, pool));
- SVN_ERR(svn_ra_serf__blncache_set(session->blncache,
- baseline_url, actual_revision,
- basecoll_url, pool));
- }
- if (latest_revnum)
- {
- *latest_revnum = actual_revision;
- }
- }
- }
+svn_error_t *
+svn_ra_serf__get_stable_url(const char **stable_url,
+ svn_revnum_t *latest_revnum,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *basecoll_url;
+ const char *repos_relpath;
+ svn_revnum_t revnum_used;
- /* And let's not forget to calculate our relative path. */
- SVN_ERR(svn_ra_serf__get_relative_path(&relative_url, url, session,
- conn, pool));
+ /* No URL? No sweat. We'll use the session URL. */
+ if (! url)
+ url = session->session_url.path;
+
+ /* If the caller didn't provide a specific connection for us to use,
+ we'll use the default connection. */
+ if (! conn)
+ conn = session->conns[0];
+
+ SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used,
+ session, conn, revision, scratch_pool));
+ SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url,
+ session, conn, scratch_pool));
+
+ *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath,
+ result_pool);
+ if (latest_revnum)
+ *latest_revnum = revnum_used;
- *bc_url = basecoll_url;
- *bc_relative = relative_url;
return SVN_NO_ERROR;
}
svn_error_t *
svn_ra_serf__get_resource_type(svn_node_kind_t *kind,
- apr_hash_t *props,
- const char *url,
- svn_revnum_t revision)
+ apr_hash_t *props)
{
+ apr_hash_t *dav_props;
const char *res_type;
- res_type = svn_ra_serf__get_ver_prop(props, url, revision,
- "DAV:", "resourcetype");
+ dav_props = apr_hash_get(props, "DAV:", 4);
+ res_type = svn_prop_get_value(dav_props, "resourcetype");
if (!res_type)
{
/* How did this happen? */
return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
_("The PROPFIND response did not include the "
- "requested resourcetype value"));
+ "requested resourcetype value"));
}
if (strcmp(res_type, "collection") == 0)
@@ -1124,3 +1230,34 @@ svn_ra_serf__get_resource_type(svn_node_kind_t *kind,
return SVN_NO_ERROR;
}
+
+
+svn_error_t *
+svn_ra_serf__fetch_dav_prop(const char **value,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *props;
+ apr_hash_t *dav_props;
+
+ SVN_ERR(svn_ra_serf__fetch_node_props(&props, conn, url, revision,
+ checked_in_props,
+ scratch_pool, scratch_pool));
+ dav_props = apr_hash_get(props, "DAV:", 4);
+ if (dav_props == NULL)
+ return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
+ _("The PROPFIND response did not include "
+ "the requested 'DAV:' properties"));
+
+ /* We wouldn't get here if the resource was not found (404), so the
+ property should be present.
+
+ Note: it is okay to call apr_pstrdup() with NULL. */
+ *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra_serf/ra_serf.h b/subversion/libsvn_ra_serf/ra_serf.h
index 5c2116f..335a9e3 100644
--- a/subversion/libsvn_ra_serf/ra_serf.h
+++ b/subversion/libsvn_ra_serf/ra_serf.h
@@ -26,7 +26,7 @@
#include <serf.h>
-#include <expat.h>
+#include <expat.h> /* for XML_Parser */
#include <apr_uri.h>
#include "svn_types.h"
@@ -39,6 +39,8 @@
#include "svn_dirent_uri.h"
#include "private/svn_dav_protocol.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_editor.h"
#include "blncache.h"
@@ -48,18 +50,16 @@ extern "C" {
/* Enforce the minimum version of serf. */
-#if !SERF_VERSION_AT_LEAST(0, 7, 1)
-#error Please update your version of serf to at least 0.7.1.
+#if !SERF_VERSION_AT_LEAST(1, 2, 1)
+#error Please update your version of serf to at least 1.2.1.
#endif
/** Use this to silence compiler warnings about unused parameters. */
#define UNUSED_CTX(x) ((void)(x))
-/** Our User-Agent string. */
-#define USER_AGENT "SVN/" SVN_VER_NUMBER " serf/" \
- APR_STRINGIFY(SERF_MAJOR_VERSION) "." \
- APR_STRINGIFY(SERF_MINOR_VERSION) "." \
- APR_STRINGIFY(SERF_PATCH_VERSION)
+/** Wait duration (in microseconds) used in calls to serf_context_run() */
+#define SVN_RA_SERF__CONTEXT_RUN_DURATION 500000
+
/* Forward declarations. */
@@ -73,15 +73,8 @@ typedef struct svn_ra_serf__connection_t {
/* Bucket allocator for this connection. */
serf_bucket_alloc_t *bkt_alloc;
- /* Host name */
- const char *hostname;
-
- /* Are we using ssl */
- svn_boolean_t using_ssl;
- int server_cert_failures; /* Collected cert failures in chain */
-
- /* Should we ask for compressed responses? */
- svn_boolean_t using_compression;
+ /* Collected cert failures in chain. */
+ int server_cert_failures;
/* What was the last HTTP status code we got on this connection? */
int last_status_code;
@@ -93,11 +86,15 @@ typedef struct svn_ra_serf__connection_t {
svn_ra_serf__session_t *session;
- /* user agent string */
- const char *useragent;
-
} svn_ra_serf__connection_t;
+/** Maximum value we'll allow for the http-max-connections config option.
+ *
+ * Note: minimum 2 connections are required for ra_serf to function
+ * correctly!
+ */
+#define SVN_RA_SERF__MAX_CONNECTIONS_LIMIT 8
+
/*
* The master serf RA session.
*
@@ -110,14 +107,21 @@ struct svn_ra_serf__session_t {
/* The current context */
serf_context_t *context;
+ /* The maximum number of connections we'll use for parallelized
+ fetch operations (updates, etc.) */
+ apr_int64_t max_connections;
+
/* Are we using ssl */
svn_boolean_t using_ssl;
/* Should we ask for compressed responses? */
svn_boolean_t using_compression;
+ /* The user agent string */
+ const char *useragent;
+
/* The current connection */
- svn_ra_serf__connection_t **conns;
+ svn_ra_serf__connection_t *conns[SVN_RA_SERF__MAX_CONNECTIONS_LIMIT];
int num_conns;
int cur_conn;
@@ -129,6 +133,17 @@ struct svn_ra_serf__session_t {
apr_uri_t repos_root;
const char *repos_root_str;
+ /* The server is not Apache/mod_dav_svn (directly) and only supports
+ HTTP/1.0. Thus, we cannot send chunked requests. */
+ svn_boolean_t http10;
+
+ /* Should we use Transfer-Encoding: chunked for HTTP/1.1 servers. */
+ svn_boolean_t using_chunked_requests;
+
+ /* Do we need to detect whether the connection supports chunked requests?
+ i.e. is there a (reverse) proxy that does not support them? */
+ svn_boolean_t detect_chunking;
+
/* Our Version-Controlled-Configuration; may be NULL until we know it. */
const char *vcc_url;
@@ -148,6 +163,9 @@ struct svn_ra_serf__session_t {
svn_cancel_func_t cancel_func;
void *cancel_baton;
+ /* Ev2 shim callbacks */
+ svn_delta_shim_callbacks_t *shim_callbacks;
+
/* Error that we've received but not yet returned upstream. */
svn_error_t *pending_error;
@@ -163,8 +181,12 @@ struct svn_ra_serf__session_t {
constants' addresses, therefore). */
apr_hash_t *capabilities;
+ /* Activity collection URL. (Cached from the initial OPTIONS
+ request when run against HTTPv1 servers.) */
+ const char *activity_collection_url;
+
/* Are we using a proxy? */
- int using_proxy;
+ svn_boolean_t using_proxy;
const char *proxy_username;
const char *proxy_password;
@@ -178,7 +200,7 @@ struct svn_ra_serf__session_t {
const char *uuid;
/* Connection timeout value */
- long timeout;
+ apr_interval_time_t timeout;
/* HTTPv1 flags */
svn_tristate_t supports_deadprop_count;
@@ -205,9 +227,33 @@ struct svn_ra_serf__session_t {
const char *vtxn_stub; /* for accessing transactions (i.e. txnprops) */
const char *vtxn_root_stub; /* for accessing TXN/PATH pairs */
+ /* Hash mapping const char * server-supported POST types to
+ disinteresting-but-non-null values. */
+ apr_hash_t *supported_posts;
+
/*** End HTTP v2 stuff ***/
svn_ra_serf__blncache_t *blncache;
+
+ /* Trisate flag that indicates user preference for using bulk updates
+ (svn_tristate_true) with all the properties and content in the
+ update-report response. If svn_tristate_false, request a skelta
+ update-report with inlined properties. If svn_tristate_unknown then use
+ server preference. */
+ svn_tristate_t bulk_updates;
+
+ /* Indicates if the server wants bulk update requests (Prefer) or only
+ accepts skelta requests (Off). If this value is On both options are
+ allowed. */
+ const char *server_allows_bulk;
+
+ /* Indicates if the server supports sending inlined props in update editor
+ * in skelta mode (send-all == 'false'). */
+ svn_boolean_t supports_inline_props;
+
+ /* Indicates whether the server supports issuing replay REPORTs
+ against rev resources (children of `rev_stub', elsestruct). */
+ svn_boolean_t supports_rev_rsrc_replay;
};
#define SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(sess) ((sess)->me_resource != NULL)
@@ -272,40 +318,21 @@ static const svn_ra_serf__dav_props_t all_props[] =
{ NULL }
};
-static const svn_ra_serf__dav_props_t vcc_props[] =
-{
- { "DAV:", "version-controlled-configuration" },
- { NULL }
-};
-
static const svn_ra_serf__dav_props_t check_path_props[] =
{
{ "DAV:", "resourcetype" },
{ NULL }
};
-static const svn_ra_serf__dav_props_t uuid_props[] =
+static const svn_ra_serf__dav_props_t type_and_checksum_props[] =
{
- { SVN_DAV_PROP_NS_DAV, "repository-uuid" },
- { NULL }
-};
-
-static const svn_ra_serf__dav_props_t repos_root_props[] =
-{
- { SVN_DAV_PROP_NS_DAV, "baseline-relative-path" },
- { NULL }
-};
-
-static const svn_ra_serf__dav_props_t href_props[] =
-{
- { "DAV:", "href" },
+ { "DAV:", "resourcetype" },
+ { SVN_DAV_PROP_NS_DAV, "sha1-checksum" },
{ NULL }
};
/* WC props compatibility with ra_neon. */
-#define SVN_RA_SERF__WC_NAMESPACE SVN_PROP_WC_PREFIX "ra_dav:"
-#define SVN_RA_SERF__WC_ACTIVITY_URL SVN_RA_SERF__WC_NAMESPACE "activity-url"
-#define SVN_RA_SERF__WC_CHECKED_IN_URL SVN_RA_SERF__WC_NAMESPACE "version-url"
+#define SVN_RA_SERF__WC_CHECKED_IN_URL SVN_PROP_WC_PREFIX "ra_dav:version-url"
/** Serf utility functions **/
@@ -316,12 +343,6 @@ svn_ra_serf__conn_setup(apr_socket_t *sock,
void *baton,
apr_pool_t *pool);
-serf_bucket_t*
-svn_ra_serf__accept_response(serf_request_t *request,
- serf_bucket_t *stream,
- void *acceptor_baton,
- apr_pool_t *pool);
-
void
svn_ra_serf__conn_closed(serf_connection_t *conn,
void *closed_baton,
@@ -348,24 +369,6 @@ svn_ra_serf__handle_client_cert_pw(void *data,
const char *cert_path,
const char **password);
-/*
- * Create a REQUEST with an associated REQ_BKT in the SESSION.
- *
- * If HDRS_BKT is not-NULL, it will be set to a headers_bucket that
- * corresponds to the new request.
- *
- * The request will be METHOD at URL.
- *
- * If BODY_BKT is not-NULL, it will be sent as the request body.
- *
- * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header.
- */
-svn_error_t *
-svn_ra_serf__setup_serf_req(serf_request_t *request,
- serf_bucket_t **req_bkt, serf_bucket_t **hdrs_bkt,
- svn_ra_serf__connection_t *conn,
- const char *method, const char *url,
- serf_bucket_t *body_bkt, const char *content_type);
/*
* This function will run the serf context in SESS until *DONE is TRUE.
@@ -380,31 +383,22 @@ typedef svn_error_t *
(*svn_ra_serf__response_handler_t)(serf_request_t *request,
serf_bucket_t *response,
void *handler_baton,
- apr_pool_t *pool);
-
-/* Callback for setting up a complete serf request */
-typedef svn_error_t *
-(*svn_ra_serf__request_setup_t)(serf_request_t *request,
- void *setup_baton,
- serf_bucket_t **req_bkt,
- serf_response_acceptor_t *acceptor,
- void **acceptor_baton,
- svn_ra_serf__response_handler_t *handler,
- void **handler_baton,
- apr_pool_t *pool);
+ apr_pool_t *scratch_pool);
/* Callback for when a request body is needed. */
+/* ### should pass a scratch_pool */
typedef svn_error_t *
(*svn_ra_serf__request_body_delegate_t)(serf_bucket_t **body_bkt,
void *baton,
serf_bucket_alloc_t *alloc,
- apr_pool_t *pool);
+ apr_pool_t *request_pool);
/* Callback for when request headers are needed. */
+/* ### should pass a scratch_pool */
typedef svn_error_t *
(*svn_ra_serf__request_header_delegate_t)(serf_bucket_t *headers,
void *baton,
- apr_pool_t *pool);
+ apr_pool_t *request_pool);
/* Callback for when a response has an error. */
typedef svn_error_t *
@@ -413,6 +407,9 @@ typedef svn_error_t *
int status_code,
void *baton);
+/* ### we should reorder the types in this file. */
+typedef struct svn_ra_serf__server_error_t svn_ra_serf__server_error_t;
+
/*
* Structure that can be passed to our default handler to guide the
* execution of the request through its lifecycle.
@@ -427,10 +424,29 @@ typedef struct svn_ra_serf__handler_t {
/* The content-type of the request body. */
const char *body_type;
+ /* If TRUE then default Accept-Encoding request header is not configured for
+ request. If FALSE then 'gzip' accept encoding will be used if compression
+ enabled. */
+ svn_boolean_t custom_accept_encoding;
+
+ /* Has the request/response been completed? */
+ svn_boolean_t done;
+
+ /* If we captured an error from the server, then this will be non-NULL.
+ It will be allocated from HANDLER_POOL. */
+ svn_ra_serf__server_error_t *server_error;
+
/* The handler and baton pair for our handler. */
svn_ra_serf__response_handler_t response_handler;
void *response_baton;
+ /* When REPONSE_HANDLER is invoked, the following fields will be set
+ based on the response header. HANDLER_POOL must be non-NULL for these
+ values to be filled in. SLINE.REASON and LOCATION will be allocated
+ within HANDLER_POOL. */
+ serf_status_line sline; /* The parsed Status-Line */
+ const char *location; /* The Location: header, if any */
+
/* The handler and baton pair to be executed when a non-recoverable error
* is detected. If it is NULL in the presence of an error, an abort() may
* be triggered.
@@ -438,15 +454,6 @@ typedef struct svn_ra_serf__handler_t {
svn_ra_serf__response_error_t response_error;
void *response_error_baton;
- /* This function and baton will be executed when the request is about
- * to be delivered by serf.
- *
- * This just passes through serf's raw request creation parameters.
- * None of the other parameters will be utilized if this field is set.
- */
- svn_ra_serf__request_setup_t setup;
- void *setup_baton;
-
/* This function and baton pair allows for custom request headers to
* be set.
*
@@ -471,8 +478,34 @@ typedef struct svn_ra_serf__handler_t {
/* The connection and session to be used for this request. */
svn_ra_serf__connection_t *conn;
svn_ra_serf__session_t *session;
+
+ /* Internal flag to indicate we've parsed the headers. */
+ svn_boolean_t reading_body;
+
+ /* When this flag will be set, the core handler will discard any unread
+ portion of the response body. The registered response handler will
+ no longer be called. */
+ svn_boolean_t discard_body;
+
+ /* Pool for allocating SLINE.REASON and LOCATION. If this pool is NULL,
+ then the requestor does not care about SLINE and LOCATION. */
+ apr_pool_t *handler_pool;
+
} svn_ra_serf__handler_t;
+
+/* Run one request and process the response.
+
+ Similar to context_run_wait(), but this creates the request for HANDLER
+ and then waits for it to complete.
+
+ WARNING: context_run_wait() does NOT create a request, whereas this
+ function DOES. Avoid a double-create. */
+svn_error_t *
+svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
+ apr_pool_t *scratch_pool);
+
+
/*
* Helper function to queue a request in the @a handler's connection.
*/
@@ -510,30 +543,28 @@ typedef struct svn_ra_serf__xml_parser_t svn_ra_serf__xml_parser_t;
*/
typedef svn_error_t *
(*svn_ra_serf__xml_start_element_t)(svn_ra_serf__xml_parser_t *parser,
- void *baton,
svn_ra_serf__dav_props_t name,
- const char **attrs);
+ const char **attrs,
+ apr_pool_t *scratch_pool);
/* Callback invoked with @a baton by our XML @a parser when an element with
* the @a name is closed.
*/
typedef svn_error_t *
(*svn_ra_serf__xml_end_element_t)(svn_ra_serf__xml_parser_t *parser,
- void *baton,
- svn_ra_serf__dav_props_t name);
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool);
/* Callback invoked with @a baton by our XML @a parser when a CDATA portion
* of @a data with size @a len is encountered.
*
* This may be invoked multiple times for the same tag.
- *
- * @see svn_ra_serf__expand_string
*/
typedef svn_error_t *
(*svn_ra_serf__xml_cdata_chunk_handler_t)(svn_ra_serf__xml_parser_t *parser,
- void *baton,
const char *data,
- apr_size_t len);
+ apr_size_t len,
+ apr_pool_t *scratch_pool);
/*
* Helper structure associated with handle_xml_parser handler that will
@@ -543,6 +574,10 @@ struct svn_ra_serf__xml_parser_t {
/* Temporary allocations should be made in this pool. */
apr_pool_t *pool;
+ /* What kind of response are we parsing? If set, this should typically
+ define the report name. */
+ const char *response_type;
+
/* Caller-specific data passed to the start, end, cdata callbacks. */
void *user_data;
@@ -564,16 +599,6 @@ struct svn_ra_serf__xml_parser_t {
/* Our previously used states (will be reused). */
svn_ra_serf__xml_state_t *free_state;
- /* If non-NULL, the status code of the response will be stored here.
- *
- * If this is NULL and an error is received, an abort will be triggered.
- */
- int *status_code;
-
- /* If non-NULL, this is the value of the response's Location header.
- */
- const char *location;
-
/* If non-NULL, this value will be set to TRUE when the response is
* completed.
*/
@@ -626,26 +651,219 @@ struct svn_ra_serf__xml_parser_t {
See libsvn_ra_serf/util.c */
struct svn_ra_serf__pending_t *pending;
-
- /* Response restart support */
- const void *headers_baton; /* Last pointer to headers */
- apr_off_t skip_size; /* Number of bytes to skip */
- apr_off_t read_size; /* Number of bytes read from response */
};
+
+/* v2 of the XML parsing functions */
+
+/* The XML parsing context. */
+typedef struct svn_ra_serf__xml_context_t svn_ra_serf__xml_context_t;
+
+
+/* An opaque structure for the XML parse element/state. */
+typedef struct svn_ra_serf__xml_estate_t svn_ra_serf__xml_estate_t;
+
+/* Called just after the parser moves into ENTERED_STATE. The tag causing
+ the transition is passed in TAG.
+
+ This callback is applied to a parsing context by using the
+ svn_ra_serf__xml_context_customize() function.
+
+ NOTE: this callback, when set, will be invoked on *every* transition.
+ The callback must examine ENTERED_STATE to determine if any action
+ must be taken. The original state is not provided, but must be derived
+ from ENTERED_STATE and/or the TAG causing the transition (if needed). */
+typedef svn_error_t *
+(*svn_ra_serf__xml_opened_t)(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);
+
+
+/* Called just before the parser leaves LEAVING_STATE.
+
+ If cdata collection was enabled for this state, then CDATA will be
+ non-NULL and contain the collected cdata.
+
+ If attribute collection was enabled for this state, then ATTRS will
+ contain the attributes collected for this element only, along with
+ any values stored via svn_ra_serf__xml_note().
+
+ Use svn_ra_serf__xml_gather_since() to gather up data from outer states.
+
+ ATTRS is char* -> char*.
+
+ Temporary allocations may be made in SCRATCH_POOL. */
+typedef svn_error_t *
+(*svn_ra_serf__xml_closed_t)(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);
+
+
+/* Called for all states that are not using the builtin cdata collection.
+ This callback is (only) appropriate for unbounded-size cdata content.
+
+ CURRENT_STATE may be used to decide what to do with the data.
+
+ Temporary allocations may be made in SCRATCH_POOL. */
+typedef svn_error_t *
+(*svn_ra_serf__xml_cdata_t)(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int current_state,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool);
+
+
+/* State transition table.
+
+ When the XML Context is constructed, it is in state 0. User states are
+ positive integers.
+
+ In a list of transitions, use { 0 } to indicate the end. Specifically,
+ the code looks for NS == NULL.
+
+ ### more docco
+*/
+typedef struct svn_ra_serf__xml_transition_t {
+ /* This transition applies when in this state */
+ int from_state;
+
+ /* And when this tag is observed */
+ const char *ns;
+ const char *name;
+
+ /* Moving to this state */
+ int to_state;
+
+ /* Should the cdata of NAME be collected? Note that CUSTOM_CLOSE should
+ be TRUE in order to capture this cdata. */
+ svn_boolean_t collect_cdata;
+
+ /* Which attributes of NAME should be collected? Terminate with NULL.
+ Maximum of 10 attributes may be collected. Note that attribute
+ namespaces are ignored at this time.
+
+ Attribute names beginning with "?" are optional. Other names must
+ exist on the element, or SVN_ERR_XML_ATTRIB_NOT_FOUND will be raised. */
+ const char *collect_attrs[11];
+
+ /* When NAME is closed, should the callback be invoked? */
+ svn_boolean_t custom_close;
+
+} svn_ra_serf__xml_transition_t;
+
+
+/* Construct an XML parsing context, based on the TTABLE transition table.
+ As content is parsed, the CLOSED_CB callback will be invoked according
+ to the definition in the table.
+
+ If OPENED_CB is not NULL, then it will be invoked for *every* tag-open
+ event. The callback will need to use the ENTERED_STATE and TAG parameters
+ to decide what it would like to do.
+
+ If CDATA_CB is not NULL, then it will be called for all cdata that is
+ not be automatically collected (based on the transition table record's
+ COLLECT_CDATA flag). It will be called in every state, so the callback
+ must examine the CURRENT_STATE parameter to decide what to do.
+
+ The same BATON value will be passed to all three callbacks.
+
+ The context will be created within RESULT_POOL. */
+svn_ra_serf__xml_context_t *
+svn_ra_serf__xml_context_create(
+ const svn_ra_serf__xml_transition_t *ttable,
+ svn_ra_serf__xml_opened_t opened_cb,
+ svn_ra_serf__xml_closed_t closed_cb,
+ svn_ra_serf__xml_cdata_t cdata_cb,
+ void *baton,
+ apr_pool_t *result_pool);
+
+/* Destroy all subpools for this structure. */
+void
+svn_ra_serf__xml_context_destroy(
+ svn_ra_serf__xml_context_t *xmlctx);
+
+/* Construct a handler with the response function/baton set up to parse
+ a response body using the given XML context. The handler and its
+ internal structures are allocated in RESULT_POOL.
+
+ This also initializes HANDLER_POOL to the given RESULT_POOL. */
+svn_ra_serf__handler_t *
+svn_ra_serf__create_expat_handler(svn_ra_serf__xml_context_t *xmlctx,
+ apr_pool_t *result_pool);
+
+
+/* Allocated within XES->STATE_POOL. Changes are not allowd (callers
+ should make a deep copy if they need to make changes).
+
+ The resulting hash maps char* names to char* values. */
+apr_hash_t *
+svn_ra_serf__xml_gather_since(svn_ra_serf__xml_estate_t *xes,
+ int stop_state);
+
+
+/* Attach the NAME/VALUE pair onto this/parent state identified by STATE.
+ The name and value will be copied into the target state's pool.
+
+ These values will be available to the CLOSED_CB for the target state,
+ or part of the gathered state via xml_gather_since().
+
+ Typically, this function is used by a child state's close callback,
+ or within an opening callback to store additional data.
+
+ Note: if the state is not found, then a programmer error has occurred,
+ so the function will invoke SVN_ERR_MALFUNCTION(). */
+void
+svn_ra_serf__xml_note(svn_ra_serf__xml_estate_t *xes,
+ int state,
+ const char *name,
+ const char *value);
+
+
+/* Returns XES->STATE_POOL for allocating structures that should live
+ as long as the state identified by XES.
+
+ Note: a state pool is created upon demand, so only use this function
+ when memory is required for a given state. */
+apr_pool_t *
+svn_ra_serf__xml_state_pool(svn_ra_serf__xml_estate_t *xes);
+
+
+/* Any XML parser may be used. When an opening tag is seen, call this
+ function to feed the information into XMLCTX. */
+svn_error_t *
+svn_ra_serf__xml_cb_start(svn_ra_serf__xml_context_t *xmlctx,
+ const char *raw_name,
+ const char *const *attrs);
+
+
+/* When a close tag is seen, call this function to feed the information
+ into XMLCTX. */
+svn_error_t *
+svn_ra_serf__xml_cb_end(svn_ra_serf__xml_context_t *xmlctx,
+ const char *raw_name);
+
+
+/* When cdata is parsed by the wrapping XML parser, call this function to
+ feed the cdata into the XMLCTX. */
+svn_error_t *
+svn_ra_serf__xml_cb_cdata(svn_ra_serf__xml_context_t *xmlctx,
+ const char *data,
+ apr_size_t len);
+
+
/*
* Parses a server-side error message into a local Subversion error.
*/
-typedef struct svn_ra_serf__server_error_t {
+struct svn_ra_serf__server_error_t {
/* Our local representation of the error. */
svn_error_t *error;
- /* Have we checked to see if there's an XML error in this response? */
- svn_boolean_t init;
-
- /* Was there an XML error response? */
- svn_boolean_t has_xml_response;
-
/* Are we done with the response? */
svn_boolean_t done;
@@ -663,41 +881,8 @@ typedef struct svn_ra_serf__server_error_t {
/* XML parser and namespace used to parse the remote response */
svn_ra_serf__xml_parser_t parser;
-} svn_ra_serf__server_error_t;
-
-/* A simple request context that can be passed to handle_status_only. */
-typedef struct svn_ra_serf__simple_request_context_t {
- apr_pool_t *pool;
-
- /* The HTTP status code of the response */
- int status;
-
- /* The HTTP status line of the response */
- const char *reason;
-
- /* The Location header value of the response, or NULL if there
- wasn't one. */
- const char *location;
-
- /* This value is set to TRUE when the response is completed. */
- svn_boolean_t done;
-
- /* If an error occurred, this value will be initialized. */
- svn_ra_serf__server_error_t server_error;
-} svn_ra_serf__simple_request_context_t;
+};
-/*
- * Serf handler for @a request / @a response pair that takes in a
- * @a baton (@see svn_ra_serf__simple_request_context_t).
- * Implements svn_ra_serf__response_handler_t.
- *
- * Temporary allocations are made in @a pool.
- */
-svn_error_t *
-svn_ra_serf__handle_status_only(serf_request_t *request,
- serf_bucket_t *response,
- void *baton,
- apr_pool_t *pool);
/*
* Handler that discards the entire @a response body associated with a
@@ -714,32 +899,45 @@ svn_ra_serf__handle_discard_body(serf_request_t *request,
void *baton,
apr_pool_t *pool);
-/*
- * Handler that retrieves the embedded XML error response from the
- * the @a response body associated with a @a request.
- * Implements svn_ra_serf__response_handler_t.
- *
- * All temporary allocations will be made in a @a pool.
- */
-svn_error_t *
-svn_ra_serf__handle_server_error(serf_request_t *request,
- serf_bucket_t *response,
- apr_pool_t *pool);
/*
* Handler that retrieves the embedded XML multistatus response from the
- * the @a RESPONSE body associated with a @a REQUEST. *DONE is set to TRUE.
+ * the @a RESPONSE body associated with a @a REQUEST.
+ *
* Implements svn_ra_serf__response_handler_t.
*
- * The @a BATON should be of type svn_ra_serf__simple_request_context_t.
+ * The @a BATON should be of type svn_ra_serf__handler_t. When the request
+ * is complete, the handler's DONE flag will be set to TRUE.
*
- * All temporary allocations will be made in a @a pool.
+ * All temporary allocations will be made in a @a scratch_pool.
*/
svn_error_t *
svn_ra_serf__handle_multistatus_only(serf_request_t *request,
serf_bucket_t *response,
void *baton,
- apr_pool_t *pool);
+ apr_pool_t *scratch_pool);
+
+
+/* Handler that expects an empty body.
+
+ If a body IS present, and it is text/xml, then it will be parsed for
+ a server-side error.
+
+ BATON should be the svn_ra_serf__handler_t running REQUEST.
+
+ Status line information will be in HANDLER->SLINE.
+
+ Any parsed errors will be left in HANDLER->SERVER_ERROR. That member
+ may be NULL if no body was present, or a problem occurred trying to
+ parse the body.
+
+ All temporary allocations will be made in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__expect_empty_body(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
/*
* This function will feed the RESPONSE body into XMLP. When parsing is
@@ -768,12 +966,6 @@ svn_ra_serf__response_discard_handler(serf_request_t *request,
void *baton,
apr_pool_t *pool);
-/* Return the value of the RESPONSE's Location header if any, or NULL
- * otherwise. All allocations will be made in POOL.
- */
-const char *
-svn_ra_serf__response_get_location(serf_bucket_t *response,
- apr_pool_t *pool);
/** XML helper functions. **/
@@ -793,6 +985,7 @@ svn_ra_serf__xml_pop_state(svn_ra_serf__xml_parser_t *parser);
svn_error_t *
svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser,
+ svn_boolean_t *network_eof,
apr_pool_t *scratch_pool);
@@ -819,12 +1012,15 @@ svn_ra_serf__add_xml_header_buckets(serf_bucket_t *agg_bucket,
serf_bucket_alloc_t *bkt_alloc);
/*
- * Add the appropriate serf buckets to AGG_BUCKET representing xml tag open
- * with name TAG.
+ * Add the appropriate serf buckets to AGG_BUCKET representing the XML
+ * open tag with name TAG.
*
* Take the tag's attributes from varargs, a NULL-terminated list of
- * alternating <tt>char *</tt> key and <tt>char *</tt> val. Do xml-escaping
- * on each val. Attribute will be ignored if it's value is NULL.
+ * alternating <tt>char *</tt> key and <tt>char *</tt> val. Attribute
+ * will be ignored if it's value is NULL.
+ *
+ * NOTE: Callers are responsible for XML-escaping attribute values as
+ * necessary.
*
* The bucket will be allocated from BKT_ALLOC.
*/
@@ -859,12 +1055,12 @@ svn_ra_serf__add_cdata_len_buckets(serf_bucket_t *agg_bucket,
* Look up the @a attrs array for namespace definitions and add each one
* to the @a ns_list of namespaces.
*
- * New namespaces will be allocated in @a pool.
+ * New namespaces will be allocated in RESULT_POOL.
*/
void
svn_ra_serf__define_ns(svn_ra_serf__ns_t **ns_list,
- const char **attrs,
- apr_pool_t *pool);
+ const char *const *attrs,
+ apr_pool_t *result_pool);
/*
* Look up @a name in the @a ns_list list for previously declared namespace
@@ -875,37 +1071,12 @@ svn_ra_serf__define_ns(svn_ra_serf__ns_t **ns_list,
*/
void
svn_ra_serf__expand_ns(svn_ra_serf__dav_props_t *returned_prop_name,
- svn_ra_serf__ns_t *ns_list,
+ const svn_ra_serf__ns_t *ns_list,
const char *name);
-/*
- * Expand the string represented by @a cur with a current size of @a
- * cur_len by appending @a new with a size of @a new_len.
- *
- * The reallocated string is made in @a pool.
- */
-void
-svn_ra_serf__expand_string(const char **cur, apr_size_t *cur_len,
- const char *new, apr_size_t new_len,
- apr_pool_t *pool);
/** PROPFIND-related functions **/
-/* Opaque structure representing PROPFINDs. */
-typedef struct svn_ra_serf__propfind_context_t svn_ra_serf__propfind_context_t;
-
-/*
- * Returns a flag representing whether the PROPFIND @a ctx is completed.
- */
-svn_boolean_t
-svn_ra_serf__propfind_is_done(svn_ra_serf__propfind_context_t *ctx);
-
-/*
- * Returns the response status code of the PROPFIND @a ctx.
- */
-int
-svn_ra_serf__propfind_status_code(svn_ra_serf__propfind_context_t *ctx);
-
/*
* This function will deliver a PROP_CTX PROPFIND request in the SESS
* serf context for the properties listed in LOOKUP_PROPS at URL for
@@ -915,7 +1086,7 @@ svn_ra_serf__propfind_status_code(svn_ra_serf__propfind_context_t *ctx);
* expected to call svn_ra_serf__wait_for_props().
*/
svn_error_t *
-svn_ra_serf__deliver_props(svn_ra_serf__propfind_context_t **prop_ctx,
+svn_ra_serf__deliver_props(svn_ra_serf__handler_t **propfind_handler,
apr_hash_t *prop_vals,
svn_ra_serf__session_t *sess,
svn_ra_serf__connection_t *conn,
@@ -927,13 +1098,12 @@ svn_ra_serf__deliver_props(svn_ra_serf__propfind_context_t **prop_ctx,
apr_pool_t *pool);
/*
- * This helper function will block until the PROP_CTX indicates that is done
- * or another error is returned.
+ * This helper function will block until PROPFIND_HANDLER indicates that is
+ * done or another error is returned.
*/
svn_error_t *
-svn_ra_serf__wait_for_props(svn_ra_serf__propfind_context_t *prop_ctx,
- svn_ra_serf__session_t *sess,
- apr_pool_t *pool);
+svn_ra_serf__wait_for_props(svn_ra_serf__handler_t *handler,
+ apr_pool_t *scratch_pool);
/* This is a blocking version of deliver_props.
@@ -955,6 +1125,50 @@ svn_ra_serf__retrieve_props(apr_hash_t **results,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
+
+/* Using CONN, fetch the properties specified by WHICH_PROPS using CONN
+ for URL at REVISION. The resulting properties are placed into a 2-level
+ hash in RESULTS, mapping NAMESPACE -> hash<PROPNAME, PROPVALUE>, which
+ is allocated in RESULT_POOL.
+
+ If REVISION is SVN_INVALID_REVNUM, then the properties are fetched
+ from HEAD for URL.
+
+ This function performs the request synchronously.
+
+ Temporary allocations are made in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__fetch_node_props(apr_hash_t **results,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ const svn_ra_serf__dav_props_t *which_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Using CONN, fetch a DAV: property from the resource identified by URL
+ within REVISION. The PROPNAME may be one of:
+
+ "checked-in"
+ "href"
+
+ The resulting value will be allocated in RESULT_POOL, and may be NULL
+ if the property does not exist (note: "href" always exists).
+
+ This function performs the request synchronously.
+
+ Temporary allocations are made in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__fetch_dav_prop(const char **value,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
/* Set PROPS for PATH at REV revision with a NS:NAME VAL.
*
* The POOL governs allocation.
@@ -983,6 +1197,15 @@ svn_ra_serf__walk_all_props(apr_hash_t *props,
void *baton,
apr_pool_t *pool);
+
+/* Like walk_all_props(), but a 2-level hash. */
+svn_error_t *
+svn_ra_serf__walk_node_props(apr_hash_t *props,
+ svn_ra_serf__walker_visitor_t walker,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+
typedef svn_error_t *
(*svn_ra_serf__path_rev_walker_t)(void *baton,
const char *path, apr_ssize_t path_len,
@@ -1021,9 +1244,8 @@ svn_ra_serf__select_revprops(apr_hash_t **revprops,
apr_pool_t *scratch_pool);
-/* PROPS is nested hash tables mapping REV -> PATH -> NS -> NAME -> VALUE.
- This function takes the tree of tables identified by PATH and REVISION
- (resulting in NS:NAME:VALUE hashes) and flattens them into a set of
+/* PROPS is nested hash tables mapping NS -> NAME -> VALUE.
+ This function takes the NS:NAME:VALUE hashes and flattens them into a set of
names to VALUE. The names are composed of NS:NAME, with specific
rewrite from wire names (DAV) to SVN names. This mapping is managed
by the svn_ra_serf__set_baton_props() function.
@@ -1036,8 +1258,6 @@ svn_ra_serf__select_revprops(apr_hash_t **revprops,
svn_error_t *
svn_ra_serf__flatten_props(apr_hash_t **flat_props,
apr_hash_t *props,
- const char *path,
- svn_revnum_t revision,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
@@ -1078,24 +1298,11 @@ svn_ra_serf__set_prop(apr_hash_t *props, const char *path,
svn_error_t *
svn_ra_serf__get_resource_type(svn_node_kind_t *kind,
- apr_hash_t *props,
- const char *url,
- svn_revnum_t revision);
+ apr_hash_t *props);
/** MERGE-related functions **/
-typedef struct svn_ra_serf__merge_context_t svn_ra_serf__merge_context_t;
-
-svn_boolean_t*
-svn_ra_serf__merge_get_done_ptr(svn_ra_serf__merge_context_t *ctx);
-
-svn_commit_info_t*
-svn_ra_serf__merge_get_commit_info(svn_ra_serf__merge_context_t *ctx);
-
-int
-svn_ra_serf__merge_get_status(svn_ra_serf__merge_context_t *ctx);
-
void
svn_ra_serf__merge_lock_token_list(apr_hash_t *lock_tokens,
const char *parent,
@@ -1109,38 +1316,56 @@ svn_ra_serf__merge_lock_token_list(apr_hash_t *lock_tokens,
client. If KEEP_LOCKS is set, instruct the server to not release
locks set on the paths included in this commit. */
svn_error_t *
-svn_ra_serf__merge_create_req(svn_ra_serf__merge_context_t **merge_ctx,
- svn_ra_serf__session_t *session,
- svn_ra_serf__connection_t *conn,
- const char *merge_resource_url,
- apr_hash_t *lock_tokens,
- svn_boolean_t keep_locks,
- apr_pool_t *pool);
+svn_ra_serf__run_merge(const svn_commit_info_t **commit_info,
+ int *response_code,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *merge_resource_url,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
/** OPTIONS-related functions **/
-typedef struct svn_ra_serf__options_context_t svn_ra_serf__options_context_t;
+/* When running with a proxy, we may need to detect and correct for problems.
+ This probing function will send a simple OPTIONS request to detect problems
+ with the connection. */
+svn_error_t *
+svn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess,
+ apr_pool_t *scratch_pool);
-/* Is this OPTIONS-request done yet? */
-svn_boolean_t*
-svn_ra_serf__get_options_done_ptr(svn_ra_serf__options_context_t *ctx);
-const char *
-svn_ra_serf__options_get_activity_collection(svn_ra_serf__options_context_t *ctx);
+/* On HTTPv2 connections, run an OPTIONS request over CONN to fetch the
+ current youngest revnum, returning it in *YOUNGEST.
+
+ (the revnum is headers of the OPTIONS response)
-svn_revnum_t
-svn_ra_serf__options_get_youngest_rev(svn_ra_serf__options_context_t *ctx);
+ This function performs the request synchronously.
-/* Create an OPTIONS request. When run, ask for an
- activity-collection-set in the request body (retrievable via
- accessor above) and also parse the server's capability headers into
- the SESSION->capabilites hash. */
+ All temporary allocations will be made in SCRATCH_POOL. */
svn_error_t *
-svn_ra_serf__create_options_req(svn_ra_serf__options_context_t **opt_ctx,
- svn_ra_serf__session_t *session,
- svn_ra_serf__connection_t *conn,
- const char *path,
- apr_pool_t *pool);
+svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *scratch_pool);
+
+
+/* On HTTPv1 connections, run an OPTIONS request over CONN to fetch the
+ activity collection set and return it in *ACTIVITY_URL, allocated
+ from RESULT_POOL.
+
+ (the activity-collection-set is in the body of the OPTIONS response)
+
+ This function performs the request synchronously.
+
+ All temporary allocations will be made in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__v1_get_activity_collection(const char **activity_url,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
/* Set @a VCC_URL to the default VCC for our repository based on @a
* ORIG_PATH for the session @a SESSION, ensuring that the VCC URL and
@@ -1180,34 +1405,63 @@ svn_ra_serf__get_relative_path(const char **rel_path,
svn_ra_serf__connection_t *conn,
apr_pool_t *pool);
-/* Set *BC_URL to the baseline collection url, and set *BC_RELATIVE to
- * the path relative to that url for URL in REVISION using SESSION.
- * BC_RELATIVE will be URI decoded.
- *
- * REVISION may be SVN_INVALID_REVNUM (to mean "the current HEAD
- * revision"). If URL is NULL, use SESSION's session url.
- *
- * If LATEST_REVNUM is not NULL, set it to the baseline revision. If
- * REVISION was set to SVN_INVALID_REVNUM, this will return the current
- * HEAD revision.
- *
- * If non-NULL, use CONN for communications with the server;
- * otherwise, use the default connection.
- *
- * Use POOL for all allocations.
- */
+
+/* Using the default connection in SESSION (conns[0]), get the youngest
+ revnum from the server, returning it in *YOUNGEST.
+
+ This function operates synchronously.
+
+ All temporary allocations are performed in SCRATCH_POOL. */
svn_error_t *
-svn_ra_serf__get_baseline_info(const char **bc_url,
- const char **bc_relative,
- svn_ra_serf__session_t *session,
- svn_ra_serf__connection_t *conn,
- const char *url,
- svn_revnum_t revision,
- svn_revnum_t *latest_revnum,
- apr_pool_t *pool);
+svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest,
+ svn_ra_serf__session_t *session,
+ apr_pool_t *scratch_pool);
+
+
+/* Generate a revision-stable URL.
+
+ The RA APIs all refer to user/public URLs that float along with the
+ youngest revision. In many cases, we do NOT want to work with that URL
+ since it can change from one moment to the next. Especially if we
+ attempt to operation against multiple floating URLs -- we could end up
+ referring to two separate revisions.
+
+ The DAV RA provider(s) solve this by generating a URL that is specific
+ to a revision by using a URL into a "baseline collection".
+
+ For a specified SESSION, with an optional CONN (if NULL, then the
+ session's default connection will be used; specifically SESSION->conns[0]),
+ generate a revision-stable URL for URL at REVISION. If REVISION is
+ SVN_INVALID_REVNUM, then the stable URL will refer to the youngest
+ revision at the time this function was called.
+
+ If URL is NULL, then the session root will be used.
+
+ The stable URL will be placed into *STABLE_URL, allocated from RESULT_POOL.
+
+ If LATEST_REVNUM is not NULL, then the revision used will be placed into
+ *LATEST_REVNUM. That will be equal to youngest, or the given REVISION.
+
+ This function operates synchronously, if any communication to the server
+ is required. Communication is needed if REVISION is SVN_INVALID_REVNUM
+ (to get the current youngest revnum), or if the specified REVISION is not
+ (yet) in our cache of baseline collections.
+
+ All temporary allocations are performed in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__get_stable_url(const char **stable_url,
+ svn_revnum_t *latest_revnum,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
/** RA functions **/
+/* Implements svn_ra__vtable_t.get_log(). */
svn_error_t *
svn_ra_serf__get_log(svn_ra_session_t *session,
const apr_array_header_t *paths,
@@ -1222,6 +1476,7 @@ svn_ra_serf__get_log(svn_ra_session_t *session,
void *receiver_baton,
apr_pool_t *pool);
+/* Implements svn_ra__vtable_t.get_locations(). */
svn_error_t *
svn_ra_serf__get_locations(svn_ra_session_t *session,
apr_hash_t **locations,
@@ -1230,6 +1485,7 @@ svn_ra_serf__get_locations(svn_ra_session_t *session,
const apr_array_header_t *location_revisions,
apr_pool_t *pool);
+/* Implements svn_ra__vtable_t.get_location_segments(). */
svn_error_t *
svn_ra_serf__get_location_segments(svn_ra_session_t *session,
const char *path,
@@ -1240,6 +1496,7 @@ svn_ra_serf__get_location_segments(svn_ra_session_t *session,
void *receiver_baton,
apr_pool_t *pool);
+/* Implements svn_ra__vtable_t.do_diff(). */
svn_error_t *
svn_ra_serf__do_diff(svn_ra_session_t *session,
const svn_ra_reporter3_t **reporter,
@@ -1254,6 +1511,7 @@ svn_ra_serf__do_diff(svn_ra_session_t *session,
void *diff_baton,
apr_pool_t *pool);
+/* Implements svn_ra__vtable_t.do_status(). */
svn_error_t *
svn_ra_serf__do_status(svn_ra_session_t *ra_session,
const svn_ra_reporter3_t **reporter,
@@ -1265,6 +1523,7 @@ svn_ra_serf__do_status(svn_ra_session_t *ra_session,
void *status_baton,
apr_pool_t *pool);
+/* Implements svn_ra__vtable_t.do_update(). */
svn_error_t *
svn_ra_serf__do_update(svn_ra_session_t *ra_session,
const svn_ra_reporter3_t **reporter,
@@ -1273,10 +1532,13 @@ svn_ra_serf__do_update(svn_ra_session_t *ra_session,
const char *update_target,
svn_depth_t depth,
svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
const svn_delta_editor_t *update_editor,
void *update_baton,
- apr_pool_t *pool);
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+/* Implements svn_ra__vtable_t.do_switch(). */
svn_error_t *
svn_ra_serf__do_switch(svn_ra_session_t *ra_session,
const svn_ra_reporter3_t **reporter,
@@ -1285,10 +1547,14 @@ svn_ra_serf__do_switch(svn_ra_session_t *ra_session,
const char *switch_target,
svn_depth_t depth,
const char *switch_url,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
const svn_delta_editor_t *switch_editor,
void *switch_baton,
- apr_pool_t *pool);
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+/* Implements svn_ra__vtable_t.get_file_revs(). */
svn_error_t *
svn_ra_serf__get_file_revs(svn_ra_session_t *session,
const char *path,
@@ -1299,12 +1565,14 @@ svn_ra_serf__get_file_revs(svn_ra_session_t *session,
void *handler_baton,
apr_pool_t *pool);
+/* Implements svn_ra__vtable_t.get_dated_revision(). */
svn_error_t *
svn_ra_serf__get_dated_revision(svn_ra_session_t *session,
svn_revnum_t *revision,
apr_time_t tm,
apr_pool_t *pool);
+/* Implements svn_ra__vtable_t.get_commit_editor(). */
svn_error_t *
svn_ra_serf__get_commit_editor(svn_ra_session_t *session,
const svn_delta_editor_t **editor,
@@ -1316,6 +1584,7 @@ svn_ra_serf__get_commit_editor(svn_ra_session_t *session,
svn_boolean_t keep_locks,
apr_pool_t *pool);
+/* Implements svn_ra__vtable_t.get_file(). */
svn_error_t *
svn_ra_serf__get_file(svn_ra_session_t *session,
const char *path,
@@ -1325,6 +1594,7 @@ svn_ra_serf__get_file(svn_ra_session_t *session,
apr_hash_t **props,
apr_pool_t *pool);
+/* Implements svn_ra__vtable_t.change_rev_prop(). */
svn_error_t *
svn_ra_serf__change_rev_prop(svn_ra_session_t *session,
svn_revnum_t rev,
@@ -1333,6 +1603,7 @@ svn_ra_serf__change_rev_prop(svn_ra_session_t *session,
const svn_string_t *value,
apr_pool_t *pool);
+/* Implements svn_ra__vtable_t.replay(). */
svn_error_t *
svn_ra_serf__replay(svn_ra_session_t *ra_session,
svn_revnum_t revision,
@@ -1342,6 +1613,7 @@ svn_ra_serf__replay(svn_ra_session_t *ra_session,
void *edit_baton,
apr_pool_t *pool);
+/* Implements svn_ra__vtable_t.replay_range(). */
svn_error_t *
svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
svn_revnum_t start_revision,
@@ -1353,6 +1625,7 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
void *replay_baton,
apr_pool_t *pool);
+/* Implements svn_ra__vtable_t.lock(). */
svn_error_t *
svn_ra_serf__lock(svn_ra_session_t *ra_session,
apr_hash_t *path_revs,
@@ -1362,6 +1635,7 @@ svn_ra_serf__lock(svn_ra_session_t *ra_session,
void *lock_baton,
apr_pool_t *pool);
+/* Implements svn_ra__vtable_t.unlock(). */
svn_error_t *
svn_ra_serf__unlock(svn_ra_session_t *ra_session,
apr_hash_t *path_tokens,
@@ -1370,12 +1644,14 @@ svn_ra_serf__unlock(svn_ra_session_t *ra_session,
void *lock_baton,
apr_pool_t *pool);
+/* Implements svn_ra__vtable_t.get_lock(). */
svn_error_t *
svn_ra_serf__get_lock(svn_ra_session_t *ra_session,
svn_lock_t **lock,
const char *path,
apr_pool_t *pool);
+/* Implements svn_ra__vtable_t.get_locks(). */
svn_error_t *
svn_ra_serf__get_locks(svn_ra_session_t *ra_session,
apr_hash_t **locks,
@@ -1383,14 +1659,19 @@ svn_ra_serf__get_locks(svn_ra_session_t *ra_session,
svn_depth_t depth,
apr_pool_t *pool);
-svn_error_t * svn_ra_serf__get_mergeinfo(
- svn_ra_session_t *ra_session,
- apr_hash_t **mergeinfo,
- const apr_array_header_t *paths,
- svn_revnum_t revision,
- svn_mergeinfo_inheritance_t inherit,
- svn_boolean_t include_descendants,
- apr_pool_t *pool);
+/* Request a mergeinfo-report from the URL attached to SESSION,
+ and fill in the MERGEINFO hash with the results.
+
+ Implements svn_ra__vtable_t.get_mergeinfo().
+ */
+svn_error_t *
+svn_ra_serf__get_mergeinfo(svn_ra_session_t *ra_session,
+ apr_hash_t **mergeinfo,
+ const apr_array_header_t *paths,
+ svn_revnum_t revision,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ apr_pool_t *pool);
/* Exchange capabilities with the server, by sending an OPTIONS
* request announcing the client's capabilities, and by filling
@@ -1409,14 +1690,14 @@ svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
const char **corrected_url,
apr_pool_t *pool);
-/* Implements the has_capability RA layer function. */
+/* Implements svn_ra__vtable_t.has_capability(). */
svn_error_t *
svn_ra_serf__has_capability(svn_ra_session_t *ra_session,
svn_boolean_t *has,
const char *capability,
apr_pool_t *pool);
-/* Implements the get_deleted_rev RA layer function. */
+/* Implements svn_ra__vtable_t.get_deleted_rev(). */
svn_error_t *
svn_ra_serf__get_deleted_rev(svn_ra_session_t *session,
const char *path,
@@ -1425,6 +1706,25 @@ svn_ra_serf__get_deleted_rev(svn_ra_session_t *session,
svn_revnum_t *revision_deleted,
apr_pool_t *pool);
+/* Implements the get_inherited_props RA layer function. */
+svn_error_t * svn_ra_serf__get_inherited_props(svn_ra_session_t *session,
+ apr_array_header_t **iprops,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Implements svn_ra__vtable_t.get_repos_root(). */
+svn_error_t *
+svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
+ const char **url,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.register_editor_shim_callbacks(). */
+svn_error_t *
+svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *session,
+ svn_delta_shim_callbacks_t *callbacks);
+
/*** Authentication handler declarations ***/
/**
@@ -1447,10 +1747,40 @@ svn_ra_serf__credentials_callback(char **username, char **password,
* where it necessary.
*/
svn_error_t *
-svn_ra_serf__error_on_status(int status_code,
+svn_ra_serf__error_on_status(serf_status_line sline,
const char *path,
const char *location);
+/* ###? */
+svn_error_t *
+svn_ra_serf__copy_into_spillbuf(svn_spillbuf_t **spillbuf,
+ serf_bucket_t *bkt,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* ###? */
+serf_bucket_t *
+svn_ra_serf__create_sb_bucket(svn_spillbuf_t *spillbuf,
+ serf_bucket_alloc_t *allocator,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Wrap STATUS from an serf function. If STATUS is not serf error code,
+ * this is equivalent to svn_error_wrap_apr().
+ */
+svn_error_t *
+svn_ra_serf__wrap_err(apr_status_t status,
+ const char *fmt,
+ ...);
+
+
+#if defined(SVN_DEBUG)
+/* Wrapper macros to collect file and line information */
+#define svn_ra_serf__wrap_err \
+ (svn_error__locate(__FILE__,__LINE__), (svn_ra_serf__wrap_err))
+
+#endif
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/subversion/libsvn_ra_serf/replay.c b/subversion/libsvn_ra_serf/replay.c
index 36b3dca..66e2f58 100644
--- a/subversion/libsvn_ra_serf/replay.c
+++ b/subversion/libsvn_ra_serf/replay.c
@@ -24,9 +24,6 @@
#include <apr_uri.h>
-
-#include <expat.h>
-
#include <serf.h>
#include "svn_pools.h"
@@ -40,6 +37,8 @@
#include "svn_path.h"
#include "svn_private_config.h"
+#include "private/svn_string_private.h"
+
#include "ra_serf.h"
@@ -83,8 +82,7 @@ typedef struct prop_info_t {
const char *name;
svn_boolean_t del_prop;
- const char *data;
- apr_size_t len;
+ svn_stringbuf_t *prop_value;
replay_info_t *parent;
} prop_info_t;
@@ -92,8 +90,6 @@ typedef struct prop_info_t {
typedef struct replay_context_t {
apr_pool_t *src_rev_pool;
apr_pool_t *dst_rev_pool;
- /*file_pool is cleared after completion of each file. */
- apr_pool_t *file_pool;
/* Are we done fetching this file? */
svn_boolean_t done;
@@ -109,16 +105,17 @@ typedef struct replay_context_t {
const svn_delta_editor_t *editor;
void *editor_baton;
- /* current revision */
+ /* Path and revision used to filter replayed changes. If
+ INCLUDE_PATH is non-NULL, REVISION is unnecessary and will not be
+ included in the replay REPORT. (Because the REPORT is being
+ aimed an HTTP v2 revision resource.) */
+ const char *include_path;
svn_revnum_t revision;
/* Information needed to create the replay report body */
svn_revnum_t low_water_mark;
svn_boolean_t send_deltas;
- /* Cached report target url */
- const char *report_target;
-
/* Target and revision to fetch revision properties on */
const char *revprop_target;
svn_revnum_t revprop_rev;
@@ -130,8 +127,9 @@ typedef struct replay_context_t {
/* Keep a reference to the XML parser ctx to report any errors. */
svn_ra_serf__xml_parser_t *parser_ctx;
- /* The propfind for the revision properties of the current revision */
- svn_ra_serf__propfind_context_t *prop_ctx;
+ /* Handlers for the PROPFIND and REPORT for the current revision. */
+ svn_ra_serf__handler_t *propfind_handler;
+ svn_ra_serf__handler_t *report_handler;
} replay_context_t;
@@ -147,10 +145,11 @@ push_state(svn_ra_serf__xml_parser_t *parser,
state == OPEN_FILE || state == ADD_FILE)
{
replay_info_t *info;
+ apr_pool_t *pool = svn_pool_create(replay_ctx->dst_rev_pool);
- info = apr_palloc(replay_ctx->dst_rev_pool, sizeof(*info));
+ info = apr_palloc(pool, sizeof(*info));
- info->pool = replay_ctx->dst_rev_pool;
+ info->pool = pool;
info->parent = parser->state->private;
info->baton = NULL;
info->stream = NULL;
@@ -160,11 +159,13 @@ push_state(svn_ra_serf__xml_parser_t *parser,
else if (state == CHANGE_PROP)
{
prop_info_t *info;
+ apr_pool_t *pool = svn_pool_create(replay_ctx->dst_rev_pool);
- info = apr_pcalloc(replay_ctx->dst_rev_pool, sizeof(*info));
+ info = apr_pcalloc(pool, sizeof(*info));
- info->pool = replay_ctx->dst_rev_pool;
+ info->pool = pool;
info->parent = parser->state->private;
+ info->prop_value = svn_stringbuf_create_empty(pool);
parser->state->private = info;
}
@@ -174,11 +175,11 @@ push_state(svn_ra_serf__xml_parser_t *parser,
static svn_error_t *
start_replay(svn_ra_serf__xml_parser_t *parser,
- void *userData,
svn_ra_serf__dav_props_t name,
- const char **attrs)
+ const char **attrs,
+ apr_pool_t *scratch_pool)
{
- replay_context_t *ctx = userData;
+ replay_context_t *ctx = parser->user_data;
replay_state_e state;
state = parser->state->current_state;
@@ -189,20 +190,17 @@ start_replay(svn_ra_serf__xml_parser_t *parser,
push_state(parser, ctx, REPORT);
/* Before we can continue, we need the revision properties. */
- SVN_ERR_ASSERT(!ctx->prop_ctx
- || svn_ra_serf__propfind_is_done(ctx->prop_ctx));
+ SVN_ERR_ASSERT(!ctx->propfind_handler || ctx->propfind_handler->done);
/* Create a pool for the commit editor. */
ctx->dst_rev_pool = svn_pool_create(ctx->src_rev_pool);
- ctx->file_pool = svn_pool_create(ctx->dst_rev_pool);
- /* ### it would be nice to have a proper scratch_pool. */
SVN_ERR(svn_ra_serf__select_revprops(&ctx->props,
ctx->revprop_target,
ctx->revprop_rev,
ctx->revs_props,
ctx->dst_rev_pool,
- ctx->dst_rev_pool));
+ scratch_pool));
if (ctx->revstart_func)
{
@@ -226,7 +224,7 @@ start_replay(svn_ra_serf__xml_parser_t *parser,
SVN_ERR(ctx->editor->set_target_revision(ctx->editor_baton,
SVN_STR_TO_REV(rev),
- ctx->dst_rev_pool));
+ scratch_pool));
}
else if (state == REPORT &&
strcmp(name.name, "open-root") == 0)
@@ -271,7 +269,7 @@ start_replay(svn_ra_serf__xml_parser_t *parser,
info = push_state(parser, ctx, DELETE_ENTRY);
SVN_ERR(ctx->editor->delete_entry(file_name, SVN_STR_TO_REV(rev),
- info->baton, ctx->dst_rev_pool));
+ info->baton, scratch_pool));
svn_ra_serf__xml_pop_state(parser);
}
@@ -332,9 +330,11 @@ start_replay(svn_ra_serf__xml_parser_t *parser,
{
replay_info_t *info = parser->state->private;
- SVN_ERR(ctx->editor->close_directory(info->baton, ctx->dst_rev_pool));
+ SVN_ERR(ctx->editor->close_directory(info->baton, scratch_pool));
svn_ra_serf__xml_pop_state(parser);
+
+ svn_pool_destroy(info->pool);
}
else if ((state == OPEN_DIR || state == ADD_DIR) &&
strcmp(name.name, "open-file") == 0)
@@ -342,7 +342,6 @@ start_replay(svn_ra_serf__xml_parser_t *parser,
const char *file_name, *rev;
replay_info_t *info;
- svn_pool_clear(ctx->file_pool);
file_name = svn_xml_get_attr_value("name", attrs);
if (!file_name)
{
@@ -360,7 +359,7 @@ start_replay(svn_ra_serf__xml_parser_t *parser,
SVN_ERR(ctx->editor->open_file(file_name, info->parent->baton,
SVN_STR_TO_REV(rev),
- ctx->file_pool, &info->baton));
+ info->pool, &info->baton));
}
else if ((state == OPEN_DIR || state == ADD_DIR) &&
strcmp(name.name, "add-file") == 0)
@@ -369,7 +368,6 @@ start_replay(svn_ra_serf__xml_parser_t *parser,
svn_revnum_t rev;
replay_info_t *info;
- svn_pool_clear(ctx->file_pool);
file_name = svn_xml_get_attr_value("name", attrs);
if (!file_name)
{
@@ -388,7 +386,7 @@ start_replay(svn_ra_serf__xml_parser_t *parser,
SVN_ERR(ctx->editor->add_file(file_name, info->parent->baton,
copyfrom, rev,
- ctx->file_pool, &info->baton));
+ info->pool, &info->baton));
}
else if ((state == OPEN_FILE || state == ADD_FILE) &&
strcmp(name.name, "apply-textdelta") == 0)
@@ -408,7 +406,7 @@ start_replay(svn_ra_serf__xml_parser_t *parser,
}
SVN_ERR(ctx->editor->apply_textdelta(info->baton, checksum,
- ctx->file_pool,
+ info->pool,
&textdelta,
&textdelta_baton));
@@ -424,10 +422,11 @@ start_replay(svn_ra_serf__xml_parser_t *parser,
checksum = svn_xml_get_attr_value("checksum", attrs);
- SVN_ERR(ctx->editor->close_file(info->baton, checksum,
- ctx->file_pool));
+ SVN_ERR(ctx->editor->close_file(info->baton, checksum, scratch_pool));
svn_ra_serf__xml_pop_state(parser);
+
+ svn_pool_destroy(info->pool);
}
else if (((state == OPEN_FILE || state == ADD_FILE) &&
strcmp(name.name, "change-file-prop") == 0) ||
@@ -453,14 +452,13 @@ start_replay(svn_ra_serf__xml_parser_t *parser,
else
info->del_prop = FALSE;
+ info->name = apr_pstrdup(info->pool, prop_name);
if (state == OPEN_FILE || state == ADD_FILE)
{
- info->name = apr_pstrdup(ctx->file_pool, prop_name);
info->change = ctx->editor->change_file_prop;
}
else
{
- info->name = apr_pstrdup(ctx->dst_rev_pool, prop_name);
info->change = ctx->editor->change_dir_prop;
}
@@ -471,14 +469,12 @@ start_replay(svn_ra_serf__xml_parser_t *parser,
static svn_error_t *
end_replay(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name)
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
{
- replay_context_t *ctx = userData;
+ replay_context_t *ctx = parser->user_data;
replay_state_e state;
- UNUSED_CTX(ctx);
-
state = parser->state->current_state;
if (state == REPORT &&
@@ -535,20 +531,21 @@ end_replay(svn_ra_serf__xml_parser_t *parser,
}
else
{
- svn_string_t tmp_prop;
+ const svn_string_t *morph;
- tmp_prop.data = info->data;
- tmp_prop.len = info->len;
+ morph = svn_stringbuf__morph_into_string(info->prop_value);
+#ifdef SVN_DEBUG
+ info->prop_value = NULL; /* morph killed the stringbuf. */
+#endif
- if (strcmp(name.name, "change-file-prop") == 0)
- prop_val = svn_base64_decode_string(&tmp_prop, ctx->file_pool);
- else
- prop_val = svn_base64_decode_string(&tmp_prop, ctx->dst_rev_pool);
+ prop_val = svn_base64_decode_string(morph, info->pool);
}
SVN_ERR(info->change(info->parent->baton, info->name, prop_val,
info->parent->pool));
svn_ra_serf__xml_pop_state(parser);
+
+ svn_pool_destroy(info->pool);
}
return SVN_NO_ERROR;
@@ -556,11 +553,11 @@ end_replay(svn_ra_serf__xml_parser_t *parser,
static svn_error_t *
cdata_replay(svn_ra_serf__xml_parser_t *parser,
- void *userData,
const char *data,
- apr_size_t len)
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
{
- replay_context_t *replay_ctx = userData;
+ replay_context_t *replay_ctx = parser->user_data;
replay_state_e state;
UNUSED_CTX(replay_ctx);
@@ -584,8 +581,7 @@ cdata_replay(svn_ra_serf__xml_parser_t *parser,
{
prop_info_t *info = parser->state->private;
- svn_ra_serf__expand_string(&info->data, &info->len,
- data, len, parser->state->pool);
+ svn_stringbuf_appendbytes(info->prop_value, data, len);
}
return SVN_NO_ERROR;
@@ -607,10 +603,22 @@ create_replay_body(serf_bucket_t **bkt,
"xmlns:S", SVN_XML_NAMESPACE,
NULL);
- svn_ra_serf__add_tag_buckets(body_bkt,
- "S:revision",
- apr_ltoa(ctx->src_rev_pool, ctx->revision),
- alloc);
+ /* If we have a non-NULL include path, we add it to the body and
+ omit the revision; otherwise, the reverse. */
+ if (ctx->include_path)
+ {
+ svn_ra_serf__add_tag_buckets(body_bkt,
+ "S:include-path",
+ ctx->include_path,
+ alloc);
+ }
+ else
+ {
+ svn_ra_serf__add_tag_buckets(body_bkt,
+ "S:revision",
+ apr_ltoa(ctx->src_rev_pool, ctx->revision),
+ alloc);
+ }
svn_ra_serf__add_tag_buckets(body_bkt,
"S:low-water-mark",
apr_ltoa(ctx->src_rev_pool, ctx->low_water_mark),
@@ -642,10 +650,6 @@ svn_ra_serf__replay(svn_ra_session_t *ra_session,
svn_ra_serf__xml_parser_t *parser_ctx;
svn_error_t *err;
const char *report_target;
- /* We're not really interested in the status code here in replay, but
- the XML parsing code will abort on error if it doesn't have a place
- to store the response status code. */
- int status_code;
SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool));
@@ -657,13 +661,13 @@ svn_ra_serf__replay(svn_ra_session_t *ra_session,
replay_ctx->revision = revision;
replay_ctx->low_water_mark = low_water_mark;
replay_ctx->send_deltas = send_deltas;
- replay_ctx->report_target = report_target;
replay_ctx->revs_props = apr_hash_make(replay_ctx->src_rev_pool);
handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
handler->method = "REPORT";
- handler->path = session->session_url_str;
+ handler->path = session->session_url.path;
handler->body_delegate = create_replay_body;
handler->body_delegate_baton = replay_ctx;
handler->body_type = "text/xml";
@@ -677,7 +681,6 @@ svn_ra_serf__replay(svn_ra_session_t *ra_session,
parser_ctx->start = start_replay;
parser_ctx->end = end_replay;
parser_ctx->cdata = cdata_replay;
- parser_ctx->status_code = &status_code;
parser_ctx->done = &replay_ctx->done;
handler->response_handler = svn_ra_serf__handle_xml_parser;
@@ -685,12 +688,17 @@ svn_ra_serf__replay(svn_ra_session_t *ra_session,
/* This is only needed to handle errors during XML parsing. */
replay_ctx->parser_ctx = parser_ctx;
+ replay_ctx->report_handler = handler; /* unused */
svn_ra_serf__request_create(handler);
err = svn_ra_serf__context_run_wait(&replay_ctx->done, session, pool);
- SVN_ERR(err);
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline,
+ handler->path,
+ handler->location),
+ err));
return SVN_NO_ERROR;
}
@@ -707,8 +715,8 @@ svn_ra_serf__replay(svn_ra_session_t *ra_session,
* optimally. Originally we used 5 as the max. number of outstanding
* requests, but this turned out to be too low.
*
- * Serf doesn't exit out of the serf_context_run loop as long as it
- * has data to send or receive. With small responses (revs of a few
+ * Serf doesn't exit out of the svn_ra_serf__context_run_wait loop as long as
+ * it has data to send or receive. With small responses (revs of a few
* kB), serf doesn't come out of this loop at all. So with
* MAX_OUTSTANDING_REQUESTS set to a low number, there's a big chance
* that serf handles those requests completely in its internal loop,
@@ -741,20 +749,44 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
svn_revnum_t rev = start_revision;
const char *report_target;
int active_reports = 0;
+ const char *include_path;
SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool));
+ /* Prior to 1.8, mod_dav_svn expect to get replay REPORT requests
+ aimed at the session URL. But that's incorrect -- these reports
+ aren't about specific resources -- they are above revisions. The
+ path-based filtering offered by this API is just that: a filter
+ applied to the full set of changes made in the revision. As
+ such, the correct target for these REPORT requests is the "me
+ resource" (or, pre-http-v2, the default VCC).
+
+ Our server should have told us if it supported this protocol
+ correction. If so, we aimed our report at the correct resource
+ and include the filtering path as metadata within the report
+ body. Otherwise, we fall back to the pre-1.8 behavior and just
+ wish for the best.
+
+ See issue #4287:
+ http://subversion.tigris.org/issues/show_bug.cgi?id=4287
+ */
+ if (session->supports_rev_rsrc_replay)
+ {
+ SVN_ERR(svn_ra_serf__get_relative_path(&include_path,
+ session->session_url.path,
+ session, session->conns[0],
+ pool));
+ }
+ else
+ {
+ include_path = NULL;
+ }
+
while (active_reports || rev <= end_revision)
{
- apr_status_t status;
- svn_error_t *err;
svn_ra_serf__list_t *done_list;
svn_ra_serf__list_t *done_reports = NULL;
replay_context_t *replay_ctx;
- /* We're not really interested in the status code here in replay, but
- the XML parsing code will abort on error if it doesn't have a place
- to store the response status code. */
- int status_code;
if (session->cancel_func)
SVN_ERR(session->cancel_func(session->cancel_baton));
@@ -766,6 +798,7 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
svn_ra_serf__handler_t *handler;
svn_ra_serf__xml_parser_t *parser_ctx;
apr_pool_t *ctx_pool = svn_pool_create(pool);
+ const char *replay_target;
replay_ctx = apr_pcalloc(ctx_pool, sizeof(*replay_ctx));
replay_ctx->src_rev_pool = ctx_pool;
@@ -773,19 +806,20 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
replay_ctx->revfinish_func = revfinish_func;
replay_ctx->replay_baton = replay_baton;
replay_ctx->done = FALSE;
+ replay_ctx->include_path = include_path;
replay_ctx->revision = rev;
replay_ctx->low_water_mark = low_water_mark;
replay_ctx->send_deltas = send_deltas;
replay_ctx->done_item.data = replay_ctx;
+
/* Request all properties of a certain revision. */
- replay_ctx->report_target = report_target;
replay_ctx->revs_props = apr_hash_make(replay_ctx->src_rev_pool);
if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
- {
- replay_ctx->revprop_target = apr_psprintf(pool, "%s/%ld",
- session->rev_stub, rev);
- replay_ctx->revprop_rev = SVN_INVALID_REVNUM;
+ {
+ replay_ctx->revprop_target = apr_psprintf(pool, "%s/%ld",
+ session->rev_stub, rev);
+ replay_ctx->revprop_rev = SVN_INVALID_REVNUM;
}
else
{
@@ -793,7 +827,7 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
replay_ctx->revprop_rev = rev;
}
- SVN_ERR(svn_ra_serf__deliver_props(&replay_ctx->prop_ctx,
+ SVN_ERR(svn_ra_serf__deliver_props(&replay_ctx->propfind_handler,
replay_ctx->revs_props, session,
session->conns[0],
replay_ctx->revprop_target,
@@ -802,11 +836,25 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
NULL,
replay_ctx->src_rev_pool));
- /* Send the replay report request. */
+ /* Spin up the serf request for the PROPFIND. */
+ svn_ra_serf__request_create(replay_ctx->propfind_handler);
+
+ /* Send the replay REPORT request. */
+ if (session->supports_rev_rsrc_replay)
+ {
+ replay_target = apr_psprintf(pool, "%s/%ld",
+ session->rev_stub, rev);
+ }
+ else
+ {
+ replay_target = session->session_url.path;
+ }
+
handler = apr_pcalloc(replay_ctx->src_rev_pool, sizeof(*handler));
+ handler->handler_pool = replay_ctx->src_rev_pool;
handler->method = "REPORT";
- handler->path = session->session_url_str;
+ handler->path = replay_target;
handler->body_delegate = create_replay_body;
handler->body_delegate_baton = replay_ctx;
handler->conn = session->conns[0];
@@ -827,12 +875,12 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
parser_ctx->start = start_replay;
parser_ctx->end = end_replay;
parser_ctx->cdata = cdata_replay;
- parser_ctx->status_code = &status_code;
parser_ctx->done = &replay_ctx->done;
parser_ctx->done_list = &done_reports;
parser_ctx->done_item = &replay_ctx->done_item;
handler->response_handler = svn_ra_serf__handle_xml_parser;
handler->response_baton = parser_ctx;
+ replay_ctx->report_handler = handler;
/* This is only needed to handle errors during XML parsing. */
replay_ctx->parser_ctx = parser_ctx;
@@ -843,22 +891,8 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
active_reports++;
}
- /* Run the serf loop, send outgoing and process incoming requests.
- This request will block when there are no more requests to send or
- responses to receive, so we have to be careful on our bookkeeping. */
- status = serf_context_run(session->context, session->timeout,
- pool);
-
- err = session->pending_error;
- session->pending_error = NULL;
-
- if (APR_STATUS_IS_TIMEUP(status))
- {
- svn_error_clear(err);
- return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT,
- NULL,
- _("Connection timed out"));
- }
+ /* Run the serf loop. */
+ SVN_ERR(svn_ra_serf__context_run_wait(&replay_ctx->done, session, pool));
/* Substract the number of completely handled responses from our
total nr. of open requests', so we'll know when to stop this loop.
@@ -867,19 +901,16 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
while (done_list)
{
replay_context_t *ctx = (replay_context_t *)done_list->data;
+ svn_ra_serf__handler_t *done_handler = ctx->report_handler;
done_list = done_list->next;
+ SVN_ERR(svn_ra_serf__error_on_status(done_handler->sline,
+ done_handler->path,
+ done_handler->location));
svn_pool_destroy(ctx->src_rev_pool);
active_reports--;
}
- SVN_ERR(err);
- if (status)
- {
- return svn_error_wrap_apr(status,
- _("Error retrieving replay REPORT (%d)"),
- status);
- }
done_reports = NULL;
}
diff --git a/subversion/libsvn_ra_serf/sb_bucket.c b/subversion/libsvn_ra_serf/sb_bucket.c
new file mode 100644
index 0000000..df0541f
--- /dev/null
+++ b/subversion/libsvn_ra_serf/sb_bucket.c
@@ -0,0 +1,185 @@
+/*
+ * sb_bucket.c : a serf bucket that wraps a spillbuf
+ *
+ * ====================================================================
+ * 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 <serf.h>
+#include <serf_bucket_util.h>
+
+#include "svn_private_config.h"
+#include "private/svn_subr_private.h"
+
+#include "ra_serf.h"
+
+#define SB_BLOCKSIZE 1024
+#define SB_MAXSIZE 32768
+
+
+struct sbb_baton
+{
+ svn_spillbuf_t *spillbuf;
+
+ const char *holding;
+ apr_size_t hold_len;
+
+ apr_pool_t *scratch_pool;
+};
+
+
+svn_error_t *
+svn_ra_serf__copy_into_spillbuf(svn_spillbuf_t **spillbuf,
+ serf_bucket_t *bkt,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *spillbuf = svn_spillbuf__create(SB_BLOCKSIZE, SB_MAXSIZE, result_pool);
+
+ /* Copy all data from the bucket into the spillbuf. */
+ while (TRUE)
+ {
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(bkt, SERF_READ_ALL_AVAIL, &data, &len);
+
+ if (status != APR_SUCCESS && status != APR_EOF)
+ return svn_ra_serf__wrap_err(status, _("Failed to read the request"));
+
+ SVN_ERR(svn_spillbuf__write(*spillbuf, data, len, scratch_pool));
+
+ if (status == APR_EOF)
+ break;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static apr_status_t
+sb_bucket_read(serf_bucket_t *bucket, apr_size_t requested,
+ const char **data, apr_size_t *len)
+{
+ struct sbb_baton *sbb = bucket->data;
+ svn_error_t *err;
+
+ if (sbb->holding)
+ {
+ *data = sbb->holding;
+
+ if (requested < sbb->hold_len)
+ {
+ *len = requested;
+ sbb->holding += requested;
+ sbb->hold_len -= requested;
+ return APR_SUCCESS;
+ }
+
+ /* Return whatever we're holding, and then forget (consume) it. */
+ *len = sbb->hold_len;
+ sbb->holding = NULL;
+ return APR_SUCCESS;
+ }
+
+ err = svn_spillbuf__read(data, len, sbb->spillbuf, sbb->scratch_pool);
+ svn_pool_clear(sbb->scratch_pool);
+
+ /* ### do something with this */
+ svn_error_clear(err);
+
+ /* The spillbuf may have returned more than requested. Stash any extra
+ into our holding area. */
+ if (requested < *len)
+ {
+ sbb->holding = *data + requested;
+ sbb->hold_len = *len - requested;
+ *len = requested;
+ }
+
+ return *data == NULL ? APR_EOF : APR_SUCCESS;
+}
+
+
+static apr_status_t
+sb_bucket_readline(serf_bucket_t *bucket, int acceptable,
+ int *found,
+ const char **data, apr_size_t *len)
+{
+ /* ### for now, we know callers won't use this function. */
+ svn_error_clear(svn_error__malfunction(TRUE, __FILE__, __LINE__,
+ "Not implemented."));
+ return APR_ENOTIMPL;
+}
+
+
+static apr_status_t
+sb_bucket_peek(serf_bucket_t *bucket,
+ const char **data, apr_size_t *len)
+{
+ struct sbb_baton *sbb = bucket->data;
+ svn_error_t *err;
+
+ /* If we're not holding any data, then fill it. */
+ if (sbb->holding == NULL)
+ {
+ err = svn_spillbuf__read(&sbb->holding, &sbb->hold_len, sbb->spillbuf,
+ sbb->scratch_pool);
+ svn_pool_clear(sbb->scratch_pool);
+
+ /* ### do something with this */
+ svn_error_clear(err);
+ }
+
+ /* Return the data we are (now) holding. */
+ *data = sbb->holding;
+ *len = sbb->hold_len;
+
+ return *data == NULL ? APR_EOF : APR_SUCCESS;
+}
+
+
+static const serf_bucket_type_t sb_bucket_vtable = {
+ "SPILLBUF",
+ sb_bucket_read,
+ sb_bucket_readline,
+ serf_default_read_iovec,
+ serf_default_read_for_sendfile,
+ serf_default_read_bucket,
+ sb_bucket_peek,
+ serf_default_destroy_and_data,
+};
+
+
+serf_bucket_t *
+svn_ra_serf__create_sb_bucket(svn_spillbuf_t *spillbuf,
+ serf_bucket_alloc_t *allocator,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct sbb_baton *sbb;
+
+ sbb = serf_bucket_mem_alloc(allocator, sizeof(*sbb));
+ sbb->spillbuf = spillbuf;
+ sbb->holding = NULL;
+ sbb->scratch_pool = svn_pool_create(result_pool);
+
+ return serf_bucket_create(&sb_bucket_vtable, allocator, sbb);
+}
diff --git a/subversion/libsvn_ra_serf/serf.c b/subversion/libsvn_ra_serf/serf.c
index 391f031..66f9962 100644
--- a/subversion/libsvn_ra_serf/serf.c
+++ b/subversion/libsvn_ra_serf/serf.c
@@ -27,9 +27,6 @@
#include <apr_want.h>
#include <apr_uri.h>
-
-#include <expat.h>
-
#include <serf.h>
#include "svn_pools.h"
@@ -48,11 +45,13 @@
#include "private/svn_dav_protocol.h"
#include "private/svn_dep_compat.h"
#include "private/svn_fspath.h"
+#include "private/svn_subr_private.h"
#include "svn_private_config.h"
#include "ra_serf.h"
+/* Implements svn_ra__vtable_t.get_version(). */
static const svn_version_t *
ra_serf_version(void)
{
@@ -62,12 +61,21 @@ ra_serf_version(void)
#define RA_SERF_DESCRIPTION \
N_("Module for accessing a repository via WebDAV protocol using serf.")
+#define RA_SERF_DESCRIPTION_VER \
+ N_("Module for accessing a repository via WebDAV protocol using serf.\n" \
+ " - using serf %d.%d.%d")
+
+/* Implements svn_ra__vtable_t.get_description(). */
static const char *
-ra_serf_get_description(void)
+ra_serf_get_description(apr_pool_t *pool)
{
- return _(RA_SERF_DESCRIPTION);
+ int major, minor, patch;
+
+ serf_lib_version(&major, &minor, &patch);
+ return apr_psprintf(pool, _(RA_SERF_DESCRIPTION_VER), major, minor, patch);
}
+/* Implements svn_ra__vtable_t.get_schemes(). */
static const char * const *
ra_serf_get_schemes(apr_pool_t *pool)
{
@@ -103,12 +111,11 @@ load_http_auth_types(apr_pool_t *pool, svn_config_t *config,
if (http_auth_types)
{
- char *token, *last;
+ char *token;
char *auth_types_list = apr_palloc(pool, strlen(http_auth_types) + 1);
apr_collapse_spaces(auth_types_list, http_auth_types);
- while ((token = apr_strtok(auth_types_list, ";", &last)) != NULL)
+ while ((token = svn_cstring_tokenize(";", &auth_types_list)) != NULL)
{
- auth_types_list = NULL;
if (svn_cstring_casecmp("basic", token) == 0)
*authn_types |= SERF_AUTHN_BASIC;
else if (svn_cstring_casecmp("digest", token) == 0)
@@ -119,8 +126,9 @@ load_http_auth_types(apr_pool_t *pool, svn_config_t *config,
*authn_types |= SERF_AUTHN_NEGOTIATE;
else
return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
- _("Invalid config: unknown http auth"
- "type '%s'"), token);
+ _("Invalid config: unknown %s "
+ "'%s'"),
+ SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, token);
}
}
else
@@ -131,7 +139,15 @@ load_http_auth_types(apr_pool_t *pool, svn_config_t *config,
return SVN_NO_ERROR;
}
-#define DEFAULT_HTTP_TIMEOUT 3600
+
+/* Default HTTP timeout (in seconds); overridden by the 'http-timeout'
+ runtime configuration variable. */
+#define DEFAULT_HTTP_TIMEOUT 600
+
+/* Private symbol for the 1.9-public SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS */
+#define OPTION_HTTP_CHUNKED_REQUESTS "http-chunked-requests"
+
+
static svn_error_t *
load_config(svn_ra_serf__session_t *session,
apr_hash_t *config_hash,
@@ -144,14 +160,12 @@ load_config(svn_ra_serf__session_t *session,
const char *timeout_str = NULL;
const char *exceptions;
apr_port_t proxy_port;
- svn_boolean_t is_exception = FALSE;
+ svn_tristate_t chunked_requests;
if (config_hash)
{
- config = apr_hash_get(config_hash, SVN_CONFIG_CATEGORY_SERVERS,
- APR_HASH_KEY_STRING);
- config_client = apr_hash_get(config_hash, SVN_CONFIG_CATEGORY_CONFIG,
- APR_HASH_KEY_STRING);
+ config = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_SERVERS);
+ config_client = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_CONFIG);
}
else
{
@@ -184,16 +198,11 @@ load_config(svn_ra_serf__session_t *session,
/* Use the default proxy-specific settings if and only if
"http-proxy-exceptions" is not set to exclude this host. */
svn_config_get(config, &exceptions, SVN_CONFIG_SECTION_GLOBAL,
- SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, NULL);
- if (exceptions)
- {
- apr_array_header_t *l = svn_cstring_split(exceptions, ",", TRUE, pool);
- is_exception = svn_cstring_match_glob_list(session->session_url.hostname,
- l);
- }
- if (! is_exception)
+ SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, "");
+ if (! svn_cstring_match_glob_list(session->session_url.hostname,
+ svn_cstring_split(exceptions, ",",
+ TRUE, pool)))
{
- /* Load the global proxy server settings, if set. */
svn_config_get(config, &proxy_host, SVN_CONFIG_SECTION_GLOBAL,
SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
svn_config_get(config, &port_str, SVN_CONFIG_SECTION_GLOBAL,
@@ -214,6 +223,26 @@ load_config(svn_ra_serf__session_t *session,
svn_config_get(config, &session->ssl_authorities, SVN_CONFIG_SECTION_GLOBAL,
SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, NULL);
+ /* If set, read the flag that tells us to do bulk updates or not. Defaults
+ to skelta updates. */
+ SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
+ "auto",
+ svn_tristate_unknown));
+
+ /* Load the maximum number of parallel session connections. */
+ SVN_ERR(svn_config_get_int64(config, &session->max_connections,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
+ SVN_CONFIG_DEFAULT_OPTION_HTTP_MAX_CONNECTIONS));
+
+ /* Should we use chunked transfer encoding. */
+ SVN_ERR(svn_config_get_tristate(config, &chunked_requests,
+ SVN_CONFIG_SECTION_GLOBAL,
+ OPTION_HTTP_CHUNKED_REQUESTS,
+ "auto", svn_tristate_unknown));
+
if (config)
server_group = svn_config_find_group(config,
session->session_url.hostname,
@@ -233,26 +262,61 @@ load_config(svn_ra_serf__session_t *session,
svn_auth_set_parameter(session->wc_callbacks->auth_baton,
SVN_AUTH_PARAM_SERVER_GROUP, server_group);
- /* Load the group proxy server settings, overriding global settings. */
+ /* Load the group proxy server settings, overriding global
+ settings. We intentionally ignore 'http-proxy-exceptions'
+ here because, well, if this site was an exception, why is
+ there a per-server proxy configuration for it? */
svn_config_get(config, &proxy_host, server_group,
- SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
+ SVN_CONFIG_OPTION_HTTP_PROXY_HOST, proxy_host);
svn_config_get(config, &port_str, server_group,
- SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL);
+ SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str);
svn_config_get(config, &session->proxy_username, server_group,
- SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL);
+ SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME,
+ session->proxy_username);
svn_config_get(config, &session->proxy_password, server_group,
- SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL);
+ SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD,
+ session->proxy_password);
/* Load the group ssl settings. */
SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
server_group,
SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
- TRUE));
+ session->trust_default_ca));
svn_config_get(config, &session->ssl_authorities, server_group,
- SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, NULL);
+ SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES,
+ session->ssl_authorities);
+
+ /* Load the group bulk updates flag. */
+ SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
+ server_group,
+ SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
+ "auto",
+ session->bulk_updates));
+
+ /* Load the maximum number of parallel session connections,
+ overriding global values. */
+ SVN_ERR(svn_config_get_int64(config, &session->max_connections,
+ server_group,
+ SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
+ session->max_connections));
+
+ /* Should we use chunked transfer encoding. */
+ SVN_ERR(svn_config_get_tristate(config, &chunked_requests,
+ server_group,
+ OPTION_HTTP_CHUNKED_REQUESTS,
+ "auto", chunked_requests));
}
+ /* Don't allow the http-max-connections value to be larger than our
+ compiled-in limit, or to be too small to operate. Broken
+ functionality and angry administrators are equally undesirable. */
+ if (session->max_connections > SVN_RA_SERF__MAX_CONNECTIONS_LIMIT)
+ session->max_connections = SVN_RA_SERF__MAX_CONNECTIONS_LIMIT;
+ if (session->max_connections < 2)
+ session->max_connections = 2;
+
/* Parse the connection timeout value, if any. */
+ session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT);
if (timeout_str)
{
char *endstr;
@@ -267,8 +331,7 @@ load_config(svn_ra_serf__session_t *session,
_("Invalid config: negative timeout value"));
session->timeout = apr_time_from_sec(timeout);
}
- else
- session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT);
+ SVN_ERR_ASSERT(session->timeout >= 0);
/* Convert the proxy port value, if any. */
if (port_str)
@@ -290,7 +353,9 @@ load_config(svn_ra_serf__session_t *session,
proxy_port = (apr_port_t) port;
}
else
- proxy_port = 80;
+ {
+ proxy_port = 80;
+ }
if (proxy_host)
{
@@ -302,15 +367,35 @@ load_config(svn_ra_serf__session_t *session,
session->pool);
if (status)
{
- return svn_error_wrap_apr(status,
- _("Could not resolve proxy server '%s'"),
- proxy_host);
+ return svn_ra_serf__wrap_err(
+ status, _("Could not resolve proxy server '%s'"),
+ proxy_host);
}
session->using_proxy = TRUE;
serf_config_proxy(session->context, proxy_addr);
}
else
- session->using_proxy = FALSE;
+ {
+ session->using_proxy = FALSE;
+ }
+
+ /* Setup detect_chunking and using_chunked_requests based on
+ * the chunked_requests tristate */
+ if (chunked_requests == svn_tristate_unknown)
+ {
+ session->detect_chunking = TRUE;
+ session->using_chunked_requests = TRUE;
+ }
+ else if (chunked_requests == svn_tristate_true)
+ {
+ session->detect_chunking = FALSE;
+ session->using_chunked_requests = TRUE;
+ }
+ else /* chunked_requests == svn_tristate_false */
+ {
+ session->detect_chunking = FALSE;
+ session->using_chunked_requests = FALSE;
+ }
/* Setup authentication. */
SVN_ERR(load_http_auth_types(pool, config, server_group,
@@ -335,10 +420,23 @@ svn_ra_serf__progress(void *progress_baton, apr_off_t read, apr_off_t written)
}
}
+/** Our User-Agent string. */
+static const char *
+get_user_agent_string(apr_pool_t *pool)
+{
+ int major, minor, patch;
+ serf_lib_version(&major, &minor, &patch);
+
+ return apr_psprintf(pool, "SVN/%s (%s) serf/%d.%d.%d",
+ SVN_VER_NUMBER, SVN_BUILD_TARGET,
+ major, minor, patch);
+}
+
+/* Implements svn_ra__vtable_t.open_session(). */
static svn_error_t *
svn_ra_serf__open(svn_ra_session_t *session,
const char **corrected_url,
- const char *repos_URL,
+ const char *session_URL,
const svn_ra_callbacks2_t *callbacks,
void *callback_baton,
apr_hash_t *config,
@@ -348,6 +446,7 @@ svn_ra_serf__open(svn_ra_session_t *session,
svn_ra_serf__session_t *serf_sess;
apr_uri_t url;
const char *client_string = NULL;
+ svn_error_t *err;
if (corrected_url)
*corrected_url = NULL;
@@ -368,33 +467,40 @@ svn_ra_serf__open(svn_ra_session_t *session,
serf_sess->pool));
- status = apr_uri_parse(serf_sess->pool, repos_URL, &url);
+ status = apr_uri_parse(serf_sess->pool, session_URL, &url);
if (status)
{
return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
- _("Illegal repository URL '%s'"),
- repos_URL);
+ _("Illegal URL '%s'"),
+ session_URL);
}
- /* Contrary to what the comment for apr_uri_t.path says in apr-util 1.2.12 and
- older, for root paths url.path will be "", where serf requires "/". */
+ /* Depending the version of apr-util in use, for root paths url.path
+ will be NULL or "", where serf requires "/". */
if (url.path == NULL || url.path[0] == '\0')
- url.path = apr_pstrdup(serf_sess->pool, "/");
+ {
+ url.path = apr_pstrdup(serf_sess->pool, "/");
+ }
if (!url.port)
{
url.port = apr_uri_port_of_scheme(url.scheme);
}
serf_sess->session_url = url;
- serf_sess->session_url_str = apr_pstrdup(serf_sess->pool, repos_URL);
+ serf_sess->session_url_str = apr_pstrdup(serf_sess->pool, session_URL);
serf_sess->using_ssl = (svn_cstring_casecmp(url.scheme, "https") == 0);
serf_sess->supports_deadprop_count = svn_tristate_unknown;
serf_sess->capabilities = apr_hash_make(serf_sess->pool);
- SVN_ERR(load_config(serf_sess, config, serf_sess->pool));
+ /* We have to assume that the server only supports HTTP/1.0. Once it's clear
+ HTTP/1.1 is supported, we can upgrade. */
+ serf_sess->http10 = TRUE;
+ /* If we switch to HTTP/1.1, then we will use chunked requests. We may disable
+ this, if we find an intervening proxy does not support chunked requests. */
+ serf_sess->using_chunked_requests = TRUE;
- serf_sess->conns = apr_palloc(serf_sess->pool, sizeof(*serf_sess->conns) * 4);
+ SVN_ERR(load_config(serf_sess, config, serf_sess->pool));
serf_sess->conns[0] = apr_pcalloc(serf_sess->pool,
sizeof(*serf_sess->conns[0]));
@@ -403,21 +509,15 @@ svn_ra_serf__open(svn_ra_session_t *session,
serf_sess->conns[0]->session = serf_sess;
serf_sess->conns[0]->last_status_code = -1;
- serf_sess->conns[0]->using_ssl = serf_sess->using_ssl;
- serf_sess->conns[0]->server_cert_failures = 0;
- serf_sess->conns[0]->using_compression = serf_sess->using_compression;
- serf_sess->conns[0]->hostname = url.hostname;
- serf_sess->conns[0]->useragent = NULL;
-
/* create the user agent string */
if (callbacks->get_client_string)
- callbacks->get_client_string(callback_baton, &client_string, pool);
+ SVN_ERR(callbacks->get_client_string(callback_baton, &client_string, pool));
if (client_string)
- serf_sess->conns[0]->useragent = apr_pstrcat(pool, USER_AGENT, "/",
- client_string, (char *)NULL);
+ serf_sess->useragent = apr_pstrcat(pool, get_user_agent_string(pool), " ",
+ client_string, (char *)NULL);
else
- serf_sess->conns[0]->useragent = USER_AGENT;
+ serf_sess->useragent = get_user_agent_string(pool);
/* go ahead and tell serf about the connection. */
status =
@@ -428,7 +528,7 @@ svn_ra_serf__open(svn_ra_session_t *session,
svn_ra_serf__conn_closed, serf_sess->conns[0],
serf_sess->pool);
if (status)
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
/* Set the progress callback. */
serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress,
@@ -438,9 +538,26 @@ svn_ra_serf__open(svn_ra_session_t *session,
session->priv = serf_sess;
- return svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, pool);
+ err = svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, pool);
+
+ /* serf should produce a usable error code instead of APR_EGENERAL */
+ if (err && err->apr_err == APR_EGENERAL)
+ err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, err,
+ _("Connection to '%s' failed"), session_URL);
+ SVN_ERR(err);
+
+ /* We have set up a useful connection (that doesn't indication a redirect).
+ If we've been told there is possibly a worrisome proxy in our path to the
+ server AND we switched to HTTP/1.1 (chunked requests), then probe for
+ problems in any proxy. */
+ if ((corrected_url == NULL || *corrected_url == NULL)
+ && serf_sess->detect_chunking && !serf_sess->http10)
+ SVN_ERR(svn_ra_serf__probe_proxy(serf_sess, pool));
+
+ return SVN_NO_ERROR;
}
+/* Implements svn_ra__vtable_t.reparent(). */
static svn_error_t *
svn_ra_serf__reparent(svn_ra_session_t *ra_session,
const char *url,
@@ -470,19 +587,31 @@ svn_ra_serf__reparent(svn_ra_session_t *ra_session,
"URL '%s'"), url, session->repos_root_str);
}
- status = apr_uri_parse(session->pool, url, &new_url);
+ status = apr_uri_parse(pool, url, &new_url);
if (status)
{
return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
_("Illegal repository URL '%s'"), url);
}
- session->session_url.path = new_url.path;
+ /* Depending the version of apr-util in use, for root paths url.path
+ will be NULL or "", where serf requires "/". */
+ /* ### Maybe we should use a string buffer for these strings so we
+ ### don't allocate memory in the session on every reparent? */
+ if (new_url.path == NULL || new_url.path[0] == '\0')
+ {
+ session->session_url.path = apr_pstrdup(session->pool, "/");
+ }
+ else
+ {
+ session->session_url.path = apr_pstrdup(session->pool, new_url.path);
+ }
session->session_url_str = apr_pstrdup(session->pool, url);
return SVN_NO_ERROR;
}
+/* Implements svn_ra__vtable_t.get_session_url(). */
static svn_error_t *
svn_ra_serf__get_session_url(svn_ra_session_t *ra_session,
const char **url,
@@ -493,20 +622,19 @@ svn_ra_serf__get_session_url(svn_ra_session_t *ra_session,
return SVN_NO_ERROR;
}
+/* Implements svn_ra__vtable_t.get_latest_revnum(). */
static svn_error_t *
svn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session,
svn_revnum_t *latest_revnum,
apr_pool_t *pool)
{
- const char *relative_url, *basecoll_url;
svn_ra_serf__session_t *session = ra_session->priv;
- return svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url, session,
- NULL, session->session_url.path,
- SVN_INVALID_REVNUM, latest_revnum,
- pool);
+ return svn_error_trace(svn_ra_serf__get_youngest_revnum(
+ latest_revnum, session, pool));
}
+/* Implements svn_ra__vtable_t.rev_proplist(). */
static svn_error_t *
svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session,
svn_revnum_t rev,
@@ -533,6 +661,7 @@ svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session,
SVN_ERR(svn_ra_serf__discover_vcc(&propfind_path, session, NULL, pool));
}
+ /* ### fix: fetch hash of *just* the PATH@REV props. no nested hash. */
SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
propfind_path, rev, "0", all_props,
pool, pool));
@@ -543,6 +672,7 @@ svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session,
return SVN_NO_ERROR;
}
+/* Implements svn_ra__vtable_t.rev_prop(). */
static svn_error_t *
svn_ra_serf__rev_prop(svn_ra_session_t *session,
svn_revnum_t rev,
@@ -554,77 +684,49 @@ svn_ra_serf__rev_prop(svn_ra_session_t *session,
SVN_ERR(svn_ra_serf__rev_proplist(session, rev, &props, pool));
- *value = apr_hash_get(props, name, APR_HASH_KEY_STRING);
+ *value = svn_hash_gets(props, name);
return SVN_NO_ERROR;
}
static svn_error_t *
-fetch_path_props(svn_ra_serf__propfind_context_t **ret_prop_ctx,
- apr_hash_t **ret_props,
- const char **ret_path,
- svn_revnum_t *ret_revision,
+fetch_path_props(apr_hash_t **props,
svn_ra_serf__session_t *session,
- const char *rel_path,
+ const char *session_relpath,
svn_revnum_t revision,
const svn_ra_serf__dav_props_t *desired_props,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- svn_ra_serf__propfind_context_t *prop_ctx;
- apr_hash_t *props;
- const char *path;
+ const char *url;
- path = session->session_url.path;
+ url = session->session_url.path;
/* If we have a relative path, append it. */
- if (rel_path)
- {
- path = svn_path_url_add_component2(path, rel_path, pool);
- }
+ if (session_relpath)
+ url = svn_path_url_add_component2(url, session_relpath, scratch_pool);
- props = apr_hash_make(pool);
-
- /* If we were given a specific revision, we have to fetch the VCC and
- * do a PROPFIND off of that.
- */
- if (!SVN_IS_VALID_REVNUM(revision))
- {
- SVN_ERR(svn_ra_serf__deliver_props(&prop_ctx, props, session,
- session->conns[0], path, revision,
- "0", desired_props, NULL,
- pool));
- }
- else
+ /* If we were given a specific revision, get a URL that refers to that
+ specific revision (rather than floating with HEAD). */
+ if (SVN_IS_VALID_REVNUM(revision))
{
- const char *relative_url, *basecoll_url;
-
- SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url,
- session, NULL, path,
- revision, NULL, pool));
-
- /* We will try again with our new path; however, we're now
- * technically an unversioned resource because we are accessing
- * the revision's baseline-collection.
- */
- path = svn_path_url_add_component2(basecoll_url, relative_url, pool);
- revision = SVN_INVALID_REVNUM;
- SVN_ERR(svn_ra_serf__deliver_props(&prop_ctx, props, session,
- session->conns[0], path, revision,
- "0", desired_props, NULL,
- pool));
+ SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ url, revision,
+ scratch_pool, scratch_pool));
}
- /* ### switch to svn_ra_serf__retrieve_props? */
- SVN_ERR(svn_ra_serf__wait_for_props(prop_ctx, session, pool));
-
- *ret_path = path;
- *ret_prop_ctx = prop_ctx;
- *ret_props = props;
- *ret_revision = revision;
+ /* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant.
+ Or we started with SVN_INVALID_REVNUM and URL may be floating. */
+ SVN_ERR(svn_ra_serf__fetch_node_props(props, session->conns[0],
+ url, SVN_INVALID_REVNUM,
+ desired_props,
+ result_pool, scratch_pool));
return SVN_NO_ERROR;
}
+/* Implements svn_ra__vtable_t.check_path(). */
static svn_error_t *
svn_ra_serf__check_path(svn_ra_session_t *ra_session,
const char *rel_path,
@@ -634,13 +736,10 @@ svn_ra_serf__check_path(svn_ra_session_t *ra_session,
{
svn_ra_serf__session_t *session = ra_session->priv;
apr_hash_t *props;
- svn_ra_serf__propfind_context_t *prop_ctx;
- const char *path;
- svn_revnum_t fetched_rev;
- svn_error_t *err = fetch_path_props(&prop_ctx, &props, &path, &fetched_rev,
- session, rel_path,
- revision, check_path_props, pool);
+ svn_error_t *err = fetch_path_props(&props, session, rel_path,
+ revision, check_path_props,
+ pool, pool);
if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
{
@@ -651,9 +750,9 @@ svn_ra_serf__check_path(svn_ra_session_t *ra_session,
{
/* Any other error, raise to caller. */
if (err)
- return err;
+ return svn_error_trace(err);
- SVN_ERR(svn_ra_serf__get_resource_type(kind, props, path, fetched_rev));
+ SVN_ERR(svn_ra_serf__get_resource_type(kind, props));
}
return SVN_NO_ERROR;
@@ -775,14 +874,14 @@ path_dirent_walker(void *baton,
{
const char *base_name;
- entry = apr_pcalloc(pool, sizeof(*entry));
+ entry = svn_dirent_create(pool);
apr_hash_set(dirents->full_paths, path, path_len, entry);
base_name = svn_path_uri_decode(svn_urlpath__basename(path, pool),
pool);
- apr_hash_set(dirents->base_paths, base_name, APR_HASH_KEY_STRING, entry);
+ svn_hash_sets(dirents->base_paths, base_name, entry);
}
dwb.entry = entry;
@@ -863,6 +962,7 @@ get_dirent_props(apr_uint32_t dirent_fields,
return (svn_ra_serf__dav_props_t *) props->elts;
}
+/* Implements svn_ra__vtable_t.stat(). */
static svn_error_t *
svn_ra_serf__stat(svn_ra_session_t *ra_session,
const char *rel_path,
@@ -872,17 +972,14 @@ svn_ra_serf__stat(svn_ra_session_t *ra_session,
{
svn_ra_serf__session_t *session = ra_session->priv;
apr_hash_t *props;
- svn_ra_serf__propfind_context_t *prop_ctx;
- const char *path;
- svn_revnum_t fetched_rev;
svn_error_t *err;
struct dirent_walker_baton_t dwb;
svn_tristate_t deadprop_count = svn_tristate_unknown;
- err = fetch_path_props(&prop_ctx, &props, &path, &fetched_rev,
+ err = fetch_path_props(&props,
session, rel_path, revision,
get_dirent_props(SVN_DIRENT_ALL, session, pool),
- pool);
+ pool, pool);
if (err)
{
if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
@@ -895,12 +992,10 @@ svn_ra_serf__stat(svn_ra_session_t *ra_session,
return svn_error_trace(err);
}
- dwb.entry = apr_pcalloc(pool, sizeof(*dwb.entry));
+ dwb.entry = svn_dirent_create(pool);
dwb.supports_deadprop_count = &deadprop_count;
dwb.result_pool = pool;
- SVN_ERR(svn_ra_serf__walk_all_props(props, path, fetched_rev,
- dirent_walker, &dwb,
- pool));
+ SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool));
if (deadprop_count == svn_tristate_false
&& session->supports_deadprop_count == svn_tristate_unknown
@@ -910,14 +1005,12 @@ svn_ra_serf__stat(svn_ra_session_t *ra_session,
information */
session->supports_deadprop_count = svn_tristate_false;
- SVN_ERR(fetch_path_props(&prop_ctx, &props, &path, &fetched_rev,
- session, rel_path, fetched_rev,
+ SVN_ERR(fetch_path_props(&props,
+ session, rel_path, SVN_INVALID_REVNUM,
get_dirent_props(SVN_DIRENT_ALL, session, pool),
- pool));
+ pool, pool));
- SVN_ERR(svn_ra_serf__walk_all_props(props, path, fetched_rev,
- dirent_walker, &dwb,
- pool));
+ SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool));
}
if (deadprop_count != svn_tristate_unknown)
@@ -933,13 +1026,11 @@ svn_ra_serf__stat(svn_ra_session_t *ra_session,
* SVN_ERR_FS_NOT_DIRECTORY if not.
*/
static svn_error_t *
-resource_is_directory(apr_hash_t *props,
- const char *path,
- svn_revnum_t revision)
+resource_is_directory(apr_hash_t *props)
{
svn_node_kind_t kind;
- SVN_ERR(svn_ra_serf__get_resource_type(&kind, props, path, revision));
+ SVN_ERR(svn_ra_serf__get_resource_type(&kind, props));
if (kind != svn_node_dir)
{
@@ -950,6 +1041,7 @@ resource_is_directory(apr_hash_t *props,
return SVN_NO_ERROR;
}
+/* Implements svn_ra__vtable_t.get_dir(). */
static svn_error_t *
svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
apr_hash_t **dirents,
@@ -976,34 +1068,37 @@ svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
public url. */
if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
{
- const char *relative_url, *basecoll_url;
-
- SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url,
- session, NULL, path, revision,
- fetched_rev, pool));
-
- path = svn_path_url_add_component2(basecoll_url, relative_url, pool);
+ SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev,
+ session, NULL /* conn */,
+ path, revision,
+ pool, pool));
revision = SVN_INVALID_REVNUM;
}
+ /* REVISION is always SVN_INVALID_REVNUM */
+ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
/* If we're asked for children, fetch them now. */
if (dirents)
{
struct path_dirent_visitor_t dirent_walk;
apr_hash_t *props;
+ const char *rtype;
/* Always request node kind to check that path is really a
* directory.
*/
dirent_fields |= SVN_DIRENT_KIND;
SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
- path, revision, "1",
+ path, SVN_INVALID_REVNUM, "1",
get_dirent_props(dirent_fields,
session, pool),
pool, pool));
/* Check if the path is really a directory. */
- SVN_ERR(resource_is_directory(props, path, revision));
+ rtype = svn_ra_serf__get_prop(props, path, "DAV:", "resourcetype");
+ if (rtype == NULL || strcmp(rtype, "collection") != 0)
+ return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Can't get entries of non-directory"));
/* We're going to create two hashes to help the walker along.
* We're going to return the 2nd one back to the caller as it
@@ -1015,8 +1110,9 @@ svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
dirent_walk.supports_deadprop_count = svn_tristate_unknown;
dirent_walk.result_pool = pool;
- SVN_ERR(svn_ra_serf__walk_all_paths(props, revision, path_dirent_walker,
- &dirent_walk, pool));
+ SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM,
+ path_dirent_walker, &dirent_walk,
+ pool));
if (dirent_walk.supports_deadprop_count == svn_tristate_false
&& session->supports_deadprop_count == svn_tristate_unknown
@@ -1027,15 +1123,15 @@ svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
session->supports_deadprop_count = svn_tristate_false;
SVN_ERR(svn_ra_serf__retrieve_props(&props, session,
session->conns[0],
- path, revision, "1",
+ path, SVN_INVALID_REVNUM, "1",
get_dirent_props(dirent_fields,
session, pool),
pool, pool));
- SVN_ERR(svn_hash__clear(dirent_walk.full_paths, pool));
- SVN_ERR(svn_hash__clear(dirent_walk.base_paths, pool));
+ apr_hash_clear(dirent_walk.full_paths);
+ apr_hash_clear(dirent_walk.base_paths);
- SVN_ERR(svn_ra_serf__walk_all_paths(props, revision,
+ SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM,
path_dirent_walker,
&dirent_walk, pool));
}
@@ -1051,20 +1147,23 @@ svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
{
apr_hash_t *props;
- SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
- path, revision, "0", all_props,
- pool, pool));
+ SVN_ERR(svn_ra_serf__fetch_node_props(&props, session->conns[0],
+ path, SVN_INVALID_REVNUM,
+ all_props,
+ pool, pool));
+
/* Check if the path is really a directory. */
- SVN_ERR(resource_is_directory(props, path, revision));
+ SVN_ERR(resource_is_directory(props));
- SVN_ERR(svn_ra_serf__flatten_props(ret_props, props, path, revision,
- pool, pool));
+ /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props()
+ ### put them into POOL, so we're okay. */
+ SVN_ERR(svn_ra_serf__flatten_props(ret_props, props, pool, pool));
}
return SVN_NO_ERROR;
}
-static svn_error_t *
+svn_error_t *
svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
const char **url,
apr_pool_t *pool)
@@ -1089,6 +1188,8 @@ svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
case where the root of the repository is not readable.
However, it does not handle the case where we're fetching path not existing
in HEAD of a repository with unreadable root directory.
+
+ Implements svn_ra__vtable_t.get_uuid().
*/
static svn_error_t *
svn_ra_serf__get_uuid(svn_ra_session_t *ra_session,
@@ -1158,7 +1259,9 @@ static const svn_ra__vtable_t serf_vtable = {
svn_ra_serf__replay,
svn_ra_serf__has_capability,
svn_ra_serf__replay_range,
- svn_ra_serf__get_deleted_rev
+ svn_ra_serf__get_deleted_rev,
+ svn_ra_serf__register_editor_shim_callbacks,
+ svn_ra_serf__get_inherited_props
};
svn_error_t *
@@ -1176,7 +1279,7 @@ svn_ra_serf__init(const svn_version_t *loader_version,
int serf_minor;
int serf_patch;
- SVN_ERR(svn_ver_check_list(ra_serf_version(), checklist));
+ SVN_ERR(svn_ver_check_list2(ra_serf_version(), checklist, svn_ver_equal));
/* Simplified version check to make sure we can safely use the
VTABLE parameter. The RA loader does a more exhaustive check. */
@@ -1196,6 +1299,7 @@ svn_ra_serf__init(const svn_version_t *loader_version,
|| serf_minor < SERF_MINOR_VERSION)
{
return svn_error_createf(
+ /* ### should return a unique error */
SVN_ERR_VERSION_MISMATCH, NULL,
_("ra_serf was compiled for serf %d.%d.%d but loaded "
"an incompatible %d.%d.%d library"),
diff --git a/subversion/libsvn_ra_serf/update.c b/subversion/libsvn_ra_serf/update.c
index f82b778..88488ff 100644
--- a/subversion/libsvn_ra_serf/update.c
+++ b/subversion/libsvn_ra_serf/update.c
@@ -31,6 +31,7 @@
#include <serf.h>
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_ra.h"
#include "svn_dav.h"
@@ -43,6 +44,7 @@
#include "svn_private_config.h"
#include "private/svn_dep_compat.h"
#include "private/svn_fspath.h"
+#include "private/svn_string_private.h"
#include "ra_serf.h"
#include "../libsvn_ra/ra_loader.h"
@@ -62,13 +64,19 @@
*/
typedef enum report_state_e {
NONE = 0,
+ INITIAL = 0,
+ UPDATE_REPORT,
+ TARGET_REVISION,
OPEN_DIR,
ADD_DIR,
+ ABSENT_DIR,
OPEN_FILE,
ADD_FILE,
+ ABSENT_FILE,
PROP,
IGNORE_PROP_NAME,
- NEED_PROP_NAME
+ NEED_PROP_NAME,
+ TXDELTA
} report_state_e;
@@ -84,10 +92,10 @@ typedef enum report_state_e {
since network and parsing behavior (ie. it doesn't pause immediately)
can make the measurements quite imprecise.
- We measure outstanding requests as the sum of ACTIVE_FETCHES and
- ACTIVE_PROPFINDS in the report_context_t structure. */
-#define REQUEST_COUNT_TO_PAUSE 1000
-#define REQUEST_COUNT_TO_RESUME 100
+ We measure outstanding requests as the sum of NUM_ACTIVE_FETCHES and
+ NUM_ACTIVE_PROPFINDS in the report_context_t structure. */
+#define REQUEST_COUNT_TO_PAUSE 50
+#define REQUEST_COUNT_TO_RESUME 40
/* Forward-declare our report context. */
@@ -118,24 +126,17 @@ typedef struct report_dir_t
/* the canonical url for this directory after updating. (received) */
const char *url;
- /* The original repos_relpath of this url (from the workingcopy)
+ /* The original repos_relpath of this url (from the working copy)
or NULL if the repos_relpath can be calculated from the edit root. */
const char *repos_relpath;
/* Our base revision - SVN_INVALID_REVNUM if we're adding this dir. */
svn_revnum_t base_rev;
- /* The target revision we're retrieving. */
- svn_revnum_t target_rev;
-
- /* controlling dir baton - this is only created in open_dir() */
+ /* controlling dir baton - this is only created in ensure_dir_opened() */
void *dir_baton;
apr_pool_t *dir_baton_pool;
- /* Our master update editor and baton. */
- const svn_delta_editor_t *update_editor;
- void *update_baton;
-
/* How many references to this directory do we still have open? */
apr_size_t ref_count;
@@ -149,7 +150,7 @@ typedef struct report_dir_t
apr_hash_t *removed_props;
/* The propfind request for our current directory */
- svn_ra_serf__propfind_context_t *propfind;
+ svn_ra_serf__handler_t *propfind_handler;
/* Has the server told us to fetch the dir props? */
svn_boolean_t fetch_props;
@@ -199,9 +200,6 @@ typedef struct report_info_t
/* Our base revision - SVN_INVALID_REVNUM if we're adding this file. */
svn_revnum_t base_rev;
- /* The target revision we're retrieving. */
- svn_revnum_t target_rev;
-
/* our delta base, if present (NULL if we're adding the file) */
const char *delta_base;
@@ -212,7 +210,7 @@ typedef struct report_info_t
svn_revnum_t copyfrom_rev;
/* The propfind request for our current file (if present) */
- svn_ra_serf__propfind_context_t *propfind;
+ svn_ra_serf__handler_t *propfind_handler;
/* Has the server told us to fetch the file props? */
svn_boolean_t fetch_props;
@@ -229,20 +227,25 @@ typedef struct report_info_t
/* controlling file_baton and textdelta handler */
void *file_baton;
const char *base_checksum;
- const char *final_sha1_checksum; /* ### currently unused */
+ const char *final_sha1_checksum;
svn_txdelta_window_handler_t textdelta;
void *textdelta_baton;
+ svn_stream_t *svndiff_decoder;
+ svn_stream_t *base64_decoder;
/* Checksum for close_file */
const char *final_checksum;
+ /* Stream containing file contents already cached in the working
+ copy (which may be used to avoid a GET request for the same). */
+ svn_stream_t *cached_contents;
+
/* temporary property for this file which is currently being parsed
* It will eventually be stored in our parent directory's property hash.
*/
const char *prop_ns;
const char *prop_name;
- const char *prop_val;
- apr_size_t prop_val_len;
+ svn_stringbuf_t *prop_value;
const char *prop_encoding;
} report_info_t;
@@ -252,6 +255,9 @@ typedef struct report_info_t
*/
typedef struct report_fetch_t {
+ /* The handler representing this particular fetch. */
+ svn_ra_serf__handler_t *handler;
+
/* The session we should use to fetch the file. */
svn_ra_serf__session_t *sess;
@@ -316,8 +322,12 @@ struct report_context_t {
/* Do we want the server to send copyfrom args or not? */
svn_boolean_t send_copyfrom_args;
- /* Path -> lock token mapping. */
- apr_hash_t *lock_path_tokens;
+ /* Is the server sending everything in one response? */
+ svn_boolean_t send_all_mode;
+
+ /* Is the server including properties inline for newly added
+ files/dirs? */
+ svn_boolean_t add_props_included;
/* Path -> const char *repos_relpath mapping */
apr_hash_t *switched_paths;
@@ -341,16 +351,20 @@ struct report_context_t {
report_dir_t *root_dir;
/* number of pending GET requests */
- unsigned int active_fetches;
+ unsigned int num_active_fetches;
/* completed fetches (contains report_fetch_t) */
svn_ra_serf__list_t *done_fetches;
/* number of pending PROPFIND requests */
- unsigned int active_propfinds;
+ unsigned int num_active_propfinds;
- /* completed PROPFIND requests (contains propfind_context_t) */
+ /* completed PROPFIND requests (contains svn_ra_serf__handler_t) */
svn_ra_serf__list_t *done_propfinds;
+ svn_ra_serf__list_t *done_dir_propfinds;
+
+ /* list of outstanding prop changes (contains report_dir_t) */
+ svn_ra_serf__list_t *active_dir_propfinds;
/* list of files that only have prop changes (contains report_info_t) */
svn_ra_serf__list_t *file_propchanges_only;
@@ -361,10 +375,171 @@ struct report_context_t {
/* Are we done parsing the REPORT response? */
svn_boolean_t done;
+ /* Did we receive all data from the network? */
+ svn_boolean_t report_received;
+
+ /* Did we get a complete (non-truncated) report? */
+ svn_boolean_t report_completed;
+
/* The XML parser context for the REPORT response. */
svn_ra_serf__xml_parser_t *parser_ctx;
+
+ /* Did we close the root directory? */
+ svn_boolean_t closed_root;
};
+
+#ifdef NOT_USED_YET
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t update_ttable[] = {
+ { INITIAL, S_, "update-report", UPDATE_REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { UPDATE_REPORT, S_, "target-revision", TARGET_REVISION,
+ FALSE, { "rev", NULL }, TRUE },
+
+ { UPDATE_REPORT, S_, "open-directory", OPEN_DIR,
+ FALSE, { "rev", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "open-directory", OPEN_DIR,
+ FALSE, { "rev", "name", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "add-directory", ADD_DIR,
+ FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+
+ { ADD_DIR, S_, "add-directory", ADD_DIR,
+ FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "open-file", OPEN_FILE,
+ FALSE, { "rev", "name", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "add-file", ADD_FILE,
+ FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+
+ { ADD_DIR, S_, "add-file", ADD_FILE,
+ FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "delete-entry", OPEN_FILE,
+ FALSE, { "?rev", "name", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "absent-directory", ABSENT_DIR,
+ FALSE, { "name", NULL }, TRUE },
+
+ { ADD_DIR, S_, "absent-directory", ABSENT_DIR,
+ FALSE, { "name", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "absent-file", ABSENT_FILE,
+ FALSE, { "name", NULL }, TRUE },
+
+ { ADD_DIR, S_, "absent-file", ABSENT_FILE,
+ FALSE, { "name", NULL }, TRUE },
+
+ { 0 }
+};
+
+
+
+/* Conforms to svn_ra_serf__xml_opened_t */
+static svn_error_t *
+update_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)
+{
+ report_context_t *ctx = baton;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+update_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)
+{
+ report_context_t *ctx = baton;
+
+ if (leaving_state == TARGET_REVISION)
+ {
+ const char *rev = svn_hash_gets(attrs, "rev");
+
+ SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton,
+ SVN_STR_TO_REV(rev),
+ ctx->sess->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_ra_serf__xml_cdata_t */
+static svn_error_t *
+update_cdata(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int current_state,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = baton;
+
+ return SVN_NO_ERROR;
+}
+
+#endif /* NOT_USED_YET */
+
+
+/* Returns best connection for fetching files/properties. */
+static svn_ra_serf__connection_t *
+get_best_connection(report_context_t *ctx)
+{
+ svn_ra_serf__connection_t *conn;
+ int first_conn = 1;
+
+ /* Skip the first connection if the REPORT response hasn't been completely
+ received yet or if we're being told to limit our connections to
+ 2 (because this could be an attempt to ensure that we do all our
+ auxiliary GETs/PROPFINDs on a single connection).
+
+ ### FIXME: This latter requirement (max_connections > 2) is
+ ### really just a hack to work around the fact that some update
+ ### editor implementations (such as svnrdump's dump editor)
+ ### simply can't handle the way ra_serf violates the editor v1
+ ### drive ordering requirements.
+ ###
+ ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116.
+ */
+ if (ctx->report_received && (ctx->sess->max_connections > 2))
+ first_conn = 0;
+
+ /* Currently, we just cycle connections. In the future we could
+ store the number of pending requests on each connection, or
+ perform other heuristics, to achieve better connection usage.
+ (As an optimization, if there's only one available auxiliary
+ connection to use, don't bother doing all the cur_conn math --
+ just return that one connection.) */
+ if (ctx->sess->num_conns - first_conn == 1)
+ {
+ conn = ctx->sess->conns[first_conn];
+ }
+ else
+ {
+ conn = ctx->sess->conns[ctx->sess->cur_conn];
+ ctx->sess->cur_conn++;
+ if (ctx->sess->cur_conn >= ctx->sess->num_conns)
+ ctx->sess->cur_conn = first_conn;
+ }
+ return conn;
+}
+
/** Report state management helper **/
@@ -397,6 +572,7 @@ push_state(svn_ra_serf__xml_parser_t *parser,
new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info));
new_info->pool = svn_pool_create(info_parent_pool);
new_info->lock_token = NULL;
+ new_info->prop_value = svn_stringbuf_create_empty(new_info->pool);
new_info->dir = apr_pcalloc(new_info->pool, sizeof(*new_info->dir));
new_info->dir->pool = new_info->pool;
@@ -406,9 +582,6 @@ push_state(svn_ra_serf__xml_parser_t *parser,
new_info->props = new_info->dir->props;
new_info->dir->removed_props = apr_hash_make(new_info->pool);
- /* Point to the update_editor */
- new_info->dir->update_editor = ctx->update_editor;
- new_info->dir->update_baton = ctx->update_baton;
new_info->dir->report_context = ctx;
if (info)
@@ -441,6 +614,7 @@ push_state(svn_ra_serf__xml_parser_t *parser,
new_info->file_baton = NULL;
new_info->lock_token = NULL;
new_info->fetch_file = FALSE;
+ new_info->prop_value = svn_stringbuf_create_empty(new_info->pool);
/* Point at our parent's directory state. */
new_info->dir = info->dir;
@@ -465,13 +639,9 @@ set_file_props(void *baton,
apr_pool_t *scratch_pool)
{
report_info_t *info = baton;
- const svn_delta_editor_t *editor = info->dir->update_editor;
+ const svn_delta_editor_t *editor = info->dir->report_context->update_editor;
const char *prop_name;
- if (strcmp(name, "md5-checksum") == 0
- && strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
- info->final_checksum = apr_pstrdup(info->pool, val->data);
-
prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
if (prop_name != NULL)
return svn_error_trace(editor->change_file_prop(info->file_baton,
@@ -490,7 +660,7 @@ set_dir_props(void *baton,
apr_pool_t *scratch_pool)
{
report_dir_t *dir = baton;
- const svn_delta_editor_t *editor = dir->update_editor;
+ const svn_delta_editor_t *editor = dir->report_context->update_editor;
const char *prop_name;
prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
@@ -511,7 +681,7 @@ remove_file_props(void *baton,
apr_pool_t *scratch_pool)
{
report_info_t *info = baton;
- const svn_delta_editor_t *editor = info->dir->update_editor;
+ const svn_delta_editor_t *editor = info->dir->report_context->update_editor;
const char *prop_name;
prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
@@ -532,7 +702,7 @@ remove_dir_props(void *baton,
apr_pool_t *scratch_pool)
{
report_dir_t *dir = baton;
- const svn_delta_editor_t *editor = dir->update_editor;
+ const svn_delta_editor_t *editor = dir->report_context->update_editor;
const char *prop_name;
prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
@@ -548,8 +718,10 @@ remove_dir_props(void *baton,
/** Helpers to open and close directories */
static svn_error_t*
-open_dir(report_dir_t *dir)
+ensure_dir_opened(report_dir_t *dir)
{
+ report_context_t *ctx = dir->report_context;
+
/* if we're already open, return now */
if (dir->dir_baton)
{
@@ -560,28 +732,28 @@ open_dir(report_dir_t *dir)
{
dir->dir_baton_pool = svn_pool_create(dir->pool);
- if (dir->report_context->destination &&
- dir->report_context->sess->wc_callbacks->invalidate_wc_props)
+ if (ctx->destination
+ && ctx->sess->wc_callbacks->invalidate_wc_props)
{
- SVN_ERR(dir->report_context->sess->wc_callbacks->invalidate_wc_props(
- dir->report_context->sess->wc_callback_baton,
- dir->report_context->update_target,
+ SVN_ERR(ctx->sess->wc_callbacks->invalidate_wc_props(
+ ctx->sess->wc_callback_baton,
+ ctx->update_target,
SVN_RA_SERF__WC_CHECKED_IN_URL, dir->pool));
}
- SVN_ERR(dir->update_editor->open_root(dir->update_baton, dir->base_rev,
+ SVN_ERR(ctx->update_editor->open_root(ctx->update_baton, dir->base_rev,
dir->dir_baton_pool,
&dir->dir_baton));
}
else
{
- SVN_ERR(open_dir(dir->parent_dir));
+ SVN_ERR(ensure_dir_opened(dir->parent_dir));
dir->dir_baton_pool = svn_pool_create(dir->parent_dir->dir_baton_pool);
if (SVN_IS_VALID_REVNUM(dir->base_rev))
{
- SVN_ERR(dir->update_editor->open_directory(dir->name,
+ SVN_ERR(ctx->update_editor->open_directory(dir->name,
dir->parent_dir->dir_baton,
dir->base_rev,
dir->dir_baton_pool,
@@ -589,7 +761,7 @@ open_dir(report_dir_t *dir)
}
else
{
- SVN_ERR(dir->update_editor->add_directory(dir->name,
+ SVN_ERR(ctx->update_editor->add_directory(dir->name,
dir->parent_dir->dir_baton,
NULL, SVN_INVALID_REVNUM,
dir->dir_baton_pool,
@@ -623,12 +795,13 @@ close_dir(report_dir_t *dir)
if (dir->fetch_props)
{
SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->url,
- dir->target_rev,
+ dir->report_context->target_rev,
set_dir_props, dir,
scratch_pool));
}
- SVN_ERR(dir->update_editor->close_directory(dir->dir_baton, scratch_pool));
+ SVN_ERR(dir->report_context->update_editor->close_directory(
+ dir->dir_baton, scratch_pool));
/* remove us from our parent's children list */
if (dir->parent_dir)
@@ -670,7 +843,7 @@ static svn_error_t *close_all_dirs(report_dir_t *dir)
SVN_ERR_ASSERT(! dir->ref_count);
- SVN_ERR(open_dir(dir));
+ SVN_ERR(ensure_dir_opened(dir));
return close_dir(dir);
}
@@ -692,7 +865,7 @@ check_lock(report_info_t *info)
const char *lock_val;
lock_val = svn_ra_serf__get_ver_prop(info->props, info->url,
- info->target_rev,
+ info->dir->report_context->target_rev,
"DAV:", "lockdiscovery");
if (lock_val)
@@ -731,7 +904,7 @@ headers_fetch(serf_bucket_t *headers,
serf_bucket_headers_setn(headers, "Accept-Encoding",
"svndiff1;q=0.9,svndiff;q=0.8");
}
- else if (fetch_ctx->conn->using_compression)
+ else if (fetch_ctx->sess->using_compression)
{
serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
}
@@ -760,7 +933,7 @@ cancel_fetch(serf_request_t *request,
*/
if (fetch_ctx->read_headers)
{
- if (fetch_ctx->aborted_read == FALSE && fetch_ctx->read_size)
+ if (!fetch_ctx->aborted_read && fetch_ctx->read_size)
{
fetch_ctx->aborted_read = TRUE;
fetch_ctx->aborted_read_size = fetch_ctx->read_size;
@@ -799,6 +972,107 @@ error_fetch(serf_request_t *request,
return err;
}
+/* Wield the editor referenced by INFO to open (or add) the file
+ file also associated with INFO, setting properties on the file and
+ calling the editor's apply_textdelta() function on it if necessary
+ (or if FORCE_APPLY_TEXTDELTA is set).
+
+ Callers will probably want to also see the function that serves
+ the opposite purpose of this one, close_updated_file(). */
+static svn_error_t *
+open_updated_file(report_info_t *info,
+ svn_boolean_t force_apply_textdelta,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = info->dir->report_context;
+ const svn_delta_editor_t *update_editor = ctx->update_editor;
+
+ /* Ensure our parent is open. */
+ SVN_ERR(ensure_dir_opened(info->dir));
+ info->editor_pool = svn_pool_create(info->dir->dir_baton_pool);
+
+ /* Expand our full name now if we haven't done so yet. */
+ if (!info->name)
+ {
+ info->name = svn_relpath_join(info->dir->name, info->base_name,
+ info->editor_pool);
+ }
+
+ /* Open (or add) the file. */
+ if (SVN_IS_VALID_REVNUM(info->base_rev))
+ {
+ SVN_ERR(update_editor->open_file(info->name,
+ info->dir->dir_baton,
+ info->base_rev,
+ info->editor_pool,
+ &info->file_baton));
+ }
+ else
+ {
+ SVN_ERR(update_editor->add_file(info->name,
+ info->dir->dir_baton,
+ info->copyfrom_path,
+ info->copyfrom_rev,
+ info->editor_pool,
+ &info->file_baton));
+ }
+
+ /* Check for lock information. */
+ if (info->lock_token)
+ check_lock(info);
+
+ /* Get (maybe) a textdelta window handler for transmitting file
+ content changes. */
+ if (info->fetch_file || force_apply_textdelta)
+ {
+ SVN_ERR(update_editor->apply_textdelta(info->file_baton,
+ info->base_checksum,
+ info->editor_pool,
+ &info->textdelta,
+ &info->textdelta_baton));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Close the file associated with INFO->file_baton, and cleanup other
+ bits of that structure managed by open_updated_file(). */
+static svn_error_t *
+close_updated_file(report_info_t *info,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = info->dir->report_context;
+
+ /* Set all of the properties we received */
+ SVN_ERR(svn_ra_serf__walk_all_props(info->props,
+ info->base_name,
+ info->base_rev,
+ set_file_props, info,
+ scratch_pool));
+ SVN_ERR(svn_ra_serf__walk_all_props(info->dir->removed_props,
+ info->base_name,
+ info->base_rev,
+ remove_file_props, info,
+ scratch_pool));
+ if (info->fetch_props)
+ {
+ SVN_ERR(svn_ra_serf__walk_all_props(info->props,
+ info->url,
+ ctx->target_rev,
+ set_file_props, info,
+ scratch_pool));
+ }
+
+ /* Close the file via the editor. */
+ SVN_ERR(info->dir->report_context->update_editor->close_file(
+ info->file_baton, info->final_checksum, scratch_pool));
+
+ /* We're done with our editor pool. */
+ svn_pool_destroy(info->editor_pool);
+
+ return SVN_NO_ERROR;
+}
+
/* Implements svn_ra_serf__response_handler_t */
static svn_error_t *
handle_fetch(serf_request_t *request,
@@ -811,9 +1085,11 @@ handle_fetch(serf_request_t *request,
apr_status_t status;
report_fetch_t *fetch_ctx = handler_baton;
svn_error_t *err;
- serf_status_line sl;
- if (fetch_ctx->read_headers == FALSE)
+ /* ### new field. make sure we didn't miss some initialization. */
+ SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
+
+ if (!fetch_ctx->read_headers)
{
serf_bucket_t *hdrs;
const char *val;
@@ -823,61 +1099,23 @@ handle_fetch(serf_request_t *request,
val = serf_bucket_headers_get(hdrs, "Content-Type");
info = fetch_ctx->info;
- err = open_dir(info->dir);
- if (err)
- {
- return error_fetch(request, fetch_ctx, err);
- }
-
- info->editor_pool = svn_pool_create(info->dir->dir_baton_pool);
-
- /* Expand our full name now if we haven't done so yet. */
- if (!info->name)
- {
- info->name = svn_relpath_join(info->dir->name, info->base_name,
- info->editor_pool);
- }
-
- if (SVN_IS_VALID_REVNUM(info->base_rev))
- {
- err = info->dir->update_editor->open_file(info->name,
- info->dir->dir_baton,
- info->base_rev,
- info->editor_pool,
- &info->file_baton);
- }
- else
- {
- err = info->dir->update_editor->add_file(info->name,
- info->dir->dir_baton,
- info->copyfrom_path,
- info->copyfrom_rev,
- info->editor_pool,
- &info->file_baton);
- }
-
- if (err)
- {
- return error_fetch(request, fetch_ctx, err);
- }
-
- err = info->dir->update_editor->apply_textdelta(info->file_baton,
- info->base_checksum,
- info->editor_pool,
- &info->textdelta,
- &info->textdelta_baton);
-
- if (err)
- {
- return error_fetch(request, fetch_ctx, err);
- }
-
- if (val && svn_cstring_casecmp(val, "application/vnd.svn-svndiff") == 0)
+ if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0)
{
fetch_ctx->delta_stream =
svn_txdelta_parse_svndiff(info->textdelta,
info->textdelta_baton,
TRUE, info->editor_pool);
+
+ /* Validate the delta base claimed by the server matches
+ what we asked for! */
+ val = serf_bucket_headers_get(hdrs, SVN_DAV_DELTA_BASE_HEADER);
+ if (val && (strcmp(val, info->delta_base) != 0))
+ {
+ err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("GET request returned unexpected "
+ "delta base: %s"), val);
+ return error_fetch(request, fetch_ctx, err);
+ }
}
else
{
@@ -889,16 +1127,12 @@ handle_fetch(serf_request_t *request,
/* If the error code wasn't 200, something went wrong. Don't use the returned
data as its probably an error message. Just bail out instead. */
- status = serf_bucket_response_status(response, &sl);
- if (SERF_BUCKET_READ_ERROR(status))
- {
- return svn_error_wrap_apr(status, NULL);
- }
- if (sl.code != 200)
+ if (fetch_ctx->handler->sline.code != 200)
{
err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
_("GET request failed: %d %s"),
- sl.code, sl.reason);
+ fetch_ctx->handler->sline.code,
+ fetch_ctx->handler->sline.reason);
return error_fetch(request, fetch_ctx, err);
}
@@ -911,7 +1145,7 @@ handle_fetch(serf_request_t *request,
status = serf_bucket_read(response, 8000, &data, &len);
if (SERF_BUCKET_READ_ERROR(status))
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
fetch_ctx->read_size += len;
@@ -931,7 +1165,7 @@ handle_fetch(serf_request_t *request,
/* Skip on to the next iteration of this loop. */
if (APR_STATUS_IS_EAGAIN(status))
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
continue;
}
@@ -981,54 +1215,20 @@ handle_fetch(serf_request_t *request,
{
report_info_t *info = fetch_ctx->info;
- /* ### this doesn't feel quite right. but it gets tossed at the
- ### end of this block, so it will work for now. */
- apr_pool_t *scratch_pool = info->editor_pool;
-
if (fetch_ctx->delta_stream)
err = svn_error_trace(svn_stream_close(fetch_ctx->delta_stream));
else
err = svn_error_trace(info->textdelta(NULL,
info->textdelta_baton));
-
if (err)
{
return error_fetch(request, fetch_ctx, err);
}
- if (info->lock_token)
- check_lock(info);
-
- /* set all of the properties we received */
- err = svn_ra_serf__walk_all_props(info->props,
- info->base_name,
- info->base_rev,
- set_file_props, info,
- scratch_pool);
-
- if (!err)
- err = svn_ra_serf__walk_all_props(info->dir->removed_props,
- info->base_name,
- info->base_rev,
- remove_file_props, info,
- scratch_pool);
- if (!err && info->fetch_props)
- {
- err = svn_ra_serf__walk_all_props(info->props,
- info->url,
- info->target_rev,
- set_file_props, info,
- scratch_pool);
- }
-
- if (!err)
- err = info->dir->update_editor->close_file(info->file_baton,
- info->final_checksum,
- scratch_pool);
-
+ err = close_updated_file(info, info->pool);
if (err)
{
- return error_fetch(request, fetch_ctx, err);
+ return svn_error_trace(error_fetch(request, fetch_ctx, err));
}
fetch_ctx->done = TRUE;
@@ -1037,16 +1237,15 @@ handle_fetch(serf_request_t *request,
fetch_ctx->done_item.next = *fetch_ctx->done_list;
*fetch_ctx->done_list = &fetch_ctx->done_item;
- /* We're done with our pools. */
- svn_pool_destroy(info->editor_pool);
+ /* We're done with our pool. */
svn_pool_destroy(info->pool);
if (status)
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
if (APR_STATUS_IS_EAGAIN(status))
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
}
/* not reached */
@@ -1060,26 +1259,18 @@ handle_stream(serf_request_t *request,
apr_pool_t *pool)
{
report_fetch_t *fetch_ctx = handler_baton;
- serf_status_line sl;
- const char *location;
svn_error_t *err;
apr_status_t status;
- status = serf_bucket_response_status(response, &sl);
- if (SERF_BUCKET_READ_ERROR(status))
- {
- return svn_error_wrap_apr(status, NULL);
- }
+ /* ### new field. make sure we didn't miss some initialization. */
+ SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
- /* Woo-hoo. Nothing here to see. */
- location = svn_ra_serf__response_get_location(response, pool);
-
- err = svn_ra_serf__error_on_status(sl.code,
+ err = svn_ra_serf__error_on_status(fetch_ctx->handler->sline,
fetch_ctx->info->name,
- location);
+ fetch_ctx->handler->location);
if (err)
{
- fetch_ctx->done = TRUE;
+ fetch_ctx->handler->done = TRUE;
err = svn_error_compose_create(
err,
@@ -1096,7 +1287,7 @@ handle_stream(serf_request_t *request,
status = serf_bucket_read(response, 8000, &data, &len);
if (SERF_BUCKET_READ_ERROR(status))
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
fetch_ctx->read_size += len;
@@ -1115,7 +1306,7 @@ handle_stream(serf_request_t *request,
/* Skip on to the next iteration of this loop. */
if (APR_STATUS_IS_EAGAIN(status))
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
continue;
}
@@ -1145,88 +1336,123 @@ handle_stream(serf_request_t *request,
if (status)
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
}
/* not reached */
}
+/* Close the directory represented by DIR -- and any suitable parents
+ thereof -- if we are able to do so. This is the case whenever:
+
+ - there are no remaining open items within the directory, and
+ - the directory's XML close tag has been processed (so we know
+ there are no more children to worry about in the future), and
+ - either:
+ - we aren't fetching properties for this directory, or
+ - we've already finished fetching those properties.
+*/
static svn_error_t *
-handle_propchange_only(report_info_t *info,
- apr_pool_t *scratch_pool)
+maybe_close_dir_chain(report_dir_t *dir)
{
- /* Ensure our parent is open. */
- SVN_ERR(open_dir(info->dir));
+ report_dir_t *cur_dir = dir;
- info->editor_pool = svn_pool_create(info->dir->dir_baton_pool);
+ SVN_ERR(ensure_dir_opened(cur_dir));
- /* Expand our full name now if we haven't done so yet. */
- if (!info->name)
+ while (cur_dir
+ && !cur_dir->ref_count
+ && cur_dir->tag_closed
+ && (!cur_dir->fetch_props || cur_dir->propfind_handler->done))
{
- info->name = svn_relpath_join(info->dir->name, info->base_name,
- info->editor_pool);
- }
+ report_dir_t *parent = cur_dir->parent_dir;
+ report_context_t *report_context = cur_dir->report_context;
+ svn_boolean_t propfind_in_done_list = FALSE;
+ svn_ra_serf__list_t *done_list;
- if (SVN_IS_VALID_REVNUM(info->base_rev))
- {
- SVN_ERR(info->dir->update_editor->open_file(info->name,
- info->dir->dir_baton,
- info->base_rev,
- info->editor_pool,
- &info->file_baton));
- }
- else
- {
- SVN_ERR(info->dir->update_editor->add_file(info->name,
- info->dir->dir_baton,
- info->copyfrom_path,
- info->copyfrom_rev,
- info->editor_pool,
- &info->file_baton));
- }
+ /* Make sure there are no references to this dir in the
+ active_dir_propfinds list. If there are, don't close the
+ directory -- which would delete the pool from which the
+ relevant active_dir_propfinds list item is allocated -- and
+ of course don't crawl upward to check the parents for
+ a closure opportunity, either. */
+ done_list = report_context->active_dir_propfinds;
+ while (done_list)
+ {
+ if (done_list->data == cur_dir)
+ {
+ propfind_in_done_list = TRUE;
+ break;
+ }
+ done_list = done_list->next;
+ }
+ if (propfind_in_done_list)
+ break;
- if (info->fetch_file)
- {
- SVN_ERR(info->dir->update_editor->apply_textdelta(info->file_baton,
- info->base_checksum,
- info->editor_pool,
- &info->textdelta,
- &info->textdelta_baton));
+ SVN_ERR(close_dir(cur_dir));
+ if (parent)
+ {
+ parent->ref_count--;
+ }
+ else
+ {
+ report_context->closed_root = TRUE;
+ }
+ cur_dir = parent;
}
- if (info->lock_token)
- check_lock(info);
+ return SVN_NO_ERROR;
+}
- /* set all of the properties we received */
- SVN_ERR(svn_ra_serf__walk_all_props(info->props,
- info->base_name, info->base_rev,
- set_file_props, info,
- scratch_pool));
- SVN_ERR(svn_ra_serf__walk_all_props(info->dir->removed_props,
- info->base_name, info->base_rev,
- remove_file_props, info,
- scratch_pool));
- if (info->fetch_props)
- {
- SVN_ERR(svn_ra_serf__walk_all_props(info->props, info->url,
- info->target_rev,
- set_file_props, info,
- scratch_pool));
- }
+/* Open the file associated with INFO for editing, pass along any
+ propchanges we've recorded for it, and then close the file. */
+static svn_error_t *
+handle_propchange_only(report_info_t *info,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(open_updated_file(info, FALSE, scratch_pool));
+ SVN_ERR(close_updated_file(info, scratch_pool));
- SVN_ERR(info->dir->update_editor->close_file(info->file_baton,
- info->final_checksum,
- scratch_pool));
+ /* We're done with our pool. */
+ svn_pool_destroy(info->pool);
- /* We're done with our pools. */
- svn_pool_destroy(info->editor_pool);
+ info->dir->ref_count--;
+
+ /* See if the parent directory of this file (and perhaps even
+ parents of that) can be closed now. */
+ SVN_ERR(maybe_close_dir_chain(info->dir));
+
+ return SVN_NO_ERROR;
+}
+
+/* "Fetch" a file whose contents were made available via the
+ get_wc_contents() callback (as opposed to requiring a GET to the
+ server), and feed the information through the associated update
+ editor. In editor-speak, this will add/open the file, transmit any
+ property changes, handle the contents, and then close the file. */
+static svn_error_t *
+handle_local_content(report_info_t *info,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_txdelta_send_stream(info->cached_contents, info->textdelta,
+ info->textdelta_baton, NULL, scratch_pool));
+ SVN_ERR(svn_stream_close(info->cached_contents));
+ info->cached_contents = NULL;
+ SVN_ERR(close_updated_file(info, scratch_pool));
+
+ /* We're done with our pool. */
svn_pool_destroy(info->pool);
info->dir->ref_count--;
+ /* See if the parent directory of this fetched item (and
+ perhaps even parents of that) can be closed now. */
+ SVN_ERR(maybe_close_dir_chain(info->dir));
+
return SVN_NO_ERROR;
}
+/* --------------------------------------------------------- */
+
static svn_error_t *
fetch_file(report_context_t *ctx, report_info_t *info)
{
@@ -1234,32 +1460,23 @@ fetch_file(report_context_t *ctx, report_info_t *info)
svn_ra_serf__handler_t *handler;
/* What connection should we go on? */
- conn = ctx->sess->conns[ctx->sess->cur_conn];
-
- /* go fetch info->name from DAV:checked-in */
- info->url =
- svn_ra_serf__get_ver_prop(info->props, info->base_name,
- info->base_rev, "DAV:", "checked-in");
-
- if (!info->url)
- {
- return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
- _("The OPTIONS response did not include the "
- "requested checked-in value"));
- }
+ conn = get_best_connection(ctx);
/* If needed, create the PROPFIND to retrieve the file's properties. */
- info->propfind = NULL;
+ info->propfind_handler = NULL;
if (info->fetch_props)
{
- SVN_ERR(svn_ra_serf__deliver_props(&info->propfind, info->props,
+ SVN_ERR(svn_ra_serf__deliver_props(&info->propfind_handler, info->props,
ctx->sess, conn, info->url,
- info->target_rev, "0", all_props,
+ ctx->target_rev, "0", all_props,
&ctx->done_propfinds,
info->dir->pool));
- SVN_ERR_ASSERT(info->propfind);
+ SVN_ERR_ASSERT(info->propfind_handler);
+
+ /* Create a serf request for the PROPFIND. */
+ svn_ra_serf__request_create(info->propfind_handler);
- ctx->active_propfinds++;
+ ctx->num_active_propfinds++;
}
/* If we've been asked to fetch the file or it's an add, do so.
@@ -1267,36 +1484,114 @@ fetch_file(report_context_t *ctx, report_info_t *info)
*/
if (info->fetch_file && ctx->text_deltas)
{
- report_fetch_t *fetch_ctx;
+ svn_stream_t *contents = NULL;
- fetch_ctx = apr_pcalloc(info->dir->pool, sizeof(*fetch_ctx));
- fetch_ctx->info = info;
- fetch_ctx->done_list = &ctx->done_fetches;
- fetch_ctx->sess = ctx->sess;
- fetch_ctx->conn = conn;
+ /* Open the file for editing. */
+ SVN_ERR(open_updated_file(info, FALSE, info->pool));
- handler = apr_pcalloc(info->dir->pool, sizeof(*handler));
+ if (info->textdelta == svn_delta_noop_window_handler)
+ {
+ /* There is nobody looking for an actual stream.
- handler->method = "GET";
- handler->path = fetch_ctx->info->url;
+ Just report an empty stream instead of fetching
+ to be ingored data */
+ info->cached_contents = svn_stream_empty(info->pool);
+ }
+ else if (ctx->sess->wc_callbacks->get_wc_contents
+ && info->final_sha1_checksum)
+ {
+ svn_error_t *err = NULL;
+ svn_checksum_t *checksum = NULL;
- handler->conn = conn;
- handler->session = ctx->sess;
+ /* Parse the optional SHA1 checksum (1.7+) */
+ err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
+ info->final_sha1_checksum,
+ info->pool);
- handler->header_delegate = headers_fetch;
- handler->header_delegate_baton = fetch_ctx;
+ /* Okay so far? Let's try to get a stream on some readily
+ available matching content. */
+ if (!err && checksum)
+ {
+ err = ctx->sess->wc_callbacks->get_wc_contents(
+ ctx->sess->wc_callback_baton, &contents,
+ checksum, info->pool);
- handler->response_handler = handle_fetch;
- handler->response_baton = fetch_ctx;
+ if (! err)
+ info->cached_contents = contents;
+ }
- handler->response_error = cancel_fetch;
- handler->response_error_baton = fetch_ctx;
+ if (err)
+ {
+ /* Meh. Maybe we'll care one day why we're in an
+ errorful state, but this codepath is optional. */
+ svn_error_clear(err);
+ }
+ }
- svn_ra_serf__request_create(handler);
+ /* If the working copy can provide cached contents for this
+ file, we don't have to fetch them from the server. */
+ if (info->cached_contents)
+ {
+ /* If we'll be doing a PROPFIND for this file... */
+ if (info->propfind_handler)
+ {
+ /* ... then we'll just leave ourselves a little "todo"
+ about that fact (and we'll deal with the file content
+ stuff later, after we've handled that PROPFIND
+ response. */
+ svn_ra_serf__list_t *list_item;
+
+ list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
+ list_item->data = info;
+ list_item->next = ctx->file_propchanges_only;
+ ctx->file_propchanges_only = list_item;
+ }
+ else
+ {
+ /* Otherwise, if we've no PROPFIND to do, we might as
+ well take care of those locally accessible file
+ contents now. */
+ SVN_ERR(handle_local_content(info, info->pool));
+ }
+ }
+ else
+ {
+ /* Otherwise, we use a GET request for the file's contents. */
+ report_fetch_t *fetch_ctx;
+
+ fetch_ctx = apr_pcalloc(info->dir->pool, sizeof(*fetch_ctx));
+ fetch_ctx->info = info;
+ fetch_ctx->done_list = &ctx->done_fetches;
+ fetch_ctx->sess = ctx->sess;
+ fetch_ctx->conn = conn;
+
+ handler = apr_pcalloc(info->dir->pool, sizeof(*handler));
- ctx->active_fetches++;
+ handler->handler_pool = info->dir->pool;
+ handler->method = "GET";
+ handler->path = fetch_ctx->info->url;
+
+ handler->conn = conn;
+ handler->session = ctx->sess;
+
+ handler->custom_accept_encoding = TRUE;
+ handler->header_delegate = headers_fetch;
+ handler->header_delegate_baton = fetch_ctx;
+
+ handler->response_handler = handle_fetch;
+ handler->response_baton = fetch_ctx;
+
+ handler->response_error = cancel_fetch;
+ handler->response_error_baton = fetch_ctx;
+
+ fetch_ctx->handler = handler;
+
+ svn_ra_serf__request_create(handler);
+
+ ctx->num_active_fetches++;
+ }
}
- else if (info->propfind)
+ else if (info->propfind_handler)
{
svn_ra_serf__list_t *list_item;
@@ -1307,16 +1602,12 @@ fetch_file(report_context_t *ctx, report_info_t *info)
}
else
{
- /* No propfind or GET request. Just handle the prop changes now.
-
- Note: we'll use INFO->POOL for the scratch_pool here since it will
- be destroyed at the end of handle_propchange_only(). That pool
- would be quite fine, but it is unclear how long INFO->POOL will
- stick around since its lifetime and usage are unclear. */
+ /* No propfind or GET request. Just handle the prop changes now. */
SVN_ERR(handle_propchange_only(info, info->pool));
}
- if (ctx->active_fetches + ctx->active_propfinds > REQUEST_COUNT_TO_PAUSE)
+ if (ctx->num_active_fetches + ctx->num_active_propfinds
+ > REQUEST_COUNT_TO_PAUSE)
ctx->parser_ctx->paused = TRUE;
return SVN_NO_ERROR;
@@ -1327,16 +1618,33 @@ fetch_file(report_context_t *ctx, report_info_t *info)
static svn_error_t *
start_report(svn_ra_serf__xml_parser_t *parser,
- void *userData,
svn_ra_serf__dav_props_t name,
- const char **attrs)
+ const char **attrs,
+ apr_pool_t *scratch_pool)
{
- report_context_t *ctx = userData;
+ report_context_t *ctx = parser->user_data;
report_state_e state;
state = parser->state->current_state;
- if (state == NONE && strcmp(name.name, "target-revision") == 0)
+ if (state == NONE && strcmp(name.name, "update-report") == 0)
+ {
+ const char *val;
+
+ val = svn_xml_get_attr_value("inline-props", attrs);
+ if (val && (strcmp(val, "true") == 0))
+ ctx->add_props_included = TRUE;
+
+ val = svn_xml_get_attr_value("send-all", attrs);
+ if (val && (strcmp(val, "true") == 0))
+ {
+ ctx->send_all_mode = TRUE;
+
+ /* All properties are included in send-all mode. */
+ ctx->add_props_included = TRUE;
+ }
+ }
+ else if (state == NONE && strcmp(name.name, "target-revision") == 0)
{
const char *rev;
@@ -1371,7 +1679,6 @@ start_report(svn_ra_serf__xml_parser_t *parser,
info->base_rev = SVN_STR_TO_REV(rev);
info->dir->base_rev = info->base_rev;
- info->dir->target_rev = ctx->target_rev;
info->fetch_props = TRUE;
info->dir->base_name = "";
@@ -1380,8 +1687,7 @@ start_report(svn_ra_serf__xml_parser_t *parser,
info->base_name = info->dir->base_name;
info->name = info->dir->name;
- info->dir->repos_relpath = apr_hash_get(ctx->switched_paths, "",
- APR_HASH_KEY_STRING);
+ info->dir->repos_relpath = svn_hash_gets(ctx->switched_paths, "");
if (!info->dir->repos_relpath)
SVN_ERR(svn_ra_serf__get_relative_path(&info->dir->repos_relpath,
@@ -1424,7 +1730,6 @@ start_report(svn_ra_serf__xml_parser_t *parser,
info->base_rev = SVN_STR_TO_REV(rev);
dir->base_rev = info->base_rev;
- dir->target_rev = ctx->target_rev;
info->fetch_props = FALSE;
@@ -1436,8 +1741,7 @@ start_report(svn_ra_serf__xml_parser_t *parser,
dir->pool);
info->name = dir->name;
- dir->repos_relpath = apr_hash_get(ctx->switched_paths, dir->name,
- APR_HASH_KEY_STRING);
+ dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->name);
if (!dir->repos_relpath)
dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath,
@@ -1478,8 +1782,11 @@ start_report(svn_ra_serf__xml_parser_t *parser,
/* Mark that we don't have a base. */
info->base_rev = SVN_INVALID_REVNUM;
dir->base_rev = info->base_rev;
- dir->target_rev = ctx->target_rev;
- dir->fetch_props = TRUE;
+
+ /* If the server isn't included properties for added items,
+ we'll need to fetch them ourselves. */
+ if (! ctx->add_props_included)
+ dir->fetch_props = TRUE;
dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath,
dir->base_name, dir->pool);
@@ -1511,7 +1818,6 @@ start_report(svn_ra_serf__xml_parser_t *parser,
info = push_state(parser, ctx, OPEN_FILE);
info->base_rev = SVN_STR_TO_REV(rev);
- info->target_rev = ctx->target_rev;
info->fetch_props = FALSE;
info->base_name = apr_pstrdup(info->pool, file_name);
@@ -1537,9 +1843,16 @@ start_report(svn_ra_serf__xml_parser_t *parser,
info = push_state(parser, ctx, ADD_FILE);
info->base_rev = SVN_INVALID_REVNUM;
- info->target_rev = ctx->target_rev;
- info->fetch_props = TRUE;
- info->fetch_file = TRUE;
+
+ /* If the server isn't in "send-all" mode, we should expect to
+ fetch contents for added files. */
+ if (! ctx->send_all_mode)
+ info->fetch_file = TRUE;
+
+ /* If the server isn't included properties for added items,
+ we'll need to fetch them ourselves. */
+ if (! ctx->add_props_included)
+ info->fetch_props = TRUE;
info->base_name = apr_pstrdup(info->pool, file_name);
info->name = NULL;
@@ -1578,16 +1891,16 @@ start_report(svn_ra_serf__xml_parser_t *parser,
info = parser->state->private;
- SVN_ERR(open_dir(info->dir));
+ SVN_ERR(ensure_dir_opened(info->dir));
tmppool = svn_pool_create(info->dir->dir_baton_pool);
full_path = svn_relpath_join(info->dir->name, file_name, tmppool);
- SVN_ERR(info->dir->update_editor->delete_entry(full_path,
- delete_rev,
- info->dir->dir_baton,
- tmppool));
+ SVN_ERR(ctx->update_editor->delete_entry(full_path,
+ delete_rev,
+ info->dir->dir_baton,
+ tmppool));
svn_pool_destroy(tmppool);
}
@@ -1608,7 +1921,7 @@ start_report(svn_ra_serf__xml_parser_t *parser,
info = parser->state->private;
- SVN_ERR(open_dir(info->dir));
+ SVN_ERR(ensure_dir_opened(info->dir));
SVN_ERR(ctx->update_editor->absent_directory(
svn_relpath_join(info->name, file_name,
@@ -1633,7 +1946,7 @@ start_report(svn_ra_serf__xml_parser_t *parser,
info = parser->state->private;
- SVN_ERR(open_dir(info->dir));
+ SVN_ERR(ensure_dir_opened(info->dir));
SVN_ERR(ctx->update_editor->absent_file(
svn_relpath_join(info->name, file_name,
@@ -1651,8 +1964,7 @@ start_report(svn_ra_serf__xml_parser_t *parser,
info->prop_ns = name.namespace;
info->prop_name = apr_pstrdup(parser->state->pool, name.name);
info->prop_encoding = NULL;
- info->prop_val = NULL;
- info->prop_val_len = 0;
+ svn_stringbuf_setempty(info->prop_value);
}
else if (strcmp(name.name, "set-prop") == 0 ||
strcmp(name.name, "remove-prop") == 0)
@@ -1681,8 +1993,7 @@ start_report(svn_ra_serf__xml_parser_t *parser,
colon - full_prop_name);
info->prop_name = apr_pstrdup(parser->state->pool, colon);
info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
- info->prop_val = NULL;
- info->prop_val_len = 0;
+ svn_stringbuf_setempty(info->prop_value);
}
else if (strcmp(name.name, "prop") == 0)
{
@@ -1713,8 +2024,7 @@ start_report(svn_ra_serf__xml_parser_t *parser,
info->prop_ns = name.namespace;
info->prop_name = apr_pstrdup(parser->state->pool, name.name);
info->prop_encoding = NULL;
- info->prop_val = NULL;
- info->prop_val_len = 0;
+ svn_stringbuf_setempty(info->prop_value);
}
else if (strcmp(name.name, "prop") == 0)
{
@@ -1769,8 +2079,7 @@ start_report(svn_ra_serf__xml_parser_t *parser,
colon - full_prop_name);
info->prop_name = apr_pstrdup(parser->state->pool, colon);
info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
- info->prop_val = NULL;
- info->prop_val_len = 0;
+ svn_stringbuf_setempty(info->prop_value);
}
else if (strcmp(name.name, "txdelta") == 0)
{
@@ -1778,7 +2087,31 @@ start_report(svn_ra_serf__xml_parser_t *parser,
addition to <fetch-file>s and such) when *not* in
"send-all" mode. As a client, we're smart enough to know
that's wrong, so we'll just ignore these tags. */
- ;
+ if (ctx->send_all_mode)
+ {
+ const svn_delta_editor_t *update_editor = ctx->update_editor;
+
+ info = push_state(parser, ctx, TXDELTA);
+
+ if (! info->file_baton)
+ {
+ SVN_ERR(open_updated_file(info, FALSE, info->pool));
+ }
+
+ info->base_checksum = svn_xml_get_attr_value("base-checksum",
+ attrs);
+ SVN_ERR(update_editor->apply_textdelta(info->file_baton,
+ info->base_checksum,
+ info->editor_pool,
+ &info->textdelta,
+ &info->textdelta_baton));
+ info->svndiff_decoder = svn_txdelta_parse_svndiff(
+ info->textdelta,
+ info->textdelta_baton,
+ TRUE, info->pool);
+ info->base64_decoder = svn_base64_decode(info->svndiff_decoder,
+ info->pool);
+ }
}
else
{
@@ -1801,8 +2134,7 @@ start_report(svn_ra_serf__xml_parser_t *parser,
info->prop_ns = name.namespace;
info->prop_name = apr_pstrdup(parser->state->pool, name.name);
info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
- info->prop_val = NULL;
- info->prop_val_len = 0;
+ svn_stringbuf_setempty(info->prop_value);
}
return SVN_NO_ERROR;
@@ -1810,18 +2142,25 @@ start_report(svn_ra_serf__xml_parser_t *parser,
static svn_error_t *
end_report(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name)
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
{
- report_context_t *ctx = userData;
+ report_context_t *ctx = parser->user_data;
report_state_e state;
state = parser->state->current_state;
if (state == NONE)
{
- /* nothing to close yet. */
- return SVN_NO_ERROR;
+ if (strcmp(name.name, "update-report") == 0)
+ {
+ ctx->report_completed = TRUE;
+ }
+ else
+ {
+ /* nothing to close yet. */
+ return SVN_NO_ERROR;
+ }
}
if (((state == OPEN_DIR && (strcmp(name.name, "open-directory") == 0)) ||
@@ -1844,9 +2183,9 @@ end_report(svn_ra_serf__xml_parser_t *parser,
if (!checked_in_url &&
(!SVN_IS_VALID_REVNUM(info->dir->base_rev) || info->dir->fetch_props))
{
- return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
- _("The OPTIONS response did not include the "
- "requested checked-in value"));
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("The REPORT or PROPFIND response did not "
+ "include the requested checked-in value"));
}
info->dir->url = checked_in_url;
@@ -1854,32 +2193,45 @@ end_report(svn_ra_serf__xml_parser_t *parser,
/* At this point, we should have the checked-in href.
* If needed, create the PROPFIND to retrieve the dir's properties.
*/
- if (!SVN_IS_VALID_REVNUM(info->dir->base_rev) || info->dir->fetch_props)
+ if (info->dir->fetch_props)
{
- /* Unconditionally set fetch_props now. */
- info->dir->fetch_props = TRUE;
+ svn_ra_serf__list_t *list_item;
- SVN_ERR(svn_ra_serf__deliver_props(&info->dir->propfind,
+ SVN_ERR(svn_ra_serf__deliver_props(&info->dir->propfind_handler,
info->dir->props, ctx->sess,
- ctx->sess->conns[ctx->sess->cur_conn],
+ get_best_connection(ctx),
info->dir->url,
- info->dir->target_rev, "0",
+ ctx->target_rev, "0",
all_props,
- &ctx->done_propfinds,
+ &ctx->done_dir_propfinds,
info->dir->pool));
- SVN_ERR_ASSERT(info->dir->propfind);
+ SVN_ERR_ASSERT(info->dir->propfind_handler);
+
+ /* Create a serf request for the PROPFIND. */
+ svn_ra_serf__request_create(info->dir->propfind_handler);
+
+ ctx->num_active_propfinds++;
- ctx->active_propfinds++;
+ list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
+ list_item->data = info->dir;
+ list_item->next = ctx->active_dir_propfinds;
+ ctx->active_dir_propfinds = list_item;
- if (ctx->active_fetches + ctx->active_propfinds
+ if (ctx->num_active_fetches + ctx->num_active_propfinds
> REQUEST_COUNT_TO_PAUSE)
ctx->parser_ctx->paused = TRUE;
}
else
{
- info->dir->propfind = NULL;
+ info->dir->propfind_handler = NULL;
}
+ /* See if this directory (and perhaps even parents of that) can
+ be closed now. This is likely to be the case only if we
+ didn't need to contact the server for supplemental
+ information required to handle any of this directory's
+ children. */
+ SVN_ERR(maybe_close_dir_chain(info->dir));
svn_ra_serf__xml_pop_state(parser);
}
else if (state == OPEN_FILE && strcmp(name.name, "open-file") == 0)
@@ -1893,10 +2245,7 @@ end_report(svn_ra_serf__xml_parser_t *parser,
info->pool);
}
- info->lock_token = apr_hash_get(ctx->lock_path_tokens, info->name,
- APR_HASH_KEY_STRING);
-
- if (info->lock_token && info->fetch_props == FALSE)
+ if (info->lock_token && !info->fetch_props)
info->fetch_props = TRUE;
/* If possible, we'd like to fetch only a delta against a
@@ -1913,8 +2262,7 @@ end_report(svn_ra_serf__xml_parser_t *parser,
/* If this file is switched vs the editor root we should provide
its real url instead of the one calculated from the session root.
*/
- repos_relpath = apr_hash_get(ctx->switched_paths, info->name,
- APR_HASH_KEY_STRING);
+ repos_relpath = svn_hash_gets(ctx->switched_paths, info->name);
if (!repos_relpath)
{
@@ -1925,8 +2273,7 @@ end_report(svn_ra_serf__xml_parser_t *parser,
SVN_ERR_ASSERT(*svn_relpath_dirname(info->name, info->pool)
== '\0');
- repos_relpath = apr_hash_get(ctx->switched_paths, "",
- APR_HASH_KEY_STRING);
+ repos_relpath = svn_hash_gets(ctx->switched_paths, "");
}
else
repos_relpath = svn_relpath_join(info->dir->repos_relpath,
@@ -1953,18 +2300,94 @@ end_report(svn_ra_serf__xml_parser_t *parser,
info->delta_base = value ? value->data : NULL;
}
- SVN_ERR(fetch_file(ctx, info));
+ /* go fetch info->name from DAV:checked-in */
+ info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name,
+ info->base_rev, "DAV:", "checked-in");
+ if (!info->url)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("The REPORT or PROPFIND response did not "
+ "include the requested checked-in value"));
+ }
+
+ /* If the server is in "send-all" mode, we might have opened the
+ file when we started seeing content for it. If we didn't get
+ any content for it, we still need to open the file. But in
+ any case, we can then immediately close it. */
+ if (ctx->send_all_mode)
+ {
+ if (! info->file_baton)
+ {
+ SVN_ERR(open_updated_file(info, FALSE, info->pool));
+ }
+ SVN_ERR(close_updated_file(info, info->pool));
+ info->dir->ref_count--;
+ }
+ /* Otherwise, if the server is *not* in "send-all" mode, we
+ should be at a point where we can queue up any auxiliary
+ content-fetching requests. */
+ else
+ {
+ SVN_ERR(fetch_file(ctx, info));
+ }
+
svn_ra_serf__xml_pop_state(parser);
}
else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0)
{
- /* We should have everything we need to fetch the file. */
- SVN_ERR(fetch_file(ctx, parser->state->private));
+ report_info_t *info = parser->state->private;
+
+ /* go fetch info->name from DAV:checked-in */
+ info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name,
+ info->base_rev, "DAV:", "checked-in");
+ if (!info->url)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("The REPORT or PROPFIND response did not "
+ "include the requested checked-in value"));
+ }
+
+ /* If the server is in "send-all" mode, we might have opened the
+ file when we started seeing content for it. If we didn't get
+ any content for it, we still need to open the file. But in
+ any case, we can then immediately close it. */
+ if (ctx->send_all_mode)
+ {
+ if (! info->file_baton)
+ {
+ SVN_ERR(open_updated_file(info, FALSE, info->pool));
+ }
+ SVN_ERR(close_updated_file(info, info->pool));
+ info->dir->ref_count--;
+ }
+ /* Otherwise, if the server is *not* in "send-all" mode, we
+ should be at a point where we can queue up any auxiliary
+ content-fetching requests. */
+ else
+ {
+ SVN_ERR(fetch_file(ctx, info));
+ }
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == TXDELTA && strcmp(name.name, "txdelta") == 0)
+ {
+ report_info_t *info = parser->state->private;
+
+ /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
+ <fetch-file>s and such) when *not* in "send-all" mode. As a
+ client, we're smart enough to know that's wrong, so when not
+ in "receiving-all" mode, we'll ignore these tags. */
+ if (ctx->send_all_mode)
+ {
+ SVN_ERR(svn_stream_close(info->base64_decoder));
+ }
+
svn_ra_serf__xml_pop_state(parser);
}
else if (state == PROP)
{
- /* We need to move the prop_ns, prop_name, and prop_val into the
+ /* We need to move the prop_ns, prop_name, and prop_value into the
* same lifetime as the dir->pool.
*/
svn_ra_serf__ns_t *ns, *ns_name_match;
@@ -1972,8 +2395,7 @@ end_report(svn_ra_serf__xml_parser_t *parser,
report_info_t *info;
report_dir_t *dir;
apr_hash_t *props;
- const char *set_val;
- svn_string_t *set_val_str;
+ const svn_string_t *set_val_str;
apr_pool_t *pool;
info = parser->state->private;
@@ -2023,24 +2445,21 @@ end_report(svn_ra_serf__xml_parser_t *parser,
{
props = dir->removed_props;
pool = dir->pool;
- info->prop_val = "";
- info->prop_val_len = 1;
+ svn_stringbuf_setempty(info->prop_value);
}
if (info->prop_encoding)
{
if (strcmp(info->prop_encoding, "base64") == 0)
{
- svn_string_t encoded;
- const svn_string_t *decoded;
+ svn_string_t tmp;
- encoded.data = info->prop_val;
- encoded.len = info->prop_val_len;
+ /* Don't use morph_info_string cuz we need prop_value to
+ remain usable. */
+ tmp.data = info->prop_value->data;
+ tmp.len = info->prop_value->len;
- decoded = svn_base64_decode_string(&encoded, parser->state->pool);
-
- info->prop_val = decoded->data;
- info->prop_val_len = decoded->len;
+ set_val_str = svn_base64_decode_string(&tmp, pool);
}
else
{
@@ -2049,14 +2468,21 @@ end_report(svn_ra_serf__xml_parser_t *parser,
_("Got unrecognized encoding '%s'"),
info->prop_encoding);
}
-
}
-
- set_val = apr_pmemdup(pool, info->prop_val, info->prop_val_len);
- set_val_str = svn_string_ncreate(set_val, info->prop_val_len, pool);
+ else
+ {
+ set_val_str = svn_string_create_from_buf(info->prop_value, pool);
+ }
svn_ra_serf__set_ver_prop(props, info->base_name, info->base_rev,
ns->namespace, ns->url, set_val_str, pool);
+
+ /* Advance handling: if we spotted the md5-checksum property on
+ the wire, remember it's value. */
+ if (strcmp(ns->url, "md5-checksum") == 0
+ && strcmp(ns->namespace, SVN_DAV_PROP_NS_DAV) == 0)
+ info->final_checksum = apr_pstrdup(info->pool, set_val_str->data);
+
svn_ra_serf__xml_pop_state(parser);
}
else if (state == IGNORE_PROP_NAME || state == NEED_PROP_NAME)
@@ -2069,11 +2495,11 @@ end_report(svn_ra_serf__xml_parser_t *parser,
static svn_error_t *
cdata_report(svn_ra_serf__xml_parser_t *parser,
- void *userData,
const char *data,
- apr_size_t len)
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
{
- report_context_t *ctx = userData;
+ report_context_t *ctx = parser->user_data;
UNUSED_CTX(ctx);
@@ -2081,8 +2507,28 @@ cdata_report(svn_ra_serf__xml_parser_t *parser,
{
report_info_t *info = parser->state->private;
- svn_ra_serf__expand_string(&info->prop_val, &info->prop_val_len,
- data, len, parser->state->pool);
+ svn_stringbuf_appendbytes(info->prop_value, data, len);
+ }
+ else if (parser->state->current_state == TXDELTA)
+ {
+ /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
+ <fetch-file>s and such) when *not* in "send-all" mode. As a
+ client, we're smart enough to know that's wrong, so when not
+ in "receiving-all" mode, we'll ignore these tags. */
+ if (ctx->send_all_mode)
+ {
+ apr_size_t nlen = len;
+ report_info_t *info = parser->state->private;
+
+ SVN_ERR(svn_stream_write(info->base64_decoder, data, &nlen));
+ if (nlen != len)
+ {
+ /* Short write without associated error? "Can't happen." */
+ return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
+ _("Error writing to '%s': unexpected EOF"),
+ info->name);
+ }
+ }
}
return SVN_NO_ERROR;
@@ -2127,14 +2573,6 @@ set_path(void *report_baton,
SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
NULL, pool));
- if (lock_token)
- {
- apr_hash_set(report->lock_path_tokens,
- apr_pstrdup(report->pool, path),
- APR_HASH_KEY_STRING,
- apr_pstrdup(report->pool, lock_token));
- }
-
return SVN_NO_ERROR;
}
@@ -2204,51 +2642,38 @@ link_path(void *report_baton,
/* Store the switch roots to allow generating repos_relpaths from just
the working copy paths. (Needed for HTTPv2) */
path = apr_pstrdup(report->pool, path);
- apr_hash_set(report->switched_paths, path, APR_HASH_KEY_STRING,
- apr_pstrdup(report->pool, link+1));
+ svn_hash_sets(report->switched_paths,
+ path, apr_pstrdup(report->pool, link + 1));
if (!*path)
report->root_is_switched = TRUE;
- if (lock_token)
- {
- apr_hash_set(report->lock_path_tokens, path, APR_HASH_KEY_STRING,
- apr_pstrdup(report->pool, lock_token));
- }
-
return APR_SUCCESS;
}
-/** Max. number of connctions we'll open to the server. */
-#define MAX_NR_OF_CONNS 4
/** Minimum nr. of outstanding requests needed before a new connection is
* opened. */
#define REQS_PER_CONN 8
/** This function creates a new connection for this serf session, but only
- * if the number of ACTIVE_REQS > REQS_PER_CONN or if there currently is
+ * if the number of NUM_ACTIVE_REQS > REQS_PER_CONN or if there currently is
* only one main connection open.
*/
static svn_error_t *
-open_connection_if_needed(svn_ra_serf__session_t *sess, int active_reqs)
+open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs)
{
/* For each REQS_PER_CONN outstanding requests open a new connection, with
* a minimum of 1 extra connection. */
if (sess->num_conns == 1 ||
- ((active_reqs / REQS_PER_CONN) > sess->num_conns))
+ ((num_active_reqs / REQS_PER_CONN) > sess->num_conns))
{
int cur = sess->num_conns;
apr_status_t status;
- sess->conns[cur] = apr_palloc(sess->pool, sizeof(*sess->conns[cur]));
+ sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur]));
sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool,
NULL, NULL);
- sess->conns[cur]->hostname = sess->conns[0]->hostname;
- sess->conns[cur]->using_ssl = sess->conns[0]->using_ssl;
- sess->conns[cur]->using_compression = sess->conns[0]->using_compression;
- sess->conns[cur]->useragent = sess->conns[0]->useragent;
sess->conns[cur]->last_status_code = -1;
- sess->conns[cur]->ssl_context = NULL;
sess->conns[cur]->session = sess;
status = serf_connection_create2(&sess->conns[cur]->conn,
sess->context,
@@ -2259,7 +2684,7 @@ open_connection_if_needed(svn_ra_serf__session_t *sess, int active_reqs)
sess->conns[cur],
sess->pool);
if (status)
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
sess->num_conns++;
}
@@ -2285,16 +2710,23 @@ create_update_report_body(serf_bucket_t **body_bkt,
return SVN_NO_ERROR;
}
+/* Serf callback to setup update request headers. */
static svn_error_t *
-headers_report(serf_bucket_t *headers,
- void *baton,
- apr_pool_t *pool)
+setup_update_report_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
{
report_context_t *report = baton;
- if (report->conn->using_compression)
+ if (report->sess->using_compression)
{
- serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
+ serf_bucket_headers_setn(headers, "Accept-Encoding",
+ "gzip,svndiff1;q=0.9,svndiff;q=0.8");
+ }
+ else
+ {
+ serf_bucket_headers_setn(headers, "Accept-Encoding",
+ "svndiff1;q=0.9,svndiff;q=0.8");
}
return SVN_NO_ERROR;
@@ -2309,11 +2741,10 @@ finish_report(void *report_baton,
svn_ra_serf__handler_t *handler;
svn_ra_serf__xml_parser_t *parser_ctx;
const char *report_target;
- svn_boolean_t closed_root;
- int status_code;
svn_stringbuf_t *buf = NULL;
apr_pool_t *iterpool = svn_pool_create(pool);
svn_error_t *err;
+ apr_interval_time_t waittime_left = sess->timeout;
svn_xml_make_close_tag(&buf, iterpool, "S:update-report");
SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
@@ -2340,27 +2771,27 @@ finish_report(void *report_baton,
handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
handler->method = "REPORT";
handler->path = report->path;
handler->body_delegate = create_update_report_body;
handler->body_delegate_baton = report;
handler->body_type = "text/xml";
+ handler->custom_accept_encoding = TRUE;
+ handler->header_delegate = setup_update_report_headers;
+ handler->header_delegate_baton = report;
handler->conn = sess->conns[0];
handler->session = sess;
- handler->header_delegate = headers_report;
- handler->header_delegate_baton = report;
parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
parser_ctx->pool = pool;
+ parser_ctx->response_type = "update-report";
parser_ctx->user_data = report;
parser_ctx->start = start_report;
parser_ctx->end = end_report;
parser_ctx->cdata = cdata_report;
parser_ctx->done = &report->done;
- /* While we provide a location here to store the status code, we don't
- do anything with it. The error in parser_ctx->error is sufficient. */
- parser_ctx->status_code = &status_code;
handler->response_handler = svn_ra_serf__handle_xml_parser;
handler->response_baton = parser_ctx;
@@ -2373,14 +2804,15 @@ finish_report(void *report_baton,
SVN_ERR(open_connection_if_needed(sess, 0));
sess->cur_conn = 1;
- closed_root = FALSE;
/* Note that we may have no active GET or PROPFIND requests, yet the
processing has not been completed. This could be from a delay on the
network or because we've spooled the entire response into our "pending"
content of the XML parser. The DONE flag will get set when all the
XML content has been received *and* parsed. */
- while (!report->done || report->active_fetches || report->active_propfinds)
+ while (!report->done
+ || report->num_active_fetches
+ || report->num_active_propfinds)
{
apr_pool_t *iterpool_inner;
svn_ra_serf__list_t *done_list;
@@ -2397,43 +2829,75 @@ finish_report(void *report_baton,
and what items are allocated within. */
iterpool_inner = svn_pool_create(iterpool);
- status = serf_context_run(sess->context, sess->timeout, iterpool_inner);
+ status = serf_context_run(sess->context,
+ SVN_RA_SERF__CONTEXT_RUN_DURATION,
+ iterpool_inner);
err = sess->pending_error;
sess->pending_error = SVN_NO_ERROR;
+ if (!err && handler->done && handler->server_error)
+ {
+ err = handler->server_error->error;
+ }
+
+ /* If the context duration timeout is up, we'll subtract that
+ duration from the total time alloted for such things. If
+ there's no time left, we fail with a message indicating that
+ the connection timed out. */
if (APR_STATUS_IS_TIMEUP(status))
{
svn_error_clear(err);
- return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT,
- NULL,
- _("Connection timed out"));
+ err = SVN_NO_ERROR;
+ status = 0;
+
+ if (sess->timeout)
+ {
+ if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
+ {
+ waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
+ _("Connection timed out"));
+ }
+ }
+ }
+ else
+ {
+ waittime_left = sess->timeout;
}
+ if (status && handler->sline.code != 200)
+ {
+ return svn_error_trace(
+ svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline,
+ handler->path,
+ handler->location),
+ err));
+ }
SVN_ERR(err);
if (status)
{
- return svn_error_wrap_apr(status, _("Error retrieving REPORT (%d)"),
- status);
+ return svn_ra_serf__wrap_err(status, _("Error retrieving REPORT"));
}
/* Open extra connections if we have enough requests to send. */
- if (sess->num_conns < MAX_NR_OF_CONNS)
- SVN_ERR(open_connection_if_needed(sess, report->active_fetches +
- report->active_propfinds));
+ if (sess->num_conns < sess->max_connections)
+ SVN_ERR(open_connection_if_needed(sess, report->num_active_fetches +
+ report->num_active_propfinds));
- /* Switch our connection. */
- if (!report->done)
- if (++sess->cur_conn == sess->num_conns)
- sess->cur_conn = 1;
-
- /* prune our propfind list if they are done. */
+ /* Prune completed file PROPFINDs. */
done_list = report->done_propfinds;
while (done_list)
{
+ svn_ra_serf__list_t *next_done = done_list->next;
+
svn_pool_clear(iterpool_inner);
- report->active_propfinds--;
+ report->num_active_propfinds--;
/* If we have some files that we won't be fetching the content
* for, ensure that we update the file with any altered props.
@@ -2449,7 +2913,7 @@ finish_report(void *report_baton,
{
report_info_t *item = cur->data;
- if (item->propfind == done_list->data)
+ if (item->propfind_handler == done_list->data)
{
break;
}
@@ -2463,7 +2927,7 @@ finish_report(void *report_baton,
*/
if (cur)
{
- SVN_ERR(handle_propchange_only(cur->data, iterpool_inner));
+ report_info_t *info = cur->data;
if (!prev)
{
@@ -2473,61 +2937,123 @@ finish_report(void *report_baton,
{
prev->next = cur->next;
}
+
+ /* If we've got cached file content for this file,
+ take care of the locally collected properties and
+ file content at once. Otherwise, just deal with
+ the collected properties.
+
+ NOTE: These functions below could delete
+ info->dir->pool (via maybe_close_dir_chain()),
+ from which is allocated the list item in
+ report->file_propchanges_only.
+ */
+ if (info->cached_contents)
+ {
+ SVN_ERR(handle_local_content(info, iterpool_inner));
+ }
+ else
+ {
+ SVN_ERR(handle_propchange_only(info, iterpool_inner));
+ }
}
}
- done_list = done_list->next;
+ done_list = next_done;
}
report->done_propfinds = NULL;
- /* prune our fetches list if they are done. */
+ /* Prune completed fetches from our list. */
done_list = report->done_fetches;
while (done_list)
{
report_fetch_t *done_fetch = done_list->data;
+ svn_ra_serf__list_t *next_done = done_list->next;
report_dir_t *cur_dir;
- /* decrease our parent's directory refcount. */
+ /* Decrease the refcount in the parent directory of the file
+ whose fetch has completed. */
cur_dir = done_fetch->info->dir;
cur_dir->ref_count--;
/* Decrement our active fetch count. */
- report->active_fetches--;
+ report->num_active_fetches--;
- done_list = done_list->next;
+ /* See if the parent directory of this fetched item (and
+ perhaps even parents of that) can be closed now.
- /* If we have a valid directory and
- * we have no open items in this dir and
- * we've closed the directory tag (no more children can be added)
- * and either:
- * we know we won't be fetching props or
- * we've already completed the propfind
- * then, we know it's time for us to close this directory.
- */
- while (cur_dir && !cur_dir->ref_count && cur_dir->tag_closed &&
- (!cur_dir->fetch_props ||
- svn_ra_serf__propfind_is_done(cur_dir->propfind)))
+ NOTE: This could delete cur_dir->pool, from which is
+ allocated the list item in report->done_fetches.
+ */
+ SVN_ERR(maybe_close_dir_chain(cur_dir));
+
+ done_list = next_done;
+ }
+ report->done_fetches = NULL;
+
+ /* Prune completed directory PROPFINDs. */
+ done_list = report->done_dir_propfinds;
+ while (done_list)
+ {
+ svn_ra_serf__list_t *next_done = done_list->next;
+
+ report->num_active_propfinds--;
+
+ if (report->active_dir_propfinds)
{
- report_dir_t *parent = cur_dir->parent_dir;
+ svn_ra_serf__list_t *cur, *prev;
- SVN_ERR(close_dir(cur_dir));
- if (parent)
+ prev = NULL;
+ cur = report->active_dir_propfinds;
+
+ while (cur)
{
- parent->ref_count--;
+ report_dir_t *item = cur->data;
+
+ if (item->propfind_handler == done_list->data)
+ {
+ break;
+ }
+
+ prev = cur;
+ cur = cur->next;
}
- else
+ SVN_ERR_ASSERT(cur); /* we expect to find a matching propfind! */
+
+ /* If we found a match, set the new props and remove this
+ * propchange from our list.
+ */
+ if (cur)
{
- closed_root = TRUE;
+ report_dir_t *cur_dir = cur->data;
+
+ if (!prev)
+ {
+ report->active_dir_propfinds = cur->next;
+ }
+ else
+ {
+ prev->next = cur->next;
+ }
+
+ /* See if this directory (and perhaps even parents of that)
+ can be closed now.
+
+ NOTE: This could delete cur_dir->pool, from which is
+ allocated the list item in report->active_dir_propfinds.
+ */
+ SVN_ERR(maybe_close_dir_chain(cur_dir));
}
- cur_dir = parent;
}
+
+ done_list = next_done;
}
- report->done_fetches = NULL;
+ report->done_dir_propfinds = NULL;
/* If the parser is paused, and the number of active requests has
dropped far enough, then resume parsing. */
if (parser_ctx->paused
- && (report->active_fetches + report->active_propfinds
+ && (report->num_active_fetches + report->num_active_propfinds
< REQUEST_COUNT_TO_RESUME))
parser_ctx->paused = FALSE;
@@ -2535,23 +3061,37 @@ finish_report(void *report_baton,
present (we can't know for sure because of the private structure),
then go process the pending content. */
if (!parser_ctx->paused && parser_ctx->pending != NULL)
- SVN_ERR(svn_ra_serf__process_pending(parser_ctx, iterpool_inner));
+ SVN_ERR(svn_ra_serf__process_pending(parser_ctx,
+ &report->report_received,
+ iterpool_inner));
/* Debugging purposes only! */
for (i = 0; i < sess->num_conns; i++)
{
- serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
+ serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
}
}
- /* Ensure that we opened and closed our root dir and that we closed
- * all of our children. */
- if (closed_root == FALSE && report->root_dir != NULL)
+ /* If we got a complete report, close the edit. Otherwise, abort it. */
+ if (report->report_completed)
{
- SVN_ERR(close_all_dirs(report->root_dir));
+ /* Ensure that we opened and closed our root dir and that we closed
+ * all of our children. */
+ if (!report->closed_root && report->root_dir != NULL)
+ {
+ SVN_ERR(close_all_dirs(report->root_dir));
+ }
+
+ err = report->update_editor->close_edit(report->update_baton, iterpool);
}
+ else
+ {
+ /* Tell the editor that something failed */
+ err = report->update_editor->abort_edit(report->update_baton, iterpool);
- err = report->update_editor->close_edit(report->update_baton, iterpool);
+ err = svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
+ _("Missing update-report close tag"));
+ }
svn_pool_destroy(iterpool);
return svn_error_trace(err);
@@ -2596,10 +3136,9 @@ make_update_reporter(svn_ra_session_t *ra_session,
svn_boolean_t send_copyfrom_args,
const svn_delta_editor_t *update_editor,
void *update_baton,
- apr_pool_t *result_pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- /* ### would be nice to get a SCRATCH_POOL passed to us. */
- apr_pool_t *scratch_pool = svn_pool_create(result_pool);
report_context_t *report;
const svn_delta_editor_t *filter_editor;
void *filter_baton;
@@ -2607,6 +3146,7 @@ make_update_reporter(svn_ra_session_t *ra_session,
svn_boolean_t server_supports_depth;
svn_ra_serf__session_t *sess = ra_session->priv;
svn_stringbuf_t *buf = NULL;
+ svn_boolean_t use_bulk_updates;
SVN_ERR(svn_ra_serf__has_capability(ra_session, &server_supports_depth,
SVN_RA_CAPABILITY_DEPTH, scratch_pool));
@@ -2635,7 +3175,6 @@ make_update_reporter(svn_ra_session_t *ra_session,
report->ignore_ancestry = ignore_ancestry;
report->send_copyfrom_args = send_copyfrom_args;
report->text_deltas = text_deltas;
- report->lock_path_tokens = apr_hash_make(report->pool);
report->switched_paths = apr_hash_make(report->pool);
report->source = src_path;
@@ -2653,9 +3192,74 @@ make_update_reporter(svn_ra_session_t *ra_session,
svn_io_file_del_on_pool_cleanup,
report->pool, scratch_pool));
- svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal, "S:update-report",
- "xmlns:S", SVN_XML_NAMESPACE,
- NULL);
+ if (sess->bulk_updates == svn_tristate_true)
+ {
+ /* User would like to use bulk updates. */
+ use_bulk_updates = TRUE;
+ }
+ else if (sess->bulk_updates == svn_tristate_false)
+ {
+ /* User doesn't want bulk updates. */
+ use_bulk_updates = FALSE;
+ }
+ else
+ {
+ /* User doesn't have any preferences on bulk updates. Decide on server
+ preferences and capabilities. */
+ if (sess->server_allows_bulk)
+ {
+ if (apr_strnatcasecmp(sess->server_allows_bulk, "off") == 0)
+ {
+ /* Server doesn't want bulk updates */
+ use_bulk_updates = FALSE;
+ }
+ else if (apr_strnatcasecmp(sess->server_allows_bulk, "prefer") == 0)
+ {
+ /* Server prefers bulk updates, and we respect that */
+ use_bulk_updates = TRUE;
+ }
+ else
+ {
+ /* Server allows bulk updates, but doesn't dictate its use. Do
+ whatever is the default. */
+ use_bulk_updates = FALSE;
+ }
+ }
+ else
+ {
+ /* Pre-1.8 server didn't send the bulk_updates header. Check if server
+ supports inlining properties in update editor report. */
+ if (sess->supports_inline_props)
+ {
+ /* Inline props supported: do not use bulk updates. */
+ use_bulk_updates = FALSE;
+ }
+ else
+ {
+ /* Inline props are not supported: use bulk updates to avoid
+ * PROPFINDs for every added node. */
+ use_bulk_updates = TRUE;
+ }
+ }
+ }
+
+ if (use_bulk_updates)
+ {
+ svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
+ "S:update-report",
+ "xmlns:S", SVN_XML_NAMESPACE, "send-all", "true",
+ NULL);
+ }
+ else
+ {
+ svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
+ "S:update-report",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+ /* Subversion 1.8+ servers can be told to send properties for newly
+ added items inline even when doing a skelta response. */
+ make_simple_xml_tag(&buf, "S:include-props", "yes", scratch_pool);
+ }
make_simple_xml_tag(&buf, "S:src-path", report->source, scratch_pool);
@@ -2694,12 +3298,26 @@ make_update_reporter(svn_ra_session_t *ra_session,
make_simple_xml_tag(&buf, "S:recursive", "no", scratch_pool);
}
+ /* When in 'send-all' mode, mod_dav_svn will assume that it should
+ calculate and transmit real text-deltas (instead of empty windows
+ that merely indicate "text is changed") unless it finds this
+ element.
+
+ NOTE: Do NOT count on servers actually obeying this, as some exist
+ which obey send-all, but do not check for this directive at all!
+
+ NOTE 2: When not in 'send-all' mode, mod_dav_svn can still be configured to
+ override our request and send text-deltas. */
+ if (! text_deltas)
+ {
+ make_simple_xml_tag(&buf, "S:text-deltas", "no", scratch_pool);
+ }
+
make_simple_xml_tag(&buf, "S:depth", svn_depth_to_word(depth), scratch_pool);
SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
NULL, scratch_pool));
- svn_pool_destroy(scratch_pool);
return SVN_NO_ERROR;
}
@@ -2711,17 +3329,22 @@ svn_ra_serf__do_update(svn_ra_session_t *ra_session,
const char *update_target,
svn_depth_t depth,
svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
const svn_delta_editor_t *update_editor,
void *update_baton,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
svn_ra_serf__session_t *session = ra_session->priv;
- return make_update_reporter(ra_session, reporter, report_baton,
- revision_to_update_to,
- session->session_url.path, NULL, update_target,
- depth, FALSE, TRUE, send_copyfrom_args,
- update_editor, update_baton, pool);
+ SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
+ revision_to_update_to,
+ session->session_url.path, NULL, update_target,
+ depth, ignore_ancestry, TRUE /* text_deltas */,
+ send_copyfrom_args,
+ update_editor, update_baton,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
}
svn_error_t *
@@ -2739,12 +3362,16 @@ svn_ra_serf__do_diff(svn_ra_session_t *ra_session,
apr_pool_t *pool)
{
svn_ra_serf__session_t *session = ra_session->priv;
-
- return make_update_reporter(ra_session, reporter, report_baton,
- revision,
- session->session_url.path, versus_url, diff_target,
- depth, ignore_ancestry, text_deltas, FALSE,
- diff_editor, diff_baton, pool);
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
+ revision,
+ session->session_url.path, versus_url, diff_target,
+ depth, ignore_ancestry, text_deltas, FALSE,
+ diff_editor, diff_baton,
+ pool, scratch_pool));
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
}
svn_error_t *
@@ -2759,12 +3386,16 @@ svn_ra_serf__do_status(svn_ra_session_t *ra_session,
apr_pool_t *pool)
{
svn_ra_serf__session_t *session = ra_session->priv;
-
- return make_update_reporter(ra_session, reporter, report_baton,
- revision,
- session->session_url.path, NULL, status_target,
- depth, FALSE, FALSE, FALSE,
- status_editor, status_baton, pool);
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
+ revision,
+ session->session_url.path, NULL, status_target,
+ depth, FALSE, FALSE, FALSE,
+ status_editor, status_baton,
+ pool, scratch_pool));
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
}
svn_error_t *
@@ -2775,9 +3406,12 @@ svn_ra_serf__do_switch(svn_ra_session_t *ra_session,
const char *switch_target,
svn_depth_t depth,
const char *switch_url,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
const svn_delta_editor_t *switch_editor,
void *switch_baton,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
svn_ra_serf__session_t *session = ra_session->priv;
@@ -2785,8 +3419,82 @@ svn_ra_serf__do_switch(svn_ra_session_t *ra_session,
revision_to_switch_to,
session->session_url.path,
switch_url, switch_target,
- depth, TRUE, TRUE, FALSE /* TODO(sussman) */,
- switch_editor, switch_baton, pool);
+ depth,
+ ignore_ancestry,
+ TRUE /* text_deltas */,
+ send_copyfrom_args,
+ switch_editor, switch_baton,
+ result_pool, scratch_pool);
+}
+
+/* Helper svn_ra_serf__get_file(). Attempts to fetch file contents
+ * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is
+ * present in PROPS.
+ *
+ * Sets *FOUND_P to TRUE if file contents was successfuly fetched.
+ *
+ * Performs all temporary allocations in POOL.
+ */
+static svn_error_t *
+try_get_wc_contents(svn_boolean_t *found_p,
+ svn_ra_serf__session_t *session,
+ apr_hash_t *props,
+ svn_stream_t *dst_stream,
+ apr_pool_t *pool)
+{
+ apr_hash_t *svn_props;
+ const char *sha1_checksum_prop;
+ svn_checksum_t *checksum;
+ svn_stream_t *wc_stream;
+ svn_error_t *err;
+
+ /* No contents found by default. */
+ *found_p = FALSE;
+
+ if (!session->wc_callbacks->get_wc_contents)
+ {
+ /* No callback, nothing to do. */
+ return SVN_NO_ERROR;
+ }
+
+
+ svn_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
+ if (!svn_props)
+ {
+ /* No properties -- therefore no checksum property -- in response. */
+ return SVN_NO_ERROR;
+ }
+
+ sha1_checksum_prop = svn_prop_get_value(svn_props, "sha1-checksum");
+ if (sha1_checksum_prop == NULL)
+ {
+ /* No checksum property in response. */
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
+ sha1_checksum_prop, pool));
+
+ err = session->wc_callbacks->get_wc_contents(
+ session->wc_callback_baton, &wc_stream, checksum, pool);
+
+ if (err)
+ {
+ svn_error_clear(err);
+
+ /* Ignore errors for now. */
+ return SVN_NO_ERROR;
+ }
+
+ if (wc_stream)
+ {
+ SVN_ERR(svn_stream_copy3(wc_stream,
+ svn_stream_disown(dst_stream, pool),
+ NULL, NULL, pool));
+ *found_p = TRUE;
+ }
+
+ return SVN_NO_ERROR;
}
svn_error_t *
@@ -2803,6 +3511,7 @@ svn_ra_serf__get_file(svn_ra_session_t *ra_session,
const char *fetch_url;
apr_hash_t *fetch_props;
svn_node_kind_t res_kind;
+ const svn_ra_serf__dav_props_t *which_props;
/* What connection should we go on? */
conn = session->conns[session->cur_conn];
@@ -2818,23 +3527,35 @@ svn_ra_serf__get_file(svn_ra_session_t *ra_session,
*/
if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
{
- const char *baseline_url, *rel_path;
-
- SVN_ERR(svn_ra_serf__get_baseline_info(&baseline_url, &rel_path,
- session, conn, fetch_url,
- revision, fetched_rev, pool));
- fetch_url = svn_path_url_add_component2(baseline_url, rel_path, pool);
+ SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev,
+ session, conn,
+ fetch_url, revision,
+ pool, pool));
revision = SVN_INVALID_REVNUM;
}
+ /* REVISION is always SVN_INVALID_REVNUM */
+ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
+
+ if (props)
+ {
+ which_props = all_props;
+ }
+ else if (stream && session->wc_callbacks->get_wc_contents)
+ {
+ which_props = type_and_checksum_props;
+ }
+ else
+ {
+ which_props = check_path_props;
+ }
- SVN_ERR(svn_ra_serf__retrieve_props(&fetch_props, session, conn, fetch_url,
- revision, "0",
- props ? all_props : check_path_props,
- pool, pool));
+ SVN_ERR(svn_ra_serf__fetch_node_props(&fetch_props, conn, fetch_url,
+ SVN_INVALID_REVNUM,
+ which_props,
+ pool, pool));
- /* Verify that resource type is not colelction. */
- SVN_ERR(svn_ra_serf__get_resource_type(&res_kind, fetch_props, fetch_url,
- revision));
+ /* Verify that resource type is not collection. */
+ SVN_ERR(svn_ra_serf__get_resource_type(&res_kind, fetch_props));
if (res_kind != svn_node_file)
{
return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
@@ -2844,41 +3565,55 @@ svn_ra_serf__get_file(svn_ra_session_t *ra_session,
/* TODO Filter out all of our props into a usable format. */
if (props)
{
- SVN_ERR(svn_ra_serf__flatten_props(props, fetch_props, fetch_url,
- revision, pool, pool));
+ /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props()
+ ### put them into POOL, so we're okay. */
+ SVN_ERR(svn_ra_serf__flatten_props(props, fetch_props,
+ pool, pool));
}
if (stream)
{
- report_fetch_t *stream_ctx;
- svn_ra_serf__handler_t *handler;
+ svn_boolean_t found;
+ SVN_ERR(try_get_wc_contents(&found, session, fetch_props, stream, pool));
+
+ /* No contents found in the WC, let's fetch from server. */
+ if (!found)
+ {
+ report_fetch_t *stream_ctx;
+ svn_ra_serf__handler_t *handler;
+
+ /* Create the fetch context. */
+ stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx));
+ stream_ctx->target_stream = stream;
+ stream_ctx->sess = session;
+ stream_ctx->conn = conn;
+ stream_ctx->info = apr_pcalloc(pool, sizeof(*stream_ctx->info));
+ stream_ctx->info->name = fetch_url;
- /* Create the fetch context. */
- stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx));
- stream_ctx->target_stream = stream;
- stream_ctx->sess = session;
- stream_ctx->conn = conn;
- stream_ctx->info = apr_pcalloc(pool, sizeof(*stream_ctx->info));
- stream_ctx->info->name = fetch_url;
+ handler = apr_pcalloc(pool, sizeof(*handler));
- handler = apr_pcalloc(pool, sizeof(*handler));
- handler->method = "GET";
- handler->path = fetch_url;
- handler->conn = conn;
- handler->session = session;
+ handler->handler_pool = pool;
+ handler->method = "GET";
+ handler->path = fetch_url;
+ handler->conn = conn;
+ handler->session = session;
- handler->header_delegate = headers_fetch;
- handler->header_delegate_baton = stream_ctx;
+ handler->custom_accept_encoding = TRUE;
+ handler->header_delegate = headers_fetch;
+ handler->header_delegate_baton = stream_ctx;
- handler->response_handler = handle_stream;
- handler->response_baton = stream_ctx;
+ handler->response_handler = handle_stream;
+ handler->response_baton = stream_ctx;
- handler->response_error = cancel_fetch;
- handler->response_error_baton = stream_ctx;
+ handler->response_error = cancel_fetch;
+ handler->response_error_baton = stream_ctx;
- svn_ra_serf__request_create(handler);
+ stream_ctx->handler = handler;
- SVN_ERR(svn_ra_serf__context_run_wait(&stream_ctx->done, session, pool));
+ svn_ra_serf__request_create(handler);
+
+ SVN_ERR(svn_ra_serf__context_run_wait(&stream_ctx->done, session, pool));
+ }
}
return SVN_NO_ERROR;
diff --git a/subversion/libsvn_ra_serf/util.c b/subversion/libsvn_ra_serf/util.c
index 180ff7a..8f6c1bb 100644
--- a/subversion/libsvn_ra_serf/util.c
+++ b/subversion/libsvn_ra_serf/util.c
@@ -28,18 +28,27 @@
#define APR_WANT_STRFUNC
#include <apr.h>
#include <apr_want.h>
-#include <apr_fnmatch.h>
#include <serf.h>
#include <serf_bucket_types.h>
+#include <expat.h>
+
+#include "svn_hash.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_private_config.h"
#include "svn_string.h"
#include "svn_xml.h"
+#include "svn_props.h"
+#include "svn_dirent_uri.h"
+
+#include "../libsvn_ra/ra_loader.h"
#include "private/svn_dep_compat.h"
#include "private/svn_fspath.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_auth_private.h"
+#include "private/svn_cert.h"
#include "ra_serf.h"
@@ -52,61 +61,54 @@
#define XML_STATUS_ERROR 0
#endif
+#ifndef XML_VERSION_AT_LEAST
+#define XML_VERSION_AT_LEAST(major,minor,patch) \
+(((major) < XML_MAJOR_VERSION) \
+ || ((major) == XML_MAJOR_VERSION && (minor) < XML_MINOR_VERSION) \
+ || ((major) == XML_MAJOR_VERSION && (minor) == XML_MINOR_VERSION && \
+ (patch) <= XML_MICRO_VERSION))
+#endif /* APR_VERSION_AT_LEAST */
-#define PARSE_CHUNK_SIZE 8000
+#if XML_VERSION_AT_LEAST(1, 95, 8)
+#define EXPAT_HAS_STOPPARSER
+#endif
-/* As chunks of content arrive from the server, and we need to hold them
- in memory (because the XML parser is paused), they are copied into
- these buffers. The buffers are arranged into a linked list. */
-struct pending_buffer_t {
- apr_size_t size;
- char data[PARSE_CHUNK_SIZE];
+/* Read/write chunks of this size into the spillbuf. */
+#define PARSE_CHUNK_SIZE 8000
- struct pending_buffer_t *next;
-};
+/* We will store one megabyte in memory, before switching to store content
+ into a temporary file. */
+#define SPILL_SIZE 1000000
/* This structure records pending data for the parser in memory blocks,
and possibly into a temporary file if "too much" content arrives. */
struct svn_ra_serf__pending_t {
- /* The amount of content in memory. */
- apr_size_t memory_size;
-
- /* HEAD points to the first block of the linked list of buffers.
- TAIL points to the last block, for quickly appending more blocks
- to the overall list. */
- struct pending_buffer_t *head;
- struct pending_buffer_t *tail;
-
- /* Available blocks for storing pending data. These were allocated
- previously, then the data consumed and returned to this list. */
- struct pending_buffer_t *avail;
-
- /* Once MEMORY_SIZE exceeds SPILL_SIZE, then arriving content will be
- appended to the (temporary) file indicated by SPILL. */
- apr_file_t *spill;
-
- /* As we consume content from SPILL, this value indicates where we
- will begin reading. */
- apr_off_t spill_start;
+ /* The spillbuf where we record the pending data. */
+ svn_spillbuf_t *buf;
/* This flag is set when the network has reached EOF. The PENDING
processing can then properly detect when parsing has completed. */
svn_boolean_t network_eof;
};
-#define HAS_PENDING_DATA(p) ((p) != NULL \
- && ((p)->head != NULL || (p)->spill != NULL))
+#define HAS_PENDING_DATA(p) ((p) != NULL && (p)->buf != NULL \
+ && svn_spillbuf__get_size((p)->buf) != 0)
-/* We will store one megabyte in memory, before switching to store content
- into a temporary file. */
-#define SPILL_SIZE 1000000
-/* See notes/ra-serf-testing.txt for some information on testing this
- new "paused" feature. */
+struct expat_ctx_t {
+ svn_ra_serf__xml_context_t *xmlctx;
+ XML_Parser parser;
+ svn_ra_serf__handler_t *handler;
+
+ svn_error_t *inner_error;
+
+ /* Do not use this pool for allocation. It is merely recorded for running
+ the cleanup handler. */
+ apr_pool_t *cleanup_pool;
+};
-
static const apr_uint32_t serf_failure_map[][2] =
{
{ SERF_SSL_CERT_NOTYETVALID, SVN_AUTH_SSL_NOTYETVALID },
@@ -142,6 +144,23 @@ ssl_convert_serf_failures(int failures)
return svn_failures;
}
+
+static apr_status_t
+save_error(svn_ra_serf__session_t *session,
+ svn_error_t *err)
+{
+ if (err || session->pending_error)
+ {
+ session->pending_error = svn_error_compose_create(
+ session->pending_error,
+ err);
+ return session->pending_error->apr_err;
+ }
+
+ return APR_SUCCESS;
+}
+
+
/* Construct the realmstring, e.g. https://svn.collab.net:443. */
static const char *
construct_realm(svn_ra_serf__session_t *session,
@@ -173,13 +192,65 @@ construct_realm(svn_ra_serf__session_t *session,
static char *
convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool)
{
- return apr_psprintf(pool, "%s, %s, %s, %s, %s (%s)",
- (char*)apr_hash_get(org, "OU", APR_HASH_KEY_STRING),
- (char*)apr_hash_get(org, "O", APR_HASH_KEY_STRING),
- (char*)apr_hash_get(org, "L", APR_HASH_KEY_STRING),
- (char*)apr_hash_get(org, "ST", APR_HASH_KEY_STRING),
- (char*)apr_hash_get(org, "C", APR_HASH_KEY_STRING),
- (char*)apr_hash_get(org, "E", APR_HASH_KEY_STRING));
+ const char *org_unit = svn_hash_gets(org, "OU");
+ const char *org_name = svn_hash_gets(org, "O");
+ const char *locality = svn_hash_gets(org, "L");
+ const char *state = svn_hash_gets(org, "ST");
+ const char *country = svn_hash_gets(org, "C");
+ const char *email = svn_hash_gets(org, "E");
+ svn_stringbuf_t *buf = svn_stringbuf_create_empty(pool);
+
+ if (org_unit)
+ {
+ svn_stringbuf_appendcstr(buf, org_unit);
+ svn_stringbuf_appendcstr(buf, ", ");
+ }
+
+ if (org_name)
+ {
+ svn_stringbuf_appendcstr(buf, org_name);
+ svn_stringbuf_appendcstr(buf, ", ");
+ }
+
+ if (locality)
+ {
+ svn_stringbuf_appendcstr(buf, locality);
+ svn_stringbuf_appendcstr(buf, ", ");
+ }
+
+ if (state)
+ {
+ svn_stringbuf_appendcstr(buf, state);
+ svn_stringbuf_appendcstr(buf, ", ");
+ }
+
+ if (country)
+ {
+ svn_stringbuf_appendcstr(buf, country);
+ svn_stringbuf_appendcstr(buf, ", ");
+ }
+
+ /* Chop ', ' if any. */
+ svn_stringbuf_chop(buf, 2);
+
+ if (email)
+ {
+ svn_stringbuf_appendcstr(buf, "(");
+ svn_stringbuf_appendcstr(buf, email);
+ svn_stringbuf_appendcstr(buf, ")");
+ }
+
+ return buf->data;
+}
+
+static void append_reason(svn_stringbuf_t *errmsg, const char *reason, int *reasons)
+{
+ if (*reasons < 1)
+ svn_stringbuf_appendcstr(errmsg, _(": "));
+ else
+ svn_stringbuf_appendcstr(errmsg, _(", "));
+ svn_stringbuf_appendcstr(errmsg, reason);
+ (*reasons)++;
}
/* This function is called on receiving a ssl certificate of a server when
@@ -199,65 +270,149 @@ ssl_server_cert(void *baton, int failures,
svn_auth_iterstate_t *state;
const char *realmstring;
apr_uint32_t svn_failures;
- apr_hash_t *issuer, *subject, *serf_cert;
- apr_array_header_t *san;
+ apr_hash_t *issuer;
+ apr_hash_t *subject = NULL;
+ apr_hash_t *serf_cert = NULL;
void *creds;
- int found_matching_hostname = 0;
- /* Implicitly approve any non-server certs. */
- if (serf_ssl_cert_depth(cert) > 0)
+ svn_failures = (ssl_convert_serf_failures(failures)
+ | conn->server_cert_failures);
+
+ if (serf_ssl_cert_depth(cert) == 0)
{
- if (failures)
- conn->server_cert_failures |= ssl_convert_serf_failures(failures);
- return APR_SUCCESS;
+ /* If the depth is 0, the hostname must match the certificate.
+
+ ### This should really be handled by serf, which should pass an error
+ for this case, but that has backwards compatibility issues. */
+ apr_array_header_t *san;
+ svn_boolean_t found_san_entry = FALSE;
+ svn_boolean_t found_matching_hostname = FALSE;
+ svn_string_t *actual_hostname =
+ svn_string_create(conn->session->session_url.hostname, scratch_pool);
+
+ serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
+
+ san = svn_hash_gets(serf_cert, "subjectAltName");
+ /* Try to find matching server name via subjectAltName first... */
+ if (san)
+ {
+ int i;
+ found_san_entry = san->nelts > 0;
+ for (i = 0; i < san->nelts; i++)
+ {
+ const char *s = APR_ARRAY_IDX(san, i, const char*);
+ svn_string_t *cert_hostname = svn_string_create(s, scratch_pool);
+
+ if (svn_cert__match_dns_identity(cert_hostname, actual_hostname))
+ {
+ found_matching_hostname = TRUE;
+ break;
+ }
+ }
+ }
+
+ /* Match server certificate CN with the hostname of the server iff
+ * we didn't find any subjectAltName fields and try to match them.
+ * Per RFC 2818 they are authoritative if present and CommonName
+ * should be ignored. */
+ if (!found_matching_hostname && !found_san_entry)
+ {
+ const char *hostname = NULL;
+
+ subject = serf_ssl_cert_subject(cert, scratch_pool);
+
+ if (subject)
+ hostname = svn_hash_gets(subject, "CN");
+
+ if (hostname)
+ {
+ svn_string_t *cert_hostname = svn_string_create(hostname,
+ scratch_pool);
+
+ if (svn_cert__match_dns_identity(cert_hostname, actual_hostname))
+ {
+ found_matching_hostname = TRUE;
+ }
+ }
+ }
+
+ if (!found_matching_hostname)
+ svn_failures |= SVN_AUTH_SSL_CNMISMATCH;
}
+ if (!svn_failures)
+ return SVN_NO_ERROR;
+
/* Extract the info from the certificate */
- subject = serf_ssl_cert_subject(cert, scratch_pool);
+ if (! subject)
+ subject = serf_ssl_cert_subject(cert, scratch_pool);
issuer = serf_ssl_cert_issuer(cert, scratch_pool);
- serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
+ if (! serf_cert)
+ serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
- cert_info.hostname = apr_hash_get(subject, "CN", APR_HASH_KEY_STRING);
- san = apr_hash_get(serf_cert, "subjectAltName", APR_HASH_KEY_STRING);
- cert_info.fingerprint = apr_hash_get(serf_cert, "sha1", APR_HASH_KEY_STRING);
+ cert_info.hostname = svn_hash_gets(subject, "CN");
+ cert_info.fingerprint = svn_hash_gets(serf_cert, "sha1");
if (! cert_info.fingerprint)
cert_info.fingerprint = apr_pstrdup(scratch_pool, "<unknown>");
- cert_info.valid_from = apr_hash_get(serf_cert, "notBefore",
- APR_HASH_KEY_STRING);
+ cert_info.valid_from = svn_hash_gets(serf_cert, "notBefore");
if (! cert_info.valid_from)
cert_info.valid_from = apr_pstrdup(scratch_pool, "[invalid date]");
- cert_info.valid_until = apr_hash_get(serf_cert, "notAfter",
- APR_HASH_KEY_STRING);
+ cert_info.valid_until = svn_hash_gets(serf_cert, "notAfter");
if (! cert_info.valid_until)
cert_info.valid_until = apr_pstrdup(scratch_pool, "[invalid date]");
cert_info.issuer_dname = convert_organisation_to_str(issuer, scratch_pool);
cert_info.ascii_cert = serf_ssl_cert_export(cert, scratch_pool);
- svn_failures = (ssl_convert_serf_failures(failures)
- | conn->server_cert_failures);
+ /* Handle any non-server certs. */
+ if (serf_ssl_cert_depth(cert) > 0)
+ {
+ svn_error_t *err;
- /* Try to find matching server name via subjectAltName first... */
- if (san) {
- int i;
- for (i = 0; i < san->nelts; i++) {
- char *s = APR_ARRAY_IDX(san, i, char*);
- if (apr_fnmatch(s, conn->hostname,
- APR_FNM_PERIOD) == APR_SUCCESS) {
- found_matching_hostname = 1;
- cert_info.hostname = s;
- break;
- }
- }
- }
+ svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
+ &cert_info);
- /* Match server certificate CN with the hostname of the server */
- if (!found_matching_hostname && cert_info.hostname)
- {
- if (apr_fnmatch(cert_info.hostname, conn->hostname,
- APR_FNM_PERIOD) == APR_FNM_NOMATCH)
+ svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
+ &svn_failures);
+
+ realmstring = apr_psprintf(scratch_pool, "AUTHORITY:%s",
+ cert_info.fingerprint);
+
+ err = svn_auth_first_credentials(&creds, &state,
+ SVN_AUTH_CRED_SSL_SERVER_AUTHORITY,
+ realmstring,
+ conn->session->wc_callbacks->auth_baton,
+ scratch_pool);
+
+ svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
+
+ svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_SSL_SERVER_FAILURES, NULL);
+
+ if (err)
{
- svn_failures |= SVN_AUTH_SSL_CNMISMATCH;
+ if (err->apr_err != SVN_ERR_AUTHN_NO_PROVIDER)
+ return svn_error_trace(err);
+
+ /* No provider registered that handles server authorities */
+ svn_error_clear(err);
+ creds = NULL;
+ }
+
+ if (creds)
+ {
+ server_creds = creds;
+ SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
+
+ svn_failures &= ~server_creds->accepted_failures;
}
+
+ if (svn_failures)
+ conn->server_cert_failures |= svn_failures;
+
+ return APR_SUCCESS;
}
svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
@@ -278,14 +433,56 @@ ssl_server_cert(void *baton, int failures,
if (creds)
{
server_creds = creds;
+ svn_failures &= ~server_creds->accepted_failures;
SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
}
+ while (svn_failures && creds)
+ {
+ SVN_ERR(svn_auth_next_credentials(&creds, state, scratch_pool));
+
+ if (creds)
+ {
+ server_creds = creds;
+ svn_failures &= ~server_creds->accepted_failures;
+ SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
+ }
+ }
+
svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
- if (!server_creds)
- return svn_error_create(SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED, NULL, NULL);
+ /* Are there non accepted failures left? */
+ if (svn_failures)
+ {
+ svn_stringbuf_t *errmsg;
+ int reasons = 0;
+
+ errmsg = svn_stringbuf_create(
+ _("Server SSL certificate verification failed"),
+ scratch_pool);
+
+
+ if (svn_failures & SVN_AUTH_SSL_NOTYETVALID)
+ append_reason(errmsg, _("certificate is not yet valid"), &reasons);
+
+ if (svn_failures & SVN_AUTH_SSL_EXPIRED)
+ append_reason(errmsg, _("certificate has expired"), &reasons);
+
+ if (svn_failures & SVN_AUTH_SSL_CNMISMATCH)
+ append_reason(errmsg,
+ _("certificate issued for a different hostname"),
+ &reasons);
+
+ if (svn_failures & SVN_AUTH_SSL_UNKNOWNCA)
+ append_reason(errmsg, _("issuer is not trusted"), &reasons);
+
+ if (svn_failures & SVN_AUTH_SSL_OTHER)
+ append_reason(errmsg, _("and other reason(s)"), &reasons);
+
+ return svn_error_create(SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED, NULL,
+ errmsg->data);
+ }
return SVN_NO_ERROR;
}
@@ -301,45 +498,36 @@ ssl_server_cert_cb(void *baton, int failures,
svn_error_t *err;
subpool = svn_pool_create(session->pool);
- err = ssl_server_cert(baton, failures, cert, subpool);
-
+ err = svn_error_trace(ssl_server_cert(baton, failures, cert, subpool));
svn_pool_destroy(subpool);
- if (err || session->pending_error)
- {
- session->pending_error = svn_error_compose_create(
- session->pending_error,
- err);
-
- return session->pending_error->apr_err;
- }
-
- return APR_SUCCESS;
-
+ return save_error(session, err);
}
static svn_error_t *
load_authorities(svn_ra_serf__connection_t *conn, const char *authorities,
apr_pool_t *pool)
{
- char *files, *file, *last;
- files = apr_pstrdup(pool, authorities);
+ apr_array_header_t *files = svn_cstring_split(authorities, ";",
+ TRUE /* chop_whitespace */,
+ pool);
+ int i;
- while ((file = apr_strtok(files, ";", &last)) != NULL)
+ for (i = 0; i < files->nelts; ++i)
{
+ const char *file = APR_ARRAY_IDX(files, i, const char *);
serf_ssl_certificate_t *ca_cert;
apr_status_t status = serf_ssl_load_cert_file(&ca_cert, file, pool);
+
if (status == APR_SUCCESS)
status = serf_ssl_trust_cert(conn->ssl_context, ca_cert);
if (status != APR_SUCCESS)
{
- return svn_error_createf
- (SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
_("Invalid config: unable to load certificate file '%s'"),
svn_dirent_local_style(file, pool));
}
- files = NULL;
}
return SVN_NO_ERROR;
@@ -357,7 +545,7 @@ conn_setup(apr_socket_t *sock,
*read_bkt = serf_context_bucket_socket_create(conn->session->context,
sock, conn->bkt_alloc);
- if (conn->using_ssl)
+ if (conn->session->using_ssl)
{
/* input stream */
*read_bkt = serf_bucket_ssl_decrypt_create(*read_bkt, conn->ssl_context,
@@ -366,9 +554,8 @@ conn_setup(apr_socket_t *sock,
{
conn->ssl_context = serf_bucket_ssl_encrypt_context_get(*read_bkt);
-#if SERF_VERSION_AT_LEAST(1,0,0)
- serf_ssl_set_hostname(conn->ssl_context, conn->hostname);
-#endif
+ serf_ssl_set_hostname(conn->ssl_context,
+ conn->session->session_url.hostname);
serf_ssl_client_cert_provider_set(conn->ssl_context,
svn_ra_serf__handle_client_cert,
@@ -417,31 +604,23 @@ svn_ra_serf__conn_setup(apr_socket_t *sock,
{
svn_ra_serf__connection_t *conn = baton;
svn_ra_serf__session_t *session = conn->session;
- apr_status_t status = SVN_NO_ERROR;
-
- svn_error_t *err = conn_setup(sock,
- read_bkt,
- write_bkt,
- baton,
- pool);
-
- if (err || session->pending_error)
- {
- session->pending_error = svn_error_compose_create(
- session->pending_error,
- err);
-
- status = session->pending_error->apr_err;
- }
+ svn_error_t *err;
- return status;
+ err = svn_error_trace(conn_setup(sock,
+ read_bkt,
+ write_bkt,
+ baton,
+ pool));
+ return save_error(session, err);
}
-serf_bucket_t*
-svn_ra_serf__accept_response(serf_request_t *request,
- serf_bucket_t *stream,
- void *acceptor_baton,
- apr_pool_t *pool)
+
+/* Our default serf response acceptor. */
+static serf_bucket_t *
+accept_response(serf_request_t *request,
+ serf_bucket_t *stream,
+ void *acceptor_baton,
+ apr_pool_t *pool)
{
serf_bucket_t *c;
serf_bucket_alloc_t *bkt_alloc;
@@ -452,7 +631,9 @@ svn_ra_serf__accept_response(serf_request_t *request,
return serf_bucket_response_create(c, bkt_alloc);
}
-static serf_bucket_t*
+
+/* Custom response acceptor for HEAD requests. */
+static serf_bucket_t *
accept_head(serf_request_t *request,
serf_bucket_t *stream,
void *acceptor_baton,
@@ -460,8 +641,7 @@ accept_head(serf_request_t *request,
{
serf_bucket_t *response;
- response = svn_ra_serf__accept_response(request, stream, acceptor_baton,
- pool);
+ response = accept_response(request, stream, acceptor_baton, pool);
/* We know we shouldn't get a response body. */
serf_bucket_response_set_head(response);
@@ -470,18 +650,17 @@ accept_head(serf_request_t *request,
}
static svn_error_t *
-connection_closed(serf_connection_t *conn,
- svn_ra_serf__connection_t *sc,
+connection_closed(svn_ra_serf__connection_t *conn,
apr_status_t why,
apr_pool_t *pool)
{
if (why)
{
- SVN_ERR_MALFUNCTION();
+ return svn_error_wrap_apr(why, NULL);
}
- if (sc->using_ssl)
- sc->ssl_context = NULL;
+ if (conn->session->using_ssl)
+ conn->ssl_context = NULL;
return SVN_NO_ERROR;
}
@@ -492,15 +671,12 @@ svn_ra_serf__conn_closed(serf_connection_t *conn,
apr_status_t why,
apr_pool_t *pool)
{
- svn_ra_serf__connection_t *sc = closed_baton;
+ svn_ra_serf__connection_t *ra_conn = closed_baton;
svn_error_t *err;
- err = connection_closed(conn, sc, why, pool);
+ err = svn_error_trace(connection_closed(ra_conn, why, pool));
- if (err)
- sc->session->pending_error = svn_error_compose_create(
- sc->session->pending_error,
- err);
+ (void) save_error(ra_conn->session, err);
}
@@ -551,20 +727,11 @@ apr_status_t svn_ra_serf__handle_client_cert(void *data,
{
svn_ra_serf__connection_t *conn = data;
svn_ra_serf__session_t *session = conn->session;
- svn_error_t *err = handle_client_cert(data,
- cert_path,
- session->pool);
+ svn_error_t *err;
- if (err || session->pending_error)
- {
- session->pending_error = svn_error_compose_create(
- session->pending_error,
- err);
+ err = svn_error_trace(handle_client_cert(data, cert_path, session->pool));
- return session->pending_error->apr_err;
- }
-
- return APR_SUCCESS;
+ return save_error(session, err);
}
/* Implementation for svn_ra_serf__handle_client_cert_pw */
@@ -613,60 +780,115 @@ apr_status_t svn_ra_serf__handle_client_cert_pw(void *data,
{
svn_ra_serf__connection_t *conn = data;
svn_ra_serf__session_t *session = conn->session;
- svn_error_t *err = handle_client_cert_pw(data,
- cert_path,
- password,
- session->pool);
+ svn_error_t *err;
- if (err || session->pending_error)
- {
- session->pending_error = svn_error_compose_create(
- session->pending_error,
- err);
+ err = svn_error_trace(handle_client_cert_pw(data,
+ cert_path,
+ password,
+ session->pool));
- return session->pending_error->apr_err;
- }
-
- return APR_SUCCESS;
+ return save_error(session, err);
}
-svn_error_t *
-svn_ra_serf__setup_serf_req(serf_request_t *request,
- serf_bucket_t **req_bkt,
- serf_bucket_t **ret_hdrs_bkt,
- svn_ra_serf__connection_t *conn,
- const char *method, const char *url,
- serf_bucket_t *body_bkt, const char *content_type)
+/*
+ * Given a REQUEST on connection CONN, construct a request bucket for it,
+ * returning the bucket in *REQ_BKT.
+ *
+ * If HDRS_BKT is not-NULL, it will be set to a headers_bucket that
+ * corresponds to the new request.
+ *
+ * The request will be METHOD at URL.
+ *
+ * If BODY_BKT is not-NULL, it will be sent as the request body.
+ *
+ * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header.
+ *
+ * REQUEST_POOL should live for the duration of the request. Serf will
+ * construct this and provide it to the request_setup callback, so we
+ * should just use that one.
+ */
+static svn_error_t *
+setup_serf_req(serf_request_t *request,
+ serf_bucket_t **req_bkt,
+ serf_bucket_t **hdrs_bkt,
+ svn_ra_serf__session_t *session,
+ const char *method, const char *url,
+ serf_bucket_t *body_bkt, const char *content_type,
+ const char *accept_encoding,
+ apr_pool_t *request_pool,
+ apr_pool_t *scratch_pool)
{
- serf_bucket_t *hdrs_bkt;
+ serf_bucket_alloc_t *allocator = serf_request_get_alloc(request);
+
+ svn_spillbuf_t *buf;
+ svn_boolean_t set_CL = session->http10 || !session->using_chunked_requests;
+
+ if (set_CL && body_bkt != NULL)
+ {
+ /* Ugh. Use HTTP/1.0 to talk to the server because we don't know if
+ it speaks HTTP/1.1 (and thus, chunked requests), or because the
+ server actually responded as only supporting HTTP/1.0.
+
+ We'll take the existing body_bkt, spool it into a spillbuf, and
+ then wrap a bucket around that spillbuf. The spillbuf will give
+ us the Content-Length value. */
+ SVN_ERR(svn_ra_serf__copy_into_spillbuf(&buf, body_bkt,
+ request_pool,
+ scratch_pool));
+ /* Destroy original bucket since it content is already copied
+ to spillbuf. */
+ serf_bucket_destroy(body_bkt);
+
+ body_bkt = svn_ra_serf__create_sb_bucket(buf, allocator,
+ request_pool,
+ scratch_pool);
+ }
/* Create a request bucket. Note that this sucker is kind enough to
add a "Host" header for us. */
- *req_bkt =
- serf_request_bucket_request_create(request, method, url, body_bkt,
- serf_request_get_alloc(request));
+ *req_bkt = serf_request_bucket_request_create(request, method, url,
+ body_bkt, allocator);
+
+ /* Set the Content-Length value. This will also trigger an HTTP/1.0
+ request (rather than the default chunked request). */
+ if (set_CL)
+ {
+ if (body_bkt == NULL)
+ serf_bucket_request_set_CL(*req_bkt, 0);
+ else
+ serf_bucket_request_set_CL(*req_bkt, svn_spillbuf__get_size(buf));
+ }
- hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
- serf_bucket_headers_setn(hdrs_bkt, "User-Agent", conn->useragent);
+ *hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
+
+ /* We use serf_bucket_headers_setn() because the USERAGENT has a
+ lifetime longer than this bucket. Thus, there is no need to copy
+ the header values. */
+ serf_bucket_headers_setn(*hdrs_bkt, "User-Agent", session->useragent);
if (content_type)
{
- serf_bucket_headers_setn(hdrs_bkt, "Content-Type", content_type);
+ serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type);
}
- /* These headers need to be sent with every request; see issue #3255
- ("mod_dav_svn does not pass client capabilities to start-commit
- hooks") for why. */
- serf_bucket_headers_set(hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
- serf_bucket_headers_set(hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
- serf_bucket_headers_set(hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
+ if (session->http10)
+ {
+ serf_bucket_headers_setn(*hdrs_bkt, "Connection", "keep-alive");
+ }
- if (ret_hdrs_bkt)
+ if (accept_encoding)
{
- *ret_hdrs_bkt = hdrs_bkt;
+ serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding);
}
+ /* These headers need to be sent with every request; see issue #3255
+ ("mod_dav_svn does not pass client capabilities to start-commit
+ hooks") for why. */
+ serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
+ serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
+ serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
+
return SVN_NO_ERROR;
}
@@ -676,6 +898,7 @@ svn_ra_serf__context_run_wait(svn_boolean_t *done,
apr_pool_t *scratch_pool)
{
apr_pool_t *iterpool;
+ apr_interval_time_t waittime_left = sess->timeout;
assert(sess->pending_error == SVN_NO_ERROR);
@@ -691,17 +914,40 @@ svn_ra_serf__context_run_wait(svn_boolean_t *done,
if (sess->cancel_func)
SVN_ERR((*sess->cancel_func)(sess->cancel_baton));
- status = serf_context_run(sess->context, sess->timeout, iterpool);
+ status = serf_context_run(sess->context,
+ SVN_RA_SERF__CONTEXT_RUN_DURATION,
+ iterpool);
err = sess->pending_error;
sess->pending_error = SVN_NO_ERROR;
+ /* If the context duration timeout is up, we'll subtract that
+ duration from the total time alloted for such things. If
+ there's no time left, we fail with a message indicating that
+ the connection timed out. */
if (APR_STATUS_IS_TIMEUP(status))
{
- svn_error_clear(err);
- return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT,
- NULL,
- _("Connection timed out"));
+ status = 0;
+
+ if (sess->timeout)
+ {
+ if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
+ {
+ waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
+ }
+ else
+ {
+ return
+ svn_error_compose_create(
+ err,
+ svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
+ _("Connection timed out")));
+ }
+ }
+ }
+ else
+ {
+ waittime_left = sess->timeout;
}
SVN_ERR(err);
@@ -714,7 +960,7 @@ svn_ra_serf__context_run_wait(svn_boolean_t *done,
_("Error running context"));
}
- return svn_error_wrap_apr(status, _("Error running context"));
+ return svn_ra_serf__wrap_err(status, _("Error running context"));
}
/* Debugging purposes only! */
@@ -729,16 +975,55 @@ svn_ra_serf__context_run_wait(svn_boolean_t *done,
}
+svn_error_t *
+svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ /* Create a serf request based on HANDLER. */
+ svn_ra_serf__request_create(handler);
+
+ /* Wait until the response logic marks its DONE status. */
+ err = svn_ra_serf__context_run_wait(&handler->done, handler->session,
+ scratch_pool);
+
+ /* A callback invocation has been canceled. In this simple case of
+ context_run_one, we can keep the ra-session operational by resetting
+ the connection.
+
+ If we don't do this, the next context run will notice that the connection
+ is still in the error state and will just return SVN_ERR_CEASE_INVOCATION
+ (=the last error for the connection) again */
+ if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
+ {
+ apr_status_t status = serf_connection_reset(handler->conn->conn);
+
+ if (status)
+ err = svn_error_compose_create(err,
+ svn_ra_serf__wrap_err(status, NULL));
+ }
+
+ if (handler->server_error)
+ {
+ err = svn_error_compose_create(err, handler->server_error->error);
+ handler->server_error = NULL;
+ }
+
+ return svn_error_trace(err);
+}
+
+
/*
* Expat callback invoked on a start element tag for an error response.
*/
static svn_error_t *
start_error(svn_ra_serf__xml_parser_t *parser,
- void *userData,
svn_ra_serf__dav_props_t name,
- const char **attrs)
+ const char **attrs,
+ apr_pool_t *scratch_pool)
{
- svn_ra_serf__server_error_t *ctx = userData;
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
if (!ctx->in_error &&
strcmp(name.namespace, "DAV:") == 0 &&
@@ -758,7 +1043,12 @@ start_error(svn_ra_serf__xml_parser_t *parser,
SVN_ERR(svn_cstring_atoi64(&val, err_code));
ctx->error->apr_err = (apr_status_t)val;
}
- else
+
+ /* If there's no error code provided, or if the provided code is
+ 0 (which can happen sometimes depending on how the error is
+ constructed on the server-side), just pick a generic error
+ code to run with. */
+ if (! ctx->error->apr_err)
{
ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
}
@@ -776,10 +1066,10 @@ start_error(svn_ra_serf__xml_parser_t *parser,
*/
static svn_error_t *
end_error(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name)
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
{
- svn_ra_serf__server_error_t *ctx = userData;
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
if (ctx->in_error &&
strcmp(name.namespace, "DAV:") == 0 &&
@@ -792,15 +1082,10 @@ end_error(svn_ra_serf__xml_parser_t *parser,
/* On the server dav_error_response_tag() will add a leading
and trailing newline if DEBUG_CR is defined in mod_dav.h,
so remove any such characters here. */
- apr_size_t len;
- const char *cd = ctx->cdata->data;
- if (*cd == '\n')
- ++cd;
- len = strlen(cd);
- if (len > 0 && cd[len-1] == '\n')
- --len;
-
- ctx->error->message = apr_pstrmemdup(ctx->error->pool, cd, len);
+ svn_stringbuf_strip_whitespace(ctx->cdata);
+
+ ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data,
+ ctx->cdata->len);
ctx->collect_cdata = FALSE;
}
@@ -814,11 +1099,11 @@ end_error(svn_ra_serf__xml_parser_t *parser,
*/
static svn_error_t *
cdata_error(svn_ra_serf__xml_parser_t *parser,
- void *userData,
const char *data,
- apr_size_t len)
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
{
- svn_ra_serf__server_error_t *ctx = userData;
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
if (ctx->collect_cdata)
{
@@ -828,6 +1113,47 @@ cdata_error(svn_ra_serf__xml_parser_t *parser,
return SVN_NO_ERROR;
}
+
+static apr_status_t
+drain_bucket(serf_bucket_t *bucket)
+{
+ /* Read whatever is in the bucket, and just drop it. */
+ while (1)
+ {
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(bucket, SERF_READ_ALL_AVAIL, &data, &len);
+ if (status)
+ return status;
+ }
+}
+
+
+static svn_ra_serf__server_error_t *
+begin_error_parsing(svn_ra_serf__xml_start_element_t start,
+ svn_ra_serf__xml_end_element_t end,
+ svn_ra_serf__xml_cdata_chunk_handler_t cdata,
+ apr_pool_t *result_pool)
+{
+ svn_ra_serf__server_error_t *server_err;
+
+ server_err = apr_pcalloc(result_pool, sizeof(*server_err));
+ server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL);
+ server_err->contains_precondition_error = FALSE;
+ server_err->cdata = svn_stringbuf_create_empty(server_err->error->pool);
+ server_err->collect_cdata = FALSE;
+ server_err->parser.pool = server_err->error->pool;
+ server_err->parser.user_data = server_err;
+ server_err->parser.start = start;
+ server_err->parser.end = end;
+ server_err->parser.cdata = cdata;
+ server_err->parser.ignore_errors = TRUE;
+
+ return server_err;
+}
+
/* Implements svn_ra_serf__response_handler_t */
svn_error_t *
svn_ra_serf__handle_discard_body(serf_request_t *request,
@@ -836,63 +1162,10 @@ svn_ra_serf__handle_discard_body(serf_request_t *request,
apr_pool_t *pool)
{
apr_status_t status;
- svn_ra_serf__server_error_t *server_err = baton;
-
- if (server_err)
- {
- if (!server_err->init)
- {
- serf_bucket_t *hdrs;
- const char *val;
-
- server_err->init = TRUE;
- 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)
- {
- server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL);
- server_err->has_xml_response = TRUE;
- server_err->contains_precondition_error = FALSE;
- server_err->cdata = svn_stringbuf_create("", pool);
- server_err->collect_cdata = FALSE;
- server_err->parser.pool = server_err->error->pool;
- server_err->parser.user_data = server_err;
- server_err->parser.start = start_error;
- server_err->parser.end = end_error;
- server_err->parser.cdata = cdata_error;
- server_err->parser.done = &server_err->done;
- server_err->parser.ignore_errors = TRUE;
- }
- else
- {
- server_err->error = SVN_NO_ERROR;
- }
- }
-
- if (server_err->has_xml_response)
- {
- svn_error_t *err = svn_ra_serf__handle_xml_parser(
- request,
- response,
- &server_err->parser,
- pool);
-
- if (server_err->done && server_err->error->apr_err == APR_SUCCESS)
- {
- svn_error_clear(server_err->error);
- server_err->error = SVN_NO_ERROR;
- }
-
- return svn_error_trace(err);
- }
-
- }
-
- status = svn_ra_serf__response_discard_handler(request, response,
- NULL, pool);
+ status = drain_bucket(response);
if (status)
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
return SVN_NO_ERROR;
}
@@ -903,71 +1176,107 @@ svn_ra_serf__response_discard_handler(serf_request_t *request,
void *baton,
apr_pool_t *pool)
{
- /* Just loop through and discard the body. */
- while (1)
+ return drain_bucket(response);
+}
+
+
+/* Return the value of the RESPONSE's Location header if any, or NULL
+ otherwise. */
+static const char *
+response_get_location(serf_bucket_t *response,
+ const char *base_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ serf_bucket_t *headers;
+ const char *location;
+
+ headers = serf_bucket_response_get_headers(response);
+ location = serf_bucket_headers_get(headers, "Location");
+ if (location == NULL)
+ return NULL;
+
+ /* The RFCs say we should have received a full url in LOCATION, but
+ older apache versions and many custom web handlers just return a
+ relative path here...
+
+ And we can't trust anything because it is network data.
+ */
+ if (*location == '/')
{
+ apr_uri_t uri;
apr_status_t status;
- const char *data;
- apr_size_t len;
- status = serf_bucket_read(response, SERF_READ_ALL_AVAIL, &data, &len);
+ status = apr_uri_parse(scratch_pool, base_url, &uri);
- if (status)
- {
- return status;
- }
+ if (status != APR_SUCCESS)
+ return NULL;
- /* feed me */
- }
-}
+ /* Replace the path path with what we got */
+ uri.path = (char*)svn_urlpath__canonicalize(location, scratch_pool);
-const char *
-svn_ra_serf__response_get_location(serf_bucket_t *response,
- apr_pool_t *pool)
-{
- serf_bucket_t *headers;
- const char *val;
+ /* And make APR produce a proper full url for us */
+ location = apr_uri_unparse(scratch_pool, &uri, 0);
- headers = serf_bucket_response_get_headers(response);
- val = serf_bucket_headers_get(headers, "Location");
- return val ? svn_urlpath__canonicalize(val, pool) : NULL;
+ /* Fall through to ensure our canonicalization rules */
+ }
+ else if (!svn_path_is_url(location))
+ {
+ return NULL; /* Any other formats we should support? */
+ }
+
+ return svn_uri_canonicalize(location, result_pool);
}
+
/* Implements svn_ra_serf__response_handler_t */
svn_error_t *
-svn_ra_serf__handle_status_only(serf_request_t *request,
- serf_bucket_t *response,
- void *baton,
- apr_pool_t *pool)
+svn_ra_serf__expect_empty_body(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
{
- svn_error_t *err;
- svn_ra_serf__simple_request_context_t *ctx = baton;
+ svn_ra_serf__handler_t *handler = baton;
+ serf_bucket_t *hdrs;
+ const char *val;
- SVN_ERR_ASSERT(ctx->pool);
+ /* This function is just like handle_multistatus_only() except for the
+ XML parsing callbacks. We want to look for the human-readable element. */
- err = svn_ra_serf__handle_discard_body(request, response,
- &ctx->server_error, pool);
+ /* 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);
- if (err && APR_STATUS_IS_EOF(err->apr_err))
+ 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)
{
- serf_status_line sl;
- apr_status_t status;
+ svn_ra_serf__server_error_t *server_err;
- status = serf_bucket_response_status(response, &sl);
- if (SERF_BUCKET_READ_ERROR(status))
- {
- return svn_error_wrap_apr(status, NULL);
- }
+ server_err = begin_error_parsing(start_error, end_error, cdata_error,
+ handler->handler_pool);
+
+ /* Get the parser to set our DONE flag. */
+ server_err->parser.done = &handler->done;
- ctx->status = sl.code;
- ctx->reason = sl.reason ? apr_pstrdup(ctx->pool, sl.reason) : NULL;
- ctx->location = svn_ra_serf__response_get_location(response, ctx->pool);
- ctx->done = TRUE;
+ 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;
}
- return svn_error_trace(err);
+ /* 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;
}
+
/* 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 *
@@ -1001,11 +1310,11 @@ parse_dav_status(int *status_code_out, svn_stringbuf_t *buf,
*/
static svn_error_t *
start_207(svn_ra_serf__xml_parser_t *parser,
- void *userData,
svn_ra_serf__dav_props_t name,
- const char **attrs)
+ const char **attrs,
+ apr_pool_t *scratch_pool)
{
- svn_ra_serf__server_error_t *ctx = userData;
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
if (!ctx->in_error &&
strcmp(name.namespace, "DAV:") == 0 &&
@@ -1036,10 +1345,10 @@ start_207(svn_ra_serf__xml_parser_t *parser,
*/
static svn_error_t *
end_207(svn_ra_serf__xml_parser_t *parser,
- void *userData,
- svn_ra_serf__dav_props_t name)
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
{
- svn_ra_serf__server_error_t *ctx = userData;
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
if (ctx->in_error &&
strcmp(name.namespace, "DAV:") == 0 &&
@@ -1049,6 +1358,9 @@ end_207(svn_ra_serf__xml_parser_t *parser,
}
if (ctx->in_error && strcmp(name.name, "responsedescription") == 0)
{
+ /* Remove leading newline added by DEBUG_CR on server */
+ svn_stringbuf_strip_whitespace(ctx->cdata);
+
ctx->collect_cdata = FALSE;
ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data,
ctx->cdata->len);
@@ -1080,11 +1392,11 @@ end_207(svn_ra_serf__xml_parser_t *parser,
*/
static svn_error_t *
cdata_207(svn_ra_serf__xml_parser_t *parser,
- void *userData,
const char *data,
- apr_size_t len)
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
{
- svn_ra_serf__server_error_t *ctx = userData;
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
if (ctx->collect_cdata)
{
@@ -1099,97 +1411,61 @@ svn_error_t *
svn_ra_serf__handle_multistatus_only(serf_request_t *request,
serf_bucket_t *response,
void *baton,
- apr_pool_t *pool)
+ apr_pool_t *scratch_pool)
{
- svn_error_t *err;
- svn_ra_serf__simple_request_context_t *ctx = baton;
- svn_ra_serf__server_error_t *server_err = &ctx->server_error;
+ 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. */
- SVN_ERR_ASSERT(ctx->pool);
+ /* 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);
- /* If necessary, initialize our XML parser. */
- if (server_err && !server_err->init)
{
serf_bucket_t *hdrs;
const char *val;
- server_err->init = TRUE;
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)
{
- server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL);
- server_err->has_xml_response = TRUE;
- server_err->contains_precondition_error = FALSE;
- server_err->cdata = svn_stringbuf_create("", server_err->error->pool);
- server_err->collect_cdata = FALSE;
- server_err->parser.pool = server_err->error->pool;
- server_err->parser.user_data = server_err;
- server_err->parser.start = start_207;
- server_err->parser.end = end_207;
- server_err->parser.cdata = cdata_207;
- server_err->parser.done = &ctx->done;
- server_err->parser.ignore_errors = TRUE;
- }
- else
- {
- ctx->done = TRUE;
- server_err->error = SVN_NO_ERROR;
- }
- }
+ svn_ra_serf__server_error_t *server_err;
- /* If server_err->error still contains APR_SUCCESS, it means that we
- have not successfully parsed the XML yet. */
- if (server_err && server_err->error
- && server_err->error->apr_err == APR_SUCCESS)
- {
- err = svn_ra_serf__handle_xml_parser(request, response,
- &server_err->parser, pool);
-
- /* APR_EOF will be returned when parsing is complete. If we see
- any other error, return it immediately. In practice the only
- other error we expect to see is APR_EAGAIN, which indicates that
- we could not parse the XML because the contents are not yet
- available to be read. */
- if (!err || !APR_STATUS_IS_EOF(err->apr_err))
- {
- return svn_error_trace(err);
- }
- else if (ctx->done && server_err->error->apr_err == APR_SUCCESS)
- {
- svn_error_clear(server_err->error);
- server_err->error = SVN_NO_ERROR;
- }
+ server_err = begin_error_parsing(start_207, end_207, cdata_207,
+ handler->handler_pool);
- svn_error_clear(err);
- }
-
- err = svn_ra_serf__handle_discard_body(request, response, NULL, pool);
-
- if (err && APR_STATUS_IS_EOF(err->apr_err))
- {
- serf_status_line sl;
- apr_status_t status;
+ /* Get the parser to set our DONE flag. */
+ server_err->parser.done = &handler->done;
- status = serf_bucket_response_status(response, &sl);
- if (SERF_BUCKET_READ_ERROR(status))
+ handler->server_error = server_err;
+ }
+ else
{
- return svn_error_wrap_apr(status, NULL);
+ /* The body was not text/xml, so we don't know what to do with it.
+ Toss anything that arrives. */
+ handler->discard_body = TRUE;
}
-
- ctx->status = sl.code;
- ctx->reason = sl.reason ? apr_pstrdup(ctx->pool, sl.reason) : NULL;
- ctx->location = svn_ra_serf__response_get_location(response, ctx->pool);
}
- return svn_error_trace(err);
+ /* 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;
}
+
+/* Conforms to Expat's XML_StartElementHandler */
static void
start_xml(void *userData, const char *raw_name, const char **attrs)
{
svn_ra_serf__xml_parser_t *parser = userData;
svn_ra_serf__dav_props_t name;
+ apr_pool_t *scratch_pool;
+ svn_error_t *err;
if (parser->error)
return;
@@ -1197,31 +1473,53 @@ start_xml(void *userData, const char *raw_name, const char **attrs)
if (!parser->state)
svn_ra_serf__xml_push_state(parser, 0);
+ /* ### get a real scratch_pool */
+ scratch_pool = parser->state->pool;
+
svn_ra_serf__define_ns(&parser->state->ns_list, attrs, parser->state->pool);
svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name);
- parser->error = parser->start(parser, parser->user_data, name, attrs);
+ err = parser->start(parser, name, attrs, scratch_pool);
+ if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
+ err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
+
+ parser->error = err;
}
+
+/* Conforms to Expat's XML_EndElementHandler */
static void
end_xml(void *userData, const char *raw_name)
{
svn_ra_serf__xml_parser_t *parser = userData;
svn_ra_serf__dav_props_t name;
+ svn_error_t *err;
+ apr_pool_t *scratch_pool;
if (parser->error)
return;
+ /* ### get a real scratch_pool */
+ scratch_pool = parser->state->pool;
+
svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name);
- parser->error = parser->end(parser, parser->user_data, name);
+ err = parser->end(parser, name, scratch_pool);
+ if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
+ err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
+
+ parser->error = err;
}
+
+/* Conforms to Expat's XML_CharacterDataHandler */
static void
cdata_xml(void *userData, const char *data, int len)
{
svn_ra_serf__xml_parser_t *parser = userData;
+ svn_error_t *err;
+ apr_pool_t *scratch_pool;
if (parser->error)
return;
@@ -1229,7 +1527,14 @@ cdata_xml(void *userData, const char *data, int len)
if (!parser->state)
svn_ra_serf__xml_push_state(parser, 0);
- parser->error = parser->cdata(parser, parser->user_data, data, len);
+ /* ### get a real scratch_pool */
+ scratch_pool = parser->state->pool;
+
+ err = parser->cdata(parser, data, len, scratch_pool);
+ if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
+ err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
+
+ parser->error = err;
}
/* Flip the requisite bits in CTX to indicate that processing of the
@@ -1252,101 +1557,24 @@ add_done_item(svn_ra_serf__xml_parser_t *ctx)
}
-/* Get a buffer from the parsing context. It will come from the free list,
- or allocated as necessary. */
-static struct pending_buffer_t *
-get_buffer(svn_ra_serf__xml_parser_t *parser)
-{
- struct pending_buffer_t *pb;
-
- if (parser->pending->avail == NULL)
- return apr_palloc(parser->pool, sizeof(*pb));
-
- pb = parser->pending->avail;
- parser->pending->avail = pb->next;
- return pb;
-}
-
-
-/* Return PB to the list of available buffers in PARSER. */
-static void
-return_buffer(svn_ra_serf__xml_parser_t *parser,
- struct pending_buffer_t *pb)
-{
- pb->next = parser->pending->avail;
- parser->pending->avail = pb;
-}
-
-
static svn_error_t *
write_to_pending(svn_ra_serf__xml_parser_t *ctx,
const char *data,
apr_size_t len,
apr_pool_t *scratch_pool)
{
- struct pending_buffer_t *pb;
-
- /* The caller should not have provided us more than we can store into
- a single memory block. */
- SVN_ERR_ASSERT(len <= PARSE_CHUNK_SIZE);
-
if (ctx->pending == NULL)
- ctx->pending = apr_pcalloc(ctx->pool, sizeof(*ctx->pending));
-
- /* We do not (yet) have a spill file, but the amount stored in memory
- has grown too large. Create the file and place the pending data into
- the temporary file. */
- if (ctx->pending->spill == NULL
- && ctx->pending->memory_size > SPILL_SIZE)
- {
- SVN_ERR(svn_io_open_unique_file3(&ctx->pending->spill,
- NULL /* temp_path */,
- NULL /* dirpath */,
- svn_io_file_del_on_pool_cleanup,
- ctx->pool, scratch_pool));
- }
-
- /* Once a spill file has been constructed, then we need to put all
- arriving data into the file. We will no longer attempt to hold it
- in memory. */
- if (ctx->pending->spill != NULL)
- {
- /* NOTE: we assume the file position is at the END. The caller should
- ensure this, so that we will append. */
- SVN_ERR(svn_io_file_write_full(ctx->pending->spill, data, len,
- NULL, scratch_pool));
- return SVN_NO_ERROR;
- }
-
- /* We're still within bounds of holding the pending information in
- memory. Get a buffer, copy the data there, and link it into our
- pending data. */
- pb = get_buffer(ctx);
- /* NOTE: *pb is uninitialized. All fields must be stored. */
-
- pb->size = len;
- memcpy(pb->data, data, len);
- pb->next = NULL;
-
- /* Start a list of buffers, or append to the end of the linked list
- of buffers. */
- if (ctx->pending->tail == NULL)
{
- ctx->pending->head = pb;
- ctx->pending->tail = pb;
+ ctx->pending = apr_pcalloc(ctx->pool, sizeof(*ctx->pending));
+ ctx->pending->buf = svn_spillbuf__create(PARSE_CHUNK_SIZE,
+ SPILL_SIZE,
+ ctx->pool);
}
- else
- {
- ctx->pending->tail->next = pb;
- ctx->pending->tail = pb;
- }
-
- /* We need to record how much is buffered in memory. Once we reach
- SPILL_SIZE (or thereabouts, it doesn't have to be precise), then
- we'll switch to putting the content into a file. */
- ctx->pending->memory_size += len;
- return SVN_NO_ERROR;
+ /* Copy the data into one or more chunks in the spill buffer. */
+ return svn_error_trace(svn_spillbuf__write(ctx->pending->buf,
+ data, len,
+ scratch_pool));
}
@@ -1358,20 +1586,23 @@ inject_to_parser(svn_ra_serf__xml_parser_t *ctx,
{
int xml_status;
- xml_status = XML_Parse(ctx->xmlp, data, len, 0);
- if (xml_status == XML_STATUS_ERROR && !ctx->ignore_errors)
+ xml_status = XML_Parse(ctx->xmlp, data, (int) len, 0);
+
+ if (! ctx->ignore_errors)
{
- if (sl == NULL)
- return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("XML parsing failed"));
+ SVN_ERR(ctx->error);
- return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("XML parsing failed: (%d %s)"),
- sl->code, sl->reason);
- }
+ if (xml_status != XML_STATUS_OK)
+ {
+ if (sl == NULL)
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("XML parsing failed"));
- if (ctx->error && !ctx->ignore_errors)
- return svn_error_trace(ctx->error);
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("XML parsing failed: (%d %s)"),
+ sl->code, sl->reason);
+ }
+ }
return SVN_NO_ERROR;
}
@@ -1385,119 +1616,158 @@ xml_parser_cleanup(void *baton)
if (*xmlp)
{
- XML_ParserFree(*xmlp);
+ (void) XML_ParserFree(*xmlp);
*xmlp = NULL;
}
return APR_SUCCESS;
}
+/* Limit the amount of pending content to parse at once to < 100KB per
+ iteration. This number is chosen somewhat arbitrarely. Making it lower
+ will have a drastical negative impact on performance, whereas increasing it
+ increases the risk for connection timeouts.
+ */
+#define PENDING_TO_PARSE PARSE_CHUNK_SIZE * 5
+
svn_error_t *
svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser,
+ svn_boolean_t *network_eof,
apr_pool_t *scratch_pool)
{
- struct pending_buffer_t *pb;
- svn_error_t *err;
- apr_off_t output_unused;
+ svn_boolean_t pending_empty = FALSE;
+ apr_size_t cur_read = 0;
/* Fast path exit: already paused, nothing to do, or already done. */
if (parser->paused || parser->pending == NULL || *parser->done)
- return SVN_NO_ERROR;
-
- /* ### it is possible that the XML parsing of the pending content is
- ### so slow, and that we don't return to reading the connection
- ### fast enough... that the server will disconnect us. right now,
- ### that is highly improbably, but is noted for future's sake.
- ### should that ever happen, the loops in this function can simply
- ### terminate after N seconds. */
-
- /* Empty out memory buffers until we run out, or we get paused again. */
- while (parser->pending->head != NULL)
{
- /* Pull the HEAD buffer out of the list. */
- pb = parser->pending->head;
- if (parser->pending->tail == pb)
- parser->pending->head = parser->pending->tail = NULL;
- else
- parser->pending->head = pb->next;
+ *network_eof = parser->pending ? parser->pending->network_eof : FALSE;
+ return SVN_NO_ERROR;
+ }
- /* We're using less memory now. If we haven't hit the spill file,
- then we may be able to keep using memory. */
- parser->pending->memory_size -= pb->size;
+ /* Parsing the pending conten in the spillbuf will result in many disc i/o
+ operations. This can be so slow that we don't run the network event
+ processing loop often enough, resulting in timed out connections.
- err = inject_to_parser(parser, pb->data, pb->size, NULL);
+ So we limit the amounts of bytes parsed per iteration.
+ */
+ while (cur_read < PENDING_TO_PARSE)
+ {
+ const char *data;
+ apr_size_t len;
- return_buffer(parser, pb);
+ /* Get a block of content, stopping the loop when we run out. */
+ SVN_ERR(svn_spillbuf__read(&data, &len, parser->pending->buf,
+ scratch_pool));
+ if (data)
+ {
+ /* Inject the content into the XML parser. */
+ SVN_ERR(inject_to_parser(parser, data, len, NULL));
- if (err)
- return svn_error_trace(err);
+ /* If the XML parsing callbacks paused us, then we're done for now. */
+ if (parser->paused)
+ break;
- /* If the callbacks paused us, then we're done for now. */
- if (parser->paused)
- return SVN_NO_ERROR;
+ cur_read += len;
+ }
+ else
+ {
+ /* The buffer is empty. */
+ pending_empty = TRUE;
+ break;
+ }
}
- /* If we don't have a spill file, then we've exhausted all
- pending content. */
- if (parser->pending->spill == NULL)
- goto pending_empty;
-
- /* Seek once to where we left off reading. */
- output_unused = parser->pending->spill_start; /* ### stupid API */
- SVN_ERR(svn_io_file_seek(parser->pending->spill,
- APR_SET, &output_unused,
- scratch_pool));
+ /* If the PENDING structures are empty *and* we consumed all content from
+ the network, then we're completely done with the parsing. */
+ if (pending_empty &&
+ parser->pending->network_eof)
+ {
+ int xml_status;
+ SVN_ERR_ASSERT(parser->xmlp != NULL);
- /* We need a buffer for reading out of the file. One of these will always
- exist by the time we start reading from the spill file. */
- pb = get_buffer(parser);
+ /* Tell the parser that no more content will be parsed. */
+ xml_status = XML_Parse(parser->xmlp, NULL, 0, 1);
- /* Keep reading until we hit EOF, or get paused again. */
- while (TRUE)
- {
- apr_size_t len = sizeof(pb->data);
- apr_status_t status;
+ apr_pool_cleanup_run(parser->pool, &parser->xmlp, xml_parser_cleanup);
+ parser->xmlp = NULL;
- /* Read some data and remember where we left off. */
- status = apr_file_read(parser->pending->spill, pb->data, &len);
- if (status && !APR_STATUS_IS_EOF(status))
+ if (! parser->ignore_errors)
{
- err = svn_error_wrap_apr(status, NULL);
- break;
+ SVN_ERR(parser->error);
+
+ if (xml_status != XML_STATUS_OK)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("XML parsing failed"));
+ }
}
- parser->pending->spill_start += len;
- err = inject_to_parser(parser, pb->data, len, NULL);
- if (err)
- break;
+ add_done_item(parser);
+ }
- /* If we just consumed everything in the spill file, then we may
- be done with the parsing. */
- /* ### future change: when we hit EOF, then remove the spill file.
- ### we could go back to using memory for a while. */
- if (APR_STATUS_IS_EOF(status))
- goto pending_empty;
+ *network_eof = parser->pending ? parser->pending->network_eof : FALSE;
- /* If the callbacks paused the parsing, then we're done for now. */
- if (parser->paused)
- break;
- }
+ return SVN_NO_ERROR;
+}
+#undef PENDING_TO_PARSE
- return_buffer(parser, pb);
- return svn_error_trace(err); /* may be SVN_NO_ERROR */
- pending_empty:
- /* If the PENDING structures are empty *and* we consumed all content from
- the network, then we're completely done with the parsing. */
- if (parser->pending->network_eof)
- {
- /* Tell the parser that no more content will be parsed. Ignore the
- return status. We just don't care. */
- (void) XML_Parse(parser->xmlp, NULL, 0, 1);
+/* ### this is still broken conceptually. just shifting incrementally... */
+static svn_error_t *
+handle_server_error(serf_request_t *request,
+ serf_bucket_t *response,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t server_err = { 0 };
+ serf_bucket_t *hdrs;
+ const char *val;
+ apr_status_t err;
+
+ 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)
+ {
+ /* ### we should figure out how to reuse begin_error_parsing */
+
+ server_err.error = svn_error_create(APR_SUCCESS, NULL, NULL);
+ server_err.contains_precondition_error = FALSE;
+ server_err.cdata = svn_stringbuf_create_empty(scratch_pool);
+ server_err.collect_cdata = FALSE;
+ server_err.parser.pool = server_err.error->pool;
+ server_err.parser.user_data = &server_err;
+ server_err.parser.start = start_error;
+ server_err.parser.end = end_error;
+ server_err.parser.cdata = cdata_error;
+ server_err.parser.done = &server_err.done;
+ server_err.parser.ignore_errors = TRUE;
+
+ /* We don't care about any errors except for SERVER_ERR.ERROR */
+ svn_error_clear(svn_ra_serf__handle_xml_parser(request,
+ response,
+ &server_err.parser,
+ scratch_pool));
+
+ /* ### checking DONE is silly. the above only parses whatever has
+ ### been received at the network interface. totally wrong. but
+ ### it is what we have for now (maintaining historical code),
+ ### until we fully migrate. */
+ if (server_err.done && server_err.error->apr_err == APR_SUCCESS)
+ {
+ svn_error_clear(server_err.error);
+ server_err.error = SVN_NO_ERROR;
+ }
- apr_pool_cleanup_run(parser->pool, &parser->xmlp, xml_parser_cleanup);
- add_done_item(parser);
+ return svn_error_trace(server_err.error);
}
+
+ /* The only error that we will return is from the XML response body.
+ Otherwise, ignore the entire body but allow SUCCESS/EOF/EAGAIN to
+ surface. */
+ err = drain_bucket(response);
+ if (err && !SERF_BUCKET_READ_ERROR(err))
+ return svn_ra_serf__wrap_err(err, NULL);
+
return SVN_NO_ERROR;
}
@@ -1514,52 +1784,22 @@ svn_ra_serf__handle_xml_parser(serf_request_t *request,
svn_ra_serf__xml_parser_t *ctx = baton;
svn_error_t *err;
+ /* ### get the HANDLER rather than fetching this. */
status = serf_bucket_response_status(response, &sl);
if (SERF_BUCKET_READ_ERROR(status))
{
- return svn_error_wrap_apr(status, NULL);
- }
-
- if (ctx->status_code)
- {
- *ctx->status_code = sl.code;
- }
-
- if (sl.code == 301 || sl.code == 302 || sl.code == 307)
- {
- ctx->location = svn_ra_serf__response_get_location(response, ctx->pool);
+ return svn_ra_serf__wrap_err(status, NULL);
}
/* Woo-hoo. Nothing here to see. */
- if (sl.code == 404 && ctx->ignore_errors == FALSE)
- {
- /* If our caller won't know about the 404, abort() for now. */
- SVN_ERR_ASSERT(ctx->status_code);
-
- add_done_item(ctx);
-
- err = svn_ra_serf__handle_server_error(request, response, pool);
-
- SVN_ERR(svn_error_compose_create(
- svn_ra_serf__handle_discard_body(request, response, NULL, pool),
- err));
- return SVN_NO_ERROR;
- }
-
- if (ctx->headers_baton == NULL)
- ctx->headers_baton = serf_bucket_response_get_headers(response);
- else if (ctx->headers_baton != serf_bucket_response_get_headers(response))
+ if (sl.code == 404 && !ctx->ignore_errors)
{
- /* We got a new response to an existing parser...
- This tells us the connection has restarted and we should continue
- where we stopped last time.
- */
+ err = handle_server_error(request, response, pool);
- /* Is this a second attempt?? */
- if (!ctx->skip_size)
- ctx->skip_size = ctx->read_size;
+ if (err && APR_STATUS_IS_EOF(err->apr_err))
+ add_done_item(ctx);
- ctx->read_size = 0; /* New request, nothing read */
+ return svn_error_trace(err);
}
if (!ctx->xmlp)
@@ -1575,57 +1815,15 @@ svn_ra_serf__handle_xml_parser(serf_request_t *request,
}
}
- /* If we are storing content into a spill file, then move to the end of
- the file. We need to pre-position the file so that write_to_pending()
- will always append the content. */
- if (ctx->pending != NULL && ctx->pending->spill != NULL)
- {
- apr_off_t output_unused = 0; /* ### stupid API */
-
- SVN_ERR(svn_io_file_seek(ctx->pending->spill,
- APR_END, &output_unused,
- pool));
- }
-
while (1)
{
const char *data;
apr_size_t len;
status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
-
if (SERF_BUCKET_READ_ERROR(status))
{
- return svn_error_wrap_apr(status, NULL);
- }
-
- ctx->read_size += len;
-
- if (ctx->skip_size)
- {
- /* Handle restarted requests correctly: Skip what we already read */
- apr_size_t skip;
-
- if (ctx->skip_size >= ctx->read_size)
- {
- /* Eek. What did the file shrink or something? */
- if (APR_STATUS_IS_EOF(status))
- {
- SVN_ERR_MALFUNCTION();
- }
-
- /* Skip on to the next iteration of this loop. */
- if (APR_STATUS_IS_EAGAIN(status))
- {
- return svn_error_wrap_apr(status, NULL);
- }
- continue;
- }
-
- skip = (apr_size_t)(len - (ctx->read_size - ctx->skip_size));
- data += skip;
- len -= skip;
- ctx->skip_size = 0;
+ return svn_ra_serf__wrap_err(status, NULL);
}
/* Note: once the callbacks invoked by inject_to_parser() sets the
@@ -1652,6 +1850,8 @@ svn_ra_serf__handle_xml_parser(serf_request_t *request,
}
if (err)
{
+ SVN_ERR_ASSERT(ctx->xmlp != NULL);
+
apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup);
add_done_item(ctx);
return svn_error_trace(err);
@@ -1659,7 +1859,7 @@ svn_ra_serf__handle_xml_parser(serf_request_t *request,
if (APR_STATUS_IS_EAGAIN(status))
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
if (APR_STATUS_IS_EOF(status))
@@ -1671,14 +1871,29 @@ svn_ra_serf__handle_xml_parser(serf_request_t *request,
in the PENDING structures, then we're completely done. */
if (!HAS_PENDING_DATA(ctx->pending))
{
- /* Ignore the return status. We just don't care. */
- (void) XML_Parse(ctx->xmlp, NULL, 0, 1);
+ int xml_status;
+ SVN_ERR_ASSERT(ctx->xmlp != NULL);
+
+ xml_status = XML_Parse(ctx->xmlp, NULL, 0, 1);
apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup);
+
+ if (! ctx->ignore_errors)
+ {
+ SVN_ERR(ctx->error);
+
+ if (xml_status != XML_STATUS_OK)
+ {
+ return svn_error_create(
+ SVN_ERR_XML_MALFORMED, NULL,
+ _("The XML response contains invalid XML"));
+ }
+ }
+
add_done_item(ctx);
}
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
/* feed me! */
@@ -1686,19 +1901,6 @@ svn_ra_serf__handle_xml_parser(serf_request_t *request,
/* not reached */
}
-/* Implements svn_ra_serf__response_handler_t */
-svn_error_t *
-svn_ra_serf__handle_server_error(serf_request_t *request,
- serf_bucket_t *response,
- apr_pool_t *pool)
-{
- svn_ra_serf__server_error_t server_err = { 0 };
-
- svn_error_clear(svn_ra_serf__handle_discard_body(request, response,
- &server_err, pool));
-
- return server_err.error;
-}
apr_status_t
svn_ra_serf__credentials_callback(char **username, char **password,
@@ -1707,8 +1909,8 @@ svn_ra_serf__credentials_callback(char **username, char **password,
const char *realm,
apr_pool_t *pool)
{
- svn_ra_serf__handler_t *ctx = baton;
- svn_ra_serf__session_t *session = ctx->session;
+ svn_ra_serf__handler_t *handler = baton;
+ svn_ra_serf__session_t *session = handler->session;
void *creds;
svn_auth_cred_simple_t *simple_creds;
svn_error_t *err;
@@ -1737,8 +1939,7 @@ svn_ra_serf__credentials_callback(char **username, char **password,
if (err)
{
- session->pending_error
- = svn_error_compose_create(session->pending_error, err);
+ (void) save_error(session, err);
return err->apr_err;
}
@@ -1747,13 +1948,11 @@ svn_ra_serf__credentials_callback(char **username, char **password,
if (!creds || session->auth_attempts > 4)
{
/* No more credentials. */
- session->pending_error
- = svn_error_compose_create(
- session->pending_error,
- svn_error_create(
- SVN_ERR_AUTHN_FAILED, NULL,
- _("No more credentials or we tried too many times.\n"
- "Authentication failed")));
+ (void) save_error(session,
+ svn_error_create(
+ SVN_ERR_AUTHN_FAILED, NULL,
+ _("No more credentials or we tried too many "
+ "times.\nAuthentication failed")));
return SVN_ERR_AUTHN_FAILED;
}
@@ -1771,21 +1970,20 @@ svn_ra_serf__credentials_callback(char **username, char **password,
if (!session->proxy_username || session->proxy_auth_attempts > 4)
{
/* No more credentials. */
- session->pending_error
- = svn_error_compose_create(
- ctx->session->pending_error,
- svn_error_create(SVN_ERR_AUTHN_FAILED, NULL,
- _("Proxy authentication failed")));
+ (void) save_error(session,
+ svn_error_create(
+ SVN_ERR_AUTHN_FAILED, NULL,
+ _("Proxy authentication failed")));
return SVN_ERR_AUTHN_FAILED;
}
}
- ctx->conn->last_status_code = code;
+ handler->conn->last_status_code = code;
return APR_SUCCESS;
}
-/* Wait for HTTP response status and headers, and invoke CTX->
+/* Wait for HTTP response status and headers, and invoke HANDLER->
response_handler() to carry out operation-specific processing.
Afterwards, check for connection close.
@@ -1795,47 +1993,97 @@ svn_ra_serf__credentials_callback(char **username, char **password,
static svn_error_t *
handle_response(serf_request_t *request,
serf_bucket_t *response,
- svn_ra_serf__handler_t *ctx,
+ svn_ra_serf__handler_t *handler,
apr_status_t *serf_status,
- apr_pool_t *pool)
+ apr_pool_t *scratch_pool)
{
- serf_status_line sl;
apr_status_t status;
+ svn_error_t *err;
+
+ /* ### need to verify whether this already gets init'd on every
+ ### successful exit. for an error-exit, it will (properly) be
+ ### ignored by the caller. */
+ *serf_status = APR_SUCCESS;
if (!response)
{
- /* Uh-oh. Our connection died. Requeue. */
- if (ctx->response_error)
- SVN_ERR(ctx->response_error(request, response, 0,
- ctx->response_error_baton));
+ /* Uh-oh. Our connection died. */
+ if (handler->response_error)
+ {
+ /* Give a handler chance to prevent request requeue. */
+ SVN_ERR(handler->response_error(request, response, 0,
+ handler->response_error_baton));
- svn_ra_serf__request_create(ctx);
+ svn_ra_serf__request_create(handler);
+ }
+ /* Response error callback is not configured. Requeue another request
+ for this handler only if we didn't started to process body.
+ Return error otherwise. */
+ else if (!handler->reading_body)
+ {
+ svn_ra_serf__request_create(handler);
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("%s request on '%s' failed"),
+ handler->method, handler->path);
+ }
- return APR_SUCCESS;
+ return SVN_NO_ERROR;
}
- status = serf_bucket_response_status(response, &sl);
- if (SERF_BUCKET_READ_ERROR(status))
- {
- *serf_status = status;
- return SVN_NO_ERROR; /* Handled by serf */
- }
- if (!sl.version && (APR_STATUS_IS_EOF(status) ||
- APR_STATUS_IS_EAGAIN(status)))
+ /* If we're reading the body, then skip all this preparation. */
+ if (handler->reading_body)
+ goto process_body;
+
+ /* Copy the Status-Line info into HANDLER, if we don't yet have it. */
+ if (handler->sline.version == 0)
{
- *serf_status = status;
- return SVN_NO_ERROR; /* Handled by serf */
+ serf_status_line sl;
+
+ status = serf_bucket_response_status(response, &sl);
+ if (status != APR_SUCCESS)
+ {
+ /* The response line is not (yet) ready, or some other error. */
+ *serf_status = status;
+ return SVN_NO_ERROR; /* Handled by serf */
+ }
+
+ /* If we got APR_SUCCESS, then we should have Status-Line info. */
+ SVN_ERR_ASSERT(sl.version != 0);
+
+ handler->sline = sl;
+ handler->sline.reason = apr_pstrdup(handler->handler_pool, sl.reason);
+
+ /* HTTP/1.1? (or later) */
+ if (sl.version != SERF_HTTP_10)
+ handler->session->http10 = FALSE;
}
+ /* Keep reading from the network until we've read all the headers. */
status = serf_bucket_response_wait_for_headers(response);
if (status)
{
+ /* The typical "error" will be APR_EAGAIN, meaning that more input
+ from the network is required to complete the reading of the
+ headers. */
if (!APR_STATUS_IS_EOF(status))
{
+ /* Either the headers are not (yet) complete, or there really
+ was an error. */
*serf_status = status;
return SVN_NO_ERROR;
}
+ /* wait_for_headers() will return EOF if there is no body in this
+ response, or if we completely read the body. The latter is not
+ true since we would have set READING_BODY to get the body read,
+ and we would not be back to this code block.
+
+ It can also return EOF if we truly hit EOF while (say) processing
+ the headers. aka Badness. */
+
/* Cases where a lack of a response body (via EOF) is okay:
* - A HEAD request
* - 204/304 response
@@ -1844,171 +2092,262 @@ handle_response(serf_request_t *request,
* the server closed on us early or we're reading too much. Either way,
* scream loudly.
*/
- if (strcmp(ctx->method, "HEAD") != 0 && sl.code != 204 && sl.code != 304)
+ if (strcmp(handler->method, "HEAD") != 0
+ && handler->sline.code != 204
+ && handler->sline.code != 304)
{
- svn_error_t *err =
- svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
- svn_error_wrap_apr(status, NULL),
- _("Premature EOF seen from server "
- "(http status=%d)"), sl.code);
- /* This discard may be no-op, but let's preserve the algorithm
- used elsewhere in this function for clarity's sake. */
- svn_ra_serf__response_discard_handler(request, response, NULL, pool);
+ err = svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
+ svn_ra_serf__wrap_err(status, NULL),
+ _("Premature EOF seen from server"
+ " (http status=%d)"),
+ handler->sline.code);
+
+ /* In case anything else arrives... discard it. */
+ handler->discard_body = TRUE;
+
return err;
}
}
- if (ctx->conn->last_status_code == 401 && sl.code < 400)
+ /* ... and set up the header fields in HANDLER. */
+ handler->location = response_get_location(response,
+ handler->session->session_url_str,
+ handler->handler_pool,
+ scratch_pool);
+
+ /* On the last request, we failed authentication. We succeeded this time,
+ so let's save away these credentials. */
+ if (handler->conn->last_status_code == 401 && handler->sline.code < 400)
{
- SVN_ERR(svn_auth_save_credentials(ctx->session->auth_state,
- ctx->session->pool));
- ctx->session->auth_attempts = 0;
- ctx->session->auth_state = NULL;
+ SVN_ERR(svn_auth_save_credentials(handler->session->auth_state,
+ handler->session->pool));
+ handler->session->auth_attempts = 0;
+ handler->session->auth_state = NULL;
}
+ handler->conn->last_status_code = handler->sline.code;
- ctx->conn->last_status_code = sl.code;
-
- if (sl.code == 405 || sl.code == 409 || sl.code >= 500)
+ if (handler->sline.code == 405
+ || handler->sline.code == 408
+ || handler->sline.code == 409
+ || handler->sline.code >= 500)
{
/* 405 Method Not allowed.
+ 408 Request Timeout
409 Conflict: can indicate a hook error.
5xx (Internal) Server error. */
- SVN_ERR(svn_ra_serf__handle_server_error(request, response, pool));
+ serf_bucket_t *hdrs;
+ const char *val;
- if (!ctx->session->pending_error)
+ 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)
{
- apr_status_t apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
+ svn_ra_serf__server_error_t *server_err;
- /* 405 == Method Not Allowed (Occurs when trying to lock a working
- copy path which no longer exists at HEAD in the repository. */
+ server_err = begin_error_parsing(start_error, end_error, cdata_error,
+ handler->handler_pool);
+ /* Get the parser to set our DONE flag. */
+ server_err->parser.done = &handler->done;
- if (sl.code == 405 && !strcmp(ctx->method, "LOCK"))
- apr_err = SVN_ERR_FS_OUT_OF_DATE;
+ handler->server_error = server_err;
+ }
+ else
+ {
+ handler->discard_body = TRUE;
- return
- svn_error_createf(apr_err, NULL,
- _("%s request on '%s' failed: %d %s"),
- ctx->method, ctx->path, sl.code, sl.reason);
+ if (!handler->session->pending_error)
+ {
+ apr_status_t apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
+
+ /* 405 == Method Not Allowed (Occurs when trying to lock a working
+ copy path which no longer exists at HEAD in the repository. */
+ if (handler->sline.code == 405
+ && strcmp(handler->method, "LOCK") == 0)
+ apr_err = SVN_ERR_FS_OUT_OF_DATE;
+
+ handler->session->pending_error =
+ svn_error_createf(apr_err, NULL,
+ _("%s request on '%s' failed: %d %s"),
+ handler->method, handler->path,
+ handler->sline.code, handler->sline.reason);
+ }
}
+ }
+
+ /* Stop processing the above, on every packet arrival. */
+ handler->reading_body = TRUE;
+
+ process_body:
+
+ /* We've been instructed to ignore the body. Drain whatever is present. */
+ if (handler->discard_body)
+ {
+ *serf_status = drain_bucket(response);
- return SVN_NO_ERROR; /* Error is set in caller */
+ /* If the handler hasn't set done (which it shouldn't have) and
+ we now have the EOF, go ahead and set it so that we can stop
+ our context loops.
+ */
+ if (!handler->done && APR_STATUS_IS_EOF(*serf_status))
+ handler->done = TRUE;
+
+ return SVN_NO_ERROR;
}
- else
+
+ /* If we are supposed to parse the body as a server_error, then do
+ that now. */
+ if (handler->server_error != NULL)
{
- svn_error_t *err;
+ err = svn_ra_serf__handle_xml_parser(request, response,
+ &handler->server_error->parser,
+ scratch_pool);
+
+ /* If we do not receive an error or it is a non-transient error, return
+ immediately.
- err = ctx->response_handler(request,response, ctx->response_baton, pool);
+ APR_EOF will be returned when parsing is complete.
- if (err
- && (!SERF_BUCKET_READ_ERROR(err->apr_err)
- || APR_STATUS_IS_ECONNRESET(err->apr_err)))
+ 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))
{
- /* These errors are special cased in serf
- ### We hope no handler returns these by accident. */
*serf_status = err->apr_err;
svn_error_clear(err);
return SVN_NO_ERROR;
}
- return svn_error_trace(err);
+ /* Clear the EOF. We don't need it. */
+ svn_error_clear(err);
+
+ /* If the parsing is done, and we did not extract an error, then
+ simply toss everything, and anything else that might arrive.
+ The higher-level code will need to investigate HANDLER->SLINE,
+ as we have no further information for them. */
+ if (handler->done
+ && handler->server_error->error->apr_err == APR_SUCCESS)
+ {
+ svn_error_clear(handler->server_error->error);
+
+ /* Stop parsing for a server error. */
+ handler->server_error = NULL;
+
+ /* If anything arrives after this, then just discard it. */
+ handler->discard_body = TRUE;
+ }
+
+ *serf_status = APR_EOF;
+ return SVN_NO_ERROR;
+ }
+
+ /* Pass the body along to the registered response handler. */
+ err = handler->response_handler(request, response,
+ handler->response_baton,
+ scratch_pool);
+
+ if (err
+ && (!SERF_BUCKET_READ_ERROR(err->apr_err)
+ || APR_STATUS_IS_ECONNRESET(err->apr_err)
+ || APR_STATUS_IS_ECONNABORTED(err->apr_err)))
+ {
+ /* These errors are special cased in serf
+ ### We hope no handler returns these by accident. */
+ *serf_status = err->apr_err;
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
}
+
+ return svn_error_trace(err);
}
/* Implements serf_response_handler_t for handle_response. Storing
- errors in ctx->session->pending_error if appropriate. */
+ errors in handler->session->pending_error if appropriate. */
static apr_status_t
handle_response_cb(serf_request_t *request,
serf_bucket_t *response,
void *baton,
- apr_pool_t *pool)
+ apr_pool_t *scratch_pool)
{
- svn_ra_serf__handler_t *ctx = baton;
- svn_ra_serf__session_t *session = ctx->session;
+ svn_ra_serf__handler_t *handler = baton;
svn_error_t *err;
- apr_status_t serf_status = APR_SUCCESS;
+ apr_status_t inner_status;
+ apr_status_t outer_status;
- err = svn_error_trace(
- handle_response(request, response, ctx, &serf_status, pool));
+ err = svn_error_trace(handle_response(request, response,
+ handler, &inner_status,
+ scratch_pool));
- if (err || session->pending_error)
- {
- session->pending_error = svn_error_compose_create(session->pending_error,
- err);
+ /* Select the right status value to return. */
+ outer_status = save_error(handler->session, err);
+ if (!outer_status)
+ outer_status = inner_status;
- serf_status = session->pending_error->apr_err;
- }
+ /* Make sure the DONE flag is set properly. */
+ if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status))
+ handler->done = TRUE;
- return serf_status;
+ return outer_status;
}
-/* If the CTX->setup() callback is non-NULL, invoke it to carry out the
- majority of the serf_request_setup_t implementation. Otherwise, perform
- default setup, with special handling for HEAD requests, and finer-grained
- callbacks invoked (if non-NULL) to produce the request headers and
- body. */
+/* Perform basic request setup, with special handling for HEAD requests,
+ and finer-grained callbacks invoked (if non-NULL) to produce the request
+ headers and body. */
static svn_error_t *
setup_request(serf_request_t *request,
- svn_ra_serf__handler_t *ctx,
- serf_bucket_t **req_bkt,
- serf_response_acceptor_t *acceptor,
- void **acceptor_baton,
- serf_response_handler_t *handler,
- void **handler_baton,
- apr_pool_t *pool)
+ svn_ra_serf__handler_t *handler,
+ serf_bucket_t **req_bkt,
+ apr_pool_t *request_pool,
+ apr_pool_t *scratch_pool)
{
+ serf_bucket_t *body_bkt;
serf_bucket_t *headers_bkt;
+ const char *accept_encoding;
- *acceptor = svn_ra_serf__accept_response;
- *acceptor_baton = ctx->session;
-
- if (ctx->setup)
+ if (handler->body_delegate)
{
- svn_ra_serf__response_handler_t response_handler;
- void *response_baton;
-
- SVN_ERR(ctx->setup(request, ctx->setup_baton, req_bkt,
- acceptor, acceptor_baton,
- &response_handler, &response_baton,
- pool));
+ serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
- ctx->response_handler = response_handler;
- ctx->response_baton = response_baton;
+ /* ### should pass the scratch_pool */
+ SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton,
+ bkt_alloc, request_pool));
}
else
{
- serf_bucket_t *body_bkt;
- serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
-
- if (strcmp(ctx->method, "HEAD") == 0)
- {
- *acceptor = accept_head;
- }
+ body_bkt = NULL;
+ }
- if (ctx->body_delegate)
- {
- SVN_ERR(ctx->body_delegate(&body_bkt, ctx->body_delegate_baton,
- bkt_alloc, pool));
- }
- else
- {
- body_bkt = NULL;
- }
+ if (handler->custom_accept_encoding)
+ {
+ accept_encoding = NULL;
+ }
+ else if (handler->session->using_compression)
+ {
+ /* Accept gzip compression if enabled. */
+ accept_encoding = "gzip";
+ }
+ else
+ {
+ accept_encoding = NULL;
+ }
- SVN_ERR(svn_ra_serf__setup_serf_req(request, req_bkt, &headers_bkt,
- ctx->conn, ctx->method, ctx->path,
- body_bkt, ctx->body_type));
+ SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt,
+ handler->session, handler->method, handler->path,
+ body_bkt, handler->body_type, accept_encoding,
+ request_pool, scratch_pool));
- if (ctx->header_delegate)
- {
- SVN_ERR(ctx->header_delegate(headers_bkt, ctx->header_delegate_baton,
- pool));
- }
+ if (handler->header_delegate)
+ {
+ /* ### should pass the scratch_pool */
+ SVN_ERR(handler->header_delegate(headers_bkt,
+ handler->header_delegate_baton,
+ request_pool));
}
- *handler = handle_response_cb;
- *handler_baton = ctx;
-
return APR_SUCCESS;
}
@@ -2021,40 +2360,58 @@ setup_request_cb(serf_request_t *request,
serf_bucket_t **req_bkt,
serf_response_acceptor_t *acceptor,
void **acceptor_baton,
- serf_response_handler_t *handler,
- void **handler_baton,
+ serf_response_handler_t *s_handler,
+ void **s_handler_baton,
apr_pool_t *pool)
{
- svn_ra_serf__handler_t *ctx = setup_baton;
+ svn_ra_serf__handler_t *handler = setup_baton;
svn_error_t *err;
- err = setup_request(request, ctx,
- req_bkt,
- acceptor, acceptor_baton,
- handler, handler_baton,
- pool);
+ /* ### construct a scratch_pool? serf gives us a pool that will live for
+ ### the duration of the request. */
+ apr_pool_t *scratch_pool = pool;
- if (err)
- {
- ctx->session->pending_error
- = svn_error_compose_create(ctx->session->pending_error,
- err);
+ if (strcmp(handler->method, "HEAD") == 0)
+ *acceptor = accept_head;
+ else
+ *acceptor = accept_response;
+ *acceptor_baton = handler->session;
- return err->apr_err;
- }
+ *s_handler = handle_response_cb;
+ *s_handler_baton = handler;
- return APR_SUCCESS;
+ err = svn_error_trace(setup_request(request, handler, req_bkt,
+ pool /* request_pool */, scratch_pool));
+
+ return save_error(handler->session, err);
}
void
svn_ra_serf__request_create(svn_ra_serf__handler_t *handler)
{
+ SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL);
+
+ /* In case HANDLER is re-queued, reset the various transient fields.
+
+ ### prior to recent changes, HANDLER was constant. maybe we should
+ ### break out these processing fields, apart from the request
+ ### definition. */
+ handler->done = FALSE;
+ handler->server_error = NULL;
+ handler->sline.version = 0;
+ handler->location = NULL;
+ handler->reading_body = FALSE;
+ handler->discard_body = FALSE;
+
+ /* ### do we ever alter the >response_handler? */
+
/* ### do we need to hold onto the returned request object, or just
### not worry about it (the serf ctx will manage it). */
(void) serf_connection_request_create(handler->conn->conn,
setup_request_cb, handler);
}
+
svn_error_t *
svn_ra_serf__discover_vcc(const char **vcc_url,
svn_ra_serf__session_t *session,
@@ -2087,26 +2444,21 @@ svn_ra_serf__discover_vcc(const char **vcc_url,
apr_hash_t *props;
svn_error_t *err;
- err = svn_ra_serf__retrieve_props(&props, session, conn,
- path, SVN_INVALID_REVNUM,
- "0", base_props, pool, pool);
+ err = svn_ra_serf__fetch_node_props(&props, conn,
+ path, SVN_INVALID_REVNUM,
+ base_props, pool, pool);
if (! err)
{
- *vcc_url =
- svn_ra_serf__get_ver_prop(props, path,
- SVN_INVALID_REVNUM,
- "DAV:",
- "version-controlled-configuration");
+ apr_hash_t *ns_props;
- relative_path = svn_ra_serf__get_ver_prop(props, path,
- SVN_INVALID_REVNUM,
- SVN_DAV_PROP_NS_DAV,
- "baseline-relative-path");
+ ns_props = apr_hash_get(props, "DAV:", 4);
+ *vcc_url = svn_prop_get_value(ns_props,
+ "version-controlled-configuration");
- uuid = svn_ra_serf__get_ver_prop(props, path,
- SVN_INVALID_REVNUM,
- SVN_DAV_PROP_NS_DAV,
- "repository-uuid");
+ ns_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
+ relative_path = svn_prop_get_value(ns_props,
+ "baseline-relative-path");
+ uuid = svn_prop_get_value(ns_props, "repository-uuid");
break;
}
else
@@ -2114,7 +2466,7 @@ svn_ra_serf__discover_vcc(const char **vcc_url,
if ((err->apr_err != SVN_ERR_FS_NOT_FOUND) &&
(err->apr_err != SVN_ERR_RA_DAV_FORBIDDEN))
{
- return err; /* found a _real_ error */
+ return svn_error_trace(err); /* found a _real_ error */
}
else
{
@@ -2160,7 +2512,8 @@ svn_ra_serf__discover_vcc(const char **vcc_url,
/* Now recreate the root_url. */
session->repos_root = session->session_url;
- session->repos_root.path = apr_pstrdup(session->pool, url_buf->data);
+ session->repos_root.path =
+ (char *)svn_fspath__canonicalize(url_buf->data, session->pool);
session->repos_root_str =
svn_urlpath__canonicalize(apr_uri_unparse(session->pool,
&session->repos_root, 0),
@@ -2203,15 +2556,8 @@ svn_ra_serf__get_relative_path(const char **rel_path,
decoded_root = svn_path_uri_decode(session->repos_root.path, pool);
decoded_orig = svn_path_uri_decode(orig_path, pool);
- if (strcmp(decoded_root, decoded_orig) == 0)
- {
- *rel_path = "";
- }
- else
- {
- *rel_path = svn_urlpath__is_child(decoded_root, decoded_orig, pool);
- SVN_ERR_ASSERT(*rel_path != NULL);
- }
+ *rel_path = svn_urlpath__skip_ancestor(decoded_root, decoded_orig);
+ SVN_ERR_ASSERT(*rel_path != NULL);
return SVN_NO_ERROR;
}
@@ -2234,17 +2580,17 @@ svn_ra_serf__report_resource(const char **report_target,
}
svn_error_t *
-svn_ra_serf__error_on_status(int status_code,
+svn_ra_serf__error_on_status(serf_status_line sline,
const char *path,
const char *location)
{
- switch(status_code)
+ switch(sline.code)
{
case 301:
case 302:
case 307:
return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL,
- (status_code == 301)
+ (sline.code == 301)
? _("Repository moved permanently to '%s';"
" please relocate")
: _("Repository moved temporarily to '%s';"
@@ -2259,7 +2605,216 @@ svn_ra_serf__error_on_status(int status_code,
case 423:
return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL,
_("'%s': no lock token available"), path);
+
+ case 411:
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("DAV request failed: 411 Content length required. The "
+ "server or an intermediate proxy does not accept "
+ "chunked encoding. Try setting 'http-chunked-requests' "
+ "to 'auto' or 'no' in your client configuration."));
+ case 501:
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("The requested feature is not supported by "
+ "'%s'"), path);
}
+ if (sline.code >= 300)
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("Unexpected HTTP status %d '%s' on '%s'\n"),
+ sline.code, sline.reason, path);
+
return SVN_NO_ERROR;
}
+
+svn_error_t *
+svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session,
+ svn_delta_shim_callbacks_t *callbacks)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ session->shim_callbacks = callbacks;
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to Expat's XML_StartElementHandler */
+static void
+expat_start(void *userData, const char *raw_name, const char **attrs)
+{
+ struct expat_ctx_t *ectx = userData;
+
+ if (ectx->inner_error != NULL)
+ return;
+
+ ectx->inner_error = svn_error_trace(
+ svn_ra_serf__xml_cb_start(ectx->xmlctx,
+ raw_name, attrs));
+
+#ifdef EXPAT_HAS_STOPPARSER
+ if (ectx->inner_error)
+ (void) XML_StopParser(ectx->parser, 0 /* resumable */);
+#endif
+}
+
+
+/* Conforms to Expat's XML_EndElementHandler */
+static void
+expat_end(void *userData, const char *raw_name)
+{
+ struct expat_ctx_t *ectx = userData;
+
+ if (ectx->inner_error != NULL)
+ return;
+
+ ectx->inner_error = svn_error_trace(
+ svn_ra_serf__xml_cb_end(ectx->xmlctx, raw_name));
+
+#ifdef EXPAT_HAS_STOPPARSER
+ if (ectx->inner_error)
+ (void) XML_StopParser(ectx->parser, 0 /* resumable */);
+#endif
+}
+
+
+/* Conforms to Expat's XML_CharacterDataHandler */
+static void
+expat_cdata(void *userData, const char *data, int len)
+{
+ struct expat_ctx_t *ectx = userData;
+
+ if (ectx->inner_error != NULL)
+ return;
+
+ ectx->inner_error = svn_error_trace(
+ svn_ra_serf__xml_cb_cdata(ectx->xmlctx, data, len));
+
+#ifdef EXPAT_HAS_STOPPARSER
+ if (ectx->inner_error)
+ (void) XML_StopParser(ectx->parser, 0 /* resumable */);
+#endif
+}
+
+
+/* Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+expat_response_handler(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct expat_ctx_t *ectx = baton;
+
+ if (!ectx->parser)
+ {
+ ectx->parser = XML_ParserCreate(NULL);
+ apr_pool_cleanup_register(ectx->cleanup_pool, &ectx->parser,
+ xml_parser_cleanup, apr_pool_cleanup_null);
+ XML_SetUserData(ectx->parser, ectx);
+ XML_SetElementHandler(ectx->parser, expat_start, expat_end);
+ XML_SetCharacterDataHandler(ectx->parser, expat_cdata);
+ }
+
+ /* ### TODO: sline.code < 200 should really be handled by the core */
+ if ((ectx->handler->sline.code < 200) || (ectx->handler->sline.code >= 300))
+ {
+ /* By deferring to expect_empty_body(), it will make a choice on
+ how to handle the body. Whatever the decision, the core handler
+ will take over, and we will not be called again. */
+ return svn_error_trace(svn_ra_serf__expect_empty_body(
+ request, response, ectx->handler,
+ scratch_pool));
+ }
+
+ while (1)
+ {
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+ int expat_status;
+
+ status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return svn_ra_serf__wrap_err(status, NULL);
+
+#if 0
+ /* ### move restart/skip into the core handler */
+ ectx->handler->read_size += len;
+#endif
+
+ /* ### move PAUSED behavior to a new response handler that can feed
+ ### an inner handler, or can pause for a while. */
+
+ /* ### should we have an IGNORE_ERRORS flag like the v1 parser? */
+
+ expat_status = XML_Parse(ectx->parser, data, (int)len, 0 /* isFinal */);
+
+ /* We need to check INNER_ERROR first. This is an error from the
+ callbacks that has been "dropped off" for us to retrieve. On
+ current Expat parsers, we stop the parser when an error occurs,
+ so we want to ignore EXPAT_STATUS (which reports the stoppage).
+
+ If an error is not present, THEN we go ahead and look for parsing
+ errors. */
+ if (ectx->inner_error)
+ {
+ apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
+ xml_parser_cleanup);
+ return svn_error_trace(ectx->inner_error);
+ }
+ if (expat_status == XML_STATUS_ERROR)
+ return svn_error_createf(SVN_ERR_XML_MALFORMED,
+ ectx->inner_error,
+ _("The %s response contains invalid XML"
+ " (%d %s)"),
+ ectx->handler->method,
+ ectx->handler->sline.code,
+ ectx->handler->sline.reason);
+
+ /* The parsing went fine. What has the bucket told us? */
+
+ if (APR_STATUS_IS_EOF(status))
+ {
+ /* Tell expat we've reached the end of the content. Ignore the
+ return status. We just don't care. */
+ (void) XML_Parse(ectx->parser, NULL, 0, 1 /* isFinal */);
+
+ svn_ra_serf__xml_context_destroy(ectx->xmlctx);
+ apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
+ xml_parser_cleanup);
+
+ /* ### should check XMLCTX to see if it has returned to the
+ ### INITIAL state. we may have ended early... */
+ }
+
+ if (status && !SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+ }
+
+ /* NOTREACHED */
+}
+
+
+svn_ra_serf__handler_t *
+svn_ra_serf__create_expat_handler(svn_ra_serf__xml_context_t *xmlctx,
+ apr_pool_t *result_pool)
+{
+ svn_ra_serf__handler_t *handler;
+ struct expat_ctx_t *ectx;
+
+ ectx = apr_pcalloc(result_pool, sizeof(*ectx));
+ ectx->xmlctx = xmlctx;
+ ectx->parser = NULL;
+ ectx->cleanup_pool = result_pool;
+
+
+ handler = apr_pcalloc(result_pool, sizeof(*handler));
+ handler->handler_pool = result_pool;
+ handler->response_handler = expat_response_handler;
+ handler->response_baton = ectx;
+
+ ectx->handler = handler;
+
+ return handler;
+}
diff --git a/subversion/libsvn_ra_serf/util_error.c b/subversion/libsvn_ra_serf/util_error.c
new file mode 100644
index 0000000..da66091
--- /dev/null
+++ b/subversion/libsvn_ra_serf/util_error.c
@@ -0,0 +1,100 @@
+/*
+ * util_error.c : serf utility routines for wrapping serf status codes
+ *
+ * ====================================================================
+ * 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 <serf.h>
+
+#include "svn_utf.h"
+#include "private/svn_error_private.h"
+
+#include "ra_serf.h"
+
+/*
+ * Undefine the helpers for creating errors.
+ *
+ * *NOTE*: Any use of these functions in any other function may need
+ * to call svn_error__locate() because the macro that would otherwise
+ * do this is being undefined and the filename and line number will
+ * not be properly set in the static error_file and error_line
+ * variables.
+ */
+#undef svn_error_create
+#undef svn_error_createf
+#undef svn_error_quick_wrap
+#undef svn_error_wrap_apr
+#undef svn_ra_serf__wrap_err
+
+svn_error_t *
+svn_ra_serf__wrap_err(apr_status_t status,
+ const char *fmt,
+ ...)
+{
+ const char *serf_err_msg = serf_error_string(status);
+ svn_error_t *err;
+ va_list ap;
+
+ err = svn_error_create(status, NULL, NULL);
+
+ if (serf_err_msg || fmt)
+ {
+ const char *msg;
+ const char *err_msg;
+ char errbuf[255]; /* Buffer for APR error message. */
+
+ if (serf_err_msg)
+ {
+ err_msg = serf_err_msg;
+ }
+ else
+ {
+ svn_error_t *utf8_err;
+
+ /* Grab the APR error message. */
+ apr_strerror(status, errbuf, sizeof(errbuf));
+ utf8_err = svn_utf_cstring_to_utf8(&err_msg, errbuf, err->pool);
+ if (utf8_err)
+ err_msg = NULL;
+ svn_error_clear(utf8_err);
+ }
+
+ /* Append it to the formatted message. */
+ if (fmt)
+ {
+ va_start(ap, fmt);
+ msg = apr_pvsprintf(err->pool, fmt, ap);
+ va_end(ap);
+ }
+ else
+ {
+ msg = "ra_serf";
+ }
+ if (err_msg)
+ {
+ err->message = apr_pstrcat(err->pool, msg, ": ", err_msg, NULL);
+ }
+ else
+ {
+ err->message = msg;
+ }
+ }
+
+ return err;
+}
diff --git a/subversion/libsvn_ra_serf/xml.c b/subversion/libsvn_ra_serf/xml.c
index ad8a1a1..a95eacc 100644
--- a/subversion/libsvn_ra_serf/xml.c
+++ b/subversion/libsvn_ra_serf/xml.c
@@ -24,11 +24,9 @@
#include <apr_uri.h>
-
-#include <expat.h>
-
#include <serf.h>
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_ra.h"
#include "svn_dav.h"
@@ -37,57 +35,158 @@
#include "svn_config.h"
#include "svn_delta.h"
#include "svn_path.h"
+
#include "svn_private_config.h"
+#include "private/svn_string_private.h"
#include "ra_serf.h"
-void
-svn_ra_serf__define_ns(svn_ra_serf__ns_t **ns_list,
- const char **attrs,
- apr_pool_t *pool)
+struct svn_ra_serf__xml_context_t {
+ /* Current state information. */
+ svn_ra_serf__xml_estate_t *current;
+
+ /* If WAITING.NAMESPACE != NULL, wait for NAMESPACE:NAME element to be
+ closed before looking for transitions from CURRENT->STATE. */
+ svn_ra_serf__dav_props_t waiting;
+
+ /* The transition table. */
+ const svn_ra_serf__xml_transition_t *ttable;
+
+ /* The callback information. */
+ svn_ra_serf__xml_opened_t opened_cb;
+ svn_ra_serf__xml_closed_t closed_cb;
+ svn_ra_serf__xml_cdata_t cdata_cb;
+ void *baton;
+
+ /* Linked list of free states. */
+ svn_ra_serf__xml_estate_t *free_states;
+
+#ifdef SVN_DEBUG
+ /* Used to verify we are not re-entering a callback, specifically to
+ ensure SCRATCH_POOL is not cleared while an outer callback is
+ trying to use it. */
+ svn_boolean_t within_callback;
+#define START_CALLBACK(xmlctx) \
+ do { \
+ svn_ra_serf__xml_context_t *xmlctx__tmp = (xmlctx); \
+ SVN_ERR_ASSERT(!xmlctx__tmp->within_callback); \
+ xmlctx__tmp->within_callback = TRUE; \
+ } while (0)
+#define END_CALLBACK(xmlctx) ((xmlctx)->within_callback = FALSE)
+#else
+#define START_CALLBACK(xmlctx) /* empty */
+#define END_CALLBACK(xmlctx) /* empty */
+#endif /* SVN_DEBUG */
+
+ apr_pool_t *scratch_pool;
+
+};
+
+struct svn_ra_serf__xml_estate_t {
+ /* The current state value. */
+ int state;
+
+ /* The xml tag that opened this state. Waiting for the tag to close. */
+ svn_ra_serf__dav_props_t tag;
+
+ /* Should the CLOSED_CB function be called for custom processing when
+ this tag is closed? */
+ svn_boolean_t custom_close;
+
+ /* A pool may be constructed for this state. */
+ apr_pool_t *state_pool;
+
+ /* The namespaces extent for this state/element. This will start with
+ the parent's NS_LIST, and we will push new namespaces into our
+ local list. The parent will be unaffected by our locally-scoped data. */
+ svn_ra_serf__ns_t *ns_list;
+
+ /* Any collected attribute values. char * -> svn_string_t *. May be NULL
+ if no attributes have been collected. */
+ apr_hash_t *attrs;
+
+ /* Any collected cdata. May be NULL if no cdata is being collected. */
+ svn_stringbuf_t *cdata;
+
+ /* Previous/outer state. */
+ svn_ra_serf__xml_estate_t *prev;
+
+};
+
+
+static void
+define_namespaces(svn_ra_serf__ns_t **ns_list,
+ const char *const *attrs,
+ apr_pool_t *(*get_pool)(void *baton),
+ void *baton)
{
- const char **tmp_attrs = attrs;
+ const char *const *tmp_attrs = attrs;
- while (*tmp_attrs)
+ for (tmp_attrs = attrs; *tmp_attrs != NULL; tmp_attrs += 2)
{
if (strncmp(*tmp_attrs, "xmlns", 5) == 0)
{
- svn_ra_serf__ns_t *new_ns, *cur_ns;
- int found = 0;
+ const svn_ra_serf__ns_t *cur_ns;
+ svn_boolean_t found = FALSE;
+ const char *prefix;
+
+ /* The empty prefix, or a named-prefix. */
+ if (tmp_attrs[0][5] == ':')
+ prefix = &tmp_attrs[0][6];
+ else
+ prefix = "";
/* Have we already defined this ns previously? */
for (cur_ns = *ns_list; cur_ns; cur_ns = cur_ns->next)
{
- if (strcmp(cur_ns->namespace, tmp_attrs[0] + 6) == 0)
+ if (strcmp(cur_ns->namespace, prefix) == 0)
{
- found = 1;
+ found = TRUE;
break;
}
}
if (!found)
{
+ apr_pool_t *pool;
+ svn_ra_serf__ns_t *new_ns;
+
+ if (get_pool)
+ pool = get_pool(baton);
+ else
+ pool = baton;
new_ns = apr_palloc(pool, sizeof(*new_ns));
- new_ns->namespace = apr_pstrdup(pool, tmp_attrs[0] + 6);
+ new_ns->namespace = apr_pstrdup(pool, prefix);
new_ns->url = apr_pstrdup(pool, tmp_attrs[1]);
+ /* Push into the front of NS_LIST. Parent states will point
+ to later in the chain, so will be unaffected by
+ shadowing/other namespaces pushed onto NS_LIST. */
new_ns->next = *ns_list;
-
*ns_list = new_ns;
}
}
- tmp_attrs += 2;
}
}
+
+void
+svn_ra_serf__define_ns(svn_ra_serf__ns_t **ns_list,
+ const char *const *attrs,
+ apr_pool_t *result_pool)
+{
+ define_namespaces(ns_list, attrs, NULL /* get_pool */, result_pool);
+}
+
+
/*
* Look up NAME in the NS_LIST list for previously declared namespace
* definitions and return a DAV_PROPS_T-tuple that has values.
*/
void
svn_ra_serf__expand_ns(svn_ra_serf__dav_props_t *returned_prop_name,
- svn_ra_serf__ns_t *ns_list,
+ const svn_ra_serf__ns_t *ns_list,
const char *name)
{
const char *colon;
@@ -95,7 +194,7 @@ svn_ra_serf__expand_ns(svn_ra_serf__dav_props_t *returned_prop_name,
colon = strchr(name, ':');
if (colon)
{
- svn_ra_serf__ns_t *ns;
+ const svn_ra_serf__ns_t *ns;
for (ns = ns_list; ns; ns = ns->next)
{
@@ -107,42 +206,28 @@ svn_ra_serf__expand_ns(svn_ra_serf__dav_props_t *returned_prop_name,
}
}
}
-
- /* If there is no prefix, or if the prefix is not found, then the
- name is NOT within a namespace. */
- returned_prop_name->namespace = "";
- returned_prop_name->name = name;
-}
-
-void
-svn_ra_serf__expand_string(const char **cur, apr_size_t *cur_len,
- const char *new, apr_size_t new_len,
- apr_pool_t *pool)
-{
- if (!*cur)
- {
- *cur = apr_pstrmemdup(pool, new, new_len);
- *cur_len = new_len;
- }
else
{
- char *new_cur;
-
- /* append the data we received before. */
- new_cur = apr_palloc(pool, *cur_len + new_len + 1);
+ const svn_ra_serf__ns_t *ns;
- memcpy(new_cur, *cur, *cur_len);
- memcpy(new_cur + *cur_len, new, new_len);
-
- /* NULL-term our new string */
- new_cur[*cur_len + new_len] = '\0';
-
- /* update our length */
- *cur_len += new_len;
- *cur = new_cur;
+ for (ns = ns_list; ns; ns = ns->next)
+ {
+ if (! ns->namespace[0])
+ {
+ returned_prop_name->namespace = ns->url;
+ returned_prop_name->name = name;
+ return;
+ }
+ }
}
+
+ /* If the prefix is not found, then the name is NOT within a
+ namespace. */
+ returned_prop_name->namespace = "";
+ returned_prop_name->name = name;
}
+
#define XML_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
void
@@ -340,3 +425,409 @@ void svn_ra_serf__xml_pop_state(svn_ra_serf__xml_parser_t *parser)
cur_state->prev = parser->free_state;
parser->free_state = cur_state;
}
+
+
+/* Return a pool for XES to use for self-alloc (and other specifics). */
+static apr_pool_t *
+xes_pool(const svn_ra_serf__xml_estate_t *xes)
+{
+ /* Move up through parent states looking for one with a pool. This
+ will always terminate since the initial state has a pool. */
+ while (xes->state_pool == NULL)
+ xes = xes->prev;
+ return xes->state_pool;
+}
+
+
+static void
+ensure_pool(svn_ra_serf__xml_estate_t *xes)
+{
+ if (xes->state_pool == NULL)
+ xes->state_pool = svn_pool_create(xes_pool(xes));
+}
+
+
+/* This callback is used by define_namespaces() to wait until a pool is
+ required before constructing it. */
+static apr_pool_t *
+lazy_create_pool(void *baton)
+{
+ svn_ra_serf__xml_estate_t *xes = baton;
+
+ ensure_pool(xes);
+ return xes->state_pool;
+}
+
+void
+svn_ra_serf__xml_context_destroy(
+ svn_ra_serf__xml_context_t *xmlctx)
+{
+ svn_pool_destroy(xmlctx->scratch_pool);
+}
+
+svn_ra_serf__xml_context_t *
+svn_ra_serf__xml_context_create(
+ const svn_ra_serf__xml_transition_t *ttable,
+ svn_ra_serf__xml_opened_t opened_cb,
+ svn_ra_serf__xml_closed_t closed_cb,
+ svn_ra_serf__xml_cdata_t cdata_cb,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ svn_ra_serf__xml_context_t *xmlctx;
+ svn_ra_serf__xml_estate_t *xes;
+
+ xmlctx = apr_pcalloc(result_pool, sizeof(*xmlctx));
+ xmlctx->ttable = ttable;
+ xmlctx->opened_cb = opened_cb;
+ xmlctx->closed_cb = closed_cb;
+ xmlctx->cdata_cb = cdata_cb;
+ xmlctx->baton = baton;
+ xmlctx->scratch_pool = svn_pool_create(result_pool);
+
+ xes = apr_pcalloc(result_pool, sizeof(*xes));
+ /* XES->STATE == 0 */
+
+ /* Child states may use this pool to allocate themselves. If a child
+ needs to collect information, then it will construct a subpool and
+ will use that to allocate itself and its collected data. */
+ xes->state_pool = result_pool;
+
+ xmlctx->current = xes;
+
+ return xmlctx;
+}
+
+
+apr_hash_t *
+svn_ra_serf__xml_gather_since(svn_ra_serf__xml_estate_t *xes,
+ int stop_state)
+{
+ apr_hash_t *data;
+ apr_pool_t *pool;
+
+ ensure_pool(xes);
+ pool = xes->state_pool;
+
+ data = apr_hash_make(pool);
+
+ for (; xes != NULL; xes = xes->prev)
+ {
+ if (xes->attrs != NULL)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, xes->attrs); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ void *val;
+
+ /* Parent name/value lifetimes are at least as long as POOL. */
+ apr_hash_this(hi, &key, &klen, &val);
+ apr_hash_set(data, key, klen, val);
+ }
+ }
+
+ if (xes->state == stop_state)
+ break;
+ }
+
+ return data;
+}
+
+
+void
+svn_ra_serf__xml_note(svn_ra_serf__xml_estate_t *xes,
+ int state,
+ const char *name,
+ const char *value)
+{
+ svn_ra_serf__xml_estate_t *scan;
+
+ for (scan = xes; scan != NULL && scan->state != state; scan = scan->prev)
+ /* pass */ ;
+
+ SVN_ERR_ASSERT_NO_RETURN(scan != NULL);
+
+ /* Make sure the target state has a pool. */
+ ensure_pool(scan);
+
+ /* ... and attribute storage. */
+ if (scan->attrs == NULL)
+ scan->attrs = apr_hash_make(scan->state_pool);
+
+ /* In all likelihood, NAME is a string constant. But we can't really
+ be sure. And it isn't like we're storing a billion of these into
+ the state pool. */
+ svn_hash_sets(scan->attrs,
+ apr_pstrdup(scan->state_pool, name),
+ apr_pstrdup(scan->state_pool, value));
+}
+
+
+apr_pool_t *
+svn_ra_serf__xml_state_pool(svn_ra_serf__xml_estate_t *xes)
+{
+ /* If they asked for a pool, then ensure that we have one to provide. */
+ ensure_pool(xes);
+
+ return xes->state_pool;
+}
+
+
+svn_error_t *
+svn_ra_serf__xml_cb_start(svn_ra_serf__xml_context_t *xmlctx,
+ const char *raw_name,
+ const char *const *attrs)
+{
+ svn_ra_serf__xml_estate_t *current = xmlctx->current;
+ svn_ra_serf__dav_props_t elemname;
+ const svn_ra_serf__xml_transition_t *scan;
+ apr_pool_t *new_pool;
+ svn_ra_serf__xml_estate_t *new_xes;
+
+ /* If we're waiting for an element to close, then just ignore all
+ other element-opens. */
+ if (xmlctx->waiting.namespace != NULL)
+ return SVN_NO_ERROR;
+
+ /* Look for xmlns: attributes. Lazily create the state pool if any
+ were found. */
+ define_namespaces(&current->ns_list, attrs, lazy_create_pool, current);
+
+ svn_ra_serf__expand_ns(&elemname, current->ns_list, raw_name);
+
+ for (scan = xmlctx->ttable; scan->ns != NULL; ++scan)
+ {
+ if (scan->from_state != current->state)
+ continue;
+
+ /* Wildcard tag match. */
+ if (*scan->name == '*')
+ break;
+
+ /* Found a specific transition. */
+ if (strcmp(elemname.name, scan->name) == 0
+ && strcmp(elemname.namespace, scan->ns) == 0)
+ break;
+ }
+ if (scan->ns == NULL)
+ {
+ if (current->state == 0)
+ {
+ return svn_error_createf(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("XML Parsing failed: Unexpected root element '%s'"),
+ elemname.name);
+ }
+
+ xmlctx->waiting = elemname;
+ /* ### return? */
+ return SVN_NO_ERROR;
+ }
+
+ /* We should not be told to collect cdata if the closed_cb will not
+ be called. */
+ SVN_ERR_ASSERT(!scan->collect_cdata || scan->custom_close);
+
+ /* Found a transition. Make it happen. */
+
+ /* ### todo. push state */
+
+ /* ### how to use free states? */
+ /* This state should be allocated in the extent pool. If we will be
+ collecting information for this state, then construct a subpool.
+
+ ### potentially optimize away the subpool if none of the
+ ### attributes are present. subpools are cheap, tho... */
+ new_pool = xes_pool(current);
+ if (scan->collect_cdata || scan->collect_attrs[0])
+ {
+ new_pool = svn_pool_create(new_pool);
+
+ /* Prep the new state. */
+ new_xes = apr_pcalloc(new_pool, sizeof(*new_xes));
+ new_xes->state_pool = new_pool;
+
+ /* If we're supposed to collect cdata, then set up a buffer for
+ this. The existence of this buffer will instruct our cdata
+ callback to collect the cdata. */
+ if (scan->collect_cdata)
+ new_xes->cdata = svn_stringbuf_create_empty(new_pool);
+
+ if (scan->collect_attrs[0] != NULL)
+ {
+ const char *const *saveattr = &scan->collect_attrs[0];
+
+ new_xes->attrs = apr_hash_make(new_pool);
+ for (; *saveattr != NULL; ++saveattr)
+ {
+ const char *name;
+ const char *value;
+
+ if (**saveattr == '?')
+ {
+ name = *saveattr + 1;
+ value = svn_xml_get_attr_value(name, attrs);
+ }
+ else
+ {
+ name = *saveattr;
+ value = svn_xml_get_attr_value(name, attrs);
+ if (value == NULL)
+ return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND,
+ NULL,
+ _("Missing XML attribute: '%s'"),
+ name);
+ }
+
+ if (value)
+ svn_hash_sets(new_xes->attrs, name,
+ apr_pstrdup(new_pool, value));
+ }
+ }
+ }
+ else
+ {
+ /* Prep the new state. */
+ new_xes = apr_pcalloc(new_pool, sizeof(*new_xes));
+ /* STATE_POOL remains NULL. */
+ }
+
+ /* Some basic copies to set up the new estate. */
+ new_xes->state = scan->to_state;
+ new_xes->tag.name = apr_pstrdup(new_pool, elemname.name);
+ new_xes->tag.namespace = apr_pstrdup(new_pool, elemname.namespace);
+ new_xes->custom_close = scan->custom_close;
+
+ /* Start with the parent's namespace set. */
+ new_xes->ns_list = current->ns_list;
+
+ /* The new state is prepared. Make it current. */
+ new_xes->prev = current;
+ xmlctx->current = new_xes;
+
+ if (xmlctx->opened_cb)
+ {
+ START_CALLBACK(xmlctx);
+ SVN_ERR(xmlctx->opened_cb(new_xes, xmlctx->baton,
+ new_xes->state, &new_xes->tag,
+ xmlctx->scratch_pool));
+ END_CALLBACK(xmlctx);
+ svn_pool_clear(xmlctx->scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__xml_cb_end(svn_ra_serf__xml_context_t *xmlctx,
+ const char *raw_name)
+{
+ svn_ra_serf__xml_estate_t *xes = xmlctx->current;
+ svn_ra_serf__dav_props_t elemname;
+
+ svn_ra_serf__expand_ns(&elemname, xes->ns_list, raw_name);
+
+ if (xmlctx->waiting.namespace != NULL)
+ {
+ /* If this element is not the closer, then keep waiting... */
+ if (strcmp(elemname.name, xmlctx->waiting.name) != 0
+ || strcmp(elemname.namespace, xmlctx->waiting.namespace) != 0)
+ return SVN_NO_ERROR;
+
+ /* Found it. Stop waiting, and go back for more. */
+ xmlctx->waiting.namespace = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* We should be looking at the same tag that opened the current state.
+
+ Unknown elements are simply skipped, so we wouldn't reach this check.
+
+ Known elements push a new state for a given tag. Some other elemname
+ would imply closing an ancestor tag (where did ours go?) or a spurious
+ tag closure. */
+ if (strcmp(elemname.name, xes->tag.name) != 0
+ || strcmp(elemname.namespace, xes->tag.namespace) != 0)
+ return svn_error_create(SVN_ERR_XML_MALFORMED, NULL,
+ _("The response contains invalid XML"));
+
+ if (xes->custom_close)
+ {
+ const svn_string_t *cdata;
+
+ if (xes->cdata)
+ {
+ cdata = svn_stringbuf__morph_into_string(xes->cdata);
+#ifdef SVN_DEBUG
+ /* We might toss the pool holding this structure, but it could also
+ be within a parent pool. In any case, for safety's sake, disable
+ the stringbuf against future Badness. */
+ xes->cdata->pool = NULL;
+#endif
+ }
+ else
+ cdata = NULL;
+
+ START_CALLBACK(xmlctx);
+ SVN_ERR(xmlctx->closed_cb(xes, xmlctx->baton, xes->state,
+ cdata, xes->attrs,
+ xmlctx->scratch_pool));
+ END_CALLBACK(xmlctx);
+ svn_pool_clear(xmlctx->scratch_pool);
+ }
+
+ /* Pop the state. */
+ xmlctx->current = xes->prev;
+
+ /* ### not everything should go on the free state list. XES may go
+ ### away with the state pool. */
+ xes->prev = xmlctx->free_states;
+ xmlctx->free_states = xes;
+
+ /* If there is a STATE_POOL, then toss it. This will get rid of as much
+ memory as possible. Potentially the XES (if we didn't create a pool
+ right away, then XES may be in a parent pool). */
+ if (xes->state_pool)
+ svn_pool_destroy(xes->state_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__xml_cb_cdata(svn_ra_serf__xml_context_t *xmlctx,
+ const char *data,
+ apr_size_t len)
+{
+ /* If we are waiting for a closing tag, then we are uninterested in
+ the cdata. Just return. */
+ if (xmlctx->waiting.namespace != NULL)
+ return SVN_NO_ERROR;
+
+ /* If the current state is collecting cdata, then copy the cdata. */
+ if (xmlctx->current->cdata != NULL)
+ {
+ svn_stringbuf_appendbytes(xmlctx->current->cdata, data, len);
+ }
+ /* ... else if a CDATA_CB has been supplied, then invoke it for
+ all states. */
+ else if (xmlctx->cdata_cb != NULL)
+ {
+ START_CALLBACK(xmlctx);
+ SVN_ERR(xmlctx->cdata_cb(xmlctx->current,
+ xmlctx->baton,
+ xmlctx->current->state,
+ data, len,
+ xmlctx->scratch_pool));
+ END_CALLBACK(xmlctx);
+ svn_pool_clear(xmlctx->scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+