Example #1
0
int apiGetFullPathName(LPCWSTR lpFileName, CEStr& rsPath)
{
	bool bFound = false;
	wchar_t *pszFilePart, *pszBuffer = NULL;
	wchar_t szFind[MAX_PATH+1];

	DWORD nLen = GetFullPathName(lpFileName, countof(szFind), szFind, &pszFilePart);
	if (nLen)
	{
		if (nLen < countof(szFind))
		{
			bFound = true;
			rsPath.Set(szFind);
		}
		else
		{
			// Too long path, allocate more space
			pszBuffer = (wchar_t*)malloc((++nLen)*sizeof(*pszBuffer));
			if (pszBuffer)
			{
				DWORD nLen2 = GetFullPathName(lpFileName, nLen, pszBuffer, &pszFilePart);
				if (nLen2 && (nLen2 < nLen))
				{
					bFound = true;
					rsPath.Set(pszBuffer);
				}
				free(pszBuffer);
			}
		}
	}

	return bFound ? rsPath.GetLen() : 0;
}
Example #2
0
bool IsNeedCmd(BOOL bRootCmd, LPCWSTR asCmdLine, CEStr &szExe,
			   LPCWSTR* rsArguments /*= NULL*/, BOOL* rpbNeedCutStartEndQuot /*= NULL*/,
			   BOOL* rpbRootIsCmdExe /*= NULL*/, BOOL* rpbAlwaysConfirmExit /*= NULL*/, BOOL* rpbAutoDisableConfirmExit /*= NULL*/)
{
	bool rbNeedCutStartEndQuot = false;
	bool rbRootIsCmdExe = true;
	bool rbAlwaysConfirmExit = false;
	bool rbAutoDisableConfirmExit = false;

	wchar_t *pwszEndSpace;

	if (rsArguments) *rsArguments = NULL;

	bool lbRc = false;
	BOOL lbFirstWasGot = FALSE;
	LPCWSTR pwszCopy;
	int nLastChar;
	#ifdef _DEBUG
	CmdArg szDbgFirst;
	#endif

	if (!asCmdLine || !*asCmdLine)
	{
		_ASSERTE(asCmdLine && *asCmdLine);
		goto wrap;
	}

	#ifdef _DEBUG
	// Это минимальные проверки, собственно к коду - не относятся
	bool bIsBatch = false;
	{
		NextArg(asCmdLine, szDbgFirst);
		LPCWSTR psz = PointToExt(szDbgFirst);
		if (lstrcmpi(psz, L".cmd")==0 || lstrcmpi(psz, L".bat")==0)
			bIsBatch = true;
	}
	#endif

	if (!szExe.GetBuffer(MAX_PATH))
	{
		_ASSERTE(FALSE && "Failed to allocate MAX_PATH");
		lbRc = true;
		goto wrap;
	}
	szExe.Empty();

	if (!asCmdLine || *asCmdLine == 0)
	{
		_ASSERTE(asCmdLine && *asCmdLine);
		lbRc = true;
		goto wrap;
	}


	pwszCopy = asCmdLine;
	// cmd /c ""c:\program files\arc\7z.exe" -?"   // да еще и внутри могут быть двойными...
	// cmd /c "dir c:\"
	nLastChar = lstrlenW(pwszCopy) - 1;

	if (pwszCopy[0] == L'"' && pwszCopy[nLastChar] == L'"')
	{
		//if (pwszCopy[1] == L'"' && pwszCopy[2])
		//{
		//	pwszCopy ++; // Отбросить первую кавычку в командах типа: ""c:\program files\arc\7z.exe" -?"

		//	if (rbNeedCutStartEndQuot) *rbNeedCutStartEndQuot = TRUE;
		//}
		//else
			// глючила на ""F:\VCProject\FarPlugin\#FAR180\far.exe  -new_console""
			//if (wcschr(pwszCopy+1, L'"') == (pwszCopy+nLastChar)) {
			//	LPCWSTR pwszTemp = pwszCopy;
			//	// Получим первую команду (исполняемый файл?)
			//	if ((iRc = NextArg(&pwszTemp, szArg)) != 0) {
			//		//Parsing command line failed
			//		lbRc = true; goto wrap;
			//	}
			//	pwszCopy ++; // Отбросить первую кавычку в командах типа: "c:\arc\7z.exe -?"
			//	lbFirstWasGot = TRUE;
			//	if (rbNeedCutStartEndQuot) *rbNeedCutStartEndQuot = TRUE;
			//} else
		{
			// Will be dequoted in 'NextArg' function. Examples
			// "C:\GCC\msys\bin\make.EXE -f "makefile" COMMON="../../../plugins/common""
			// ""F:\VCProject\FarPlugin\#FAR180\far.exe  -new_console""
			// ""cmd""
			// cmd /c ""c:\program files\arc\7z.exe" -?"   // да еще и внутри могут быть двойными...
			// cmd /c "dir c:\"

			// Получим первую команду (исполняемый файл?)
			CmdArg arg;
			if (!NextArg(pwszCopy, arg))
			{
				//Parsing command line failed
				#ifdef WARN_NEED_CMD
				_ASSERTE(FALSE);
				#endif
				lbRc = true; goto wrap;
			}
			szExe.Set(arg);

			if (lstrcmpiW(szExe, L"start") == 0)
			{
				// Команду start обрабатывает только процессор
				#ifdef WARN_NEED_CMD
				_ASSERTE(FALSE);
				#endif
				lbRc = true; goto wrap;
			}

			LPCWSTR pwszQ = pwszCopy + 1 + lstrlen(szExe);
			wchar_t* pszExpand = NULL;

			if (*pwszQ != L'"' && IsExecutable(szExe, &pszExpand))
			{
				pwszCopy ++; // отбрасываем
				lbFirstWasGot = TRUE;

				if (pszExpand)
				{
					szExe.Set(pszExpand);
					SafeFree(pszExpand);
					if (rsArguments)
						*rsArguments = pwszQ;
				}

				rbNeedCutStartEndQuot = true;
			}
		}
	}

	// Получим первую команду (исполняемый файл?)
	if (!lbFirstWasGot)
	{
		szExe.Empty();

		// `start` command must be processed by processor itself
		if ((lstrcmpni(pwszCopy, L"start", 5) == 0)
			&& (!pwszCopy[5] || isSpace(pwszCopy[5])))
		{
			#ifdef WARN_NEED_CMD
			_ASSERTE(FALSE);
			#endif
			lbRc = true; goto wrap;
		}

		// 17.10.2010 - support executable file path without parameters, but with spaces in its path
		// 22.11.2015 - or some weirdness, like `C:\Program Files\CB/cb_console_runner.exe "C:\sources\app.exe"`
		LPCWSTR pchEnd = wcschr(pwszCopy, L' ');
		if (!pchEnd)
			pchEnd = pwszCopy + lstrlenW(pwszCopy);

		CEStr szTemp;
		DWORD nTempSize;
		while (pchEnd)
		{
			szTemp.Set(pwszCopy, (pchEnd - pwszCopy));
			_ASSERTE(szTemp[(pchEnd - pwszCopy)] == 0);

			// Argument was quoted?
			if (!szTemp.IsEmpty())
			{
				INT_PTR len = szTemp.GetLen();
				if ((len > 2) && (szTemp[0] == L'"') && (szTemp[len-1] == L'"'))
				{
					memmove(szTemp.ms_Val, szTemp.ms_Val+1, (len-2)*sizeof(*szTemp.ms_Val));
					szTemp.ms_Val[len-2] = 0;
				}
			}

			// If this is a full path without environment variables
			if (!szTemp.IsEmpty()
				&& ((IsFilePath(szTemp, true) && !wcschr(szTemp, L'%'))
					// or file/dir may be found via env.var. substitution or searching in %PATH%
					|| FileExistsSearch((LPCWSTR)szTemp, szTemp))
				// Than check if it is a FILE (not a directory)
				&& FileExists(szTemp, &nTempSize) && nTempSize)
			{
				// OK, it an our executable?
				if (rsArguments)
					*rsArguments = pchEnd;
				szExe.Set(szTemp);
				break;
			}

			_ASSERTE(*pchEnd == 0 || *pchEnd == L' ');
			if (!*pchEnd)
				break;
			// Find next space after nonspace
			while (*(pchEnd) == L' ') pchEnd++;
			// If quoted string starts from here - it's supposed to be an argument
			if (*pchEnd == L'"')
			{
				// And we must not get here, because the executable must be already processed above
				// _ASSERTE(*pchEnd != L'"');
				break;
			}
			pchEnd = wcschr(pchEnd, L' ');
			if (!pchEnd)
				pchEnd = pwszCopy + lstrlenW(pwszCopy);
		}

		if (szExe.IsEmpty())
		{
			CmdArg arg;
			if (!(pwszCopy = NextArg(pwszCopy, arg)))
			{
				//Parsing command line failed
				#ifdef WARN_NEED_CMD
				_ASSERTE(FALSE);
				#endif
				lbRc = true; goto wrap;
			}
			szExe.Set(arg);

			_ASSERTE(lstrcmpiW(szExe, L"start") != 0);

			// Обработка переменных окружения и поиск в PATH
			if (FileExistsSearch((LPCWSTR)szExe, szExe))
			{
				if (rsArguments)
					*rsArguments = pwszCopy;
			}
		}
	}

	if (!*szExe)
	{
		_ASSERTE(szExe[0] != 0);
	}
	else
	{
		// Если юзеру нужен редирект - то он должен явно указать ком.процессор
		// Иначе нереально (или достаточно сложно) определить, является ли символ
		// редиректом, или это просто один из аргументов команды...

		// "Левые" символы в имени файла (а вот в "первом аргументе" все однозначно)
		if (wcspbrk(szExe, L"?*<>|"))
		{
			rbRootIsCmdExe = TRUE; // запуск через "процессор"
			lbRc = true; goto wrap; // добавить "cmd.exe"
		}

		// если "путь" не указан
		if (wcschr(szExe, L'\\') == NULL)
		{
			bool bHasExt = (wcschr(szExe, L'.') != NULL);
			// Проверим, может это команда процессора (типа "DIR")?
			if (!bHasExt)
			{
				bool bIsCommand = false;
				wchar_t* pszUppr = lstrdup(szExe);
				if (pszUppr)
				{
					// избежать линковки на user32.dll
					//CharUpperBuff(pszUppr, lstrlen(pszUppr));
					for (wchar_t* p = pszUppr; *p; p++)
					{
						if (*p >= L'a' && *p <= 'z')
							*p -= 0x20;
					}

					const wchar_t* pszFind = gsInternalCommands;
					while (*pszFind)
					{
						if (lstrcmp(pszUppr, pszFind) == 0)
						{
							bIsCommand = true;
							break;
						}
						pszFind += lstrlen(pszFind)+1;
					}
					free(pszUppr);
				}
				if (bIsCommand)
				{
					#ifdef WARN_NEED_CMD
					_ASSERTE(FALSE);
					#endif
					rbRootIsCmdExe = TRUE; // запуск через "процессор"
					lbRc = true; goto wrap; // добавить "cmd.exe"
				}
			}

			// Try to find executable in %PATH%
			{
				#ifndef CONEMU_MINIMAL
				MWow64Disable wow; wow.Disable(); // Disable Wow64 file redirector
				#endif
				apiSearchPath(NULL, szExe, bHasExt ? NULL : L".exe", szExe);
			}
		} // end: if (wcschr(szExe, L'\\') == NULL)
	}

	// Если szExe не содержит путь к файлу - запускаем через cmd
	// "start "" C:\Utils\Files\Hiew32\hiew32.exe C:\00\Far.exe"
	if (!IsFilePath(szExe))
	{
		#ifdef WARN_NEED_CMD
		_ASSERTE(FALSE);
		#endif
		rbRootIsCmdExe = TRUE; // запуск через "процессор"
		lbRc = true; goto wrap; // добавить "cmd.exe"
	}

	//pwszCopy = wcsrchr(szArg, L'\\'); if (!pwszCopy) pwszCopy = szArg; else pwszCopy ++;
	pwszCopy = PointToName(szExe);
	//2009-08-27
	pwszEndSpace = szExe.ms_Val + lstrlenW(szExe) - 1;

	while ((*pwszEndSpace == L' ') && (pwszEndSpace > szExe))
	{
		*(pwszEndSpace--) = 0;
	}

#ifndef __GNUC__
#pragma warning( push )
#pragma warning(disable : 6400)
#endif

	if (lstrcmpiW(pwszCopy, L"cmd")==0 || lstrcmpiW(pwszCopy, L"cmd.exe")==0
		|| lstrcmpiW(pwszCopy, L"tcc")==0 || lstrcmpiW(pwszCopy, L"tcc.exe")==0)
	{
		rbRootIsCmdExe = TRUE; // уже должен быть выставлен, но проверим
		rbAlwaysConfirmExit = TRUE; rbAutoDisableConfirmExit = FALSE;
		_ASSERTE(!bIsBatch);
		lbRc = false; goto wrap; // уже указан командный процессор, cmd.exe в начало добавлять не нужно
	}


	// Issue 1211: Decide not to do weird heuristic.
	//   If user REALLY needs redirection (root command, huh?)
	//   - he must call "cmd /c ..." directly
	// Если есть одна из команд перенаправления, или слияния - нужен CMD.EXE
	if (!bRootCmd)
	{
		if (wcschr(asCmdLine, L'&') ||
			wcschr(asCmdLine, L'>') ||
			wcschr(asCmdLine, L'<') ||
			wcschr(asCmdLine, L'|') ||
			wcschr(asCmdLine, L'^') // или экранирования
			)
		{
			#ifdef WARN_NEED_CMD
			_ASSERTE(FALSE);
			#endif
			lbRc = true; goto wrap;
		}
	}

	//if (lstrcmpiW(pwszCopy, L"far")==0 || lstrcmpiW(pwszCopy, L"far.exe")==0)
	if (IsFarExe(pwszCopy))
	{
		bool bFound = (wcschr(pwszCopy, L'.') != NULL);
		// Если указали при запуске просто "far" - это может быть батник, расположенный в %PATH%
		if (!bFound)
		{
			CEStr szSearch;
			if (apiSearchPath(NULL, pwszCopy, L".exe", szSearch))
			{
				if (lstrcmpi(PointToExt(szSearch), L".exe") == 0)
					bFound = true;
			}
		}

		if (bFound)
		{
			rbAutoDisableConfirmExit = TRUE;
			rbRootIsCmdExe = FALSE; // FAR!
			_ASSERTE(!bIsBatch);
			lbRc = false; goto wrap; // уже указан исполняемый файл, cmd.exe в начало добавлять не нужно
		}
	}

	if (IsExecutable(szExe))
	{
		rbRootIsCmdExe = FALSE; // Для других программ - буфер не включаем
		_ASSERTE(!bIsBatch);
		lbRc = false; goto wrap; // Запускается конкретная консольная программа. cmd.exe не требуется
	}

	//Можно еще Доделать поиски с: SearchPath, GetFullPathName, добавив расширения .exe & .com
	//хотя фар сам формирует полные пути к командам, так что можно не заморачиваться
	#ifdef WARN_NEED_CMD
	_ASSERTE(FALSE);
	#endif
	rbRootIsCmdExe = TRUE;
#ifndef __GNUC__
#pragma warning( pop )
#endif

	lbRc = true;
wrap:
	if (rpbNeedCutStartEndQuot)
		*rpbNeedCutStartEndQuot = rbNeedCutStartEndQuot;
	if (rpbRootIsCmdExe)
		*rpbRootIsCmdExe = rbRootIsCmdExe;
	if (rpbAlwaysConfirmExit)
		*rpbAlwaysConfirmExit = rbAlwaysConfirmExit;
	if (rpbAutoDisableConfirmExit)
		*rpbAutoDisableConfirmExit = rbAutoDisableConfirmExit;
	return lbRc;
}
Example #3
0
// Returns true on changes
// bDeQuote:  replace two "" with one "
// bDeEscape: process special symbols: ^e^[^r^n^t^b
bool DemangleArg(CEStr& rsDemangle, bool bDeQuote /*= true*/, bool bDeEscape /*= false*/)
{
	if (rsDemangle.IsEmpty() || !(bDeQuote || bDeEscape))
	{
		return false; // Nothing to do
	}

	LPCWSTR pszDemangles = (bDeQuote && bDeEscape) ? L"^\"" : bDeQuote ? L"\"" : L"^";
	LPCWSTR pchCap = wcspbrk(rsDemangle, pszDemangles);
	if (pchCap == NULL)
	{
		return false; // Nothing to replace
	}

	wchar_t* pszDst = rsDemangle.ms_Val;
	const wchar_t* pszSrc = rsDemangle.ms_Val;
	const wchar_t* pszEnd = rsDemangle.ms_Val + rsDemangle.GetLen();

	while (pszSrc < pszEnd)
	{
		if (bDeQuote && (*pszSrc == L'"'))
		{
			*(pszDst++) = *(pszSrc++);
			if (*pszSrc == L'"') // Expected, but may be missed by user?
				pszSrc++;
		}
		else if (bDeEscape && (*pszSrc == L'^'))
		{
			switch (*(++pszSrc))
			{
			case L'^': // Demangle cap
				*pszDst = L'^'; break;
			case 0:    // Leave single final cap
				*pszDst = L'^'; continue;
			case L'r': case L'R': // CR
				*pszDst = L'\r'; break;
			case L'n': case L'N': // LF
				*pszDst = L'\n'; break;
			case L't': case L'T': // TAB
				*pszDst = L'\t'; break;
			case L'a': case L'A': // BELL
				*pszDst = 7; break;
			case L'b': case L'B': // BACK
				*pszDst = L'\b'; break;
			case L'e': case L'E': case L'[': // ESC
				*pszDst = 27; break;
			default:
				// Unknown ctrl-sequence, bypass
				*(pszDst++) = *(pszSrc++);
				continue;
			}
			pszDst++; pszSrc++;
		}
		else
		{
			*(pszDst++) = *(pszSrc++);
		}
	}
	// Was processed? Zero terminate it.
	*pszDst = 0;

	return true;
}
Example #4
0
bool TermX::GetSubstitute(const MOUSE_EVENT_RECORD& m, TermMouseMode MouseMode, CEStr& lsSubst)
{
	_ASSERTE(lsSubst.IsEmpty());

	if (!MouseMode)
	{
		lsSubst.Clear();
		return false;
	}

	wchar_t szSubst[16] = L"";

	// Deprecated. Mouse events mimic xterm behavior now.
	#if 0
	// http://conemu.github.io/en/VimXterm.html#Vim-scrolling-using-mouse-Wheel
	// https://github.com/Maximus5/ConEmu/issues/1007
	if ((m.dwEventFlags & MOUSE_WHEELED)
		&& (MouseMode & tmm_VIM))
	{
		// If the high word of the dwButtonState member contains
		// a positive value, the wheel was rotated forward, away from the user.
		// Otherwise, the wheel was rotated backward, toward the user.
		short dir = (short)HIWORD(m.dwButtonState);

		// Ctrl/Alt/Shift
		DWORD mods = (m.dwControlKeyState & (LEFT_ALT_PRESSED|LEFT_CTRL_PRESSED|RIGHT_ALT_PRESSED|RIGHT_CTRL_PRESSED|SHIFT_PRESSED));

		if (mods == 0)
		{
			if (dir <= 0)
				wcscpy_c(szSubst, L"\033[62~"); // <MouseDown>
			else
				wcscpy_c(szSubst, L"\033[63~"); // <MouseUp>
		}
		else if (mods == SHIFT_PRESSED)
		{
			if (dir <= 0)
				wcscpy_c(szSubst, L"\033[64~"); // <S-MouseDown>
			else
				wcscpy_c(szSubst, L"\033[65~"); // <S-MouseUp>
		}
		else
			return false;
		lsSubst.Set(szSubst);
		return true;
	}
	#endif

	if ((m.dwEventFlags & MOUSE_WHEELED)
		&& (MouseMode & tmm_SCROLL))
	{
		KEY_EVENT_RECORD k = {TRUE, 1};
		short dir = (short)HIWORD(m.dwButtonState);
		DWORD mods = (m.dwControlKeyState & (LEFT_ALT_PRESSED|LEFT_CTRL_PRESSED|RIGHT_ALT_PRESSED|RIGHT_CTRL_PRESSED|SHIFT_PRESSED));
		UINT nCount = 1;
		if (mods == 0)
		{
			k.wVirtualKeyCode = (dir > 0) ? VK_UP : VK_DOWN;
			nCount = gpConEmu->mouse.GetWheelScrollLines();
		}
		else if (mods == SHIFT_PRESSED)
		{
			k.wVirtualKeyCode = (dir > 0) ? VK_PRIOR : VK_NEXT;
		}
		else
			return false;
		CEStr lsPart;
		if (!GetSubstitute(k, lsPart) || lsPart.IsEmpty())
			return false;
		if (nCount > 1)
		{
			INT_PTR l = lsPart.GetLen();
			wchar_t* ptr = lsSubst.GetBuffer(l*nCount);
			for (UINT i = 1; i <= nCount; ++i, ptr += l)
				wcscpy_s(ptr, l+1, lsPart);
		}
		else
		{
			lsSubst.Attach(lsPart.Detach());
		}
		return true;
	}
	if (!(MouseMode & ~(tmm_VIM|tmm_SCROLL)))
		return false;


	BYTE NewBtns = (m.dwButtonState & 0x1F);
	if ((NewBtns != MouseButtons)
		|| ((m.dwEventFlags & MOUSE_WHEELED) && HIWORD(m.dwButtonState))
		|| ((LastMousePos != m.dwMousePosition)
			&& ((MouseMode & tmm_ANY)
				|| ((MouseMode & tmm_BTN) && NewBtns)))
		)
	{
		// #XTERM_MOUSE Unfortunately, szSubst is too short to pass "missed" coordinates
		// Like we do for Far events, MouseMove with RBtn pressed in tmm_ANY|tmm_BTN
		// modes would send all intermediate coordinates between two events

		BYTE code; bool released = false;
		if (NewBtns & FROM_LEFT_1ST_BUTTON_PRESSED)
			code = 0; // MB1 pressed
		else if (NewBtns & FROM_LEFT_2ND_BUTTON_PRESSED)
			code = 1; // MB2 pressed
		else if (NewBtns & RIGHTMOST_BUTTON_PRESSED)
			code = 2; // MB3 pressed
		else if (NewBtns & FROM_LEFT_3RD_BUTTON_PRESSED)
			code = 64; // MB4 pressed
		else if (NewBtns & FROM_LEFT_4TH_BUTTON_PRESSED)
			code = 64 + 1; // MB5 pressed
		else if (m.dwEventFlags & MOUSE_WHEELED)
		{
			// #XTERM_MOUSE Post multiple events if dir contains multiple notches
			short dir = (short)HIWORD(m.dwButtonState);
			if (dir > 0)
				code = 64; // MB4
			else if (dir < 0)
				code = 64 + 1; // MB5
		}
		else
		{
			released = true;
			code = 3;
		}

		if (m.dwControlKeyState & SHIFT_PRESSED)
			code |= 4;
		if (m.dwControlKeyState & (LEFT_ALT_PRESSED|RIGHT_ALT_PRESSED))
			code |= 8;
		if (m.dwControlKeyState & (LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED))
			code |= 16;

		if ((m.dwEventFlags & MOUSE_MOVED)
			&& (MouseMode & (tmm_BTN|tmm_ANY)))
			code |= 32;

		// (1,1) is upper left character position
		SHORT coord[] = {std::max<SHORT>(0, m.dwMousePosition.X) + 1,
			std::max<SHORT>(0, m.dwMousePosition.Y) + 1};

		if (MouseMode & tmm_XTERM)
		{
			msprintf(szSubst, countof(szSubst), L"\033[<%u;%u;%u%c",
				code, coord[0], coord[1], released ? 'm' : 'M');
		}
		else if (MouseMode & tmm_URXVT)
		{
			msprintf(szSubst, countof(szSubst), L"\033[%u;%u;%uM", code + 0x20, coord[0], coord[1]);
		}
		else
		{
			wcscpy_c(szSubst, L"\033[M");
			size_t i = wcslen(szSubst);
			szSubst[i++] = code + 32;

			// And coords. Must be in "screen" coordinate space
			for (size_t s = 0; s < 2; ++s)
			{
				// (1,1) is upper left character position
				if (!(MouseMode & tmm_UTF8))
					szSubst[i++] = std::min<unsigned>(255, coord[s] + 32);
				else if (coord[s] < 0x80)
					szSubst[i++] = coord[s];
				else if (coord[s] < 0x800)
				{
					// xterm #262: positions from 96 to 2015 are encoded as a two-byte UTF-8 sequence
					szSubst[i++] = 0xC0 + (coord[s] >> 6);
					szSubst[i++] = 0x80 + (coord[s] & 0x3F);
				}
				else
				{
					// #XTERM_MOUSE Xterm reports out-of-range positions as a NUL byte.
					szSubst[i++] = 1; // It's impossible to post NUL byte ATM
				}
			}