// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "precomp.h" #include "_stream.h" #include "stream.h" #include "dbcs.h" #include "handle.h" #include "misc.h" #include "readDataRaw.hpp" #include "ApiRoutines.h" #include "../types/inc/GlyphWidth.hpp" #include "..\interactivity\inc\ServiceLocator.hpp" #pragma hdrstop using Microsoft::Console::Interactivity::ServiceLocator; // Routine Description: // - This routine is used in stream input. It gets input and filters it for unicode characters. // Arguments: // - pInputBuffer - The InputBuffer to read from // - pwchOut - On a successful read, the char data read // - Wait - true if a waited read should be performed // - pCommandLineEditingKeys - if present, arrow keys will be // returned. on output, if true, pwchOut contains virtual key code for // arrow key. // - pPopupKeys - if present, arrow keys will be // returned. on output, if true, pwchOut contains virtual key code for // arrow key. // Return Value: // - STATUS_SUCCESS on success or a relevant error code on failure. [[nodiscard]] NTSTATUS GetChar(_Inout_ InputBuffer* const pInputBuffer, _Out_ wchar_t* const pwchOut, const bool Wait, _Out_opt_ bool* const pCommandLineEditingKeys, _Out_opt_ bool* const pPopupKeys, _Out_opt_ DWORD* const pdwKeyState) noexcept { if (nullptr != pCommandLineEditingKeys) { *pCommandLineEditingKeys = false; } if (nullptr != pPopupKeys) { *pPopupKeys = false; } if (nullptr != pdwKeyState) { *pdwKeyState = 0; } NTSTATUS Status; for (;;) { std::unique_ptr inputEvent; Status = pInputBuffer->Read(inputEvent, false, // peek Wait, true, // unicode true); // stream if (!NT_SUCCESS(Status)) { return Status; } else if (inputEvent.get() == nullptr) { FAIL_FAST_IF(Wait); return STATUS_UNSUCCESSFUL; } if (inputEvent->EventType() == InputEventType::KeyEvent) { std::unique_ptr keyEvent = std::unique_ptr(static_cast(inputEvent.release())); bool commandLineEditKey = false; if (pCommandLineEditingKeys) { commandLineEditKey = keyEvent->IsCommandLineEditingKey(); } else if (pPopupKeys) { commandLineEditKey = keyEvent->IsPopupKey(); } if (pdwKeyState) { *pdwKeyState = keyEvent->GetActiveModifierKeys(); } if (keyEvent->GetCharData() != 0 && !commandLineEditKey) { // chars that are generated using alt + numpad if (!keyEvent->IsKeyDown() && keyEvent->GetVirtualKeyCode() == VK_MENU) { if (keyEvent->IsAltNumpadSet()) { if (HIBYTE(keyEvent->GetCharData())) { char chT[2] = { static_cast(HIBYTE(keyEvent->GetCharData())), static_cast(LOBYTE(keyEvent->GetCharData())), }; *pwchOut = CharToWchar(chT, 2); } else { // Because USER doesn't know our codepage, // it gives us the raw OEM char and we // convert it to a Unicode character. char chT = LOBYTE(keyEvent->GetCharData()); *pwchOut = CharToWchar(&chT, 1); } } else { *pwchOut = keyEvent->GetCharData(); } return STATUS_SUCCESS; } // Ignore Escape and Newline chars else if (keyEvent->IsKeyDown() && (WI_IsFlagSet(pInputBuffer->InputMode, ENABLE_VIRTUAL_TERMINAL_INPUT) || (keyEvent->GetVirtualKeyCode() != VK_ESCAPE && keyEvent->GetCharData() != UNICODE_LINEFEED))) { *pwchOut = keyEvent->GetCharData(); return STATUS_SUCCESS; } } if (keyEvent->IsKeyDown()) { if (pCommandLineEditingKeys && commandLineEditKey) { *pCommandLineEditingKeys = true; *pwchOut = static_cast(keyEvent->GetVirtualKeyCode()); return STATUS_SUCCESS; } else if (pPopupKeys && commandLineEditKey) { *pPopupKeys = true; *pwchOut = static_cast(keyEvent->GetVirtualKeyCode()); return STATUS_SUCCESS; } else { const short zeroVkeyData = ServiceLocator::LocateInputServices()->VkKeyScanW(0); const byte zeroVKey = LOBYTE(zeroVkeyData); const byte zeroControlKeyState = HIBYTE(zeroVkeyData); try { // Convert real Windows NT modifier bit into bizarre Console bits std::unordered_set consoleModKeyState = FromVkKeyScan(zeroControlKeyState); if (zeroVKey == keyEvent->GetVirtualKeyCode() && keyEvent->DoActiveModifierKeysMatch(consoleModKeyState)) { // This really is the character 0x0000 *pwchOut = keyEvent->GetCharData(); return STATUS_SUCCESS; } } catch (...) { LOG_HR(wil::ResultFromCaughtException()); } } } } } } // Routine Description: // - This routine returns the total number of screen spaces the characters up to the specified character take up. size_t RetrieveTotalNumberOfSpaces(const SHORT sOriginalCursorPositionX, _In_reads_(ulCurrentPosition) const WCHAR* const pwchBuffer, _In_ size_t ulCurrentPosition) { SHORT XPosition = sOriginalCursorPositionX; size_t NumSpaces = 0; for (size_t i = 0; i < ulCurrentPosition; i++) { WCHAR const Char = pwchBuffer[i]; size_t NumSpacesForChar; if (Char == UNICODE_TAB) { NumSpacesForChar = NUMBER_OF_SPACES_IN_TAB(XPosition); } else if (IS_CONTROL_CHAR(Char)) { NumSpacesForChar = 2; } else if (IsGlyphFullWidth(Char)) { NumSpacesForChar = 2; } else { NumSpacesForChar = 1; } XPosition = (SHORT)(XPosition + NumSpacesForChar); NumSpaces += NumSpacesForChar; } return NumSpaces; } // Routine Description: // - This routine returns the number of screen spaces the specified character takes up. size_t RetrieveNumberOfSpaces(_In_ SHORT sOriginalCursorPositionX, _In_reads_(ulCurrentPosition + 1) const WCHAR* const pwchBuffer, _In_ size_t ulCurrentPosition) { WCHAR Char = pwchBuffer[ulCurrentPosition]; if (Char == UNICODE_TAB) { size_t NumSpaces = 0; SHORT XPosition = sOriginalCursorPositionX; for (size_t i = 0; i <= ulCurrentPosition; i++) { Char = pwchBuffer[i]; if (Char == UNICODE_TAB) { NumSpaces = NUMBER_OF_SPACES_IN_TAB(XPosition); } else if (IS_CONTROL_CHAR(Char)) { NumSpaces = 2; } else if (IsGlyphFullWidth(Char)) { NumSpaces = 2; } else { NumSpaces = 1; } XPosition = (SHORT)(XPosition + NumSpaces); } return NumSpaces; } else if (IS_CONTROL_CHAR(Char)) { return 2; } else if (IsGlyphFullWidth(Char)) { return 2; } else { return 1; } } // Routine Description: // - if we have leftover input, copy as much fits into the user's // buffer and return. we may have multi line input, if a macro // has been defined that contains the $T character. // Arguments: // - inputBuffer - Pointer to input buffer to read from. // - buffer - buffer to place read char data into // - bytesRead - number of bytes read and filled into the buffer // - readHandleState - input read handle data associated with this read operation // - unicode - true if read should be unicode, false otherwise // Return Value: // - STATUS_NO_MEMORY in low memory situation // - other relevant NTSTATUS codes [[nodiscard]] static NTSTATUS _ReadPendingInput(InputBuffer& inputBuffer, gsl::span buffer, size_t& bytesRead, INPUT_READ_HANDLE_DATA& readHandleState, const bool unicode) { // TODO: MSFT: 18047766 - Correct this method to not play byte counting games. BOOL fAddDbcsLead = FALSE; size_t NumToWrite = 0; size_t NumToBytes = 0; wchar_t* pBuffer = reinterpret_cast(buffer.data()); size_t bufferRemaining = buffer.size_bytes(); bytesRead = 0; if (buffer.size_bytes() < sizeof(wchar_t)) { return STATUS_BUFFER_TOO_SMALL; } const auto pending = readHandleState.GetPendingInput(); size_t pendingBytes = pending.size() * sizeof(wchar_t); auto Tmp = pending.cbegin(); if (readHandleState.IsMultilineInput()) { if (!unicode) { if (inputBuffer.IsReadPartialByteSequenceAvailable()) { std::unique_ptr event = inputBuffer.FetchReadPartialByteSequence(false); const KeyEvent* const pKeyEvent = static_cast(event.get()); *pBuffer = static_cast(pKeyEvent->GetCharData()); ++pBuffer; bufferRemaining -= sizeof(wchar_t); pendingBytes -= sizeof(wchar_t); fAddDbcsLead = TRUE; } if (pendingBytes == 0 || bufferRemaining == 0) { readHandleState.CompletePending(); bytesRead = 1; return STATUS_SUCCESS; } else { for (NumToWrite = 0, Tmp = pending.cbegin(), NumToBytes = 0; NumToBytes < pendingBytes && NumToBytes < bufferRemaining / sizeof(wchar_t) && *Tmp != UNICODE_LINEFEED; Tmp++, NumToWrite += sizeof(wchar_t)) { NumToBytes += IsGlyphFullWidth(*Tmp) ? 2 : 1; } } } NumToWrite = 0; Tmp = pending.cbegin(); while (NumToWrite < pendingBytes && *Tmp != UNICODE_LINEFEED) { ++Tmp; NumToWrite += sizeof(wchar_t); } NumToWrite += sizeof(wchar_t); if (NumToWrite > bufferRemaining) { NumToWrite = bufferRemaining; } } else { if (!unicode) { if (inputBuffer.IsReadPartialByteSequenceAvailable()) { std::unique_ptr event = inputBuffer.FetchReadPartialByteSequence(false); const KeyEvent* const pKeyEvent = static_cast(event.get()); *pBuffer = static_cast(pKeyEvent->GetCharData()); ++pBuffer; bufferRemaining -= sizeof(wchar_t); pendingBytes -= sizeof(wchar_t); fAddDbcsLead = TRUE; } if (pendingBytes == 0) { readHandleState.CompletePending(); bytesRead = 1; return STATUS_SUCCESS; } else { for (NumToWrite = 0, Tmp = pending.cbegin(), NumToBytes = 0; NumToBytes < pendingBytes && NumToBytes < bufferRemaining / sizeof(wchar_t); Tmp++, NumToWrite += sizeof(wchar_t)) { NumToBytes += IsGlyphFullWidth(*Tmp) ? 2 : 1; } } } NumToWrite = (bufferRemaining < pendingBytes) ? bufferRemaining : pendingBytes; } memmove(pBuffer, pending.data(), NumToWrite); pendingBytes -= NumToWrite; if (pendingBytes != 0) { std::wstring_view remainingPending{ pending.data() + (NumToWrite / sizeof(wchar_t)), pendingBytes / sizeof(wchar_t) }; readHandleState.UpdatePending(remainingPending); } else { readHandleState.CompletePending(); } if (!unicode) { // if ansi, translate string. we allocated the capture buffer // large enough to handle the translated string. std::unique_ptr tempBuffer = std::make_unique(NumToBytes); std::unique_ptr partialEvent; NumToWrite = TranslateUnicodeToOem(pBuffer, gsl::narrow(NumToWrite / sizeof(wchar_t)), tempBuffer.get(), gsl::narrow(NumToBytes), partialEvent); if (partialEvent.get()) { inputBuffer.StoreReadPartialByteSequence(std::move(partialEvent)); } // clang-format off #pragma prefast(suppress: __WARNING_POTENTIAL_BUFFER_OVERFLOW_HIGH_PRIORITY, "This access is fine but prefast can't follow it, evidently") // clang-format on memmove(pBuffer, tempBuffer.get(), NumToWrite); if (fAddDbcsLead) { NumToWrite++; } } bytesRead = NumToWrite; return STATUS_SUCCESS; } // Routine Description: // - read in characters until the buffer is full or return is read. // since we may wait inside this loop, store all important variables // in the read data structure. if we do wait, a read data structure // will be allocated from the heap and its pointer will be stored // in the wait block. the CookedReadData will be copied into the // structure. the data is freed when the read is completed. // Arguments: // - inputBuffer - input buffer to read data from // - processData - process handle of process making read request // - buffer - buffer to place read char data // - bytesRead - on output, the number of bytes read into pwchBuffer // - controlKeyState - set by a cooked read // - initialData - text of initial data found in the read message // - ctrlWakeupMask - used by COOKED_READ_DATA // - readHandleState - input read handle data associated with this read operation // - exeName - name of the exe requesting the read // - unicode - true if read should be unicode, false otherwise // - waiter - If a wait is necessary this will contain the wait // object on output // Return Value: // - STATUS_UNSUCCESSFUL if not able to access current screen buffer // - STATUS_NO_MEMORY in low memory situation // - other relevant HRESULT codes [[nodiscard]] static HRESULT _ReadLineInput(InputBuffer& inputBuffer, const HANDLE processData, gsl::span buffer, size_t& bytesRead, DWORD& controlKeyState, const std::string_view initialData, const DWORD ctrlWakeupMask, INPUT_READ_HANDLE_DATA& readHandleState, const std::wstring_view exeName, const bool unicode, std::unique_ptr& waiter) noexcept { CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); RETURN_HR_IF(E_FAIL, !gci.HasActiveOutputBuffer()); SCREEN_INFORMATION& screenInfo = gci.GetActiveOutputBuffer(); CommandHistory* const pCommandHistory = CommandHistory::s_Find(processData); try { auto cookedReadData = std::make_unique(&inputBuffer, // pInputBuffer &readHandleState, // pInputReadHandleData screenInfo, // pScreenInfo buffer.size_bytes(), // UserBufferSize reinterpret_cast(buffer.data()), // UserBuffer ctrlWakeupMask, // CtrlWakeupMask pCommandHistory, // CommandHistory exeName, // exe name initialData); gci.SetCookedReadData(cookedReadData.get()); bytesRead = buffer.size_bytes(); // This parameter on the way in is the size to read, on the way out, it will be updated to what is actually read. if (CONSOLE_STATUS_WAIT == cookedReadData->Read(unicode, bytesRead, controlKeyState)) { // memory will be cleaned up by wait queue waiter.reset(cookedReadData.release()); } else { gci.SetCookedReadData(nullptr); } } CATCH_RETURN(); return S_OK; } // Routine Description: // - Character (raw) mode. Read at least one character in. After one // character has been read, get any more available characters and // return. The first call to GetChar may block. If we do wait, a read // data structure will be allocated from the heap and its pointer will // be stored in the wait block. The RawReadData will be copied into // the structure. The data is freed when the read is completed. // Arguments: // - inputBuffer - input buffer to read data from // - buffer - on output, the amount of data read, in bytes // - bytesRead - number of bytes read and placed into buffer // - readHandleState - input read handle data associated with this read operation // - unicode - true if read should be unicode, false otherwise // - waiter - if a wait is necessary, on output this will contain // the associated wait object // Return Value: // - CONSOLE_STATUS_WAIT if a wait is necessary. ppWaiter will be // populated. // - STATUS_SUCCESS on success // - Other NTSTATUS codes as necessary [[nodiscard]] static NTSTATUS _ReadCharacterInput(InputBuffer& inputBuffer, gsl::span buffer, size_t& bytesRead, INPUT_READ_HANDLE_DATA& readHandleState, const bool unicode, std::unique_ptr& waiter) { size_t NumToWrite = 0; bool addDbcsLead = false; NTSTATUS Status = STATUS_SUCCESS; wchar_t* pBuffer = reinterpret_cast(buffer.data()); size_t bufferRemaining = buffer.size_bytes(); bytesRead = 0; if (buffer.size() < 1) { return STATUS_BUFFER_TOO_SMALL; } if (bytesRead < bufferRemaining) { wchar_t* pwchBufferTmp = pBuffer; NumToWrite = 0; if (!unicode && inputBuffer.IsReadPartialByteSequenceAvailable()) { std::unique_ptr event = inputBuffer.FetchReadPartialByteSequence(false); const KeyEvent* const pKeyEvent = static_cast(event.get()); *pBuffer = static_cast(pKeyEvent->GetCharData()); ++pBuffer; bufferRemaining -= sizeof(wchar_t); addDbcsLead = true; if (bufferRemaining == 0) { bytesRead = 1; return STATUS_SUCCESS; } } else { Status = GetChar(&inputBuffer, pBuffer, true, nullptr, nullptr, nullptr); } if (Status == CONSOLE_STATUS_WAIT) { waiter = std::make_unique(&inputBuffer, &readHandleState, gsl::narrow(buffer.size_bytes()), reinterpret_cast(buffer.data())); } if (!NT_SUCCESS(Status)) { bytesRead = 0; return Status; } if (!addDbcsLead) { bytesRead += IsGlyphFullWidth(*pBuffer) ? 2 : 1; NumToWrite += sizeof(wchar_t); pBuffer++; } while (NumToWrite < static_cast(bufferRemaining)) { Status = GetChar(&inputBuffer, pBuffer, false, nullptr, nullptr, nullptr); if (!NT_SUCCESS(Status)) { break; } bytesRead += IsGlyphFullWidth(*pBuffer) ? 2 : 1; NumToWrite += sizeof(wchar_t); pBuffer++; } // if ansi, translate string. we allocated the capture buffer large enough to handle the translated string. if (!unicode) { std::unique_ptr tempBuffer; try { tempBuffer = std::make_unique(bytesRead); } catch (...) { return STATUS_NO_MEMORY; } pBuffer = pwchBufferTmp; std::unique_ptr partialEvent; bytesRead = TranslateUnicodeToOem(pBuffer, gsl::narrow(NumToWrite / sizeof(wchar_t)), tempBuffer.get(), gsl::narrow(bytesRead), partialEvent); if (partialEvent.get()) { inputBuffer.StoreReadPartialByteSequence(std::move(partialEvent)); } #pragma prefast(suppress : 26053 26015, "PREfast claims read overflow. *pReadByteCount is the exact size of tempBuffer as allocated above.") memmove(pBuffer, tempBuffer.get(), bytesRead); if (addDbcsLead) { ++bytesRead; } } else { // We always return the byte count for A & W modes, so in // the Unicode case where we didn't translate back, set // the return to the byte count that we assembled while // pulling characters from the internal buffers. bytesRead = NumToWrite; } } return STATUS_SUCCESS; } // Routine Description: // - This routine reads in characters for stream input and does the // required processing based on the input mode (line, char, echo). // - This routine returns UNICODE characters. // Arguments: // - inputBuffer - Pointer to input buffer to read from. // - processData - process handle of process making read request // - buffer - buffer to place read char data into // - bytesRead - the length of data placed in buffer. Measured in bytes. // - controlKeyState - set by a cooked read // - initialData - text of initial data found in the read message // - ctrlWakeupMask - used by COOKED_READ_DATA // - readHandleState - read handle data associated with this read // - exeName- name of the exe requesting the read // - unicode - true for a unicode read, false for ascii // - waiter - If a wait is necessary this will contain the wait // object on output // Return Value: // - STATUS_BUFFER_TOO_SMALL if pdwNumBytes is too small to store char // data. // - CONSOLE_STATUS_WAIT if a wait is necessary. ppWaiter will be // populated. // - STATUS_SUCCESS on success // - Other NSTATUS codes as necessary [[nodiscard]] NTSTATUS DoReadConsole(InputBuffer& inputBuffer, const HANDLE processData, gsl::span buffer, size_t& bytesRead, ULONG& controlKeyState, const std::string_view initialData, const DWORD ctrlWakeupMask, INPUT_READ_HANDLE_DATA& readHandleState, const std::wstring_view exeName, const bool unicode, std::unique_ptr& waiter) noexcept { try { LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); waiter.reset(); bytesRead = 0; if (buffer.size() < 1) { return STATUS_BUFFER_TOO_SMALL; } const size_t OutputBufferSize = buffer.size_bytes(); if (readHandleState.IsInputPending()) { return _ReadPendingInput(inputBuffer, buffer, bytesRead, readHandleState, unicode); } else if (WI_IsFlagSet(inputBuffer.InputMode, ENABLE_LINE_INPUT)) { return NTSTATUS_FROM_HRESULT(_ReadLineInput(inputBuffer, processData, buffer, bytesRead, controlKeyState, initialData, ctrlWakeupMask, readHandleState, exeName, unicode, waiter)); } else { return _ReadCharacterInput(inputBuffer, buffer, bytesRead, readHandleState, unicode, waiter); } } CATCH_RETURN(); } [[nodiscard]] HRESULT ApiRoutines::ReadConsoleAImpl(IConsoleInputObject& context, gsl::span buffer, size_t& written, std::unique_ptr& waiter, const std::string_view initialData, const std::wstring_view exeName, INPUT_READ_HANDLE_DATA& readHandleState, const HANDLE clientHandle, const DWORD controlWakeupMask, DWORD& controlKeyState) noexcept { try { return HRESULT_FROM_NT(DoReadConsole(context, clientHandle, buffer, written, controlKeyState, initialData, controlWakeupMask, readHandleState, exeName, false, waiter)); } CATCH_RETURN(); } [[nodiscard]] HRESULT ApiRoutines::ReadConsoleWImpl(IConsoleInputObject& context, gsl::span buffer, size_t& written, std::unique_ptr& waiter, const std::string_view initialData, const std::wstring_view exeName, INPUT_READ_HANDLE_DATA& readHandleState, const HANDLE clientHandle, const DWORD controlWakeupMask, DWORD& controlKeyState) noexcept { try { return HRESULT_FROM_NT(DoReadConsole(context, clientHandle, buffer, written, controlKeyState, initialData, controlWakeupMask, readHandleState, exeName, true, waiter)); } CATCH_RETURN(); } void UnblockWriteConsole(const DWORD dwReason) { CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); gci.Flags &= ~dwReason; if (WI_AreAllFlagsClear(gci.Flags, (CONSOLE_SUSPENDED | CONSOLE_SELECTING | CONSOLE_SCROLLBAR_TRACKING))) { // There is no longer any reason to suspend output, so unblock it. gci.OutputQueue.NotifyWaiters(true); } }