// Clip Library // Copyright (C) 2015-2024 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. #include "clip_win.h" #include "clip.h" #include "clip_lock_impl.h" #include #include #include #include #include namespace clip { namespace { // Data type used as header for custom formats to indicate the exact // size of the user custom data. This is necessary because it looks // like GlobalSize() might not return the exact size, but a greater // value. typedef uint64_t CustomSizeT; class Hglobal { public: Hglobal() : m_handle(nullptr) { } explicit Hglobal(HGLOBAL handle) : m_handle(handle) { } explicit Hglobal(size_t len) : m_handle(GlobalAlloc(GHND, len)) { } ~Hglobal() { if (m_handle) GlobalFree(m_handle); } void release() { m_handle = nullptr; } operator HGLOBAL() { return m_handle; } private: HGLOBAL m_handle; }; // From: https://issues.chromium.org/issues/40080988#comment8 // // "Adds impersonation of the anonymous token around calls to the // CloseClipboard() system call. On Windows 8+ the win32k driver // captures the access token of the caller and makes it available to // other users on the desktop through the system call // GetClipboardAccessToken(). This introduces a risk of privilege // escalation in sandboxed processes. By performing the // impersonation then whenever Chrome writes data to the clipboard // only the anonymous token is available." // class AnonymousTokenImpersonator { public: AnonymousTokenImpersonator() : m_must_revert(ImpersonateAnonymousToken(GetCurrentThread())) {} ~AnonymousTokenImpersonator() { if (m_must_revert) RevertToSelf(); } private: const bool m_must_revert; }; } // anonymous namespace lock::impl::impl(void* hwnd) : m_locked(false) { for (int i=0; i<5; ++i) { if (OpenClipboard((HWND)hwnd)) { m_locked = true; break; } Sleep(20); } if (!m_locked) { error_handler e = get_error_handler(); if (e) e(ErrorCode::CannotLock); } } lock::impl::~impl() { if (m_locked) { AnonymousTokenImpersonator guard; CloseClipboard(); } } bool lock::impl::clear() { return (EmptyClipboard() ? true: false); } bool lock::impl::is_convertible(format f) const { if (f == text_format()) { return (IsClipboardFormatAvailable(CF_TEXT) || IsClipboardFormatAvailable(CF_UNICODETEXT) || IsClipboardFormatAvailable(CF_OEMTEXT)); } #if CLIP_ENABLE_IMAGE else if (f == image_format()) { return (IsClipboardFormatAvailable(CF_DIB) || win::wic_image_format_available(nullptr) != nullptr); } #endif // CLIP_ENABLE_IMAGE else return IsClipboardFormatAvailable(f); } bool lock::impl::set_data(format f, const char* buf, size_t len) { bool result = false; if (f == text_format()) { if (len > 0) { int reqsize = MultiByteToWideChar(CP_UTF8, 0, buf, len, NULL, 0); if (reqsize > 0) { ++reqsize; Hglobal hglobal(sizeof(WCHAR)*reqsize); LPWSTR lpstr = static_cast(GlobalLock(hglobal)); MultiByteToWideChar(CP_UTF8, 0, buf, len, lpstr, reqsize); GlobalUnlock(hglobal); result = (SetClipboardData(CF_UNICODETEXT, hglobal)) ? true: false; if (result) hglobal.release(); } } } else { Hglobal hglobal(len+sizeof(CustomSizeT)); if (hglobal) { auto dst = (uint8_t*)GlobalLock(hglobal); if (dst) { *((CustomSizeT*)dst) = len; memcpy(dst+sizeof(CustomSizeT), buf, len); GlobalUnlock(hglobal); result = (SetClipboardData(f, hglobal) ? true: false); if (result) hglobal.release(); } } } return result; } bool lock::impl::get_data(format f, char* buf, size_t len) const { assert(buf); if (!buf || !is_convertible(f)) return false; bool result = false; if (f == text_format()) { if (IsClipboardFormatAvailable(CF_UNICODETEXT)) { HGLOBAL hglobal = GetClipboardData(CF_UNICODETEXT); if (hglobal) { LPWSTR lpstr = static_cast(GlobalLock(hglobal)); if (lpstr) { size_t reqsize = WideCharToMultiByte(CP_UTF8, 0, lpstr, -1, nullptr, 0, nullptr, nullptr); assert(reqsize <= len); if (reqsize <= len) { WideCharToMultiByte(CP_UTF8, 0, lpstr, -1, buf, reqsize, nullptr, nullptr); result = true; } GlobalUnlock(hglobal); } } } else if (IsClipboardFormatAvailable(CF_TEXT)) { HGLOBAL hglobal = GetClipboardData(CF_TEXT); if (hglobal) { LPSTR lpstr = static_cast(GlobalLock(hglobal)); if (lpstr) { // TODO check length memcpy(buf, lpstr, len); result = true; GlobalUnlock(hglobal); } } } } else { if (IsClipboardFormatAvailable(f)) { HGLOBAL hglobal = GetClipboardData(f); if (hglobal) { const SIZE_T total_size = GlobalSize(hglobal); auto ptr = (const uint8_t*)GlobalLock(hglobal); if (ptr) { CustomSizeT reqsize = *((CustomSizeT*)ptr); // If the registered length of data in the first CustomSizeT // number of bytes of the hglobal data is greater than the // GlobalSize(hglobal), something is wrong, it should not // happen. assert(reqsize <= total_size); if (reqsize > total_size) reqsize = total_size - sizeof(CustomSizeT); if (reqsize <= len) { memcpy(buf, ptr+sizeof(CustomSizeT), reqsize); result = true; } GlobalUnlock(hglobal); } } } } return result; } size_t lock::impl::get_data_length(format f) const { size_t len = 0; if (f == text_format()) { if (IsClipboardFormatAvailable(CF_UNICODETEXT)) { HGLOBAL hglobal = GetClipboardData(CF_UNICODETEXT); if (hglobal) { LPWSTR lpstr = static_cast(GlobalLock(hglobal)); if (lpstr) { len = WideCharToMultiByte(CP_UTF8, 0, lpstr, -1, nullptr, 0, nullptr, nullptr); GlobalUnlock(hglobal); } } } else if (IsClipboardFormatAvailable(CF_TEXT)) { HGLOBAL hglobal = GetClipboardData(CF_TEXT); if (hglobal) { LPSTR lpstr = (LPSTR)GlobalLock(hglobal); if (lpstr) { len = strlen(lpstr) + 1; GlobalUnlock(hglobal); } } } } else if (f != empty_format()) { if (IsClipboardFormatAvailable(f)) { HGLOBAL hglobal = GetClipboardData(f); if (hglobal) { const SIZE_T total_size = GlobalSize(hglobal); auto ptr = (const uint8_t*)GlobalLock(hglobal); if (ptr) { len = *((CustomSizeT*)ptr); assert(len <= total_size); if (len > total_size) len = total_size - sizeof(CustomSizeT); GlobalUnlock(hglobal); } } } } return len; } #if CLIP_ENABLE_LIST_FORMATS std::vector lock::impl::list_formats() const { static const char* standard_formats[CF_MAX] = { "", "CF_TEXT", "CF_BITMAP", "CF_METAFILEPICT", "CF_SYLK", "CF_DIF", "CF_TIFF", "CF_OEMTEXT", "CF_DIB", "CF_PALETTE", "CF_PENDATA", "CF_RIFF", "CF_WAVE", "CF_UNICODETEXT", "CF_ENHMETAFILE", "CF_HDROP", "CF_LOCALE", "CF_DIBV5" }; std::vector formats; std::vector format_name(512); formats.reserve(CountClipboardFormats()); UINT format_id = EnumClipboardFormats(0); while (format_id != 0) { if (format_id >= CF_TEXT && format_id < CF_MAX) { // Standard clipboard format formats.emplace_back(format_id, standard_formats[format_id]); } // Get user-defined format name else { int size = GetClipboardFormatNameA( format_id, format_name.data(), format_name.size()); formats.emplace_back(format_id, std::string(format_name.data(), size)); } format_id = EnumClipboardFormats(format_id); } return formats; } #endif // CLIP_ENABLE_LIST_FORMATS #if CLIP_ENABLE_IMAGE bool lock::impl::set_image(const image& image) { const image_spec& spec = image.spec(); // Add the PNG clipboard format for images with alpha channel // (useful to communicate with some Windows programs that only use // alpha data from PNG clipboard format) if (spec.bits_per_pixel == 32 && spec.alpha_mask) { UINT png_format = RegisterClipboardFormatA("PNG"); if (png_format) { Hglobal png_handle(win::write_png(image)); if (png_handle) SetClipboardData(png_format, png_handle); } } Hglobal hmem(clip::win::create_dibv5(image)); if (!hmem) return false; SetClipboardData(CF_DIBV5, hmem); return true; } bool lock::impl::get_image(image& output_img) const { // Tries to get the first image format that can be read using WIC // ("PNG", "JPG", "GIF", etc). UINT cbformat; if (auto read_img = win::wic_image_format_available(&cbformat)) { HANDLE handle = GetClipboardData(cbformat); if (handle) { size_t size = GlobalSize(handle); uint8_t* data = (uint8_t*)GlobalLock(handle); bool result = read_img(data, size, &output_img, nullptr); GlobalUnlock(handle); if (result) return true; } } // If we couldn't find any, we try to use the regular DIB format. win::BitmapInfo bi; return bi.to_image(output_img); } bool lock::impl::get_image_spec(image_spec& spec) const { UINT cbformat; if (auto read_img = win::wic_image_format_available(&cbformat)) { HANDLE handle = GetClipboardData(cbformat); if (handle) { size_t size = GlobalSize(handle); uint8_t* data = (uint8_t*)GlobalLock(handle); bool result = read_img(data, size, nullptr, &spec); GlobalUnlock(handle); if (result) return true; } } win::BitmapInfo bi; if (!bi.is_valid()) return false; bi.fill_spec(spec); return true; } #endif // CLIP_ENABLE_IMAGE format register_format(const std::string& name) { int reqsize = 1+MultiByteToWideChar(CP_UTF8, 0, name.c_str(), name.size(), NULL, 0); std::vector buf(reqsize); MultiByteToWideChar(CP_UTF8, 0, name.c_str(), name.size(), &buf[0], reqsize); // From MSDN, registered clipboard formats are identified by values // in the range 0xC000 through 0xFFFF. return (format)RegisterClipboardFormatW(&buf[0]); } } // namespace clip