terminal/src/host/alias.cpp

1273 lines
51 KiB
C++

// 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<std::wstring> 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<std::wstring,
std::unordered_map<std::wstring,
std::wstring,
case_insensitive_hash,
case_insensitive_equality>,
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<gsl::span<wchar_t>> target,
size_t& writtenOrNeeded,
const std::wstring_view exeName)
{
// Ensure output variables are initialized
writtenOrNeeded = 0;
if (target.has_value() && target->size() > 0)
{
til::at(*target, 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), target->size() < neededSize);
RETURN_IF_FAILED(StringCchCopyNW(target->data(), target->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<char> 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)
{
til::at(target, 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<wchar_t[]> targetBuffer = std::make_unique<wchar_t[]>(targetNeeded);
RETURN_IF_NULL_ALLOC(targetBuffer);
// Call the Unicode version of this method
size_t targetWritten;
RETURN_IF_FAILED(GetConsoleAliasWImplHelper(sourceW,
gsl::span<wchar_t>(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<wchar_t> 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<gsl::span<wchar_t>> aliasBuffer,
size_t& writtenOrNeeded)
{
// Ensure output variables are initialized.
writtenOrNeeded = 0;
if (aliasBuffer.has_value() && aliasBuffer->size() > 0)
{
til::at(*aliasBuffer, 0) = UNICODE_NULL;
}
std::wstring exeNameString(exeName);
LPWSTR AliasesBufferPtrW = aliasBuffer.has_value() ? aliasBuffer->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 > aliasBuffer->size());
size_t cchAliasBufferRemaining;
RETURN_IF_FAILED(SizeTSub(aliasBuffer->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<char> 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)
{
til::at(alias, 0) = '\0';
}
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
// Convert our input parameters to Unicode.
const auto exeNameW = ConvertToW(codepage, exeName);
wistd::unique_ptr<wchar_t[]> 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<wchar_t[]> aliasBuffer = std::make_unique<wchar_t[]>(bufferNeeded);
RETURN_IF_NULL_ALLOC(aliasBuffer);
// Call the Unicode version of this method
size_t bufferWritten;
RETURN_IF_FAILED(GetConsoleAliasesWImplHelper(exeNameW, gsl::span<wchar_t>(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() > 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<wchar_t> 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<gsl::span<wchar_t>> aliasExesBuffer,
size_t& writtenOrNeeded)
{
// Ensure output variables are initialized.
writtenOrNeeded = 0;
if (aliasExesBuffer.has_value() && aliasExesBuffer->size() > 0)
{
til::at(*aliasExesBuffer, 0) = UNICODE_NULL;
}
LPWSTR AliasExesBufferPtrW = aliasExesBuffer.has_value() ? aliasExesBuffer->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 > aliasExesBuffer->size());
size_t cchRemaining;
RETURN_IF_FAILED(SizeTSub(aliasExesBuffer->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<char> 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)
{
til::at(aliasExes, 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<wchar_t[]> targetBuffer = std::make_unique<wchar_t[]>(bufferNeeded);
RETURN_IF_NULL_ALLOC(targetBuffer);
// Call the Unicode version of this method
size_t bufferWritten;
RETURN_IF_FAILED(GetConsoleAliasExesWImplHelper(gsl::span<wchar_t>(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() > 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<wchar_t> 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<std::wstring> Alias::s_Tokenize(const std::wstring& str)
{
std::deque<std::wstring> 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<std::wstring>& 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<std::wstring>& 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<ULONG>(targetText.size() * sizeof(wchar_t));
// Return lines info.
lines = gsl::narrow<DWORD>(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