Modified: subversion/branches/addremove/subversion/libsvn_repos/load-fs-vtable.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_repos/load-fs-vtable.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_repos/load-fs-vtable.c 
(original)
+++ subversion/branches/addremove/subversion/libsvn_repos/load-fs-vtable.c Sat 
May 23 14:16:56 2020
@@ -75,7 +75,7 @@ struct parse_baton
      (svn_revnum_t *) in the dump stream to their corresponding revisions
      (svn_revnum_t *) in the loaded repository.  The hash and its
      contents are allocated in POOL. */
-  /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=3903
+  /* ### See https://issues.apache.org/jira/browse/SVN-3903
      ### for discussion about improving the memory costs of this mapping. */
   apr_hash_t *rev_map;
 
@@ -155,9 +155,11 @@ get_revision_mapping(apr_hash_t *rev_map
 }
 
 
-/* Change revision property NAME to VALUE for REVISION in REPOS.  If
-   VALIDATE_PROPS is set, use functions which perform validation of
-   the property value.  Otherwise, bypass those checks. */
+/* Change revision property NAME to VALUE for REVISION in REPOS.
+   If NORMALIZE_PROPS is set, attempt to normalize properties before
+   changing them, if that is needed.  If VALIDATE_PROPS is set, use
+   functions which perform validation of the property value.
+   Otherwise, bypass those checks. */
 static svn_error_t *
 change_rev_prop(svn_repos_t *repos,
                 svn_revnum_t revision,
@@ -179,17 +181,23 @@ change_rev_prop(svn_repos_t *repos,
                                    NULL, value, pool);
 }
 
-/* Change property NAME to VALUE for PATH in TXN_ROOT.  If
-   VALIDATE_PROPS is set, use functions which perform validation of
-   the property value.  Otherwise, bypass those checks. */
+/* Change property NAME to VALUE for PATH in TXN_ROOT.
+   If NORMALIZE_PROPS is set, attempt to normalize properties before
+   changing them, if that is needed.  If VALIDATE_PROPS is set, use
+   functions which perform validation of the property value.
+   Otherwise, bypass those checks. */
 static svn_error_t *
 change_node_prop(svn_fs_root_t *txn_root,
                  const char *path,
                  const char *name,
                  const svn_string_t *value,
                  svn_boolean_t validate_props,
+                 svn_boolean_t normalize_props,
                  apr_pool_t *pool)
 {
+  if (normalize_props)
+    SVN_ERR(svn_repos__normalize_prop(&value, NULL, name, value, pool, pool));
+
   if (validate_props)
     return svn_repos_fs_change_node_prop(txn_root, path, name, value, pool);
   else
@@ -213,9 +221,11 @@ prefix_mergeinfo_paths(svn_string_t **me
     {
       const char *merge_source = apr_hash_this_key(hi);
       svn_rangelist_t *rangelist = apr_hash_this_val(hi);
-      const char *path;
+      const char *path, *canonicalized_path;
 
-      merge_source = svn_relpath_canonicalize(merge_source, pool);
+      SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL,
+                                       merge_source, pool, pool));
+      merge_source = canonicalized_path;
 
       /* The svn:mergeinfo property syntax demands a repos abspath */
       path = svn_fspath__canonicalize(svn_relpath_join(parent_dir,
@@ -253,7 +263,7 @@ renumber_mergeinfo_revs(svn_string_t **f
   SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
 
   /* Issue #3020
-     http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16
+     https://issues.apache.org/jira/browse/SVN-3020#desc16
      Remove mergeinfo older than the oldest revision in the dump stream
      and adjust its revisions by the difference between the head rev of
      the target repository and the current dump stream rev. */
@@ -323,7 +333,7 @@ renumber_mergeinfo_revs(svn_string_t **f
                  mergeinfo with a start rev > end rev.  If that gets into the
                  repository then a world of bustage breaks loose anytime that
                  bogus mergeinfo is parsed.  See
-                 
http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16.
+                 https://issues.apache.org/jira/browse/SVN-3020#desc16.
                  */
               continue;
             }
@@ -377,7 +387,10 @@ make_node_baton(struct node_baton **node
   /* Then add info from the headers.  */
   if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH)))
   {
-    val = svn_relpath_canonicalize(val, pool);
+    const char *canonicalized_path;
+    SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL,
+                                          val, pool, pool));
+    val = canonicalized_path;
     if (rb->pb->parent_dir)
       nb->path = svn_relpath_join(rb->pb->parent_dir, val, pool);
     else
@@ -869,7 +882,8 @@ set_node_property(void *baton,
     }
 
   return change_node_prop(rb->txn_root, nb->path, name, value,
-                          pb->validate_props, nb->pool);
+                          pb->validate_props, rb->pb->normalize_props,
+                          nb->pool);
 }
 
 
@@ -885,7 +899,8 @@ delete_node_property(void *baton,
     return SVN_NO_ERROR;
 
   return change_node_prop(rb->txn_root, nb->path, name, NULL,
-                          rb->pb->validate_props, nb->pool);
+                          rb->pb->validate_props, rb->pb->normalize_props,
+                          nb->pool);
 }
 
 
@@ -909,7 +924,8 @@ remove_node_props(void *baton)
       const char *key = apr_hash_this_key(hi);
 
       SVN_ERR(change_node_prop(rb->txn_root, nb->path, key, NULL,
-                               rb->pb->validate_props, nb->pool));
+                               rb->pb->validate_props, rb->pb->normalize_props,
+                               nb->pool));
     }
 
   return SVN_NO_ERROR;
@@ -1202,7 +1218,12 @@ svn_repos_get_fs_build_parser6(const svn
   struct parse_baton *pb = apr_pcalloc(pool, sizeof(*pb));
 
   if (parent_dir)
-    parent_dir = svn_relpath_canonicalize(parent_dir, pool);
+    {
+      const char *canonicalized_path;
+      SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL,
+                                            parent_dir, pool, pool));
+      parent_dir = canonicalized_path;
+    }
 
   SVN_ERR_ASSERT((SVN_IS_VALID_REVNUM(start_rev) &&
                   SVN_IS_VALID_REVNUM(end_rev))
@@ -1260,8 +1281,8 @@ svn_repos_load_fs6(svn_repos_t *repos,
                    svn_boolean_t use_pre_commit_hook,
                    svn_boolean_t use_post_commit_hook,
                    svn_boolean_t validate_props,
-                   svn_boolean_t normalize_props,
                    svn_boolean_t ignore_dates,
+                   svn_boolean_t normalize_props,
                    svn_repos_notify_func_t notify_func,
                    void *notify_baton,
                    svn_cancel_func_t cancel_func,
@@ -1400,7 +1421,7 @@ revprops_close_revision(void *baton)
  * both of these values are #SVN_INVALID_REVNUM (in  which case no
  * revision-based filtering occurs at all), or both are valid revisions
  * (where START_REV is older than or equivalent to END_REV).
- * 
+ *
  * START_REV and END_REV act as filters, the lower and upper (inclusive)
  * range values of revisions which will
  * be loaded.  Either both of these values are #SVN_INVALID_REVNUM (in

Modified: subversion/branches/addremove/subversion/libsvn_repos/load.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_repos/load.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_repos/load.c (original)
+++ subversion/branches/addremove/subversion/libsvn_repos/load.c Sat May 23 
14:16:56 2020
@@ -355,24 +355,62 @@ parse_text_block(svn_stream_t *stream,
 
 
 
-/* Parse VERSIONSTRING and verify that we support the dumpfile format
-   version number, setting *VERSION appropriately. */
+/* Parse VERSIONSTRING from STREAM and verify that we support the dumpfile
+   format version number, setting *VERSION appropriately. */
 static svn_error_t *
 parse_format_version(int *version,
-                     const char *versionstring)
+                     svn_stream_t *stream,
+                     apr_pool_t *scratch_pool)
 {
   static const int magic_len = sizeof(SVN_REPOS_DUMPFILE_MAGIC_HEADER) - 1;
-  const char *p = strchr(versionstring, ':');
+  svn_stringbuf_t *linebuf;
+  const char *p;
   int value;
 
+  /* No svn_stream_readline() here, because malformed streams may not have
+     the EOL at all, and currently svn_stream_readline() keeps loading the
+     whole thing into memory until it encounters an EOL or the stream ends.
+     This is particularly troublesome, because users may incorrectly attempt
+     to load arbitrary large files instead of proper dump files.
+
+     As a workaround, parse the first line with a length limit.  While this
+     is not a complete solution, doing so handles the common case described
+     above.  For a complete solution, svn_stream_readline() may need to grow
+     a `limit` argument that would allow us to safely use it everywhere within
+     this parser.
+   */
+  linebuf = svn_stringbuf_create_empty(scratch_pool);
+  while (1)
+    {
+      apr_size_t len;
+      char c;
+
+      len = 1;
+      SVN_ERR(svn_stream_read_full(stream, &c, &len));
+      if (len != 1)
+        return stream_ran_dry();
+
+      if (c == '\n')
+        break;
+
+      if (linebuf->len + 1 > 80)
+        return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
+                                 _("Malformed dumpfile header '%s'"),
+                                 linebuf->data);
+
+      svn_stringbuf_appendbyte(linebuf, c);
+    }
+
+  p = strchr(linebuf->data, ':');
+
   if (p == NULL
-      || p != (versionstring + magic_len)
-      || strncmp(versionstring,
+      || p != (linebuf->data + magic_len)
+      || strncmp(linebuf->data,
                  SVN_REPOS_DUMPFILE_MAGIC_HEADER,
                  magic_len))
     return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
                              _("Malformed dumpfile header '%s'"),
-                             versionstring);
+                             linebuf->data);
 
   SVN_ERR(svn_cstring_atoi(&value, p + 1));
 
