diff options
Diffstat (limited to 'subversion/mod_dav_svn/reports/update.c')
-rw-r--r-- | subversion/mod_dav_svn/reports/update.c | 398 |
1 files changed, 264 insertions, 134 deletions
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); |