On 04/23/2010 11:24 PM, Stefan Sperling wrote:
On Fri, Apr 23, 2010 at 12:46:44PM -0400, C. Michael Pilato wrote:
As I noted, the check needs to happen inside the client API. The question
becomes "Do we patch svn_client_addX() and svn_client_statusX() and
svn_client_updateX() and ..." or is there a more general fix to be made
(like perhaps a wrapper around svn_dirent_get_absolute() that gracefully
handles the my-caller-passed-a-url case)?
I think the problem is that the CLI client passes non-canonicalized
paths to the client library.
If we make the CLI client canonicalize its arguments using
svn_dirent_canonicalize(), the assertion does not trigger.
This makes sense since the 'svn add' command only accepts local working
copy paths, so it should try to interpret its arguments as such.
With the patch below I get an error such as:
$ svn add ^/
svn: warning: '/path/to/working/copy/file:/tmp/svn-sandbox/repos' not found
(The repository lives in "/tmp/svn-sandbox/repos".)
Do others agree? If so, I'd like to ask Uwe to send similar patches
for other subcommands, making sure they canonicalize their arguments
correctly.
[...]
Meanwhile Michael Pilato and Stefan Sperling gave me even
more input (thank you) and I was able to prepare a suite of tests
modeled after others in subversion/tests in order to check most
of the other commands which, like 'add' and 'status', only accept
paths or URLs in certain argument positions.
Nearly all tests are now marked as XFail() [and the error message
which the tests expect is debatable], but I intend to follow up with
patches that will make them succeed.
The first patch, attached for review and criticism, changes only
the CLI commands. It is mostly boring, adding new checks except
in checkout-cmd.c. (Note that the checkout test fails before and
after the patch because of something happening inside
svn_cl__args_to_target_array_print_reserved() that I couldn't
explain yet.)
Now, since it has been a while I will try to sum up what has been
suggested - (feel free to skip to the end)...
a) Validating all input in the libsvn_client library and gracefully
handling any errors instead of triggering an assertion allows the
client to recover. I think everyone agreed that this is good.
--- subversion/libsvn_client/add.c (revision 953325)
+++ subversion/libsvn_client/add.c (working copy)
@@ -593,6 +593,12 @@
const char *local_abspath;
struct add_with_write_lock_baton baton;
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_WC_BAD_PATH,
+ NULL,
+ _("Path '%s' is not a working copy path"),
+ path);
+
SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
/* ### this is a hack.
b) Checking the arguments in the CLI commands before calling
libsvn_client functions makes it possible to abort early and not
in the middle of processing the command.
--- subversion/svn/add-cmd.c (revision 953325)
+++ subversion/svn/add-cmd.c (working copy)
@@ -66,6 +68,17 @@
SVN_ERR(svn_opt_eat_peg_revisions(&targets, targets, pool));
+ /* Check that no targets are URLs */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ if (svn_path_is_url(target))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR,
+ NULL,
+ _("Path '%s' is not a working copy
path"),
+ target);
+ }
+
subpool = svn_pool_create(pool);
for (i = 0; i < targets->nelts; i++)
{
But svn_path_is_url() only does a syntactic check, and ^/ (or the
expanded URL) is in fact a syntactically correct POSIX pathname.
That is why c) also appeals to me.
c) Stefan's suggested fix through canonicalization of path arguments
produces an error message that seems more rational to me than, say,
"Path '%s' is not a working copy path, but a URL" - since even a URL
is a syntactically correct pathname (on POSIX systems). However,
this would imply that target arguments aren't parsed and interpreted
in a uniform way and could lead to confusion? That is why d) also
seems plausible.
(citing Stefan)
Index: subversion/svn/add-cmd.c
===================================================================
--- subversion/svn/add-cmd.c (revision 937185)
+++ subversion/svn/add-cmd.c (working copy)
@@ -30,6 +30,7 @@
#include<apr_want.h>
#include "svn_client.h"
+#include "svn_dirent_uri.h"
#include "svn_error.h"
#include "svn_pools.h"
#include "cl.h"
@@ -70,11 +71,15 @@ svn_cl__add(apr_getopt_t *os,
for (i = 0; i< targets->nelts; i++)
{
const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ const char *canon_target;
svn_pool_clear(subpool);
+
SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ canon_target = svn_dirent_canonicalize(target, subpool);
SVN_ERR(svn_cl__try
- (svn_client_add4(target,
+ (svn_client_add4(canon_target,
opt_state->depth,
opt_state->force, opt_state->no_ignore,
opt_state->parents, ctx, subpool),
d) With consistent expansion of ^/ to the repository URL and consistent
parsing of @peg-revisions in all target arguments, and behavioral
differences depending on the kind of target given, I could understand
a policy that treats pathnames which look like URLs as syntactically
incorrect and rejects them where only local pathnames are expected
and vice versa.
I'm unsure, however, whether c) path canonicalization, can be entirely
dismissed. I don't know the library interface contracts well enough yet.
Cheers (sorry for the delay),
Uwe Stuehler
Index: subversion/svn/checkout-cmd.c
===================================================================
--- subversion/svn/checkout-cmd.c (revision 953325)
+++ subversion/svn/checkout-cmd.c (working copy)
@@ -116,6 +116,16 @@
SVN_ERR(svn_cl__get_notifier(&ctx->notify_func2, &ctx->notify_baton2, TRUE,
FALSE, FALSE, pool));
+ /* Validate the REPOS_URL(s) */
+ for (i = 0; i < targets->nelts - 1; ++i)
+ {
+ repos_url = APR_ARRAY_IDX(targets, i, const char *);
+ if (! svn_path_is_url(repos_url))
+ return svn_error_createf
+ (SVN_ERR_BAD_URL, NULL,
+ _("'%s' does not appear to be a URL"), repos_url);
+ }
+
subpool = svn_pool_create(pool);
for (i = 0; i < targets->nelts - 1; ++i)
{
@@ -128,12 +138,7 @@
SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
- /* Validate the REPOS_URL */
repos_url = APR_ARRAY_IDX(targets, i, const char *);
- if (! svn_path_is_url(repos_url))
- return svn_error_createf
- (SVN_ERR_BAD_URL, NULL,
- _("'%s' does not appear to be a URL"), repos_url);
/* Get a possible peg revision. */
SVN_ERR(svn_opt_parse_path(&peg_revision, &true_url, repos_url,
Index: subversion/svn/revert-cmd.c
===================================================================
--- subversion/svn/revert-cmd.c (revision 953325)
+++ subversion/svn/revert-cmd.c (working copy)
@@ -30,6 +30,7 @@
#include "svn_client.h"
#include "svn_error_codes.h"
#include "svn_error.h"
+#include "svn_path.h"
#include "cl.h"
#include "svn_private_config.h"
@@ -47,6 +48,7 @@
svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
apr_array_header_t *targets = NULL;
svn_error_t *err;
+ int i;
SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
opt_state->targets,
@@ -67,6 +69,17 @@
SVN_ERR(svn_opt_eat_peg_revisions(&targets, targets, scratch_pool));
+ /* Check that no targets are URLs */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ if (svn_path_is_url(target))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR,
+ NULL,
+ _("Path '%s' is not a working copy path"),
+ target);
+ }
+
err = svn_client_revert2(targets, opt_state->depth,
opt_state->changelists, ctx, scratch_pool);
Index: subversion/svn/changelist-cmd.c
===================================================================
--- subversion/svn/changelist-cmd.c (revision 953325)
+++ subversion/svn/changelist-cmd.c (working copy)
@@ -24,6 +24,7 @@
#include "svn_client.h"
#include "svn_error_codes.h"
#include "svn_error.h"
+#include "svn_path.h"
#include "svn_utf.h"
#include "cl.h"
@@ -44,6 +45,7 @@
svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
apr_array_header_t *targets;
svn_depth_t depth = opt_state->depth;
+ int i;
/* If we're not removing changelists, then our first argument should
be the name of a changelist. */
@@ -99,6 +101,17 @@
SVN_ERR(svn_opt_eat_peg_revisions(&targets, targets, pool));
+ /* Check that no targets are URLs */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ if (svn_path_is_url(target))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR,
+ NULL,
+ _("Path '%s' is not a working copy path"),
+ target);
+ }
+
if (changelist_name)
{
return svn_cl__try
Index: subversion/svn/resolved-cmd.c
===================================================================
--- subversion/svn/resolved-cmd.c (revision 953325)
+++ subversion/svn/resolved-cmd.c (working copy)
@@ -31,6 +31,7 @@
#include "svn_client.h"
#include "svn_error.h"
+#include "svn_path.h"
#include "svn_pools.h"
#include "cl.h"
@@ -68,6 +69,17 @@
SVN_ERR(svn_opt_eat_peg_revisions(&targets, targets, scratch_pool));
+ /* Check that no targets are URLs */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ if (svn_path_is_url(target))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR,
+ NULL,
+ _("Path '%s' is not a working copy path"),
+ target);
+ }
+
iterpool = svn_pool_create(scratch_pool);
for (i = 0; i < targets->nelts; i++)
{
Index: subversion/svn/cleanup-cmd.c
===================================================================
--- subversion/svn/cleanup-cmd.c (revision 953325)
+++ subversion/svn/cleanup-cmd.c (working copy)
@@ -28,10 +28,12 @@
/*** Includes. ***/
#include "svn_client.h"
+#include "svn_error.h"
#include "svn_pools.h"
-#include "svn_error.h"
+#include "svn_path.h"
#include "cl.h"
+#include "svn_private_config.h"
/*** Code. ***/
@@ -57,6 +59,17 @@
SVN_ERR(svn_opt_eat_peg_revisions(&targets, targets, pool));
+ /* Check that no targets are URLs */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ if (svn_path_is_url(target))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR,
+ NULL,
+ _("Path '%s' is not a working copy path"),
+ target);
+ }
+
subpool = svn_pool_create(pool);
for (i = 0; i < targets->nelts; i++)
{
Index: subversion/svn/upgrade-cmd.c
===================================================================
--- subversion/svn/upgrade-cmd.c (revision 953325)
+++ subversion/svn/upgrade-cmd.c (working copy)
@@ -31,6 +31,7 @@
#include "svn_client.h"
#include "svn_error_codes.h"
#include "svn_error.h"
+#include "svn_path.h"
#include "cl.h"
#include "svn_private_config.h"
@@ -63,6 +64,17 @@
SVN_ERR(svn_opt_eat_peg_revisions(&targets, targets, scratch_pool));
+ /* Check that no targets are URLs */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ if (svn_path_is_url(target))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR,
+ NULL,
+ _("Path '%s' is not a working copy path"),
+ target);
+ }
+
iterpool = svn_pool_create(scratch_pool);
for (i = 0; i < targets->nelts; i++)
{
Index: subversion/svn/add-cmd.c
===================================================================
--- subversion/svn/add-cmd.c (revision 953325)
+++ subversion/svn/add-cmd.c (working copy)
@@ -31,9 +31,11 @@
#include "svn_client.h"
#include "svn_error.h"
+#include "svn_path.h"
#include "svn_pools.h"
#include "cl.h"
+#include "svn_private_config.h"
/*** Code. ***/
@@ -66,6 +68,17 @@
SVN_ERR(svn_opt_eat_peg_revisions(&targets, targets, pool));
+ /* Check that no targets are URLs */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ if (svn_path_is_url(target))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR,
+ NULL,
+ _("Path '%s' is not a working copy path"),
+ target);
+ }
+
subpool = svn_pool_create(pool);
for (i = 0; i < targets->nelts; i++)
{
Index: subversion/svn/switch-cmd.c
===================================================================
--- subversion/svn/switch-cmd.c (revision 953325)
+++ subversion/svn/switch-cmd.c (working copy)
@@ -65,6 +65,17 @@
subpool = svn_pool_create(pool);
+ /* Check that no targets are URLs */
+ for (i = 2; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ if (svn_path_is_url(target))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR,
+ NULL,
+ _("Path '%s' is not a working copy path"),
+ target);
+ }
+
if (targets->nelts == 2)
{
SVN_ERR(svn_client_relocate("", from, to, recurse, ctx, pool));
@@ -145,6 +156,13 @@
/* Canonicalize the URL. */
switch_url = svn_uri_canonicalize(switch_url, scratch_pool);
+ /* Check that the target isn't a URL */
+ if (svn_path_is_url(target))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR,
+ NULL,
+ _("Path '%s' is not a working copy path"),
+ target);
+
if (! opt_state->quiet)
SVN_ERR(svn_cl__get_notifier(&ctx->notify_func2, &ctx->notify_baton2,
FALSE, FALSE, FALSE, scratch_pool));
Index: subversion/svn/import-cmd.c
===================================================================
--- subversion/svn/import-cmd.c (revision 953325)
+++ subversion/svn/import-cmd.c (working copy)
@@ -101,6 +101,11 @@
url = APR_ARRAY_IDX(targets, 1, const char *);
}
+ if (svn_path_is_url(path))
+ return svn_error_createf
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Invalid path '%s'"), path);
+
if (! svn_path_is_url(url))
return svn_error_createf
(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
Index: subversion/svn/resolve-cmd.c
===================================================================
--- subversion/svn/resolve-cmd.c (revision 953325)
+++ subversion/svn/resolve-cmd.c (working copy)
@@ -31,6 +31,7 @@
#include "svn_client.h"
#include "svn_error.h"
+#include "svn_path.h"
#include "svn_pools.h"
#include "cl.h"
@@ -97,6 +98,17 @@
SVN_ERR(svn_opt_eat_peg_revisions(&targets, targets, scratch_pool));
+ /* Check that no targets are URLs */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ if (svn_path_is_url(target))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR,
+ NULL,
+ _("Path '%s' is not a working copy path"),
+ target);
+ }
+
iterpool = svn_pool_create(scratch_pool);
for (i = 0; i < targets->nelts; i++)
{
Index: subversion/svn/status-cmd.c
===================================================================
--- subversion/svn/status-cmd.c (revision 953325)
+++ subversion/svn/status-cmd.c (working copy)
@@ -292,6 +292,16 @@
SVN_ERR(svn_opt_eat_peg_revisions(&targets, targets, scratch_pool));
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ if (svn_path_is_url(target))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR,
+ NULL,
+ _("Path '%s' is not a working copy path"),
+ target);
+ }
+
iterpool = svn_pool_create(scratch_pool);
for (i = 0; i < targets->nelts; i++)
{
#!/usr/bin/env python
#
# patharg_tests.py: testing path vs. URL argument handling.
#
# Subversion is a tool for revision control.
# See http://subversion.apache.org for more information.
#
# ====================================================================
# 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.
######################################################################
# General modules
import string, sys, os, re
# Our testing module
import svntest
# (abbreviation)
Skip = svntest.testcase.Skip
SkipUnless = svntest.testcase.SkipUnless
XFail = svntest.testcase.XFail
Item = svntest.wc.StateItem
######################################################################
# Utilities
# Common URL targets to pass where only path arguments are expected.
_url_targets = ['file:///', '^/']
# Regular expressions for 'svn changelist' output.
_re_cl_bad_path = "svn: Path '.*' is not a working copy path"
_re_cl_co_bad_url = "svn: '.*' does not appear to be a URL"
_re_cl_ci_bad_path = "svn: Must give local path \(not URL\) as the target of a commit"
_re_cl_import_bad_path = "svn: Invalid path '.*'"
def run_and_verify_svn_in_wc(sbox, expected_stderr, *varargs):
"""Like svntest.actions.run_and_verify_svn, but temporarily
changes the current working directory to the sandboxes'
working copy and only checks the expected error output."""
wc_dir = sbox.wc_dir
old_dir = os.getcwd()
try:
os.chdir(wc_dir)
svntest.actions.run_and_verify_svn(None, [], expected_stderr,
*varargs)
finally:
os.chdir(old_dir)
######################################################################
# Tests
#
# Each test must return on success or raise on failure.
#----------------------------------------------------------------------
def add(sbox):
"non-working copy paths for 'add'"
sbox.build()
for target in _url_targets:
run_and_verify_svn_in_wc(sbox, _re_cl_bad_path, 'add', target)
def changelist(sbox):
"non-working copy paths for 'changelist'"
sbox.build()
for target in _url_targets:
run_and_verify_svn_in_wc(sbox, _re_cl_bad_path, 'changelist',
'foo', target)
run_and_verify_svn_in_wc(sbox, _re_cl_bad_path, 'changelist',
'--remove', target)
def checkout(sbox):
"non-working copy paths for 'checkout'"
sbox.build()
# Should this give out more like the other invocations?
expected_stderr = 'svn: Error parsing arguments'
run_and_verify_svn_in_wc(sbox, expected_stderr, 'checkout', 'foo')
run_and_verify_svn_in_wc(sbox, _re_cl_co_bad_url, 'checkout', 'bar', 'foo')
for target in _url_targets:
run_and_verify_svn_in_wc(sbox, _re_cl_co_bad_url, 'checkout',
target, # URL, okay
'bar', # path instead of URL, error
'foo') # path, okay
def cleanup(sbox):
"non-working copy paths for 'cleanup'"
sbox.build()
for target in _url_targets:
run_and_verify_svn_in_wc(sbox, _re_cl_bad_path, 'cleanup', target)
def commit(sbox):
"non-working copy paths for 'commit'"
sbox.build()
for target in _url_targets:
run_and_verify_svn_in_wc(sbox, _re_cl_ci_bad_path, 'commit', target)
# Renamed to avoid a collision with the Python builtin.
def import_(sbox):
"non-working copy paths for 'import'"
sbox.build()
for target in _url_targets:
run_and_verify_svn_in_wc(sbox, _re_cl_import_bad_path, 'import',
target, target)
def resolve(sbox):
"non-working copy paths for 'resolve'"
sbox.build()
for target in _url_targets:
run_and_verify_svn_in_wc(sbox, _re_cl_bad_path, 'resolve',
'--accept', 'working', target)
def resolved(sbox):
"non-working copy paths for 'resolved'"
sbox.build()
for target in _url_targets:
run_and_verify_svn_in_wc(sbox, _re_cl_bad_path, 'resolved', target)
def revert(sbox):
"non-working copy paths for 'revert'"
sbox.build()
for target in _url_targets:
run_and_verify_svn_in_wc(sbox, _re_cl_bad_path, 'revert', target)
def status(sbox):
"non-working copy paths for 'status'"
sbox.build()
for target in _url_targets:
run_and_verify_svn_in_wc(sbox, _re_cl_bad_path, 'status', target)
def switch(sbox):
"non-working copy paths for 'switch'"
sbox.build()
for target in _url_targets:
run_and_verify_svn_in_wc(sbox, _re_cl_bad_path, 'switch', target,
target)
run_and_verify_svn_in_wc(sbox, _re_cl_bad_path, 'switch',
'--relocate', target, target, target)
def upgrade(sbox):
"non-working copy paths for 'upgrade'"
sbox.build()
for target in _url_targets:
run_and_verify_svn_in_wc(sbox, _re_cl_bad_path, 'upgrade', target)
########################################################################
# Run the tests
# list all tests here, starting with None:
test_list = [ None,
XFail(add),
XFail(changelist),
#checkout, -- todo
XFail(cleanup),
commit,
#copy, -- todo
#export, -- todo
XFail(import_),
#patch, -- todo
XFail(resolve),
XFail(resolved),
XFail(revert),
XFail(status),
XFail(switch),
#update, -- todo
XFail(upgrade)
]
if __name__ == '__main__':
svntest.main.run_tests(test_list)
# NOTREACHED
### End of file.