Author: neels
Date: Fri Sep 23 12:17:24 2011
New Revision: 1174693

URL: http://svn.apache.org/viewvc?rev=1174693&view=rev
Log:
Follow-up to r1174342.
Fix some errors around relative externals URLs during upgrade, and around
checked out WCs at an external's target path which don't match the external
definition. Also improve upgrade continuation on errors with externals.

The function that resolves relative externals URLs to absolute ones is needed
in svn_client_upgrade(), so it goes from static to private API. Makes a lot of
sense: svn_wc_external_item2_t already is even *public* API, but there's no
function to make proper use of the svn_wc_external_item2_t.url field. IMHO
svn_wc__resolve_relative_external_url() should go to public API eventually,
but such move is unrelated to this patch.

* subversion/libsvn_client/cleanup.c
  (svn_client_upgrade):
    - Use svn_wc__resolve_relative_external_url() to properly resolve relative
      external URLs. Previously, this would wrongly assume all externals' URLs
      to be absolute already.
    - Verify that the URL, if gotten from a readily checked out external,
      actually is identical to the externals definition (i.e. detect if a
      mismatching WC is checked out at the target path).
    - Don't abort on errors in externals definitions, go on with the next
      external definition (like the inner loop does already).
    - Instead of obtaining an absolute path of every external item, get the
      abspath of the path that defines the externals once, and join relpaths
      with that.
    - Make the error handling slightly less complex to read by a little bit of
      code dup.
    - Rename local var (EXTERNAL_KIND), tweak comments.

* subversion/include/private/svn_wc_private.h,
* subversion/libsvn_wc/externals.c
  (svn_wc__resolve_relative_external_url):
    Move here and rename from ...
* subversion/libsvn_client/externals.c
  (resolve_relative_external_url):
    ... this.
  (uri_scheme):
    Move along with resolve_relative_external_url, keep as static.
  (handle_external_item_change, svn_client__export_externals):
    Apply rename, no functional change.

Modified:
    subversion/trunk/subversion/include/private/svn_wc_private.h
    subversion/trunk/subversion/libsvn_client/cleanup.c
    subversion/trunk/subversion/libsvn_client/externals.c
    subversion/trunk/subversion/libsvn_wc/externals.c

Modified: subversion/trunk/subversion/include/private/svn_wc_private.h
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/include/private/svn_wc_private.h?rev=1174693&r1=1174692&r2=1174693&view=diff
==============================================================================
--- subversion/trunk/subversion/include/private/svn_wc_private.h (original)
+++ subversion/trunk/subversion/include/private/svn_wc_private.h Fri Sep 23 
12:17:24 2011
@@ -1184,6 +1184,35 @@ svn_wc__upgrade_add_external_info(svn_wc
                                   svn_revnum_t def_revision,
                                   apr_pool_t *scratch_pool);
 
+/* If the URL for @a item is relative, then using the repository root
+   URL @a repos_root_url and the parent directory URL @parent_dir_url,
+   resolve it into an absolute URL and save it in @a *resolved_url.
+
+   Regardless if the URL is absolute or not, if there are no errors,
+   the URL returned in @a *resolved_url will be canonicalized.
+
+   The following relative URL formats are supported:
+
+     ../    relative to the parent directory of the external
+     ^/     relative to the repository root
+     //     relative to the scheme
+     /      relative to the server's hostname
+
+   The ../ and ^/ relative URLs may use .. to remove path elements up
+   to the server root.
+
+   The external URL should not be canonicalized before calling this function,
+   as otherwise the scheme relative URL '//host/some/path' would have been
+   canonicalized to '/host/some/path' and we would not be able to match on
+   the leading '//'. */
+svn_error_t *
+svn_wc__resolve_relative_external_url(const char **resolved_url,
+                                      const svn_wc_external_item2_t *item,
+                                      const char *repos_root_url,
+                                      const char *parent_dir_url,
+                                      apr_pool_t *result_pool,
+                                      apr_pool_t *scratch_pool);
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */

