| /* |
| * replay.c: mod_dav_svn REPORT handler for replaying revisions |
| * |
| * ==================================================================== |
| * 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_fs.h" |
| #include "svn_base64.h" |
| #include "svn_xml.h" |
| #include "svn_path.h" |
| #include "svn_dav.h" |
| #include "svn_props.h" |
| #include "private/svn_log.h" |
| |
| #include "../dav_svn.h" |
| |
| |
| typedef struct edit_baton_t { |
| apr_bucket_brigade *bb; |
| dav_svn__output *output; |
| svn_boolean_t started; |
| svn_boolean_t sending_textdelta; |
| int compression_level; |
| int svndiff_version; |
| } edit_baton_t; |
| |
| |
| |
| /*** Helper Functions ***/ |
| |
| static svn_error_t * |
| maybe_start_report(edit_baton_t *eb) |
| { |
| if (! eb->started) |
| { |
| SVN_ERR(dav_svn__brigade_puts(eb->bb, eb->output, |
| DAV_XML_HEADER DEBUG_CR |
| "<S:editor-report xmlns:S=\"" |
| SVN_XML_NAMESPACE "\">" DEBUG_CR)); |
| eb->started = TRUE; |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| end_report(edit_baton_t *eb) |
| { |
| SVN_ERR(dav_svn__brigade_puts(eb->bb, eb->output, |
| "</S:editor-report>" DEBUG_CR)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| maybe_close_textdelta(edit_baton_t *eb) |
| { |
| if (eb->sending_textdelta) |
| { |
| SVN_ERR(dav_svn__brigade_puts(eb->bb, eb->output, |
| "</S:apply-textdelta>" DEBUG_CR)); |
| eb->sending_textdelta = FALSE; |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| add_file_or_directory(const char *file_or_directory, |
| const char *path, |
| edit_baton_t *eb, |
| const char *copyfrom_path, |
| svn_revnum_t copyfrom_rev, |
| apr_pool_t *pool, |
| void **added_baton) |
| { |
| const char *qname = apr_xml_quote_string(pool, path, 1); |
| const char *qcopy = |
| copyfrom_path ? apr_xml_quote_string(pool, copyfrom_path, 1) : NULL; |
| |
| SVN_ERR(maybe_close_textdelta(eb)); |
| |
| *added_baton = eb; |
| |
| if (! copyfrom_path) |
| SVN_ERR(dav_svn__brigade_printf(eb->bb, eb->output, |
| "<S:add-%s name=\"%s\"/>" DEBUG_CR, |
| file_or_directory, qname)); |
| else |
| SVN_ERR(dav_svn__brigade_printf(eb->bb, eb->output, |
| "<S:add-%s name=\"%s\" " |
| "copyfrom-path=\"%s\" " |
| "copyfrom-rev=\"%ld\"/>" DEBUG_CR, |
| file_or_directory, qname, |
| qcopy, copyfrom_rev)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| open_file_or_directory(const char *file_or_directory, |
| const char *path, |
| edit_baton_t *eb, |
| svn_revnum_t base_revision, |
| apr_pool_t *pool, |
| void **opened_baton) |
| { |
| const char *qname = apr_xml_quote_string(pool, path, 1); |
| SVN_ERR(maybe_close_textdelta(eb)); |
| *opened_baton = eb; |
| return dav_svn__brigade_printf(eb->bb, eb->output, |
| "<S:open-%s name=\"%s\" rev=\"%ld\"/>" |
| DEBUG_CR, |
| file_or_directory, qname, base_revision); |
| } |
| |
| |
| static svn_error_t * |
| change_file_or_dir_prop(const char *file_or_dir, |
| edit_baton_t *eb, |
| const char *name, |
| const svn_string_t *value, |
| apr_pool_t *pool) |
| { |
| const char *qname = apr_xml_quote_string(pool, name, 1); |
| |
| SVN_ERR(maybe_close_textdelta(eb)); |
| |
| if (value) |
| { |
| const svn_string_t *enc_value = |
| svn_base64_encode_string2(value, TRUE, pool); |
| |
| /* Some versions of apr_brigade_vprintf() have a buffer overflow |
| bug that can be triggered by just the wrong size of a large |
| property value. The bug has been fixed (see |
| http://svn.apache.org/viewvc?view=rev&revision=768417), but |
| we need a workaround for the buggy APR versions, so we write |
| our potentially large block of property data using a |
| different underlying function. */ |
| SVN_ERR(dav_svn__brigade_printf(eb->bb, eb->output, |
| "<S:change-%s-prop name=\"%s\">", |
| file_or_dir, qname)); |
| SVN_ERR(dav_svn__brigade_write(eb->bb, eb->output, |
| enc_value->data, enc_value->len)); |
| SVN_ERR(dav_svn__brigade_printf(eb->bb, eb->output, |
| "</S:change-%s-prop>" DEBUG_CR, |
| file_or_dir)); |
| } |
| else |
| { |
| SVN_ERR(dav_svn__brigade_printf |
| (eb->bb, eb->output, |
| "<S:change-%s-prop name=\"%s\" del=\"true\"/>" DEBUG_CR, |
| file_or_dir, qname)); |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /*** Editor Implementation ***/ |
| |
| |
| static svn_error_t * |
| set_target_revision(void *edit_baton, |
| svn_revnum_t target_revision, |
| apr_pool_t *pool) |
| { |
| edit_baton_t *eb = edit_baton; |
| SVN_ERR(maybe_start_report(eb)); |
| return dav_svn__brigade_printf(eb->bb, eb->output, |
| "<S:target-revision rev=\"%ld\"/>" DEBUG_CR, |
| target_revision); |
| } |
| |
| |
| static svn_error_t * |
| open_root(void *edit_baton, |
| svn_revnum_t base_revision, |
| apr_pool_t *pool, |
| void **root_baton) |
| { |
| edit_baton_t *eb = edit_baton; |
| *root_baton = edit_baton; |
| SVN_ERR(maybe_start_report(eb)); |
| return dav_svn__brigade_printf(eb->bb, eb->output, |
| "<S:open-root rev=\"%ld\"/>" DEBUG_CR, |
| base_revision); |
| } |
| |
| |
| static svn_error_t * |
| delete_entry(const char *path, |
| svn_revnum_t revision, |
| void *parent_baton, |
| apr_pool_t *pool) |
| { |
| edit_baton_t *eb = parent_baton; |
| const char *qname = apr_xml_quote_string(pool, path, 1); |
| SVN_ERR(maybe_close_textdelta(eb)); |
| return dav_svn__brigade_printf(eb->bb, eb->output, |
| "<S:delete-entry name=\"%s\" rev=\"%ld\"/>" |
| DEBUG_CR, |
| qname, revision); |
| } |
| |
| |
| static svn_error_t * |
| add_directory(const char *path, |
| void *parent_baton, |
| const char *copyfrom_path, |
| svn_revnum_t copyfrom_rev, |
| apr_pool_t *pool, |
| void **child_baton) |
| { |
| return add_file_or_directory("directory", path, parent_baton, |
| copyfrom_path, copyfrom_rev, pool, child_baton); |
| } |
| |
| |
| static svn_error_t * |
| open_directory(const char *path, |
| void *parent_baton, |
| svn_revnum_t base_revision, |
| apr_pool_t *pool, |
| void **child_baton) |
| { |
| return open_file_or_directory("directory", path, parent_baton, |
| base_revision, pool, child_baton); |
| } |
| |
| |
| static svn_error_t * |
| change_dir_prop(void *baton, |
| const char *name, |
| const svn_string_t *value, |
| apr_pool_t *pool) |
| { |
| return change_file_or_dir_prop("dir", baton, name, value, pool); |
| } |
| |
| |
| static svn_error_t * |
| add_file(const char *path, |
| void *parent_baton, |
| const char *copyfrom_path, |
| svn_revnum_t copyfrom_rev, |
| apr_pool_t *pool, |
| void **file_baton) |
| { |
| return add_file_or_directory("file", path, parent_baton, |
| copyfrom_path, copyfrom_rev, pool, file_baton); |
| } |
| |
| |
| static svn_error_t * |
| open_file(const char *path, |
| void *parent_baton, |
| svn_revnum_t base_revision, |
| apr_pool_t *pool, |
| void **file_baton) |
| { |
| return open_file_or_directory("file", path, parent_baton, |
| base_revision, pool, file_baton); |
| } |
| |
| |
| static svn_error_t * |
| change_file_prop(void *baton, |
| const char *name, |
| const svn_string_t *value, |
| apr_pool_t *pool) |
| { |
| return change_file_or_dir_prop("file", baton, name, value, pool); |
| } |
| |
| |
| static svn_error_t * |
| apply_textdelta(void *file_baton, |
| const char *base_checksum, |
| apr_pool_t *pool, |
| svn_txdelta_window_handler_t *handler, |
| void **handler_baton) |
| { |
| edit_baton_t *eb = file_baton; |
| |
| SVN_ERR(dav_svn__brigade_puts(eb->bb, eb->output, "<S:apply-textdelta")); |
| |
| if (base_checksum) |
| SVN_ERR(dav_svn__brigade_printf(eb->bb, eb->output, " checksum=\"%s\">", |
| base_checksum)); |
| else |
| SVN_ERR(dav_svn__brigade_puts(eb->bb, eb->output, ">")); |
| |
| svn_txdelta_to_svndiff3(handler, |
| handler_baton, |
| dav_svn__make_base64_output_stream(eb->bb, |
| eb->output, |
| pool), |
| eb->svndiff_version, |
| eb->compression_level, |
| pool); |
| |
| eb->sending_textdelta = TRUE; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| close_file(void *file_baton, const char *text_checksum, apr_pool_t *pool) |
| { |
| edit_baton_t *eb = file_baton; |
| SVN_ERR(maybe_close_textdelta(eb)); |
| SVN_ERR(dav_svn__brigade_puts(eb->bb, eb->output, "<S:close-file")); |
| |
| if (text_checksum) |
| SVN_ERR(dav_svn__brigade_printf(eb->bb, eb->output, |
| " checksum=\"%s\"/>" DEBUG_CR, |
| text_checksum)); |
| else |
| SVN_ERR(dav_svn__brigade_puts(eb->bb, eb->output, "/>" DEBUG_CR)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| close_directory(void *dir_baton, apr_pool_t *pool) |
| { |
| edit_baton_t *eb = dir_baton; |
| return dav_svn__brigade_puts(eb->bb, eb->output, |
| "<S:close-directory/>" DEBUG_CR); |
| } |
| |
| |
| static void |
| make_editor(const svn_delta_editor_t **editor, |
| void **edit_baton, |
| apr_bucket_brigade *bb, |
| dav_svn__output *output, |
| int compression_level, |
| int svndiff_version, |
| apr_pool_t *pool) |
| { |
| edit_baton_t *eb = apr_pcalloc(pool, sizeof(*eb)); |
| svn_delta_editor_t *e = svn_delta_default_editor(pool); |
| |
| eb->bb = bb; |
| eb->output = output; |
| eb->started = FALSE; |
| eb->sending_textdelta = FALSE; |
| eb->compression_level = compression_level; |
| eb->svndiff_version = svndiff_version; |
| |
| e->set_target_revision = set_target_revision; |
| e->open_root = open_root; |
| e->delete_entry = delete_entry; |
| e->add_directory = add_directory; |
| e->open_directory = open_directory; |
| e->change_dir_prop = change_dir_prop; |
| e->add_file = add_file; |
| e->open_file = open_file; |
| e->apply_textdelta = apply_textdelta; |
| e->change_file_prop = change_file_prop; |
| e->close_file = close_file; |
| e->close_directory = close_directory; |
| |
| *editor = e; |
| *edit_baton = eb; |
| } |
| |
| |
| static dav_error * |
| malformed_element_error(const char *tagname, apr_pool_t *pool) |
| { |
| return dav_svn__new_error_svn(pool, HTTP_BAD_REQUEST, 0, 0, |
| apr_pstrcat(pool, |
| "The request's '", tagname, |
| "' element is malformed; there " |
| "is a problem with the client.", |
| SVN_VA_NULL)); |
| } |
| |
| |
| dav_error * |
| dav_svn__replay_report(const dav_resource *resource, |
| const apr_xml_doc *doc, |
| dav_svn__output *output) |
| { |
| dav_error *derr = NULL; |
| svn_revnum_t low_water_mark = 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; |
| apr_bucket_brigade *bb; |
| apr_xml_elem *child; |
| svn_fs_root_t *root; |
| svn_error_t *err; |
| void *edit_baton; |
| int ns; |
| |
| /* 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 -- |
| https://issues.apache.org/jira/browse/SVN-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; |
| |
| ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE); |
| if (ns == -1) |
| return dav_svn__new_error_svn(resource->pool, HTTP_BAD_REQUEST, 0, 0, |
| "The request does not contain the 'svn:' " |
| "namespace, so it is not going to have an " |
| "svn:revision element. That element is " |
| "required"); |
| |
| for (child = doc->root->first_child; child != NULL; child = child->next) |
| { |
| if (child->ns == ns) |
| { |
| const char *cdata; |
| |
| 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); |
| rev = SVN_STR_TO_REV(cdata); |
| } |
| else if (strcmp(child->name, "low-water-mark") == 0) |
| { |
| cdata = dav_xml_get_cdata(child, resource->pool, 1); |
| if (! cdata) |
| return malformed_element_error("low-water-mark", |
| resource->pool); |
| low_water_mark = SVN_STR_TO_REV(cdata); |
| } |
| else if (strcmp(child->name, "send-deltas") == 0) |
| { |
| apr_int64_t parsed_val; |
| |
| cdata = dav_xml_get_cdata(child, resource->pool, 1); |
| if (! cdata) |
| return malformed_element_error("send-deltas", resource->pool); |
| err = svn_cstring_strtoi64(&parsed_val, cdata, 0, 1, 10); |
| if (err) |
| { |
| svn_error_clear(err); |
| return malformed_element_error("send-deltas", resource->pool); |
| } |
| 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); |
| } |
| } |
| } |
| |
| if (! SVN_IS_VALID_REVNUM(rev)) |
| return dav_svn__new_error_svn |
| (resource->pool, HTTP_BAD_REQUEST, 0, 0, |
| "Request was missing the revision argument"); |
| |
| if (! SVN_IS_VALID_REVNUM(low_water_mark)) |
| return dav_svn__new_error_svn |
| (resource->pool, HTTP_BAD_REQUEST, 0, 0, |
| "Request was missing the low-water-mark argument"); |
| |
| if (! base_dir) |
| base_dir = ""; |
| |
| bb = apr_brigade_create(resource->pool, |
| dav_svn__output_get_bucket_alloc(output)); |
| |
| if ((err = svn_fs_revision_root(&root, resource->info->repos->fs, rev, |
| resource->pool))) |
| { |
| derr = dav_svn__convert_err(err, HTTP_INTERNAL_SERVER_ERROR, |
| "Couldn't retrieve revision root", |
| resource->pool); |
| goto cleanup; |
| } |
| |
| make_editor(&editor, &edit_baton, bb, output, |
| dav_svn__get_compression_level(resource->info->r), |
| resource->info->svndiff_version, |
| resource->pool); |
| |
| if ((err = svn_repos_replay2(root, base_dir, low_water_mark, |
| send_deltas, editor, edit_baton, |
| dav_svn__authz_read_func(&arb), &arb, |
| resource->pool))) |
| { |
| derr = dav_svn__convert_err(err, HTTP_INTERNAL_SERVER_ERROR, |
| "Problem replaying revision", |
| resource->pool); |
| goto cleanup; |
| } |
| |
| if ((err = end_report(edit_baton))) |
| { |
| derr = dav_svn__convert_err(err, HTTP_INTERNAL_SERVER_ERROR, |
| "Problem closing editor drive", |
| resource->pool); |
| goto cleanup; |
| } |
| |
| cleanup: |
| dav_svn__operational_log(resource->info, |
| svn_log__replay(base_dir, rev, |
| resource->info->r->pool)); |
| |
| /* Flush the brigade. */ |
| return dav_svn__final_flush_or_error(resource->info->r, bb, output, |
| derr, resource->pool); |
| } |