Author: rhuijben
Date: Fri Dec 13 21:51:45 2013
New Revision: 1550840

URL: http://svn.apache.org/r1550840
Log:
Implement the mod_dav_svn overwrite protection of 'svnmucc' in the mtcc api.
Also add several verifications for properties. Use specific fs error codes
instead of the same illegal target error everywhere.

* subversion/include/svn_client.h
  (svn_client_mtcc_check_path): New function.

* subversion/libsvn_client/mtcc.c
  (mtcc_op_find): Use better error code.
  (get_origin): New function.
  (svn_client_mtcc_get_origin): New function.
  (mtcc_verify_create): New function.

  (svn_client_mtcc_add_add_file,
   svn_client_mtcc_add_copy,
   svn_client_mtcc_add_delete,
   svn_client_mtcc_add_mkdir): Verify target location before scheduling a
     possible replacement.

  (svn_client_mtcc_add_move): Move from the mtcc location instead of from the
     repository location.
  (svn_client_mtcc_add_propset): Verify property names and values.
  (svn_client_mtcc_add_update_file): Verify target location.
  (svn_client_mtcc_check_path): New function.

* subversion/tests/libsvn_client/mtcc-test.c
  (make_greek_tree): Assume that the base revision is 0.
  (test_overwrite): New function.
  (test_funcs): Add test_overwrite.

Modified:
    subversion/trunk/subversion/include/svn_client.h
    subversion/trunk/subversion/libsvn_client/mtcc.c
    subversion/trunk/subversion/tests/libsvn_client/mtcc-test.c

Modified: subversion/trunk/subversion/include/svn_client.h
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/include/svn_client.h?rev=1550840&r1=1550839&r2=1550840&view=diff
==============================================================================
--- subversion/trunk/subversion/include/svn_client.h (original)
+++ subversion/trunk/subversion/include/svn_client.h Fri Dec 13 21:51:45 2013
@@ -6813,6 +6813,20 @@ svn_client_mtcc_add_update_file(const ch
                                 svn_client_mtcc_t *mtcc,
                                 apr_pool_t *scratch_pool);
 
