{"thread":{"id":"16677","subject":"[JGIT PATCH 4/6 v2] Add QuotedString class to handle Git path style quoting rules","startedAt":"2008-12-10T23:59:52Z","lastAt":"2008-12-10T23:59:54Z","messageCount":3,"participants":["Shawn O. Pearce"],"isPatch":true,"patchVersion":2,"patchTotal":6},"messages":[{"id":"97544","messageId":"1228953594-12749-1-git-send-email-spearce@spearce.org","threadId":"16677","inReplyTo":null,"subject":"[JGIT PATCH 4/6 v2] Add QuotedString class to handle Git path style quoting rules","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-10T23:59:52Z","receivedAt":"2008-12-10T23:59:52Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Git patch files can contain file names which are quoted using the\nroughly the C language quoting rules.  In order to correctly create\nor parse these files we must implement a quoting style that matches\nthose specific rules.\n\nQuotedString itself is an abstract API so callers can be passed a\nquoting style based on the context of where their output will be\nused, and multiple styles could be supported.  This may be useful\nif jgit ever grows a \"git for-each-ref\" style of output where Perl,\nPython, Tcl and Bourne style quoting might be necessary.\n\nReferences through the singleton QuotedString.GIT_PATH should be\nable to bypass the virtual function table, as the specific type is\nmentioned in the field declaration and that type is final.  A good\nJIT should be able to remove the abstraction costs when the caller\nhas hardcoded the quoting style.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../jgit/util/QuotedStringGitPathStyleTest.java    |  144 +++++++++++\n .../src/org/spearce/jgit/util/QuotedString.java    |  268 ++++++++++++++++++++\n 2 files changed, 412 insertions(+), 0 deletions(-)\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/util/QuotedStringGitPathStyleTest.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/util/QuotedString.java\n\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/util/QuotedStringGitPathStyleTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/util/QuotedStringGitPathStyleTest.java\nnew file mode 100644\nindex 0000000..ed9eaac\n--- /dev/null\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/util/QuotedStringGitPathStyleTest.java\n@@ -0,0 +1,144 @@\n+/*\n+ * Copyright (C) 2008, Google Inc.\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.util;\n+\n+import static org.spearce.jgit.util.QuotedString.GIT_PATH;\n+import junit.framework.TestCase;\n+\n+import org.spearce.jgit.lib.Constants;\n+\n+public class QuotedStringGitPathStyleTest extends TestCase {\n+\tprivate static void assertQuote(final String in, final String exp) {\n+\t\tfinal String r = GIT_PATH.quote(in);\n+\t\tassertNotSame(in, r);\n+\t\tassertFalse(in.equals(r));\n+\t\tassertEquals('\"' + exp + '\"', r);\n+\t}\n+\n+\tprivate static void assertDequote(final String exp, final String in) {\n+\t\tfinal byte[] b = Constants.encode('\"' + in + '\"');\n+\t\tfinal String r = GIT_PATH.dequote(b, 0, b.length);\n+\t\tassertEquals(exp, r);\n+\t}\n+\n+\tpublic void testQuote_Empty() {\n+\t\tassertEquals(\"\\\"\\\"\", GIT_PATH.quote(\"\"));\n+\t}\n+\n+\tpublic void testDequote_Empty1() {\n+\t\tassertEquals(\"\", GIT_PATH.dequote(new byte[0], 0, 0));\n+\t}\n+\n+\tpublic void testDequote_Empty2() {\n+\t\tassertEquals(\"\", GIT_PATH.dequote(new byte[] { '\"', '\"' }, 0, 2));\n+\t}\n+\n+\tpublic void testDequote_SoleDq() {\n+\t\tassertEquals(\"\\\"\", GIT_PATH.dequote(new byte[] { '\"' }, 0, 1));\n+\t}\n+\n+\tpublic void testQuote_BareA() {\n+\t\tfinal String in = \"a\";\n+\t\tassertSame(in, GIT_PATH.quote(in));\n+\t}\n+\n+\tpublic void testDequote_BareA() {\n+\t\tfinal String in = \"a\";\n+\t\tfinal byte[] b = Constants.encode(in);\n+\t\tassertEquals(in, GIT_PATH.dequote(b, 0, b.length));\n+\t}\n+\n+\tpublic void testDequote_BareABCZ_OnlyBC() {\n+\t\tfinal String in = \"abcz\";\n+\t\tfinal byte[] b = Constants.encode(in);\n+\t\tfinal int p = in.indexOf('b');\n+\t\tassertEquals(\"bc\", GIT_PATH.dequote(b, p, p + 2));\n+\t}\n+\n+\tpublic void testDequote_LoneBackslash() {\n+\t\tassertDequote(\"\\\\\", \"\\\\\");\n+\t}\n+\n+\tpublic void testQuote_NamedEscapes() {\n+\t\tassertQuote(\"\\u0007\", \"\\\\a\");\n+\t\tassertQuote(\"\\b\", \"\\\\b\");\n+\t\tassertQuote(\"\\f\", \"\\\\f\");\n+\t\tassertQuote(\"\\n\", \"\\\\n\");\n+\t\tassertQuote(\"\\r\", \"\\\\r\");\n+\t\tassertQuote(\"\\t\", \"\\\\t\");\n+\t\tassertQuote(\"\\u000B\", \"\\\\v\");\n+\t\tassertQuote(\"\\\\\", \"\\\\\\\\\");\n+\t\tassertQuote(\"\\\"\", \"\\\\\\\"\");\n+\t}\n+\n+\tpublic void testDequote_NamedEscapes() {\n+\t\tassertDequote(\"\\u0007\", \"\\\\a\");\n+\t\tassertDequote(\"\\b\", \"\\\\b\");\n+\t\tassertDequote(\"\\f\", \"\\\\f\");\n+\t\tassertDequote(\"\\n\", \"\\\\n\");\n+\t\tassertDequote(\"\\r\", \"\\\\r\");\n+\t\tassertDequote(\"\\t\", \"\\\\t\");\n+\t\tassertDequote(\"\\u000B\", \"\\\\v\");\n+\t\tassertDequote(\"\\\\\", \"\\\\\\\\\");\n+\t\tassertDequote(\"\\\"\", \"\\\\\\\"\");\n+\t}\n+\n+\tpublic void testDequote_OctalAll() {\n+\t\tfor (int i = 0; i < 256; i++) {\n+\t\t\tString s = Integer.toOctalString(i);\n+\t\t\twhile (s.length() < 3) {\n+\t\t\t\ts = \"0\" + s;\n+\t\t\t}\n+\t\t\tassertDequote(\"\" + (char) i, \"\\\\\" + s);\n+\t\t}\n+\t}\n+\n+\tpublic void testQuote_OctalAll() {\n+\t\tassertQuote(\"\\1\", \"\\\\001\");\n+\t\tassertQuote(\"~\", \"\\\\176\");\n+\t\tassertQuote(\"\\u00ff\", \"\\\\303\\\\277\"); // \\u00ff in UTF-8\n+\t}\n+\n+\tpublic void testDequote_UnknownEscapeQ() {\n+\t\tassertDequote(\"\\\\q\", \"\\\\q\");\n+\t}\n+\n+\tpublic void testDequote_FooTabBar() {\n+\t\tassertDequote(\"foo\\tbar\", \"foo\\\\tbar\");\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/QuotedString.java b/org.spearce.jgit/src/org/spearce/jgit/util/QuotedString.java\nnew file mode 100644\nindex 0000000..279b713\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/QuotedString.java\n@@ -0,0 +1,268 @@\n+/*\n+ * Copyright (C) 2008, Google Inc.\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.util;\n+\n+import java.util.Arrays;\n+\n+import org.spearce.jgit.lib.Constants;\n+\n+/** Utility functions related to quoted string handling. */\n+public abstract class QuotedString {\n+\t/** Quoting style that obeys the rules Git applies to file names */\n+\tpublic static final GitPathStyle GIT_PATH = new GitPathStyle();\n+\n+\t/**\n+\t * Quote an input string by the quoting rules.\n+\t * <p>\n+\t * If the input string does not require any quoting, the same String\n+\t * reference is returned to the caller.\n+\t * <p>\n+\t * Otherwise a quoted string is returned, including the opening and closing\n+\t * quotation marks at the start and end of the string. If the style does not\n+\t * permit raw Unicode characters then the string will first be encoded in\n+\t * UTF-8, with unprintable sequences possibly escaped by the rules.\n+\t * \n+\t * @param in\n+\t *            any non-null Unicode string.\n+\t * @return a quoted string. See above for details.\n+\t */\n+\tpublic abstract String quote(String in);\n+\n+\t/**\n+\t * Clean a previously quoted input, decoding the result via UTF-8.\n+\t * <p>\n+\t * This method must match quote such that:\n+\t * \n+\t * <pre>\n+\t * a.equals(dequote(quote(a)));\n+\t * </pre>\n+\t * \n+\t * is true for any <code>a</code>.\n+\t * \n+\t * @param in\n+\t *            a Unicode string to remove quoting from.\n+\t * @return the cleaned string.\n+\t * @see #dequote(byte[], int, int)\n+\t */\n+\tpublic String dequote(final String in) {\n+\t\tfinal byte[] b = Constants.encode(in);\n+\t\treturn dequote(b, 0, b.length);\n+\t}\n+\n+\t/**\n+\t * Decode a previously quoted input, scanning a UTF-8 encoded buffer.\n+\t * <p>\n+\t * This method must match quote such that:\n+\t * \n+\t * <pre>\n+\t * a.equals(dequote(Constants.encode(quote(a))));\n+\t * </pre>\n+\t * \n+\t * is true for any <code>a</code>.\n+\t * <p>\n+\t * This method removes any opening/closing quotation marks added by\n+\t * {@link #quote(String)}.\n+\t * \n+\t * @param in\n+\t *            the input buffer to parse.\n+\t * @param offset\n+\t *            first position within <code>in</code> to scan.\n+\t * @param end\n+\t *            one position past in <code>in</code> to scan.\n+\t * @return the cleaned string.\n+\t */\n+\tpublic abstract String dequote(byte[] in, int offset, int end);\n+\n+\t/** Quoting style that obeys the rules Git applies to file names */\n+\tpublic static final class GitPathStyle extends QuotedString {\n+\t\tprivate static final byte[] quote;\n+\t\tstatic {\n+\t\t\tquote = new byte[128];\n+\t\t\tArrays.fill(quote, (byte) -1);\n+\n+\t\t\tfor (int i = '0'; i <= '9'; i++)\n+\t\t\t\tquote[i] = 0;\n+\t\t\tfor (int i = 'a'; i <= 'z'; i++)\n+\t\t\t\tquote[i] = 0;\n+\t\t\tfor (int i = 'A'; i <= 'Z'; i++)\n+\t\t\t\tquote[i] = 0;\n+\t\t\tquote[' '] = 0;\n+\t\t\tquote['+'] = 0;\n+\t\t\tquote[','] = 0;\n+\t\t\tquote['-'] = 0;\n+\t\t\tquote['.'] = 0;\n+\t\t\tquote['/'] = 0;\n+\t\t\tquote['='] = 0;\n+\t\t\tquote['_'] = 0;\n+\t\t\tquote['^'] = 0;\n+\n+\t\t\tquote['\\u0007'] = 'a';\n+\t\t\tquote['\\b'] = 'b';\n+\t\t\tquote['\\f'] = 'f';\n+\t\t\tquote['\\n'] = 'n';\n+\t\t\tquote['\\r'] = 'r';\n+\t\t\tquote['\\t'] = 't';\n+\t\t\tquote['\\u000B'] = 'v';\n+\t\t\tquote['\\\\'] = '\\\\';\n+\t\t\tquote['\"'] = '\"';\n+\t\t}\n+\n+\t\t@Override\n+\t\tpublic String quote(final String instr) {\n+\t\t\tif (instr.length() == 0)\n+\t\t\t\treturn \"\\\"\\\"\";\n+\t\t\tboolean reuse = true;\n+\t\t\tfinal byte[] in = Constants.encode(instr);\n+\t\t\tfinal StringBuilder r = new StringBuilder(2 + in.length);\n+\t\t\tr.append('\"');\n+\t\t\tfor (int i = 0; i < in.length; i++) {\n+\t\t\t\tfinal int c = in[i] & 0xff;\n+\t\t\t\tif (c < quote.length) {\n+\t\t\t\t\tfinal byte style = quote[c];\n+\t\t\t\t\tif (style == 0) {\n+\t\t\t\t\t\tr.append((char) c);\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\t}\n+\t\t\t\t\tif (style > 0) {\n+\t\t\t\t\t\treuse = false;\n+\t\t\t\t\t\tr.append('\\\\');\n+\t\t\t\t\t\tr.append((char) style);\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\n+\t\t\t\treuse = false;\n+\t\t\t\tr.append('\\\\');\n+\t\t\t\tr.append((char) (((c >> 6) & 03) + '0'));\n+\t\t\t\tr.append((char) (((c >> 3) & 07) + '0'));\n+\t\t\t\tr.append((char) (((c >> 0) & 07) + '0'));\n+\t\t\t}\n+\t\t\tif (reuse)\n+\t\t\t\treturn instr;\n+\t\t\tr.append('\"');\n+\t\t\treturn r.toString();\n+\t\t}\n+\n+\t\t@Override\n+\t\tpublic String dequote(final byte[] in, final int inPtr, final int inEnd) {\n+\t\t\tif (2 <= inEnd - inPtr && in[inPtr] == '\"' && in[inEnd - 1] == '\"')\n+\t\t\t\treturn dq(in, inPtr + 1, inEnd - 1);\n+\t\t\treturn RawParseUtils.decode(Constants.CHARSET, in, inPtr, inEnd);\n+\t\t}\n+\n+\t\tprivate static String dq(final byte[] in, int inPtr, final int inEnd) {\n+\t\t\tfinal byte[] r = new byte[inEnd - inPtr];\n+\t\t\tint rPtr = 0;\n+\t\t\twhile (inPtr < inEnd) {\n+\t\t\t\tfinal byte b = in[inPtr++];\n+\t\t\t\tif (b != '\\\\') {\n+\t\t\t\t\tr[rPtr++] = b;\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tif (inPtr == inEnd) {\n+\t\t\t\t\t// Lone trailing backslash. Treat it as a literal.\n+\t\t\t\t\t//\n+\t\t\t\t\tr[rPtr++] = '\\\\';\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\n+\t\t\t\tswitch (in[inPtr++]) {\n+\t\t\t\tcase 'a':\n+\t\t\t\t\tr[rPtr++] = 0x07 /* \\a = BEL */;\n+\t\t\t\t\tcontinue;\n+\t\t\t\tcase 'b':\n+\t\t\t\t\tr[rPtr++] = '\\b';\n+\t\t\t\t\tcontinue;\n+\t\t\t\tcase 'f':\n+\t\t\t\t\tr[rPtr++] = '\\f';\n+\t\t\t\t\tcontinue;\n+\t\t\t\tcase 'n':\n+\t\t\t\t\tr[rPtr++] = '\\n';\n+\t\t\t\t\tcontinue;\n+\t\t\t\tcase 'r':\n+\t\t\t\t\tr[rPtr++] = '\\r';\n+\t\t\t\t\tcontinue;\n+\t\t\t\tcase 't':\n+\t\t\t\t\tr[rPtr++] = '\\t';\n+\t\t\t\t\tcontinue;\n+\t\t\t\tcase 'v':\n+\t\t\t\t\tr[rPtr++] = 0x0B/* \\v = VT */;\n+\t\t\t\t\tcontinue;\n+\n+\t\t\t\tcase '\\\\':\n+\t\t\t\tcase '\"':\n+\t\t\t\t\tr[rPtr++] = in[inPtr - 1];\n+\t\t\t\t\tcontinue;\n+\n+\t\t\t\tcase '0':\n+\t\t\t\tcase '1':\n+\t\t\t\tcase '2':\n+\t\t\t\tcase '3': {\n+\t\t\t\t\tint cp = in[inPtr - 1] - '0';\n+\t\t\t\t\twhile (inPtr < inEnd) {\n+\t\t\t\t\t\tfinal byte c = in[inPtr];\n+\t\t\t\t\t\tif ('0' <= c && c <= '7') {\n+\t\t\t\t\t\t\tcp <<= 3;\n+\t\t\t\t\t\t\tcp |= c - '0';\n+\t\t\t\t\t\t\tinPtr++;\n+\t\t\t\t\t\t} else {\n+\t\t\t\t\t\t\tbreak;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t\tr[rPtr++] = (byte) cp;\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tdefault:\n+\t\t\t\t\t// Any other code is taken literally.\n+\t\t\t\t\t//\n+\t\t\t\t\tr[rPtr++] = '\\\\';\n+\t\t\t\t\tr[rPtr++] = in[inPtr - 1];\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\treturn RawParseUtils.decode(Constants.CHARSET, r, 0, rPtr);\n+\t\t}\n+\n+\t\tprivate GitPathStyle() {\n+\t\t\t// Singleton\n+\t\t}\n+\t}\n+}\n-- \n1.6.1.rc2.299.gead4c\n"},{"id":"97545","messageId":"1228953594-12749-2-git-send-email-spearce@spearce.org","threadId":"16677","inReplyTo":"1228953594-12749-1-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 5/6 v2] Add Bourne style quoting for TransportGitSsh","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-10T23:59:53Z","receivedAt":"2008-12-10T23:59:53Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Now that we have a nice QuotedString abstraction we can port our\nstring quoting logic from being private within the SSH transport\ncode to being available in the rest of the library.\n\nCurrently we only support the super-restrictive quoting style used\nfor the repository path name argument over SSH.  We don't support the\n\"minimal\" style used to invoke the command name, nor do we support\nthe ~user/ style format, which cannot be quoted.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../jgit/util/QuotedStringBourneStyleTest.java     |  111 ++++++++++++++++++++\n .../spearce/jgit/transport/TransportGitSsh.java    |   13 +--\n .../src/org/spearce/jgit/util/QuotedString.java    |   66 ++++++++++++\n 3 files changed, 179 insertions(+), 11 deletions(-)\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/util/QuotedStringBourneStyleTest.java\n\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/util/QuotedStringBourneStyleTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/util/QuotedStringBourneStyleTest.java\nnew file mode 100644\nindex 0000000..86d46fe\n--- /dev/null\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/util/QuotedStringBourneStyleTest.java\n@@ -0,0 +1,111 @@\n+/*\n+ * Copyright (C) 2008, Google Inc.\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.util;\n+\n+import static org.spearce.jgit.util.QuotedString.BOURNE;\n+import junit.framework.TestCase;\n+\n+import org.spearce.jgit.lib.Constants;\n+\n+public class QuotedStringBourneStyleTest extends TestCase {\n+\tprivate static void assertQuote(final String in, final String exp) {\n+\t\tfinal String r = BOURNE.quote(in);\n+\t\tassertNotSame(in, r);\n+\t\tassertFalse(in.equals(r));\n+\t\tassertEquals('\\'' + exp + '\\'', r);\n+\t}\n+\n+\tprivate static void assertDequote(final String exp, final String in) {\n+\t\tfinal byte[] b = Constants.encode('\\'' + in + '\\'');\n+\t\tfinal String r = BOURNE.dequote(b, 0, b.length);\n+\t\tassertEquals(exp, r);\n+\t}\n+\n+\tpublic void testQuote_Empty() {\n+\t\tassertEquals(\"''\", BOURNE.quote(\"\"));\n+\t}\n+\n+\tpublic void testDequote_Empty1() {\n+\t\tassertEquals(\"\", BOURNE.dequote(new byte[0], 0, 0));\n+\t}\n+\n+\tpublic void testDequote_Empty2() {\n+\t\tassertEquals(\"\", BOURNE.dequote(new byte[] { '\\'', '\\'' }, 0, 2));\n+\t}\n+\n+\tpublic void testDequote_SoleSq() {\n+\t\tassertEquals(\"\", BOURNE.dequote(new byte[] { '\\'' }, 0, 1));\n+\t}\n+\n+\tpublic void testQuote_BareA() {\n+\t\tassertQuote(\"a\", \"a\");\n+\t}\n+\n+\tpublic void testDequote_BareA() {\n+\t\tfinal String in = \"a\";\n+\t\tfinal byte[] b = Constants.encode(in);\n+\t\tassertEquals(in, BOURNE.dequote(b, 0, b.length));\n+\t}\n+\n+\tpublic void testDequote_BareABCZ_OnlyBC() {\n+\t\tfinal String in = \"abcz\";\n+\t\tfinal byte[] b = Constants.encode(in);\n+\t\tfinal int p = in.indexOf('b');\n+\t\tassertEquals(\"bc\", BOURNE.dequote(b, p, p + 2));\n+\t}\n+\n+\tpublic void testDequote_LoneBackslash() {\n+\t\tassertDequote(\"\\\\\", \"\\\\\");\n+\t}\n+\n+\tpublic void testQuote_NamedEscapes() {\n+\t\tassertQuote(\"'\", \"'\\\\''\");\n+\t\tassertQuote(\"!\", \"'\\\\!'\");\n+\n+\t\tassertQuote(\"a'b\", \"a'\\\\''b\");\n+\t\tassertQuote(\"a!b\", \"a'\\\\!'b\");\n+\t}\n+\n+\tpublic void testDequote_NamedEscapes() {\n+\t\tassertDequote(\"'\", \"'\\\\''\");\n+\t\tassertDequote(\"!\", \"'\\\\!'\");\n+\n+\t\tassertDequote(\"a'b\", \"a'\\\\''b\");\n+\t\tassertDequote(\"a!b\", \"a'\\\\!'b\");\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitSsh.java b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitSsh.java\nindex 3f2cd37..e3f5ae8 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitSsh.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitSsh.java\n@@ -47,6 +47,7 @@\n import org.spearce.jgit.errors.NoRemoteRepositoryException;\n import org.spearce.jgit.errors.TransportException;\n import org.spearce.jgit.lib.Repository;\n+import org.spearce.jgit.util.QuotedString;\n \n import com.jcraft.jsch.ChannelExec;\n import com.jcraft.jsch.JSchException;\n@@ -154,17 +155,7 @@ private static void sq(final StringBuilder cmd, final String val) {\n \t\t\t\treturn;\n \t\t}\n \n-\t\tcmd.append('\\'');\n-\t\tfor (; i < val.length(); i++) {\n-\t\t\tfinal char c = val.charAt(i);\n-\t\t\tif (c == '\\'')\n-\t\t\t\tcmd.append(\"'\\\\''\");\n-\t\t\telse if (c == '!')\n-\t\t\t\tcmd.append(\"'\\\\!'\");\n-\t\t\telse\n-\t\t\t\tcmd.append(c);\n-\t\t}\n-\t\tcmd.append('\\'');\n+\t\tcmd.append(QuotedString.BOURNE.quote(val.substring(i)));\n \t}\n \n \tprivate void initSession() throws TransportException {\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/QuotedString.java b/org.spearce.jgit/src/org/spearce/jgit/util/QuotedString.java\nindex 279b713..82ce716 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/util/QuotedString.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/QuotedString.java\n@@ -47,6 +47,15 @@\n \tpublic static final GitPathStyle GIT_PATH = new GitPathStyle();\n \n \t/**\n+\t * Quoting style used by the Bourne shell.\n+\t * <p>\n+\t * Quotes are unconditionally inserted during {@link #quote(String)}. This\n+\t * protects shell meta-characters like <code>$</code> or <code>~</code> from\n+\t * being recognized as special.\n+\t */\n+\tpublic static final BourneStyle BOURNE = new BourneStyle();\n+\n+\t/**\n \t * Quote an input string by the quoting rules.\n \t * <p>\n \t * If the input string does not require any quoting, the same String\n@@ -108,6 +117,63 @@ public String dequote(final String in) {\n \t */\n \tpublic abstract String dequote(byte[] in, int offset, int end);\n \n+\t/**\n+\t * Quoting style used by the Bourne shell.\n+\t * <p>\n+\t * Quotes are unconditionally inserted during {@link #quote(String)}. This\n+\t * protects shell meta-characters like <code>$</code> or <code>~</code> from\n+\t * being recognized as special.\n+\t */\n+\tpublic static class BourneStyle extends QuotedString {\n+\t\t@Override\n+\t\tpublic String quote(final String in) {\n+\t\t\tfinal StringBuilder r = new StringBuilder();\n+\t\t\tr.append('\\'');\n+\t\t\tint start = 0, i = 0;\n+\t\t\tfor (; i < in.length(); i++) {\n+\t\t\t\tswitch (in.charAt(i)) {\n+\t\t\t\tcase '\\'':\n+\t\t\t\tcase '!':\n+\t\t\t\t\tr.append(in, start, i);\n+\t\t\t\t\tr.append('\\'');\n+\t\t\t\t\tr.append('\\\\');\n+\t\t\t\t\tr.append(in.charAt(i));\n+\t\t\t\t\tr.append('\\'');\n+\t\t\t\t\tstart = i + 1;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tr.append(in, start, i);\n+\t\t\tr.append('\\'');\n+\t\t\treturn r.toString();\n+\t\t}\n+\n+\t\t@Override\n+\t\tpublic String dequote(final byte[] in, int ip, final int ie) {\n+\t\t\tboolean inquote = false;\n+\t\t\tfinal byte[] r = new byte[ie - ip];\n+\t\t\tint rPtr = 0;\n+\t\t\twhile (ip < ie) {\n+\t\t\t\tfinal byte b = in[ip++];\n+\t\t\t\tswitch (b) {\n+\t\t\t\tcase '\\'':\n+\t\t\t\t\tinquote = !inquote;\n+\t\t\t\t\tcontinue;\n+\t\t\t\tcase '\\\\':\n+\t\t\t\t\tif (inquote || ip == ie)\n+\t\t\t\t\t\tr[rPtr++] = b; // literal within a quote\n+\t\t\t\t\telse\n+\t\t\t\t\t\tr[rPtr++] = in[ip++];\n+\t\t\t\t\tcontinue;\n+\t\t\t\tdefault:\n+\t\t\t\t\tr[rPtr++] = b;\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\treturn RawParseUtils.decode(Constants.CHARSET, r, 0, rPtr);\n+\t\t}\n+\t}\n+\n \t/** Quoting style that obeys the rules Git applies to file names */\n \tpublic static final class GitPathStyle extends QuotedString {\n \t\tprivate static final byte[] quote;\n-- \n1.6.1.rc2.299.gead4c\n"},{"id":"97546","messageId":"1228953594-12749-3-git-send-email-spearce@spearce.org","threadId":"16677","inReplyTo":"1228953594-12749-2-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 6/6 v2] Add ~user friendly Bourne style quoting for TransportGitSsh","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-10T23:59:54Z","receivedAt":"2008-12-10T23:59:54Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"This mostly completes the migration of our quoting rules from the\nSSH transport to our QuotedString pattern.  User names may be left\nalone for the shell to expand when the string is evaluated, if the\ncaller wants that sort of behavior in a particular context.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../util/QuotedStringBourneUserPathStyleTest.java  |  130 ++++++++++++++++++++\n .../spearce/jgit/transport/TransportGitSsh.java    |   27 +----\n .../src/org/spearce/jgit/util/QuotedString.java    |   28 ++++\n 3 files changed, 160 insertions(+), 25 deletions(-)\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/util/QuotedStringBourneUserPathStyleTest.java\n\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/util/QuotedStringBourneUserPathStyleTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/util/QuotedStringBourneUserPathStyleTest.java\nnew file mode 100644\nindex 0000000..36fb52a\n--- /dev/null\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/util/QuotedStringBourneUserPathStyleTest.java\n@@ -0,0 +1,130 @@\n+/*\n+ * Copyright (C) 2008, Google Inc.\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.util;\n+\n+import static org.spearce.jgit.util.QuotedString.BOURNE_USER_PATH;\n+import junit.framework.TestCase;\n+\n+import org.spearce.jgit.lib.Constants;\n+\n+public class QuotedStringBourneUserPathStyleTest extends TestCase {\n+\tprivate static void assertQuote(final String in, final String exp) {\n+\t\tfinal String r = BOURNE_USER_PATH.quote(in);\n+\t\tassertNotSame(in, r);\n+\t\tassertFalse(in.equals(r));\n+\t\tassertEquals('\\'' + exp + '\\'', r);\n+\t}\n+\n+\tprivate static void assertDequote(final String exp, final String in) {\n+\t\tfinal byte[] b = Constants.encode('\\'' + in + '\\'');\n+\t\tfinal String r = BOURNE_USER_PATH.dequote(b, 0, b.length);\n+\t\tassertEquals(exp, r);\n+\t}\n+\n+\tpublic void testQuote_Empty() {\n+\t\tassertEquals(\"''\", BOURNE_USER_PATH.quote(\"\"));\n+\t}\n+\n+\tpublic void testDequote_Empty1() {\n+\t\tassertEquals(\"\", BOURNE_USER_PATH.dequote(new byte[0], 0, 0));\n+\t}\n+\n+\tpublic void testDequote_Empty2() {\n+\t\tassertEquals(\"\", BOURNE_USER_PATH.dequote(new byte[] { '\\'', '\\'' }, 0,\n+\t\t\t\t2));\n+\t}\n+\n+\tpublic void testDequote_SoleSq() {\n+\t\tassertEquals(\"\", BOURNE_USER_PATH.dequote(new byte[] { '\\'' }, 0, 1));\n+\t}\n+\n+\tpublic void testQuote_BareA() {\n+\t\tassertQuote(\"a\", \"a\");\n+\t}\n+\n+\tpublic void testDequote_BareA() {\n+\t\tfinal String in = \"a\";\n+\t\tfinal byte[] b = Constants.encode(in);\n+\t\tassertEquals(in, BOURNE_USER_PATH.dequote(b, 0, b.length));\n+\t}\n+\n+\tpublic void testDequote_BareABCZ_OnlyBC() {\n+\t\tfinal String in = \"abcz\";\n+\t\tfinal byte[] b = Constants.encode(in);\n+\t\tfinal int p = in.indexOf('b');\n+\t\tassertEquals(\"bc\", BOURNE_USER_PATH.dequote(b, p, p + 2));\n+\t}\n+\n+\tpublic void testDequote_LoneBackslash() {\n+\t\tassertDequote(\"\\\\\", \"\\\\\");\n+\t}\n+\n+\tpublic void testQuote_NamedEscapes() {\n+\t\tassertQuote(\"'\", \"'\\\\''\");\n+\t\tassertQuote(\"!\", \"'\\\\!'\");\n+\n+\t\tassertQuote(\"a'b\", \"a'\\\\''b\");\n+\t\tassertQuote(\"a!b\", \"a'\\\\!'b\");\n+\t}\n+\n+\tpublic void testDequote_NamedEscapes() {\n+\t\tassertDequote(\"'\", \"'\\\\''\");\n+\t\tassertDequote(\"!\", \"'\\\\!'\");\n+\n+\t\tassertDequote(\"a'b\", \"a'\\\\''b\");\n+\t\tassertDequote(\"a!b\", \"a'\\\\!'b\");\n+\t}\n+\n+\tpublic void testQuote_User() {\n+\t\tassertEquals(\"~foo/\", BOURNE_USER_PATH.quote(\"~foo\"));\n+\t\tassertEquals(\"~foo/\", BOURNE_USER_PATH.quote(\"~foo/\"));\n+\t\tassertEquals(\"~/\", BOURNE_USER_PATH.quote(\"~/\"));\n+\n+\t\tassertEquals(\"~foo/'a'\", BOURNE_USER_PATH.quote(\"~foo/a\"));\n+\t\tassertEquals(\"~/'a'\", BOURNE_USER_PATH.quote(\"~/a\"));\n+\t}\n+\n+\tpublic void testDequote_User() {\n+\t\tassertEquals(\"~foo\", BOURNE_USER_PATH.dequote(\"~foo\"));\n+\t\tassertEquals(\"~foo/\", BOURNE_USER_PATH.dequote(\"~foo/\"));\n+\t\tassertEquals(\"~/\", BOURNE_USER_PATH.dequote(\"~/\"));\n+\n+\t\tassertEquals(\"~foo/a\", BOURNE_USER_PATH.dequote(\"~foo/'a'\"));\n+\t\tassertEquals(\"~/a\", BOURNE_USER_PATH.dequote(\"~/'a'\"));\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitSsh.java b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitSsh.java\nindex e3f5ae8..d4bf466 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitSsh.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitSsh.java\n@@ -131,31 +131,8 @@ private static void sqAlways(final StringBuilder cmd, final String val) {\n \t}\n \n \tprivate static void sq(final StringBuilder cmd, final String val) {\n-\t\tint i = 0;\n-\n-\t\tif (val.length() == 0)\n-\t\t\treturn;\n-\t\tif (val.matches(\"^~[A-Za-z0-9_-]+$\")) {\n-\t\t\t// If the string is just \"~user\" we can assume they\n-\t\t\t// mean \"~user/\" and evaluate it within the shell.\n-\t\t\t//\n-\t\t\tcmd.append(val);\n-\t\t\tcmd.append('/');\n-\t\t\treturn;\n-\t\t}\n-\n-\t\tif (val.matches(\"^~[A-Za-z0-9_-]*/.*$\")) {\n-\t\t\t// If the string is of \"~/path\" or \"~user/path\"\n-\t\t\t// we must not escape ~/ or ~user/ from the shell\n-\t\t\t// as we need that portion to be evaluated.\n-\t\t\t//\n-\t\t\ti = val.indexOf('/') + 1;\n-\t\t\tcmd.append(val.substring(0, i));\n-\t\t\tif (i == val.length())\n-\t\t\t\treturn;\n-\t\t}\n-\n-\t\tcmd.append(QuotedString.BOURNE.quote(val.substring(i)));\n+\t\tif (val.length() > 0)\n+\t\t\tcmd.append(QuotedString.BOURNE.quote(val));\n \t}\n \n \tprivate void initSession() throws TransportException {\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/QuotedString.java b/org.spearce.jgit/src/org/spearce/jgit/util/QuotedString.java\nindex 82ce716..60c4195 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/util/QuotedString.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/QuotedString.java\n@@ -55,6 +55,9 @@\n \t */\n \tpublic static final BourneStyle BOURNE = new BourneStyle();\n \n+\t/** Bourne style, but permits <code>~user</code> at the start of the string. */\n+\tpublic static final BourneUserPathStyle BOURNE_USER_PATH = new BourneUserPathStyle();\n+\n \t/**\n \t * Quote an input string by the quoting rules.\n \t * <p>\n@@ -174,6 +177,31 @@ public String dequote(final byte[] in, int ip, final int ie) {\n \t\t}\n \t}\n \n+\t/** Bourne style, but permits <code>~user</code> at the start of the string. */\n+\tpublic static class BourneUserPathStyle extends BourneStyle {\n+\t\t@Override\n+\t\tpublic String quote(final String in) {\n+\t\t\tif (in.matches(\"^~[A-Za-z0-9_-]+$\")) {\n+\t\t\t\t// If the string is just \"~user\" we can assume they\n+\t\t\t\t// mean \"~user/\".\n+\t\t\t\t//\n+\t\t\t\treturn in + \"/\";\n+\t\t\t}\n+\n+\t\t\tif (in.matches(\"^~[A-Za-z0-9_-]*/.*$\")) {\n+\t\t\t\t// If the string is of \"~/path\" or \"~user/path\"\n+\t\t\t\t// we must not escape ~/ or ~user/ from the shell.\n+\t\t\t\t//\n+\t\t\t\tfinal int i = in.indexOf('/') + 1;\n+\t\t\t\tif (i == in.length())\n+\t\t\t\t\treturn in;\n+\t\t\t\treturn in.substring(0, i) + super.quote(in.substring(i));\n+\t\t\t}\n+\n+\t\t\treturn super.quote(in);\n+\t\t}\n+\t}\n+\n \t/** Quoting style that obeys the rules Git applies to file names */\n \tpublic static final class GitPathStyle extends QuotedString {\n \t\tprivate static final byte[] quote;\n-- \n1.6.1.rc2.299.gead4c\n"}]}