@@ -542,14 +580,10 @@ svn_repos_parse_dumpstream3(svn_stream_t
   parse_fns = complete_vtable(parse_fns, pool);
 
   /* Start parsing process. */
-  SVN_ERR(svn_stream_readline(stream, &linebuf, "\n", &eof, linepool));
-  if (eof)
-    return stream_ran_dry();
-
   /* The first two lines of the stream are the dumpfile-format version
      number, and a blank line.  To preserve backward compatibility,
      don't assume the existence of newer parser-vtable functions. */
-  SVN_ERR(parse_format_version(&version, linebuf->data));
+  SVN_ERR(parse_format_version(&version, stream, linepool));
   if (parse_fns->magic_header_record != NULL)
     SVN_ERR(parse_fns->magic_header_record(version, parse_baton, pool));
 

Modified: subversion/branches/addremove/subversion/libsvn_repos/log.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_repos/log.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_repos/log.c (original)
+++ subversion/branches/addremove/subversion/libsvn_repos/log.c Sat May 23 
14:16:56 2020
@@ -655,7 +655,7 @@ fs_mergeinfo_changed(svn_mergeinfo_catal
      because that greatly influences the costs for log processing.
      So, it is faster to iterate over the changes twice - in the worst
      case b/c most times there is no m/i at all and we exit out early
-     without any overhead. 
+     without any overhead.
    */
   while (change && (!any_mergeinfo || !any_copy))
     {
@@ -999,7 +999,7 @@ get_combined_mergeinfo_changes(svn_merge
       /* Issue #4022 'svn log -g interprets change in inherited mergeinfo due
          to move as a merge': A copy where the source and destination inherit
          mergeinfo from the same parent means the inherited mergeinfo of the
-         source and destination will differ, but this diffrence is not
+         source and destination will differ, but this difference is not
          indicative of a merge unless the mergeinfo on the inherited parent
          has actually changed.
 
@@ -1053,7 +1053,7 @@ get_combined_mergeinfo_changes(svn_merge
             continue;
         }
 
-      /* Compare, constrast, and combine the results. */
+      /* Compare, contrast, and combine the results. */
       SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
                                   mergeinfo, FALSE, result_pool, iterpool));
       SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo, deleted,
@@ -1227,7 +1227,7 @@ typedef struct interesting_merge_baton_t
   void *inner_baton;
 } interesting_merge_baton_t;
 
-/* Implements svn_repos_path_change_receiver_t. 
+/* Implements svn_repos_path_change_receiver_t.
  * *BATON is a interesting_merge_baton_t.
  *
  * If BATON->REV a merged revision that is not already part of
@@ -1909,7 +1909,7 @@ store_search(svn_mergeinfo_t processed,
              apr_pool_t *scratch_pool)
 {
   /* We add 1 to end so that we can use the mergeinfo API to handle
-     singe revisions where HIST_START is equal to HIST_END. */
+     single revisions where HIST_START is equal to HIST_END. */
   svn_revnum_t start = hist_start <= hist_end ? hist_start : hist_end;
   svn_revnum_t end = hist_start <= hist_end ? hist_end + 1 : hist_start + 1;
   svn_mergeinfo_t mergeinfo = svn_hash__make(scratch_pool);
@@ -2447,7 +2447,7 @@ svn_repos_get_logs5(svn_repos_t *repos,
      represents all of PATHS' history between START and END.  We will use
      this later to squelch duplicate log revisions that might exist in
      both natural history and merged-in history.  See
-     http://subversion.tigris.org/issues/show_bug.cgi?id=3650#desc5 */
+     https://issues.apache.org/jira/browse/SVN-3650#desc5 */
   if (include_merged_revisions)
     {
       apr_pool_t *subpool = svn_pool_create(scratch_pool);

Modified: subversion/branches/addremove/subversion/libsvn_repos/notify.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_repos/notify.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_repos/notify.c (original)
+++ subversion/branches/addremove/subversion/libsvn_repos/notify.c Sat May 23 
14:16:56 2020
@@ -1,4 +1,4 @@
-/* notify.c --- notifcation system
+/* notify.c --- notification system
  *
  * ====================================================================
  *    Licensed to the Apache Software Foundation (ASF) under one

Modified: subversion/branches/addremove/subversion/libsvn_repos/replay.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_repos/replay.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_repos/replay.c (original)
+++ subversion/branches/addremove/subversion/libsvn_repos/replay.c Sat May 23 
14:16:56 2020
@@ -126,9 +126,6 @@ struct copy_info
 
 struct path_driver_cb_baton
 {
-  const svn_delta_editor_t *editor;
-  void *edit_baton;
-
   /* The root of the revision we're replaying. */
   svn_fs_root_t *root;
 
@@ -454,14 +451,14 @@ fill_copyfrom(svn_fs_root_t **copyfrom_r
 
 static svn_error_t *
 path_driver_cb_func(void **dir_baton,
+                    const svn_delta_editor_t *editor,
+                    void *edit_baton,
                     void *parent_baton,
                     void *callback_baton,
                     const char *edit_path,
                     apr_pool_t *pool)
 {
   struct path_driver_cb_baton *cb = callback_baton;
-  const svn_delta_editor_t *editor = cb->editor;
-  void *edit_baton = cb->edit_baton;
   svn_fs_root_t *root = cb->root;
   svn_fs_path_change3_t *change;
   svn_boolean_t do_add = FALSE, do_delete = FALSE;
@@ -894,7 +891,7 @@ get_relevant_changes(apr_hash_t **change
             }
 
           /* If the base_path doesn't match the top directory of this path
-             we don't want anything to do with it... 
+             we don't want anything to do with it...
              ...unless this was a change to one of the parent directories of
              base_path. */
           if (   svn_relpath_skip_ancestor(base_relpath, path)
@@ -957,8 +954,6 @@ svn_repos_replay2(svn_fs_root_t *root,
     low_water_mark = 0;
 
   /* Initialize our callback baton. */
-  cb_baton.editor = editor;
-  cb_baton.edit_baton = edit_baton;
   cb_baton.root = root;
   cb_baton.changed_paths = changed_paths;
   cb_baton.authz_read_func = authz_read_func;
@@ -989,7 +984,7 @@ svn_repos_replay2(svn_fs_root_t *root,
     }
 
   /* Call the path-based editor driver. */
-  return svn_delta_path_driver2(editor, edit_baton,
+  return svn_delta_path_driver3(editor, edit_baton,
                                 paths, TRUE,
                                 path_driver_cb_func, &cb_baton, pool);
 #else

Modified: subversion/branches/addremove/subversion/libsvn_repos/repos.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_repos/repos.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_repos/repos.c (original)
+++ subversion/branches/addremove/subversion/libsvn_repos/repos.c Sat May 23 
14:16:56 2020
@@ -1183,7 +1183,7 @@ svn_repos_create(svn_repos_t **repos_p,
   if ((err = svn_fs_create2(&repos->fs, repos->db_path, fs_config,
                             result_pool, scratch_pool)))
     {
-      /* If there was an error making the filesytem, e.g. unknown/supported
+      /* If there was an error making the filesystem, e.g. unknown/supported
        * filesystem type.  Clean up after ourselves.  Yes this is safe because
        * create_repos_structure will fail if the path existed before we started
        * so we can't accidentally remove a directory that previously existed.
@@ -1452,7 +1452,7 @@ svn_repos_upgrade2(const char *path,
   if (notify_func)
     {
       /* We notify *twice* here, because there are two different logistical
-         actions occuring. */
+         actions occurring. */
       svn_repos_notify_t *notify = svn_repos_notify_create(
                                     svn_repos_notify_mutex_acquired, subpool);
       notify_func(notify_baton, notify, subpool);
@@ -1702,7 +1702,7 @@ svn_repos_recover4(const char *path,
   if (notify_func)
     {
       /* We notify *twice* here, because there are two different logistical
-         actions occuring. */
+         actions occurring. */
       svn_repos_notify_t *notify = svn_repos_notify_create(
                                     svn_repos_notify_mutex_acquired, subpool);
       notify_func(notify_baton, notify, subpool);
@@ -1721,7 +1721,7 @@ svn_repos_recover4(const char *path,
 }
 
 struct freeze_baton_t {
-  apr_array_header_t *paths;
+  const apr_array_header_t *paths;
   int counter;
   svn_repos_freeze_func_t freeze_func;
   void *freeze_baton;
@@ -1788,7 +1788,7 @@ multi_freeze(void *baton,
    and an SQLite reserved lock which means the repository is readable
    while frozen. */
 svn_error_t *
-svn_repos_freeze(apr_array_header_t *paths,
+svn_repos_freeze(const apr_array_header_t *paths,
                  svn_repos_freeze_func_t freeze_func,
                  void *freeze_baton,
                  apr_pool_t *pool)

Modified: subversion/branches/addremove/subversion/libsvn_repos/repos.h
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_repos/repos.h?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_repos/repos.h (original)
+++ subversion/branches/addremove/subversion/libsvn_repos/repos.h Sat May 23 
14:16:56 2020
@@ -128,7 +128,7 @@ struct svn_repos_t
   /* The format number of this repository. */
   int format;
 
-  /* The path to the repository's hooks enviroment file. If NULL, hooks run
+  /* The path to the repository's hooks environment file. If NULL, hooks run
    * in an empty environment. */
   const char *hooks_env_path;
 

Modified: subversion/branches/addremove/subversion/libsvn_subr/cache-memcache.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_subr/cache-memcache.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_subr/cache-memcache.c 
(original)
+++ subversion/branches/addremove/subversion/libsvn_subr/cache-memcache.c Sat 
May 23 14:16:56 2020
@@ -234,7 +234,7 @@ memcache_has_key(svn_boolean_t *found,
   return SVN_NO_ERROR;
 }
 
-/* Core functionality of our setter functions: store LENGH bytes of DATA
+/* Core functionality of our setter functions: store LENGTH bytes of DATA
  * to be identified by KEY in the memcached given by CACHE_VOID. Use POOL
  * for temporary allocations.
  */

Modified: subversion/branches/addremove/subversion/libsvn_subr/cmdline.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_subr/cmdline.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_subr/cmdline.c (original)
+++ subversion/branches/addremove/subversion/libsvn_subr/cmdline.c Sat May 23 
14:16:56 2020
@@ -39,6 +39,7 @@
 
 #include <apr.h>                /* for STDIN_FILENO */
 #include <apr_errno.h>          /* for apr_strerror */
+#include <apr_escape.h>
 #include <apr_general.h>        /* for apr_initialize/apr_terminate */
 #include <apr_strings.h>        /* for apr_snprintf */
 #include <apr_pools.h>
@@ -343,6 +344,23 @@ svn_cmdline_path_local_style_from_utf8(c
 }
 
 svn_error_t *
+svn_cmdline__stdin_readline(const char **result,
+                            apr_pool_t *result_pool,
+                            apr_pool_t *scratch_pool)
+{
+  svn_stringbuf_t *buf = NULL;
+  svn_stream_t *stdin_stream = NULL;
+  svn_boolean_t oob = FALSE;
+
+  SVN_ERR(svn_stream_for_stdin2(&stdin_stream, TRUE, scratch_pool));
+  SVN_ERR(svn_stream_readline(stdin_stream, &buf, APR_EOL_STR, &oob, 
result_pool));
+
+  *result = buf->data;
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
 svn_cmdline_printf(apr_pool_t *pool, const char *fmt, ...)
 {
   const char *message;
@@ -1216,7 +1234,7 @@ svn_cmdline__be_interactive(svn_boolean_
 }
 
 
-/* Helper for the next two functions.  Set *EDITOR to some path to an
+/* Helper for the edit_externally functions.  Set *EDITOR to some path to an
    editor binary.  Sources to search include: the EDITOR_CMD argument
    (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG
    is not NULL), $VISUAL, $EDITOR.  Return
@@ -1282,6 +1300,98 @@ find_editor_binary(const char **editor,
   return SVN_NO_ERROR;
 }
 
+/* Wrapper around apr_pescape_shell() which also escapes whitespace. */
+static const char *
+escape_path(apr_pool_t *pool, const char *orig_path)
+{
+  apr_size_t len, esc_len;
+  apr_status_t status;
+
+  len = strlen(orig_path);
+  esc_len = 0;
+
+  status = apr_escape_shell(NULL, orig_path, len, &esc_len);
+
+  if (status == APR_NOTFOUND)
+    {
+      /* No special characters found by APR, so just surround it in double
+         quotes in case there is whitespace, which APR (as of 1.6.5) doesn't
+         consider special. */
+      return apr_psprintf(pool, "\"%s\"", orig_path);
+    }
+  else
+    {
+#ifdef WIN32
+      const char *p;
+      /* Following the advice from
+         
https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
+         1. Surround argument with double-quotes
+         2. Escape backslashes, if they're followed by a double-quote, and 
double-quotes
+         3. Escape any metacharacter, including double-quotes, with ^ */
+
+      /* Use APR's buffer size as an approximation for how large the escaped
+         string should be, plus 4 bytes for the leading/trailing ^" */
+      svn_stringbuf_t *buf = svn_stringbuf_create_ensure(esc_len + 4, pool);
+      svn_stringbuf_appendcstr(buf, "^\"");
+      for (p = orig_path; *p; p++)
+        {
+          int nr_backslash = 0;
+          while (*p && *p == '\\')
+            {
+              nr_backslash++;
+              p++;
+            }
+
+          if (!*p)
+            /* We've reached the end of the argument, so we need 2n backslash
+               characters.  That will be interpreted as n backslashes and the
+               final double-quote character will be interpreted as the final
+               string delimiter. */
+            svn_stringbuf_appendfill(buf, '\\', nr_backslash * 2);
+          else if (*p == '"')
+            {
+              /* Double-quote as part of the argument means we need to double
+                 any preceding backslashes and then add one to escape the
+                 double-quote. */
+              svn_stringbuf_appendfill(buf, '\\', nr_backslash * 2 + 1);
+              svn_stringbuf_appendbyte(buf, '^');
+              svn_stringbuf_appendbyte(buf, *p);
+            }
+          else
+            {
+              /* Since there's no double-quote, we just insert any backslashes
+                 literally.  No escaping needed. */
+              svn_stringbuf_appendfill(buf, '\\', nr_backslash);
+              if (strchr("()%!^<>&|", *p))
+                svn_stringbuf_appendbyte(buf, '^');
+              svn_stringbuf_appendbyte(buf, *p);
+            }
+        }
+      svn_stringbuf_appendcstr(buf, "^\"");
+      return buf->data;
+#else
+      char *path, *p, *esc_path;
+
+      /* Account for whitespace, since APR doesn't */
+      for (p = (char *)orig_path; *p; p++)
+        if (strchr(" \t\n\r", *p))
+          esc_len++;
+
+      path = apr_pcalloc(pool, esc_len);
+      apr_escape_shell(path, orig_path, len, NULL);
+
+      p = esc_path = apr_pcalloc(pool, len + esc_len + 1);
+      while (*path)
+        {
+          if (strchr(" \t\n\r", *path))
+            *p++ = '\\';
+          *p++ = *path++;
+        }
+
+      return esc_path;
+#endif
+    }
+}
 
 svn_error_t *
 svn_cmdline__edit_file_externally(const char *path,
@@ -1313,7 +1423,9 @@ svn_cmdline__edit_file_externally(const
     return svn_error_wrap_apr
       (apr_err, _("Can't change working directory to '%s'"), base_dir);
 
-  cmd = apr_psprintf(pool, "%s %s", editor, file_name);
+  /* editor is explicitly documented as being interpreted by the user's shell,
+     and as such should already be quoted/escaped as needed. */
+  cmd = apr_psprintf(pool, "%s %s", editor, escape_path(pool, file_name));
   sys_err = system(cmd);
 
   apr_err = apr_filepath_set(old_cwd, pool);
@@ -1472,7 +1584,10 @@ svn_cmdline__edit_string_externally(svn_
   err = svn_utf_cstring_from_utf8(&tmpfile_native, tmpfile_name, pool);
   if (err)
     goto cleanup;
-  cmd = apr_psprintf(pool, "%s %s", editor, tmpfile_native);
+
+  /* editor is explicitly documented as being interpreted by the user's shell,
+     and as such should already be quoted/escaped as needed. */
+  cmd = apr_psprintf(pool, "%s %s", editor, escape_path(pool, tmpfile_native));
 
   /* If the caller wants us to leave the file around, return the path
      of the file we'll use, and make a note not to destroy it.  */

Modified: subversion/branches/addremove/subversion/libsvn_subr/compress_lz4.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_subr/compress_lz4.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_subr/compress_lz4.c 
(original)
+++ subversion/branches/addremove/subversion/libsvn_subr/compress_lz4.c Sat May 
23 14:16:56 2020
@@ -27,7 +27,7 @@
 
 #include "svn_private_config.h"
 
-#if SVN_INTERNAL_LZ4
+#ifdef SVN_INTERNAL_LZ4
 #include "lz4/lz4internal.h"
 #else
 #include <lz4.h>
@@ -126,3 +126,19 @@ svn__decompress_lz4(const void *data, ap
 
   return SVN_NO_ERROR;
 }
+
+const char *
+svn_lz4__compiled_version(void)
+{
+  static const char lz4_version_str[] = APR_STRINGIFY(LZ4_VERSION_MAJOR) "." \
+                                        APR_STRINGIFY(LZ4_VERSION_MINOR) "." \
+                                        APR_STRINGIFY(LZ4_VERSION_RELEASE);
+
+  return lz4_version_str;
+}
+
+int
+svn_lz4__runtime_version(void)
+{
+  return LZ4_versionNumber();
+}

Modified: subversion/branches/addremove/subversion/libsvn_subr/config_file.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_subr/config_file.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_subr/config_file.c 
(original)
+++ subversion/branches/addremove/subversion/libsvn_subr/config_file.c Sat May 
23 14:16:56 2020
@@ -466,6 +466,7 @@ parse_value_continuation_lines(int *pch,
               else
                 {
                   /* This is a continuation line. Read it. */
+                  SVN_ERR(parser_ungetc(ctx, ch));
                   SVN_ERR(parser_get_line(ctx, ctx->line_read, &ch));
 
                   /* Trailing whitespace is ignored. */
@@ -549,7 +550,7 @@ parse_option(int *pch, parse_context_t *
 }
 
 
-/* Read chars until enounter ']', then skip everything to the end of
+/* Read chars until encounter ']', then skip everything to the end of
  * the line.  Set *PCH to the character that ended the line (either
  * newline or EOF), and set CTX->section to the string of characters
  * seen before ']'.
@@ -1044,7 +1045,7 @@ svn_config_ensure(const char *config_dir
    "The syntax of the configuration files is a subset of the one used by"    NL
    "Python's ConfigParser module; see"                                       NL
    ""                                                                        NL
-   "   http://www.python.org/doc/current/lib/module-ConfigParser.html";       NL
+   "   https://docs.python.org/3/library/configparser.html";                  NL
    ""                                                                        NL
    "Configuration data in the Windows registry"                              NL
    "=========================================="                              NL
@@ -1154,6 +1155,7 @@ svn_config_ensure(const char *config_dir
         "###                              HTTP operation."                   NL
         "###   http-chunked-requests      Whether to use chunked transfer"   NL
         "###                              encoding for HTTP requests body."  NL
+        "###   http-auth-types            List of HTTP authentication types."NL
         "###   ssl-authority-files        List of files, each of a trusted CA"
                                                                              NL
         "###   ssl-trust-default-ca       Trust the system 'default' CAs"    NL
@@ -1190,16 +1192,13 @@ svn_config_ensure(const char *config_dir
         "###                              may be cached to disk."            NL
         "###   username                   Specifies the default username."   NL
         "###"                                                                NL
-        "### Set store-passwords to 'no' to avoid storing passwords on disk" NL
-        "### in any way, including in password stores.  It defaults to"      NL
+        "### Set store-passwords to 'no' to avoid storing new passwords on"  NL
+        "### disk in any way, including in password stores.  It defaults to" NL
         "### 'yes', but Subversion will never save your password to disk in" NL
         "### plaintext unless explicitly configured to do so."               NL
-        "### Note that this option only prevents saving of *new* passwords;" NL
-        "### it doesn't invalidate existing passwords.  (To do that, remove" NL
-        "### the cache files by hand as described in the Subversion book.)"  NL
         "###"                                                                NL
 #ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
-        "### Set store-plaintext-passwords to 'no' to avoid storing"         NL
+        "### Set store-plaintext-passwords to 'no' to avoid storing new"     NL
         "### passwords in unencrypted form in the auth/ area of your config" NL
         "### directory. Set it to 'yes' to allow Subversion to store"        NL
         "### unencrypted passwords in the auth/ area.  The default is"       NL
@@ -1209,22 +1208,15 @@ svn_config_ensure(const char *config_dir
         "### 'store-auth-creds' is set to 'no'."                             NL
         "###"                                                                NL
 #endif
-        "### Set store-ssl-client-cert-pp to 'no' to avoid storing ssl"      NL
+        "### Set store-ssl-client-cert-pp to 'no' to avoid storing new ssl"  NL
         "### client certificate passphrases in the auth/ area of your"       NL
         "### config directory.  It defaults to 'yes', but Subversion will"   NL
         "### never save your passphrase to disk in plaintext unless"         NL
         "### explicitly configured to do so."                                NL
         "###"                                                                NL
-        "### Note store-ssl-client-cert-pp only prevents the saving of *new*"NL
-        "### passphrases; it doesn't invalidate existing passphrases.  To do"NL
-        "### that, remove the cache files by hand as described in the"       NL
-        "### Subversion book at http://svnbook.red-bean.com/nightly/en/\\";   NL
-        "###                    svn.serverconfig.netmodel.html\\"            NL
-        "###                    #svn.serverconfig.netmodel.credcache"        NL
-        "###"                                                                NL
 #ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
         "### Set store-ssl-client-cert-pp-plaintext to 'no' to avoid storing"NL
-        "### passphrases in unencrypted form in the auth/ area of your"      NL
+        "### new passphrases in unencrypted form in the auth/ area of your"  NL
         "### config directory.  Set it to 'yes' to allow Subversion to"      NL
         "### store unencrypted passphrases in the auth/ area.  The default"  NL
         "### is 'ask', which means that Subversion will prompt before"       NL
@@ -1233,12 +1225,19 @@ svn_config_ensure(const char *config_dir
         "### 'store-ssl-client-cert-pp' is set to 'no'."                     NL
         "###"                                                                NL
 #endif
-        "### Set store-auth-creds to 'no' to avoid storing any Subversion"   NL
+        "### Set store-auth-creds to 'no' to avoid storing any new Subversion"
+                                                                             NL
         "### credentials in the auth/ area of your config directory."        NL
         "### Note that this includes SSL server certificates."               NL
-        "### It defaults to 'yes'.  Note that this option only prevents"     NL
-        "### saving of *new* credentials;  it doesn't invalidate existing"   NL
-        "### caches.  (To do that, remove the cache files by hand.)"         NL
+        "### It defaults to 'yes'."                                          NL
+        "###"                                                                NL
+        "### Note that setting a 'store-*' option to 'no' only prevents"     NL
+        "### saving of *new* passwords, passphrases or other credentials."   NL
+        "### It does not remove or invalidate existing stored credentials."  NL
+        "### To do that, see the 'svn auth --remove' command, or remove the" NL
+        "### cache files by hand as described in the Subversion book at"     NL
+        "### 
http://svnbook.red-bean.com/nightly/en/svn.serverconfig.netmodel.html#svn.tour.initial.authn-cache-purge";
+                                                                             NL
         "###"                                                                NL
         "### HTTP timeouts, if given, are specified in seconds.  A timeout"  NL
         "### of 0, i.e. zero, causes a builtin default to be used."          NL

Modified: subversion/branches/addremove/subversion/libsvn_subr/config_win.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_subr/config_win.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_subr/config_win.c (original)
+++ subversion/branches/addremove/subversion/libsvn_subr/config_win.c Sat May 
23 14:16:56 2020
@@ -137,7 +137,7 @@ parse_section(svn_config_t *cfg, HKEY hk
                                 _("Can't enumerate registry values"));
 
       /* Ignore option names that start with '#', see
-         http://subversion.tigris.org/issues/show_bug.cgi?id=671 */
+         https://issues.apache.org/jira/browse/SVN-671 */
       if (type == REG_SZ && option->data[0] != '#')
         {
           DWORD value_len = (DWORD)value->blocksize;

Modified: subversion/branches/addremove/subversion/libsvn_subr/date.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_subr/date.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_subr/date.c (original)
+++ subversion/branches/addremove/subversion/libsvn_subr/date.c Sat May 23 
14:16:56 2020
@@ -37,7 +37,7 @@ enum rule_action {
                if the next template character matches the current
                value character, continue processing as normal.
                Otherwise, attempt to complete matching starting
-               immediately after the first subsequent occurrance of
+               immediately after the first subsequent occurrence of
                ']' in the template. */
   SKIP,     /* Ignore this template character */
   ACCEPT    /* Accept the value */

Modified: subversion/branches/addremove/subversion/libsvn_subr/deprecated.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_subr/deprecated.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_subr/deprecated.c (original)
+++ subversion/branches/addremove/subversion/libsvn_subr/deprecated.c Sat May 
23 14:16:56 2020
@@ -390,6 +390,30 @@ print_command_info(const svn_opt_subcomm
   return SVN_NO_ERROR;
 }
 
+const svn_opt_subcommand_desc2_t *
+svn_opt_get_canonical_subcommand2(const svn_opt_subcommand_desc2_t *table,
+                                  const char *cmd_name)
+{
+  int i = 0;
+
+  if (cmd_name == NULL)
+    return NULL;
+
+  while (table[i].name) {
+    int j;
+    if (strcmp(cmd_name, table[i].name) == 0)
+      return table + i;
+    for (j = 0; (j < SVN_OPT_MAX_ALIASES) && table[i].aliases[j]; j++)
+      if (strcmp(cmd_name, table[i].aliases[j]) == 0)
+        return table + i;
+
+    i++;
+  }
+
+  /* If we get here, there was no matching subcommand name or alias. */
+  return NULL;
+}
+
 const svn_opt_subcommand_desc_t *
 svn_opt_get_canonical_subcommand(const svn_opt_subcommand_desc_t *table,
                                  const char *cmd_name)
@@ -414,6 +438,344 @@ svn_opt_get_canonical_subcommand(const s
   return NULL;
 }
 
+const apr_getopt_option_t *
+svn_opt_get_option_from_code2(int code,
+                              const apr_getopt_option_t *option_table,
+                              const svn_opt_subcommand_desc2_t *command,
+                              apr_pool_t *pool)
+{
+  apr_size_t i;
+
+  for (i = 0; option_table[i].optch; i++)
+    if (option_table[i].optch == code)
+      {
+        if (command)
+          {
+            int j;
+
+            for (j = 0; ((j < SVN_OPT_MAX_OPTIONS) &&
+                         command->desc_overrides[j].optch); j++)
+              if (command->desc_overrides[j].optch == code)
+                {
+                  apr_getopt_option_t *tmpopt =
+                      apr_palloc(pool, sizeof(*tmpopt));
+                  *tmpopt = option_table[i];
+                  tmpopt->description = command->desc_overrides[j].desc;
+                  return tmpopt;
+                }
+          }
+        return &(option_table[i]);
+      }
+
+  return NULL;
+}
+
+const apr_getopt_option_t *
+svn_opt_get_option_from_code(int code,
+                             const apr_getopt_option_t *option_table)
+{
+  apr_size_t i;
+
+  for (i = 0; option_table[i].optch; i++)
+    if (option_table[i].optch == code)
+      return &(option_table[i]);
+
+  return NULL;
+}
+
+/* Like svn_opt_get_option_from_code2(), but also, if CODE appears a second
+ * time in OPTION_TABLE with a different name, then set *LONG_ALIAS to that
+ * second name, else set it to NULL. */
+static const apr_getopt_option_t *
+get_option_from_code(const char **long_alias,
+                     int code,
+                     const apr_getopt_option_t *option_table,
+                     const svn_opt_subcommand_desc2_t *command,
+                     apr_pool_t *pool)
+{
+  const apr_getopt_option_t *i;
+  const apr_getopt_option_t *opt
+    = svn_opt_get_option_from_code2(code, option_table, command, pool);
+
+  /* Find a long alias in the table, if there is one. */
+  *long_alias = NULL;
+  for (i = option_table; i->optch; i++)
+    {
+      if (i->optch == code && i->name != opt->name)
+        {
+          *long_alias = i->name;
+          break;
+        }
+    }
+
+  return opt;
+}
+
+/* Print an option OPT nicely into a STRING allocated in POOL.
+ * If OPT has a single-character short form, then print OPT->name (if not
+ * NULL) as an alias, else print LONG_ALIAS (if not NULL) as an alias.
+ * If DOC is set, include the generic documentation string of OPT,
+ * localized to the current locale if a translation is available.
+ */
+static void
+format_option(const char **string,
+              const apr_getopt_option_t *opt,
+              const char *long_alias,
+              svn_boolean_t doc,
+              apr_pool_t *pool)
+{
+  char *opts;
+
+  if (opt == NULL)
+    {
+      *string = "?";
+      return;
+    }
+
+  /* We have a valid option which may or may not have a "short
+     name" (a single-character alias for the long option). */
+  if (opt->optch <= 255)
+    opts = apr_psprintf(pool, "-%c [--%s]", opt->optch, opt->name);
+  else if (long_alias)
+    opts = apr_psprintf(pool, "--%s [--%s]", opt->name, long_alias);
+  else
+    opts = apr_psprintf(pool, "--%s", opt->name);
+
+  if (opt->has_arg)
+    opts = apr_pstrcat(pool, opts, _(" ARG"), SVN_VA_NULL);
+
+  if (doc)
+    opts = apr_psprintf(pool, "%-24s : %s", opts, _(opt->description));
+
+  *string = opts;
+}
+
+/* Print the canonical command name for CMD, and all its aliases, to
+   STREAM.  If HELP is set, print CMD's help string too, in which case
+   obtain option usage from OPTIONS_TABLE. */
+static svn_error_t *
+print_command_info2(const svn_opt_subcommand_desc2_t *cmd,
+                    const apr_getopt_option_t *options_table,
+                    const int *global_options,
+                    svn_boolean_t help,
+                    apr_pool_t *pool,
+                    FILE *stream)
+{
+  svn_boolean_t first_time;
+  apr_size_t i;
+
+  /* Print the canonical command name. */
+  SVN_ERR(svn_cmdline_fputs(cmd->name, stream, pool));
+
+  /* Print the list of aliases. */
+  first_time = TRUE;
+  for (i = 0; i < SVN_OPT_MAX_ALIASES; i++)
+    {
+      if (cmd->aliases[i] == NULL)
+        break;
+
+      if (first_time) {
+        SVN_ERR(svn_cmdline_fputs(" (", stream, pool));
+        first_time = FALSE;
+      }
+      else
+        SVN_ERR(svn_cmdline_fputs(", ", stream, pool));
+
+      SVN_ERR(svn_cmdline_fputs(cmd->aliases[i], stream, pool));
+    }
+
+  if (! first_time)
+    SVN_ERR(svn_cmdline_fputs(")", stream, pool));
+
+  if (help)
+    {
+      const apr_getopt_option_t *option;
+      const char *long_alias;
+      svn_boolean_t have_options = FALSE;
+
+      SVN_ERR(svn_cmdline_fprintf(stream, pool, ": %s", _(cmd->help)));
+
+      /* Loop over all valid option codes attached to the subcommand */
+      for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
+        {
+          if (cmd->valid_options[i])
+            {
+              if (!have_options)
+                {
+                  SVN_ERR(svn_cmdline_fputs(_("\nValid options:\n"),
+                                            stream, pool));
+                  have_options = TRUE;
+                }
+
+              /* convert each option code into an option */
+              option = get_option_from_code(&long_alias, cmd->valid_options[i],
+                                            options_table, cmd, pool);
+
+              /* print the option's docstring */
+              if (option && option->description)
+                {
+                  const char *optstr;
+                  format_option(&optstr, option, long_alias, TRUE, pool);
+                  SVN_ERR(svn_cmdline_fprintf(stream, pool, "  %s\n",
+                                              optstr));
+                }
+            }
+        }
+      /* And global options too */
+      if (global_options && *global_options)
+        {
+          SVN_ERR(svn_cmdline_fputs(_("\nGlobal options:\n"),
+                                    stream, pool));
+          have_options = TRUE;
+
+          for (i = 0; global_options[i]; i++)
+            {
+
+              /* convert each option code into an option */
+              option = get_option_from_code(&long_alias, global_options[i],
+                                            options_table, cmd, pool);
+
+              /* print the option's docstring */
+              if (option && option->description)
+                {
+                  const char *optstr;
+                  format_option(&optstr, option, long_alias, TRUE, pool);
+                  SVN_ERR(svn_cmdline_fprintf(stream, pool, "  %s\n",
+                                              optstr));
+                }
+            }
+        }
+
+      if (have_options)
+        SVN_ERR(svn_cmdline_fprintf(stream, pool, "\n"));
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* The body for svn_opt_print_generic_help2() function with standard error
+ * handling semantic. Handling of errors implemented at caller side. */
+static svn_error_t *
+print_generic_help_body(const char *header,
+                        const svn_opt_subcommand_desc2_t *cmd_table,
+                        const apr_getopt_option_t *opt_table,
+                        const char *footer,
+                        apr_pool_t *pool, FILE *stream)
+{
+  int i = 0;
+
+  if (header)
+    SVN_ERR(svn_cmdline_fputs(header, stream, pool));
+
+  while (cmd_table[i].name)
+    {
+      SVN_ERR(svn_cmdline_fputs("   ", stream, pool));
+      SVN_ERR(print_command_info2(cmd_table + i, opt_table,
+                                  NULL, FALSE,
+                                  pool, stream));
+      SVN_ERR(svn_cmdline_fputs("\n", stream, pool));
+      i++;
+    }
+
+  SVN_ERR(svn_cmdline_fputs("\n", stream, pool));
+
+  if (footer)
+    SVN_ERR(svn_cmdline_fputs(footer, stream, pool));
+
+  return SVN_NO_ERROR;
+}
+
+void
+svn_opt_print_generic_help2(const char *header,
+                            const svn_opt_subcommand_desc2_t *cmd_table,
+                            const apr_getopt_option_t *opt_table,
+                            const char *footer,
+                            apr_pool_t *pool, FILE *stream)
+{
+  svn_error_t *err;
+
+  err = print_generic_help_body(header, cmd_table, opt_table, footer, pool,
+                                stream);
+
+  /* Issue #3014:
+   * Don't print anything on broken pipes. The pipe was likely
+   * closed by the process at the other end. We expect that
+   * process to perform error reporting as necessary.
+   *
+   * ### This assumes that there is only one error in a chain for
+   * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */
+  if (err && err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
+    svn_handle_error2(err, stderr, FALSE, "svn: ");
+  svn_error_clear(err);
+}
+
+svn_boolean_t
+svn_opt_subcommand_takes_option3(const svn_opt_subcommand_desc2_t *command,
+                                 int option_code,
+                                 const int *global_options)
+{
+  apr_size_t i;
+
+  for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
+    if (command->valid_options[i] == option_code)
+      return TRUE;
+
+  if (global_options)
+    for (i = 0; global_options[i]; i++)
+      if (global_options[i] == option_code)
+        return TRUE;
+
+  return FALSE;
+}
+
+svn_boolean_t
+svn_opt_subcommand_takes_option2(const svn_opt_subcommand_desc2_t *command,
+                                 int option_code)
+{
+  return svn_opt_subcommand_takes_option3(command,
+                                          option_code,
+                                          NULL);
+}
+
+svn_boolean_t
+svn_opt_subcommand_takes_option(const svn_opt_subcommand_desc_t *command,
+                                int option_code)
+{
+  apr_size_t i;
+
+  for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
+    if (command->valid_options[i] == option_code)
+      return TRUE;
+
+  return FALSE;
+}
+
+void
+svn_opt_subcommand_help3(const char *subcommand,
+                         const svn_opt_subcommand_desc2_t *table,
+                         const apr_getopt_option_t *options_table,
+                         const int *global_options,
+                         apr_pool_t *pool)
+{
+  const svn_opt_subcommand_desc2_t *cmd =
+    svn_opt_get_canonical_subcommand2(table, subcommand);
+  svn_error_t *err;
+
+  if (cmd)
+    err = print_command_info2(cmd, options_table, global_options,
+                              TRUE, pool, stdout);
+  else
+    err = svn_cmdline_fprintf(stderr, pool,
+                              _("\"%s\": unknown command.\n\n"), subcommand);
+
+  if (err) {
+    /* Issue #3014: Don't print anything on broken pipes. */
+    if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
+      svn_handle_error2(err, stderr, FALSE, "svn: ");
+    svn_error_clear(err);
+  }
+}
+
 void
 svn_opt_subcommand_help2(const char *subcommand,
                          const svn_opt_subcommand_desc2_t *table,
@@ -521,6 +883,56 @@ svn_opt_args_to_target_array(apr_array_h
   return SVN_NO_ERROR;
 }
 
+svn_error_t *
+svn_opt_print_help4(apr_getopt_t *os,
+                    const char *pgm_name,
+                    svn_boolean_t print_version,
+                    svn_boolean_t quiet,
+                    svn_boolean_t verbose,
+                    const char *version_footer,
+                    const char *header,
+                    const svn_opt_subcommand_desc2_t *cmd_table,
+                    const apr_getopt_option_t *option_table,
+                    const int *global_options,
+                    const char *footer,
+                    apr_pool_t *pool)
+{
+  apr_array_header_t *targets = NULL;
+
+  if (os)
+    SVN_ERR(svn_opt_parse_all_args(&targets, os, pool));
+
+  if (os && targets->nelts)  /* help on subcommand(s) requested */
+    {
+      int i;
+
+      for (i = 0; i < targets->nelts; i++)
+        {
+          svn_opt_subcommand_help3(APR_ARRAY_IDX(targets, i, const char *),
+                                   cmd_table, option_table,
+                                   global_options, pool);
+        }
+    }
+  else if (print_version)   /* just --version */
+    {
+      SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer,
+                                          svn_version_extended(verbose, pool),
+                                          quiet, verbose, pool));
+    }
+  else if (os && !targets->nelts)            /* `-h', `--help', or `help' */
+    svn_opt_print_generic_help2(header,
+                                cmd_table,
+                                option_table,
+                                footer,
+                                pool,
+                                stdout);
+  else                                       /* unknown option or cmd */
+    SVN_ERR(svn_cmdline_fprintf(stderr, pool,
+                                _("Type '%s help' for usage.\n"), pgm_name));
+
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
 svn_opt_print_help3(apr_getopt_t *os,
                     const char *pgm_name,

Modified: subversion/branches/addremove/subversion/libsvn_subr/dirent_uri.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_subr/dirent_uri.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_subr/dirent_uri.c (original)
+++ subversion/branches/addremove/subversion/libsvn_subr/dirent_uri.c Sat May 
23 14:16:56 2020
@@ -37,6 +37,7 @@
 #include "svn_ctype.h"
 
 #include "dirent_uri.h"
+#include "private/svn_dirent_uri_private.h"
 #include "private/svn_fspath.h"
 #include "private/svn_cert.h"
 
@@ -292,8 +293,9 @@ uri_previous_segment(const char *uri,
 /* Return the canonicalized version of PATH, of type TYPE, allocated in
  * POOL.
  */
-static const char *
-canonicalize(path_type_t type, const char *path, apr_pool_t *pool)
+static svn_error_t *
+canonicalize(const char **canonical_path,
+             path_type_t type, const char *path, apr_pool_t *pool)
 {
   char *canon, *dst;
   const char *src;
@@ -307,8 +309,12 @@ canonicalize(path_type_t type, const cha
      depends on path not being zero-length.  */
   if (SVN_PATH_IS_EMPTY(path))
     {
-      assert(type != type_uri);
-      return "";
+      *canonical_path = "";
+      if (type == type_uri)
+        return svn_error_create(SVN_ERR_CANONICALIZATION_FAILED, NULL,
+                                _("An empty URI can not be canonicalized"));
+      else
+        return SVN_NO_ERROR;
     }
 
   dst = canon = apr_pcalloc(pool, strlen(path) + 1);
@@ -319,7 +325,12 @@ canonicalize(path_type_t type, const cha
   src = path;
   if (type == type_uri)
     {
-      assert(*src != '/');
+      if (*src == '/')
+        {
+          *canonical_path = src;
+          return svn_error_create(SVN_ERR_CANONICALIZATION_FAILED, NULL,
+                                  _("A URI can not start with '/'"));
+        }
 
       while (*src && (*src != '/') && (*src != ':'))
         src++;
@@ -546,7 +557,10 @@ canonicalize(path_type_t type, const cha
   if ((type == type_dirent) && canon[0] == '/' && canon[1] == '/')
     {
       if (canon_segments < 2)
-        return canon + 1;
+        {
+          *canonical_path = canon + 1;
+          return SVN_NO_ERROR;
+        }
       else
         {
           /* Now we're sure this is a valid UNC path, convert the server name
@@ -654,7 +668,8 @@ canonicalize(path_type_t type, const cha
       *dst = '\0';
     }
 
-  return canon;
+  *canonical_path = canon;
+  return SVN_NO_ERROR;
 }
 
 /* Return the string length of the longest common ancestor of PATH1 and PATH2.
@@ -883,6 +898,20 @@ svn_dirent_internal_style(const char *di
   return svn_dirent_canonicalize(internal_style(dirent, pool), pool);
 }
 
+svn_error_t *
+svn_dirent_internal_style_safe(const char **internal_style_dirent,
+                               const char **non_canonical_result,
+                               const char *dirent,
+                               apr_pool_t *result_pool,
+                               apr_pool_t *scratch_pool)
+{
+  return svn_error_trace(
+      svn_dirent_canonicalize_safe(internal_style_dirent,
+                                   non_canonical_result,
+                                   internal_style(dirent, scratch_pool),
+                                   result_pool, scratch_pool));
+}
+
 const char *
 svn_dirent_local_style(const char *dirent, apr_pool_t *pool)
 {
@@ -906,14 +935,18 @@ svn_dirent_local_style(const char *diren
   return dirent;
 }
 
-const char *
-svn_relpath__internal_style(const char *relpath,
-                            apr_pool_t *pool)
-{
-  return svn_relpath_canonicalize(internal_style(relpath, pool), pool);
+svn_error_t *
+svn_relpath__make_internal(const char **internal_style_relpath,
+                           const char *relpath,
+                           apr_pool_t *result_pool,
+                           apr_pool_t *scratch_pool)
+{
+  return svn_error_trace(
+      svn_relpath_canonicalize_safe(internal_style_relpath, NULL,
+                                    internal_style(relpath, scratch_pool),
+                                    result_pool, scratch_pool));
 }
 
-
 /* We decided against using apr_filepath_root here because of the negative
    performance impact (creating a pool and converting strings ). */
 svn_boolean_t
@@ -1643,19 +1676,84 @@ svn_dirent_get_absolute(const char **pab
 const char *
 svn_uri_canonicalize(const char *uri, apr_pool_t *pool)
 {
-  return canonicalize(type_uri, uri, pool);
+  const char *result;
+  svn_error_t *const err = canonicalize(&result, type_uri, uri, pool);
+  if (err)
+    {
+      svn_error_clear(err);
+      SVN_ERR_ASSERT_NO_RETURN(!"URI canonicalization failed");
+    }
+  return result;
+}
+
+svn_error_t *
+svn_uri_canonicalize_safe(const char **canonical_uri,
+                          const char **non_canonical_result,
+                          const char *uri,
+                          apr_pool_t *result_pool,
+                          apr_pool_t *scratch_pool)
+{
+  const char *result = NULL;
+  SVN_ERR(canonicalize(&result, type_uri, uri, result_pool));
+  if (!svn_uri_is_canonical(result, scratch_pool))
+    {
+      if (non_canonical_result)
+        *non_canonical_result = result;
+
+      return svn_error_createf(
+          SVN_ERR_CANONICALIZATION_FAILED, NULL,
+          _("Could not canonicalize URI '%s'"
+            " (the result '%s' is not canonical)"),
+          uri, result);
+    }
+  *canonical_uri = result;
+  return SVN_NO_ERROR;
 }
 
 const char *
 svn_relpath_canonicalize(const char *relpath, apr_pool_t *pool)
 {
-  return canonicalize(type_relpath, relpath, pool);
+  const char *result;
+  svn_error_t *const err = canonicalize(&result, type_relpath, relpath, pool);
+  if (err)
+    {
+      svn_error_clear(err);
+      SVN_ERR_ASSERT_NO_RETURN(!"relpath canonicalization failed");
+    }
+  return result;
 }
 
-const char *
-svn_dirent_canonicalize(const char *dirent, apr_pool_t *pool)
+svn_error_t *
+svn_relpath_canonicalize_safe(const char **canonical_relpath,
+                              const char **non_canonical_result,
+                              const char *relpath,
+                              apr_pool_t *result_pool,
+                              apr_pool_t *scratch_pool)
+{
+  const char *result = NULL;
+  SVN_ERR(canonicalize(&result, type_relpath, relpath, result_pool));
+  if (!svn_relpath_is_canonical(result))
+    {
+      if (non_canonical_result)
+        *non_canonical_result = result;
+
+      return svn_error_createf(
+          SVN_ERR_CANONICALIZATION_FAILED, NULL,
+          _("Could not canonicalize relpath '%s'"
+            " (the result '%s' is not canonical)"),
+          relpath, result);
+    }
+
+  SVN_UNUSED(scratch_pool);
+  *canonical_relpath = result;
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+canonicalize_dirent(const char **result, const char *dirent, apr_pool_t *pool)
 {
-  const char *dst = canonicalize(type_dirent, dirent, pool);
+  const char *dst;
+  SVN_ERR(canonicalize(&dst, type_dirent, dirent, pool));
 
 #ifdef SVN_USE_DOS_PATHS
   /* Handle a specific case on Windows where path == "X:/". Here we have to
@@ -1671,11 +1769,50 @@ svn_dirent_canonicalize(const char *dire
       dst_slash[2] = '/';
       dst_slash[3] = '\0';
 
-      return dst_slash;
+      *result = dst_slash;
+      return SVN_NO_ERROR;
     }
 #endif /* SVN_USE_DOS_PATHS */
 
-  return dst;
+  *result = dst;
+  return SVN_NO_ERROR;
+}
+
+const char *
+svn_dirent_canonicalize(const char *dirent, apr_pool_t *pool)
+{
+  const char *result;
+  svn_error_t *const err = canonicalize_dirent(&result, dirent, pool);
+  if (err)
+    {
+      svn_error_clear(err);
+      SVN_ERR_ASSERT_NO_RETURN(!"dirent canonicalization failed");
+    }
+  return result;
+}
+
+svn_error_t *
+svn_dirent_canonicalize_safe(const char **canonical_dirent,
+                             const char **non_canonical_result,
+                             const char *dirent,
+                             apr_pool_t *result_pool,
+                             apr_pool_t *scratch_pool)
+{
+  const char *result = NULL;
+  SVN_ERR(canonicalize_dirent(&result, dirent, result_pool));
+  if (!svn_dirent_is_canonical(result, scratch_pool))
+    {
+      if (non_canonical_result)
+        *non_canonical_result = result;
+
+      return svn_error_createf(
+          SVN_ERR_CANONICALIZATION_FAILED, NULL,
+          _("Could not canonicalize dirent '%s'"
+            " (the result '%s' is not canonical)"),
+          dirent, result);
+    }
+  *canonical_dirent = result;
+  return SVN_NO_ERROR;
 }
 
 svn_boolean_t

Modified: subversion/branches/addremove/subversion/libsvn_subr/error.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_subr/error.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_subr/error.c (original)
+++ subversion/branches/addremove/subversion/libsvn_subr/error.c Sat May 23 
14:16:56 2020
@@ -146,6 +146,7 @@ svn_error__locate(const char *file, long
 
 /* Cleanup function for errors.  svn_error_clear () removes this so
    errors that are properly handled *don't* hit this code. */
+#ifdef SVN_DEBUG
 static apr_status_t err_abort(void *data)
 {
   svn_error_t *err = data;  /* For easy viewing in a debugger */
@@ -155,6 +156,7 @@ static apr_status_t err_abort(void *data
     abort();
   return APR_SUCCESS;
 }
+#endif
 
 
 static svn_error_t *

Modified: subversion/branches/addremove/subversion/libsvn_subr/gpg_agent.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_subr/gpg_agent.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_subr/gpg_agent.c (original)
+++ subversion/branches/addremove/subversion/libsvn_subr/gpg_agent.c Sat May 23 
14:16:56 2020
@@ -108,7 +108,7 @@ escape_blanks(char *str)
 
 #define is_hex(c) (((c) >= '0' && (c) <= '9') || ((c) >= 'A' && (c) <= 'F'))
 #define hex_to_int(c) ((c) < '9' ? (c) - '0' : (c) - 'A' + 10)
- 
+
 /* Modify STR in-place.  '%', CR and LF are always percent escaped,
    other characters may be percent escaped, always using uppercase
    hex, see https://www.gnupg.org/documentation/manuals/assuan.pdf */
@@ -680,9 +680,9 @@ simple_gpg_agent_next_creds(void **crede
   /* TODO: This attempt limit hard codes it at 3 attempts (or 2 retries)
    * which matches svn command line client's retry_limit as set in
    * svn_cmdline_create_auth_baton().  It would be nice to have that
-   * limit reflected here but that violates the boundry between the
+   * limit reflected here but that violates the boundary between the
    * prompt provider and the cache provider.  gpg-agent is acting as
-   * both here due to the peculiarties of their design so we'll have to
+   * both here due to the peculiarities of their design so we'll have to
    * live with this for now.  Note that when these failures get exceeded
    * it'll eventually fall back on the retry limits of whatever prompt
    * provider is in effect, so this effectively doubles the limit. */

Modified: subversion/branches/addremove/subversion/libsvn_subr/io.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_subr/io.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_subr/io.c (original)
+++ subversion/branches/addremove/subversion/libsvn_subr/io.c Sat May 23 
14:16:56 2020
@@ -155,8 +155,14 @@ typedef struct _FILE_DISPOSITION_INFO {
   BOOL DeleteFile;
 } FILE_DISPOSITION_INFO, *PFILE_DISPOSITION_INFO;
 
+typedef struct _FILE_ATTRIBUTE_TAG_INFO {
+  DWORD FileAttributes;
+  DWORD ReparseTag;
+} FILE_ATTRIBUTE_TAG_INFO, *PFILE_ATTRIBUTE_TAG_INFO;
+
 #define FileRenameInfo 3
 #define FileDispositionInfo 4
+#define FileAttributeTagInfo 9
 #endif /* WIN32 < Vista */
 
 /* One-time initialization of the late bound Windows API functions. */
@@ -169,19 +175,30 @@ typedef DWORD (WINAPI *GETFINALPATHNAMEB
                DWORD cchFilePath,
                DWORD dwFlags);
 
+typedef BOOL (WINAPI *GetFileInformationByHandleEx_t)(HANDLE hFile,
+                                                      int FileInformationClass,
+                                                      LPVOID lpFileInformation,
+                                                      DWORD dwBufferSize);
+
 typedef BOOL (WINAPI *SetFileInformationByHandle_t)(HANDLE hFile,
                                                     int FileInformationClass,
                                                     LPVOID lpFileInformation,
                                                     DWORD dwBufferSize);
 
 static GETFINALPATHNAMEBYHANDLE get_final_path_name_by_handle_proc = NULL;
+static GetFileInformationByHandleEx_t get_file_information_by_handle_ex_proc = 
NULL;
 static SetFileInformationByHandle_t set_file_information_by_handle_proc = NULL;
 
-/* Forward declaration. */
+/* Forward declarations. */
 static svn_error_t * io_win_read_link(svn_string_t **dest,
                                       const char *path,
                                       apr_pool_t *pool);
 
+static svn_error_t * io_win_check_path(svn_node_kind_t *kind_p,
+                                       svn_boolean_t *is_symlink_p,
+                                       const char *path,
+                                       apr_pool_t *pool);
+
 #endif
 
 /* Forward declaration */
@@ -342,7 +359,6 @@ io_check_path(const char *path,
   /* Not using svn_io_stat() here because we want to check the
      apr_err return explicitly. */
   SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
-
   flags = resolve_symlinks ? APR_FINFO_MIN : (APR_FINFO_MIN | APR_FINFO_LINK);
   apr_err = apr_stat(&finfo, path_apr, flags, pool);
 
@@ -405,8 +421,12 @@ svn_io_check_resolved_path(const char *p
                            svn_node_kind_t *kind,
                            apr_pool_t *pool)
 {
+#if WIN32
+  return io_win_check_path(kind, NULL, path, pool);
+#else
   svn_boolean_t ignored;
   return io_check_path(path, TRUE, &ignored, kind, pool);
+#endif
 }
 
 svn_error_t *
@@ -414,8 +434,19 @@ svn_io_check_path(const char *path,
                   svn_node_kind_t *kind,
                   apr_pool_t *pool)
 {
+#if WIN32
+  svn_boolean_t is_symlink;
+
+  SVN_ERR(io_win_check_path(kind, &is_symlink, path, pool));
+
+  if (is_symlink)
+    *kind = svn_node_file;
+
+  return SVN_NO_ERROR;
+#else
   svn_boolean_t ignored;
   return io_check_path(path, FALSE, &ignored, kind, pool);
+#endif
 }
 
 svn_error_t *
@@ -424,7 +455,23 @@ svn_io_check_special_path(const char *pa
                           svn_boolean_t *is_special,
                           apr_pool_t *pool)
 {
+#ifdef WIN32
+  svn_boolean_t is_symlink;
+
+  SVN_ERR(io_win_check_path(kind, &is_symlink, path, pool));
+
+  if (is_symlink)
+    {
+      *is_special = TRUE;
+      *kind = svn_node_file;
+    }
+  else
+    *is_special = FALSE;
+
+  return SVN_NO_ERROR;
+#else
   return io_check_path(path, FALSE, is_special, kind, pool);
+#endif
 }
 
 struct temp_file_cleanup_s
@@ -1527,7 +1574,7 @@ reown_file(const char *path,
 }
 
 /* Determine what the PERMS for a new file should be by looking at the
-   permissions of a temporary file that we create in DIRECTORY.  
+   permissions of a temporary file that we create in DIRECTORY.
    DIRECTORY can be NULL in which case the system temporary dir is used.
    Unfortunately, umask() as defined in POSIX provides no thread-safe way
    to get at the current value of the umask, so what we're doing here is
@@ -1617,13 +1664,14 @@ merge_default_file_perms(apr_file_t *fd,
    that attempts to honor the users umask when dealing with
    permission changes.  It is a no-op when invoked on a symlink. */
 static svn_error_t *
-io_set_file_perms(const char *path,
-                  svn_boolean_t change_readwrite,
-                  svn_boolean_t enable_write,
-                  svn_boolean_t change_executable,
-                  svn_boolean_t executable,
-                  svn_boolean_t ignore_enoent,
-                  apr_pool_t *pool)
+io_set_perms(const char *path,
+             svn_boolean_t is_file,
+             svn_boolean_t change_readwrite,
+             svn_boolean_t enable_write,
+             svn_boolean_t change_executable,
+             svn_boolean_t executable,
+             svn_boolean_t ignore_enoent,
+             apr_pool_t *pool)
 {
   apr_status_t status;
   const char *path_apr;
@@ -1643,9 +1691,16 @@ io_set_file_perms(const char *path,
                             || SVN__APR_STATUS_IS_ENOTDIR(status)))
         return SVN_NO_ERROR;
       else if (status != APR_ENOTIMPL)
-        return svn_error_wrap_apr(status,
-                                  _("Can't change perms of file '%s'"),
-                                  svn_dirent_local_style(path, pool));
+        {
+          if (is_file)
+            return svn_error_wrap_apr(status,
+                                      _("Can't change perms of file '%s'"),
+                                      svn_dirent_local_style(path, pool));
+          else
+            return svn_error_wrap_apr(status,
+                                      _("Can't change perms of directory 
'%s'"),
+                                      svn_dirent_local_style(path, pool));
+        }
       return SVN_NO_ERROR;
     }
 
@@ -1745,10 +1800,50 @@ io_set_file_perms(const char *path,
       status = apr_file_attrs_set(path_apr, attrs, attrs_values, pool);
     }
 
-  return svn_error_wrap_apr(status,
-                            _("Can't change perms of file '%s'"),
-                            svn_dirent_local_style(path, pool));
+  if (is_file)
+    {
+      return svn_error_wrap_apr(status,
+                                _("Can't change perms of file '%s'"),
+                                svn_dirent_local_style(path, pool));
+    }
+  else
+    {
+      return svn_error_wrap_apr(status,
+                                _("Can't change perms of directory '%s'"),
+                                svn_dirent_local_style(path, pool));
+    }
 }
+
+static svn_error_t *
+io_set_file_perms(const char *path,
+                  svn_boolean_t change_readwrite,
+                  svn_boolean_t enable_write,
+                  svn_boolean_t change_executable,
+                  svn_boolean_t executable,
+                  svn_boolean_t ignore_enoent,
+                  apr_pool_t *pool)
+{
+  return svn_error_trace(io_set_perms(path, TRUE,
+                                      change_readwrite, enable_write,
+                                      change_executable, executable,
+                                      ignore_enoent, pool));
+}
+
+static svn_error_t *
+io_set_dir_perms(const char *path,
+                 svn_boolean_t change_readwrite,
+                 svn_boolean_t enable_write,
+                 svn_boolean_t change_executable,
+                 svn_boolean_t executable,
+                 svn_boolean_t ignore_enoent,
+                 apr_pool_t *pool)
+{
+  return svn_error_trace(io_set_perms(path, FALSE,
+                                      change_readwrite, enable_write,
+                                      change_executable, executable,
+                                      ignore_enoent, pool));
+}
+
 #endif /* !WIN32 && !__OS2__ */
 
 #ifdef WIN32
@@ -1897,6 +1992,9 @@ static svn_error_t *win_init_dynamic_imp
       get_final_path_name_by_handle_proc = (GETFINALPATHNAMEBYHANDLE)
         GetProcAddress(kernel32, "GetFinalPathNameByHandleW");
 
+      get_file_information_by_handle_ex_proc = (GetFileInformationByHandleEx_t)
+        GetProcAddress(kernel32, "GetFileInformationByHandleEx");
+
       set_file_information_by_handle_proc = (SetFileInformationByHandle_t)
         GetProcAddress(kernel32, "SetFileInformationByHandle");
     }
@@ -1973,6 +2071,33 @@ static svn_error_t * io_win_read_link(sv
       }
 }
 
+/* Wrapper around Windows API function GetFileInformationByHandleEx() that
+ * returns APR status instead of boolean flag. */
+static apr_status_t
+win32_get_file_information_by_handle(HANDLE hFile,
+                                     int FileInformationClass,
+                                     LPVOID lpFileInformation,
+                                     DWORD dwBufferSize)
+{
+  svn_error_clear(svn_atomic__init_once(&win_dynamic_imports_state,
+                                        win_init_dynamic_imports,
+                                        NULL, NULL));
+
+  if (!get_file_information_by_handle_ex_proc)
+    {
+      return SVN_ERR_UNSUPPORTED_FEATURE;
+    }
+
+  if (!get_file_information_by_handle_ex_proc(hFile, FileInformationClass,
+                                              lpFileInformation,
+                                              dwBufferSize))
+    {
+      return apr_get_os_error();
+    }
+
+  return APR_SUCCESS;
+}
+
 /* Wrapper around Windows API function SetFileInformationByHandle() that
  * returns APR status instead of boolean flag. */
 static apr_status_t
@@ -2000,6 +2125,105 @@ win32_set_file_information_by_handle(HAN
   return APR_SUCCESS;
 }
 
+/* Fast Win32-specific helper for svn_io_check_path() and related functions
+ * that only requires a single GetFileAttributes() call in most cases.
+ */
+static svn_error_t * io_win_check_path(svn_node_kind_t *kind_p,
+                                       svn_boolean_t *is_symlink_p,
+                                       const char *path,
+                                       apr_pool_t *pool)
+{
+  DWORD attrs;
+  const WCHAR *wpath;
+  apr_status_t status;
+
+  if (path[0] == '\0')
+    path = ".";
+
+  SVN_ERR(svn_io__utf8_to_unicode_longpath(&wpath, path, pool));
+
+  attrs = GetFileAttributesW(wpath);
+  if (attrs == INVALID_FILE_ATTRIBUTES)
+    {
+      status = apr_get_os_error();
+      if (APR_STATUS_IS_ENOENT(status) || SVN__APR_STATUS_IS_ENOTDIR(status))
+        {
+          *kind_p = svn_node_none;
+          if (is_symlink_p)
+            *is_symlink_p = FALSE;
+          return SVN_NO_ERROR;
+        }
+      else
+        {
+          return svn_error_wrap_apr(status, _("Can't stat '%s'"),
+                                    svn_dirent_local_style(path, pool));
+        }
+    }
+
+  if (attrs & FILE_ATTRIBUTE_DIRECTORY)
+    *kind_p = svn_node_dir;
+  else
+    *kind_p = svn_node_file;
+
+  /* If this is a reparse point, and if we've been asked to check whether
+     we are dealing with a symlink, then open the file and check that.
+
+     Otherwise, it's either definitely not a symlink or the caller
+     doesn't care about this distinction.
+   */
+  if (is_symlink_p && (attrs & FILE_ATTRIBUTE_REPARSE_POINT))
+    {
+      const WCHAR *wfname;
+      HANDLE hFile;
+      FILE_ATTRIBUTE_TAG_INFO taginfo = { 0 };
+
+      SVN_ERR(svn_io__utf8_to_unicode_longpath(&wfname, path, pool));
+
+      hFile = CreateFileW(wfname, FILE_READ_ATTRIBUTES,
+                          FILE_SHARE_READ | FILE_SHARE_WRITE | 
FILE_SHARE_DELETE,
+                          NULL, OPEN_EXISTING,
+                          FILE_FLAG_OPEN_REPARSE_POINT | 
FILE_FLAG_BACKUP_SEMANTICS,
+                          NULL);
+      if (hFile == INVALID_HANDLE_VALUE)
+        {
+          status = apr_get_os_error();
+          if (APR_STATUS_IS_ENOENT(status) || 
SVN__APR_STATUS_IS_ENOTDIR(status))
+            {
+              *kind_p = svn_node_none;
+              *is_symlink_p = FALSE;
+              return SVN_NO_ERROR;
+            }
+          else
+            {
+              return svn_error_wrap_apr(status, _("Can't stat '%s'"),
+                                        svn_dirent_local_style(path, pool));
+            }
+        }
+
+      status = win32_get_file_information_by_handle(hFile, 
FileAttributeTagInfo,
+                                                    &taginfo, sizeof(taginfo));
+      CloseHandle(hFile);
+
+      if (status)
+        return svn_error_wrap_apr(status, _("Can't stat '%s'"),
+                                  svn_dirent_local_style(path, pool));
+
+      /* The surrogate bit in the reparse tag specifies if "the file or 
directory
+         represents another named entity in the system" which is used to 
determine
+         if this reparse point behaves like a symlink.
+
+         
https://docs.microsoft.com/en-us/windows/desktop/fileio/reparse-point-tags
+       */
+      *is_symlink_p = IsReparseTagNameSurrogate(taginfo.ReparseTag);
+    }
+  else if (is_symlink_p)
+    {
+      *is_symlink_p = FALSE;
+    }
+
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
 svn_io__win_delete_file_on_close(apr_file_t *file,
                                  const char *path,
@@ -2110,6 +2334,55 @@ svn_io_set_file_read_write_carefully(con
   return svn_io_set_file_read_only(path, ignore_enoent, pool);
 }
 
+#if defined(WIN32) || defined(__OS2__)
+/* Helper for svn_io_set_file_read_* */
+static svn_error_t *
+io_set_readonly_flag(const char *path_apr, /* file-system path */
+                     const char *path,     /* UTF-8 path */
+                     svn_boolean_t set_flag,
+                     svn_boolean_t is_file,
+                     svn_boolean_t ignore_enoent,
+                     apr_pool_t *pool)
+{
+  apr_status_t status;
+
+  status = apr_file_attrs_set(path_apr,
+                              (set_flag ? APR_FILE_ATTR_READONLY : 0),
+                              APR_FILE_ATTR_READONLY,
+                              pool);
+
+  if (status && status != APR_ENOTIMPL)
+    if (!(ignore_enoent && (APR_STATUS_IS_ENOENT(status)
+                            || SVN__APR_STATUS_IS_ENOTDIR(status))))
+      {
+        if (is_file)
+          {
+            if (set_flag)
+              return svn_error_wrap_apr(status,
+                                        _("Can't set file '%s' read-only"),
+                                        svn_dirent_local_style(path, pool));
+            else
+              return svn_error_wrap_apr(status,
+                                        _("Can't set file '%s' read-write"),
+                                        svn_dirent_local_style(path, pool));
+          }
+        else
+          {
+            if (set_flag)
+              return svn_error_wrap_apr(status,
+                                        _("Can't set directory '%s' 
read-only"),
+                                        svn_dirent_local_style(path, pool));
+            else
+              return svn_error_wrap_apr(status,
+                                        _("Can't set directory '%s' 
read-write"),
+                                        svn_dirent_local_style(path, pool));
+          }
+      }
+  return SVN_NO_ERROR;
+}
+#endif
+
+
 svn_error_t *
 svn_io_set_file_read_only(const char *path,
                           svn_boolean_t ignore_enoent,
@@ -2121,24 +2394,11 @@ svn_io_set_file_read_only(const char *pa
   return io_set_file_perms(path, TRUE, FALSE, FALSE, FALSE,
                            ignore_enoent, pool);
 #else
-  apr_status_t status;
   const char *path_apr;
 
   SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
-
-  status = apr_file_attrs_set(path_apr,
-                              APR_FILE_ATTR_READONLY,
-                              APR_FILE_ATTR_READONLY,
-                              pool);
-
-  if (status && status != APR_ENOTIMPL)
-    if (!(ignore_enoent && (APR_STATUS_IS_ENOENT(status)
-                            || SVN__APR_STATUS_IS_ENOTDIR(status))))
-      return svn_error_wrap_apr(status,
-                                _("Can't set file '%s' read-only"),
-                                svn_dirent_local_style(path, pool));
-
-  return SVN_NO_ERROR;
+  return io_set_readonly_flag(path_apr, path,
+                              TRUE, TRUE, ignore_enoent, pool);
 #endif
 }
 
@@ -2154,23 +2414,11 @@ svn_io_set_file_read_write(const char *p
   return io_set_file_perms(path, TRUE, TRUE, FALSE, FALSE,
                            ignore_enoent, pool);
 #else
-  apr_status_t status;
   const char *path_apr;
 
   SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
-
-  status = apr_file_attrs_set(path_apr,
-                              0,
-                              APR_FILE_ATTR_READONLY,
-                              pool);
-
-  if (status && status != APR_ENOTIMPL)
-    if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
-      return svn_error_wrap_apr(status,
-                                _("Can't set file '%s' read-write"),
-                                svn_dirent_local_style(path, pool));
-
-  return SVN_NO_ERROR;
+  return io_set_readonly_flag(path_apr, path,
+                              FALSE, TRUE, ignore_enoent, pool);
 #endif
 }
 
@@ -2541,27 +2789,37 @@ stringbuf_from_aprfile(svn_stringbuf_t *
     {
       apr_finfo_t finfo = { 0 };
 
-      /* In some cases we get size 0 and no error for non files,
-          so we also check for the name. (= cached in apr_file_t) */
+      /* In some cases we get size 0 and no error for non files, so we
+         also check for the name. (= cached in apr_file_t) */
       if (! apr_file_info_get(&finfo, APR_FINFO_SIZE, file) && finfo.fname)
         {
-          /* we've got the file length. Now, read it in one go. */
+          /* In general, there is no guarantee that the given file size is
+             correct, for instance, because the underlying handle could be
+             pointing to a pipe.  We don't know that in advance, so attempt
+             to read *one more* byte than necessary.  If we get an EOF, then
+             we're done and we have successfully avoided reading the file 
chunk-
+             by-chunk.  If we don't, we fall through and do so to read the
+             remaining part of the file. */
           svn_boolean_t eof;
-          res_initial_len = (apr_size_t)finfo.size;
+          res_initial_len = (apr_size_t)finfo.size + 1;
           res = svn_stringbuf_create_ensure(res_initial_len, pool);
           SVN_ERR(svn_io_file_read_full2(file, res->data,
                                          res_initial_len, &res->len,
                                          &eof, pool));
           res->data[res->len] = 0;
 
-          *result = res;
-          return SVN_NO_ERROR;
+          if (eof)
+            {
+              *result = res;
+              return SVN_NO_ERROR;
+            }
         }
     }
 
   /* XXX: We should check the incoming data for being of type binary. */
   buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
-  res = svn_stringbuf_create_ensure(res_initial_len, pool);
+  if (!res)
+    res = svn_stringbuf_create_ensure(res_initial_len, pool);
 
   /* apr_file_read will not return data and eof in the same call. So this loop
    * is safe from missing read data.  */
@@ -2707,8 +2965,8 @@ svn_io_remove_dir(const char *path, apr_
  directory scan.  A previous workaround involving rewinddir is
  problematic on Win32 and some NFS clients, notably NetBSD.
 
- See http://subversion.tigris.org/issues/show_bug.cgi?id=1896 and
- http://subversion.tigris.org/issues/show_bug.cgi?id=3501.
+ See https://issues.apache.org/jira/browse/SVN-1896 and
+ https://issues.apache.org/jira/browse/SVN-3501.
 */
 
 /* Neither windows nor unix allows us to delete a non-empty
@@ -2746,6 +3004,12 @@ svn_io_remove_dir2(const char *path, svn
       return svn_error_trace(err);
     }
 
+  /* On Unix, nothing can be removed from a non-writable directory. */
+#if !defined(WIN32) && !defined(__OS2__)
+  SVN_ERR(io_set_dir_perms(path, TRUE, TRUE, FALSE, FALSE,
+                           ignore_enoent, pool));
+#endif
+
   for (hi = apr_hash_first(subpool, dirents); hi; hi = apr_hash_next(hi))
     {
       const char *name = apr_hash_this_key(hi);
@@ -4227,7 +4491,45 @@ win32_file_rename(const WCHAR *from_path
     }
 
   if (!MoveFileExW(from_path_w, to_path_w, flags))
-      return apr_get_os_error();
+    {
+      apr_status_t err = apr_get_os_error();
+      /* If the target file is read only NTFS reports EACCESS and
+         FAT/FAT32 reports EEXIST */
+      if (APR_STATUS_IS_EACCES(err) || APR_STATUS_IS_EEXIST(err))
+        {
+          DWORD attrs = GetFileAttributesW(to_path_w);
+          if (attrs == INVALID_FILE_ATTRIBUTES)
+            {
+              apr_status_t stat_err = apr_get_os_error();
+              if (!(APR_STATUS_IS_ENOENT(stat_err) || 
SVN__APR_STATUS_IS_ENOTDIR(stat_err)))
+                /* We failed to stat the file, propagate the original error */
+                return err;
+            }
+          else if (attrs & FILE_ATTRIBUTE_READONLY)
+            {
+              /* Try to set the destination file writable because Windows will
+                 not allow us to rename when to_path is read-only, but will
+                 allow renaming when from_path is read only. */
+              attrs &= ~FILE_ATTRIBUTE_READONLY;
+              if (!SetFileAttributesW(to_path_w, attrs))
+                {
+                  err = apr_get_os_error();
+                  if (!(APR_STATUS_IS_ENOENT(err) || 
SVN__APR_STATUS_IS_ENOTDIR(err)))
+                    /* We failed to set file attributes, propagate this new 
error */
+                    return err;
+                }
+            }
+
+          /* NOTE: If the file is not read-only, we don't know if the file did
+             not have the read-only attribute in the first place or if this
+             attribute disappeared due to a race, so try to rename it anyway.
+           */
+          if (!MoveFileExW(from_path_w, to_path_w, flags))
+            return apr_get_os_error();
+        }
+      else
+        return err;
+    }
 
   return APR_SUCCESS;
 }
@@ -4251,18 +4553,6 @@ svn_io_file_rename2(const char *from_pat
   SVN_ERR(svn_io__utf8_to_unicode_longpath(&from_path_w, from_path_apr, pool));
   SVN_ERR(svn_io__utf8_to_unicode_longpath(&to_path_w, to_path_apr, pool));
   status = win32_file_rename(from_path_w, to_path_w, flush_to_disk);
-
-  /* If the target file is read only NTFS reports EACCESS and
-     FAT/FAT32 reports EEXIST */
-  if (APR_STATUS_IS_EACCES(status) || APR_STATUS_IS_EEXIST(status))
-    {
-      /* Set the destination file writable because Windows will not
-         allow us to rename when to_path is read-only, but will
-         allow renaming when from_path is read only. */
-      SVN_ERR(svn_io_set_file_read_write(to_path, TRUE, pool));
-
-      status = win32_file_rename(from_path_w, to_path_w, flush_to_disk);
-    }
   WIN32_RETRY_LOOP(status, win32_file_rename(from_path_w, to_path_w,
                                              flush_to_disk));
 #elif defined(__OS2__)
@@ -4484,8 +4774,17 @@ svn_io_dir_remove_nonrecursive(const cha
   {
     svn_boolean_t retry = TRUE;
 
+    if (APR_STATUS_IS_EACCES(status) || APR_STATUS_IS_EEXIST(status))
+      {
+        /* Make the destination directory writable because Windows
+           forbids deleting read-only items. */
+        SVN_ERR(io_set_readonly_flag(dirname_apr, dirname,
+                                     FALSE, FALSE, TRUE, pool));
+        status = apr_dir_remove(dirname_apr, pool);
+      }
+
     if (status == APR_FROM_OS_ERROR(ERROR_DIR_NOT_EMPTY))
-    {
+      {
         apr_status_t empty_status = dir_is_empty(dirname_apr, pool);
 
         if (APR_STATUS_IS_ENOTEMPTY(empty_status))
@@ -4631,7 +4930,7 @@ svn_io_dir_walk2(const char *dirname,
         }
       else if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK)
         {
-          /* some other directory. pass it to the callback. */
+          /* a regular file or a symlink. pass it to the callback. */
           SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname,
                                      subpool));
           full_path = svn_dirent_join(dirname, name_utf8, subpool);


Reply via email to