+/** Obtains the kind of node at @a relpath in the current state of @a mtcc.
+ * This value might be from the cache (in case of modifications, copies)
+ * or fetched from the repository.
+ *
+ * When a node does not exist this functions sets @a *kind to @c svn_node_node.
+ *
+ * @since New in 1.9.
+ */
+svn_error_t *
+svn_client_mtcc_check_path(svn_node_kind_t *kind,
+                           const char *relpath,
+                           svn_client_mtcc_t *mtcc,
+                           apr_pool_t *scratch_pool);
+
 /** Commits all operations stored in @a mtcc as a new revision and destroys
  * @a mtcc.
  *

Modified: subversion/trunk/subversion/libsvn_client/mtcc.c
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/mtcc.c?rev=1550840&r1=1550839&r2=1550840&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_client/mtcc.c (original)
+++ subversion/trunk/subversion/libsvn_client/mtcc.c Fri Dec 13 21:51:45 2013
@@ -99,7 +99,7 @@ mtcc_op_find(svn_client_mtcc_op_t **op,
     name = relpath;
 
   if (child && !base_op->children)
-    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+    return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
                              _("Can't operate on '%s' because '%s' is not a "
                                "directory"),
                              child, base_op->name);
@@ -147,6 +147,93 @@ mtcc_op_find(svn_client_mtcc_op_t **op,
   }
 }
 
+/* Gets the original repository location of RELPATH, checking things
+   like copies, moves, etc.  */
+static svn_error_t *
+get_origin(const char **origin_relpath,
+           svn_revnum_t *rev,
+           svn_client_mtcc_op_t *op,
+           const char *relpath,
+           apr_pool_t *result_pool,
+           apr_pool_t *scratch_pool)
+{
+  const char *child;
+  const char *name;
+  if (!*relpath)
+    {
+      *origin_relpath = op->src_relpath
+                                ? apr_pstrdup(result_pool, op->src_relpath)
+                                : NULL;
+      *rev = op->src_rev;
+      return SVN_NO_ERROR;
+    }
+
+  child = strchr(relpath, '/');
+  if (child)
+    {
+      name = apr_pstrmemdup(scratch_pool, relpath, child-relpath);
+      child++; /* Skip '/' */
+    }
+  else
+    name = relpath;
+
+  if (op->children && op->children->nelts)
+    {
+      int i;
+
+      for (i = 0; i < op->children->nelts; i++)
+        {
+           svn_client_mtcc_op_t *cop;
+
+           cop = APR_ARRAY_IDX(op->children, i, svn_client_mtcc_op_t *);
+
+           if (! strcmp(cop->name, name) &&  cop->kind != OP_DELETE)
+            {
+              SVN_ERR(get_origin(origin_relpath, rev,
+                                 cop, child ? child : "",
+                                 result_pool, scratch_pool));
+
+              if (*origin_relpath)
+                return SVN_NO_ERROR;
+
+              break;
+            }
+        }
+    }
+
+  if (op->src_relpath)
+    {
+      *origin_relpath = svn_relpath_join(op->src_relpath, relpath,
+                                         result_pool);
+      *rev = op->src_rev;
+    }
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t * /* ### Make public? */
+svn_client_mtcc_get_origin(const char **origin_relpath,
+                           svn_revnum_t *rev,
+                           const char *relpath,
+                           svn_client_mtcc_t *mtcc,
+                           apr_pool_t *result_pool,
+                           apr_pool_t *scratch_pool)
+{
+  *origin_relpath = NULL;
+  *rev = SVN_INVALID_REVNUM;
+
+  SVN_ERR(get_origin(origin_relpath, rev, mtcc->root_op, relpath,
+                     result_pool, scratch_pool));
+
+  if (!*origin_relpath)
+    {
+      *origin_relpath = apr_pstrdup(result_pool, relpath);
+      *rev = mtcc->base_revision;
+    }
+
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
 svn_client_mtcc_create(svn_client_mtcc_t **mtcc,
                        const char *anchor_url,
@@ -260,6 +347,42 @@ svn_client_mtcc_get_relpath(const char *
                                                 result_pool));
 }
 
+/* Check if it is safe to create a new node at NEW_RELPATH. Return a proper
+   error if it is not */
+static svn_error_t *
+mtcc_verify_create(svn_client_mtcc_t *mtcc,
+                   const char *new_relpath,
+                   apr_pool_t *scratch_pool)
+{
+  svn_client_mtcc_op_t *op;
+  svn_node_kind_t kind;
+
+  SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, FALSE,
+                       FALSE, mtcc->pool, scratch_pool));
+
+  if (op)
+    return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
+                             _("Can't copy to '%s': target already operated 
on"),
+                             new_relpath);
+
+  SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, TRUE,
+                       FALSE, mtcc->pool, scratch_pool));
+
+  if (op)
+    return SVN_NO_ERROR; /* Node is explicitly deleted. We can replace */
+
+  /* mod_dav_svn allows overwriting existing directories. Let's hide that
+     for users of this api */
+  SVN_ERR(svn_client_mtcc_check_path(&kind, new_relpath, mtcc, scratch_pool));
+
+  if (kind != svn_node_none)
+    return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
+                             _("Can't copy to '%s': target already exists"),
+                             new_relpath);
+
+  return SVN_NO_ERROR;
+}
+
 
 svn_error_t *
 svn_client_mtcc_add_add_file(const char *relpath,
@@ -272,6 +395,8 @@ svn_client_mtcc_add_add_file(const char 
   svn_boolean_t created;
   SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream);
 
+  SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool));
+
   SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE,
                        TRUE, mtcc->pool, scratch_pool));
 
@@ -305,6 +430,8 @@ svn_client_mtcc_add_copy(const char *src
                  && svn_relpath_is_canonical(dst_relpath)
                  && SVN_IS_VALID_REVNUM(revision));
 
+  SVN_ERR(mtcc_verify_create(mtcc, dst_relpath, scratch_pool));
+
   /* Subversion requires the kind of a copy */
   SVN_ERR(svn_ra_check_path(mtcc->ra_session, src_relpath, revision, &kind,
                             scratch_pool));
@@ -341,9 +468,18 @@ svn_client_mtcc_add_delete(const char *r
 {
   svn_client_mtcc_op_t *op;
   svn_boolean_t created;
+  svn_node_kind_t kind;
 
   SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
 
+  SVN_ERR(svn_client_mtcc_check_path(&kind, relpath, mtcc, scratch_pool));
+
+  if (kind == svn_node_none)
+    return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+                             _("Can't delete node at '%s' as it "
+                                "does not exist"),
+                             relpath);
+
   SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, TRUE,
                        TRUE, mtcc->pool, scratch_pool));
 
