diff options
Diffstat (limited to 'subversion/mod_dav_svn/repos.c')
-rw-r--r-- | subversion/mod_dav_svn/repos.c | 862 |
1 files changed, 519 insertions, 343 deletions
diff --git a/subversion/mod_dav_svn/repos.c b/subversion/mod_dav_svn/repos.c index 590cca9..9bf5ea0 100644 --- a/subversion/mod_dav_svn/repos.c +++ b/subversion/mod_dav_svn/repos.c @@ -56,6 +56,7 @@ #include "private/svn_log.h" #include "private/svn_fspath.h" #include "private/svn_repos_private.h" +#include "private/svn_sorts_private.h" #include "dav_svn.h" @@ -899,7 +900,7 @@ prep_version(dav_resource_combined *comb) comb->res.uri = dav_svn__build_uri(comb->priv.repos, DAV_SVN__BUILD_URI_BASELINE, comb->priv.root.rev, NULL, - 0 /* add_href */, + FALSE /* add_href */, pool); return NULL; @@ -1034,6 +1035,28 @@ prep_working(dav_resource_combined *comb) comb->res.exists = (kind != svn_node_none); comb->res.collection = (kind == svn_node_dir); + if (comb->res.exists + && comb->priv.r->method_number == M_MKCOL + && comb->priv.repos->is_svn_client) + { + /* mod_dav will now continue returning a generic HTTP_METHOD_NOT_ALLOWED + error, which doesn't produce nice output on SVN, nor gives any details + on why the operation failed. + + Let's error out a bit earlier and produce an error message that is + easier to understand for both clients and users. */ + + /* It would be nice if we could error out a bit later (see issue #2295), + like in create_collection(), but mod_dav outsmarts us by just + returning the error when the node exists. */ + + return dav_svn__convert_err( + svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, + "Path already exists, path '%s'", + comb->priv.repos_path), + HTTP_METHOD_NOT_ALLOWED, NULL, pool); + } + return NULL; } @@ -1168,7 +1191,7 @@ create_private_resource(const dav_resource *base, if (base->info->repos->root_path[1]) comb->res.uri = apr_pstrcat(base->pool, base->info->repos->root_path, - path->data, (char *)NULL); + path->data, SVN_VA_NULL); else comb->res.uri = path->data; comb->res.info = &comb->priv; @@ -1208,14 +1231,15 @@ static void log_warning(void *baton, svn_error_t *err) AP_MODULE_DECLARE(dav_error *) -dav_svn_split_uri(request_rec *r, - const char *uri_to_split, - const char *root_path, - const char **cleaned_uri, - int *trailing_slash, - const char **repos_basename, - const char **relative_path, - const char **repos_path) +dav_svn_split_uri2(request_rec *r, + const char *uri_to_split, + const char *root_path, + const char **cleaned_uri, + int *trailing_slash, + const char **repos_basename, + const char **relative_path, + const char **repos_path, + apr_pool_t *pool) { apr_size_t len1; int had_slash; @@ -1231,7 +1255,7 @@ dav_svn_split_uri(request_rec *r, if ((fs_path == NULL) && (fs_parent_path == NULL)) { /* ### are SVN_ERR_APMOD codes within the right numeric space? */ - return dav_svn__new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, + return dav_svn__new_error(pool, HTTP_INTERNAL_SERVER_ERROR, SVN_ERR_APMOD_MISSING_PATH_TO_FS, "The server is misconfigured: " "either an SVNPath or SVNParentPath " @@ -1240,7 +1264,7 @@ dav_svn_split_uri(request_rec *r, } /* make a copy so that we can do some work on it */ - uri = apr_pstrdup(r->pool, uri_to_split); + uri = apr_pstrdup(pool, uri_to_split); /* remove duplicate slashes, and make sure URI has no trailing '/' */ ap_no2slash(uri); @@ -1255,7 +1279,7 @@ dav_svn_split_uri(request_rec *r, *trailing_slash = FALSE; /* return the first item. */ - *cleaned_uri = apr_pstrdup(r->pool, uri); + *cleaned_uri = apr_pstrdup(pool, uri); /* The URL space defined by the SVN provider is always a virtual space. Construct the path relative to the configured Location @@ -1296,7 +1320,7 @@ dav_svn_split_uri(request_rec *r, if (fs_path != NULL) { /* the repos_basename is the last component of root_path. */ - *repos_basename = svn_dirent_basename(root_path, r->pool); + *repos_basename = svn_dirent_basename(root_path, pool); /* 'relative' is already correct for SVNPath; the root_path already contains the name of the repository, so relative is @@ -1314,7 +1338,7 @@ dav_svn_split_uri(request_rec *r, if (relative[1] == '\0') { /* ### are SVN_ERR_APMOD codes within the right numeric space? */ - return dav_svn__new_error(r->pool, HTTP_FORBIDDEN, + return dav_svn__new_error(pool, HTTP_FORBIDDEN, SVN_ERR_APMOD_MALFORMED_URI, "The URI does not contain the name " "of a repository."); @@ -1331,7 +1355,7 @@ dav_svn_split_uri(request_rec *r, } else { - magic_component = apr_pstrndup(r->pool, relative + 1, + magic_component = apr_pstrndup(pool, relative + 1, magic_end - relative - 1); relative = magic_end; } @@ -1341,7 +1365,7 @@ dav_svn_split_uri(request_rec *r, } /* We can return 'relative' at this point too. */ - *relative_path = apr_pstrdup(r->pool, relative); + *relative_path = apr_pstrdup(pool, relative); /* Code to remove the !svn junk from the front of the relative path, mainly stolen from parse_uri(). This code assumes that @@ -1362,7 +1386,7 @@ dav_svn_split_uri(request_rec *r, if (ch == '\0') { /* relative is just "!svn", which is malformed. */ - return dav_svn__new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, + return dav_svn__new_error(pool, HTTP_NOT_FOUND, SVN_ERR_APMOD_MALFORMED_URI, "Nothing follows the svn special_uri."); } @@ -1389,7 +1413,7 @@ dav_svn_split_uri(request_rec *r, *repos_path = NULL; else return dav_svn__new_error( - r->pool, HTTP_INTERNAL_SERVER_ERROR, + pool, HTTP_NOT_FOUND, SVN_ERR_APMOD_MALFORMED_URI, "Missing info after special_uri."); } @@ -1413,7 +1437,7 @@ dav_svn_split_uri(request_rec *r, /* Did we break from the loop prematurely? */ if (j != (defn->numcomponents - 1)) return dav_svn__new_error( - r->pool, HTTP_INTERNAL_SERVER_ERROR, + pool, HTTP_NOT_FOUND, SVN_ERR_APMOD_MALFORMED_URI, "Not enough components after " "special_uri."); @@ -1427,13 +1451,13 @@ dav_svn_split_uri(request_rec *r, else { /* Found a slash after the special components. */ - *repos_path = apr_pstrdup(r->pool, start); + *repos_path = apr_pstrdup(pool, start - 1); } } else { return - dav_svn__new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, + dav_svn__new_error(pool, HTTP_NOT_FOUND, SVN_ERR_APMOD_MALFORMED_URI, "Unknown data after special_uri."); } @@ -1444,7 +1468,7 @@ dav_svn_split_uri(request_rec *r, if (defn->name == NULL) return - dav_svn__new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, + dav_svn__new_error(pool, HTTP_NOT_FOUND, SVN_ERR_APMOD_MALFORMED_URI, "Couldn't match subdir after special_uri."); } @@ -1453,13 +1477,27 @@ dav_svn_split_uri(request_rec *r, { /* There's no "!svn/" at all, so the relative path is already a valid path within the repository. */ - *repos_path = apr_pstrdup(r->pool, relative); + *repos_path = apr_pstrdup(pool, relative - 1); } } return NULL; } +AP_MODULE_DECLARE(dav_error *) +dav_svn_split_uri(request_rec *r, + const char *uri_to_split, + const char *root_path, + const char **cleaned_uri, + int *trailing_slash, + const char **repos_basename, + const char **relative_path, + const char **repos_path) +{ + return dav_svn_split_uri2(r, uri_to_split, root_path, cleaned_uri, + trailing_slash, repos_basename, relative_path, + repos_path, r->pool); +} /* Context for cleanup handler. */ struct cleanup_fs_access_baton @@ -1530,7 +1568,7 @@ get_parentpath_resource(request_rec *r, if (r->uri[len-1] != '/') { new_uri = apr_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri), - "/", (char *)NULL); + "/", SVN_VA_NULL); apr_table_setn(r->headers_out, "Location", ap_construct_url(r->pool, new_uri, r)); return dav_svn__new_error(r->pool, HTTP_MOVED_PERMANENTLY, 0, @@ -1820,9 +1858,24 @@ do_out_of_date_check(dav_resource_combined *comb, request_rec *r) "Attempting to modify out-of-date resource.", r->pool); } + else if (comb->priv.version_name > created_rev) + { + svn_revnum_t txn_base_rev; + + txn_base_rev = svn_fs_txn_base_revision(comb->res.info->root.txn); + if (comb->priv.version_name > txn_base_rev) + { + serr = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, + "No such revision %ld", + comb->priv.version_name); + + return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, + "Unknown base revision", + r->pool); + } + } } - else if (SVN_IS_VALID_REVNUM(comb->priv.version_name) - && comb->res.collection) + else if (comb->res.collection) { /* Issue #4480: With HTTPv2 we can receive the first change for a directory after it has been made mutable, because one of its @@ -1837,7 +1890,7 @@ do_out_of_date_check(dav_resource_combined *comb, request_rec *r) properties changed since the BASE version. ### I think svn_fs_node_relation() checks for more changes than we - should check for here. Needs further review. But it looks like\ + should check for here. Needs further review. But it looks like this check matches the checks in the libsvn_fs commit editor. For now I would say reporting out of date in a few too many @@ -1846,8 +1899,7 @@ do_out_of_date_check(dav_resource_combined *comb, request_rec *r) svn_revnum_t txn_base_rev; svn_fs_root_t *txn_base_root; svn_fs_root_t *rev_root; - const svn_fs_id_t *txn_base_id; - const svn_fs_id_t *rev_id; + svn_fs_node_relation_t node_relation; txn_base_rev = svn_fs_txn_base_revision(comb->res.info->root.txn); @@ -1856,15 +1908,11 @@ do_out_of_date_check(dav_resource_combined *comb, request_rec *r) serr = svn_fs_revision_root(&txn_base_root, comb->res.info->repos->fs, txn_base_rev, r->pool); - - if (!serr) - serr = svn_fs_node_id(&txn_base_id, txn_base_root, - comb->priv.repos_path, r->pool); if (serr != NULL) { return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, - "Could not open youngest revision root " + "Could not open the transaction revision " "for verification against the base " "revision", r->pool); } @@ -1872,22 +1920,35 @@ do_out_of_date_check(dav_resource_combined *comb, request_rec *r) serr = svn_fs_revision_root(&rev_root, comb->res.info->repos->fs, comb->priv.version_name, r->pool); - if (!serr) - serr = svn_fs_node_id(&rev_id, rev_root, - comb->priv.repos_path, r->pool); - if (serr != NULL) { + svn_fs_close_root(txn_base_root); return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, - "Could not open the base revision" - "for verification against the youngest " - "revision", r->pool); + "Could not open the base revision " + "for verification against the " + "transaction revision", r->pool); } + serr = svn_fs_node_relation(&node_relation, rev_root, + comb->priv.repos_path, + txn_base_root, + comb->priv.repos_path, + r->pool); + svn_fs_close_root(rev_root); svn_fs_close_root(txn_base_root); - if (0 != svn_fs_compare_ids(txn_base_id, rev_id)) + if (serr != NULL) + { + /* ### correct HTTP error? */ + return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, + "Unable to fetch the node revision id " + "of the version resource within the " + "revision", + r->pool); + } + + if (node_relation != svn_fs_node_unchanged) { serr = svn_error_createf(SVN_ERR_RA_OUT_OF_DATE, NULL, "Directory '%s' is out of date", @@ -2065,6 +2126,16 @@ get_resource(request_rec *r, xslt_uri = dav_svn__get_xslt_uri(r); fs_parent_path = dav_svn__get_fs_parent_path(r); + if (r->method_number == M_COPY) + { + /* Workaround for issue #4531: Avoid a depth-infinity walk on + the copy source by overriding the Depth header here. + mod_dav defaults to infinite depth if this header is not set + which makes copies O(size of source) rather than the desired O(1). + ### Should be fixed by an explicit provider API feature in mod_dav. */ + apr_table_setn(r->headers_in, "Depth", "0"); + } + /* Special case: detect and build the SVNParentPath as a unique type of private resource, iff the SVNListParentPath directive is 'on'. */ if (dav_svn__is_parentpath_list(r)) @@ -2261,7 +2332,7 @@ get_resource(request_rec *r, } /* Retrieve/cache open repository */ - repos_key = apr_pstrcat(r->pool, "mod_dav_svn:", fs_path, (char *)NULL); + repos_key = apr_pstrcat(r->pool, "mod_dav_svn:", fs_path, SVN_VA_NULL); apr_pool_userdata_get(&userdata, repos_key, r->connection->pool); repos->repos = userdata; if (repos->repos == NULL) @@ -2275,7 +2346,9 @@ get_resource(request_rec *r, svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, dav_svn__get_fulltext_cache_flag(r) ? "1" :"0"); svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, - dav_svn__get_revprop_cache_flag(r) ? "1" :"0"); + dav_svn__get_revprop_cache_flag(r) ? "2" :"0"); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ, + dav_svn__get_block_read_flag(r) ? "1" :"0"); /* Disallow BDB/event until issue 4157 is fixed. */ if (!strcmp(ap_show_mpm(), "event")) @@ -2298,8 +2371,8 @@ get_resource(request_rec *r, /* open the FS */ if (!serr) - serr = svn_repos_open2(&(repos->repos), fs_path, fs_config, - r->connection->pool); + serr = svn_repos_open3(&(repos->repos), fs_path, fs_config, + r->connection->pool, r->pool); if (serr != NULL) { /* The error returned by svn_repos_open2 might contain the @@ -2307,9 +2380,16 @@ get_resource(request_rec *r, leak that path back to the client, because that would be a security risk, but we do want to log the real error on the server side. */ - return dav_svn__sanitize_error(serr, "Could not open the requested " - "SVN filesystem", - HTTP_INTERNAL_SERVER_ERROR, r); + + apr_status_t cause = svn_error_root_cause(serr)->apr_err; + if (APR_STATUS_IS_ENOENT(cause) || APR_STATUS_IS_ENOTDIR(cause)) + return dav_svn__sanitize_error( + serr, "Could not find the requested SVN filesystem", + HTTP_NOT_FOUND, r); + else + return dav_svn__sanitize_error( + serr, "Could not open the requested SVN filesystem", + HTTP_INTERNAL_SERVER_ERROR, r); } /* Cache the open repos for the next request on this connection */ @@ -2467,7 +2547,7 @@ get_resource(request_rec *r, "/", r->args ? "?" : "", r->args ? r->args : "", - (char *)NULL); + SVN_VA_NULL); apr_table_setn(r->headers_out, "Location", ap_construct_url(r->pool, new_path, r)); return dav_svn__new_error(r->pool, HTTP_MOVED_PERMANENTLY, 0, @@ -3161,8 +3241,8 @@ set_headers(request_rec *r, const dav_resource *resource) typedef struct diff_ctx_t { - ap_filter_t *output; - apr_pool_t *pool; + dav_svn__output *output; + apr_bucket_brigade *bb; } diff_ctx_t; @@ -3170,18 +3250,9 @@ static svn_error_t * __attribute__((warn_unused_result)) write_to_filter(void *baton, const char *buffer, apr_size_t *len) { diff_ctx_t *dc = baton; - apr_bucket_brigade *bb; - apr_bucket *bkt; - apr_status_t status; /* take the current data and shove it into the filter */ - bb = apr_brigade_create(dc->pool, dc->output->c->bucket_alloc); - bkt = apr_bucket_transient_create(buffer, *len, dc->output->c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, bkt); - if ((status = ap_pass_brigade(dc->output, bb)) != APR_SUCCESS) { - return svn_error_create(status, NULL, - "Could not write data to filter"); - } + SVN_ERR(dav_svn__brigade_write(dc->bb, dc->output, buffer, *len)); return SVN_NO_ERROR; } @@ -3191,28 +3262,270 @@ static svn_error_t * __attribute__((warn_unused_result)) close_filter(void *baton) { diff_ctx_t *dc = baton; - apr_bucket_brigade *bb; apr_bucket *bkt; - apr_status_t status; /* done with the file. write an EOS bucket now. */ - bb = apr_brigade_create(dc->pool, dc->output->c->bucket_alloc); - bkt = apr_bucket_eos_create(dc->output->c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, bkt); - if ((status = ap_pass_brigade(dc->output, bb)) != APR_SUCCESS) - return svn_error_create(status, NULL, "Could not write EOS to filter"); + bkt = apr_bucket_eos_create(dav_svn__output_get_bucket_alloc(dc->output)); + APR_BRIGADE_INSERT_TAIL(dc->bb, bkt); + SVN_ERR(dav_svn__output_pass_brigade(dc->output, dc->bb)); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +emit_collection_head(const dav_resource *resource, + apr_bucket_brigade *bb, + dav_svn__output *output, + svn_boolean_t gen_html, + apr_pool_t *pool) +{ + /* XML schema for the directory index if xslt_uri is set: + + <?xml version="1.0"?> + <?xml-stylesheet type="text/xsl" href="[info->repos->xslt_uri]"?> */ + static const char xml_index_dtd[] = + "<!DOCTYPE svn [\n" + " <!ELEMENT svn (index)>\n" + " <!ATTLIST svn version CDATA #REQUIRED\n" + " href CDATA #REQUIRED>\n" + " <!ELEMENT index (updir?, (file | dir)*)>\n" + " <!ATTLIST index name CDATA #IMPLIED\n" + " path CDATA #IMPLIED\n" + " rev CDATA #IMPLIED\n" + " base CDATA #IMPLIED>\n" + " <!ELEMENT updir EMPTY>\n" + " <!ATTLIST updir href CDATA #REQUIRED>\n" + " <!ELEMENT file EMPTY>\n" + " <!ATTLIST file name CDATA #REQUIRED\n" + " href CDATA #REQUIRED>\n" + " <!ELEMENT dir EMPTY>\n" + " <!ATTLIST dir name CDATA #REQUIRED\n" + " href CDATA #REQUIRED>\n" + "]>\n"; + + if (gen_html) + { + const char *title; + if (resource->info->repos_path == NULL) + title = "unknown location"; + else + title = resource->info->repos_path; + + if (resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION) + { + if (SVN_IS_VALID_REVNUM(resource->info->root.rev)) + title = apr_psprintf(pool, + "Revision %ld: %s", + resource->info->root.rev, title); + if (resource->info->repos->repo_basename) + title = apr_psprintf(pool, "%s - %s", + resource->info->repos->repo_basename, + title); + if (resource->info->repos->repo_name) + title = apr_psprintf(pool, "%s: %s", + resource->info->repos->repo_name, + title); + } + + SVN_ERR(dav_svn__brigade_printf(bb, output, + "<html><head><title>%s</title></head>\n" + "<body>\n <h2>%s</h2>\n <ul>\n", + title, title)); + } + else + { + const char *name = resource->info->repos->repo_name; + const char *href = resource->info->repos_path; + const char *base = resource->info->repos->repo_basename; + + SVN_ERR(dav_svn__brigade_puts(bb, output, "<?xml version=\"1.0\"?>\n")); + SVN_ERR(dav_svn__brigade_printf(bb, output, + "<?xml-stylesheet type=\"text/xsl\" " + "href=\"%s\"?>\n", + resource->info->repos->xslt_uri)); + SVN_ERR(dav_svn__brigade_puts(bb, output, xml_index_dtd)); + SVN_ERR(dav_svn__brigade_puts(bb, output, + "<svn version=\"" SVN_VERSION "\"\n" + " href=\"http://subversion.apache.org/\">\n")); + SVN_ERR(dav_svn__brigade_puts(bb, output, " <index")); + + if (name) + SVN_ERR(dav_svn__brigade_printf(bb, output, + " name=\"%s\"", + apr_xml_quote_string(resource->pool, + name, 1))); + if (SVN_IS_VALID_REVNUM(resource->info->root.rev)) + SVN_ERR(dav_svn__brigade_printf(bb, output, " rev=\"%ld\"", + resource->info->root.rev)); + if (href) + SVN_ERR(dav_svn__brigade_printf(bb, output, " path=\"%s\"", + apr_xml_quote_string(resource->pool, + href, 1))); + if (base) + SVN_ERR(dav_svn__brigade_printf(bb, output, " base=\"%s\"", base)); + + SVN_ERR(dav_svn__brigade_puts(bb, output, ">\n")); + } + + if ((resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION) + && resource->info->repos_path + && ((resource->info->repos_path[1] != '\0') + || dav_svn__get_list_parentpath_flag(resource->info->r))) + { + const char *href; + if (resource->info->pegged) + { + href = apr_psprintf(pool, "../?p=%ld", resource->info->root.rev); + } + else + { + href = "../"; + } + + if (gen_html) + { + SVN_ERR(dav_svn__brigade_printf(bb, output, + " <li><a href=\"%s\">..</a></li>\n", + href)); + } + else + { + SVN_ERR(dav_svn__brigade_printf(bb, output, + " <updir href=\"%s\"/>\n", + href)); + } + } + + return SVN_NO_ERROR; +} + + +static svn_error_t * +emit_collection_entry(const dav_resource *resource, + apr_bucket_brigade *bb, + dav_svn__output *output, + const svn_fs_dirent_t *entry, + svn_boolean_t gen_html, + apr_pool_t *pool) +{ + const char *name = entry->name; + const char *href = name; + svn_boolean_t is_dir = (entry->kind == svn_node_dir); + + /* append a trailing slash onto the name for directories. we NEED + this for the href portion so that the relative reference will + descend properly. for the visible portion, it is just nice. */ + /* ### The xml output doesn't like to see a trailing slash on + ### the visible portion, so avoid that. */ + if (is_dir) + href = apr_pstrcat(pool, href, "/", SVN_VA_NULL); + + if (gen_html) + name = href; + + /* We quote special characters in both XML and HTML. */ + name = apr_xml_quote_string(pool, name, !gen_html); + + /* According to httpd-2.0.54/include/httpd.h, ap_os_escape_path() + behaves differently on different platforms. It claims to + "convert an OS path to a URL in an OS dependant way". + Nevertheless, there appears to be only one implementation + of the function in httpd, and the code seems completely + platform independent, so we'll assume it's appropriate for + mod_dav_svn to use it to quote outbound paths. */ + href = ap_os_escape_path(pool, href, 0); + href = apr_xml_quote_string(pool, href, 1); + + if (gen_html) + { + /* If our directory was access using the public peg-rev + CGI query interface, we'll let its dirents carry that + peg-rev, too. */ + if (resource->info->pegged) + { + SVN_ERR(dav_svn__brigade_printf(bb, output, + " <li><a href=\"%s?p=%ld\">%s</a></li>\n", + href, resource->info->root.rev, name)); + } + else + { + SVN_ERR(dav_svn__brigade_printf(bb, output, + " <li><a href=\"%s\">%s</a></li>\n", + href, name)); + } + } + else + { + const char *const tag = (is_dir ? "dir" : "file"); + + /* This is where we could search for props */ + + /* If our directory was access using the public peg-rev + CGI query interface, we'll let its dirents carry that + peg-rev, too. */ + if (resource->info->pegged) + { + SVN_ERR(dav_svn__brigade_printf(bb, output, + " <%s name=\"%s\" href=\"%s?p=%ld\" />\n", + tag, name, href, resource->info->root.rev)); + } + else + { + SVN_ERR(dav_svn__brigade_printf(bb, output, + " <%s name=\"%s\" href=\"%s\" />\n", + tag, name, href)); + } + } + + return SVN_NO_ERROR; +} + + +static svn_error_t * +emit_collection_tail(const dav_resource *resource, + apr_bucket_brigade *bb, + dav_svn__output *output, + svn_boolean_t gen_html, + apr_pool_t *pool) +{ + if (gen_html) + { + if (strcmp(ap_psignature("FOO", resource->info->r), "") != 0) + { + /* Apache's signature generation code didn't eat our prefix. + ServerSignature must be enabled. Print our version info. + + WARNING: This is a kludge!! ap_psignature() doesn't promise + to return the empty string when ServerSignature is off. We + know it does by code inspection, but this behavior is subject + to change. (Perhaps we should try to get the Apache folks to + make this promise, though. Seems harmless/useful enough...) + */ + SVN_ERR(dav_svn__brigade_puts(bb, output, + " </ul>\n <hr noshade><em>Powered by " + "<a href=\"http://subversion.apache.org/\">" + "Apache Subversion" + "</a> version " SVN_VERSION "." + "</em>\n</body></html>")); + } + else + SVN_ERR(dav_svn__brigade_puts(bb, output, " </ul>\n</body></html>")); + } + else + SVN_ERR(dav_svn__brigade_puts(bb, output, " </index>\n</svn>\n")); return SVN_NO_ERROR; } static dav_error * -deliver(const dav_resource *resource, ap_filter_t *output) +deliver(const dav_resource *resource, ap_filter_t *unused) { svn_error_t *serr; apr_bucket_brigade *bb; apr_bucket *bkt; - apr_status_t status; + dav_svn__output *output; /* Check resource type */ if (resource->baselined @@ -3225,39 +3538,17 @@ deliver(const dav_resource *resource, ap_filter_t *output) "Cannot GET this type of resource."); } + output = dav_svn__output_create(resource->info->r, resource->pool); + if (resource->collection) { const int gen_html = !resource->info->repos->xslt_uri; apr_hash_t *entries; - apr_pool_t *entry_pool; + apr_pool_t *iterpool; apr_array_header_t *sorted; svn_revnum_t dir_rev = SVN_INVALID_REVNUM; int i; - /* XML schema for the directory index if xslt_uri is set: - - <?xml version="1.0"?> - <?xml-stylesheet type="text/xsl" href="[info->repos->xslt_uri]"?> */ - static const char xml_index_dtd[] = - "<!DOCTYPE svn [\n" - " <!ELEMENT svn (index)>\n" - " <!ATTLIST svn version CDATA #REQUIRED\n" - " href CDATA #REQUIRED>\n" - " <!ELEMENT index (updir?, (file | dir)*)>\n" - " <!ATTLIST index name CDATA #IMPLIED\n" - " path CDATA #IMPLIED\n" - " rev CDATA #IMPLIED\n" - " base CDATA #IMPLIED>\n" - " <!ELEMENT updir EMPTY>\n" - " <!ATTLIST updir href CDATA #REQUIRED>\n" - " <!ELEMENT file EMPTY>\n" - " <!ATTLIST file name CDATA #REQUIRED\n" - " href CDATA #REQUIRED>\n" - " <!ELEMENT dir EMPTY>\n" - " <!ATTLIST dir name CDATA #REQUIRED\n" - " href CDATA #REQUIRED>\n" - "]>\n"; - /* <svn version="1.3.0 (dev-build)" href="http://subversion.apache.org"> <index name="[info->repos->repo_name]" @@ -3338,99 +3629,21 @@ deliver(const dav_resource *resource, ap_filter_t *output) resource->pool); } - bb = apr_brigade_create(resource->pool, output->c->bucket_alloc); - - if (gen_html) - { - const char *title; - if (resource->info->repos_path == NULL) - title = "unknown location"; - else - title = resource->info->repos_path; - - if (resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION) - { - if (SVN_IS_VALID_REVNUM(resource->info->root.rev)) - title = apr_psprintf(resource->pool, - "Revision %ld: %s", - resource->info->root.rev, title); - if (resource->info->repos->repo_basename) - title = apr_psprintf(resource->pool, "%s - %s", - resource->info->repos->repo_basename, - title); - if (resource->info->repos->repo_name) - title = apr_psprintf(resource->pool, "%s: %s", - resource->info->repos->repo_name, - title); - } - - ap_fprintf(output, bb, "<html><head><title>%s</title></head>\n" - "<body>\n <h2>%s</h2>\n <ul>\n", title, title); - } - else - { - const char *name = resource->info->repos->repo_name; - const char *href = resource->info->repos_path; - const char *base = resource->info->repos->repo_basename; - - ap_fputs(output, bb, "<?xml version=\"1.0\"?>\n"); - ap_fprintf(output, bb, - "<?xml-stylesheet type=\"text/xsl\" href=\"%s\"?>\n", - resource->info->repos->xslt_uri); - ap_fputs(output, bb, xml_index_dtd); - ap_fputs(output, bb, - "<svn version=\"" SVN_VERSION "\"\n" - " href=\"http://subversion.apache.org/\">\n"); - ap_fputs(output, bb, " <index"); - if (name) - ap_fprintf(output, bb, " name=\"%s\"", - apr_xml_quote_string(resource->pool, name, 1)); - if (SVN_IS_VALID_REVNUM(resource->info->root.rev)) - ap_fprintf(output, bb, " rev=\"%ld\"", - resource->info->root.rev); - if (href) - ap_fprintf(output, bb, " path=\"%s\"", - apr_xml_quote_string(resource->pool, - href, - 1)); - if (base) - ap_fprintf(output, bb, " base=\"%s\"", base); - - ap_fputs(output, bb, ">\n"); - } - - if ((resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION) - && resource->info->repos_path - && ((resource->info->repos_path[1] != '\0') - || dav_svn__get_list_parentpath_flag(resource->info->r))) - { - const char *href; - if (resource->info->pegged) - { - href = apr_psprintf(resource->pool, "../?p=%ld", - resource->info->root.rev); - } - else - { - href = "../"; - } + bb = apr_brigade_create(resource->pool, + dav_svn__output_get_bucket_alloc(output)); - if (gen_html) - { - ap_fprintf(output, bb, - " <li><a href=\"%s\">..</a></li>\n", href); - } - else - { - ap_fprintf(output, bb, " <updir href=\"%s\"/>\n", href); - } - } + serr = emit_collection_head(resource, bb, output, gen_html, + resource->pool); + if (serr != NULL) + return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, + "could not output collection", + resource->pool); /* get a sorted list of the entries */ sorted = svn_sort__hash(entries, svn_sort_compare_items_as_paths, resource->pool); - entry_pool = svn_pool_create(resource->pool); + iterpool = svn_pool_create(resource->pool); for (i = 0; i < sorted->nelts; ++i) { @@ -3438,11 +3651,9 @@ deliver(const dav_resource *resource, ap_filter_t *output) const svn_sort__item_t); const svn_fs_dirent_t *entry = item->value; const char *name = item->key; - const char *href = name; - svn_boolean_t is_dir = (entry->kind == svn_node_dir); const char *repos_relpath = NULL; - svn_pool_clear(entry_pool); + svn_pool_clear(iterpool); /* DIR_REV is set to a valid revision if we're looking at the entries of a versioned directory. Otherwise, we're @@ -3450,120 +3661,45 @@ deliver(const dav_resource *resource, ap_filter_t *output) if (SVN_IS_VALID_REVNUM(dir_rev)) { repos_relpath = svn_fspath__join(resource->info->repos_path, - name, entry_pool); + name, iterpool); if (! dav_svn__allow_read(resource->info->r, resource->info->repos, repos_relpath, dir_rev, - entry_pool)) + iterpool)) continue; } else { if (! dav_svn__allow_list_repos(resource->info->r, - entry->name, entry_pool)) + entry->name, iterpool)) continue; } - /* append a trailing slash onto the name for directories. we NEED - this for the href portion so that the relative reference will - descend properly. for the visible portion, it is just nice. */ - /* ### The xml output doesn't like to see a trailing slash on - ### the visible portion, so avoid that. */ - if (is_dir) - href = apr_pstrcat(entry_pool, href, "/", (char *)NULL); - - if (gen_html) - name = href; - - /* We quote special characters in both XML and HTML. */ - name = apr_xml_quote_string(entry_pool, name, !gen_html); - - /* According to httpd-2.0.54/include/httpd.h, ap_os_escape_path() - behaves differently on different platforms. It claims to - "convert an OS path to a URL in an OS dependant way". - Nevertheless, there appears to be only one implementation - of the function in httpd, and the code seems completely - platform independent, so we'll assume it's appropriate for - mod_dav_svn to use it to quote outbound paths. */ - href = ap_os_escape_path(entry_pool, href, 0); - href = apr_xml_quote_string(entry_pool, href, 1); - - if (gen_html) - { - /* If our directory was access using the public peg-rev - CGI query interface, we'll let its dirents carry that - peg-rev, too. */ - if (resource->info->pegged) - { - ap_fprintf(output, bb, - " <li><a href=\"%s?p=%ld\">%s</a></li>\n", - href, resource->info->root.rev, name); - } - else - { - ap_fprintf(output, bb, - " <li><a href=\"%s\">%s</a></li>\n", - href, name); - } - } - else - { - const char *const tag = (is_dir ? "dir" : "file"); - - /* This is where we could search for props */ - - /* If our directory was access using the public peg-rev - CGI query interface, we'll let its dirents carry that - peg-rev, too. */ - if (resource->info->pegged) - { - ap_fprintf(output, bb, - " <%s name=\"%s\" href=\"%s?p=%ld\" />\n", - tag, name, href, resource->info->root.rev); - } - else - { - ap_fprintf(output, bb, - " <%s name=\"%s\" href=\"%s\" />\n", - tag, name, href); - } - } + serr = emit_collection_entry(resource, bb, output, entry, gen_html, + iterpool); + if (serr != NULL) + return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, + "could not output collection entry", + resource->pool); } - svn_pool_destroy(entry_pool); + svn_pool_destroy(iterpool); - if (gen_html) - { - if (strcmp(ap_psignature("FOO", resource->info->r), "") != 0) - { - /* Apache's signature generation code didn't eat our prefix. - ServerSignature must be enabled. Print our version info. - - WARNING: This is a kludge!! ap_psignature() doesn't promise - to return the empty string when ServerSignature is off. We - know it does by code inspection, but this behavior is subject - to change. (Perhaps we should try to get the Apache folks to - make this promise, though. Seems harmless/useful enough...) - */ - ap_fputs(output, bb, - " </ul>\n <hr noshade><em>Powered by " - "<a href=\"http://subversion.apache.org/\">" - "Apache Subversion" - "</a> version " SVN_VERSION "." - "</em>\n</body></html>"); - } - else - ap_fputs(output, bb, " </ul>\n</body></html>"); - } - else - ap_fputs(output, bb, " </index>\n</svn>\n"); + serr = emit_collection_tail(resource, bb, output, gen_html, + resource->pool); + if (serr != NULL) + return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, + "could not output collection", + resource->pool); - bkt = apr_bucket_eos_create(output->c->bucket_alloc); + bkt = apr_bucket_eos_create(dav_svn__output_get_bucket_alloc(output)); APR_BRIGADE_INSERT_TAIL(bb, bkt); - if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) - return dav_svn__new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0, - "Could not write EOS to filter."); + serr = dav_svn__output_pass_brigade(output, bb); + if (serr != NULL) + return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, + "Could not write EOS to filter.", + resource->pool); return NULL; } @@ -3626,10 +3762,13 @@ deliver(const dav_resource *resource, ap_filter_t *output) "could not prepare to read a delta", resource->pool); + bb = apr_brigade_create(resource->pool, + dav_svn__output_get_bucket_alloc(output)); + /* create a stream that svndiff data will be written to, which will copy it to the network */ dc.output = output; - dc.pool = resource->pool; + dc.bb = bb; o_stream = svn_stream_create(&dc, resource->pool); svn_stream_set_write(o_stream, write_to_filter); svn_stream_set_close(o_stream, close_filter); @@ -3645,6 +3784,8 @@ deliver(const dav_resource *resource, ap_filter_t *output) to the network. */ serr = svn_txdelta_send_txstream(txd_stream, handler, h_baton, resource->pool); + apr_brigade_destroy(bb); + if (serr != NULL) return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "could not deliver the txdelta stream", @@ -3723,11 +3864,11 @@ deliver(const dav_resource *resource, ap_filter_t *output) resource->info->repos->base_url, ap_escape_uri(resource->pool, resource->info->r->uri), - NULL); + SVN_VA_NULL); str_root = apr_pstrcat(resource->pool, resource->info->repos->base_url, resource->info->repos->root_path, - NULL); + SVN_VA_NULL); serr = svn_subst_build_keywords3(&kw, keywords->data, str_cmt_rev, str_uri, str_root, @@ -3750,13 +3891,17 @@ deliver(const dav_resource *resource, ap_filter_t *output) ### which will read from the FS stream on demand */ block = apr_palloc(resource->pool, SVN__STREAM_CHUNK_SIZE); + bb = apr_brigade_create(resource->pool, + dav_svn__output_get_bucket_alloc(output)); + while (1) { apr_size_t bufsize = SVN__STREAM_CHUNK_SIZE; /* read from the FS ... */ - serr = svn_stream_read(stream, block, &bufsize); + serr = svn_stream_read_full(stream, block, &bufsize); if (serr != NULL) { + apr_brigade_destroy(bb); return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "could not read the file contents", resource->pool); @@ -3764,30 +3909,35 @@ deliver(const dav_resource *resource, ap_filter_t *output) if (bufsize == 0) break; - /* build a brigade and write to the filter ... */ - bb = apr_brigade_create(resource->pool, output->c->bucket_alloc); - bkt = apr_bucket_transient_create(block, bufsize, - output->c->bucket_alloc); + /* write to the filter ... */ + bkt = apr_bucket_transient_create( + block, bufsize, dav_svn__output_get_bucket_alloc(output)); APR_BRIGADE_INSERT_TAIL(bb, bkt); - if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) { - /* ### what to do with status; and that HTTP code... */ - return dav_svn__new_error(resource->pool, - HTTP_INTERNAL_SERVER_ERROR, 0, - "Could not write data to filter."); - } + serr = dav_svn__output_pass_brigade(output, bb); + if (serr != NULL) + { + apr_brigade_destroy(bb); + /* ### that HTTP code... */ + return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, + "Could not write data to filter.", + resource->pool); + } } /* done with the file. write an EOS bucket now. */ - bb = apr_brigade_create(resource->pool, output->c->bucket_alloc); - bkt = apr_bucket_eos_create(output->c->bucket_alloc); + bkt = apr_bucket_eos_create(dav_svn__output_get_bucket_alloc(output)); APR_BRIGADE_INSERT_TAIL(bb, bkt); - if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) { - /* ### what to do with status; and that HTTP code... */ - return dav_svn__new_error(resource->pool, - HTTP_INTERNAL_SERVER_ERROR, 0, - "Could not write EOS to filter."); - } + serr = dav_svn__output_pass_brigade(output, bb); + if (serr != NULL) + { + apr_brigade_destroy(bb); + /* ### that HTTP code... */ + return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, + "Could not write EOS to filter.", + resource->pool); + } + apr_brigade_destroy(bb); return NULL; } } @@ -3904,23 +4054,28 @@ copy_resource(const dav_resource *src, return err; } - serr = svn_dirent_get_absolute(&src_repos_path, - svn_repos_path(src->info->repos->repos, - src->pool), - src->pool); - if (!serr) - serr = svn_dirent_get_absolute(&dst_repos_path, - svn_repos_path(dst->info->repos->repos, - dst->pool), - dst->pool); + src_repos_path = svn_repos_path(src->info->repos->repos, src->pool); + dst_repos_path = svn_repos_path(dst->info->repos->repos, dst->pool); + + if (strcmp(src_repos_path, dst_repos_path) != 0) + { + /* Perhaps the source and dst repos use different path formats? */ + serr = svn_error_compose_create( + svn_dirent_get_absolute(&src_repos_path, src_repos_path, + src->pool), + svn_dirent_get_absolute(&dst_repos_path, dst_repos_path, + dst->pool)); + + if (!serr && (strcmp(src_repos_path, dst_repos_path) != 0)) + return dav_svn__new_error_svn( + dst->pool, HTTP_INTERNAL_SERVER_ERROR, 0, + "Copy source and destination are in different repositories"); + } + else + serr = SVN_NO_ERROR; if (!serr) { - if (strcmp(src_repos_path, dst_repos_path) != 0) - return dav_svn__new_error_tag - (dst->pool, HTTP_INTERNAL_SERVER_ERROR, 0, - "Copy source and destination are in different repositories.", - SVN_DAV_ERROR_NAMESPACE, SVN_DAV_ERROR_TAG); serr = svn_fs_copy(src->info->root.root, /* root object of src rev*/ src->info->repos_path, /* relative path of src */ dst->info->root.root, /* root object of dst txn*/ @@ -4030,7 +4185,11 @@ remove_resource(dav_resource *resource, dav_response **response) if (resource->info->version_name < created_rev) { serr = svn_error_createf(SVN_ERR_RA_OUT_OF_DATE, NULL, - "Item '%s' is out of date", + resource->collection + ? "Directory '%s' is out of date" + : (resource->exists + ? "File '%s' is out of date" + : "'%s' is out of date"), resource->info->repos_path); return dav_svn__convert_err(serr, HTTP_CONFLICT, "Can't DELETE out-of-date resource", @@ -4042,8 +4201,9 @@ remove_resource(dav_resource *resource, dav_response **response) incoming lock-tokens into the filesystem's access_t. Normally they come in via 'If:' header, and get_resource() automatically notices them and does this work for us. In the - case of a directory deletion, however, svn clients are sending - 'child' lock-tokens in the DELETE request body. */ + case of a directory deletion, however, older subversion clients + are sending 'child' lock-tokens in the non-standard DELETE + request body. */ err = dav_svn__build_lock_hash(&locks, resource->info->r, resource->info->repos_path, resource->pool); @@ -4157,10 +4317,15 @@ typedef struct walker_ctx_t { } walker_ctx_t; - +/* Recursively walk a resource for walk(). When DEPTH != 0, recurse with + DEPTH-1 on child nodes. WALK_ROOT should be TRUE for the root and will be + FALSE for any descendants, to avoid unneeded work for every descendant + node. + */ static dav_error * do_walk(walker_ctx_t *ctx, int depth, + svn_boolean_t walk_root, apr_pool_t *scratch_pool) { const dav_walk_params *params = ctx->params; @@ -4225,16 +4390,19 @@ do_walk(walker_ctx_t *ctx, uri_len = ctx->uri->len; repos_len = ctx->repos_path->len; - /* Tell our logging subsystem that we're listing a directory. - - Note: if we cared, we could look at the 'User-Agent:' request - header and distinguish an svn client ('svn ls') from a generic - DAV client. */ - dav_svn__operational_log(&ctx->info, - svn_log__get_dir(ctx->info.repos_path, - ctx->info.root.rev, - TRUE, FALSE, SVN_DIRENT_ALL, - scratch_pool)); + if (walk_root) + { + /* Tell our logging subsystem that we're listing a directory. + + Note: if we cared, we could look at the 'User-Agent:' request + header and distinguish an svn client ('svn ls') from a generic + DAV client. */ + dav_svn__operational_log(&ctx->info, + svn_log__get_dir(ctx->info.repos_path, + ctx->info.root.rev, + TRUE, FALSE, SVN_DIRENT_ALL, + scratch_pool)); + } /* fetch this collection's children */ serr = svn_fs_dir_entries(&children, ctx->info.root.root, @@ -4267,7 +4435,7 @@ do_walk(walker_ctx_t *ctx, apr_pstrmemdup(iterpool, ctx->repos_path->data, ctx->repos_path->len), - key, (char *)NULL); + key, SVN_VA_NULL); if (! dav_svn__allow_read(ctx->info.r, ctx->info.repos, repos_relpath, ctx->info.root.rev, iterpool)) @@ -4287,7 +4455,10 @@ do_walk(walker_ctx_t *ctx, { err = (*params->func)(&ctx->wres, DAV_CALLTYPE_MEMBER); if (err != NULL) - return err; + { + svn_pool_destroy(iterpool); + return err; + } } else { @@ -4299,9 +4470,12 @@ do_walk(walker_ctx_t *ctx, ctx->res.uri = ctx->uri->data; /* recurse on this collection */ - err = do_walk(ctx, depth - 1, iterpool); + err = do_walk(ctx, depth - 1, FALSE, iterpool); if (err != NULL) - return err; + { + svn_pool_destroy(iterpool); + return err; + } /* restore the data */ ctx->res.collection = FALSE; @@ -4381,7 +4555,7 @@ walk(const dav_walk_params *params, int depth, dav_response **response) /* ### is the root already/always open? need to verify */ /* always return the error, and any/all multistatus responses */ - err = do_walk(&ctx, depth, params->pool); + err = do_walk(&ctx, depth, TRUE, params->pool); *response = ctx.wres.response; return err; @@ -4428,7 +4602,7 @@ dav_svn__create_working_resource(dav_resource *base, if (base->info->repos->root_path[1]) res->uri = apr_pstrcat(base->pool, base->info->repos->root_path, - path, (char *)NULL); + path, SVN_VA_NULL); else res->uri = path; res->hooks = &dav_svn__hooks_repository; @@ -4481,7 +4655,7 @@ dav_svn__working_to_regular_resource(dav_resource *resource) /* if rev was specific, create baseline-collection URL */ path = dav_svn__build_uri(repos, DAV_SVN__BUILD_URI_BC, priv->root.rev, priv->repos_path, - 0, resource->pool); + FALSE /* add_href */, resource->pool); } path = svn_path_uri_encode(path, resource->pool); priv->uri_path = svn_stringbuf_create(path, resource->pool); @@ -4526,7 +4700,7 @@ dav_svn__create_version_resource(dav_resource **version_res, static dav_error * handle_post_request(request_rec *r, dav_resource *resource, - ap_filter_t *output) + dav_svn__output *output) { svn_skel_t *request_skel, *post_skel; int status; @@ -4609,7 +4783,9 @@ int dav_svn__method_post(request_rec *r) content_type = apr_table_get(r->headers_in, "content-type"); if (content_type && (strcmp(content_type, SVN_SKEL_MIME_TYPE) == 0)) { - derr = handle_post_request(r, resource, r->output_filters); + dav_svn__output *output = dav_svn__output_create(resource->info->r, + resource->pool); + derr = handle_post_request(r, resource, output); } else { |