2019-05-03 00:29:04 +02:00
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
# include "precomp.h"
# include "history.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"
2020-11-25 22:02:10 +01:00
# include "../interactivity/inc/ServiceLocator.hpp"
2019-05-03 00:29:04 +02:00
# pragma hdrstop
2019-05-30 20:14:21 +02:00
using Microsoft : : Console : : Interactivity : : ServiceLocator ;
2019-05-03 00:29:04 +02:00
// I need to be a list because we rearrange elements inside to maintain a
// "least recently used" state. Doing many rearrangement operations with
// a list will maintain the iterator pointers as valid to the elements
// (where other collections like deque do not.)
// If CommandHistory::s_Allocate and friends stop shuffling elements
// for maintaining LRU, then this datatype can be changed.
std : : list < CommandHistory > CommandHistory : : s_historyLists ;
CommandHistory * CommandHistory : : s_Find ( const HANDLE processHandle )
{
for ( auto & historyList : s_historyLists )
{
if ( historyList . _processHandle = = processHandle )
{
FAIL_FAST_IF ( WI_IsFlagClear ( historyList . Flags , CLE_ALLOCATED ) ) ;
return & historyList ;
}
}
return nullptr ;
}
// Routine Description:
// - This routine marks the command history buffer freed.
// Arguments:
// - processHandle - handle to client process.
void CommandHistory : : s_Free ( const HANDLE processHandle )
{
CommandHistory * const History = CommandHistory : : s_Find ( processHandle ) ;
if ( History )
{
WI_ClearFlag ( History - > Flags , CLE_ALLOCATED ) ;
History - > _processHandle = nullptr ;
}
}
void CommandHistory : : s_ResizeAll ( const size_t commands )
{
CONSOLE_INFORMATION & gci = ServiceLocator : : LocateGlobals ( ) . getConsoleInformation ( ) ;
FAIL_FAST_IF ( commands > SHORT_MAX ) ;
gci . SetHistoryBufferSize ( gsl : : narrow < UINT > ( commands ) ) ;
for ( auto & historyList : s_historyLists )
{
historyList . Realloc ( commands ) ;
}
}
static bool CaseInsensitiveEquality ( wchar_t a , wchar_t b )
{
return : : towlower ( a ) = = : : towlower ( b ) ;
}
bool CommandHistory : : IsAppNameMatch ( const std : : wstring_view other ) const
{
return std : : equal ( _appName . cbegin ( ) , _appName . cend ( ) , other . cbegin ( ) , other . cend ( ) , CaseInsensitiveEquality ) ;
}
// Routine Description:
// - This routine is called when escape is entered or a command is added.
void CommandHistory : : _Reset ( )
{
LastDisplayed = gsl : : narrow < SHORT > ( _commands . size ( ) ) - 1 ;
WI_SetFlag ( Flags , CLE_RESET ) ;
}
2019-06-11 22:27:09 +02:00
[[nodiscard]] HRESULT CommandHistory : : Add ( const std : : wstring_view newCommand ,
const bool suppressDuplicates )
2019-05-03 00:29:04 +02:00
{
RETURN_HR_IF ( E_OUTOFMEMORY , _maxCommands = = 0 ) ;
FAIL_FAST_IF ( WI_IsFlagClear ( Flags , CLE_ALLOCATED ) ) ;
if ( newCommand . size ( ) = = 0 )
{
return S_OK ;
}
try
{
if ( _commands . size ( ) = = 0 | |
_commands . back ( ) . size ( ) ! = newCommand . size ( ) | |
2019-06-11 22:27:09 +02:00
! std : : equal ( _commands . back ( ) . cbegin ( ) ,
_commands . back ( ) . cbegin ( ) + newCommand . size ( ) ,
newCommand . cbegin ( ) ,
newCommand . cend ( ) ) )
2019-05-03 00:29:04 +02:00
{
std : : wstring reuse { } ;
if ( suppressDuplicates )
{
SHORT index ;
if ( FindMatchingCommand ( newCommand , LastDisplayed , index , CommandHistory : : MatchOptions : : ExactMatch ) )
{
reuse = Remove ( index ) ;
}
}
// find free record. if all records are used, free the lru one.
if ( ( SHORT ) _commands . size ( ) = = _maxCommands )
{
_commands . erase ( _commands . cbegin ( ) ) ;
// move LastDisplayed back one in order to stay synced with the
// command it referred to before erasing the lru one
- - LastDisplayed ;
}
// add newCommand to array
if ( ! reuse . empty ( ) )
{
_commands . emplace_back ( reuse ) ;
}
else
{
_commands . emplace_back ( newCommand ) ;
}
if ( LastDisplayed = = - 1 | |
_commands . at ( LastDisplayed ) . size ( ) ! = newCommand . size ( ) | |
2019-06-11 22:27:09 +02:00
! std : : equal ( _commands . at ( LastDisplayed ) . cbegin ( ) ,
_commands . at ( LastDisplayed ) . cbegin ( ) + newCommand . size ( ) ,
newCommand . cbegin ( ) ,
newCommand . cend ( ) ) )
2019-05-03 00:29:04 +02:00
{
_Reset ( ) ;
}
}
}
CATCH_RETURN ( ) ;
WI_SetFlag ( Flags , CLE_RESET ) ; // remember that we've returned a cmd
return S_OK ;
}
std : : wstring_view CommandHistory : : GetNth ( const SHORT index ) const
{
try
{
return _commands . at ( index ) ;
}
CATCH_LOG ( ) ;
return { } ;
}
2019-06-11 22:27:09 +02:00
[[nodiscard]] HRESULT CommandHistory : : RetrieveNth ( const SHORT index ,
gsl : : span < wchar_t > buffer ,
size_t & commandSize )
2019-05-03 00:29:04 +02:00
{
LastDisplayed = index ;
try
{
const auto & cmd = _commands . at ( index ) ;
if ( cmd . size ( ) > ( size_t ) buffer . size ( ) )
{
commandSize = buffer . size ( ) ; // room for CRLF?
}
else
{
commandSize = cmd . size ( ) ;
}
std : : copy_n ( cmd . cbegin ( ) , commandSize , buffer . begin ( ) ) ;
commandSize * = sizeof ( wchar_t ) ;
return S_OK ;
}
CATCH_RETURN ( ) ;
}
2019-06-11 22:27:09 +02:00
[[nodiscard]] HRESULT CommandHistory : : Retrieve ( const SearchDirection searchDirection ,
const gsl : : span < wchar_t > buffer ,
size_t & commandSize )
2019-05-03 00:29:04 +02:00
{
FAIL_FAST_IF ( ! ( WI_IsFlagSet ( Flags , CLE_ALLOCATED ) ) ) ;
if ( _commands . size ( ) = = 0 )
{
return E_FAIL ;
}
if ( _commands . size ( ) = = 1 )
{
LastDisplayed = 0 ;
}
else if ( searchDirection = = SearchDirection : : Previous )
{
// if this is the first time for this read that a command has
// been retrieved, return the current command. otherwise, return
// the previous command.
if ( WI_IsFlagSet ( Flags , CLE_RESET ) )
{
WI_ClearFlag ( Flags , CLE_RESET ) ;
}
else
{
_Prev ( LastDisplayed ) ;
}
}
else
{
_Next ( LastDisplayed ) ;
}
return RetrieveNth ( LastDisplayed , buffer , commandSize ) ;
}
std : : wstring_view CommandHistory : : GetLastCommand ( ) const
{
if ( _commands . size ( ) ! = 0 )
{
try
{
return _commands . at ( LastDisplayed ) ;
}
CATCH_LOG ( ) ;
}
return { } ;
}
void CommandHistory : : Empty ( )
{
_commands . clear ( ) ;
LastDisplayed = - 1 ;
2020-03-19 19:14:52 +01:00
WI_SetFlag ( Flags , CLE_RESET ) ;
2019-05-03 00:29:04 +02:00
}
bool CommandHistory : : AtFirstCommand ( ) const
{
if ( WI_IsFlagSet ( Flags , CLE_RESET ) )
{
return FALSE ;
}
SHORT i = ( SHORT ) ( LastDisplayed - 1 ) ;
if ( i = = - 1 )
{
i = ( ( SHORT ) _commands . size ( ) ) - 1 i16 ;
}
return ( i = = ( ( SHORT ) _commands . size ( ) ) - 1 i16 ) ;
}
bool CommandHistory : : AtLastCommand ( ) const
{
return LastDisplayed = = ( ( SHORT ) _commands . size ( ) ) - 1 i16 ;
}
void CommandHistory : : Realloc ( const size_t commands )
{
// To protect ourselves from overflow and general arithmetic errors, a limit of SHORT_MAX is put on the size of the command history.
if ( _maxCommands = = ( SHORT ) commands | | commands > SHORT_MAX )
{
return ;
}
const auto oldCommands = _commands ;
const auto newNumberOfCommands = gsl : : narrow < SHORT > ( std : : min ( _commands . size ( ) , commands ) ) ;
_commands . clear ( ) ;
for ( SHORT i = 0 ; i < newNumberOfCommands ; i + + )
{
_commands . emplace_back ( oldCommands [ i ] ) ;
}
WI_SetFlag ( Flags , CLE_RESET ) ;
LastDisplayed = gsl : : narrow < SHORT > ( _commands . size ( ) ) - 1 ;
_maxCommands = ( SHORT ) commands ;
}
void CommandHistory : : s_ReallocExeToFront ( const std : : wstring_view appName , const size_t commands )
{
for ( auto it = s_historyLists . begin ( ) ; it ! = s_historyLists . end ( ) ; it + + )
{
if ( WI_IsFlagSet ( it - > Flags , CLE_ALLOCATED ) & & it - > IsAppNameMatch ( appName ) )
{
CommandHistory backup = * it ;
backup . Realloc ( commands ) ;
s_historyLists . erase ( it ) ;
s_historyLists . push_front ( backup ) ;
return ;
}
}
}
CommandHistory * CommandHistory : : s_FindByExe ( const std : : wstring_view appName )
{
for ( auto & historyList : s_historyLists )
{
if ( WI_IsFlagSet ( historyList . Flags , CLE_ALLOCATED ) & & historyList . IsAppNameMatch ( appName ) )
{
return & historyList ;
}
}
return nullptr ;
}
size_t CommandHistory : : s_CountOfHistories ( )
{
return s_historyLists . size ( ) ;
}
// Routine Description:
// - This routine returns the LRU command history buffer, or the command history buffer that corresponds to the app name.
// Arguments:
// - Console - pointer to console.
// Return Value:
// - Pointer to command history buffer. if none are available, returns nullptr.
CommandHistory * CommandHistory : : s_Allocate ( const std : : wstring_view appName , const HANDLE processHandle )
{
CONSOLE_INFORMATION & gci = ServiceLocator : : LocateGlobals ( ) . getConsoleInformation ( ) ;
// Reuse a history buffer. The buffer must be !CLE_ALLOCATED.
// If possible, the buffer should have the same app name.
std : : optional < CommandHistory > BestCandidate ;
bool SameApp = false ;
for ( auto it = s_historyLists . cbegin ( ) ; it ! = s_historyLists . cend ( ) ; it + + )
{
if ( WI_IsFlagClear ( it - > Flags , CLE_ALLOCATED ) )
{
// use LRU history buffer with same app name
if ( it - > IsAppNameMatch ( appName ) )
{
BestCandidate = * it ;
SameApp = true ;
s_historyLists . erase ( it ) ;
break ;
}
}
}
// if there isn't a free buffer for the app name and the maximum number of
// command history buffers hasn't been allocated, allocate a new one.
if ( ! SameApp & & s_historyLists . size ( ) < gci . GetNumberOfHistoryBuffers ( ) )
{
CommandHistory History ;
History . _appName = appName ;
History . Flags = CLE_ALLOCATED ;
History . LastDisplayed = - 1 ;
History . _maxCommands = gsl : : narrow < SHORT > ( gci . GetHistoryBufferSize ( ) ) ;
History . _processHandle = processHandle ;
return & s_historyLists . emplace_front ( History ) ;
}
else if ( ! BestCandidate . has_value ( ) & & s_historyLists . size ( ) > 0 )
{
// If we have no candidate already and we need one, take the LRU (which is the back/last one) which isn't allocated.
for ( auto it = s_historyLists . crbegin ( ) ; it ! = s_historyLists . crend ( ) ; it + + )
{
if ( WI_IsFlagClear ( it - > Flags , CLE_ALLOCATED ) )
{
BestCandidate = * it ;
s_historyLists . erase ( std : : next ( it ) . base ( ) ) ; // trickery to turn reverse iterator into forward iterator for erase.
break ;
}
}
}
// If the app name doesn't match, copy in the new app name and free the old commands.
if ( BestCandidate . has_value ( ) )
{
if ( ! SameApp )
{
BestCandidate - > _commands . clear ( ) ;
BestCandidate - > LastDisplayed = - 1 ;
BestCandidate - > _appName = appName ;
}
BestCandidate - > _processHandle = processHandle ;
WI_SetFlag ( BestCandidate - > Flags , CLE_ALLOCATED ) ;
return & s_historyLists . emplace_front ( BestCandidate . value ( ) ) ;
}
return nullptr ;
}
size_t CommandHistory : : GetNumberOfCommands ( ) const
{
return _commands . size ( ) ;
}
void CommandHistory : : _Prev ( SHORT & ind ) const
{
if ( ind < = 0 )
{
ind = gsl : : narrow < SHORT > ( _commands . size ( ) ) ;
}
ind - - ;
}
void CommandHistory : : _Next ( SHORT & ind ) const
{
+ + ind ;
if ( ind > = ( SHORT ) _commands . size ( ) )
{
ind = 0 ;
}
}
void CommandHistory : : _Dec ( SHORT & ind ) const
{
if ( ind < = 0 )
{
ind = _maxCommands ;
}
ind - - ;
}
void CommandHistory : : _Inc ( SHORT & ind ) const
{
+ + ind ;
if ( ind > = _maxCommands )
{
ind = 0 ;
}
}
std : : wstring CommandHistory : : Remove ( const SHORT iDel )
{
SHORT iFirst = 0 ;
SHORT iLast = gsl : : narrow < SHORT > ( _commands . size ( ) - 1 ) ;
SHORT iDisp = LastDisplayed ;
if ( _commands . size ( ) = = 0 )
{
return { } ;
}
SHORT const nDel = iDel ;
if ( ( nDel < iFirst ) | | ( nDel > iLast ) )
{
return { } ;
}
if ( iDisp = = iDel )
{
LastDisplayed = - 1 ;
}
try
{
const auto str = _commands . at ( iDel ) ;
if ( iDel < iLast )
{
_commands . erase ( _commands . cbegin ( ) + iDel ) ;
if ( ( iDisp > iDel ) & & ( iDisp < = iLast ) )
{
_Dec ( iDisp ) ;
}
_Dec ( iLast ) ;
}
else if ( iFirst < = iDel )
{
_commands . erase ( _commands . cbegin ( ) + iDel ) ;
if ( ( iDisp > = iFirst ) & & ( iDisp < iDel ) )
{
_Inc ( iDisp ) ;
}
_Inc ( iFirst ) ;
}
LastDisplayed = iDisp ;
return str ;
}
CATCH_LOG ( ) ;
return { } ;
}
// Routine Description:
// - this routine finds the most recent command that starts with the letters already in the current command. it returns the array index (no mod needed).
2019-06-11 22:27:09 +02:00
[[nodiscard]] bool CommandHistory : : FindMatchingCommand ( const std : : wstring_view givenCommand ,
const SHORT startingIndex ,
SHORT & indexFound ,
const MatchOptions options )
2019-05-03 00:29:04 +02:00
{
indexFound = startingIndex ;
if ( _commands . size ( ) = = 0 )
{
return false ;
}
if ( WI_IsFlagClear ( options , MatchOptions : : JustLooking ) & & WI_IsFlagSet ( Flags , CLE_RESET ) )
{
WI_ClearFlag ( Flags , CLE_RESET ) ;
}
else
{
_Prev ( indexFound ) ;
}
if ( givenCommand . empty ( ) )
{
return true ;
}
try
{
for ( size_t i = 0 ; i < _commands . size ( ) ; i + + )
{
const auto & storedCommand = _commands . at ( indexFound ) ;
if ( ( WI_IsFlagClear ( options , MatchOptions : : ExactMatch ) & & ( givenCommand . size ( ) < = storedCommand . size ( ) ) ) | | ( givenCommand . size ( ) = = storedCommand . size ( ) ) )
{
2019-06-11 22:27:09 +02:00
if ( std : : equal ( storedCommand . begin ( ) ,
storedCommand . begin ( ) + givenCommand . size ( ) ,
givenCommand . begin ( ) ,
givenCommand . end ( ) ,
2019-05-03 00:29:04 +02:00
CaseInsensitiveEquality ) )
{
return true ;
}
}
_Prev ( indexFound ) ;
}
}
CATCH_LOG ( ) ;
return false ;
}
# ifdef UNIT_TESTING
void CommandHistory : : s_ClearHistoryListStorage ( )
{
s_historyLists . clear ( ) ;
}
# endif
// Routine Description:
// - swaps the locations of two history items
// Arguments:
// - indexA - index of one history item to swap
// - indexB - index of one history item to swap
void CommandHistory : : Swap ( const short indexA , const short indexB )
{
std : : swap ( _commands . at ( indexA ) , _commands . at ( indexB ) ) ;
}
// Routine Description:
// - Clears all command history for the given EXE name
// - Will convert input parameters and call the W version of this method
// Arguments:
// - exeName - The client EXE application attached to the host whose history we should clear
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT ApiRoutines : : ExpungeConsoleCommandHistoryAImpl ( const std : : string_view exeName ) noexcept
{
const CONSOLE_INFORMATION & gci = ServiceLocator : : LocateGlobals ( ) . getConsoleInformation ( ) ;
try
{
const auto exeNameW = ConvertToW ( gci . CP , exeName ) ;
return ExpungeConsoleCommandHistoryWImpl ( exeNameW ) ;
}
CATCH_RETURN ( ) ;
}
// Routine Description:
// - Clears all command history for the given EXE name
// Arguments:
// - exeName - The client EXE application attached to the host whose history we should clear
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT ApiRoutines : : ExpungeConsoleCommandHistoryWImpl ( const std : : wstring_view exeName ) noexcept
{
LockConsole ( ) ;
auto Unlock = wil : : scope_exit ( [ & ] { UnlockConsole ( ) ; } ) ;
try
{
const auto history = CommandHistory : : s_FindByExe ( exeName ) ;
if ( history )
{
history - > Empty ( ) ;
}
return S_OK ;
}
CATCH_RETURN ( ) ;
}
// Routine Description:
// - Sets the number of commands that will be stored in history for a given EXE name
// - Will convert input parameters and call the W version of this method
// Arguments:
// - exeName - A client EXE application attached to the host
// - numberOfCommands - Specifies the maximum length of the associated history buffer
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT ApiRoutines : : SetConsoleNumberOfCommandsAImpl ( const std : : string_view exeName ,
const size_t numberOfCommands ) noexcept
{
const CONSOLE_INFORMATION & gci = ServiceLocator : : LocateGlobals ( ) . getConsoleInformation ( ) ;
try
{
const auto exeNameW = ConvertToW ( gci . CP , exeName ) ;
return SetConsoleNumberOfCommandsWImpl ( exeNameW , numberOfCommands ) ;
}
CATCH_RETURN ( ) ;
}
// Routine Description:
// - Sets the number of commands that will be stored in history for a given EXE name
// Arguments:
// - exeName - A client EXE application attached to the host
// - numberOfCommands - Specifies the maximum length of the associated history buffer
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT ApiRoutines : : SetConsoleNumberOfCommandsWImpl ( const std : : wstring_view exeName ,
const size_t numberOfCommands ) noexcept
{
LockConsole ( ) ;
auto Unlock = wil : : scope_exit ( [ & ] { UnlockConsole ( ) ; } ) ;
try
{
CommandHistory : : s_ReallocExeToFront ( exeName , numberOfCommands ) ;
return S_OK ;
}
CATCH_RETURN ( ) ;
}
// Routine Description:
// - Retrieves the amount of space needed to retrieve all command history for a 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 (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.)
// - historyLength - Receives the length of buffer that would be required to retrieve all history for the given exe.
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT GetConsoleCommandHistoryLengthImplHelper ( const std : : wstring_view exeName ,
const bool countInUnicode ,
const UINT codepage ,
size_t & historyLength )
{
// Ensure output variables are initialized
historyLength = 0 ;
LockConsole ( ) ;
auto Unlock = wil : : scope_exit ( [ & ] { UnlockConsole ( ) ; } ) ;
CommandHistory * const pCommandHistory = CommandHistory : : s_FindByExe ( exeName ) ;
if ( nullptr ! = pCommandHistory )
{
size_t cchNeeded = 0 ;
// Every command history item is made of a string length followed by 1 null character.
size_t const cchNull = 1 ;
for ( SHORT i = 0 ; i < gsl : : narrow < SHORT > ( pCommandHistory - > GetNumberOfCommands ( ) ) ; i + + )
{
const auto command = pCommandHistory - > GetNth ( i ) ;
size_t cchCommand = command . size ( ) ;
// This is the proposed length of the whole string.
size_t cchProposed ;
RETURN_IF_FAILED ( SizeTAdd ( cchCommand , cchNull , & cchProposed ) ) ;
// If we're counting how much multibyte space will be needed, trial convert the command string before we add.
if ( ! countInUnicode )
{
cchCommand = GetALengthFromW ( codepage , command ) ;
}
// Accumulate the result
RETURN_IF_FAILED ( SizeTAdd ( cchNeeded , cchProposed , & cchNeeded ) ) ;
}
historyLength = cchNeeded ;
}
return S_OK ;
}
// Routine Description:
// - Retrieves the amount of space needed to retrieve all command history for a 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
// - length - Receives the length of buffer that would be required to retrieve all history for the given exe.
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT ApiRoutines : : GetConsoleCommandHistoryLengthAImpl ( const std : : string_view exeName ,
size_t & length ) noexcept
{
const CONSOLE_INFORMATION & gci = ServiceLocator : : LocateGlobals ( ) . getConsoleInformation ( ) ;
UINT const codepage = gci . CP ;
// Ensure output variables are initialized
length = 0 ;
LockConsole ( ) ;
auto Unlock = wil : : scope_exit ( [ & ] { UnlockConsole ( ) ; } ) ;
try
{
const auto exeNameW = ConvertToW ( codepage , exeName ) ;
return GetConsoleCommandHistoryLengthImplHelper ( exeNameW , false , codepage , length ) ;
}
CATCH_RETURN ( ) ;
}
// Routine Description:
// - Retrieves the amount of space needed to retrieve all command history for a given EXE name
// Arguments:
// - exeName - The client EXE application attached to the host whose set we should check
// - length - Receives the length of buffer that would be required to retrieve all history for the given exe.
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT ApiRoutines : : GetConsoleCommandHistoryLengthWImpl ( const std : : wstring_view exeName ,
size_t & length ) noexcept
{
LockConsole ( ) ;
auto Unlock = wil : : scope_exit ( [ & ] { UnlockConsole ( ) ; } ) ;
try
{
return GetConsoleCommandHistoryLengthImplHelper ( exeName , true , 0 , length ) ;
}
CATCH_RETURN ( ) ;
}
// Routine Description:
2019-05-29 23:27:30 +02:00
// - Retrieves the full command history for a given EXE name known to the console.
2019-05-03 00:29:04 +02:00
// - 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
// - historyBuffer - The target buffer for data we are attempting to retrieve. Optionally empty to retrieve needed space.
// - writtenOrNeeded - Will specify how many characters were written (if buffer is valid)
// or how many characters would have been consumed.
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT GetConsoleCommandHistoryWImplHelper ( const std : : wstring_view exeName ,
gsl : : span < wchar_t > historyBuffer ,
size_t & writtenOrNeeded )
{
// Ensure output variables are initialized
writtenOrNeeded = 0 ;
if ( historyBuffer . size ( ) > 0 )
{
2020-07-15 19:29:36 +02:00
til : : at ( historyBuffer , 0 ) = UNICODE_NULL ;
2019-05-03 00:29:04 +02:00
}
CommandHistory * const CommandHistory = CommandHistory : : s_FindByExe ( exeName ) ;
if ( nullptr ! = CommandHistory )
{
PWCHAR CommandBufferW = historyBuffer . data ( ) ;
size_t cchTotalLength = 0 ;
size_t const cchNull = 1 ;
for ( SHORT i = 0 ; i < gsl : : narrow < SHORT > ( CommandHistory - > GetNumberOfCommands ( ) ) ; i + + )
{
const auto command = CommandHistory - > GetNth ( i ) ;
size_t const cchCommand = command . size ( ) ;
size_t cchNeeded ;
RETURN_IF_FAILED ( SizeTAdd ( cchCommand , 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 ( historyBuffer . size ( ) > 0 )
{
// Calculate what the new total would be after we add what we need.
size_t cchNewTotal ;
RETURN_IF_FAILED ( SizeTAdd ( cchTotalLength , cchNeeded , & cchNewTotal ) ) ;
2020-07-14 20:30:59 +02:00
RETURN_HR_IF ( HRESULT_FROM_WIN32 ( ERROR_BUFFER_OVERFLOW ) , cchNewTotal > historyBuffer . size ( ) ) ;
2019-05-03 00:29:04 +02:00
size_t cchRemaining ;
RETURN_IF_FAILED ( SizeTSub ( historyBuffer . size ( ) ,
cchTotalLength ,
& cchRemaining ) ) ;
RETURN_IF_FAILED ( StringCchCopyNW ( CommandBufferW ,
cchRemaining ,
command . data ( ) ,
command . size ( ) ) ) ;
CommandBufferW + = cchNeeded ;
}
RETURN_IF_FAILED ( SizeTAdd ( cchTotalLength , cchNeeded , & cchTotalLength ) ) ;
}
writtenOrNeeded = cchTotalLength ;
}
return S_OK ;
}
// Routine Description:
2019-05-29 23:27:30 +02:00
// - Retrieves the full command history for a given EXE name known to the console.
2019-05-03 00:29:04 +02:00
// - Converts inputs from A to W, calls the W version of this method, and then converts the resulting text W to A.
// Arguments:
// - exeName - The client EXE application attached to the host whose set we should check
// - commandHistory - The target buffer for data we are attempting 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.
HRESULT ApiRoutines : : GetConsoleCommandHistoryAImpl ( const std : : string_view exeName ,
gsl : : span < char > commandHistory ,
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 ( commandHistory . size ( ) > 0 )
{
2020-07-15 19:29:36 +02:00
til : : at ( commandHistory , 0 ) = ANSI_NULL ;
2019-05-03 00:29:04 +02:00
}
LockConsole ( ) ;
auto Unlock = wil : : scope_exit ( [ & ] { UnlockConsole ( ) ; } ) ;
// Convert our input parameters to Unicode.
const auto exeNameW = ConvertToW ( codepage , exeName ) ;
// Figure out how big our temporary Unicode buffer must be to retrieve output
size_t bufferNeeded ;
RETURN_IF_FAILED ( GetConsoleCommandHistoryWImplHelper ( exeNameW , { } , 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 [ ] > buffer = std : : make_unique < wchar_t [ ] > ( bufferNeeded ) ;
RETURN_IF_NULL_ALLOC ( buffer ) ;
// Call the Unicode version of this method
size_t bufferWritten ;
RETURN_IF_FAILED ( GetConsoleCommandHistoryWImplHelper ( exeNameW , gsl : : span < wchar_t > ( buffer . get ( ) , bufferNeeded ) , bufferWritten ) ) ;
// Convert result to A
const auto converted = ConvertToA ( codepage , { buffer . get ( ) , bufferWritten } ) ;
// Copy safely to output buffer
// - CommandHistory are a series of null terminated strings. We cannot use a SafeString function to copy.
// So instead, validate and use raw memory copy.
2020-07-14 20:30:59 +02:00
RETURN_HR_IF ( HRESULT_FROM_WIN32 ( ERROR_BUFFER_OVERFLOW ) , converted . size ( ) > commandHistory . size ( ) ) ;
2019-05-03 00:29:04 +02:00
memcpy_s ( commandHistory . data ( ) , commandHistory . size ( ) , converted . data ( ) , converted . size ( ) ) ;
// And return the size copied.
written = converted . size ( ) ;
return S_OK ;
}
CATCH_RETURN ( ) ;
}
// Routine Description:
2019-05-29 23:27:30 +02:00
// - Retrieves the full command history for a given EXE name known to the console.
2019-05-03 00:29:04 +02:00
// - Converts inputs from A to W, calls the W version of this method, and then converts the resulting text W to A.
// Arguments:
// - exeName - The client EXE application attached to the host whose set we should check
// - commandHistory - The target buffer for data we are attempting 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.
HRESULT ApiRoutines : : GetConsoleCommandHistoryWImpl ( const std : : wstring_view exeName ,
gsl : : span < wchar_t > commandHistory ,
size_t & written ) noexcept
{
LockConsole ( ) ;
auto Unlock = wil : : scope_exit ( [ & ] { UnlockConsole ( ) ; } ) ;
try
{
return GetConsoleCommandHistoryWImplHelper ( exeName , commandHistory , written ) ;
}
CATCH_RETURN ( ) ;
}