/** * Looking Glass * Copyright © 2017-2026 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "CClipboardFiles.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { static constexpr uint64_t WINDOWS_EPOCH_TICKS = UINT64_C(116444736000000000); static constexpr size_t COPY_BUFFER_BYTES = KVMFR_CLIPBOARD_FILE_READ_BYTES; class CThreadImpersonation final { private: HANDLE m_previousToken = nullptr; bool m_active = false; DWORD m_error = ERROR_SUCCESS; void ClosePreviousToken() { if (m_previousToken) { CloseHandle(m_previousToken); m_previousToken = nullptr; } } bool RevertInternal() { if (!m_active) return true; const bool restored = m_previousToken ? SetThreadToken(nullptr, m_previousToken) != FALSE : RevertToSelf() != FALSE; if (restored) { m_active = false; ClosePreviousToken(); return true; } const DWORD error = GetLastError(); if (!m_previousToken && SetThreadToken(nullptr, nullptr)) { m_active = false; m_error = error; SetLastError(error); return false; } RevertToSelf(); TerminateProcess(GetCurrentProcess(), error ? error : ERROR_ACCESS_DENIED); std::terminate(); } public: explicit CThreadImpersonation(HANDLE token) { if (!token) return; if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &m_previousToken)) { const DWORD error = GetLastError(); if (error != ERROR_NO_TOKEN) { m_error = error; return; } SetLastError(ERROR_SUCCESS); } if (SetThreadToken(nullptr, token)) m_active = true; else { const DWORD error = GetLastError(); ClosePreviousToken(); m_error = error; SetLastError(error); } } ~CThreadImpersonation() { const DWORD error = GetLastError(); bool restored = true; if (m_active) restored = RevertInternal(); ClosePreviousToken(); if (restored) SetLastError(error); } bool Active() const { return m_active; } DWORD Error() const { return m_error; } bool Finish() { const DWORD error = GetLastError(); if (!RevertInternal()) return false; SetLastError(error); return true; } }; KVMFRClipboardFileError TokenError(DWORD error) { switch (error) { case ERROR_NOT_ENOUGH_MEMORY: case ERROR_OUTOFMEMORY: return KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY; default: return KVMFR_CLIPBOARD_FILE_ERROR_ACCESS; } } bool CaptureUserToken(HANDLE& token, KVMFRClipboardFileError& error, DWORD& winError) { token = nullptr; winError = ERROR_SUCCESS; HANDLE processToken = nullptr; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE, &processToken)) { winError = GetLastError(); error = TokenError(winError); return false; } const bool duplicated = DuplicateTokenEx(processToken, TOKEN_QUERY | TOKEN_IMPERSONATE, nullptr, SecurityImpersonation, TokenImpersonation, &token) != FALSE; const DWORD duplicateError = duplicated ? ERROR_SUCCESS : GetLastError(); CloseHandle(processToken); if (!duplicated) { winError = duplicateError; error = TokenError(duplicateError); return false; } return true; } uint64_t FileTimeToUnixNs(const FILETIME& time) { ULARGE_INTEGER value = {}; value.LowPart = time.dwLowDateTime; value.HighPart = time.dwHighDateTime; if (value.QuadPart <= WINDOWS_EPOCH_TICKS) return 0; const uint64_t ticks = value.QuadPart - WINDOWS_EPOCH_TICKS; return ticks <= (std::numeric_limits::max)() / 100U ? ticks * 100U : (std::numeric_limits::max)(); } FILETIME UnixNsToFileTime(uint64_t ns) { ULARGE_INTEGER value = {}; const uint64_t ticks = ns / 100U; value.QuadPart = ticks <= (std::numeric_limits::max)() - WINDOWS_EPOCH_TICKS ? ticks + WINDOWS_EPOCH_TICKS : (std::numeric_limits::max)(); FILETIME result = {}; result.dwLowDateTime = value.LowPart; result.dwHighDateTime = value.HighPart; return result; } KVMFRClipboardFileError FileError(DWORD error) { switch (error) { case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: case ERROR_INVALID_HANDLE: return KVMFR_CLIPBOARD_FILE_ERROR_NOT_FOUND; case ERROR_ACCESS_DENIED: case ERROR_SHARING_VIOLATION: case ERROR_LOCK_VIOLATION: return KVMFR_CLIPBOARD_FILE_ERROR_ACCESS; case ERROR_DIRECTORY: return KVMFR_CLIPBOARD_FILE_ERROR_NOT_DIRECTORY; case ERROR_NOT_ENOUGH_MEMORY: case ERROR_OUTOFMEMORY: return KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY; case ERROR_DISK_FULL: case ERROR_HANDLE_DISK_FULL: return KVMFR_CLIPBOARD_FILE_ERROR_NO_SPACE; case ERROR_DEVICE_NOT_CONNECTED: case ERROR_BROKEN_PIPE: return KVMFR_CLIPBOARD_FILE_ERROR_DISCONNECTED; case ERROR_OPERATION_ABORTED: case ERROR_CANCELLED: return KVMFR_CLIPBOARD_FILE_ERROR_CANCELLED; case ERROR_NOT_SUPPORTED: return KVMFR_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED; case ERROR_INVALID_DATA: case ERROR_INVALID_NAME: case ERROR_INVALID_PARAMETER: return KVMFR_CLIPBOARD_FILE_ERROR_INVALID; default: return KVMFR_CLIPBOARD_FILE_ERROR_IO; } } bool SameIdentity(const CLocalClipboardFiles::Node& node, const BY_HANDLE_FILE_INFORMATION& information) { const bool directory = (information.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; const uint64_t size = directory ? 0 : (static_cast(information.nFileSizeHigh) << 32) | information.nFileSizeLow; return !(information.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) && directory == (node.type == KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY) && size == node.size && FileTimeToUnixNs(information.ftCreationTime) == node.createdNs && FileTimeToUnixNs(information.ftLastWriteTime) == node.modifiedNs && information.dwVolumeSerialNumber == node.volumeSerial && information.nFileIndexHigh == node.fileIndexHigh && information.nFileIndexLow == node.fileIndexLow; } std::wstring BaseName(std::wstring path) { while (path.size() > 1 && (path.back() == L'\\' || path.back() == L'/')) { if (path.size() == 3 && path[1] == L':') break; path.pop_back(); } size_t end = path.size(); while (end && (path[end - 1] == L'\\' || path[end - 1] == L'/')) --end; const size_t separator = end ? path.find_last_of(L"\\/", end - 1) : std::wstring::npos; std::wstring name = path.substr( separator == std::wstring::npos ? 0 : separator + 1, end - (separator == std::wstring::npos ? 0 : separator + 1)); if (name.size() == 2 && name[1] == L':') name.resize(1); return name; } std::wstring JoinPath(const std::wstring& parent, const std::wstring& child) { std::wstring result = parent; if (!result.empty() && result.back() != L'\\' && result.back() != L'/') result.push_back(L'\\'); result += child; return result; } bool HasDotComponent(const std::wstring& path, size_t offset) { while (offset < path.size()) { const size_t end = path.find(L'\\', offset); const size_t length = (end == std::wstring::npos ? path.size() : end) - offset; if ((length == 1 && path[offset] == L'.') || (length == 2 && path[offset] == L'.' && path[offset + 1] == L'.')) return true; if (end == std::wstring::npos) break; offset = end + 1U; } return false; } bool SourcePath(const std::wstring& input, std::wstring& output) { if (input.empty()) { SetLastError(ERROR_INVALID_NAME); return false; } try { std::wstring path = input; for (wchar_t& value : path) if (value == L'/') value = L'\\'; const bool drive = path.size() >= 3 && ((path[0] >= L'A' && path[0] <= L'Z') || (path[0] >= L'a' && path[0] <= L'z')) && path[1] == L':' && path[2] == L'\\'; const bool unc = path.size() >= 3 && path[0] == L'\\' && path[1] == L'\\' && path[2] != L'?' && path[2] != L'.'; const bool extendedDrive = path.size() >= 7 && path.compare(0, 4, L"\\\\?\\") == 0 && ((path[4] >= L'A' && path[4] <= L'Z') || (path[4] >= L'a' && path[4] <= L'z')) && path[5] == L':' && path[6] == L'\\'; const bool extendedUNC = path.size() >= 8 && path.compare(0, 4, L"\\\\?\\") == 0 && (path[4] == L'U' || path[4] == L'u') && (path[5] == L'N' || path[5] == L'n') && (path[6] == L'C' || path[6] == L'c') && path[7] == L'\\'; const size_t componentOffset = drive ? 3U : unc ? 2U : extendedDrive ? 7U : extendedUNC ? 8U : 0U; if (!componentOffset || HasDotComponent(path, componentOffset)) { SetLastError(ERROR_INVALID_NAME); return false; } if (extendedDrive) output = std::move(path); else if (extendedUNC) { path[4] = L'U'; path[5] = L'N'; path[6] = L'C'; output = std::move(path); } else if (drive) output = L"\\\\?\\" + path; else if (unc) output = L"\\\\?\\UNC\\" + path.substr(2); else { SetLastError(ERROR_INVALID_NAME); return false; } } catch (const std::bad_alloc&) { SetLastError(ERROR_OUTOFMEMORY); return false; } return true; } bool ToUTF8(const std::wstring& text, std::vector& output) { if (text.empty() || text.size() > static_cast((std::numeric_limits::max)())) { SetLastError(ERROR_INVALID_DATA); return false; } const int count = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, text.data(), static_cast(text.size()), nullptr, 0, nullptr, nullptr); if (count <= 0) return false; try { const size_t offset = output.size(); output.resize(offset + static_cast(count)); if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, text.data(), static_cast(text.size()), reinterpret_cast(output.data() + offset), count, nullptr, nullptr) != count) { output.resize(offset); return false; } } catch (const std::bad_alloc&) { SetLastError(ERROR_OUTOFMEMORY); return false; } return true; } bool FromUTF8(const uint8_t * data, size_t length, std::wstring& output) { if (!data || !length || length > static_cast((std::numeric_limits::max)())) return false; const int count = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast(data), static_cast(length), nullptr, 0); if (count <= 0) return false; try { output.resize(static_cast(count)); } catch (const std::bad_alloc&) { return false; } return MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast(data), static_cast(length), output.data(), count) == count; } bool ValidComponent(const std::wstring& name) { if (name.empty() || name.size() > 255 || name == L"." || name == L".." || name.back() == L'.' || name.back() == L' ') return false; for (wchar_t value : name) if (value < 32 || value == L'\\' || value == L'/' || value == L'<' || value == L'>' || value == L':' || value == L'"' || value == L'|' || value == L'?' || value == L'*') return false; std::wstring base = name.substr(0, name.find(L'.')); for (wchar_t& value : base) if (value >= L'a' && value <= L'z') value = static_cast(value - (L'a' - L'A')); if (base == L"CON" || base == L"PRN" || base == L"AUX" || base == L"NUL") return false; if (base.size() == 4 && (base.compare(0, 3, L"COM") == 0 || base.compare(0, 3, L"LPT") == 0)) { const wchar_t suffix = base[3]; if ((suffix >= L'1' && suffix <= L'9') || suffix == L'\u00b9' || suffix == L'\u00b2' || suffix == L'\u00b3') return false; } return true; } int CompareComponent(const std::wstring& left, const std::wstring& right) { const int result = CompareStringOrdinal(left.c_str(), static_cast(left.size()), right.c_str(), static_cast(right.size()), TRUE); return result ? result : (left < right ? CSTR_LESS_THAN : (left == right ? CSTR_EQUAL : CSTR_GREATER_THAN)); } struct ComponentLess { bool operator()(const std::wstring& left, const std::wstring& right) const { return CompareComponent(left, right) == CSTR_LESS_THAN; } }; bool SameComponent(const std::wstring& left, const std::wstring& right) { return CompareComponent(left, right) == CSTR_EQUAL; } class CClipboardFileDatasetLease final { public: const uint64_t dataset; const uint64_t acquisition; const std::shared_ptr provider; CClipboardFileDatasetLease(uint64_t dataset, uint64_t acquisition, std::shared_ptr provider) : dataset(dataset), acquisition(acquisition), provider(std::move(provider)) { } ~CClipboardFileDatasetLease() { provider->ReleaseClipboardFileDataset(dataset, acquisition); } }; class CClipboardFileStream final : public IStream { private: std::atomic m_refs { 1 }; ClipboardRemoteFileEntry m_entry; std::shared_ptr m_lease; std::mutex m_lock; uint64_t m_offset = 0; public: CClipboardFileStream( const ClipboardRemoteFileEntry& entry, std::shared_ptr lease, uint64_t offset = 0) : m_entry(entry), m_lease(std::move(lease)), m_offset(offset) { } HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** object) override { if (!object) return E_POINTER; *object = nullptr; if (iid == IID_IUnknown || iid == IID_ISequentialStream || iid == IID_IStream) { *object = static_cast(this); AddRef(); return S_OK; } return E_NOINTERFACE; } ULONG STDMETHODCALLTYPE AddRef() override { return m_refs.fetch_add(1, std::memory_order_relaxed) + 1; } ULONG STDMETHODCALLTYPE Release() override { const ULONG refs = m_refs.fetch_sub(1, std::memory_order_acq_rel) - 1; if (!refs) delete this; return refs; } HRESULT STDMETHODCALLTYPE Read(void * data, ULONG length, ULONG * read) override { if (read) *read = 0; if (!data && length) return STG_E_INVALIDPOINTER; std::lock_guard lock(m_lock); if (!length) return S_OK; if (m_offset >= m_entry.size) return S_FALSE; const ULONG requested = static_cast((std::min)( length, m_entry.size - m_offset)); ULONG actual = 0; const HRESULT result = m_lease->provider->ReadClipboardFile( m_lease->dataset, m_lease->acquisition, m_entry.node, m_offset, data, requested, actual); if (FAILED(result)) return result; if (actual != requested) return STG_E_READFAULT; m_offset += actual; if (read) *read = actual; return requested == length ? S_OK : S_FALSE; } HRESULT STDMETHODCALLTYPE Write(const void *, ULONG, ULONG *) override { return STG_E_ACCESSDENIED; } HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER * position) override { std::lock_guard lock(m_lock); uint64_t base = 0; switch (origin) { case STREAM_SEEK_SET: base = 0; break; case STREAM_SEEK_CUR: base = m_offset; break; case STREAM_SEEK_END: base = m_entry.size; break; default: return STG_E_INVALIDFUNCTION; } uint64_t next = 0; if (move.QuadPart < 0) { const uint64_t amount = static_cast(-(move.QuadPart + 1)) + 1U; if (amount > base) return STG_E_INVALIDFUNCTION; next = base - amount; } else { const uint64_t amount = static_cast(move.QuadPart); if (amount > (std::numeric_limits::max)() - base) return STG_E_INVALIDFUNCTION; next = base + amount; } m_offset = next; if (position) position->QuadPart = next; return S_OK; } HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER) override { return STG_E_ACCESSDENIED; } HRESULT STDMETHODCALLTYPE CopyTo(IStream * stream, ULARGE_INTEGER count, ULARGE_INTEGER * readTotal, ULARGE_INTEGER * writtenTotal) override { if (!stream) return STG_E_INVALIDPOINTER; if (readTotal) readTotal->QuadPart = 0; if (writtenTotal) writtenTotal->QuadPart = 0; std::vector buffer; try { buffer.resize(COPY_BUFFER_BYTES); } catch (const std::bad_alloc&) { return E_OUTOFMEMORY; } catch (const std::length_error&) { return E_OUTOFMEMORY; } uint64_t remaining = count.QuadPart; while (remaining) { const ULONG wanted = static_cast((std::min)( remaining, buffer.size())); ULONG got = 0; HRESULT result = Read(buffer.data(), wanted, &got); if (FAILED(result)) return result; if (!got) return S_FALSE; ULONG written = 0; result = stream->Write(buffer.data(), got, &written); if (readTotal) readTotal->QuadPart += got; if (writtenTotal) writtenTotal->QuadPart += written; if (FAILED(result) || written != got) return FAILED(result) ? result : STG_E_WRITEFAULT; remaining -= got; } return S_OK; } HRESULT STDMETHODCALLTYPE Commit(DWORD) override { return S_OK; } HRESULT STDMETHODCALLTYPE Revert() override { return STG_E_REVERTED; } HRESULT STDMETHODCALLTYPE LockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD) override { return STG_E_INVALIDFUNCTION; } HRESULT STDMETHODCALLTYPE UnlockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD) override { return STG_E_INVALIDFUNCTION; } HRESULT STDMETHODCALLTYPE Stat(STATSTG * stat, DWORD flags) override { if (!stat) return STG_E_INVALIDPOINTER; *stat = {}; stat->type = STGTY_STREAM; stat->cbSize.QuadPart = m_entry.size; stat->grfMode = STGM_READ; stat->ctime = UnixNsToFileTime(m_entry.createdNs); stat->mtime = UnixNsToFileTime(m_entry.modifiedNs); if (!(flags & STATFLAG_NONAME)) { const size_t bytes = (m_entry.name.size() + 1U) * sizeof(wchar_t); stat->pwcsName = static_cast(CoTaskMemAlloc(bytes)); if (!stat->pwcsName) return E_OUTOFMEMORY; memcpy(stat->pwcsName, m_entry.name.c_str(), bytes); } return S_OK; } HRESULT STDMETHODCALLTYPE Clone(IStream ** stream) override { if (!stream) return E_POINTER; *stream = nullptr; std::lock_guard lock(m_lock); try { CClipboardFileStream * clone = new (std::nothrow) CClipboardFileStream(m_entry, m_lease, m_offset); if (!clone) return E_OUTOFMEMORY; *stream = clone; return S_OK; } catch (const std::bad_alloc&) { return E_OUTOFMEMORY; } catch (const std::length_error&) { return E_OUTOFMEMORY; } } }; class CFormatEnumerator final : public IEnumFORMATETC { private: std::atomic m_refs { 1 }; std::vector m_formats; size_t m_index = 0; public: CFormatEnumerator(std::vector formats, size_t index = 0) : m_formats(std::move(formats)), m_index(index) { } HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** object) override { if (!object) return E_POINTER; *object = nullptr; if (iid == IID_IUnknown || iid == IID_IEnumFORMATETC) { *object = static_cast(this); AddRef(); return S_OK; } return E_NOINTERFACE; } ULONG STDMETHODCALLTYPE AddRef() override { return m_refs.fetch_add(1, std::memory_order_relaxed) + 1; } ULONG STDMETHODCALLTYPE Release() override { const ULONG refs = m_refs.fetch_sub(1, std::memory_order_acq_rel) - 1; if (!refs) delete this; return refs; } HRESULT STDMETHODCALLTYPE Next(ULONG count, FORMATETC * formats, ULONG * fetched) override { if (fetched) *fetched = 0; if (!formats || (count != 1 && !fetched)) return E_INVALIDARG; ULONG copied = 0; while (copied < count && m_index < m_formats.size()) formats[copied++] = m_formats[m_index++]; if (fetched) *fetched = copied; return copied == count ? S_OK : S_FALSE; } HRESULT STDMETHODCALLTYPE Skip(ULONG count) override { const size_t available = m_formats.size() - m_index; const size_t skipped = (std::min)(available, count); m_index += skipped; return skipped == count ? S_OK : S_FALSE; } HRESULT STDMETHODCALLTYPE Reset() override { m_index = 0; return S_OK; } HRESULT STDMETHODCALLTYPE Clone(IEnumFORMATETC ** clone) override { if (!clone) return E_POINTER; *clone = nullptr; try { *clone = new (std::nothrow) CFormatEnumerator(m_formats, m_index); return *clone ? S_OK : E_OUTOFMEMORY; } catch (const std::bad_alloc&) { return E_OUTOFMEMORY; } catch (const std::length_error&) { return E_OUTOFMEMORY; } } }; class CClipboardFileDataObject final : public IDataObject { private: std::atomic m_refs { 1 }; std::vector m_entries; std::vector m_descriptors; std::shared_ptr m_lease; UINT m_fileDescriptor = 0; UINT m_fileContents = 0; UINT m_preferredDropEffect = 0; bool Matches(const FORMATETC& format, UINT clipboardFormat, DWORD tymed) const { return format.cfFormat == clipboardFormat && format.dwAspect == DVASPECT_CONTENT && format.lindex >= -1 && (format.tymed & tymed); } public: CClipboardFileDataObject( std::vector&& entries, std::vector&& descriptors, UINT fileDescriptor, UINT fileContents, UINT preferredDropEffect) noexcept : m_entries(std::move(entries)), m_descriptors(std::move(descriptors)), m_fileDescriptor(fileDescriptor), m_fileContents(fileContents), m_preferredDropEffect(preferredDropEffect) { } void SetLease(std::shared_ptr lease) noexcept { m_lease = std::move(lease); } HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** object) override { if (!object) return E_POINTER; *object = nullptr; if (iid == IID_IUnknown || iid == IID_IDataObject) { *object = static_cast(this); AddRef(); return S_OK; } return E_NOINTERFACE; } ULONG STDMETHODCALLTYPE AddRef() override { return m_refs.fetch_add(1, std::memory_order_relaxed) + 1; } ULONG STDMETHODCALLTYPE Release() override { const ULONG refs = m_refs.fetch_sub(1, std::memory_order_acq_rel) - 1; if (!refs) delete this; return refs; } HRESULT STDMETHODCALLTYPE GetData(FORMATETC * format, STGMEDIUM * medium) override { if (!format || !medium) return E_POINTER; *medium = {}; if (Matches(*format, m_fileDescriptor, TYMED_HGLOBAL) && format->lindex == -1) { if (m_descriptors.size() > (std::numeric_limits::max)() || m_descriptors.size() > ((std::numeric_limits::max)() - offsetof(FILEGROUPDESCRIPTORW, fgd)) / sizeof(FILEDESCRIPTORW)) return E_OUTOFMEMORY; const SIZE_T bytes = offsetof(FILEGROUPDESCRIPTORW, fgd) + m_descriptors.size() * sizeof(FILEDESCRIPTORW); HGLOBAL memory = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, bytes); if (!memory) return E_OUTOFMEMORY; FILEGROUPDESCRIPTORW * group = static_cast(GlobalLock(memory)); if (!group) { GlobalFree(memory); return E_OUTOFMEMORY; } group->cItems = static_cast(m_descriptors.size()); if (!m_descriptors.empty()) memcpy(group->fgd, m_descriptors.data(), m_descriptors.size() * sizeof(FILEDESCRIPTORW)); GlobalUnlock(memory); medium->tymed = TYMED_HGLOBAL; medium->hGlobal = memory; return S_OK; } if (Matches(*format, m_fileContents, TYMED_ISTREAM)) { if (format->lindex < 0 || static_cast(format->lindex) >= m_entries.size()) return DV_E_LINDEX; const ClipboardRemoteFileEntry& entry = m_entries[static_cast(format->lindex)]; if (entry.type != KVMFR_CLIPBOARD_FILE_TYPE_REGULAR) return DV_E_LINDEX; try { CClipboardFileStream * stream = new (std::nothrow) CClipboardFileStream(entry, m_lease); if (!stream) return E_OUTOFMEMORY; medium->tymed = TYMED_ISTREAM; medium->pstm = stream; return S_OK; } catch (const std::bad_alloc&) { return E_OUTOFMEMORY; } catch (const std::length_error&) { return E_OUTOFMEMORY; } } if (Matches(*format, m_preferredDropEffect, TYMED_HGLOBAL) && format->lindex == -1) { HGLOBAL memory = GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD)); if (!memory) return E_OUTOFMEMORY; DWORD * effect = static_cast(GlobalLock(memory)); if (!effect) { GlobalFree(memory); return E_OUTOFMEMORY; } *effect = DROPEFFECT_COPY; GlobalUnlock(memory); medium->tymed = TYMED_HGLOBAL; medium->hGlobal = memory; return S_OK; } return DV_E_FORMATETC; } HRESULT STDMETHODCALLTYPE GetDataHere(FORMATETC *, STGMEDIUM *) override { return DATA_E_FORMATETC; } HRESULT STDMETHODCALLTYPE QueryGetData(FORMATETC * format) override { if (!format) return E_POINTER; if (Matches(*format, m_fileDescriptor, TYMED_HGLOBAL) && format->lindex == -1) return S_OK; if (Matches(*format, m_preferredDropEffect, TYMED_HGLOBAL) && format->lindex == -1) return S_OK; if (Matches(*format, m_fileContents, TYMED_ISTREAM)) { if (format->lindex == -1) for (const ClipboardRemoteFileEntry& entry : m_entries) if (entry.type == KVMFR_CLIPBOARD_FILE_TYPE_REGULAR) return S_OK; if (format->lindex >= 0 && static_cast(format->lindex) < m_entries.size() && m_entries[static_cast(format->lindex)].type == KVMFR_CLIPBOARD_FILE_TYPE_REGULAR) return S_OK; } return DV_E_FORMATETC; } HRESULT STDMETHODCALLTYPE GetCanonicalFormatEtc(FORMATETC * input, FORMATETC * output) override { if (!input || !output) return E_POINTER; *output = *input; output->ptd = nullptr; return DATA_S_SAMEFORMATETC; } HRESULT STDMETHODCALLTYPE SetData(FORMATETC *, STGMEDIUM *, BOOL) override { return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE EnumFormatEtc(DWORD direction, IEnumFORMATETC ** enumerator) override { if (!enumerator) return E_POINTER; *enumerator = nullptr; if (direction != DATADIR_GET) return E_NOTIMPL; try { std::vector formats; formats.reserve(3U); formats.push_back({ static_cast(m_fileDescriptor), nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }); for (const ClipboardRemoteFileEntry& entry : m_entries) if (entry.type == KVMFR_CLIPBOARD_FILE_TYPE_REGULAR) { formats.push_back({ static_cast(m_fileContents), nullptr, DVASPECT_CONTENT, -1, TYMED_ISTREAM }); break; } formats.push_back({ static_cast(m_preferredDropEffect), nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }); *enumerator = new (std::nothrow) CFormatEnumerator(std::move(formats)); return *enumerator ? S_OK : E_OUTOFMEMORY; } catch (const std::bad_alloc&) { return E_OUTOFMEMORY; } catch (const std::length_error&) { return E_OUTOFMEMORY; } } HRESULT STDMETHODCALLTYPE DAdvise(FORMATETC *, DWORD, IAdviseSink *, DWORD *) override { return OLE_E_ADVISENOTSUPPORTED; } HRESULT STDMETHODCALLTYPE DUnadvise(DWORD) override { return OLE_E_ADVISENOTSUPPORTED; } HRESULT STDMETHODCALLTYPE EnumDAdvise(IEnumSTATDATA **) override { return OLE_E_ADVISENOTSUPPORTED; } }; } CLocalClipboardFiles::CLocalClipboardFiles(HANDLE userToken) : m_userToken(userToken) { } CLocalClipboardFiles::~CLocalClipboardFiles() { if (m_userToken) CloseHandle(m_userToken); } std::shared_ptr CLocalClipboardFiles::Capture( HDROP drop, KVMFRClipboardFileError& error) { error = KVMFR_CLIPBOARD_FILE_ERROR_INVALID; if (!drop) { SetLastError(ERROR_INVALID_HANDLE); return nullptr; } std::vector roots; HANDLE userToken = nullptr; std::shared_ptr dataset; try { DWORD tokenError = ERROR_SUCCESS; if (!CaptureUserToken(userToken, error, tokenError)) return nullptr; CLocalClipboardFiles * raw = new CLocalClipboardFiles(userToken); userToken = nullptr; dataset = std::shared_ptr(raw); const UINT count = DragQueryFileW(drop, 0xffffffffU, nullptr, 0); if (!count) { SetLastError(ERROR_INVALID_DATA); return nullptr; } roots.reserve(count); for (UINT index = 0; index < count; ++index) { const UINT length = DragQueryFileW(drop, index, nullptr, 0); if (!length) { SetLastError(ERROR_INVALID_DATA); return nullptr; } std::vector path(static_cast(length) + 1U); std::wstring source; if (DragQueryFileW(drop, index, path.data(), length + 1U) != length) { SetLastError(ERROR_INVALID_DATA); error = KVMFR_CLIPBOARD_FILE_ERROR_INVALID; return nullptr; } if (!SourcePath(path.data(), source)) { error = FileError(GetLastError()); return nullptr; } roots.emplace_back(std::move(source)); } CThreadImpersonation impersonation(dataset->m_userToken); if (!impersonation.Active()) { error = TokenError(impersonation.Error()); return nullptr; } for (const std::wstring& root : roots) if (!dataset->AddRoot(root, error)) return nullptr; if (!impersonation.Finish()) { error = TokenError(impersonation.Error()); return nullptr; } } catch (const std::bad_alloc&) { if (userToken) CloseHandle(userToken); SetLastError(ERROR_OUTOFMEMORY); error = KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY; return nullptr; } error = KVMFR_CLIPBOARD_FILE_ERROR_NONE; return dataset; } size_t CLocalClipboardFiles::RootCount() const { // Roots are immutable after Capture publishes the dataset. return m_roots.size(); } bool CLocalClipboardFiles::AddRoot(const std::wstring& path, KVMFRClipboardFileError& error) { Node node; try { node.path = path; node.name = BaseName(path); } catch (const std::bad_alloc&) { error = KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY; return false; } if (!ValidComponent(node.name)) { error = KVMFR_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED; return false; } if (!CaptureIdentity(node, error)) return false; for (uint64_t root : m_roots) if (SameComponent(m_nodes[static_cast(root - 1U)].name, node.name)) { error = KVMFR_CLIPBOARD_FILE_ERROR_INVALID; return false; } node.id = static_cast(m_nodes.size()) + 1U; m_nodes.emplace_back(std::move(node)); m_roots.push_back(static_cast(m_nodes.size())); return true; } bool CLocalClipboardFiles::RefreshRoots(KVMFRClipboardFileError& error) { std::vector refreshed; try { refreshed.reserve(m_roots.size()); for (uint64_t id : m_roots) { const Node& source = m_nodes[static_cast(id - 1U)]; Node node; node.id = source.id; node.path = source.path; node.name = source.name; if (!CaptureIdentity(node, error)) return false; refreshed.emplace_back(std::move(node)); } } catch (const std::bad_alloc&) { error = KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY; return false; } for (size_t index = 0; index < refreshed.size(); ++index) { Node& target = m_nodes[static_cast(m_roots[index] - 1U)]; Node& source = refreshed[index]; target.size = source.size; target.createdNs = source.createdNs; target.modifiedNs = source.modifiedNs; target.type = source.type; target.volumeSerial = source.volumeSerial; target.fileIndexHigh = source.fileIndexHigh; target.fileIndexLow = source.fileIndexLow; } return true; } bool CLocalClipboardFiles::CaptureIdentity(Node& node, KVMFRClipboardFileError& error) const { const DWORD flags = FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS; HANDLE file = CreateFileW(node.path.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, flags, nullptr); if (file == INVALID_HANDLE_VALUE) { error = FileError(GetLastError()); return false; } FILE_ATTRIBUTE_TAG_INFO tag = {}; BY_HANDLE_FILE_INFORMATION information = {}; bool success = GetFileInformationByHandle(file, &information) != FALSE; if (success && (information.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) success = GetFileInformationByHandleEx(file, FileAttributeTagInfo, &tag, sizeof(tag)) != FALSE; const DWORD winError = success ? ERROR_SUCCESS : GetLastError(); CloseHandle(file); if (!success) { error = FileError(winError); return false; } const DWORD attributes = tag.FileAttributes | information.dwFileAttributes; if ((attributes & FILE_ATTRIBUTE_DEVICE) || ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) && IsReparseTagNameSurrogate(tag.ReparseTag))) { error = KVMFR_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED; return false; } node.type = information.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY : KVMFR_CLIPBOARD_FILE_TYPE_REGULAR; node.size = node.type == KVMFR_CLIPBOARD_FILE_TYPE_REGULAR ? (static_cast(information.nFileSizeHigh) << 32) | information.nFileSizeLow : 0; node.createdNs = FileTimeToUnixNs(information.ftCreationTime); node.modifiedNs = FileTimeToUnixNs(information.ftLastWriteTime); node.volumeSerial = information.dwVolumeSerialNumber; node.fileIndexHigh = information.nFileIndexHigh; node.fileIndexLow = information.nFileIndexLow; return true; } bool CLocalClipboardFiles::CheckIdentity(const Node& node, KVMFRClipboardFileError& error) const { Node current; try { current.path = node.path; } catch (const std::bad_alloc&) { error = KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY; return false; } if (!CaptureIdentity(current, error)) return false; if (current.volumeSerial != node.volumeSerial || current.fileIndexHigh != node.fileIndexHigh || current.fileIndexLow != node.fileIndexLow || current.type != node.type || current.size != node.size || current.createdNs != node.createdNs || current.modifiedNs != node.modifiedNs) { error = KVMFR_CLIPBOARD_FILE_ERROR_STALE; return false; } return true; } bool CLocalClipboardFiles::LoadChildren(size_t index, KVMFRClipboardFileError& error) { if (index >= m_nodes.size()) { error = KVMFR_CLIPBOARD_FILE_ERROR_NOT_FOUND; return false; } if (m_nodes[index].type != KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY) { error = KVMFR_CLIPBOARD_FILE_ERROR_NOT_DIRECTORY; return false; } if (m_nodes[index].childrenLoaded) return true; if (!CheckIdentity(m_nodes[index], error)) return false; HANDLE search = INVALID_HANDLE_VALUE; try { const std::wstring parent = m_nodes[index].path; const std::wstring pattern = JoinPath(parent, L"*"); WIN32_FIND_DATAW found = {}; search = FindFirstFileExW(pattern.c_str(), FindExInfoBasic, &found, FindExSearchNameMatch, nullptr, 0); if (search == INVALID_HANDLE_VALUE) { const DWORD winError = GetLastError(); if (winError == ERROR_FILE_NOT_FOUND && CheckIdentity(m_nodes[index], error)) { m_nodes[index].childrenLoaded = true; return true; } if (!error) error = FileError(winError); return false; } std::vector children; do { if (!wcscmp(found.cFileName, L".") || !wcscmp(found.cFileName, L"..")) continue; if (!ValidComponent(found.cFileName)) { FindClose(search); search = INVALID_HANDLE_VALUE; error = KVMFR_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED; return false; } Node child; child.path = JoinPath(parent, found.cFileName); child.name = found.cFileName; if (!CaptureIdentity(child, error)) { FindClose(search); search = INVALID_HANDLE_VALUE; return false; } children.emplace_back(std::move(child)); } while (FindNextFileW(search, &found)); const DWORD enumerationError = GetLastError(); FindClose(search); search = INVALID_HANDLE_VALUE; if (enumerationError != ERROR_NO_MORE_FILES) { error = FileError(enumerationError); return false; } if (!CheckIdentity(m_nodes[index], error)) return false; const size_t first = m_nodes.size(); if (children.size() > m_nodes.max_size() - first) { error = KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY; return false; } m_nodes.reserve(first + children.size()); std::vector& ids = m_nodes[index].children; ids.reserve(children.size()); for (Node& child : children) { child.id = static_cast(m_nodes.size()) + 1U; ids.push_back(child.id); m_nodes.emplace_back(std::move(child)); } m_nodes[index].childrenLoaded = true; } catch (const std::bad_alloc&) { if (search != INVALID_HANDLE_VALUE) FindClose(search); error = KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY; return false; } return true; } bool CLocalClipboardFiles::List(uint64_t node, std::vector& data, KVMFRClipboardFileError& error) { std::lock_guard lock(m_lock); error = KVMFR_CLIPBOARD_FILE_ERROR_NONE; const std::vector * children = nullptr; if (node == KVMFR_CLIPBOARD_FILE_ROOT_NODE) { if (!m_published) { CThreadImpersonation impersonation(m_userToken); if (!impersonation.Active()) { error = TokenError(impersonation.Error()); return false; } if (!RefreshRoots(error)) return false; if (!impersonation.Finish()) { error = TokenError(impersonation.Error()); return false; } m_published = true; } children = &m_roots; } else { if (!m_published) { error = KVMFR_CLIPBOARD_FILE_ERROR_INVALID; return false; } if (node > m_nodes.size()) { error = KVMFR_CLIPBOARD_FILE_ERROR_NOT_FOUND; return false; } const size_t index = static_cast(node - 1U); CThreadImpersonation impersonation(m_userToken); if (!impersonation.Active()) { error = TokenError(impersonation.Error()); return false; } if (!LoadChildren(index, error)) return false; if (!impersonation.Finish()) { error = TokenError(impersonation.Error()); return false; } children = &m_nodes[index].children; } data.clear(); try { for (uint64_t id : *children) { const Node& child = m_nodes[static_cast(id - 1U)]; std::vector name; if (!ToUTF8(child.name, name)) { error = FileError(GetLastError()); return false; } if (name.size() > (std::numeric_limits::max)()) { error = KVMFR_CLIPBOARD_FILE_ERROR_INVALID; return false; } const uint64_t recordBytes = KVMFR_CLIPBOARD_FILE_ENTRY_BYTES( name.size()); if (recordBytes > (std::numeric_limits::max)() || data.size() > (std::numeric_limits::max)() - static_cast(recordBytes)) { error = KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY; return false; } const size_t offset = data.size(); data.resize(offset + static_cast(recordBytes), 0); KVMFRClipboardFileEntry entry = {}; entry.node = child.id; entry.size = child.size; entry.createdNs = child.createdNs; entry.modifiedNs = child.modifiedNs; entry.type = child.type; entry.nameLength = static_cast(name.size()); memcpy(data.data() + offset, &entry, sizeof(entry)); memcpy(data.data() + offset + sizeof(entry), name.data(), name.size()); } } catch (const std::bad_alloc&) { data.clear(); error = KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY; return false; } return true; } bool CLocalClipboardFiles::Read(uint64_t node, uint64_t offset, uint32_t length, std::vector& data, KVMFRClipboardFileError& error) { error = KVMFR_CLIPBOARD_FILE_ERROR_NONE; std::wstring path; Node expected; { std::lock_guard lock(m_lock); if (!m_published || !node || node > m_nodes.size()) { error = KVMFR_CLIPBOARD_FILE_ERROR_NOT_FOUND; return false; } const Node& source = m_nodes[static_cast(node - 1U)]; if (source.type != KVMFR_CLIPBOARD_FILE_TYPE_REGULAR) { error = KVMFR_CLIPBOARD_FILE_ERROR_IS_DIRECTORY; return false; } try { path = source.path; expected.id = source.id; expected.size = source.size; expected.createdNs = source.createdNs; expected.modifiedNs = source.modifiedNs; expected.type = source.type; expected.volumeSerial = source.volumeSerial; expected.fileIndexHigh = source.fileIndexHigh; expected.fileIndexLow = source.fileIndexLow; } catch (const std::bad_alloc&) { error = KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY; return false; } } if (length > KVMFR_CLIPBOARD_FILE_READ_BYTES || offset > static_cast( (std::numeric_limits::max)()) || offset > (std::numeric_limits::max)() - length) { error = KVMFR_CLIPBOARD_FILE_ERROR_INVALID; return false; } CThreadImpersonation impersonation(m_userToken); if (!impersonation.Active()) { error = TokenError(impersonation.Error()); return false; } HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, nullptr); if (file == INVALID_HANDLE_VALUE) { error = FileError(GetLastError()); return false; } FILE_STANDARD_INFO standard = {}; FILE_ATTRIBUTE_TAG_INFO tag = {}; BY_HANDLE_FILE_INFORMATION identity = {}; if (!GetFileInformationByHandleEx(file, FileStandardInfo, &standard, sizeof(standard)) || !GetFileInformationByHandleEx(file, FileAttributeTagInfo, &tag, sizeof(tag)) || !GetFileInformationByHandle(file, &identity)) { error = FileError(GetLastError()); CloseHandle(file); return false; } if (standard.Directory || (tag.FileAttributes & FILE_ATTRIBUTE_DEVICE) || ((tag.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && IsReparseTagNameSurrogate(tag.ReparseTag))) { error = standard.Directory ? KVMFR_CLIPBOARD_FILE_ERROR_IS_DIRECTORY : KVMFR_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED; CloseHandle(file); return false; } { std::lock_guard lock(m_lock); if (!node || node > m_nodes.size()) { error = KVMFR_CLIPBOARD_FILE_ERROR_STALE; CloseHandle(file); return false; } const Node& source = m_nodes[static_cast(node - 1U)]; if (!SameIdentity(source, identity)) { error = KVMFR_CLIPBOARD_FILE_ERROR_STALE; CloseHandle(file); return false; } } if (standard.EndOfFile.QuadPart < 0) { error = KVMFR_CLIPBOARD_FILE_ERROR_IO; CloseHandle(file); return false; } const uint64_t actualSize = static_cast( standard.EndOfFile.QuadPart); if (!SameIdentity(expected, identity) || actualSize != expected.size) { error = KVMFR_CLIPBOARD_FILE_ERROR_STALE; CloseHandle(file); return false; } const uint32_t wanted = offset >= actualSize ? 0 : static_cast((std::min)(length, actualSize - offset)); try { data.resize(wanted); } catch (const std::bad_alloc&) { error = KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY; CloseHandle(file); return false; } LARGE_INTEGER position = {}; position.QuadPart = offset; DWORD read = 0; bool success = SetFilePointerEx(file, position, nullptr, FILE_BEGIN) && (!wanted || ReadFile(file, data.data(), wanted, &read, nullptr)); DWORD ioError = success ? ERROR_SUCCESS : GetLastError(); BY_HANDLE_FILE_INFORMATION after = {}; if (success && !GetFileInformationByHandle(file, &after)) { success = false; ioError = GetLastError(); } if (success && !SameIdentity(expected, after)) { success = false; error = KVMFR_CLIPBOARD_FILE_ERROR_STALE; } CloseHandle(file); if (!success || read != wanted) { data.clear(); if (error == KVMFR_CLIPBOARD_FILE_ERROR_NONE) error = FileError(ioError ? ioError : ERROR_READ_FAULT); return false; } if (!impersonation.Finish()) { data.clear(); error = TokenError(impersonation.Error()); return false; } return true; } HRESULT ParseClipboardFileList(uint64_t parent, const uint8_t * data, size_t length, std::vector& entries) { if ((!data && length) || (length && length < sizeof(KVMFRClipboardFileEntry))) return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); size_t offset = 0; try { while (offset < length) { if (length - offset < sizeof(KVMFRClipboardFileEntry)) return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); KVMFRClipboardFileEntry wire = {}; memcpy(&wire, data + offset, sizeof(wire)); const uint64_t bytes = KVMFR_CLIPBOARD_FILE_ENTRY_BYTES(wire.nameLength); if (!wire.node || !wire.nameLength || (wire.type != KVMFR_CLIPBOARD_FILE_TYPE_REGULAR && wire.type != KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY) || bytes > length - offset || wire.nameLength > bytes - sizeof(wire)) return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); std::wstring name; if (!FromUTF8(data + offset + sizeof(wire), wire.nameLength, name) || !ValidComponent(name)) return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); for (size_t pad = sizeof(wire) + wire.nameLength; pad < static_cast(bytes); ++pad) if (data[offset + pad]) return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); ClipboardRemoteFileEntry entry; entry.node = wire.node; entry.parent = parent; entry.size = wire.type == KVMFR_CLIPBOARD_FILE_TYPE_REGULAR ? wire.size : 0; entry.createdNs = wire.createdNs; entry.modifiedNs = wire.modifiedNs; entry.type = wire.type; entry.name = std::move(name); entries.emplace_back(std::move(entry)); offset += static_cast(bytes); } } catch (const std::bad_alloc&) { return E_OUTOFMEMORY; } catch (const std::length_error&) { return E_OUTOFMEMORY; } return S_OK; } HRESULT CreateClipboardFileDataObject(uint64_t dataset, uint64_t acquisition, std::vector&& entries, std::shared_ptr provider, IDataObject ** object) { if (!object) return E_POINTER; *object = nullptr; if (!dataset || !acquisition || !provider || entries.empty()) return E_INVALIDARG; try { if (entries.size() > static_cast((std::numeric_limits::max)()) || entries.size() > static_cast((std::numeric_limits::max)())) return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); std::unordered_map byID; std::map> siblings; byID.reserve(entries.size()); for (size_t index = 0; index < entries.size(); ++index) { const ClipboardRemoteFileEntry& entry = entries[index]; if (!entry.node || !ValidComponent(entry.name) || !byID.emplace(entry.node, index).second || !siblings[entry.parent].insert(entry.name).second) return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); } std::vector paths(entries.size()); std::vector state(entries.size(), 0); std::vector chain; chain.reserve((std::min)(entries.size(), MAX_PATH)); for (size_t start = 0; start < entries.size(); ++start) { if (state[start] == 2) continue; chain.clear(); size_t current = start; while (state[current] != 2) { if (state[current] == 1 || chain.size() >= MAX_PATH) return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); state[current] = 1; chain.push_back(current); const ClipboardRemoteFileEntry& entry = entries[current]; if (!entry.parent) break; const auto parent = byID.find(entry.parent); if (parent == byID.end() || entries[parent->second].type != KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY) return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); current = parent->second; } while (!chain.empty()) { const size_t index = chain.back(); chain.pop_back(); const ClipboardRemoteFileEntry& entry = entries[index]; if (!entry.parent) paths[index] = entry.name; else { const size_t parent = byID.find(entry.parent)->second; if (state[parent] != 2) return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); paths[index] = paths[parent] + L"\\" + entry.name; } if (paths[index].size() >= MAX_PATH) return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); state[index] = 2; } } std::vector descriptors; if (entries.size() > descriptors.max_size()) return E_OUTOFMEMORY; descriptors.resize(entries.size()); for (size_t index = 0; index < entries.size(); ++index) { const ClipboardRemoteFileEntry& entry = entries[index]; FILEDESCRIPTORW& descriptor = descriptors[index]; descriptor.dwFlags = FD_ATTRIBUTES | FD_UNICODE | FD_PROGRESSUI; descriptor.dwFileAttributes = entry.type == KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL; if (entry.createdNs) { descriptor.dwFlags |= FD_CREATETIME; descriptor.ftCreationTime = UnixNsToFileTime(entry.createdNs); } if (entry.modifiedNs) { descriptor.dwFlags |= FD_WRITESTIME; descriptor.ftLastWriteTime = UnixNsToFileTime(entry.modifiedNs); } if (entry.type == KVMFR_CLIPBOARD_FILE_TYPE_REGULAR) { descriptor.dwFlags |= FD_FILESIZE; descriptor.nFileSizeHigh = static_cast(entry.size >> 32); descriptor.nFileSizeLow = static_cast(entry.size); } if (FAILED(StringCchCopyW(descriptor.cFileName, ARRAYSIZE(descriptor.cFileName), paths[index].c_str()))) return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); } const UINT fileDescriptor = RegisterClipboardFormatW(CFSTR_FILEDESCRIPTORW); const UINT fileContents = RegisterClipboardFormatW(CFSTR_FILECONTENTS); const UINT preferredDropEffect = RegisterClipboardFormatW(CFSTR_PREFERREDDROPEFFECT); if (!fileDescriptor || !fileContents || !preferredDropEffect) return HRESULT_FROM_WIN32(GetLastError() ? GetLastError() : ERROR_INVALID_DATA); CClipboardFileDataObject * result = new (std::nothrow) CClipboardFileDataObject(std::move(entries), std::move(descriptors), fileDescriptor, fileContents, preferredDropEffect); if (!result) return E_OUTOFMEMORY; try { std::shared_ptr lease = std::make_shared(dataset, acquisition, std::move(provider)); result->SetLease(std::move(lease)); } catch (const std::bad_alloc&) { result->Release(); return E_OUTOFMEMORY; } *object = result; return S_OK; } catch (const std::bad_alloc&) { return E_OUTOFMEMORY; } catch (const std::length_error&) { return E_OUTOFMEMORY; } }