Modified: subversion/trunk/subversion/libsvn_client/cleanup.c
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/cleanup.c?rev=1174693&r1=1174692&r2=1174693&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_client/cleanup.c (original)
+++ subversion/trunk/subversion/libsvn_client/cleanup.c Fri Sep 23 12:17:24 2011
@@ -155,62 +155,108 @@ svn_client_upgrade(const char *path,
        hi = apr_hash_next(hi))
     {
       int i;
+      const char *externals_parent_abspath;
+      const char *externals_parent_url;
+      const char *externals_parent_repos_root_url;
       const char *externals_parent = svn__apr_hash_index_key(hi);
       svn_string_t *external_desc = svn__apr_hash_index_val(hi);
       apr_array_header_t *externals_p;
+      svn_error_t *err;
 
       svn_pool_clear(iterpool);
       externals_p = apr_array_make(iterpool, 1,
                                    sizeof(svn_wc_external_item2_t*));
 
-      SVN_ERR(svn_wc_parse_externals_description3(
+      /* In this loop, an error causes the respective externals definition, or
+       * the external (inner loop), to be skipped, so that upgrade carries on
+       * with the other externals. */
+
+      err = svn_dirent_get_absolute(&externals_parent_abspath,
+                                    externals_parent, iterpool);
+
+      if (!err)
+        err = svn_wc__node_get_url(&externals_parent_url, ctx->wc_ctx,
+                                   externals_parent_abspath,
+                                   iterpool, iterpool);
+      if (!err)
+        err = svn_wc__node_get_repos_info(&externals_parent_repos_root_url,
+                                          NULL,
+                                          ctx->wc_ctx,
+                                          externals_parent_abspath,
+                                          iterpool, iterpool);
+      if (!err)
+        err = svn_wc_parse_externals_description3(
                   &externals_p, svn_dirent_dirname(path, iterpool),
-                  external_desc->data, TRUE, iterpool));
+                  external_desc->data, TRUE, iterpool);
+      if (err)
+        {
+          svn_wc_notify_t *notify =
+              svn_wc_create_notify(externals_parent,
+                                   svn_wc_notify_failed_external,
+                                   scratch_pool);
+          notify->err = err;
+
+          ctx->notify_func2(ctx->notify_baton2,
+                            notify, scratch_pool);
+
+          svn_error_clear(err);
+
+          /* Next externals definition, please... */
+          continue;
+        }
+
       for (i = 0; i < externals_p->nelts; i++)
         {
           svn_wc_external_item2_t *item;
+          const char *resolved_url;
           const char *external_abspath;
-          const char *external_path;
           const char *repos_relpath;
           const char *repos_root_url;
           const char *repos_uuid;
-          svn_node_kind_t kind;
+          svn_node_kind_t external_kind;
           svn_revnum_t peg_revision;
           svn_revnum_t revision;
-          svn_error_t *err;
 
           item = APR_ARRAY_IDX(externals_p, i, svn_wc_external_item2_t*);
 
           svn_pool_clear(iterpool2);
-          external_path = svn_dirent_join(externals_parent, item->target_dir,
-                                          iterpool2);
-
-          err = svn_dirent_get_absolute(&external_abspath, external_path,
-                                        iterpool2);
+          external_abspath = svn_dirent_join(externals_parent_abspath,
+                                             item->target_dir,
+                                             iterpool2);
+
+          err = svn_wc__resolve_relative_external_url(
+                                              &resolved_url,
+                                              item,
+                                              externals_parent_repos_root_url,
+                                              externals_parent_url,
+                                              scratch_pool, scratch_pool);
           if (err)
             goto handle_error;
 
-          /* This is hack. We can only send dirs to svn_wc_upgrade(). This
-             way we will get an exception saying that the wc must be
-             upgraded if it's a dir. If it's a file then the lookup is done
-             in an adm_dir belonging to the real wc and since that was
-             updated before the externals no error is returned. */
-          err = svn_wc_read_kind(&kind, ctx->wc_ctx, external_abspath, FALSE,
-                                 iterpool2);
-
+          /* This is a hack. We only need to call svn_wc_upgrade() on external
+           * dirs, as file externals are upgraded along with their defining
+           * WC.  Reading the kind will throw an exception on an external dir,
+           * saying that the wc must be upgraded.  If it's a file, the lookup
+           * is done in an adm_dir belonging to the defining wc (which has
+           * already been upgraded) and no error is returned.  If it doesn't
+           * exist (external that isn't checked out yet), we'll just get
+           * svn_node_none. */
+          err = svn_wc_read_kind(&external_kind, ctx->wc_ctx,
+                                 external_abspath, FALSE, iterpool2);
           if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
             {
               svn_error_clear(err);
 
               err = svn_client_upgrade(external_abspath, ctx, iterpool2);
+              if (err)
+                goto handle_error;
             }
-
-          if (err)
+          else if (err)
             goto handle_error;
 
-          /* The upgrade of any dir should be done now, get the (supposedly
-           * now reliable) kind. */
-          err = svn_wc_read_kind(&kind, ctx->wc_ctx, external_abspath,
+          /* The upgrade of any dir should be done now, get the now reliable
+           * kind. */
+          err = svn_wc_read_kind(&external_kind, ctx->wc_ctx, external_abspath,
                                  FALSE, iterpool2);
           if (err)
             goto handle_error;
@@ -223,7 +269,7 @@ svn_client_upgrade(const char *path,
 
           /* First get the relpath, as that returns SVN_ERR_WC_PATH_NOT_FOUND
            * when the node is not present in the file system.
-           * (svn_wc__node_get_repos_info() would try to derive the URL). */
+           * svn_wc__node_get_repos_info() would try to derive the URL. */
           err = svn_wc__node_get_repos_relpath(&repos_relpath,
                                                ctx->wc_ctx,
                                                external_abspath,
@@ -236,35 +282,46 @@ svn_client_upgrade(const char *path,
                                                 ctx->wc_ctx,
                                                 external_abspath,
                                                 iterpool2, iterpool2);
+              if (err)
+                goto handle_error;
             }
           else if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
             {
               /* The external is not currently checked out. Try to figure out
                * the URL parts via the defined URL and fetch_repos_info(). */
               svn_error_clear(err);
-
-              /* The repos root / uuid from above get_repos_info() call, if it
-               * was successful, has returned the URL as derived from the WC's
-               * parent path, which is not what we want for the external. Only
-               * makes sense for added/deleted/not-present files. So make sure
-               * those values are not used. */
-              repos_root_url = NULL;
               repos_relpath = NULL;
+              repos_root_url = NULL;
+              repos_uuid = NULL;
+            }
+          else if (err)
+            goto handle_error;
 
+          /* If we haven't got any information from the checked out external,
+           * or if the URL information mismatches the external's definition,
+           * ask fetch_repos_info() to find out the repos root. */
+          if (! repos_relpath || ! repos_root_url
+              || 0 != strcmp(resolved_url,
+                             svn_path_url_add_component2(repos_root_url,
+                                                         repos_relpath,
+                                                         scratch_pool)))
+            {
               err = fetch_repos_info(&repos_root_url,
                                      &repos_uuid,
                                      &info_baton,
-                                     item->url,
+                                     resolved_url,
                                      scratch_pool, scratch_pool);
               if (err)
                 goto handle_error;
 
-
-              repos_relpath = svn_uri_skip_ancestor(repos_root_url, item->url,
+              repos_relpath = svn_uri_skip_ancestor(repos_root_url,
+                                                    resolved_url,
                                                     iterpool2);
 
-              /* There's just this URL, no idea what kind it is. */
-              kind = svn_node_unknown;
+              /* There's just the URL, no idea what kind the external is.
+               * That's fine, as the external isn't even checked out yet.
+               * The kind will be set during the next 'update'. */
+              external_kind = svn_node_unknown;
             }
 
           if (err)
@@ -280,7 +337,7 @@ svn_client_upgrade(const char *path,
 
           err = svn_wc__upgrade_add_external_info(ctx->wc_ctx,
                                                   external_abspath,
-                                                  kind,
+                                                  external_kind,
                                                   externals_parent,
                                                   repos_relpath,
                                                   repos_root_url,
@@ -296,11 +353,10 @@ handle_error:
                                        svn_wc_notify_failed_external,
                                        scratch_pool);
               notify->err = err;
-
               ctx->notify_func2(ctx->notify_baton2,
                                 notify, scratch_pool);
-
               svn_error_clear(err);
+              /* Next external node, please... */
             }
         }
     }

Modified: subversion/trunk/subversion/libsvn_client/externals.c
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/externals.c?rev=1174693&r1=1174692&r2=1174693&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_client/externals.c (original)
+++ subversion/trunk/subversion/libsvn_client/externals.c Fri Sep 23 12:17:24 
2011
@@ -512,219 +512,6 @@ cleanup:
   return svn_error_trace(err);
 }
 
-/* Return the scheme of @a uri in @a scheme allocated from @a pool.
-   If @a uri does not appear to be a valid URI, then @a scheme will
-   not be updated.  */
-static svn_error_t *
-uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool)
-{
-  apr_size_t i;
-
-  for (i = 0; uri[i] && uri[i] != ':'; ++i)
-    if (uri[i] == '/')
-      goto error;
-
-  if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/')
-    {
-      *scheme = apr_pstrmemdup(pool, uri, i);
-      return SVN_NO_ERROR;
-    }
-
-error:
-  return svn_error_createf(SVN_ERR_BAD_URL, 0,
-                           _("URL '%s' does not begin with a scheme"),
-                           uri);
-}
-
-/* If the URL for @a item is relative, then using the repository root
-   URL @a repos_root_url and the parent directory URL @parent_dir_url,
-   resolve it into an absolute URL and save it in @a *resolved_url.
-
-   Regardless if the URL is absolute or not, if there are no errors,
-   the URL returned in @a *resolved_url will be canonicalized.
-
-   The following relative URL formats are supported:
-
-     ../    relative to the parent directory of the external
-     ^/     relative to the repository root
-     //     relative to the scheme
-     /      relative to the server's hostname
-
-   The ../ and ^/ relative URLs may use .. to remove path elements up
-   to the server root.
-
-   The external URL should not be canonicalized before calling this function,
-   as otherwise the scheme relative URL '//host/some/path' would have been
-   canonicalized to '/host/some/path' and we would not be able to match on
-   the leading '//'. */
-static svn_error_t *
-resolve_relative_external_url(const char **resolved_url,
-                              const svn_wc_external_item2_t *item,
-                              const char *repos_root_url,
-                              const char *parent_dir_url,
-                              apr_pool_t *result_pool,
-                              apr_pool_t *scratch_pool)
-{
-  const char *url = item->url;
-  apr_uri_t parent_dir_uri;
-  apr_status_t status;
-
-  *resolved_url = item->url;
-
-  /* If the URL is already absolute, there is nothing to do. */
-  if (svn_path_is_url(url))
-    {
-      /* "http://server/path"; */
-      *resolved_url = svn_uri_canonicalize(url, result_pool);
-      return SVN_NO_ERROR;
-    }
-
-  if (url[0] == '/')
-    {
-      /* "/path", "//path", and "///path" */
-      int num_leading_slashes = 1;
-      if (url[1] == '/')
-        {
-          num_leading_slashes++;
-          if (url[2] == '/')
-            num_leading_slashes++;
-        }
-
-      /* "//schema-relative" and in some cases "///schema-relative".
-         This last format is supported on file:// schema relative. */
-      url = apr_pstrcat(scratch_pool,
-                        apr_pstrndup(scratch_pool, url, num_leading_slashes),
-                        svn_relpath_canonicalize(url + num_leading_slashes,
-                                                 scratch_pool),
-                        (char*)NULL);
-    }
-  else
-    {
-      /* "^/path" and "../path" */
-      url = svn_relpath_canonicalize(url, scratch_pool);
-    }
-
-  /* Parse the parent directory URL into its parts. */
-  status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri);
-  if (status)
-    return svn_error_createf(SVN_ERR_BAD_URL, 0,
-                             _("Illegal parent directory URL '%s'"),
-                             parent_dir_url);
-
-  /* If the parent directory URL is at the server root, then the URL
-     may have no / after the hostname so apr_uri_parse() will leave
-     the URL's path as NULL. */
-  if (! parent_dir_uri.path)
-    parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
-  parent_dir_uri.query = NULL;
-  parent_dir_uri.fragment = NULL;
-
-  /* Handle URLs relative to the current directory or to the
-     repository root.  The backpaths may only remove path elements,
-     not the hostname.  This allows an external to refer to another
-     repository in the same server relative to the location of this
-     repository, say using SVNParentPath. */
-  if ((0 == strncmp("../", url, 3)) ||
-      (0 == strncmp("^/", url, 2)))
-    {
-      apr_array_header_t *base_components;
-      apr_array_header_t *relative_components;
-      int i;
-
-      /* Decompose either the parent directory's URL path or the
-         repository root's URL path into components.  */
-      if (0 == strncmp("../", url, 3))
-        {
-          base_components = svn_path_decompose(parent_dir_uri.path,
-                                               scratch_pool);
-          relative_components = svn_path_decompose(url, scratch_pool);
-        }
-      else
-        {
-          apr_uri_t repos_root_uri;
-
-          status = apr_uri_parse(scratch_pool, repos_root_url,
-                                 &repos_root_uri);
-          if (status)
-            return svn_error_createf(SVN_ERR_BAD_URL, 0,
-                                     _("Illegal repository root URL '%s'"),
-                                     repos_root_url);
-
-          /* If the repository root URL is at the server root, then
-             the URL may have no / after the hostname so
-             apr_uri_parse() will leave the URL's path as NULL. */
-          if (! repos_root_uri.path)
-            repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
-
-          base_components = svn_path_decompose(repos_root_uri.path,
-                                               scratch_pool);
-          relative_components = svn_path_decompose(url + 2, scratch_pool);
-        }
-
-      for (i = 0; i < relative_components->nelts; ++i)
-        {
-          const char *component = APR_ARRAY_IDX(relative_components,
-                                                i,
-                                                const char *);
-          if (0 == strcmp("..", component))
-            {
-              /* Constructing the final absolute URL together with
-                 apr_uri_unparse() requires that the path be absolute,
-                 so only pop a component if the component being popped
-                 is not the component for the root directory. */
-              if (base_components->nelts > 1)
-                apr_array_pop(base_components);
-            }
-          else
-            APR_ARRAY_PUSH(base_components, const char *) = component;
-        }
-
-      parent_dir_uri.path = (char *)svn_path_compose(base_components,
-                                                     scratch_pool);
-      *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
-                                                           &parent_dir_uri, 0),
-                                       result_pool);
-      return SVN_NO_ERROR;
-    }
-
-  /* The remaining URLs are relative to either the scheme or server root
-     and can only refer to locations inside that scope, so backpaths are
-     not allowed. */
-  if (svn_path_is_backpath_present(url + 2))
-    return svn_error_createf(SVN_ERR_BAD_URL, 0,
-                             _("The external relative URL '%s' cannot have "
-                               "backpaths, i.e. '..'"),
-                             item->url);
-
-  /* Relative to the scheme: Build a new URL from the parts we know. */
-  if (0 == strncmp("//", url, 2))
-    {
-      const char *scheme;
-
-      SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool));
-      *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme,
-                                                       ":", url, (char *)NULL),
-                                           result_pool);
-      return SVN_NO_ERROR;
-    }
-
-  /* Relative to the server root: Just replace the path portion of the
-     parent's URL. */
-  if (url[0] == '/')
-    {
-      parent_dir_uri.path = (char *)url;
-      *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
-                                                           &parent_dir_uri, 0),
-                                           result_pool);
-      return SVN_NO_ERROR;
-    }
-
-  return svn_error_createf(SVN_ERR_BAD_URL, 0,
-                           _("Unrecognized format for the relative external "
-                             "URL '%s'"),
-                           item->url);
-}
-
 static svn_error_t *
 handle_external_item_removal(const struct external_change_baton_t *eb,
                              const char *defining_abspath,
@@ -832,10 +619,10 @@ handle_external_item_change(const struct
      iterpool, since the hash table values outlive the iterpool and
      any pointers they have should also outlive the iterpool.  */
 
-  SVN_ERR(resolve_relative_external_url(&new_url,
-                                        new_item, eb->repos_root_url,
-                                        parent_dir_url,
-                                        scratch_pool, scratch_pool));
+  SVN_ERR(svn_wc__resolve_relative_external_url(&new_url,
+                                                new_item, eb->repos_root_url,
+                                                parent_dir_url,
+                                                scratch_pool, scratch_pool));
 
   /* If the external is being checked out, exported or updated,
      determine if the external is a file or directory. */
@@ -1232,9 +1019,10 @@ svn_client__export_externals(apr_hash_t 
           item_abspath = svn_dirent_join(local_abspath, item->target_dir,
                                          sub_iterpool);
 
-          SVN_ERR(resolve_relative_external_url(&new_url, item,
-                                                repos_root_url, dir_url,
-                                                sub_iterpool, sub_iterpool));
+          SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, item,
+                                                        repos_root_url,
+                                                        dir_url, sub_iterpool,
+                                                        sub_iterpool));
 
           /* The target dir might have multiple components.  Guarantee
              the path leading down to the last component. */

Modified: subversion/trunk/subversion/libsvn_wc/externals.c
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_wc/externals.c?rev=1174693&r1=1174692&r2=1174693&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_wc/externals.c (original)
+++ subversion/trunk/subversion/libsvn_wc/externals.c Fri Sep 23 12:17:24 2011
@@ -30,6 +30,7 @@
 #include <apr_hash.h>
 #include <apr_tables.h>
 #include <apr_general.h>
+#include <apr_uri.h>
 
 #include "svn_dirent_uri.h"
 #include "svn_path.h"
@@ -1248,3 +1249,195 @@ svn_wc__externals_gather_definitions(apr
       return SVN_NO_ERROR;
     }
 }
