Index: subversion/include/svn_path.h =================================================================== --- subversion/include/svn_path.h (revision 19084) +++ subversion/include/svn_path.h (working copy) @@ -185,6 +185,9 @@ * separator characters, and possibly other semantically inoperative * transformations. * + * Convert the scheme and hostname to lowercase case-insensitive handling + * of hostnames ( see issue 2475 ) + * * The returned path may be statically allocated, equal to @a path, or * allocated from @a pool. */ Index: subversion/libsvn_ra_dav/session.c =================================================================== --- subversion/libsvn_ra_dav/session.c (revision 19084) +++ subversion/libsvn_ra_dav/session.c (working copy) @@ -639,15 +639,6 @@ /* we want to know if the repository is actually somewhere else */ /* ### not yet: http_redirect_register(sess, ... ); */ - /* HACK! Neon uses strcmp when checking for https, but RFC 2396 says - * we should be using case-insensitive comparisons when checking for - * URI schemes. To allow our users to use WeIrd CasE HttPS we force - * the scheme to lower case before we pass it on to Neon, otherwise we - * would crash later on when we assume Neon has set up its https stuff - * but it really didn't. */ - for (itr = uri.scheme; *itr; ++itr) - *itr = tolower(*itr); - is_ssl_session = (strcasecmp(uri.scheme, "https") == 0); if (is_ssl_session) { Index: subversion/libsvn_subr/path.c =================================================================== --- subversion/libsvn_subr/path.c (revision 19084) +++ subversion/libsvn_subr/path.c (working copy) @@ -23,6 +23,7 @@ #include #include +#include #include "svn_string.h" #include "svn_path.h" @@ -1106,22 +1107,14 @@ apr_size_t seglen; apr_size_t canon_segments = 0; svn_boolean_t uri; + apr_uri_t host_uri; dst = canon = apr_pcalloc(pool, strlen(path) + 1); + src = path; - /* Copy over the URI scheme if present. */ - src = skip_uri_scheme(path); - if (src) - { - uri = TRUE; - memcpy(dst, path, src - path); - dst += (src - path); - } - else - { - uri = FALSE; - src = path; - } + /* parse the uri, check if we found a scheme */ + apr_uri_parse(pool, path, &host_uri); + uri = host_uri.scheme ? TRUE : FALSE; /* If this is an absolute path, then just copy over the initial separator character. */ @@ -1139,6 +1132,33 @@ } + if (uri) + { + /* convert scheme and hostname to lowercase */ + apr_size_t offset; + int i; + + for(i = 0; host_uri.scheme[i]; i++) + host_uri.scheme[i] = tolower(host_uri.scheme[i]); + for(i = 0; host_uri.hostname[i]; i++) + host_uri.hostname[i] = tolower(host_uri.hostname[i]); + + /* path will be pointing to a new memory location, so update src to + * point to the new location too. */ + offset = strlen(host_uri.scheme)+3; // "(scheme)://" + path = apr_uri_unparse(pool, &host_uri, APR_URI_UNP_REVEALPASSWORD); + + /* skip 3rd '/' in file:/// uri */ + if (path[offset] == '/') + offset++; + + /* copy src to dst */ + memcpy(dst, path, offset); + dst += offset; + + src = path + offset; + } + while (*src) { /* Parse each segment, find the closing '/' */ Index: subversion/libsvn_wc/entries.c =================================================================== --- subversion/libsvn_wc/entries.c (revision 19084) +++ subversion/libsvn_wc/entries.c (working copy) @@ -242,7 +242,10 @@ = apr_hash_get(atts, SVN_WC__ENTRY_ATTR_URL, APR_HASH_KEY_STRING); if (entry->url) - *modify_flags |= SVN_WC__ENTRY_MODIFY_URL; + { + *modify_flags |= SVN_WC__ENTRY_MODIFY_URL; + entry->url = svn_path_canonicalize(entry->url, pool); + } } /* Set up repository root. Make sure it is a prefix of url. */ @@ -251,6 +254,7 @@ APR_HASH_KEY_STRING); if (entry->repos) { + entry->repos = svn_path_canonicalize(entry->repos, pool); if (entry->url && ! svn_path_is_ancestor(entry->repos, entry->url)) return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, _("Entry for '%s' has invalid repository " Index: subversion/tests/cmdline/path_tests.py =================================================================== --- subversion/tests/cmdline/path_tests.py (revision 0) +++ subversion/tests/cmdline/path_tests.py (revision 0) @@ -0,0 +1,561 @@ +#!/usr/bin/env python +# +# path_tests.py: testing case insensitive hostname handling. +# +# Subversion is a tool for revision control. +# See http://subversion.tigris.org for more information. +# +# ==================================================================== +# Copyright (c) 2000-2006 CollabNet. All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://subversion.tigris.org/license-1.html. +# If newer versions of this license are posted there, you may use a +# newer version instead, at your option. +# +###################################################################### + +# General modules +import os +from urlparse import urlparse, urlunparse +from string import lower, upper + +# Our testing module +import svntest + +import re + +###################################################################### +# Utilities +# + +def change_case_of_hostname(input): + "Change the case of the hostname, try uppercase first" + + m = re.match(r"^(.*://)([^/]*)(.*)", input) + if m: + scheme = m.group(1) + host = upper(m.group(2)) + if host == m.group(2): + host = lower(m.group(2)) + + path = m.group(3) + + return scheme + host + path + +###################################################################### +# Tests +# +# Each test must return on success or raise on failure. + + +#---------------------------------------------------------------------- + +# regression test for issue #2475 - move file and folder +def path_move_between_wcs(sbox): + "case issue #2475 - move file and folder" + sbox.build() + + # checkout a second working copy, use repository url with different case + wc_dir2 = sbox.wc_dir + '2' + repo_url2 = change_case_of_hostname(svntest.main.current_repo_url) + + # Generate the expected output tree. + expected_output = svntest.main.greek_state.copy() + expected_output.wc_dir = wc_dir2 + expected_output.tweak(status='A ', contents=None) + + # Generate an expected wc tree. + expected_wc = svntest.main.greek_state + + # Do a checkout, and verify the resulting output and disk contents. + svntest.actions.run_and_verify_checkout(repo_url2, + wc_dir2, + expected_output, + expected_wc) + + # Move a file from wc to wc2 + mu_path = os.path.join(sbox.wc_dir, 'A', 'mu') + E_path = os.path.join(wc_dir2, 'A', 'B', 'E') + + svntest.main.run_svn(None, 'mv', mu_path, E_path) + + # Move a folder from wc to wc2 + E_path = os.path.join(sbox.wc_dir, 'A', 'B', 'E') + A_path = os.path.join(wc_dir2, 'A') + + svntest.main.run_svn(None, 'mv', E_path, A_path) + +# regression test for issue #2475 - move file and folder +def path_copy_between_wcs(sbox): + "case issue #2475 - copy file and folder" + sbox.build() + + # checkout a second working copy, use repository url with different case + wc_dir2 = sbox.wc_dir + '2' + repo_url2 = change_case_of_hostname(svntest.main.current_repo_url) + + # Generate the expected output tree. + expected_output = svntest.main.greek_state.copy() + expected_output.wc_dir = wc_dir2 + expected_output.tweak(status='A ', contents=None) + + # Generate an expected wc tree. + expected_wc = svntest.main.greek_state + + # Do a checkout, and verify the resulting output and disk contents. + svntest.actions.run_and_verify_checkout(repo_url2, + wc_dir2, + expected_output, + expected_wc) + + # Copy a file from wc to wc2 + mu_path = os.path.join(sbox.wc_dir, 'A', 'mu') + E_path = os.path.join(wc_dir2, 'A', 'B', 'E') + + svntest.main.run_svn(None, 'cp', mu_path, E_path) + + # Copy a folder from wc to wc2 + E_path = os.path.join(sbox.wc_dir, 'A', 'B', 'E') + A_path = os.path.join(wc_dir2, 'A') + + svntest.main.run_svn(None, 'cp', E_path, A_path) + +# regression test for issue #2475 - direct copy in the repository +# this test handles the 'direct move' case too, that uses the same code. +def path_copy_in_repo(sbox): + "case issue #2475 - direct copy in the repository" + sbox.build() + + repo_url2 = change_case_of_hostname(svntest.main.current_repo_url) + + # Copy a file from repo to repo2 + mu_url = svntest.main.current_repo_url + '/A/mu' + E_url = repo_url2 + '/A/B/E' + + svntest.main.run_svn(None, 'cp', mu_url, E_url, '-m', 'copy mu to /A/B/E') + +# regression test for issue #2475 - checkout in a working copy +def path_checkout(sbox): + "case issue #2475 - checkout in wc" + sbox.build() + + # checkout a working copy non-recursively + wc_dir2 = sbox.wc_dir + '2' + + output, errput = svntest.main.run_svn (None, 'co', + '--username', svntest.main.wc_author, + '--password', svntest.main.wc_passwd, + '-N', + svntest.main.current_repo_url, wc_dir2) + + # checkout in the same working copy, use repository url with different case + repo_url2 = change_case_of_hostname(svntest.main.current_repo_url) + + # Generate the expected output tree. + expected_output = svntest.main.greek_state.copy() + expected_output.wc_dir = wc_dir2 + expected_output.tweak(status='A ', contents=None) + + # Generate an expected wc tree. + expected_wc = svntest.main.greek_state + + # Do a checkout, and verify the resulting output and disk contents. + svntest.actions.run_and_verify_checkout(repo_url2, + wc_dir2, + expected_output, + expected_wc) + +######################################################################## +# Run the tests + + +# list all tests here, starting with None: +test_list = [ None, + path_move_between_wcs, + path_copy_between_wcs, + path_copy_in_repo, + path_checkout + ] + +if __name__ == '__main__': + svntest.main.run_tests(test_list) + # NOTREACHED + + +### End of file. +#!/usr/bin/env python +# +# path_tests.py: testing case insensitive hostname handling. +# +# Subversion is a tool for revision control. +# See http://subversion.tigris.org for more information. +# +# ==================================================================== +# Copyright (c) 2000-2006 CollabNet. All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://subversion.tigris.org/license-1.html. +# If newer versions of this license are posted there, you may use a +# newer version instead, at your option. +# +###################################################################### + +# General modules +import os +from urlparse import urlparse, urlunparse +from string import lower, upper + +# Our testing module +import svntest + +import re + +###################################################################### +# Utilities +# + +def change_case_of_hostname(input): + "Change the case of the hostname, try uppercase first" + + m = re.match(r"^(.*://)([^/]*)(.*)", input) + if m: + scheme = m.group(1) + host = upper(m.group(2)) + if host == m.group(2): + host = lower(m.group(2)) + + path = m.group(3) + + return scheme + host + path + +###################################################################### +# Tests +# +# Each test must return on success or raise on failure. + + +#---------------------------------------------------------------------- + +# regression test for issue #2475 - move file and folder +def path_move_between_wcs(sbox): + "case issue #2475 - move file and folder" + sbox.build() + + # checkout a second working copy, use repository url with different case + wc_dir2 = sbox.wc_dir + '2' + repo_url2 = change_case_of_hostname(svntest.main.current_repo_url) + + # Generate the expected output tree. + expected_output = svntest.main.greek_state.copy() + expected_output.wc_dir = wc_dir2 + expected_output.tweak(status='A ', contents=None) + + # Generate an expected wc tree. + expected_wc = svntest.main.greek_state + + # Do a checkout, and verify the resulting output and disk contents. + svntest.actions.run_and_verify_checkout(repo_url2, + wc_dir2, + expected_output, + expected_wc) + + # Move a file from wc to wc2 + mu_path = os.path.join(sbox.wc_dir, 'A', 'mu') + E_path = os.path.join(wc_dir2, 'A', 'B', 'E') + + svntest.main.run_svn(None, 'mv', mu_path, E_path) + + # Move a folder from wc to wc2 + E_path = os.path.join(sbox.wc_dir, 'A', 'B', 'E') + A_path = os.path.join(wc_dir2, 'A') + + svntest.main.run_svn(None, 'mv', E_path, A_path) + +# regression test for issue #2475 - move file and folder +def path_copy_between_wcs(sbox): + "case issue #2475 - copy file and folder" + sbox.build() + + # checkout a second working copy, use repository url with different case + wc_dir2 = sbox.wc_dir + '2' + repo_url2 = change_case_of_hostname(svntest.main.current_repo_url) + + # Generate the expected output tree. + expected_output = svntest.main.greek_state.copy() + expected_output.wc_dir = wc_dir2 + expected_output.tweak(status='A ', contents=None) + + # Generate an expected wc tree. + expected_wc = svntest.main.greek_state + + # Do a checkout, and verify the resulting output and disk contents. + svntest.actions.run_and_verify_checkout(repo_url2, + wc_dir2, + expected_output, + expected_wc) + + # Copy a file from wc to wc2 + mu_path = os.path.join(sbox.wc_dir, 'A', 'mu') + E_path = os.path.join(wc_dir2, 'A', 'B', 'E') + + svntest.main.run_svn(None, 'cp', mu_path, E_path) + + # Copy a folder from wc to wc2 + E_path = os.path.join(sbox.wc_dir, 'A', 'B', 'E') + A_path = os.path.join(wc_dir2, 'A') + + svntest.main.run_svn(None, 'cp', E_path, A_path) + +# regression test for issue #2475 - direct copy in the repository +# this test handles the 'direct move' case too, that uses the same code. +def path_copy_in_repo(sbox): + "case issue #2475 - direct copy in the repository" + sbox.build() + + repo_url2 = change_case_of_hostname(svntest.main.current_repo_url) + + # Copy a file from repo to repo2 + mu_url = svntest.main.current_repo_url + '/A/mu' + E_url = repo_url2 + '/A/B/E' + + svntest.main.run_svn(None, 'cp', mu_url, E_url, '-m', 'copy mu to /A/B/E') + +# regression test for issue #2475 - checkout in a working copy +def path_checkout(sbox): + "case issue #2475 - checkout in wc" + sbox.build() + + # checkout a working copy non-recursively + wc_dir2 = sbox.wc_dir + '2' + + output, errput = svntest.main.run_svn (None, 'co', + '--username', svntest.main.wc_author, + '--password', svntest.main.wc_passwd, + '-N', + svntest.main.current_repo_url, wc_dir2) + + # checkout in the same working copy, use repository url with different case + repo_url2 = change_case_of_hostname(svntest.main.current_repo_url) + + # Generate the expected output tree. + expected_output = svntest.main.greek_state.copy() + expected_output.wc_dir = wc_dir2 + expected_output.tweak(status='A ', contents=None) + + # Generate an expected wc tree. + expected_wc = svntest.main.greek_state + + # Do a checkout, and verify the resulting output and disk contents. + svntest.actions.run_and_verify_checkout(repo_url2, + wc_dir2, + expected_output, + expected_wc) + +######################################################################## +# Run the tests + + +# list all tests here, starting with None: +test_list = [ None, + path_move_between_wcs, + path_copy_between_wcs, + path_copy_in_repo, + path_checkout + ] + +if __name__ == '__main__': + svntest.main.run_tests(test_list) + # NOTREACHED + + +### End of file. +#!/usr/bin/env python +# +# path_tests.py: testing case insensitive hostname handling. +# +# Subversion is a tool for revision control. +# See http://subversion.tigris.org for more information. +# +# ==================================================================== +# Copyright (c) 2000-2006 CollabNet. All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://subversion.tigris.org/license-1.html. +# If newer versions of this license are posted there, you may use a +# newer version instead, at your option. +# +###################################################################### + +# General modules +import os +from urlparse import urlparse, urlunparse +from string import lower, upper + +# Our testing module +import svntest + +import re + +###################################################################### +# Utilities +# + +def change_case_of_hostname(input): + "Change the case of the hostname, try uppercase first" + + m = re.match(r"^(.*://)([^/]*)(.*)", input) + if m: + scheme = m.group(1) + host = upper(m.group(2)) + if host == m.group(2): + host = lower(m.group(2)) + + path = m.group(3) + + return scheme + host + path + +###################################################################### +# Tests +# +# Each test must return on success or raise on failure. + + +#---------------------------------------------------------------------- + +# regression test for issue #2475 - move file and folder +def path_move_between_wcs(sbox): + "case issue #2475 - move file and folder" + sbox.build() + + # checkout a second working copy, use repository url with different case + wc_dir2 = sbox.wc_dir + '2' + repo_url2 = change_case_of_hostname(svntest.main.current_repo_url) + + # Generate the expected output tree. + expected_output = svntest.main.greek_state.copy() + expected_output.wc_dir = wc_dir2 + expected_output.tweak(status='A ', contents=None) + + # Generate an expected wc tree. + expected_wc = svntest.main.greek_state + + # Do a checkout, and verify the resulting output and disk contents. + svntest.actions.run_and_verify_checkout(repo_url2, + wc_dir2, + expected_output, + expected_wc) + + # Move a file from wc to wc2 + mu_path = os.path.join(sbox.wc_dir, 'A', 'mu') + E_path = os.path.join(wc_dir2, 'A', 'B', 'E') + + svntest.main.run_svn(None, 'mv', mu_path, E_path) + + # Move a folder from wc to wc2 + E_path = os.path.join(sbox.wc_dir, 'A', 'B', 'E') + A_path = os.path.join(wc_dir2, 'A') + + svntest.main.run_svn(None, 'mv', E_path, A_path) + +# regression test for issue #2475 - move file and folder +def path_copy_between_wcs(sbox): + "case issue #2475 - copy file and folder" + sbox.build() + + # checkout a second working copy, use repository url with different case + wc_dir2 = sbox.wc_dir + '2' + repo_url2 = change_case_of_hostname(svntest.main.current_repo_url) + + # Generate the expected output tree. + expected_output = svntest.main.greek_state.copy() + expected_output.wc_dir = wc_dir2 + expected_output.tweak(status='A ', contents=None) + + # Generate an expected wc tree. + expected_wc = svntest.main.greek_state + + # Do a checkout, and verify the resulting output and disk contents. + svntest.actions.run_and_verify_checkout(repo_url2, + wc_dir2, + expected_output, + expected_wc) + + # Copy a file from wc to wc2 + mu_path = os.path.join(sbox.wc_dir, 'A', 'mu') + E_path = os.path.join(wc_dir2, 'A', 'B', 'E') + + svntest.main.run_svn(None, 'cp', mu_path, E_path) + + # Copy a folder from wc to wc2 + E_path = os.path.join(sbox.wc_dir, 'A', 'B', 'E') + A_path = os.path.join(wc_dir2, 'A') + + svntest.main.run_svn(None, 'cp', E_path, A_path) + +# regression test for issue #2475 - direct copy in the repository +# this test handles the 'direct move' case too, that uses the same code. +def path_copy_in_repo(sbox): + "case issue #2475 - direct copy in the repository" + sbox.build() + + repo_url2 = change_case_of_hostname(svntest.main.current_repo_url) + + # Copy a file from repo to repo2 + mu_url = svntest.main.current_repo_url + '/A/mu' + E_url = repo_url2 + '/A/B/E' + + svntest.main.run_svn(None, 'cp', mu_url, E_url, '-m', 'copy mu to /A/B/E') + +# regression test for issue #2475 - checkout in a working copy +def path_checkout(sbox): + "case issue #2475 - checkout in wc" + sbox.build() + + # checkout a working copy non-recursively + wc_dir2 = sbox.wc_dir + '2' + + output, errput = svntest.main.run_svn (None, 'co', + '--username', svntest.main.wc_author, + '--password', svntest.main.wc_passwd, + '-N', + svntest.main.current_repo_url, wc_dir2) + + # checkout in the same working copy, use repository url with different case + repo_url2 = change_case_of_hostname(svntest.main.current_repo_url) + + # Generate the expected output tree. + expected_output = svntest.main.greek_state.copy() + expected_output.wc_dir = wc_dir2 + expected_output.tweak(status='A ', contents=None) + + # Generate an expected wc tree. + expected_wc = svntest.main.greek_state + + # Do a checkout, and verify the resulting output and disk contents. + svntest.actions.run_and_verify_checkout(repo_url2, + wc_dir2, + expected_output, + expected_wc) + +######################################################################## +# Run the tests + + +# list all tests here, starting with None: +test_list = [ None, + path_move_between_wcs, + path_copy_between_wcs, + path_copy_in_repo, + path_checkout + ] + +if __name__ == '__main__': + svntest.main.run_tests(test_list) + # NOTREACHED + + +### End of file. Index: subversion/tests/libsvn_subr/path-test.c =================================================================== --- subversion/tests/libsvn_subr/path-test.c (revision 19084) +++ subversion/tests/libsvn_subr/path-test.c (working copy) @@ -670,6 +670,12 @@ { "http://hst", "http://hst" }, { "http://hst/foo/../bar","http://hst/foo/../bar" }, { "http://hst/", "http://hst" }, + { "http://HST/", "http://hst" }, + { "http://HST/FOO/BaR", "http://hst/FOO/BaR" }, + { "svn+ssh://j.random@HST/HST/FOO/BaR", "svn+ssh://j.random@hst/HST/FOO/BaR" }, + { "svn+ssh://j.random:jray@HST/HST/FOO/BaR", "svn+ssh://j.random:jray@hst/HST/FOO/BaR" }, + { "SVN+ssh://j.random:jray@HST/HST/FOO/BaR", "svn+ssh://j.random:jray@hst/HST/FOO/BaR" }, + { "file:///Users/jrandom/wc", "file:///Users/jrandom/wc" }, #if defined(WIN32) || defined(__CYGWIN__) /* We permit UNC paths on Windows. By definition UNC * paths must have two components so we should remove the