summaryrefslogtreecommitdiff
path: root/subversion/mod_dav_svn/reports
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/mod_dav_svn/reports')
-rw-r--r--subversion/mod_dav_svn/reports/deleted-rev.c3
-rw-r--r--subversion/mod_dav_svn/reports/file-revs.c9
-rw-r--r--subversion/mod_dav_svn/reports/get-location-segments.c34
-rw-r--r--subversion/mod_dav_svn/reports/get-locations.c3
-rw-r--r--subversion/mod_dav_svn/reports/inherited-props.c236
-rw-r--r--subversion/mod_dav_svn/reports/log.c6
-rw-r--r--subversion/mod_dav_svn/reports/mergeinfo.c3
-rw-r--r--subversion/mod_dav_svn/reports/replay.c62
-rw-r--r--subversion/mod_dav_svn/reports/update.c398
9 files changed, 601 insertions, 153 deletions
diff --git a/subversion/mod_dav_svn/reports/deleted-rev.c b/subversion/mod_dav_svn/reports/deleted-rev.c
index dc2bccd..66d0192 100644
--- a/subversion/mod_dav_svn/reports/deleted-rev.c
+++ b/subversion/mod_dav_svn/reports/deleted-rev.c
@@ -56,6 +56,9 @@ dav_svn__get_deleted_rev_report(const dav_resource *resource,
dav_error *derr = NULL;
/* Sanity check. */
+ if (!resource->info->repos_path)
+ return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0,
+ "The request does not specify a repository path");
ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
if (ns == -1)
return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0,
diff --git a/subversion/mod_dav_svn/reports/file-revs.c b/subversion/mod_dav_svn/reports/file-revs.c
index f8a15c1..108fd7f 100644
--- a/subversion/mod_dav_svn/reports/file-revs.c
+++ b/subversion/mod_dav_svn/reports/file-revs.c
@@ -52,6 +52,9 @@ struct file_rev_baton {
/* SVNDIFF version to use when sending to client. */
int svndiff_version;
+ /* Compression level to use for SVNDIFF. */
+ int compression_level;
+
/* Used by the delta iwndow handler. */
svn_txdelta_window_handler_t window_handler;
void *window_baton;
@@ -208,7 +211,7 @@ file_rev_handler(void *baton,
pool);
svn_txdelta_to_svndiff3(&frb->window_handler, &frb->window_baton,
base64_stream, frb->svndiff_version,
- dav_svn__get_compression_level(), pool);
+ frb->compression_level, pool);
*window_handler = delta_window_handler;
*window_baton = frb;
/* Start the txdelta element wich will be terminated by the window
@@ -251,6 +254,9 @@ dav_svn__file_revs_report(const dav_resource *resource,
arb.repos = resource->info->repos;
/* Sanity check. */
+ if (!resource->info->repos_path)
+ return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0,
+ "The request does not specify a repository path");
ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
/* ### This is done on other places, but the document element is
in this namespace, so is this necessary at all? */
@@ -306,6 +312,7 @@ dav_svn__file_revs_report(const dav_resource *resource,
frb.output = output;
frb.needs_header = TRUE;
frb.svndiff_version = resource->info->svndiff_version;
+ frb.compression_level = dav_svn__get_compression_level(resource->info->r);
/* file_rev_handler will send header first time it is called. */
diff --git a/subversion/mod_dav_svn/reports/get-location-segments.c b/subversion/mod_dav_svn/reports/get-location-segments.c
index bba0cc2..d3e91e4 100644
--- a/subversion/mod_dav_svn/reports/get-location-segments.c
+++ b/subversion/mod_dav_svn/reports/get-location-segments.c
@@ -123,6 +123,9 @@ dav_svn__get_location_segments_report(const dav_resource *resource,
struct location_segment_baton location_segment_baton;
/* Sanity check. */
+ if (!resource->info->repos_path)
+ return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0,
+ "The request does not specify a repository path");
ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
if (ns == -1)
{
@@ -178,17 +181,36 @@ dav_svn__get_location_segments_report(const dav_resource *resource,
"Not all parameters passed.",
SVN_DAV_ERROR_NAMESPACE,
SVN_DAV_ERROR_TAG);
- if (SVN_IS_VALID_REVNUM(start_rev)
- && SVN_IS_VALID_REVNUM(end_rev)
- && (end_rev > start_rev))
+
+ /* No START_REV or PEG_REVISION? We'll use HEAD. */
+ if (!SVN_IS_VALID_REVNUM(start_rev) || !SVN_IS_VALID_REVNUM(peg_revision))
+ {
+ svn_revnum_t youngest;
+
+ serr = svn_fs_youngest_rev(&youngest, resource->info->repos->fs,
+ resource->pool);
+ if (serr != NULL)
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Could not determine youngest revision",
+ resource->pool);
+
+ if (!SVN_IS_VALID_REVNUM(start_rev))
+ start_rev = youngest;
+ if (!SVN_IS_VALID_REVNUM(peg_revision))
+ peg_revision = youngest;
+ }
+
+ /* No END_REV? We'll use 0. */
+ if (!SVN_IS_VALID_REVNUM(end_rev))
+ end_rev = 0;
+
+ if (end_rev > start_rev)
return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0,
"End revision must not be younger than "
"start revision",
SVN_DAV_ERROR_NAMESPACE,
SVN_DAV_ERROR_TAG);
- if (SVN_IS_VALID_REVNUM(peg_revision)
- && SVN_IS_VALID_REVNUM(start_rev)
- && (start_rev > peg_revision))
+ if (start_rev > peg_revision)
return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0,
"Start revision must not be younger than "
"peg revision",
diff --git a/subversion/mod_dav_svn/reports/get-locations.c b/subversion/mod_dav_svn/reports/get-locations.c
index 8764ddb..164045f 100644
--- a/subversion/mod_dav_svn/reports/get-locations.c
+++ b/subversion/mod_dav_svn/reports/get-locations.c
@@ -106,6 +106,9 @@ dav_svn__get_locations_report(const dav_resource *resource,
sizeof(svn_revnum_t));
/* Sanity check. */
+ if (!resource->info->repos_path)
+ return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0,
+ "The request does not specify a repository path");
ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
if (ns == -1)
{
diff --git a/subversion/mod_dav_svn/reports/inherited-props.c b/subversion/mod_dav_svn/reports/inherited-props.c
new file mode 100644
index 0000000..ce0f4de
--- /dev/null
+++ b/subversion/mod_dav_svn/reports/inherited-props.c
@@ -0,0 +1,236 @@
+/*
+ * inherited-props.c: mod_dav_svn REPORT handler for querying 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_pools.h>
+#include <apr_strings.h>
+#include <apr_xml.h>
+
+#include <http_request.h>
+#include <http_log.h>
+#include <mod_dav.h>
+
+#include "svn_pools.h"
+#include "svn_repos.h"
+#include "svn_xml.h"
+#include "svn_path.h"
+#include "svn_dav.h"
+#include "svn_props.h"
+#include "svn_base64.h"
+
+#include "private/svn_fspath.h"
+#include "private/svn_dav_protocol.h"
+#include "private/svn_log.h"
+#include "private/svn_mergeinfo_private.h"
+
+#include "../dav_svn.h"
+
+dav_error *
+dav_svn__get_inherited_props_report(const dav_resource *resource,
+ const apr_xml_doc *doc,
+ ap_filter_t *output)
+{
+ svn_error_t *serr;
+ dav_error *derr = NULL;
+ apr_xml_elem *child;
+ apr_array_header_t *inherited_props;
+ dav_svn__authz_read_baton arb;
+ int ns;
+ apr_bucket_brigade *bb;
+ const char *path = "/";
+ svn_fs_root_t *root;
+ int i;
+ svn_revnum_t rev = SVN_INVALID_REVNUM;
+ apr_pool_t *iterpool;
+
+ /* Sanity check. */
+ if (!resource->info->repos_path)
+ return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0,
+ "The request does not specify a repository path");
+ ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
+ if (ns == -1)
+ {
+ return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0,
+ "The request does not contain the 'svn:' "
+ "namespace, so it is not going to have "
+ "certain required elements.",
+ SVN_DAV_ERROR_NAMESPACE,
+ SVN_DAV_ERROR_TAG);
+ }
+
+ iterpool = svn_pool_create(resource->pool);
+
+ for (child = doc->root->first_child;
+ child != NULL;
+ child = child->next)
+ {
+ /* if this element isn't one of ours, then skip it */
+ if (child->ns != ns)
+ continue;
+
+ if (strcmp(child->name, SVN_DAV__REVISION) == 0)
+ {
+ rev = SVN_STR_TO_REV(dav_xml_get_cdata(child, iterpool, 1));
+ }
+ else if (strcmp(child->name, SVN_DAV__PATH) == 0)
+ {
+ path = dav_xml_get_cdata(child, resource->pool, 0);
+ if ((derr = dav_svn__test_canonical(path, iterpool)))
+ return derr;
+ path = svn_fspath__join(resource->info->repos_path, path,
+ resource->pool);
+ }
+ /* else unknown element; skip it */
+ }
+
+ /* Build authz read baton */
+ arb.r = resource->info->r;
+ arb.repos = resource->info->repos;
+
+ /* Build inherited property brigade */
+ bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
+
+ serr = svn_fs_revision_root(&root, resource->info->repos->fs,
+ rev, resource->pool);
+ if (serr != NULL)
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "couldn't retrieve revision root",
+ resource->pool);
+
+ serr = svn_repos_fs_get_inherited_props(&inherited_props, root, path, NULL,
+ dav_svn__authz_read_func(&arb),
+ &arb, resource->pool, iterpool);
+ if (serr)
+ {
+ derr = dav_svn__convert_err(serr, HTTP_BAD_REQUEST, serr->message,
+ resource->pool);
+ goto cleanup;
+ }
+
+ serr = dav_svn__brigade_puts(bb, output,
+ DAV_XML_HEADER DEBUG_CR
+ "<S:" SVN_DAV__INHERITED_PROPS_REPORT " "
+ "xmlns:S=\"" SVN_XML_NAMESPACE "\" "
+ "xmlns:D=\"DAV:\">" DEBUG_CR);
+ if (serr)
+ {
+ derr = dav_svn__convert_err(serr, HTTP_BAD_REQUEST, serr->message,
+ resource->pool);
+ goto cleanup;
+ }
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *elt =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ svn_pool_clear(iterpool);
+
+ serr = dav_svn__brigade_printf(
+ bb, output,
+ "<S:" SVN_DAV__IPROP_ITEM ">"
+ DEBUG_CR
+ "<S:" SVN_DAV__IPROP_PATH ">%s</S:" SVN_DAV__IPROP_PATH ">"
+ DEBUG_CR,
+ apr_xml_quote_string(resource->pool, elt->path_or_url, 0));
+
+ if (!serr)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(resource->pool, elt->prop_hash);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+ svn_string_t *propval = svn__apr_hash_index_val(hi);
+ const char *xml_safe;
+
+ serr = dav_svn__brigade_printf(
+ bb, output,
+ "<S:" SVN_DAV__IPROP_PROPNAME ">%s</S:"
+ SVN_DAV__IPROP_PROPNAME ">" DEBUG_CR,
+ apr_xml_quote_string(iterpool, propname, 0));
+
+ if (!serr)
+ {
+ if (svn_xml_is_xml_safe(propval->data, propval->len))
+ {
+ svn_stringbuf_t *tmp = NULL;
+ svn_xml_escape_cdata_string(&tmp, propval,
+ iterpool);
+ xml_safe = tmp->data;
+ serr = dav_svn__brigade_printf(
+ bb, output,
+ "<S:" SVN_DAV__IPROP_PROPVAL ">%s</S:"
+ SVN_DAV__IPROP_PROPVAL ">" DEBUG_CR, xml_safe);
+ }
+ else
+ {
+ xml_safe = svn_base64_encode_string2(
+ propval, TRUE, iterpool)->data;
+ serr = dav_svn__brigade_printf(
+ bb, output,
+ "<S:" SVN_DAV__IPROP_PROPVAL
+ " encoding=\"base64\"" ">%s</S:"
+ SVN_DAV__IPROP_PROPVAL ">" DEBUG_CR, xml_safe);
+ }
+ }
+
+ if (serr)
+ break;
+ }
+ if (!serr)
+ serr = dav_svn__brigade_printf(bb, output,
+ "</S:" SVN_DAV__IPROP_ITEM ">"
+ DEBUG_CR);
+ }
+
+ if (serr)
+ {
+ derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Error ending REPORT response.",
+ resource->pool);
+ goto cleanup;
+ }
+ }
+
+ if ((serr = dav_svn__brigade_puts(bb, output,
+ "</S:" SVN_DAV__INHERITED_PROPS_REPORT ">"
+ DEBUG_CR)))
+ {
+ derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Error ending REPORT response.",
+ resource->pool);
+ goto cleanup;
+ }
+
+ cleanup:
+
+ /* Log this 'high level' svn action. */
+ dav_svn__operational_log(resource->info,
+ svn_log__get_inherited_props(path, rev,
+ resource->pool));
+ svn_pool_destroy(iterpool);
+ return dav_svn__final_flush_or_error(resource->info->r, bb, output,
+ derr, resource->pool);
+}
diff --git a/subversion/mod_dav_svn/reports/log.c b/subversion/mod_dav_svn/reports/log.c
index 8a5fd6e..acd33ed 100644
--- a/subversion/mod_dav_svn/reports/log.c
+++ b/subversion/mod_dav_svn/reports/log.c
@@ -307,6 +307,9 @@ dav_svn__log_report(const dav_resource *resource,
= apr_array_make(resource->pool, 1, sizeof(const char *));
/* Sanity check. */
+ if (!resource->info->repos_path)
+ return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0,
+ "The request does not specify a repository path");
ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
if (ns == -1)
{
@@ -341,10 +344,9 @@ dav_svn__log_report(const dav_resource *resource,
dav_xml_get_cdata(child, resource->pool, 1));
if (serr)
{
- derr = dav_svn__convert_err(serr, HTTP_BAD_REQUEST,
+ return dav_svn__convert_err(serr, HTTP_BAD_REQUEST,
"Malformed CDATA in element "
"\"limit\"", resource->pool);
- goto cleanup;
}
}
else if (strcmp(child->name, "discover-changed-paths") == 0)
diff --git a/subversion/mod_dav_svn/reports/mergeinfo.c b/subversion/mod_dav_svn/reports/mergeinfo.c
index 79eb86c..15c3071 100644
--- a/subversion/mod_dav_svn/reports/mergeinfo.c
+++ b/subversion/mod_dav_svn/reports/mergeinfo.c
@@ -67,6 +67,9 @@ dav_svn__get_mergeinfo_report(const dav_resource *resource,
= apr_array_make(resource->pool, 0, sizeof(const char *));
/* Sanity check. */
+ if (!resource->info->repos_path)
+ return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0,
+ "The request does not specify a repository path");
ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
if (ns == -1)
{
diff --git a/subversion/mod_dav_svn/reports/replay.c b/subversion/mod_dav_svn/reports/replay.c
index 7679ee3..0d57f32 100644
--- a/subversion/mod_dav_svn/reports/replay.c
+++ b/subversion/mod_dav_svn/reports/replay.c
@@ -47,6 +47,7 @@ typedef struct edit_baton_t {
ap_filter_t *output;
svn_boolean_t started;
svn_boolean_t sending_textdelta;
+ int compression_level;
} edit_baton_t;
@@ -326,7 +327,7 @@ apply_textdelta(void *file_baton,
eb->output,
pool),
0,
- dav_svn__get_compression_level(),
+ eb->compression_level,
pool);
eb->sending_textdelta = TRUE;
@@ -367,6 +368,7 @@ make_editor(const svn_delta_editor_t **editor,
void **edit_baton,
apr_bucket_brigade *bb,
ap_filter_t *output,
+ int compression_level,
apr_pool_t *pool)
{
edit_baton_t *eb = apr_pcalloc(pool, sizeof(*eb));
@@ -376,6 +378,7 @@ make_editor(const svn_delta_editor_t **editor,
eb->output = output;
eb->started = FALSE;
eb->sending_textdelta = FALSE;
+ eb->compression_level = compression_level;
e->set_target_revision = set_target_revision;
e->open_root = open_root;
@@ -415,11 +418,11 @@ dav_svn__replay_report(const dav_resource *resource,
{
dav_error *derr = NULL;
svn_revnum_t low_water_mark = SVN_INVALID_REVNUM;
- svn_revnum_t rev = SVN_INVALID_REVNUM;
+ svn_revnum_t rev;
const svn_delta_editor_t *editor;
svn_boolean_t send_deltas = TRUE;
dav_svn__authz_read_baton arb;
- const char *base_dir = resource->info->repos_path;
+ const char *base_dir;
apr_bucket_brigade *bb;
apr_xml_elem *child;
svn_fs_root_t *root;
@@ -427,9 +430,26 @@ dav_svn__replay_report(const dav_resource *resource,
void *edit_baton;
int ns;
- /* The request won't have a repos_path if it's for the root. */
- if (! base_dir)
- base_dir = "";
+ /* In Subversion 1.8, we allowed this REPORT to be issue against a
+ revision resource. Doing so means the REV is part of the request
+ URL, and BASE_DIR is embedded in the request body.
+
+ The old-school (and incorrect, see issue #4287 --
+ http://subversion.tigris.org/issues/show_bug.cgi?id=4287) way was
+ to REPORT on the public URL of the BASE_DIR and embed the REV in
+ the report body.
+ */
+ if (resource->baselined
+ && (resource->type == DAV_RESOURCE_TYPE_VERSION))
+ {
+ rev = resource->info->root.rev;
+ base_dir = NULL;
+ }
+ else
+ {
+ rev = SVN_INVALID_REVNUM;
+ base_dir = resource->info->repos_path;
+ }
arb.r = resource->info->r;
arb.repos = resource->info->repos;
@@ -452,9 +472,17 @@ dav_svn__replay_report(const dav_resource *resource,
if (strcmp(child->name, "revision") == 0)
{
+ if (SVN_IS_VALID_REVNUM(rev))
+ {
+ /* Uh... we already have a revision to use, either
+ because this tag is non-unique or because the
+ request was submitted against a revision-bearing
+ resource URL. Either way, something's not
+ right. */
+ return malformed_element_error("revision", resource->pool);
+ }
+
cdata = dav_xml_get_cdata(child, resource->pool, 1);
- if (! cdata)
- return malformed_element_error("revision", resource->pool);
rev = SVN_STR_TO_REV(cdata);
}
else if (strcmp(child->name, "low-water-mark") == 0)
@@ -478,7 +506,16 @@ dav_svn__replay_report(const dav_resource *resource,
svn_error_clear(err);
return malformed_element_error("send-deltas", resource->pool);
}
- send_deltas = parsed_val ? TRUE : FALSE;
+ send_deltas = parsed_val != 0;
+ }
+ else if (strcmp(child->name, "include-path") == 0)
+ {
+ cdata = dav_xml_get_cdata(child, resource->pool, 1);
+ if ((derr = dav_svn__test_canonical(cdata, resource->pool)))
+ return derr;
+
+ /* Force BASE_DIR to be a relative path, not an fspath. */
+ base_dir = svn_relpath_canonicalize(cdata, resource->pool);
}
}
}
@@ -495,6 +532,9 @@ dav_svn__replay_report(const dav_resource *resource,
"Request was missing the low-water-mark argument.",
SVN_DAV_ERROR_NAMESPACE, SVN_DAV_ERROR_TAG);
+ if (! base_dir)
+ base_dir = "";
+
bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
if ((err = svn_fs_revision_root(&root, resource->info->repos->fs, rev,
@@ -506,7 +546,9 @@ dav_svn__replay_report(const dav_resource *resource,
goto cleanup;
}
- make_editor(&editor, &edit_baton, bb, output, resource->pool);
+ make_editor(&editor, &edit_baton, bb, output,
+ dav_svn__get_compression_level(resource->info->r),
+ resource->pool);
if ((err = svn_repos_replay2(root, base_dir, low_water_mark,
send_deltas, editor, edit_baton,
diff --git a/subversion/mod_dav_svn/reports/update.c b/subversion/mod_dav_svn/reports/update.c
index 0154325..541d551 100644
--- a/subversion/mod_dav_svn/reports/update.c
+++ b/subversion/mod_dav_svn/reports/update.c
@@ -29,6 +29,7 @@
#include <http_log.h>
#include <mod_dav.h>
+#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_repos.h"
#include "svn_fs.h"
@@ -45,6 +46,7 @@
#include "../dav_svn.h"
+/* State baton for the overall update process. */
typedef struct update_ctx_t {
const dav_resource *resource;
@@ -80,26 +82,58 @@ typedef struct update_ctx_t {
/* True iff client requested all data inline in the report. */
svn_boolean_t send_all;
+ /* True iff client requested that properties be transmitted
+ inline. (This is implied when "send_all" is set.) */
+ svn_boolean_t include_props;
+
/* SVNDIFF version to send to client. */
int svndiff_version;
+
+ /* Compression level of SVNDIFF deltas. */
+ int compression_level;
+
+ /* Did the client submit this REPORT request via the HTTPv2 "me
+ resource" and are we advertising support for as much? */
+ svn_boolean_t enable_v2_response;
+
} update_ctx_t;
+
+/* State baton for a file or directory. */
typedef struct item_baton_t {
apr_pool_t *pool;
update_ctx_t *uc;
- struct item_baton_t *parent; /* the parent of this item. */
- const char *name; /* the single-component name of this item */
- const char *path; /* a telescoping extension of uc->anchor */
- const char *path2; /* a telescoping extension of uc->dst_path */
- const char *path3; /* a telescoping extension of uc->dst_path
- without dst_path as prefix. */
- const char *base_checksum; /* base_checksum (from apply_textdelta) */
+ /* Uplink -- the parent of this item. */
+ struct item_baton_t *parent;
+
+ /* Single-component name of this item. */
+ const char *name;
+
+ /* Telescoping extension paths ... */
+ const char *path; /* ... of uc->anchor. */
+ const char *path2; /* ... of uc->dst_path. */
+ const char *path3; /* ... uc->dst_path, without dst_path prefix. */
- svn_boolean_t text_changed; /* Did the file's contents change? */
- svn_boolean_t added; /* File added? (Implies text_changed.) */
- svn_boolean_t copyfrom; /* File copied? */
- apr_array_header_t *removed_props; /* array of const char * prop names */
+ /* Base_checksum (from apply_textdelta). */
+ const char *base_checksum;
+
+ /* Did the file's contents change? */
+ svn_boolean_t text_changed;
+
+ /* File/dir added? (Implies text_changed for files.) */
+ svn_boolean_t added;
+
+ /* File/dir copied? */
+ svn_boolean_t copyfrom;
+
+ /* Does the client need to fetch additional properties for this
+ item? */
+ svn_boolean_t fetch_props;
+
+ /* Array of const char * names of removed properties. (Used only
+ for copied files/dirs in skelta mode.) */
+ apr_array_header_t *removed_props;
} item_baton_t;
@@ -120,7 +154,7 @@ add_to_path_map(apr_hash_t *hash, const char *path, const char *linkpath)
const char *repos_path = linkpath ? linkpath : norm_path;
/* now, geez, put the path in the map already! */
- apr_hash_set(hash, path, APR_HASH_KEY_STRING, repos_path);
+ svn_hash_sets(hash, path, repos_path);
}
@@ -136,7 +170,7 @@ get_from_path_map(apr_hash_t *hash, const char *path, apr_pool_t *pool)
if (! hash)
return apr_pstrdup(pool, path);
- if ((repos_path = apr_hash_get(hash, path, APR_HASH_KEY_STRING)))
+ if ((repos_path = svn_hash_gets(hash, path)))
{
/* what luck! this path is a hash key! if there is a linkpath,
use that, else return the path itself. */
@@ -219,9 +253,18 @@ send_vsn_url(item_baton_t *baton, apr_pool_t *pool)
path = get_real_fs_path(baton, pool);
revision = dav_svn__get_safe_cr(baton->uc->rev_root, path, pool);
- href = dav_svn__build_uri(baton->uc->resource->info->repos,
- DAV_SVN__BUILD_URI_VERSION,
- revision, path, 0 /* add_href */, pool);
+ if (baton->uc->enable_v2_response)
+ {
+ href = dav_svn__build_uri(baton->uc->resource->info->repos,
+ DAV_SVN__BUILD_URI_REVROOT,
+ revision, path, 0 /* add_href */, pool);
+ }
+ else
+ {
+ href = dav_svn__build_uri(baton->uc->resource->info->repos,
+ DAV_SVN__BUILD_URI_VERSION,
+ revision, path, 0 /* add_href */, pool);
+ }
return dav_svn__brigade_printf(baton->uc->bb, baton->uc->output,
"<D:checked-in><D:href>%s</D:href>"
@@ -401,7 +444,7 @@ open_helper(svn_boolean_t is_dir,
static svn_error_t *
-close_helper(svn_boolean_t is_dir, item_baton_t *baton)
+close_helper(svn_boolean_t is_dir, item_baton_t *baton, apr_pool_t *pool)
{
if (baton->uc->resource_walk)
return SVN_NO_ERROR;
@@ -415,14 +458,21 @@ close_helper(svn_boolean_t is_dir, item_baton_t *baton)
for (i = 0; i < baton->removed_props->nelts; i++)
{
- /* We already XML-escaped the property name in change_xxx_prop. */
qname = APR_ARRAY_IDX(baton->removed_props, i, const char *);
+ qname = apr_xml_quote_string(pool, qname, 1);
SVN_ERR(dav_svn__brigade_printf(baton->uc->bb, baton->uc->output,
"<S:remove-prop name=\"%s\"/>"
DEBUG_CR, qname));
}
}
+ /* If our client need to fetch properties, let it know. */
+ if (baton->fetch_props)
+ SVN_ERR(dav_svn__brigade_printf(baton->uc->bb, baton->uc->output,
+ "<S:fetch-props/>" DEBUG_CR));
+
+
+ /* Let's tie it off, nurse. */
if (baton->added)
SVN_ERR(dav_svn__brigade_printf(baton->uc->bb, baton->uc->output,
"</S:add-%s>" DEBUG_CR,
@@ -442,13 +492,13 @@ maybe_start_update_report(update_ctx_t *uc)
{
if ((! uc->resource_walk) && (! uc->started_update))
{
- SVN_ERR(dav_svn__brigade_printf(uc->bb, uc->output,
- DAV_XML_HEADER DEBUG_CR
- "<S:update-report xmlns:S=\""
- SVN_XML_NAMESPACE "\" "
- "xmlns:V=\"" SVN_DAV_PROP_NS_DAV "\" "
- "xmlns:D=\"DAV:\" %s>" DEBUG_CR,
- uc->send_all ? "send-all=\"true\"" : ""));
+ SVN_ERR(dav_svn__brigade_printf(
+ uc->bb, uc->output,
+ DAV_XML_HEADER DEBUG_CR "<S:update-report xmlns:S=\""
+ SVN_XML_NAMESPACE "\" xmlns:V=\"" SVN_DAV_PROP_NS_DAV "\" "
+ "xmlns:D=\"DAV:\" %s %s>" DEBUG_CR,
+ uc->send_all ? "send-all=\"true\"" : "",
+ uc->include_props ? "inline-props=\"true\"" : ""));
uc->started_update = TRUE;
}
@@ -551,10 +601,10 @@ upd_add_directory(const char *path,
static svn_error_t *
upd_open_directory(const char *path,
- void *parent_baton,
- svn_revnum_t base_revision,
- apr_pool_t *pool,
- void **child_baton)
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
{
return open_helper(TRUE /* is_dir */,
path, parent_baton, base_revision, pool, child_baton);
@@ -562,79 +612,115 @@ upd_open_directory(const char *path,
static svn_error_t *
+send_propchange(item_baton_t *b,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ const char *qname;
+
+ /* Ensure that the property name is XML-safe. */
+ qname = apr_xml_quote_string(pool, name, 1);
+
+ if (value)
+ {
+ const char *qval;
+
+ if (svn_xml_is_xml_safe(value->data, value->len))
+ {
+ svn_stringbuf_t *tmp = NULL;
+ svn_xml_escape_cdata_string(&tmp, value, pool);
+ qval = tmp->data;
+ SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
+ "<S:set-prop name=\"%s\">",
+ qname));
+ }
+ else
+ {
+ qval = svn_base64_encode_string2(value, TRUE, pool)->data;
+ SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
+ "<S:set-prop name=\"%s\" "
+ "encoding=\"base64\">" DEBUG_CR,
+ qname));
+ }
+
+ SVN_ERR(dav_svn__brigade_puts(b->uc->bb, b->uc->output, qval));
+ SVN_ERR(dav_svn__brigade_puts(b->uc->bb, b->uc->output,
+ "</S:set-prop>" DEBUG_CR));
+ }
+ else /* value is null, so this is a prop removal */
+ {
+ SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
+ "<S:remove-prop name=\"%s\"/>"
+ DEBUG_CR,
+ qname));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
upd_change_xxx_prop(void *baton,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
item_baton_t *b = baton;
- const char *qname;
/* Resource walks say nothing about props. */
if (b->uc->resource_walk)
return SVN_NO_ERROR;
- /* Else this not a resource walk, so either send props or cache them
- to send later, depending on whether this is a modern report
- response or not. */
-
- qname = apr_xml_quote_string(b->pool, name, 1);
-
- /* apr_xml_quote_string doesn't realloc if there is nothing to
- quote, so dup the name, but only if necessary. */
- if (qname == name)
- qname = apr_pstrdup(b->pool, name);
+ /* If we get here, this not a resource walk, so either send props or
+ cache them to send later, depending on whether this is a modern
+ report response or not. */
/* Even if we are not in send-all mode we have the prop changes already,
so send them to the client now instead of telling the client to fetch
them later. */
- if (b->uc->send_all || !b->added)
+ if (b->uc->send_all)
{
- if (value)
+ SVN_ERR(send_propchange(b, name, value, pool));
+ }
+ else
+ {
+ if (b->added)
{
- const char *qval;
-
- if (svn_xml_is_xml_safe(value->data, value->len))
+ /* This is an addition in "skelta" (that is, "not send-all")
+ mode so there is no strict need for an inline response.
+ Clients will assume that added objects need all to have
+ all their properties explicitly fetched from the
+ server. */
+
+ /* That said, beginning in Subversion 1.8, clients might
+ request even in skelta mode that we transmit properties
+ on added files and directories explicitly. */
+ if (value && b->uc->include_props)
{
- svn_stringbuf_t *tmp = NULL;
- svn_xml_escape_cdata_string(&tmp, value, pool);
- qval = tmp->data;
- SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
- "<S:set-prop name=\"%s\">",
- qname));
+ SVN_ERR(send_propchange(b, name, value, pool));
}
- else
+
+ /* Now, if the object is actually a copy and this is a
+ property removal, we'll still need to cache (and later
+ transmit) property removals, because fetching the
+ object's current property set alone isn't sufficient to
+ communicate the fact that additional properties were, in
+ fact, removed from the copied base object in order to
+ arrive at that set. */
+ if (b->copyfrom && (! value))
{
- qval = svn_base64_encode_string2(value, TRUE, pool)->data;
- SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
- "<S:set-prop name=\"%s\" "
- "encoding=\"base64\">" DEBUG_CR,
- qname));
- }
+ if (! b->removed_props)
+ b->removed_props = apr_array_make(b->pool, 1, sizeof(name));
- SVN_ERR(dav_svn__brigade_puts(b->uc->bb, b->uc->output, qval));
- SVN_ERR(dav_svn__brigade_puts(b->uc->bb, b->uc->output,
- "</S:set-prop>" DEBUG_CR));
+ APR_ARRAY_PUSH(b->removed_props, const char *) = name;
+ }
}
- else /* value is null, so this is a prop removal */
+ else
{
- SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
- "<S:remove-prop name=\"%s\"/>"
- DEBUG_CR,
- qname));
+ /* "skelta" mode non-addition. Just send the change. */
+ SVN_ERR(send_propchange(b, name, value, pool));
}
}
- else if (!value) /* This is an addition in 'skelta' mode so there is no
- need for an inline response since property fetching
- is implied in addition. We still need to cache
- property removals because a copied path might
- have removed properties. */
- {
- if (! b->removed_props)
- b->removed_props = apr_array_make(b->pool, 1, sizeof(name));
-
- APR_ARRAY_PUSH(b->removed_props, const char *) = qname;
- }
return SVN_NO_ERROR;
}
@@ -643,7 +729,7 @@ upd_change_xxx_prop(void *baton,
static svn_error_t *
upd_close_directory(void *dir_baton, apr_pool_t *pool)
{
- return close_helper(TRUE /* is_dir */, dir_baton);
+ return close_helper(TRUE /* is_dir */, dir_baton, pool);
}
@@ -720,18 +806,6 @@ window_handler(svn_txdelta_window_t *window, void *baton)
return SVN_NO_ERROR;
}
-
-/* This implements 'svn_txdelta_window_handler_t'.
- During a resource walk, the driver sends an empty window as a
- boolean indicating that a change happened to this file, but we
- don't want to send anything over the wire as a result. */
-static svn_error_t *
-dummy_window_handler(svn_txdelta_window_t *window, void *baton)
-{
- return SVN_NO_ERROR;
-}
-
-
static svn_error_t *
upd_apply_textdelta(void *file_baton,
const char *base_checksum,
@@ -751,7 +825,7 @@ upd_apply_textdelta(void *file_baton,
we don't actually want to transmit text-deltas. */
if (file->uc->resource_walk || (! file->uc->send_all))
{
- *handler = dummy_window_handler;
+ *handler = svn_delta_noop_window_handler;
*handler_baton = NULL;
return SVN_NO_ERROR;
}
@@ -766,7 +840,7 @@ upd_apply_textdelta(void *file_baton,
svn_txdelta_to_svndiff3(&(wb->handler), &(wb->handler_baton),
base64_stream, file->uc->svndiff_version,
- dav_svn__get_compression_level(), file->pool);
+ file->uc->compression_level, file->pool);
*handler = window_handler;
*handler_baton = wb;
@@ -816,7 +890,7 @@ upd_close_file(void *file_baton, const char *text_checksum, apr_pool_t *pool)
text_checksum));
}
- return close_helper(FALSE /* is_dir */, file);
+ return close_helper(FALSE /* is_dir */, file, pool);
}
@@ -845,6 +919,49 @@ malformed_element_error(const char *tagname, apr_pool_t *pool)
}
+/* Validate that REVISION is a valid revision number for repository in
+ which YOUNGEST is the latest revision. Use RESOURCE as a
+ convenient way to access the request record and a pool for error
+ messaging. (It's okay if REVISION is SVN_INVALID_REVNUM, as in
+ the related contexts that just means "the youngest revision".)
+
+ REVTYPE is just a string describing the type/purpose of REVISION,
+ used in the generated error string. */
+static dav_error *
+validate_input_revision(svn_revnum_t revision,
+ svn_revnum_t youngest,
+ const char *revtype,
+ const dav_resource *resource)
+{
+ if (! SVN_IS_VALID_REVNUM(revision))
+ return SVN_NO_ERROR;
+
+ if (revision > youngest)
+ {
+ svn_error_t *serr;
+
+ if (dav_svn__get_master_uri(resource->info->r))
+ {
+ serr = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, 0,
+ "No such %s '%ld' found in the repository. "
+ "Perhaps the repository is out of date with "
+ "respect to the master repository?",
+ revtype, revision);
+ }
+ else
+ {
+ serr = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, 0,
+ "No such %s '%ld' found in the repository.",
+ revtype, revision);
+ }
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Invalid revision found in update report "
+ "request.", resource->pool);
+ }
+ return SVN_NO_ERROR;
+}
+
+
dav_error *
dav_svn__update_report(const dav_resource *resource,
const apr_xml_doc *doc,
@@ -854,8 +971,7 @@ dav_svn__update_report(const dav_resource *resource,
apr_xml_elem *child;
void *rbaton = NULL;
update_ctx_t uc = { 0 };
- svn_revnum_t revnum = SVN_INVALID_REVNUM;
- svn_boolean_t revnum_is_head = FALSE;
+ svn_revnum_t youngest, revnum = SVN_INVALID_REVNUM;
svn_revnum_t from_revnum = SVN_INVALID_REVNUM;
int ns;
/* entry_counter and entry_is_empty are for operational logging. */
@@ -901,11 +1017,15 @@ dav_svn__update_report(const dav_resource *resource,
SVN_DAV_ERROR_TAG);
}
- /* If server configuration permits bulk updates (a report with props
- and textdeltas inline, rather than placeholder tags that tell the
- client to do further fetches), look to see if client requested as
- much. */
- if (repos->bulk_updates)
+ /* SVNAllowBulkUpdates On/Prefer: server configuration permits bulk updates
+ (a report with props and textdeltas inline, rather than placeholder tags
+ that tell the client to do further fetches), look to see if client
+ requested as much.
+
+ SVNAllowBulkUpdates Off: no bulk updates allowed, force skelta mode.
+ */
+ if (repos->bulk_updates == CONF_BULKUPD_ON ||
+ repos->bulk_updates == CONF_BULKUPD_PREFER)
{
apr_xml_attr *this_attr;
@@ -915,11 +1035,20 @@ dav_svn__update_report(const dav_resource *resource,
&& (strcmp(this_attr->value, "true") == 0))
{
uc.send_all = TRUE;
+ uc.include_props = TRUE;
break;
}
}
}
+ /* Ask the repository about its youngest revision (which we'll need
+ for some input validation later). */
+ if ((serr = svn_fs_youngest_rev(&youngest, repos->fs, resource->pool)))
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Could not determine the youngest "
+ "revision for the update process.",
+ resource->pool);
+
for (child = doc->root->first_child; child != NULL; child = child->next)
{
/* Note that child->name might not match any of the cases below.
@@ -1037,6 +1166,31 @@ dav_svn__update_report(const dav_resource *resource,
if (strcmp(cdata, "no") == 0)
text_deltas = FALSE;
}
+ if (child->ns == ns && strcmp(child->name, "include-props") == 0)
+ {
+ cdata = dav_xml_get_cdata(child, resource->pool, 1);
+ if (! *cdata)
+ return malformed_element_error(child->name, resource->pool);
+ if (strcmp(cdata, "no") != 0)
+ uc.include_props = TRUE;
+ }
+ }
+
+ /* If a target revision wasn't requested, or the requested target
+ revision was invalid, just update to HEAD as of the moment we
+ queried the youngest revision. Otherwise, at least make sure the
+ request makes sense in light of that youngest revision
+ number. */
+ if (! SVN_IS_VALID_REVNUM(revnum))
+ {
+ revnum = youngest;
+ }
+ else
+ {
+ derr = validate_input_revision(revnum, youngest, "target revision",
+ resource);
+ if (derr)
+ return derr;
}
if (!saw_depth && !saw_recursive && (requested_depth == svn_depth_unknown))
@@ -1054,25 +1208,17 @@ dav_svn__update_report(const dav_resource *resource,
SVN_DAV_ERROR_TAG);
}
- /* If a revision for this operation was not dictated to us, this
- means "update to whatever the current HEAD is now". */
- if (revnum == SVN_INVALID_REVNUM)
- {
- if ((serr = svn_fs_youngest_rev(&revnum, repos->fs, resource->pool)))
- return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
- "Could not determine the youngest "
- "revision for the update process.",
- resource->pool);
- revnum_is_head = TRUE;
- }
-
uc.svndiff_version = resource->info->svndiff_version;
+ uc.compression_level = dav_svn__get_compression_level(resource->info->r);
uc.resource = resource;
uc.output = output;
uc.anchor = src_path;
uc.target = target;
uc.bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
uc.pathmap = NULL;
+ uc.enable_v2_response = ((resource->info->restype == DAV_SVN_RESTYPE_ME)
+ && (resource->info->repos->v2_protocol));
+
if (dst_path) /* we're doing a 'switch' */
{
if (*target)
@@ -1133,7 +1279,7 @@ dav_svn__update_report(const dav_resource *resource,
editor->close_file = upd_close_file;
editor->absent_file = upd_absent_file;
editor->close_edit = upd_close_edit;
- if ((serr = svn_repos_begin_report2(&rbaton, revnum,
+ if ((serr = svn_repos_begin_report3(&rbaton, revnum,
repos->repos,
src_path, target,
dst_path,
@@ -1144,6 +1290,7 @@ dav_svn__update_report(const dav_resource *resource,
editor, &uc,
dav_svn__authz_read_func(&arb),
&arb,
+ 0, /* disable zero-copy for now */
resource->pool)))
{
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
@@ -1179,27 +1326,10 @@ dav_svn__update_report(const dav_resource *resource,
{
rev = SVN_STR_TO_REV(this_attr->value);
saw_rev = TRUE;
- if (revnum_is_head && rev > revnum)
- {
- if (dav_svn__get_master_uri(resource->info->r))
- return dav_svn__new_error_tag(
- resource->pool,
- HTTP_INTERNAL_SERVER_ERROR, 0,
- "A reported revision is higher than the "
- "current repository HEAD revision. "
- "Perhaps the repository is out of date "
- "with respect to the master repository?",
- SVN_DAV_ERROR_NAMESPACE,
- SVN_DAV_ERROR_TAG);
- else
- return dav_svn__new_error_tag(
- resource->pool,
- HTTP_INTERNAL_SERVER_ERROR, 0,
- "A reported revision is higher than the "
- "current repository HEAD revision.",
- SVN_DAV_ERROR_NAMESPACE,
- SVN_DAV_ERROR_TAG);
- }
+ if ((derr = validate_input_revision(rev, youngest,
+ "reported revision",
+ resource)))
+ return derr;
}
else if (strcmp(this_attr->name, "depth") == 0)
depth = svn_depth_from_word(this_attr->value);