diff --git a/build/VStudio/testing/winfsp-tests.vcxproj b/build/VStudio/testing/winfsp-tests.vcxproj
index 6790d6b0..c6e5651b 100644
--- a/build/VStudio/testing/winfsp-tests.vcxproj
+++ b/build/VStudio/testing/winfsp-tests.vcxproj
@@ -191,6 +191,7 @@
+
diff --git a/build/VStudio/testing/winfsp-tests.vcxproj.filters b/build/VStudio/testing/winfsp-tests.vcxproj.filters
index 67ff3de6..b07e62e6 100644
--- a/build/VStudio/testing/winfsp-tests.vcxproj.filters
+++ b/build/VStudio/testing/winfsp-tests.vcxproj.filters
@@ -100,6 +100,9 @@
Source
+
+ Source
+
diff --git a/build/VStudio/tools/launcher.vcxproj b/build/VStudio/tools/launcher.vcxproj
index 7c0cf280..ae17af2e 100644
--- a/build/VStudio/tools/launcher.vcxproj
+++ b/build/VStudio/tools/launcher.vcxproj
@@ -186,6 +186,7 @@
+
diff --git a/build/VStudio/tools/launcher.vcxproj.filters b/build/VStudio/tools/launcher.vcxproj.filters
index d2ad1d56..1aed461c 100644
--- a/build/VStudio/tools/launcher.vcxproj.filters
+++ b/build/VStudio/tools/launcher.vcxproj.filters
@@ -16,6 +16,9 @@
Source
+
+ Source
+
diff --git a/src/launcher/launcher.c b/src/launcher/launcher.c
index 5a7bbed9..373424d3 100644
--- a/src/launcher/launcher.c
+++ b/src/launcher/launcher.c
@@ -503,24 +503,19 @@ static SVC_INSTANCE *SvcInstanceLookup(PWSTR ClassName, PWSTR InstanceName)
return 0;
}
-static ULONG SvcInstanceArgumentLength(PWSTR Arg)
+static inline ULONG SvcInstanceArgumentLength(PWSTR Arg, PWSTR Pattern)
{
- ULONG Length;
+ PWSTR PathTransform(PWSTR Dest, PWSTR Arg, PWSTR Pattern);
- Length = 2; /* for beginning and ending quotes */
- for (PWSTR P = Arg; *P; P++)
- if (L'"' != *P)
- Length++;
-
- return Length;
+ return 2 + (ULONG)(UINT_PTR)PathTransform(0, Arg, Pattern);
}
-static PWSTR SvcInstanceArgumentCopy(PWSTR Dest, PWSTR Arg)
+static inline PWSTR SvcInstanceArgumentCopy(PWSTR Dest, PWSTR Arg, PWSTR Pattern)
{
+ PWSTR PathTransform(PWSTR Dest, PWSTR Arg, PWSTR Pattern);
+
*Dest++ = L'"';
- for (PWSTR P = Arg; *P; P++)
- if (L'"' != *P)
- *Dest++ = *P;
+ Dest = PathTransform(Dest, Arg, Pattern);
*Dest++ = L'"';
return Dest;
@@ -532,6 +527,7 @@ static NTSTATUS SvcInstanceReplaceArguments(PWSTR String, ULONG Argc, PWSTR *Arg
PWSTR NewString = 0, P, Q;
PWSTR EmptyArg = L"";
ULONG Length;
+ PWSTR Pattern;
*PNewString = 0;
@@ -541,24 +537,36 @@ static NTSTATUS SvcInstanceReplaceArguments(PWSTR String, ULONG Argc, PWSTR *Arg
switch (*P)
{
case L'%':
+ Pattern = 0;
P++;
+ if (L'\\' == *P)
+ {
+ Pattern = ++P;
+ while (!(L'\0' == *P ||
+ (L'0' <= *P && *P <= '9') ||
+ (L'A' <= *P && *P <= 'Z')))
+ P++;
+ }
if (L'0' <= *P && *P <= '9')
{
if (Argc > (ULONG)(*P - L'0'))
- Length += SvcInstanceArgumentLength(Argv[*P - L'0']);
+ Length += SvcInstanceArgumentLength(Argv[*P - L'0'], Pattern);
else
- Length += SvcInstanceArgumentLength(EmptyArg);
+ Length += SvcInstanceArgumentLength(EmptyArg, 0);
}
else
if (L'U' == *P)
{
if (0 != SvcInstanceUserName())
- Length += SvcInstanceArgumentLength(SvcInstanceUserName());
+ Length += SvcInstanceArgumentLength(SvcInstanceUserName(), Pattern);
else
- Length += SvcInstanceArgumentLength(EmptyArg);
+ Length += SvcInstanceArgumentLength(EmptyArg, 0);
}
else
+ if (*P)
Length++;
+ else
+ P--;
break;
default:
Length++;
@@ -575,24 +583,36 @@ static NTSTATUS SvcInstanceReplaceArguments(PWSTR String, ULONG Argc, PWSTR *Arg
switch (*P)
{
case L'%':
+ Pattern = 0;
P++;
+ if (L'\\' == *P)
+ {
+ Pattern = ++P;
+ while (!(L'\0' == *P ||
+ (L'0' <= *P && *P <= '9') ||
+ (L'A' <= *P && *P <= 'Z')))
+ P++;
+ }
if (L'0' <= *P && *P <= '9')
{
if (Argc > (ULONG)(*P - L'0'))
- Q = SvcInstanceArgumentCopy(Q, Argv[*P - L'0']);
+ Q = SvcInstanceArgumentCopy(Q, Argv[*P - L'0'], Pattern);
else
- Q = SvcInstanceArgumentCopy(Q, EmptyArg);
+ Q = SvcInstanceArgumentCopy(Q, EmptyArg, 0);
}
else
if (L'U' == *P)
{
if (0 != SvcInstanceUserName())
- Q = SvcInstanceArgumentCopy(Q, SvcInstanceUserName());
+ Q = SvcInstanceArgumentCopy(Q, SvcInstanceUserName(), Pattern);
else
- Q = SvcInstanceArgumentCopy(Q, EmptyArg);
+ Q = SvcInstanceArgumentCopy(Q, EmptyArg, 0);
}
else
+ if (*P)
*Q++ = *P;
+ else
+ P--;
break;
default:
*Q++ = *P;
diff --git a/src/launcher/ptrans.c b/src/launcher/ptrans.c
new file mode 100644
index 00000000..15320d1f
--- /dev/null
+++ b/src/launcher/ptrans.c
@@ -0,0 +1,182 @@
+/**
+ * @file launcher/ptrans.c
+ *
+ * @copyright 2015-2019 Bill Zissimopoulos
+ */
+/*
+ * This file is part of WinFsp.
+ *
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License version 3 as published by the Free Software
+ * Foundation.
+ *
+ * Licensees holding a valid commercial license may use this software
+ * in accordance with the commercial license agreement provided in
+ * conjunction with the software. The terms and conditions of any such
+ * commercial license agreement shall govern, supersede, and render
+ * ineffective any application of the GPLv3 license to this software,
+ * notwithstanding of any reference thereto in the software or
+ * associated repository.
+ */
+
+/**
+ * Path Transformation Language
+ *
+ * Syntax:
+ * PERCENT BACKLASH replace-sep *(*sep component) [*sep UNDERSCORE] arg
+ *
+ * Arg:
+ * The command line argument that a rule is applied on. These are denoted
+ * by digits [0-9] or a capital letter (A-Z).
+ *
+ * Component:
+ * A path component denoted by a small letter (a-z). The letter a denotes
+ * the first path component, the letter b the second path component, etc.
+ *
+ * UNDERSCORE:
+ * The UNDERSCORE (_) denotes the "rest of the path". This is any path
+ * left after any path components explicitly mentioned by using small
+ * letters (a-z).
+ *
+ * Sep:
+ * A separator symbol that is used to separated components.
+ * E.g. slash (/), colon (:), etc.
+ *
+ * Replace-sep:
+ * A separator symbol that replaces the backslash (\) separator in the
+ * "rest of the path" (UNDERSCORE). E.g. slash (/), colon (:), etc.
+ *
+ *
+ * Examples:
+ * - %\/b:_1
+ * - Transforms \rclone\REMOTE\PATH\TO\FILES to REMOTE:PATH/TO/FILES
+ * - %\/b:/_1
+ * - Transforms \rclone\REMOTE\PATH\TO\FILES to REMOTE:/PATH/TO/FILES
+ * - %\/_1
+ * - Transforms \P1\P2\P3 to /P1/P2/P3
+ * - %\+_1
+ * - Transforms \P1\P2\P3 to +P1+P2+P3
+ * - %\\_1
+ * - Transforms \P1\P2\P3 to \\P1\\P2\\P3
+ * (Backslash is doubled up when used as a replacement separator!)
+ */
+
+#include
+#include
+
+static PWSTR PathCopy(PWSTR Dest, PWSTR Arg, PWSTR ArgEnd, BOOLEAN WriteDest, WCHAR Replacement)
+{
+ if (0 != Replacement)
+ {
+ for (PWSTR P = Arg, EndP = (0 != ArgEnd ? ArgEnd : (PWSTR)(UINT_PTR)~0); EndP > P && *P; P++)
+ if (L'\\' == *P)
+ {
+ if (L'\\' == Replacement)
+ {
+ if (WriteDest)
+ *Dest = Replacement;
+ Dest++;
+ }
+
+ if (WriteDest)
+ *Dest = Replacement;
+ Dest++;
+ }
+ else if (L'"' != *P)
+ {
+ if (WriteDest)
+ *Dest = *P;
+ Dest++;
+ }
+ }
+ else
+ {
+ for (PWSTR P = Arg, EndP = (0 != ArgEnd ? ArgEnd : (PWSTR)(UINT_PTR)~0); EndP > P && *P; P++)
+ if (L'"' != *P)
+ {
+ if (WriteDest)
+ *Dest = *P;
+ Dest++;
+ }
+ }
+
+ return Dest;
+}
+
+static inline BOOLEAN PatternEnd(WCHAR C)
+{
+ return L'\0' == C ||
+ (L'0' <= C && C <= '9') ||
+ (L'A' <= C && C <= 'Z');
+}
+
+PWSTR PathTransform(PWSTR Dest, PWSTR Arg, PWSTR Pattern)
+{
+ BOOLEAN WriteDest = 0 != Dest;
+ WCHAR Replacement;
+ PWSTR Components[26][2];
+ PWSTR Remainder = Arg;
+ ULONG RemainderIndex = 0;
+ PWSTR P;
+
+ if (0 == Pattern)
+ return PathCopy(Dest, Arg, 0, WriteDest, 0);
+
+ for (ULONG I = 0; 26 > I; I++)
+ Components[I][0] = 0;
+
+ Replacement = *Pattern++;
+ if (PatternEnd(Replacement))
+ return Dest;
+
+ while (!PatternEnd(*Pattern))
+ {
+ if (L'a' <= *Pattern && *Pattern <= 'z')
+ {
+ ULONG I = *Pattern - 'a', J;
+ if (0 == Components[I][0])
+ {
+ P = Remainder;
+ J = RemainderIndex;
+
+ while (L'\\' == *P)
+ P++;
+
+ for (;;)
+ {
+ Components[J][0] = P;
+ while (*P && L'\\' != *P)
+ P++;
+ Components[J][1] = P;
+
+ while (L'\\' == *P)
+ P++;
+
+ if (I == J)
+ {
+ Remainder = P;
+ RemainderIndex = I + 1;
+ break;
+ }
+
+ J++;
+ }
+ }
+
+ Dest = PathCopy(Dest, Components[I][0], Components[I][1], WriteDest, Replacement);
+ }
+ else
+ if (L'_' == *Pattern)
+ Dest = PathCopy(Dest, Remainder, 0, WriteDest, Replacement);
+ else
+ {
+ if (WriteDest)
+ *Dest = *Pattern;
+ Dest++;
+ }
+
+ Pattern++;
+ }
+
+ return Dest;
+}
diff --git a/tst/launcher-tests/echo.c b/tst/launcher-tests/echo.c
new file mode 100644
index 00000000..4780b85d
--- /dev/null
+++ b/tst/launcher-tests/echo.c
@@ -0,0 +1,35 @@
+/*
+ * Compile:
+ * - cl -I"%ProgramFiles(x86)%\WinFsp\inc" "%ProgramFiles(x86)%\WinFsp\lib\winfsp-x64.lib" echo.c
+ *
+ * Register:
+ * - echo.reg (fix Executable path first)
+ *
+ * Run:
+ * - launchctl-x64 start echo 1 \foo\bar\baz
+ *
+ * Expect:
+ * - "\foo\bar\baz" "bar:baz" "DOMAIN\\USERNAME"
+ */
+
+#include
+
+int wmain(int argc, wchar_t *argv[])
+{
+ WCHAR buf[512], *bufp;
+ int len;
+
+ bufp = buf;
+ for (int i = 0; argc > i; i++)
+ {
+ len = lstrlenW(argv[i]);
+ memcpy(bufp, argv[i], len * sizeof(WCHAR));
+ bufp += len;
+ *bufp++ = '\n';
+ }
+ *bufp = '\0';
+
+ FspServiceLog(EVENTLOG_INFORMATION_TYPE, L"%s", buf);
+
+ return 0;
+}
diff --git a/tst/launcher-tests/echo.reg b/tst/launcher-tests/echo.reg
new file mode 100644
index 00000000..0f5210aa
Binary files /dev/null and b/tst/launcher-tests/echo.reg differ
diff --git a/tst/secret/secret.c b/tst/launcher-tests/secret.c
similarity index 100%
rename from tst/secret/secret.c
rename to tst/launcher-tests/secret.c
diff --git a/tst/launcher-tests/secret.reg b/tst/launcher-tests/secret.reg
new file mode 100644
index 00000000..f55351d1
Binary files /dev/null and b/tst/launcher-tests/secret.reg differ
diff --git a/tst/secret/secret.reg b/tst/secret/secret.reg
deleted file mode 100644
index d508c0cc..00000000
Binary files a/tst/secret/secret.reg and /dev/null differ
diff --git a/tst/winfsp-tests/launcher-ptrans-test.c b/tst/winfsp-tests/launcher-ptrans-test.c
new file mode 100644
index 00000000..9ee39b9d
--- /dev/null
+++ b/tst/winfsp-tests/launcher-ptrans-test.c
@@ -0,0 +1,112 @@
+/**
+ * @file launcher-ptrans-test.c
+ *
+ * @copyright 2015-2019 Bill Zissimopoulos
+ */
+/*
+ * This file is part of WinFsp.
+ *
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License version 3 as published by the Free Software
+ * Foundation.
+ *
+ * Licensees holding a valid commercial license may use this software
+ * in accordance with the commercial license agreement provided in
+ * conjunction with the software. The terms and conditions of any such
+ * commercial license agreement shall govern, supersede, and render
+ * ineffective any application of the GPLv3 license to this software,
+ * notwithstanding of any reference thereto in the software or
+ * associated repository.
+ */
+
+#include
+#include
+
+#include "winfsp-tests.h"
+
+#include
+
+static void launcher_ptrans_test(void)
+{
+ PWSTR ipaths[] =
+ {
+ L"", 0,
+ L"\\foo\\bar", 0,
+ L"", L"",
+ L"\\foo\\bar", L"",
+ L"\\foo\\bar", L"/",
+ L"\\foo\\bar", L"/_",
+ L"\\foo\\bar", L"/_1",
+ L"\\foo\\bar", L"\\_",
+ L"\\foo\\bar", L"\\_A",
+ L"\\foo\\bar", L"/a",
+ L"\\foo\\bar", L"/b",
+ L"\\foo\\bar", L"/c",
+ L"\\foo\\bar", L"/d",
+ L"\\foo\\bar", L"/a:b",
+ L"\\foo\\bar", L"/a:b&c!d",
+ L"\\foo\\bar", L"/b:a",
+ L"\\foo\\bar", L"/d!c&b:a",
+ L"\\foo\\bar", L"/a:_",
+ L"\\foo\\bar", L"/b:_",
+ L"\\foo\\bar", L"/c:_",
+ L"\\foo\\bar\\baz", L"/b:_",
+ L"\\foo\\bar\\baz\\bag", L"/b:_",
+ L"\\foo\\bar\\baz", L"/b:/_",
+ L"\\foo\\bar\\baz\\bag", L"/b:/_",
+ L"\\foo\\bar\\baz\\bag", L"/a:_:b:_",
+ L"\\foo\\bar\\baz\\bag", L"/_:_",
+ };
+ PWSTR opaths[] =
+ {
+ L"",
+ L"\\foo\\bar",
+ L"",
+ L"",
+ L"",
+ L"/foo/bar",
+ L"/foo/bar",
+ L"\\\\foo\\\\bar",
+ L"\\\\foo\\\\bar",
+ L"foo",
+ L"bar",
+ L"",
+ L"",
+ L"foo:bar",
+ L"foo:bar&!",
+ L"bar:foo",
+ L"!&bar:foo",
+ L"foo:bar",
+ L"bar:",
+ L":",
+ L"bar:baz",
+ L"bar:baz/bag",
+ L"bar:/baz",
+ L"bar:/baz/bag",
+ L"foo:bar/baz/bag:bar:baz/bag",
+ L"/foo/bar/baz/bag:/foo/bar/baz/bag",
+ };
+
+ for (size_t i = 0; sizeof ipaths / (sizeof ipaths[0] * 2) > i; i++)
+ {
+ WCHAR Buf[1024];
+ ULONG Length;
+ PWSTR Dest;
+
+ Length = (ULONG)(UINT_PTR)PathTransform(0, ipaths[2 * i + 0], ipaths[2 * i + 1]);
+ ASSERT(Length == wcslen(opaths[i]) * sizeof(WCHAR));
+
+ Dest = PathTransform(Buf, ipaths[2 * i + 0], ipaths[2 * i + 1]);
+ *Dest = L'\0';
+ ASSERT(Dest == Buf + wcslen(opaths[i]));
+ ASSERT(0 == wcscmp(Buf, opaths[i]));
+ }
+}
+
+void launcher_ptrans_tests(void)
+{
+ if (OptExternal)
+ return;
+
+ TEST_OPT(launcher_ptrans_test);
+}
diff --git a/tst/winfsp-tests/winfsp-tests.c b/tst/winfsp-tests/winfsp-tests.c
index 57480565..06d9a61f 100644
--- a/tst/winfsp-tests/winfsp-tests.c
+++ b/tst/winfsp-tests/winfsp-tests.c
@@ -193,6 +193,7 @@ int main(int argc, char *argv[])
TESTSUITE(dirbuf_tests);
TESTSUITE(version_tests);
TESTSUITE(launch_tests);
+ TESTSUITE(launcher_ptrans_tests);
TESTSUITE(mount_tests);
TESTSUITE(timeout_tests);
TESTSUITE(memfs_tests);