+
+/* Return the scheme of @a uri in @a scheme allocated from @a pool.
+   If @a uri does not appear to be a valid URI, then @a scheme will
+   not be updated.  */
+static svn_error_t *
+uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool)
+{
+  apr_size_t i;
+
+  for (i = 0; uri[i] && uri[i] != ':'; ++i)
+    if (uri[i] == '/')
+      goto error;
+
+  if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/')
+    {
+      *scheme = apr_pstrmemdup(pool, uri, i);
+      return SVN_NO_ERROR;
+    }
+
+error:
+  return svn_error_createf(SVN_ERR_BAD_URL, 0,
+                           _("URL '%s' does not begin with a scheme"),
+                           uri);
+}
+
+svn_error_t *
+svn_wc__resolve_relative_external_url(const char **resolved_url,
+                                      const svn_wc_external_item2_t *item,
+                                      const char *repos_root_url,
+                                      const char *parent_dir_url,
+                                      apr_pool_t *result_pool,
+                                      apr_pool_t *scratch_pool)
+{
+  const char *url = item->url;
+  apr_uri_t parent_dir_uri;
+  apr_status_t status;
+
+  *resolved_url = item->url;
+
+  /* If the URL is already absolute, there is nothing to do. */
+  if (svn_path_is_url(url))
+    {
+      /* "http://server/path"; */
+      *resolved_url = svn_uri_canonicalize(url, result_pool);
+      return SVN_NO_ERROR;
+    }
+
+  if (url[0] == '/')
+    {
+      /* "/path", "//path", and "///path" */
+      int num_leading_slashes = 1;
+      if (url[1] == '/')
+        {
+          num_leading_slashes++;
+          if (url[2] == '/')
+            num_leading_slashes++;
+        }
+
+      /* "//schema-relative" and in some cases "///schema-relative".
+         This last format is supported on file:// schema relative. */
+      url = apr_pstrcat(scratch_pool,
+                        apr_pstrndup(scratch_pool, url, num_leading_slashes),
+                        svn_relpath_canonicalize(url + num_leading_slashes,
+                                                 scratch_pool),
+                        (char*)NULL);
+    }
+  else
+    {
+      /* "^/path" and "../path" */
+      url = svn_relpath_canonicalize(url, scratch_pool);
+    }
+
+  /* Parse the parent directory URL into its parts. */
+  status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri);
+  if (status)
+    return svn_error_createf(SVN_ERR_BAD_URL, 0,
+                             _("Illegal parent directory URL '%s'"),
+                             parent_dir_url);
+
+  /* If the parent directory URL is at the server root, then the URL
+     may have no / after the hostname so apr_uri_parse() will leave
+     the URL's path as NULL. */
+  if (! parent_dir_uri.path)
+    parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
+  parent_dir_uri.query = NULL;
+  parent_dir_uri.fragment = NULL;
+
+  /* Handle URLs relative to the current directory or to the
+     repository root.  The backpaths may only remove path elements,
+     not the hostname.  This allows an external to refer to another
+     repository in the same server relative to the location of this
+     repository, say using SVNParentPath. */
+  if ((0 == strncmp("../", url, 3)) ||
+      (0 == strncmp("^/", url, 2)))
+    {
+      apr_array_header_t *base_components;
+      apr_array_header_t *relative_components;
+      int i;
+
+      /* Decompose either the parent directory's URL path or the
+         repository root's URL path into components.  */
+      if (0 == strncmp("../", url, 3))
+        {
+          base_components = svn_path_decompose(parent_dir_uri.path,
+                                               scratch_pool);
+          relative_components = svn_path_decompose(url, scratch_pool);
+        }
+      else
+        {
+          apr_uri_t repos_root_uri;
+
+          status = apr_uri_parse(scratch_pool, repos_root_url,
+                                 &repos_root_uri);
+          if (status)
+            return svn_error_createf(SVN_ERR_BAD_URL, 0,
+                                     _("Illegal repository root URL '%s'"),
+                                     repos_root_url);
+
+          /* If the repository root URL is at the server root, then
+             the URL may have no / after the hostname so
+             apr_uri_parse() will leave the URL's path as NULL. */
+          if (! repos_root_uri.path)
+            repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
+
+          base_components = svn_path_decompose(repos_root_uri.path,
+                                               scratch_pool);
+          relative_components = svn_path_decompose(url + 2, scratch_pool);
+        }
+
+      for (i = 0; i < relative_components->nelts; ++i)
+        {
+          const char *component = APR_ARRAY_IDX(relative_components,
+                                                i,
+                                                const char *);
+          if (0 == strcmp("..", component))
+            {
+              /* Constructing the final absolute URL together with
+                 apr_uri_unparse() requires that the path be absolute,
+                 so only pop a component if the component being popped
+                 is not the component for the root directory. */
+              if (base_components->nelts > 1)
+                apr_array_pop(base_components);
+            }
+          else
+            APR_ARRAY_PUSH(base_components, const char *) = component;
+        }
+
+      parent_dir_uri.path = (char *)svn_path_compose(base_components,
+                                                     scratch_pool);
+      *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
+                                                           &parent_dir_uri, 0),
+                                       result_pool);
+      return SVN_NO_ERROR;
+    }
+
+  /* The remaining URLs are relative to either the scheme or server root
+     and can only refer to locations inside that scope, so backpaths are
+     not allowed. */
+  if (svn_path_is_backpath_present(url + 2))
+    return svn_error_createf(SVN_ERR_BAD_URL, 0,
+                             _("The external relative URL '%s' cannot have "
+                               "backpaths, i.e. '..'"),
+                             item->url);
+
+  /* Relative to the scheme: Build a new URL from the parts we know. */
+  if (0 == strncmp("//", url, 2))
+    {
+      const char *scheme;
+
+      SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool));
+      *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme,
+                                                       ":", url, (char *)NULL),
+                                           result_pool);
+      return SVN_NO_ERROR;
+    }
+
+  /* Relative to the server root: Just replace the path portion of the
+     parent's URL. */
+  if (url[0] == '/')
+    {
+      parent_dir_uri.path = (char *)url;
+      *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
+                                                           &parent_dir_uri, 0),
+                                           result_pool);
+      return SVN_NO_ERROR;
+    }
+
+  return svn_error_createf(SVN_ERR_BAD_URL, 0,
+                           _("Unrecognized format for the relative external "
+                             "URL '%s'"),
+                           item->url);
+}


Reply via email to