// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "precomp.h" #include "alias.h" #include "_output.h" #include "output.h" #include "stream.h" #include "_stream.h" #include "dbcs.h" #include "handle.h" #include "misc.h" #include "../types/inc/convert.hpp" #include "srvinit.h" #include "resource.h" #include "ApiRoutines.h" #include "..\interactivity\inc\ServiceLocator.hpp" #pragma hdrstop using Microsoft::Console::Interactivity::ServiceLocator; struct case_insensitive_hash { std::size_t operator()(const std::wstring& key) const { std::wstring lower(key); std::transform(lower.begin(), lower.end(), lower.begin(), ::towlower); std::hash hash; return hash(lower); } }; struct case_insensitive_equality { bool operator()(const std::wstring& lhs, const std::wstring& rhs) const { return 0 == _wcsicmp(lhs.data(), rhs.data()); } }; std::unordered_map, case_insensitive_hash, case_insensitive_equality> g_aliasData; // Routine Description: // - Adds a command line alias to the global set. // - Converts and calls the W version of this function. // Arguments: // - source - The shorthand/alias or source buffer to set // - target- The destination/expansion or target buffer to set // - exeName - The client EXE application attached to the host to whom this substitution will apply // Return Value: // - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors. [[nodiscard]] HRESULT ApiRoutines::AddConsoleAliasAImpl(const std::string_view source, const std::string_view target, const std::string_view exeName) noexcept { const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); UINT const codepage = gci.CP; try { const auto sourceW = ConvertToW(codepage, source); const auto targetW = ConvertToW(codepage, target); const auto exeNameW = ConvertToW(codepage, exeName); return AddConsoleAliasWImpl(sourceW, targetW, exeNameW); } CATCH_RETURN(); } // Routine Description: // - Adds a command line alias to the global set. // Arguments: // - source - The shorthand/alias or source buffer to set // - target - The destination/expansion or target buffer to set // - exeName - The client EXE application attached to the host to whom this substitution will apply // Return Value: // - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors. [[nodiscard]] HRESULT ApiRoutines::AddConsoleAliasWImpl(const std::wstring_view source, const std::wstring_view target, const std::wstring_view exeName) noexcept { LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); RETURN_HR_IF(E_INVALIDARG, source.size() == 0); try { std::wstring exeNameString(exeName); std::wstring sourceString(source); std::wstring targetString(target); std::transform(exeNameString.begin(), exeNameString.end(), exeNameString.begin(), towlower); std::transform(sourceString.begin(), sourceString.end(), sourceString.begin(), towlower); if (targetString.size() == 0) { // Only try to dig in and erase if the exeName exists. auto exeData = g_aliasData.find(exeNameString); if (exeData != g_aliasData.end()) { g_aliasData[exeNameString].erase(sourceString); } } else { // Map will auto-create each level as necessary g_aliasData[exeNameString][sourceString] = targetString; } } CATCH_RETURN(); return S_OK; } // Routine Description: // - Retrieves a command line alias from the global set. // - It is permitted to call this function without having a target buffer. Use the result to allocate // the appropriate amount of space and call again. // - This behavior exists to allow the A version of the function to help allocate the right temp buffer for conversion of // the output/result data. // Arguments: // - source - The shorthand/alias or source buffer to use in lookup // - target - The destination/expansion or target buffer we are attempting to retrieve. Optionally nullopt to retrieve needed space. // - writtenOrNeeded - Will specify how many characters were written (if target has value) // or how many characters would have been consumed (if target is nullopt). // - exeName - The client EXE application attached to the host whose set we should check // Return Value: // - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors. [[nodiscard]] HRESULT GetConsoleAliasWImplHelper(const std::wstring_view source, std::optional> target, size_t& writtenOrNeeded, const std::wstring_view exeName) { // Ensure output variables are initialized writtenOrNeeded = 0; if (target.has_value() && target.value().size() > 0) { target.value().at(0) = UNICODE_NULL; } std::wstring exeNameString(exeName); std::wstring sourceString(source); // For compatibility, return ERROR_GEN_FAILURE for any result where the alias can't be found. // We use .find for the iterators then dereference to search without creating entries. const auto exeIter = g_aliasData.find(exeNameString); RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_GEN_FAILURE), exeIter == g_aliasData.end()); const auto exeData = exeIter->second; const auto sourceIter = exeData.find(sourceString); RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_GEN_FAILURE), sourceIter == exeData.end()); const auto targetString = sourceIter->second; RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_GEN_FAILURE), targetString.size() == 0); // TargetLength is a byte count, convert to characters. size_t targetSize = targetString.size(); size_t const cchNull = 1; // The total space we need is the length of the string + the null terminator. size_t neededSize; RETURN_IF_FAILED(SizeTAdd(targetSize, cchNull, &neededSize)); writtenOrNeeded = neededSize; if (target.has_value()) { // if the user didn't give us enough space, return with insufficient buffer code early. RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER), gsl::narrow(target.value().size()) < neededSize); RETURN_IF_FAILED(StringCchCopyNW(target.value().data(), target.value().size(), targetString.data(), targetSize)); } return S_OK; } // Routine Description: // - Retrieves a command line alias from the global set. // - This function will convert input parameters from A to W, call the W version of the routine, // and attempt to convert the resulting data back to A for return. // Arguments: // - source - The shorthand/alias or source buffer to use in lookup // - target - The destination/expansion or target buffer we are attempting to retrieve. // - written - Will specify how many characters were written // - exeName - The client EXE application attached to the host whose set we should check // Return Value: // - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors. [[nodiscard]] HRESULT ApiRoutines::GetConsoleAliasAImpl(const std::string_view source, gsl::span target, size_t& written, const std::string_view exeName) noexcept { const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); UINT const codepage = gci.CP; // Ensure output variables are initialized written = 0; try { if (target.size() > 0) { target.at(0) = ANSI_NULL; } LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); // Convert our input parameters to Unicode. const auto sourceW = ConvertToW(codepage, source); const auto exeNameW = ConvertToW(codepage, exeName); // Figure out how big our temporary Unicode buffer must be to retrieve output size_t targetNeeded; RETURN_IF_FAILED(GetConsoleAliasWImplHelper(sourceW, std::nullopt, targetNeeded, exeNameW)); // If there's nothing to get, then simply return. RETURN_HR_IF(S_OK, 0 == targetNeeded); // If the user hasn't given us a buffer at all and we need one, return an error. RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER), 0 == target.size()); // Allocate a unicode buffer of the right size. std::unique_ptr targetBuffer = std::make_unique(targetNeeded); RETURN_IF_NULL_ALLOC(targetBuffer); // Call the Unicode version of this method size_t targetWritten; RETURN_IF_FAILED(GetConsoleAliasWImplHelper(sourceW, gsl::span(targetBuffer.get(), targetNeeded), targetWritten, exeNameW)); // Set the return size copied to the size given before we attempt to copy. // Then multiply by sizeof(wchar_t) due to a long standing bug that we must preserve for compatibility. // On failure, the API has historically given back this value. written = target.size() * sizeof(wchar_t); // Convert result to A const auto converted = ConvertToA(codepage, { targetBuffer.get(), targetWritten }); // Copy safely to output buffer RETURN_IF_FAILED(StringCchCopyNA(target.data(), target.size(), converted.data(), converted.size())); // And return the size copied. written = converted.size(); return S_OK; } CATCH_RETURN(); } // Routine Description: // - Retrieves a command line alias from the global set. // Arguments: // - source - The shorthand/alias or source buffer to use in lookup // - target - The destination/expansion or target buffer we are attempting to retrieve. // - written - Will specify how many characters were written // - exeName - The client EXE application attached to the host whose set we should check // Return Value: // - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors. [[nodiscard]] HRESULT ApiRoutines::GetConsoleAliasWImpl(const std::wstring_view source, gsl::span target, size_t& written, const std::wstring_view exeName) noexcept { LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); try { HRESULT hr = GetConsoleAliasWImplHelper(source, target, written, exeName); if (FAILED(hr)) { written = target.size(); } return hr; } CATCH_RETURN(); } // These variables define the separator character and the length of the string. // They will be used to as the joiner between source and target strings when returning alias data in list form. static std::wstring aliasesSeparator(L"="); // Routine Description: // - Retrieves the amount of space needed to hold all aliases (source=target pairs) for the given EXE name // - Works for both Unicode and Multibyte text. // - This method configuration is called for both A/W routines to allow us an efficient way of asking the system // the lengths of how long each conversion would be without actually performing the full allocations/conversions. // Arguments: // - exeName - The client EXE application attached to the host whose set we should check // - countInUnicode - True for W version (UTF-16 Unicode) calls. False for A version calls (all multibyte formats.) // - codepage - Set to valid Windows Codepage for A version calls. Ignored for W (but typically just set to 0.) // - bufferRequired - Receives the length of buffer that would be required to retrieve all aliases for the given exe. // Return Value: // - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors. [[nodiscard]] HRESULT GetConsoleAliasesLengthWImplHelper(const std::wstring_view exeName, const bool countInUnicode, const UINT codepage, size_t& bufferRequired) { // Ensure output variables are initialized bufferRequired = 0; try { const std::wstring exeNameString(exeName); size_t cchNeeded = 0; // Each of the aliases will be made up of the source, a separator, the target, then a null character. // They are of the form "Source=Target" when returned. size_t const cchNull = 1; size_t cchSeparator = aliasesSeparator.size(); // If we're counting how much multibyte space will be needed, trial convert the separator before we add. if (!countInUnicode) { cchSeparator = GetALengthFromW(codepage, aliasesSeparator); } // Find without creating. auto exeIter = g_aliasData.find(exeNameString); if (exeIter != g_aliasData.end()) { auto list = exeIter->second; for (auto& pair : list) { // Alias stores lengths in bytes. size_t cchSource = pair.first.size(); size_t cchTarget = pair.second.size(); // If we're counting how much multibyte space will be needed, trial convert the source and target strings before we add. if (!countInUnicode) { cchSource = GetALengthFromW(codepage, pair.first); cchTarget = GetALengthFromW(codepage, pair.second); } // Accumulate all sizes to the final string count. RETURN_IF_FAILED(SizeTAdd(cchNeeded, cchSource, &cchNeeded)); RETURN_IF_FAILED(SizeTAdd(cchNeeded, cchSeparator, &cchNeeded)); RETURN_IF_FAILED(SizeTAdd(cchNeeded, cchTarget, &cchNeeded)); RETURN_IF_FAILED(SizeTAdd(cchNeeded, cchNull, &cchNeeded)); } } bufferRequired = cchNeeded; } CATCH_RETURN(); return S_OK; } // Routine Description: // - Retrieves the amount of space needed to hold all aliases (source=target pairs) for the given EXE name // - Converts input text from A to W then makes the call to the W implementation. // Arguments: // - exeName - The client EXE application attached to the host whose set we should check // - bufferRequired - Receives the length of buffer that would be required to retrieve all aliases for the given exe. // Return Value: // - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors. [[nodiscard]] HRESULT ApiRoutines::GetConsoleAliasesLengthAImpl(const std::string_view exeName, size_t& bufferRequired) noexcept { const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); UINT const codepage = gci.CP; // Ensure output variables are initialized bufferRequired = 0; LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); // Convert our input parameters to Unicode try { const auto exeNameW = ConvertToW(codepage, exeName); return GetConsoleAliasesLengthWImplHelper(exeNameW, false, codepage, bufferRequired); } CATCH_RETURN(); } // Routine Description: // - Retrieves the amount of space needed to hold all aliases (source=target pairs) for the given EXE name // - Converts input text from A to W then makes the call to the W implementation. // Arguments: // - exeName - The client EXE application attached to the host whose set we should check // - bufferRequired - Receives the length of buffer that would be required to retrieve all aliases for the given exe. // Return Value: // - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors. [[nodiscard]] HRESULT ApiRoutines::GetConsoleAliasesLengthWImpl(const std::wstring_view exeName, size_t& bufferRequired) noexcept { LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); try { return GetConsoleAliasesLengthWImplHelper(exeName, true, 0, bufferRequired); } CATCH_RETURN(); } // Routine Description: // - Clears all aliases on CMD.exe. void Alias::s_ClearCmdExeAliases() { // find without creating. auto exeIter = g_aliasData.find(L"cmd.exe"); if (exeIter != g_aliasData.end()) { exeIter->second.clear(); } } // Routine Description: // - Retrieves all source=target pairs representing alias definitions for a given EXE name // - It is permitted to call this function without having a target buffer. Use the result to allocate // the appropriate amount of space and call again. // - This behavior exists to allow the A version of the function to help allocate the right temp buffer for conversion of // the output/result data. // Arguments: // - exeName - The client EXE application attached to the host whose set we should check // - aliasBuffer - The target buffer to hold all alias pairs we are trying to retrieve. // Optionally nullopt to retrieve needed space. // - writtenOrNeeded - Pointer to space that will specify how many characters were written (if buffer is valid) // or how many characters would have been needed (if buffer is nullopt). // Return Value: // - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors. [[nodiscard]] HRESULT GetConsoleAliasesWImplHelper(const std::wstring_view exeName, std::optional> aliasBuffer, size_t& writtenOrNeeded) { // Ensure output variables are initialized. writtenOrNeeded = 0; if (aliasBuffer.has_value() && aliasBuffer.value().size() > 0) { aliasBuffer.value().at(0) = UNICODE_NULL; } std::wstring exeNameString(exeName); LPWSTR AliasesBufferPtrW = aliasBuffer.has_value() ? aliasBuffer.value().data() : nullptr; size_t cchTotalLength = 0; // accumulate the characters we need/have copied as we walk the list // Each of the aliases will be made up of the source, a separator, the target, then a null character. // They are of the form "Source=Target" when returned. size_t const cchNull = 1; // Find without creating. auto exeIter = g_aliasData.find(exeNameString); if (exeIter != g_aliasData.end()) { auto list = exeIter->second; for (auto& pair : list) { // Alias stores lengths in bytes. size_t const cchSource = pair.first.size(); size_t const cchTarget = pair.second.size(); // Add up how many characters we will need for the full alias data. size_t cchNeeded = 0; RETURN_IF_FAILED(SizeTAdd(cchNeeded, cchSource, &cchNeeded)); RETURN_IF_FAILED(SizeTAdd(cchNeeded, aliasesSeparator.size(), &cchNeeded)); RETURN_IF_FAILED(SizeTAdd(cchNeeded, cchTarget, &cchNeeded)); RETURN_IF_FAILED(SizeTAdd(cchNeeded, cchNull, &cchNeeded)); // If we can return the data, attempt to do so until we're done or it overflows. // If we cannot return data, we're just going to loop anyway and count how much space we'd need. if (aliasBuffer.has_value()) { // Calculate the new final total after we add what we need to see if it will exceed the limit size_t cchNewTotal; RETURN_IF_FAILED(SizeTAdd(cchTotalLength, cchNeeded, &cchNewTotal)); RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW), cchNewTotal > gsl::narrow(aliasBuffer.value().size())); size_t cchAliasBufferRemaining; RETURN_IF_FAILED(SizeTSub(aliasBuffer.value().size(), cchTotalLength, &cchAliasBufferRemaining)); RETURN_IF_FAILED(StringCchCopyNW(AliasesBufferPtrW, cchAliasBufferRemaining, pair.first.data(), cchSource)); RETURN_IF_FAILED(SizeTSub(cchAliasBufferRemaining, cchSource, &cchAliasBufferRemaining)); AliasesBufferPtrW += cchSource; RETURN_IF_FAILED(StringCchCopyNW(AliasesBufferPtrW, cchAliasBufferRemaining, aliasesSeparator.data(), aliasesSeparator.size())); RETURN_IF_FAILED(SizeTSub(cchAliasBufferRemaining, aliasesSeparator.size(), &cchAliasBufferRemaining)); AliasesBufferPtrW += aliasesSeparator.size(); RETURN_IF_FAILED(StringCchCopyNW(AliasesBufferPtrW, cchAliasBufferRemaining, pair.second.data(), cchTarget)); RETURN_IF_FAILED(SizeTSub(cchAliasBufferRemaining, cchTarget, &cchAliasBufferRemaining)); AliasesBufferPtrW += cchTarget; // StringCchCopyNW ensures that the destination string is null terminated, so simply advance the pointer. RETURN_IF_FAILED(SizeTSub(cchAliasBufferRemaining, 1, &cchAliasBufferRemaining)); AliasesBufferPtrW += cchNull; } RETURN_IF_FAILED(SizeTAdd(cchTotalLength, cchNeeded, &cchTotalLength)); } } writtenOrNeeded = cchTotalLength; return S_OK; } // Routine Description: // - Retrieves all source=target pairs representing alias definitions for a given EXE name // - Will convert all input from A to W, call the W version of the function, then convert resulting W to A text and return. // Arguments: // - exeName - The client EXE application attached to the host whose set we should check // - alias - The target buffer to hold all alias pairs we are trying to retrieve. // - written - Will specify how many characters were written // Return Value: // - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors. [[nodiscard]] HRESULT ApiRoutines::GetConsoleAliasesAImpl(const std::string_view exeName, gsl::span alias, size_t& written) noexcept { const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); UINT const codepage = gci.CP; // Ensure output variables are initialized written = 0; try { if (alias.size() > 0) { alias.at(0) = '\0'; } LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); // Convert our input parameters to Unicode. const auto exeNameW = ConvertToW(codepage, exeName); wistd::unique_ptr pwsExeName; // Figure out how big our temporary Unicode buffer must be to retrieve output size_t bufferNeeded; RETURN_IF_FAILED(GetConsoleAliasesWImplHelper(exeNameW, std::nullopt, bufferNeeded)); // If there's nothing to get, then simply return. RETURN_HR_IF(S_OK, 0 == bufferNeeded); // Allocate a unicode buffer of the right size. std::unique_ptr aliasBuffer = std::make_unique(bufferNeeded); RETURN_IF_NULL_ALLOC(aliasBuffer); // Call the Unicode version of this method size_t bufferWritten; RETURN_IF_FAILED(GetConsoleAliasesWImplHelper(exeNameW, gsl::span(aliasBuffer.get(), bufferNeeded), bufferWritten)); // Convert result to A const auto converted = ConvertToA(codepage, { aliasBuffer.get(), bufferWritten }); // Copy safely to the output buffer // - Aliases are a series of null terminated strings. We cannot use a SafeString function to copy. // So instead, validate and use raw memory copy. RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW), converted.size() > gsl::narrow(alias.size())); memcpy_s(alias.data(), alias.size(), converted.data(), converted.size()); // And return the size copied. written = converted.size(); return S_OK; } CATCH_RETURN(); } // Routine Description: // - Retrieves all source=target pairs representing alias definitions for a given EXE name // Arguments: // - exeName - The client EXE application attached to the host whose set we should check // - alias - The target buffer to hold all alias pairs we are trying to retrieve. // - written - Will specify how many characters were written // Return Value: // - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors. [[nodiscard]] HRESULT ApiRoutines::GetConsoleAliasesWImpl(const std::wstring_view exeName, gsl::span alias, size_t& written) noexcept { LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); try { return GetConsoleAliasesWImplHelper(exeName, alias, written); } CATCH_RETURN(); } // Routine Description: // - Retrieves the amount of space needed to hold all EXE names with aliases defined that are known to the console // - Works for both Unicode and Multibyte text. // - This method configuration is called for both A/W routines to allow us an efficient way of asking the system // the lengths of how long each conversion would be without actually performing the full allocations/conversions. // Arguments: // - countInUnicode - True for W version (UCS-2 Unicode) calls. False for A version calls (all multibyte formats.) // - codepage - Set to valid Windows Codepage for A version calls. Ignored for W (but typically just set to 0.) // - bufferRequired - Receives the length of buffer that would be required to retrieve all relevant EXE names. // Return Value: // - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors. [[nodiscard]] HRESULT GetConsoleAliasExesLengthImplHelper(const bool countInUnicode, const UINT codepage, size_t& bufferRequired) { // Ensure output variables are initialized bufferRequired = 0; size_t cchNeeded = 0; // Each alias exe will be made up of the string payload and a null terminator. size_t const cchNull = 1; for (auto& pair : g_aliasData) { size_t cchExe = pair.first.size(); // If we're counting how much multibyte space will be needed, trial convert the exe string before we add. if (!countInUnicode) { cchExe = GetALengthFromW(codepage, pair.first); } // Accumulate to total RETURN_IF_FAILED(SizeTAdd(cchNeeded, cchExe, &cchNeeded)); RETURN_IF_FAILED(SizeTAdd(cchNeeded, cchNull, &cchNeeded)); } bufferRequired = cchNeeded; return S_OK; } // Routine Description: // - Retrieves the amount of space needed to hold all EXE names with aliases defined that are known to the console // Arguments: // - bufferRequired - Receives the length of buffer that would be required to retrieve all relevant EXE names. // Return Value: // - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors. [[nodiscard]] HRESULT ApiRoutines::GetConsoleAliasExesLengthAImpl(size_t& bufferRequired) noexcept { LockConsole(); const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); return GetConsoleAliasExesLengthImplHelper(false, gci.CP, bufferRequired); } // Routine Description: // - Retrieves the amount of space needed to hold all EXE names with aliases defined that are known to the console // Arguments: // - bufferRequired - Pointer to receive the length of buffer that would be required to retrieve all relevant EXE names. // Return Value: // - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors. [[nodiscard]] HRESULT ApiRoutines::GetConsoleAliasExesLengthWImpl(size_t& bufferRequired) noexcept { LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); return GetConsoleAliasExesLengthImplHelper(true, 0, bufferRequired); } // Routine Description: // - Retrieves all EXE names with aliases defined that are known to the console. // - It is permitted to call this function without having a target buffer. Use the result to allocate // the appropriate amount of space and call again. // - This behavior exists to allow the A version of the function to help allocate the right temp buffer for conversion of // the output/result data. // Arguments: // - pwsAliasExesBuffer - The target buffer to hold all known EXE names we are trying to retrieve. // Optionally nullopt to retrieve needed space. // - cchAliasExesBufferLength - Length in characters of target buffer. Set to 0 when buffer is nullptr. // - pcchAliasExesBufferWrittenOrNeeded - Pointer to space that will specify how many characters were written (if buffer is valid) // or how many characters would have been needed (if buffer is nullopt). // Return Value: // - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors. [[nodiscard]] HRESULT GetConsoleAliasExesWImplHelper(std::optional> aliasExesBuffer, size_t& writtenOrNeeded) { // Ensure output variables are initialized. writtenOrNeeded = 0; if (aliasExesBuffer.has_value() && aliasExesBuffer.value().size() > 0) { aliasExesBuffer.value().at(0) = UNICODE_NULL; } LPWSTR AliasExesBufferPtrW = aliasExesBuffer.has_value() ? aliasExesBuffer.value().data() : nullptr; size_t cchTotalLength = 0; // accumulate the characters we need/have copied as we walk the list size_t const cchNull = 1; for (auto& pair : g_aliasData) { // AliasList stores length in bytes. Add 1 for null terminator. size_t const cchExe = pair.first.size(); size_t cchNeeded; RETURN_IF_FAILED(SizeTAdd(cchExe, cchNull, &cchNeeded)); // If we can return the data, attempt to do so until we're done or it overflows. // If we cannot return data, we're just going to loop anyway and count how much space we'd need. if (aliasExesBuffer.has_value()) { // Calculate the new total length after we add to the buffer // Error out early if there is a problem. size_t cchNewTotal; RETURN_IF_FAILED(SizeTAdd(cchTotalLength, cchNeeded, &cchNewTotal)); RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW), cchNewTotal > gsl::narrow(aliasExesBuffer.value().size())); size_t cchRemaining; RETURN_IF_FAILED(SizeTSub(aliasExesBuffer.value().size(), cchTotalLength, &cchRemaining)); RETURN_IF_FAILED(StringCchCopyNW(AliasExesBufferPtrW, cchRemaining, pair.first.data(), cchExe)); AliasExesBufferPtrW += cchNeeded; } // Accumulate the total written amount. RETURN_IF_FAILED(SizeTAdd(cchTotalLength, cchNeeded, &cchTotalLength)); } writtenOrNeeded = cchTotalLength; return S_OK; } // Routine Description: // - Retrieves all EXE names with aliases defined that are known to the console. // - Will call the W version of the function and convert all text back to A on returning. // Arguments: // - aliasExes - The target buffer to hold all known EXE names we are trying to retrieve. // - written - Specifies how many characters were written // Return Value: // - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors. [[nodiscard]] HRESULT ApiRoutines::GetConsoleAliasExesAImpl(gsl::span aliasExes, size_t& written) noexcept { const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); UINT const codepage = gci.CP; // Ensure output variables are initialized written = 0; try { if (aliasExes.size() > 0) { aliasExes.at(0) = '\0'; } LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); // Figure our how big our temporary Unicode buffer must be to retrieve output size_t bufferNeeded; RETURN_IF_FAILED(GetConsoleAliasExesWImplHelper(std::nullopt, bufferNeeded)); // If there's nothing to get, then simply return. RETURN_HR_IF(S_OK, 0 == bufferNeeded); // Allocate a unicode buffer of the right size. std::unique_ptr targetBuffer = std::make_unique(bufferNeeded); RETURN_IF_NULL_ALLOC(targetBuffer); // Call the Unicode version of this method size_t bufferWritten; RETURN_IF_FAILED(GetConsoleAliasExesWImplHelper(gsl::span(targetBuffer.get(), bufferNeeded), bufferWritten)); // Convert result to A const auto converted = ConvertToA(codepage, { targetBuffer.get(), bufferWritten }); // Copy safely to the output buffer // - AliasExes are a series of null terminated strings. We cannot use a SafeString function to copy. // So instead, validate and use raw memory copy. RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW), converted.size() > gsl::narrow(aliasExes.size())); memcpy_s(aliasExes.data(), aliasExes.size(), converted.data(), converted.size()); // And return the size copied. written = converted.size(); return S_OK; } CATCH_RETURN(); } // Routine Description: // - Retrieves all EXE names with aliases defined that are known to the console. // Arguments: // - pwsAliasExesBuffer - The target buffer to hold all known EXE names we are trying to retrieve. // - cchAliasExesBufferLength - Length in characters of target buffer. Set to 0 when buffer is nullptr. // - pcchAliasExesBufferWrittenOrNeeded - Pointer to space that will specify how many characters were written // Return Value: // - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors. [[nodiscard]] HRESULT ApiRoutines::GetConsoleAliasExesWImpl(gsl::span aliasExes, size_t& written) noexcept { LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); try { return GetConsoleAliasExesWImplHelper(aliasExes, written); } CATCH_RETURN(); } // Routine Description: // - Trims leading spaces off of a string // Arguments: // - str - String to trim void Alias::s_TrimLeadingSpaces(std::wstring& str) { // Erase from the beginning of the string up until the first // character found that is not a space. str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](wchar_t ch) { return !std::iswspace(ch); })); } // Routine Description: // - Trims trailing \r\n off of a string // Arguments: // - str - String to trim void Alias::s_TrimTrailingCrLf(std::wstring& str) { const auto trailingCrLfPos = str.find_last_of(UNICODE_CARRIAGERETURN); if (std::wstring::npos != trailingCrLfPos) { str.erase(trailingCrLfPos); } } // Routine Description: // - Tokenizes a string into a collection using space as a separator // Arguments: // - str - String to tokenize // Return Value: // - Collection of tokenized strings std::deque Alias::s_Tokenize(const std::wstring& str) { std::deque result; size_t prevIndex = 0; auto spaceIndex = str.find(L' '); while (std::wstring::npos != spaceIndex) { const auto length = spaceIndex - prevIndex; result.emplace_back(str.substr(prevIndex, length)); spaceIndex++; prevIndex = spaceIndex; spaceIndex = str.find(L' ', spaceIndex); } // Place the final one into the set. result.emplace_back(str.substr(prevIndex)); return result; } // Routine Description: // - Gets just the arguments portion of the command string // Specifically, all text after the first space character. // Arguments: // - str - String to split into just args // Return Value: // - Only the arguments part of the string or empty if there are no arguments. std::wstring Alias::s_GetArgString(const std::wstring& str) { std::wstring result; auto firstSpace = str.find_first_of(L' '); if (std::wstring::npos != firstSpace) { firstSpace++; if (firstSpace < str.size()) { result = str.substr(firstSpace); } } return result; } // Routine Description: // - Checks the given character to see if it is a numbered arg replacement macro // and replaces it with the counted argument if there is a match // Arguments: // - ch - Character to test as a macro // - appendToStr - Append the macro result here if it matched // - tokens - Tokens of the original command string. 0 is alias. 1-N are arguments. // Return Value: // - True if we found the macro and appended to the string. // - False if the given character doesn't match this macro. bool Alias::s_TryReplaceNumberedArgMacro(const wchar_t ch, std::wstring& appendToStr, const std::deque& tokens) { if (ch >= L'1' && ch <= L'9') { // Numerical macros substitute that numbered argument const size_t index = ch - L'0'; if (index < tokens.size() && index > 0) { appendToStr.append(tokens[index]); } return true; } return false; } // Routine Description: // - Checks the given character to see if it is a wildcard arg replacement macro // and replaces it with the entire argument string if there is a match // Arguments: // - ch - Character to test as a macro // - appendToStr - Append the macro result here if it matched // - fullArgString - All of the arguments as one big string. // Return Value: // - True if we found the macro and appended to the string. // - False if the given character doesn't match this macro. bool Alias::s_TryReplaceWildcardArgMacro(const wchar_t ch, std::wstring& appendToStr, const std::wstring fullArgString) { if (L'*' == ch) { // Wildcard substitutes all arguments appendToStr.append(fullArgString); return true; } return false; } // Routine Description: // - Checks the given character to see if it is an input redirection macro // and replaces it with the < redirector if there is a match // Arguments: // - ch - Character to test as a macro // - appendToStr - Append the macro result here if it matched // Return Value: // - True if we found the macro and appended to the string. // - False if the given character doesn't match this macro. bool Alias::s_TryReplaceInputRedirMacro(const wchar_t ch, std::wstring& appendToStr) { if (L'L' == towupper(ch)) { // L (either case) replaces with input redirector < appendToStr.push_back(L'<'); return true; } return false; } // Routine Description: // - Checks the given character to see if it is an output redirection macro // and replaces it with the > redirector if there is a match // Arguments: // - ch - Character to test as a macro // - appendToStr - Append the macro result here if it matched // Return Value: // - True if we found the macro and appended to the string. // - False if the given character doesn't match this macro. bool Alias::s_TryReplaceOutputRedirMacro(const wchar_t ch, std::wstring& appendToStr) { if (L'G' == towupper(ch)) { // G (either case) replaces with output redirector > appendToStr.push_back(L'>'); return true; } return false; } // Routine Description: // - Checks the given character to see if it is a pipe redirection macro // and replaces it with the | redirector if there is a match // Arguments: // - ch - Character to test as a macro // - appendToStr - Append the macro result here if it matched // Return Value: // - True if we found the macro and appended to the string. // - False if the given character doesn't match this macro. bool Alias::s_TryReplacePipeRedirMacro(const wchar_t ch, std::wstring& appendToStr) { if (L'B' == towupper(ch)) { // B (either case) replaces with pipe operator | appendToStr.push_back(L'|'); return true; } return false; } // Routine Description: // - Checks the given character to see if it is a next command macro // and replaces it with CRLF if there is a match // Arguments: // - ch - Character to test as a macro // - appendToStr - Append the macro result here if it matched // - lineCount - Updates the rolling count of lines if we add a CRLF. // Return Value: // - True if we found the macro and appended to the string. // - False if the given character doesn't match this macro. bool Alias::s_TryReplaceNextCommandMacro(const wchar_t ch, std::wstring& appendToStr, size_t& lineCount) { if (L'T' == towupper(ch)) { // T (either case) inserts a CRLF to chain commands s_AppendCrLf(appendToStr, lineCount); return true; } return false; } // Routine Description: // - Appends the system line feed (CRLF) to the given string // Arguments: // - appendToStr - Append the system line feed here // - lineCount - Updates the rolling count of lines if we add a CRLF. void Alias::s_AppendCrLf(std::wstring& appendToStr, size_t& lineCount) { appendToStr.push_back(L'\r'); appendToStr.push_back(L'\n'); lineCount++; } // Routine Description: // - Searches through the given string for macros and replaces them // with the matching action // Arguments: // - str - On input, the string to search. On output, the string is replaced. // - tokens - The tokenized command line input. 0 is the alias, 1-N are arguments. // - fullArgString - Shorthand to 1-N argument string in case of wildcard match. // Return Value: // - The number of commands in the final string (line feeds, CRLFs) size_t Alias::s_ReplaceMacros(std::wstring& str, const std::deque& tokens, const std::wstring& fullArgString) { size_t lineCount = 0; std::wstring finalText; // The target text may contain substitution macros indicated by $. // Walk through and substitute them as appropriate. for (auto ch = str.cbegin(); ch < str.cend(); ch++) { if (L'$' == *ch) { // Attempt to read ahead by one character. const auto chNext = ch + 1; if (chNext < str.cend()) { auto isProcessed = s_TryReplaceNumberedArgMacro(*chNext, finalText, tokens); if (!isProcessed) { isProcessed = s_TryReplaceWildcardArgMacro(*chNext, finalText, fullArgString); } if (!isProcessed) { isProcessed = s_TryReplaceInputRedirMacro(*chNext, finalText); } if (!isProcessed) { isProcessed = s_TryReplaceOutputRedirMacro(*chNext, finalText); } if (!isProcessed) { isProcessed = s_TryReplacePipeRedirMacro(*chNext, finalText); } if (!isProcessed) { isProcessed = s_TryReplaceNextCommandMacro(*chNext, finalText, lineCount); } if (!isProcessed) { // If nothing matches, just push these two characters in. finalText.push_back(*ch); finalText.push_back(*chNext); } // Since we read ahead and used that character, // advance the iterator one extra to compensate. ch++; } else { // If no read-ahead, just push this character and be done. finalText.push_back(*ch); } } else { // If it didn't match the macro specifier $, push the character. finalText.push_back(*ch); } } // We always terminate with a CRLF to symbolize end of command. s_AppendCrLf(finalText, lineCount); // Give back the final text and count. str.swap(finalText); return lineCount; } // Routine Description: // - Takes the source text and searches it for an alias belonging to exe name's list. // Arguments: // - sourceText - The string to search for an alias // - exeName - The name of the EXE that has aliases associated // - lineCount - Number of lines worth of text processed. // Return Value: // - If we found a matching alias, this will be the processed data // and lineCount is updated to the new number of lines. // - If we didn't match and process an alias, return an empty string. std::wstring Alias::s_MatchAndCopyAlias(const std::wstring& sourceText, const std::wstring& exeName, size_t& lineCount) { // Copy source text into a local for manipulation. std::wstring sourceCopy(sourceText); // Trim trailing \r\n off of sourceCopy if it has one. s_TrimTrailingCrLf(sourceCopy); // Trim leading spaces off of sourceCopy if it has any. s_TrimLeadingSpaces(sourceCopy); // Check if we have an EXE in the list that matches the request first. auto exeIter = g_aliasData.find(exeName); if (exeIter == g_aliasData.end()) { // We found no data for this exe. Give back an empty string. return std::wstring(); } auto exeList = exeIter->second; if (exeList.size() == 0) { // If there's no match, give back an empty string. return std::wstring(); } // Tokenize the text by spaces const auto tokens = s_Tokenize(sourceCopy); // If there are no tokens, return an empty string if (tokens.size() == 0) { return std::wstring(); } // Find alias. If there isn't one, return an empty string const auto alias = tokens.front(); const auto aliasIter = exeList.find(alias); if (aliasIter == exeList.end()) { // We found no alias pair with this name. Give back an empty string. return std::wstring(); } const auto target = aliasIter->second; if (target.size() == 0) { return std::wstring(); } // Get the string of all parameters as a shorthand for $* later. const auto allParams = s_GetArgString(sourceCopy); // The final text will be the target but with macros replaced. std::wstring finalText(target); lineCount = s_ReplaceMacros(finalText, tokens, allParams); return finalText; } // Routine Description: // - This routine matches the input string with an alias and copies the alias to the input buffer. // Arguments: // - pwchSource - string to match // - cbSource - length of pwchSource in bytes // - pwchTarget - where to store matched string // - cbTargetSize - on input, contains size of pwchTarget. // - cbTargetWritten - On output, contains length of alias stored in pwchTarget. // - pwchExe - Name of exe that command is associated with to find related aliases // - cbExe - Length in bytes of exe name // - LineCount - aliases can contain multiple commands. $T is the command separator // Return Value: // - None. It will just maintain the source as the target if we can't match an alias. void Alias::s_MatchAndCopyAliasLegacy(_In_reads_bytes_(cbSource) PWCHAR pwchSource, _In_ size_t cbSource, _Out_writes_bytes_(cbTargetWritten) PWCHAR pwchTarget, _In_ const size_t cbTargetSize, size_t& cbTargetWritten, const std::wstring& exeName, DWORD& lines) { try { std::wstring sourceText(pwchSource, cbSource / sizeof(WCHAR)); size_t lineCount = lines; const auto targetText = s_MatchAndCopyAlias(sourceText, exeName, lineCount); // Only return data if the reply was non-empty (we had a match). if (!targetText.empty()) { const auto cchTargetSize = cbTargetSize / sizeof(wchar_t); // If the target text will fit in the result buffer, fill out the results. if (targetText.size() <= cchTargetSize) { // Non-null terminated copy into memory space std::copy_n(targetText.data(), targetText.size(), pwchTarget); // Return bytes copied. cbTargetWritten = gsl::narrow(targetText.size() * sizeof(wchar_t)); // Return lines info. lines = gsl::narrow(lineCount); } } } catch (...) { LOG_HR(wil::ResultFromCaughtException()); } } #ifdef UNIT_TESTING void Alias::s_TestAddAlias(std::wstring& exe, std::wstring& alias, std::wstring& target) { g_aliasData[exe][alias] = target; } void Alias::s_TestClearAliases() { g_aliasData.clear(); } #endif