On Mon, Mar 16, 2020 at 5:11 AM <[email protected]> wrote:
>
> Author: jamessan
> Date: Mon Mar 16 04:11:36 2020
> New Revision: 1875230
>
> URL: http://svn.apache.org/viewvc?rev=1875230&view=rev
> Log:
> Followup to r1874093, add Windows-specific argument escaping
>
> Rather than directly using apr_pescape_shell(), use apr_escape_shell() to give
> an indication whether escaping is needed. If APR reports no escaping is
> needed, simply surround the argument in double-quotes to handle any embedded
> whitespace.
>
> When escaping is needed, on Unix we continue to use APR's escaping +
> post-processing for whitespace. On Windows, perform the escaping ourselves
> per
> these rules:
>
> 1. Surround the string with double-quotes
> 2. Escape any double-quotes or backslashes preceding a double-quote
> 3. Escape any metacharacters, including double-quotes, with ^
>
> * subversion/libsvn_subr/cmdline.c
> (escape_path): Refactored as above
I'm sorry I didn't notice this before, but on Windows, since this
commit r1875230 (which is included in 1.14.0) the escaping of
SVN_EDITOR is broken. If the envvar refers to a program with a space
in its path, between quotes, those quotes seem to be lost now, which
results in:
[[[
C:\test>set EDITOR="C:\Program Files\Notepad++\notepad++" -nosession -multiInst
C:\test>svn pe svn:ignore .
'C:\Program' is not recognized as an internal or external command,
operable program or batch file.
svn: E200012: system('"C:\Program Files\Notepad++\notepad++"
-nosession -multiInst "svn-prop.tmp"') returned 1
]]]
This used to work (it works in 1.13.x), I've been using this for a long time.
This commit was the last of three commits related to escaping:
r1874057 (Escape filenames when invoking $SVN_EDITOR)
r1874093 (Followup to r1874057, escape whitespace instead of quoting filename)
r1875230 (Followup to r1874093, add Windows-specific argument escaping)
Interestingly:
- before all three: works
- after r1874057: fails
- after r1874093: works
- after r1875230: fails
Apparently, there was a test failing on Windows after r1874093, which
was pointed out by Bert in this thread:
https://lists.apache.org/thread.html/r8b61c011f4ab66006dd96d61d0e099da03da74b6e8b1d6f73cf1baa0%40%3Cdev.subversion.apache.org%3E
Perhaps this should have been fixed in another way than the approach
of r1875230? Or maybe it just needs another small tweak? I haven't
investigated further (I have pretty much used up my 'svn cycles' these
last couple of days), but I wanted to highlight this nonetheless. If
anyone wants me to try something out on Windows, just say the word ...
Links to the revisions on viewvc:
https://svn.apache.org/r1874057
https://svn.apache.org/r1874093
https://svn.apache.org/r1875230
I'm leaving the diff from the last commit mail here below for quick
scanning, in case it helps anyone.
> Modified:
> subversion/trunk/subversion/libsvn_subr/cmdline.c
>
> Modified: subversion/trunk/subversion/libsvn_subr/cmdline.c
> URL:
> http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_subr/cmdline.c?rev=1875230&r1=1875229&r2=1875230&view=diff
> ==============================================================================
> --- subversion/trunk/subversion/libsvn_subr/cmdline.c (original)
> +++ subversion/trunk/subversion/libsvn_subr/cmdline.c Mon Mar 16 04:11:36 2020
> @@ -1305,36 +1305,92 @@ static const char *
> escape_path(apr_pool_t *pool, const char *orig_path)
> {
> apr_size_t len, esc_len;
> - const char *path;
> - char *p, *esc_path;
> + apr_status_t status;
>
> - path = apr_pescape_shell(pool, orig_path);
> + len = strlen(orig_path);
> + esc_len = 0;
>
> - len = esc_len = 0;
> + status = apr_escape_shell(NULL, orig_path, len, &esc_len);
>
> - /* Now that apr has done its escaping, we can check whether there's any
> - whitespace that also needs to be escaped. This must be done after the
> - fact, otherwise apr_pescape_shell() would escape the backslashes we're
> - inserting. */
> - for (p = (char *)path; *p; p++)
> + if (status == APR_NOTFOUND)
> {
> - len++;
> - if (*p == ' ' || *p == '\t')
> - esc_len++;
> + /* 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);
> }
> -
> - if (esc_len == 0)
> - return path;
> -
> - p = esc_path = apr_pcalloc(pool, len + esc_len + 1);
> - while (*path)
> + else
> {
> - if (*path == ' ' || *path == '\t')
> - *p++ = '\\';
> - *p++ = *path++;
> - }
> +#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 preceeding 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;
> + return esc_path;
> +#endif
> + }
> }
>
> svn_error_t *
>
>
--
Johan