@@ -368,6 +504,8 @@ svn_client_mtcc_add_mkdir(const char *re
   svn_boolean_t created;
   SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
 
+  SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool));
+
   SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE,
                        FALSE, mtcc->pool, scratch_pool));
 
@@ -389,9 +527,16 @@ svn_client_mtcc_add_move(const char *src
                          svn_client_mtcc_t *mtcc,
                          apr_pool_t *scratch_pool)
 {
-  SVN_ERR(svn_client_mtcc_add_delete(src_relpath, mtcc, scratch_pool));
+  const char *origin_relpath;
+  svn_revnum_t origin_rev;
+
+  SVN_ERR(svn_client_mtcc_get_origin(&origin_relpath, &origin_rev,
+                                     src_relpath, mtcc,
+                                     scratch_pool, scratch_pool));
+
   SVN_ERR(svn_client_mtcc_add_copy(src_relpath, mtcc->base_revision,
                                    dst_relpath, mtcc, scratch_pool));
+  SVN_ERR(svn_client_mtcc_add_delete(src_relpath, mtcc, scratch_pool));
 
   return SVN_NO_ERROR;
 }
@@ -405,8 +550,21 @@ svn_client_mtcc_add_propset(const char *
                             apr_pool_t *scratch_pool)
 {
   svn_client_mtcc_op_t *op;
-  SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)
-                 && svn_property_kind2(propname) == svn_prop_regular_kind);
+  SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+
+  if (! svn_prop_name_is_valid(propname))
+    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+                             _("Bad property name: '%s'"), propname);
+
+  if (svn_prop_is_known_svn_rev_prop(propname))
+    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+                             _("Revision property '%s' not allowed "
+                               "in this context"), propname);
+
+  if (svn_property_kind2(propname) == svn_prop_wc_kind)
+    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+                             _("'%s' is a wcprop, thus not accessible "
+                               "to clients"), propname);
 
   if (!skip_checks && svn_prop_needs_translation(propname))
     {
@@ -418,6 +576,11 @@ svn_client_mtcc_add_propset(const char *
                  _("Error normalizing property value"));
     }
 
+  if (propval && svn_prop_is_svn_prop(propname))
+    {
+      /* ### TODO: Call svn_wc_canonicalize_svn_prop() */
+    }
+
   SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE,
                        FALSE, mtcc->pool, scratch_pool));
 
@@ -425,16 +588,13 @@ svn_client_mtcc_add_propset(const char *
     {
       svn_node_kind_t kind;
       svn_boolean_t created;
-      const char *origin_relpath = relpath;
-      svn_revnum_t origin_revnum = mtcc->base_revision;
 
       /* ### TODO: Check if this node is within a newly copied directory,
                    and update origin values accordingly */
 
-      SVN_ERR(svn_ra_check_path(mtcc->ra_session, origin_relpath,
-                                origin_revnum, &kind, scratch_pool));
+      SVN_ERR(svn_client_mtcc_check_path(&kind, relpath, mtcc, scratch_pool));
 
-      if (kind != svn_node_file && kind != svn_node_dir)
+      if (kind == svn_node_none)
         return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
                                  _("Can't set properties at not existing 
'%s'"),
                                  relpath);
@@ -475,8 +635,16 @@ svn_client_mtcc_add_update_file(const ch
 {
   svn_client_mtcc_op_t *op;
   svn_boolean_t created;
+  svn_node_kind_t kind;
   SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream);
 
+  SVN_ERR(svn_client_mtcc_check_path(&kind, relpath, mtcc, scratch_pool));
+
+  if (kind != svn_node_file)
+    return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
+                             _("Can't update '%s' because it is not a file"),
+                             relpath);
+
   SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE,
                        TRUE, mtcc->pool, scratch_pool));
 
@@ -500,6 +668,46 @@ svn_client_mtcc_add_update_file(const ch
   return SVN_NO_ERROR;
 }
 
