Index: include/svn_path.h =================================================================== --- include/svn_path.h (revision 12305) +++ include/svn_path.h (working copy) @@ -356,6 +356,22 @@ const char *path2, apr_pool_t *pool); +/** @since New in 1.2. + * Check whether @a path is a valid Subversion path. + * + * A valid Subversion pathname is a UTF-8 string without control + * characters. "Valid" means Subversion can store the pathname in + * a repository. There may be other, OS-specific, limitations on what + * paths can be represented in a working copy. + * + * ASSUMPTION: @a path has already been validated as a UTF-8 string + * and no further check is necessary. + * + * Returns @c SVN_NO_ERROR if valid and @c SVN_ERR_FS_PATH_SYNTAX if + * invalid. + */ +svn_error_t *svn_path_check_valid (const char *path, apr_pool_t *pool); + /** URI/URL stuff * Index: libsvn_wc/copy.c =================================================================== --- libsvn_wc/copy.c (revision 12305) +++ libsvn_wc/copy.c (working copy) @@ -141,6 +141,7 @@ /* The 'dst_path' is simply dst_parent/dst_basename */ const char *dst_path = svn_path_join (svn_wc_adm_access_path (dst_parent), dst_basename, pool); + SVN_ERR (svn_path_check_valid (dst_path, pool)); /* Sanity check: if dst file exists already, don't allow overwrite. */ SVN_ERR (svn_io_check_path (dst_path, &dst_kind, pool)); @@ -470,6 +471,7 @@ SVN_ERR (svn_wc_adm_probe_open2 (&adm_access, NULL, src_path, FALSE, -1, pool)); + SVN_ERR (svn_path_check_valid (src_path, pool)); SVN_ERR (svn_io_check_path (src_path, &src_kind, pool)); if (src_kind == svn_node_file) Index: libsvn_wc/adm_ops.c =================================================================== --- libsvn_wc/adm_ops.c (revision 12305) +++ libsvn_wc/adm_ops.c (working copy) @@ -879,6 +879,8 @@ svn_node_kind_t kind; apr_uint32_t modify_flags = 0; svn_wc_adm_access_t *adm_access; + + SVN_ERR (svn_path_check_valid (path, pool)); /* Make sure something's there. */ SVN_ERR (svn_io_check_path (path, &kind, pool)); Index: libsvn_wc/update_editor.c =================================================================== --- libsvn_wc/update_editor.c (revision 12305) +++ libsvn_wc/update_editor.c (working copy) @@ -1014,6 +1014,7 @@ || ((! copyfrom_path) && (SVN_IS_VALID_REVNUM (copyfrom_revision)))) abort(); + SVN_ERR (svn_path_check_valid (db->path, db->pool)); /* There should be nothing with this name. */ SVN_ERR (svn_io_check_path (db->path, &kind, db->pool)); if (kind != svn_node_none) @@ -1431,10 +1432,13 @@ const svn_wc_entry_t *entry; svn_node_kind_t kind; svn_wc_adm_access_t *adm_access; + apr_pool_t *subpool; + SVN_ERR (svn_path_check_valid (path, pool)); + /* the file_pool can stick around for a *long* time, so we want to use a subpool for any temporary allocations. */ - apr_pool_t *subpool = svn_pool_create (pool); + subpool = svn_pool_create (pool); /* ### kff todo: if file is marked as removed by user, then flag a conflict in the entry and proceed. Similarly if it has changed Index: libsvn_subr/path.c =================================================================== --- libsvn_subr/path.c (revision 12305) +++ libsvn_subr/path.c (working copy) @@ -29,6 +29,7 @@ #include "svn_private_config.h" /* for SVN_PATH_LOCAL_SEPARATOR */ #include "svn_utf.h" #include "svn_io.h" /* for svn_io_stat() */ +#include "svn_ctype.h" /* The canonical empty path. Can this be changed? Well, change the empty @@ -1248,3 +1249,23 @@ else return svn_utf_cstring_to_utf8 (path_utf8, path_apr, pool); } + +svn_error_t * +svn_path_check_valid (const char *path, apr_pool_t *pool) +{ + const char *c; + + for (c = path; *c; c++) + { + if (svn_ctype_iscntrl(*c)) + { + return svn_error_createf ( + SVN_ERR_FS_PATH_SYNTAX, NULL, + _("Invalid control char. '%x' in path '%s'"), + *c, + svn_path_local_style (path, pool)); + } + } + + return SVN_NO_ERROR; +} Index: libsvn_client/copy.c =================================================================== --- libsvn_client/copy.c (revision 12305) +++ libsvn_client/copy.c (working copy) @@ -74,6 +74,8 @@ svn_wc_adm_access_t *adm_access, *src_access; svn_error_t *err; + SVN_ERR (svn_path_check_valid (src_path, pool)); + /* Verify that SRC_PATH exists. */ SVN_ERR (svn_io_check_path (src_path, &src_kind, pool)); if (src_kind == svn_node_none) Index: libsvn_client/add.c =================================================================== --- libsvn_client/add.c (revision 12305) +++ libsvn_client/add.c (working copy) @@ -209,6 +209,8 @@ svn_node_kind_t kind; svn_boolean_t is_special; + SVN_ERR (svn_path_check_valid (path, pool)); + /* add the file */ SVN_ERR (svn_wc_add (path, adm_access, NULL, SVN_INVALID_REVNUM, ctx->cancel_func, ctx->cancel_baton, @@ -276,6 +278,8 @@ svn_wc_adm_access_t *dir_access; apr_array_header_t *ignores; + SVN_ERR (svn_path_check_valid (dirname, pool)); + /* Check cancellation; note that this catches recursive calls too. */ if (ctx->cancel_func) SVN_ERR (ctx->cancel_func (ctx->cancel_baton)); Index: libsvn_client/commit.c =================================================================== --- libsvn_client/commit.c (revision 12305) +++ libsvn_client/commit.c (working copy) @@ -176,6 +176,8 @@ svn_node_kind_t kind; svn_boolean_t is_special; + SVN_ERR (svn_path_check_valid (path, pool)); + SVN_ERR (svn_io_check_special_path (path, &kind, &is_special, pool)); if (kind == svn_node_unknown) @@ -276,6 +278,8 @@ apr_hash_index_t *hi; apr_array_header_t *ignores; + SVN_ERR (svn_path_check_valid (path, pool)); + SVN_ERR (svn_wc_get_default_ignores (&ignores, ctx->config, pool)); SVN_ERR (svn_io_get_dirents (&dirents, path, pool)); Index: tests/clients/cmdline/commit_tests.py =================================================================== --- tests/clients/cmdline/commit_tests.py (revision 12305) +++ tests/clients/cmdline/commit_tests.py (working copy) @@ -857,6 +857,44 @@ #---------------------------------------------------------------------- +def tab_test(sbox): + "tab testing" + + # ripped out of commit_uri_unsafe - currently must be used with an XFail + # add test for directory + + sbox.build() + wc_dir = sbox.wc_dir + + if svntest.main.windows or sys.platform == 'cygwin': + tab_name = 'tab-path' + else: + tab_name = "tab\tpath" + + tab_path = os.path.join(wc_dir, 'A', tab_name) + svntest.main.file_append(tab_path, "This path has a tab in it.") + svntest.main.run_svn(None, 'add', '--non-recursive', tab_path) + + expected_output = svntest.wc.State(wc_dir, { + 'A/' + tab_name : Item(verb='Adding'), + }) + + expected_status = svntest.actions.get_virginal_state(wc_dir, 2) + # Items in the status list are all at rev 1 + expected_status.tweak(wc_rev=1) + + # Items in our add list will be at rev 2 + for item in expected_output.desc.keys(): + expected_status.add({ item : Item(wc_rev=2, repos_rev=2, status=' ') }) + + svntest.actions.run_and_verify_commit (wc_dir, + expected_output, + expected_status, + None, None, None, None, None, + wc_dir) + +#---------------------------------------------------------------------- + def hook_test(sbox): "hook testing" @@ -1069,17 +1107,14 @@ if svntest.main.windows or sys.platform == 'cygwin': angle_name = '_angle_' nasty_name = '#![]{}()__%' - tab_name = 'tab-path' else: angle_name = '' nasty_name = '#![]{}()<>%' - tab_name = "tab\tpath" # Make some convenient paths. hash_dir = os.path.join(wc_dir, '#hash#') nasty_dir = os.path.join(wc_dir, nasty_name) space_path = os.path.join(wc_dir, 'A', 'D', 'space path') - tab_path = os.path.join(wc_dir, 'A', 'D', 'G', tab_name) bang_path = os.path.join(wc_dir, 'A', 'D', 'H', 'bang!') bracket_path = os.path.join(wc_dir, 'A', 'D', 'H', 'bra[ket') brace_path = os.path.join(wc_dir, 'A', 'D', 'H', 'bra{e') @@ -1091,7 +1126,6 @@ os.mkdir(hash_dir) os.mkdir(nasty_dir) svntest.main.file_append(space_path, "This path has a space in it.") - svntest.main.file_append(tab_path, "This path has a tab in it.") svntest.main.file_append(bang_path, "This path has a bang in it.") svntest.main.file_append(bracket_path, "This path has a bracket in it.") svntest.main.file_append(brace_path, "This path has a brace in it.") @@ -1103,7 +1137,6 @@ add_list = [hash_dir, nasty_dir, # not xml-safe space_path, - tab_path, bang_path, bracket_path, brace_path, @@ -1119,7 +1152,6 @@ '#hash#' : Item(verb='Adding'), nasty_name : Item(verb='Adding'), 'A/D/space path' : Item(verb='Adding'), - 'A/D/G/' + tab_name : Item(verb='Adding'), 'A/D/H/bang!' : Item(verb='Adding'), 'A/D/H/bra[ket' : Item(verb='Adding'), 'A/D/H/bra{e' : Item(verb='Adding'), @@ -1891,6 +1923,7 @@ hudson_part_1_variation_2, hudson_part_2, hudson_part_2_1, + XFail(tab_test), XFail(hook_test), merge_mixed_revisions, commit_uri_unsafe, Index: libsvn_ra_svn/client.c =================================================================== --- libsvn_ra_svn/client.c (revision 12305) +++ libsvn_ra_svn/client.c (working copy) @@ -1123,6 +1123,7 @@ svn_ra_svn_conn_t *conn = sess->conn; const char *kind_word; + SVN_ERR(svn_path_check_valid(path, pool)); SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "check-path", "c(?r)", path, rev)); SVN_ERR(handle_auth_request(sess, pool)); SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "w", &kind_word)); Index: libsvn_ra_dav/props.c =================================================================== --- libsvn_ra_dav/props.c (revision 12305) +++ libsvn_ra_dav/props.c (working copy) @@ -1097,6 +1097,8 @@ svn_error_t *err; svn_boolean_t is_dir; + SVN_ERR (svn_path_check_valid (path, pool)); + /* ### For now, using svn_ra_dav__get_baseline_info() works because we only have three possibilities: dir, file, or none. When we add symlinks, we will need to do something different. Here's one