| /* |
| * file-revs.c: mod_dav_svn REPORT handler for transmitting a chain of |
| * file 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. |
| * ==================================================================== |
| */ |
| |
| #define APR_WANT_STRFUNC |
| #include <apr_want.h> /* for strcmp() */ |
| |
| #include "svn_types.h" |
| #include "svn_xml.h" |
| #include "svn_pools.h" |
| #include "svn_base64.h" |
| #include "svn_props.h" |
| #include "svn_dav.h" |
| |
| #include "private/svn_log.h" |
| #include "private/svn_fspath.h" |
| |
| #include "../dav_svn.h" |
| |
| struct file_rev_baton { |
| /* this buffers the output for a bit and is automatically flushed, |
| at appropriate times, by the Apache filter system. */ |
| apr_bucket_brigade *bb; |
| |
| /* where to deliver the output */ |
| dav_svn__output *output; |
| |
| /* Whether we've written the <S:file-revs-report> header. Allows for lazy |
| writes to support mod_dav-based error handling. */ |
| svn_boolean_t needs_header; |
| |
| /* 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; |
| }; |
| |
| |
| /* If FRB->needs_header is true, send the "<S:file-revs-report>" start |
| tag and set FRB->needs_header to zero. Else do nothing. |
| This is basically duplicated in log.c. Consider factoring if |
| duplicating again. */ |
| static svn_error_t * |
| maybe_send_header(struct file_rev_baton *frb) |
| { |
| if (frb->needs_header) |
| { |
| SVN_ERR(dav_svn__brigade_puts(frb->bb, frb->output, |
| DAV_XML_HEADER DEBUG_CR |
| "<S:file-revs-report xmlns:S=\"" |
| SVN_XML_NAMESPACE "\" " |
| "xmlns:D=\"DAV:\">" DEBUG_CR)); |
| frb->needs_header = FALSE; |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Send a property named NAME with value VAL in an element named ELEM_NAME. |
| Quote NAME and base64-encode VAL if necessary. */ |
| static svn_error_t * |
| send_prop(struct file_rev_baton *frb, |
| const char *elem_name, |
| const char *name, |
| const svn_string_t *val, |
| apr_pool_t *pool) |
| { |
| name = apr_xml_quote_string(pool, name, 1); |
| |
| if (svn_xml_is_xml_safe(val->data, val->len)) |
| { |
| svn_stringbuf_t *tmp = NULL; |
| svn_xml_escape_cdata_string(&tmp, val, pool); |
| val = svn_string_create(tmp->data, pool); |
| SVN_ERR(dav_svn__brigade_printf(frb->bb, frb->output, |
| "<S:%s name=\"%s\">%s</S:%s>" DEBUG_CR, |
| elem_name, name, val->data, elem_name)); |
| } |
| else |
| { |
| val = svn_base64_encode_string2(val, TRUE, pool); |
| SVN_ERR(dav_svn__brigade_printf(frb->bb, frb->output, |
| "<S:%s name=\"%s\" encoding=\"base64\">" |
| "%s</S:%s>" DEBUG_CR, |
| elem_name, name, val->data, elem_name)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* This implements the svn_txdelta_window_handler interface. |
| Forward to a more interesting window handler and if we're done, terminate |
| the txdelta and file-rev elements. */ |
| static svn_error_t * |
| delta_window_handler(svn_txdelta_window_t *window, void *baton) |
| { |
| struct file_rev_baton *frb = baton; |
| |
| SVN_ERR(frb->window_handler(window, frb->window_baton)); |
| |
| /* Terminate elements if we're done. */ |
| if (!window) |
| { |
| frb->window_handler = NULL; |
| frb->window_baton = NULL; |
| SVN_ERR(dav_svn__brigade_puts(frb->bb, frb->output, |
| "</S:txdelta></S:file-rev>" DEBUG_CR)); |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* This implements the svn_repos_file_rev_handler2_t interface. */ |
| static svn_error_t * |
| file_rev_handler(void *baton, |
| const char *path, |
| svn_revnum_t revnum, |
| apr_hash_t *rev_props, |
| svn_boolean_t merged_revision, |
| svn_txdelta_window_handler_t *window_handler, |
| void **window_baton, |
| apr_array_header_t *props, |
| apr_pool_t *pool) |
| { |
| struct file_rev_baton *frb = baton; |
| apr_pool_t *iterpool = svn_pool_create(pool); |
| apr_hash_index_t *hi; |
| int i; |
| |
| SVN_ERR(maybe_send_header(frb)); |
| |
| SVN_ERR(dav_svn__brigade_printf(frb->bb, frb->output, |
| "<S:file-rev path=\"%s\" rev=\"%ld\">" |
| DEBUG_CR, |
| apr_xml_quote_string(pool, path, 1), |
| revnum)); |
| |
| /* Send rev props. */ |
| for (hi = apr_hash_first(pool, rev_props); hi; hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| void *val; |
| const char *pname; |
| const svn_string_t *pval; |
| |
| svn_pool_clear(iterpool); |
| apr_hash_this(hi, &key, NULL, &val); |
| pname = key; |
| pval = val; |
| SVN_ERR(send_prop(frb, "rev-prop", pname, pval, iterpool)); |
| } |
| |
| /* Send file prop changes. */ |
| for (i = 0; i < props->nelts; ++i) |
| { |
| const svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t); |
| |
| svn_pool_clear(iterpool); |
| if (prop->value) |
| SVN_ERR(send_prop(frb, "set-prop", prop->name, prop->value, |
| iterpool)); |
| else |
| { |
| /* Property was removed. */ |
| SVN_ERR(dav_svn__brigade_printf(frb->bb, frb->output, |
| "<S:remove-prop name=\"%s\"/>" |
| DEBUG_CR, |
| apr_xml_quote_string(iterpool, |
| prop->name, |
| 1))); |
| } |
| } |
| |
| /* Send whether this was the result of a merge or not. */ |
| if (merged_revision) |
| SVN_ERR(dav_svn__brigade_puts(frb->bb, frb->output, |
| "<S:merged-revision/>")); |
| |
| /* Maybe send text delta. */ |
| if (window_handler) |
| { |
| svn_stream_t *base64_stream; |
| |
| base64_stream = dav_svn__make_base64_output_stream(frb->bb, frb->output, |
| pool); |
| svn_txdelta_to_svndiff3(&frb->window_handler, &frb->window_baton, |
| base64_stream, frb->svndiff_version, |
| frb->compression_level, pool); |
| *window_handler = delta_window_handler; |
| *window_baton = frb; |
| /* Start the txdelta element which will be terminated by the window |
| handler together with the file-rev element. */ |
| SVN_ERR(dav_svn__brigade_puts(frb->bb, frb->output, "<S:txdelta>")); |
| } |
| else |
| /* No txdelta, so terminate the element here. */ |
| SVN_ERR(dav_svn__brigade_puts(frb->bb, frb->output, |
| "</S:file-rev>" DEBUG_CR)); |
| |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Respond to a client request for a REPORT of type file-revs-report for the |
| RESOURCE. Get request body from DOC and send result to OUTPUT. */ |
| dav_error * |
| dav_svn__file_revs_report(const dav_resource *resource, |
| const apr_xml_doc *doc, |
| dav_svn__output *output) |
| { |
| svn_error_t *serr; |
| dav_error *derr = NULL; |
| apr_xml_elem *child; |
| int ns; |
| struct file_rev_baton frb; |
| dav_svn__authz_read_baton arb; |
| const char *abs_path = NULL; |
| |
| /* These get determined from the request document. */ |
| svn_revnum_t start = SVN_INVALID_REVNUM; |
| svn_revnum_t end = SVN_INVALID_REVNUM; |
| svn_boolean_t include_merged_revisions = FALSE; /* off by default */ |
| |
| /* Construct the authz read check baton. */ |
| arb.r = resource->info->r; |
| arb.repos = resource->info->repos; |
| |
| /* Sanity check. */ |
| if (!resource->info->repos_path) |
| return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0, 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? */ |
| 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 " |
| "certain required elements"); |
| } |
| |
| /* Get request information. */ |
| 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, "start-revision") == 0) |
| start = SVN_STR_TO_REV(dav_xml_get_cdata(child, resource->pool, 1)); |
| else if (strcmp(child->name, "end-revision") == 0) |
| end = SVN_STR_TO_REV(dav_xml_get_cdata(child, resource->pool, 1)); |
| else if (strcmp(child->name, "include-merged-revisions") == 0) |
| include_merged_revisions = TRUE; /* presence indicates positivity */ |
| else if (strcmp(child->name, "path") == 0) |
| { |
| const char *rel_path = dav_xml_get_cdata(child, resource->pool, 0); |
| if ((derr = dav_svn__test_canonical(rel_path, resource->pool))) |
| return derr; |
| |
| /* Force REL_PATH to be a relative path, not an fspath. */ |
| rel_path = svn_relpath_canonicalize(rel_path, resource->pool); |
| |
| /* Append the REL_PATH to the base FS path to get an |
| absolute repository path. */ |
| abs_path = svn_fspath__join(resource->info->repos_path, rel_path, |
| resource->pool); |
| } |
| /* else unknown element; skip it */ |
| } |
| |
| /* Check that all parameters are present and valid. */ |
| if (! abs_path) |
| return dav_svn__new_error_svn(resource->pool, HTTP_BAD_REQUEST, 0, 0, |
| "Not all parameters passed"); |
| |
| frb.bb = apr_brigade_create(resource->pool, |
| dav_svn__output_get_bucket_alloc(output)); |
| 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. */ |
| |
| /* Get the revisions and send them. */ |
| serr = svn_repos_get_file_revs2(resource->info->repos->repos, |
| abs_path, start, end, include_merged_revisions, |
| dav_svn__authz_read_func(&arb), &arb, |
| file_rev_handler, &frb, resource->pool); |
| |
| if (serr) |
| { |
| /* We don't 'goto cleanup' because ap_fflush() tells httpd |
| to write the HTTP headers out, and that includes whatever |
| r->status is at that particular time. When we call |
| dav_svn__convert_err(), we don't immediately set r->status |
| right then, so r->status remains 0, hence HTTP status 200 |
| would be misleadingly returned. */ |
| return (dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
| NULL, resource->pool)); |
| } |
| |
| if ((serr = maybe_send_header(&frb))) |
| { |
| derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
| "Error beginning REPORT response", |
| resource->pool); |
| goto cleanup; |
| } |
| |
| if ((serr = dav_svn__brigade_puts(frb.bb, frb.output, |
| "</S:file-revs-report>" DEBUG_CR))) |
| { |
| derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
| "Error ending REPORT response", |
| resource->pool); |
| goto cleanup; |
| } |
| |
| cleanup: |
| |
| /* We've detected a 'high level' svn action to log. */ |
| dav_svn__operational_log(resource->info, |
| svn_log__get_file_revs(abs_path, start, end, |
| include_merged_revisions, |
| resource->pool)); |
| |
| return dav_svn__final_flush_or_error(resource->info->r, frb.bb, output, |
| derr, resource->pool); |
| } |