+svn_error_t *
+svn_client_mtcc_check_path(svn_node_kind_t *kind,
+                           const char *relpath,
+                           svn_client_mtcc_t *mtcc,
+                           apr_pool_t *scratch_pool)
+{
+  const char *origin_relpath;
+  svn_revnum_t origin_rev;
+  svn_client_mtcc_op_t *op;
+
+  SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+
+  SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE,
+                       FALSE, mtcc->pool, scratch_pool));
+
+  if (op)
+    {
+      if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
+        {
+          *kind = svn_node_dir;
+          return SVN_NO_ERROR;
+        }
+      else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
+        {
+          *kind = svn_node_file;
+          return SVN_NO_ERROR;
+        }
+      SVN_ERR_MALFUNCTION(); /* No other kinds defined as delete is filtered */
+    }
+
+  SVN_ERR(svn_client_mtcc_get_origin(&origin_relpath, &origin_rev,
+                                     relpath, mtcc,
+                                     scratch_pool, scratch_pool));
+
+  SVN_ERR(svn_ra_check_path(mtcc->ra_session, origin_relpath,
+                            origin_rev, kind, scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
 static svn_error_t *
 commit_properties(const svn_delta_editor_t *editor,
                   const svn_client_mtcc_op_t *op,

Modified: subversion/trunk/subversion/tests/libsvn_client/mtcc-test.c
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/libsvn_client/mtcc-test.c?rev=1550840&r1=1550839&r2=1550840&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/libsvn_client/mtcc-test.c (original)
+++ subversion/trunk/subversion/tests/libsvn_client/mtcc-test.c Fri Dec 13 
21:51:45 2013
@@ -84,7 +84,7 @@ make_greek_tree(const char *repos_url,
   subpool = svn_pool_create(scratch_pool);
 
   SVN_ERR(svn_client_create_context2(&ctx, NULL, subpool));
-  SVN_ERR(svn_client_mtcc_create(&mtcc, repos_url, 1, ctx, subpool, subpool));
+  SVN_ERR(svn_client_mtcc_create(&mtcc, repos_url, 0, ctx, subpool, subpool));
 
   for (i = 0; svn_test__greek_tree_nodes[i].path; i++)
     {
@@ -297,6 +297,41 @@ test_update_files(const svn_test_opts_t 
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *
+test_overwrite(const svn_test_opts_t *opts,
+               apr_pool_t *pool)
+{
+  svn_client_mtcc_t *mtcc;
+  svn_client_ctx_t *ctx;
+  const char *repos_abspath;
+  const char *repos_url;
+  svn_repos_t* repos;
+
+  repos_abspath = svn_test_data_path("mtcc-overwrite", pool);
+  SVN_ERR(svn_dirent_get_absolute(&repos_abspath, repos_abspath, pool));
+  SVN_ERR(svn_uri_get_file_url_from_dirent(&repos_url, repos_abspath, pool));
+  SVN_ERR(svn_test__create_repos(&repos, repos_abspath, opts, pool));
+
+  SVN_ERR(make_greek_tree(repos_url, pool));
+
+  SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
+  SVN_ERR(svn_client_mtcc_create(&mtcc, repos_url, 1, ctx, pool, pool));
+
+  SVN_ERR(svn_client_mtcc_add_copy("A", 1, "AA", mtcc, pool));
+
+  SVN_TEST_ASSERT_ERROR(svn_client_mtcc_add_mkdir("AA/B", mtcc, pool),
+                        SVN_ERR_FS_ALREADY_EXISTS);
+
+  SVN_TEST_ASSERT_ERROR(svn_client_mtcc_add_mkdir("AA/D/H/chi", mtcc, pool),
+                        SVN_ERR_FS_ALREADY_EXISTS);
+
+  SVN_ERR(svn_client_mtcc_add_mkdir("AA/BB", mtcc, pool));
+
+  SVN_ERR(verify_mtcc_commit(mtcc, 2, pool));
+  return SVN_NO_ERROR;
+}
+
+
 /* ========================================================================== 
*/
 
 
@@ -315,6 +350,8 @@ struct svn_test_descriptor_t test_funcs[
                        "test propset and propdel"),
     SVN_TEST_OPTS_PASS(test_update_files,
                        "test update files"),
+    SVN_TEST_OPTS_PASS(test_overwrite,
+                       "test overwrite"),
     SVN_TEST_NULL
   };
  


Reply via email to