diff options
Diffstat (limited to 'subversion/mod_dav_svn/reports')
-rw-r--r-- | subversion/mod_dav_svn/reports/deleted-rev.c | 3 | ||||
-rw-r--r-- | subversion/mod_dav_svn/reports/file-revs.c | 9 | ||||
-rw-r--r-- | subversion/mod_dav_svn/reports/get-location-segments.c | 34 | ||||
-rw-r--r-- | subversion/mod_dav_svn/reports/get-locations.c | 3 | ||||
-rw-r--r-- | subversion/mod_dav_svn/reports/inherited-props.c | 236 | ||||
-rw-r--r-- | subversion/mod_dav_svn/reports/log.c | 6 | ||||
-rw-r--r-- | subversion/mod_dav_svn/reports/mergeinfo.c | 3 | ||||
-rw-r--r-- | subversion/mod_dav_svn/reports/replay.c | 62 | ||||
-rw-r--r-- | subversion/mod_dav_svn/reports/update.c | 398 |
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); |