Files
LookingGlass/client/platform/Windows/clipboard_files_win32.c
2026-11-06 14:40:26 +11:00

2571 lines
72 KiB
C

/**
* 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
*/
#define CINTERFACE
#define COBJMACROS
#define CONST_VTABLE
#include "platform/windows/clipboard_files_win32.h"
#include <LGProtocol/KVMFRClipboard.h>
#include <shlobj.h>
#include <winioctl.h>
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#define WINDOWS_EPOCH_TICKS UINT64_C(116444736000000000)
typedef struct WindowsClipboardFileIdentity
{
uint64_t size;
uint64_t createdNs;
uint64_t modifiedNs;
DWORD volumeSerial;
DWORD fileIndexHigh;
DWORD fileIndexLow;
DWORD attributes;
KVMFRClipboardFileType type;
}
WindowsClipboardFileIdentity;
typedef struct WindowsClipboardLocalNode
{
uint64_t id;
wchar_t * name;
wchar_t * path;
WindowsClipboardFileIdentity identity;
uint64_t * children;
size_t childCount;
size_t childCapacity;
bool childrenLoaded;
}
WindowsClipboardLocalNode;
struct WindowsClipboardLocalFiles
{
volatile LONG refs;
CRITICAL_SECTION lock;
WindowsClipboardLocalNode * nodes;
size_t nodeCount;
size_t nodeCapacity;
uint64_t * roots;
size_t rootCount;
size_t rootCapacity;
bool published;
};
typedef struct WindowsClipboardRemoteEntry
{
uint64_t node;
uint64_t parent;
uint64_t size;
uint64_t createdNs;
uint64_t modifiedNs;
KVMFRClipboardFileType type;
wchar_t * name;
uint8_t listState;
}
WindowsClipboardRemoteEntry;
struct WindowsClipboardRemoteManifest
{
volatile LONG refs;
CRITICAL_SECTION lock;
WindowsClipboardRemoteEntry * entries;
size_t entryCount;
size_t entryCapacity;
uint8_t rootState;
bool frozen;
};
static void setError(LG_ClipboardFileError * error,
LG_ClipboardFileError value)
{
if (error)
*error = value;
}
static LG_ClipboardFileError fileError(DWORD error)
{
switch (error)
{
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
case ERROR_INVALID_HANDLE:
return LG_CLIPBOARD_FILE_ERROR_NOT_FOUND;
case ERROR_ACCESS_DENIED:
case ERROR_SHARING_VIOLATION:
case ERROR_LOCK_VIOLATION:
return LG_CLIPBOARD_FILE_ERROR_ACCESS;
case ERROR_DIRECTORY:
return LG_CLIPBOARD_FILE_ERROR_NOT_DIRECTORY;
case ERROR_NOT_ENOUGH_MEMORY:
case ERROR_OUTOFMEMORY:
return LG_CLIPBOARD_FILE_ERROR_NO_MEMORY;
case ERROR_DISK_FULL:
case ERROR_HANDLE_DISK_FULL:
return LG_CLIPBOARD_FILE_ERROR_NO_SPACE;
case ERROR_DEVICE_NOT_CONNECTED:
case ERROR_BROKEN_PIPE:
return LG_CLIPBOARD_FILE_ERROR_DISCONNECTED;
case ERROR_OPERATION_ABORTED:
case ERROR_CANCELLED:
return LG_CLIPBOARD_FILE_ERROR_CANCELLED;
case ERROR_NOT_SUPPORTED:
return LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED;
case ERROR_INVALID_DATA:
case ERROR_INVALID_NAME:
case ERROR_INVALID_PARAMETER:
return LG_CLIPBOARD_FILE_ERROR_INVALID;
default:
return LG_CLIPBOARD_FILE_ERROR_IO;
}
}
static uint64_t fileTimeToUnixNs(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 <= UINT64_MAX / UINT64_C(100) ?
ticks * UINT64_C(100) : UINT64_MAX;
}
static FILETIME unixNsToFileTime(uint64_t ns)
{
ULARGE_INTEGER value;
FILETIME result;
const uint64_t ticks = ns / UINT64_C(100);
value.QuadPart = ticks <= UINT64_MAX - WINDOWS_EPOCH_TICKS ?
ticks + WINDOWS_EPOCH_TICKS : UINT64_MAX;
result.dwLowDateTime = value.LowPart;
result.dwHighDateTime = value.HighPart;
return result;
}
static wchar_t * duplicateWide(const wchar_t * value)
{
if (!value)
return NULL;
const size_t length = wcslen(value);
if (length > (SIZE_MAX / sizeof(*value)) - 1U)
return NULL;
wchar_t * result = malloc((length + 1U) * sizeof(*result));
if (!result)
return NULL;
memcpy(result, value, (length + 1U) * sizeof(*result));
return result;
}
static wchar_t * joinPath(const wchar_t * parent, const wchar_t * child)
{
const size_t parentLength = wcslen(parent);
const size_t childLength = wcslen(child);
const bool separator = parentLength && parent[parentLength - 1U] != L'\\' &&
parent[parentLength - 1U] != L'/';
if (parentLength > SIZE_MAX - childLength - (separator ? 2U : 1U) ||
parentLength + childLength + (separator ? 2U : 1U) >
SIZE_MAX / sizeof(wchar_t))
return NULL;
const size_t length = parentLength + childLength + (separator ? 1U : 0U);
wchar_t * result = malloc((length + 1U) * sizeof(*result));
if (!result)
return NULL;
memcpy(result, parent, parentLength * sizeof(*result));
size_t offset = parentLength;
if (separator)
result[offset++] = L'\\';
memcpy(result + offset, child, (childLength + 1U) * sizeof(*result));
return result;
}
static bool hasDotComponent(const wchar_t * path, size_t offset)
{
const size_t length = wcslen(path);
while (offset < length)
{
size_t end = offset;
while (end < length && path[end] != L'\\')
++end;
const size_t component = end - offset;
if ((component == 1U && path[offset] == L'.') ||
(component == 2U && path[offset] == L'.' &&
path[offset + 1U] == L'.'))
return true;
offset = end + 1U;
}
return false;
}
static wchar_t * normalizeSourcePath(const wchar_t * input)
{
if (!input || !*input)
{
SetLastError(ERROR_INVALID_NAME);
return NULL;
}
wchar_t * path = duplicateWide(input);
if (!path)
{
SetLastError(ERROR_OUTOFMEMORY);
return NULL;
}
for (wchar_t * value = path; *value; ++value)
if (*value == L'/')
*value = L'\\';
const size_t length = wcslen(path);
const bool drive = length >= 3U &&
((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 = length >= 3U && path[0] == L'\\' &&
path[1] == L'\\' && path[2] != L'?' && path[2] != L'.';
const bool extendedDrive = length >= 7U &&
wcsncmp(path, L"\\\\?\\", 4U) == 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 = length >= 8U &&
wcsncmp(path, L"\\\\?\\", 4U) == 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))
{
free(path);
SetLastError(ERROR_INVALID_NAME);
return NULL;
}
if (extendedDrive || extendedUNC)
{
if (extendedUNC)
{
path[4] = L'U';
path[5] = L'N';
path[6] = L'C';
}
return path;
}
const size_t prefixLength = unc ? 8U : 4U;
const size_t skip = unc ? 2U : 0U;
if (length - skip > (SIZE_MAX / sizeof(wchar_t)) - prefixLength - 1U)
{
free(path);
SetLastError(ERROR_OUTOFMEMORY);
return NULL;
}
wchar_t * result = malloc((prefixLength + length - skip + 1U) *
sizeof(*result));
if (!result)
{
free(path);
SetLastError(ERROR_OUTOFMEMORY);
return NULL;
}
const wchar_t * prefix = unc ? L"\\\\?\\UNC\\" : L"\\\\?\\";
memcpy(result, prefix, prefixLength * sizeof(*result));
memcpy(result + prefixLength, path + skip,
(length - skip + 1U) * sizeof(*result));
free(path);
return result;
}
static wchar_t * baseName(const wchar_t * path)
{
size_t end = wcslen(path);
while (end > 1U && (path[end - 1U] == L'\\' || path[end - 1U] == L'/'))
{
if (end == 3U && path[1] == L':')
break;
--end;
}
size_t start = end;
while (start && path[start - 1U] != L'\\' && path[start - 1U] != L'/')
--start;
size_t length = end - start;
if (length == 2U && path[start + 1U] == L':')
length = 1U;
if (length > (SIZE_MAX / sizeof(wchar_t)) - 1U)
return NULL;
wchar_t * result = malloc((length + 1U) * sizeof(*result));
if (!result)
return NULL;
memcpy(result, path + start, length * sizeof(*result));
result[length] = L'\0';
return result;
}
static bool validComponent(const wchar_t * name)
{
if (!name)
return false;
const size_t length = wcslen(name);
if (!length || length > 255U || !wcscmp(name, L".") ||
!wcscmp(name, L"..") || name[length - 1U] == L'.' ||
name[length - 1U] == L' ')
return false;
for (const wchar_t * value = name; *value; ++value)
if (*value < 32 || *value == L'\\' || *value == L'/' ||
*value == L'<' || *value == L'>' || *value == L':' ||
*value == L'"' || *value == L'|' || *value == L'?' ||
*value == L'*')
return false;
const size_t baseLength = wcscspn(name, L".");
wchar_t base[5] = { 0 };
size_t count = 0;
while (count < baseLength && count < ARRAYSIZE(base) - 1U)
{
wchar_t value = name[count];
if (value >= L'a' && value <= L'z')
value = (wchar_t)(value - (L'a' - L'A'));
base[count++] = value;
}
if (baseLength == 3U && (!wcscmp(base, L"CON") ||
!wcscmp(base, L"PRN") ||
!wcscmp(base, L"AUX") || !wcscmp(base, L"NUL")))
return false;
if (baseLength == 4U && (!wcsncmp(base, L"COM", 3U) ||
!wcsncmp(base, L"LPT", 3U)))
{
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;
}
static bool sameComponent(const wchar_t * first, const wchar_t * second)
{
const int result = CompareStringOrdinal(first, -1, second, -1, TRUE);
return result == CSTR_EQUAL || (!result && !_wcsicmp(first, second));
}
static bool captureIdentity(const wchar_t * path,
WindowsClipboardFileIdentity * identity, LG_ClipboardFileError * error)
{
const DWORD flags = FILE_FLAG_OPEN_REPARSE_POINT |
FILE_FLAG_BACKUP_SEMANTICS;
HANDLE file = CreateFileW(path, FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
OPEN_EXISTING, flags, NULL);
if (file == INVALID_HANDLE_VALUE)
{
setError(error, fileError(GetLastError()));
return false;
}
BY_HANDLE_FILE_INFORMATION information;
FILE_ATTRIBUTE_TAG_INFO tag;
memset(&information, 0, sizeof(information));
memset(&tag, 0, sizeof(tag));
const bool gotInformation = GetFileInformationByHandle(file,
&information) != FALSE;
const bool gotTag = gotInformation &&
GetFileInformationByHandleEx(file,
FileAttributeTagInfo, &tag, sizeof(tag)) != FALSE;
const DWORD winError = gotTag ? ERROR_SUCCESS : GetLastError();
CloseHandle(file);
if (!gotTag)
{
setError(error, fileError(winError));
return false;
}
const DWORD attributes = information.dwFileAttributes | tag.FileAttributes;
if ((attributes & FILE_ATTRIBUTE_DEVICE) ||
(attributes & FILE_ATTRIBUTE_REPARSE_POINT))
{
setError(error, LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED);
return false;
}
const bool directory =
(information.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
identity->size = directory ? 0 :
((uint64_t)information.nFileSizeHigh << 32U) |
information.nFileSizeLow;
identity->createdNs = fileTimeToUnixNs(information.ftCreationTime);
identity->modifiedNs = fileTimeToUnixNs(information.ftLastWriteTime);
identity->volumeSerial = information.dwVolumeSerialNumber;
identity->fileIndexHigh = information.nFileIndexHigh;
identity->fileIndexLow = information.nFileIndexLow;
identity->attributes = information.dwFileAttributes;
identity->type = directory ?
KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY :
KVMFR_CLIPBOARD_FILE_TYPE_REGULAR;
return true;
}
static bool sameIdentity(const WindowsClipboardFileIdentity * expected,
const BY_HANDLE_FILE_INFORMATION * information)
{
const bool directory =
(information->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
const uint64_t size = directory ? 0 :
((uint64_t)information->nFileSizeHigh << 32U) |
information->nFileSizeLow;
return !(information->dwFileAttributes &
(FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_REPARSE_POINT)) &&
directory ==
(expected->type == KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY) &&
size == expected->size &&
fileTimeToUnixNs(information->ftCreationTime) == expected->createdNs &&
fileTimeToUnixNs(information->ftLastWriteTime) == expected->modifiedNs &&
information->dwVolumeSerialNumber == expected->volumeSerial &&
information->nFileIndexHigh == expected->fileIndexHigh &&
information->nFileIndexLow == expected->fileIndexLow;
}
static bool checkIdentity(const wchar_t * path,
const WindowsClipboardFileIdentity * expected,
LG_ClipboardFileError * error)
{
WindowsClipboardFileIdentity current;
if (!captureIdentity(path, &current, error))
return false;
if (current.size != expected->size ||
current.createdNs != expected->createdNs ||
current.modifiedNs != expected->modifiedNs ||
current.volumeSerial != expected->volumeSerial ||
current.fileIndexHigh != expected->fileIndexHigh ||
current.fileIndexLow != expected->fileIndexLow ||
current.attributes != expected->attributes ||
current.type != expected->type)
{
setError(error, LG_CLIPBOARD_FILE_ERROR_STALE);
return false;
}
return true;
}
static void freeLocalNode(WindowsClipboardLocalNode * node)
{
free(node->name);
free(node->path);
free(node->children);
memset(node, 0, sizeof(*node));
}
static bool reserveLocalNodes(WindowsClipboardLocalFiles * files,
size_t count)
{
if (count <= files->nodeCapacity)
return true;
if (count > SIZE_MAX / sizeof(*files->nodes))
return false;
size_t capacity = files->nodeCapacity ? files->nodeCapacity : 16U;
while (capacity < count)
{
if (capacity > SIZE_MAX / 2U)
{
capacity = count;
break;
}
capacity *= 2U;
}
if (capacity > SIZE_MAX / sizeof(*files->nodes))
capacity = count;
void * nodes = realloc(files->nodes, capacity * sizeof(*files->nodes));
if (!nodes)
return false;
files->nodes = nodes;
files->nodeCapacity = capacity;
return true;
}
static bool reserveIDs(uint64_t ** values, size_t * capacity, size_t count)
{
if (count <= *capacity)
return true;
if (count > SIZE_MAX / sizeof(**values))
return false;
size_t next = *capacity ? *capacity : 16U;
while (next < count)
{
if (next > SIZE_MAX / 2U)
{
next = count;
break;
}
next *= 2U;
}
if (next > SIZE_MAX / sizeof(**values))
next = count;
void * result = realloc(*values, next * sizeof(**values));
if (!result)
return false;
*values = result;
*capacity = next;
return true;
}
static bool addLocalRoot(WindowsClipboardLocalFiles * files,
wchar_t * path, LG_ClipboardFileError * error)
{
WindowsClipboardLocalNode node;
memset(&node, 0, sizeof(node));
node.path = path;
node.name = baseName(path);
if (!node.name)
{
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
return false;
}
if (!validComponent(node.name))
{
free(node.name);
setError(error, LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED);
return false;
}
if (!captureIdentity(node.path, &node.identity, error))
{
free(node.name);
return false;
}
for (size_t index = 0; index < files->rootCount; ++index)
{
const uint64_t root = files->roots[index];
if (sameComponent(files->nodes[root - 1U].name, node.name))
{
free(node.name);
setError(error, LG_CLIPBOARD_FILE_ERROR_INVALID);
return false;
}
}
if (!reserveLocalNodes(files, files->nodeCount + 1U) ||
!reserveIDs(&files->roots, &files->rootCapacity,
files->rootCount + 1U))
{
free(node.name);
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
return false;
}
node.id = files->nodeCount + 1U;
files->nodes[files->nodeCount++] = node;
files->roots[files->rootCount++] = node.id;
return true;
}
static bool refreshLocalRoots(WindowsClipboardLocalFiles * files,
LG_ClipboardFileError * error)
{
WindowsClipboardFileIdentity * identities = calloc(files->rootCount,
sizeof(*identities));
if (!identities && files->rootCount)
{
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
return false;
}
for (size_t index = 0; index < files->rootCount; ++index)
{
const uint64_t id = files->roots[index];
if (!captureIdentity(files->nodes[id - 1U].path, &identities[index],
error))
{
free(identities);
return false;
}
}
for (size_t index = 0; index < files->rootCount; ++index)
{
const uint64_t id = files->roots[index];
files->nodes[id - 1U].identity = identities[index];
}
free(identities);
return true;
}
static void freeLocalNodeArray(WindowsClipboardLocalNode * nodes,
size_t count)
{
if (!nodes)
return;
for (size_t index = 0; index < count; ++index)
freeLocalNode(&nodes[index]);
free(nodes);
}
static bool loadLocalChildren(WindowsClipboardLocalFiles * files,
size_t parentIndex, LG_ClipboardFileError * error)
{
if (parentIndex >= files->nodeCount)
{
setError(error, LG_CLIPBOARD_FILE_ERROR_NOT_FOUND);
return false;
}
WindowsClipboardLocalNode * parent = &files->nodes[parentIndex];
if (parent->identity.type != KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY)
{
setError(error, LG_CLIPBOARD_FILE_ERROR_NOT_DIRECTORY);
return false;
}
if (parent->childrenLoaded)
return true;
if (!checkIdentity(parent->path, &parent->identity, error))
return false;
wchar_t * parentPath = duplicateWide(parent->path);
wchar_t * pattern = parentPath ? joinPath(parentPath, L"*") : NULL;
if (!parentPath || !pattern)
{
free(parentPath);
free(pattern);
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
return false;
}
WIN32_FIND_DATAW found;
memset(&found, 0, sizeof(found));
HANDLE search = FindFirstFileExW(pattern, FindExInfoBasic, &found,
FindExSearchNameMatch, NULL, 0);
free(pattern);
if (search == INVALID_HANDLE_VALUE)
{
const DWORD winError = GetLastError();
if (winError == ERROR_FILE_NOT_FOUND &&
checkIdentity(parentPath, &parent->identity, error))
{
parent->childrenLoaded = true;
free(parentPath);
return true;
}
free(parentPath);
if (winError != ERROR_FILE_NOT_FOUND)
setError(error, fileError(winError));
return false;
}
WindowsClipboardLocalNode * children = NULL;
size_t count = 0;
size_t capacity = 0;
bool success = true;
DWORD winError = ERROR_SUCCESS;
do
{
if (!wcscmp(found.cFileName, L".") ||
!wcscmp(found.cFileName, L".."))
continue;
if (!validComponent(found.cFileName))
{
setError(error, LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED);
success = false;
break;
}
for (size_t index = 0; index < count; ++index)
if (sameComponent(children[index].name, found.cFileName))
{
setError(error, LG_CLIPBOARD_FILE_ERROR_INVALID);
success = false;
break;
}
if (!success)
break;
if (count == capacity)
{
size_t next = capacity ? capacity * 2U : 16U;
if (next < capacity || next > SIZE_MAX / sizeof(*children))
{
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
success = false;
break;
}
void * result = realloc(children, next * sizeof(*children));
if (!result)
{
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
success = false;
break;
}
children = result;
memset(children + capacity, 0,
(next - capacity) * sizeof(*children));
capacity = next;
}
WindowsClipboardLocalNode * child = &children[count];
child->name = duplicateWide(found.cFileName);
child->path = child->name ? joinPath(parentPath, child->name) : NULL;
if (!child->name || !child->path)
{
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
success = false;
break;
}
if (!captureIdentity(child->path, &child->identity, error))
{
success = false;
break;
}
++count;
}
while (FindNextFileW(search, &found));
if (success)
{
winError = GetLastError();
if (winError != ERROR_NO_MORE_FILES)
{
setError(error, fileError(winError));
success = false;
}
}
FindClose(search);
if (success && !checkIdentity(parentPath,
&files->nodes[parentIndex].identity, error))
success = false;
free(parentPath);
if (success && (!reserveLocalNodes(files, files->nodeCount + count) ||
!reserveIDs(&files->nodes[parentIndex].children,
&files->nodes[parentIndex].childCapacity, count)))
{
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
success = false;
}
if (!success)
{
freeLocalNodeArray(children, count + (count < capacity ? 1U : 0U));
return false;
}
parent = &files->nodes[parentIndex];
for (size_t index = 0; index < count; ++index)
{
WindowsClipboardLocalNode * child = &children[index];
child->id = files->nodeCount + 1U;
parent->children[parent->childCount++] = child->id;
files->nodes[files->nodeCount++] = *child;
memset(child, 0, sizeof(*child));
}
parent->childrenLoaded = true;
freeLocalNodeArray(children, count);
return true;
}
static bool wideToUTF8(const wchar_t * text, uint8_t ** data,
size_t * size)
{
*data = NULL;
*size = 0;
if (!text || !*text)
{
SetLastError(ERROR_INVALID_DATA);
return false;
}
const size_t length = wcslen(text);
if (length > INT_MAX)
{
SetLastError(ERROR_INVALID_DATA);
return false;
}
const int count = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
text, (int)length, NULL, 0, NULL, NULL);
if (count <= 0)
return false;
uint8_t * output = malloc((size_t)count);
if (!output)
{
SetLastError(ERROR_OUTOFMEMORY);
return false;
}
if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, text, (int)length,
(char *)output, count, NULL, NULL) != count)
{
free(output);
return false;
}
*data = output;
*size = (size_t)count;
return true;
}
WindowsClipboardLocalFiles * windowsClipboardLocalFilesCapture(HDROP drop,
LG_ClipboardFileError * error)
{
setError(error, LG_CLIPBOARD_FILE_ERROR_INVALID);
if (!drop)
return NULL;
WindowsClipboardLocalFiles * files = calloc(1, sizeof(*files));
if (!files)
{
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
return NULL;
}
files->refs = 1;
if (!InitializeCriticalSectionEx(&files->lock, 0, 0))
{
const DWORD winError = GetLastError();
free(files);
setError(error, fileError(winError ? winError : ERROR_OUTOFMEMORY));
return NULL;
}
const UINT count = DragQueryFileW(drop, UINT_MAX, NULL, 0);
if (!count)
{
windowsClipboardLocalFilesRelease(files);
return NULL;
}
for (UINT index = 0; index < count; ++index)
{
const UINT length = DragQueryFileW(drop, index, NULL, 0);
if (!length)
{
windowsClipboardLocalFilesRelease(files);
return NULL;
}
wchar_t * input = malloc(((size_t)length + 1U) * sizeof(*input));
if (!input)
{
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
windowsClipboardLocalFilesRelease(files);
return NULL;
}
if (DragQueryFileW(drop, index, input, length + 1U) != length)
{
free(input);
windowsClipboardLocalFilesRelease(files);
return NULL;
}
wchar_t * path = normalizeSourcePath(input);
free(input);
if (!path)
{
setError(error, fileError(GetLastError()));
windowsClipboardLocalFilesRelease(files);
return NULL;
}
if (!addLocalRoot(files, path, error))
{
free(path);
windowsClipboardLocalFilesRelease(files);
return NULL;
}
}
setError(error, LG_CLIPBOARD_FILE_ERROR_NONE);
return files;
}
void windowsClipboardLocalFilesRetain(WindowsClipboardLocalFiles * files)
{
if (files)
InterlockedIncrement(&files->refs);
}
void windowsClipboardLocalFilesRelease(WindowsClipboardLocalFiles * files)
{
if (!files || InterlockedDecrement(&files->refs))
return;
for (size_t index = 0; index < files->nodeCount; ++index)
freeLocalNode(&files->nodes[index]);
free(files->nodes);
free(files->roots);
DeleteCriticalSection(&files->lock);
free(files);
}
bool windowsClipboardLocalFilesList(WindowsClipboardLocalFiles * files,
uint64_t node, void ** data, size_t * size,
LG_ClipboardFileError * error)
{
if (!data || !size)
{
setError(error, LG_CLIPBOARD_FILE_ERROR_INVALID);
return false;
}
*data = NULL;
*size = 0;
setError(error, LG_CLIPBOARD_FILE_ERROR_NONE);
if (!files)
{
setError(error, LG_CLIPBOARD_FILE_ERROR_INVALID);
return false;
}
EnterCriticalSection(&files->lock);
const uint64_t * children = NULL;
size_t childCount = 0;
bool success = true;
if (node == KVMFR_CLIPBOARD_FILE_ROOT_NODE)
{
if (!files->published && !refreshLocalRoots(files, error))
success = false;
if (success)
{
files->published = true;
children = files->roots;
childCount = files->rootCount;
}
}
else if (!files->published || node > files->nodeCount)
{
setError(error, !files->published ? LG_CLIPBOARD_FILE_ERROR_INVALID :
LG_CLIPBOARD_FILE_ERROR_NOT_FOUND);
success = false;
}
else
{
const size_t index = (size_t)(node - 1U);
if (!loadLocalChildren(files, index, error))
success = false;
else
{
children = files->nodes[index].children;
childCount = files->nodes[index].childCount;
}
}
uint8_t * output = NULL;
size_t used = 0;
size_t capacity = 0;
for (size_t index = 0; success && index < childCount; ++index)
{
const uint64_t id = children[index];
if (!id || id > files->nodeCount)
{
setError(error, LG_CLIPBOARD_FILE_ERROR_INVALID);
success = false;
break;
}
const WindowsClipboardLocalNode * child = &files->nodes[id - 1U];
uint8_t * name = NULL;
size_t nameSize = 0;
if (!wideToUTF8(child->name, &name, &nameSize))
{
setError(error, fileError(GetLastError()));
success = false;
break;
}
const size_t recordSize = KVMFR_CLIPBOARD_FILE_ENTRY_BYTES(nameSize);
if (recordSize < sizeof(KVMFRClipboardFileEntry) ||
used > SIZE_MAX - recordSize)
{
free(name);
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
success = false;
break;
}
if (used + recordSize > capacity)
{
size_t next = capacity ? capacity : 4096U;
while (next < used + recordSize)
{
if (next > SIZE_MAX / 2U)
{
next = used + recordSize;
break;
}
next *= 2U;
}
uint8_t * result = realloc(output, next);
if (!result)
{
free(name);
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
success = false;
break;
}
output = result;
capacity = next;
}
KVMFRClipboardFileEntry entry;
memset(&entry, 0, sizeof(entry));
entry.node = child->id;
entry.size = child->identity.size;
entry.createdNs = child->identity.createdNs;
entry.modifiedNs = child->identity.modifiedNs;
entry.type = child->identity.type;
entry.nameLength = (uint32_t)nameSize;
memset(output + used, 0, recordSize);
memcpy(output + used, &entry, sizeof(entry));
memcpy(output + used + sizeof(entry), name, nameSize);
used += recordSize;
free(name);
}
LeaveCriticalSection(&files->lock);
if (!success)
{
free(output);
return false;
}
*data = output;
*size = used;
return true;
}
bool windowsClipboardLocalFilesRead(WindowsClipboardLocalFiles * files,
uint64_t node, uint64_t offset, uint32_t length, void ** data,
size_t * size, LG_ClipboardFileError * error)
{
if (!data || !size)
{
setError(error, LG_CLIPBOARD_FILE_ERROR_INVALID);
return false;
}
*data = NULL;
*size = 0;
setError(error, LG_CLIPBOARD_FILE_ERROR_NONE);
if (!files || length > KVMFR_CLIPBOARD_FILE_READ_BYTES ||
offset > INT64_MAX || offset > UINT64_MAX - length)
{
setError(error, LG_CLIPBOARD_FILE_ERROR_INVALID);
return false;
}
wchar_t * path = NULL;
WindowsClipboardFileIdentity identity;
memset(&identity, 0, sizeof(identity));
EnterCriticalSection(&files->lock);
if (!files->published || !node || node > files->nodeCount)
setError(error, LG_CLIPBOARD_FILE_ERROR_NOT_FOUND);
else
{
const WindowsClipboardLocalNode * source = &files->nodes[node - 1U];
if (source->identity.type != KVMFR_CLIPBOARD_FILE_TYPE_REGULAR)
setError(error, LG_CLIPBOARD_FILE_ERROR_IS_DIRECTORY);
else
{
path = duplicateWide(source->path);
identity = source->identity;
if (!path)
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
}
}
LeaveCriticalSection(&files->lock);
if (!path)
return false;
HANDLE file = CreateFileW(path, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS |
FILE_FLAG_OPEN_REPARSE_POINT,
NULL);
free(path);
if (file == INVALID_HANDLE_VALUE)
{
setError(error, fileError(GetLastError()));
return false;
}
FILE_STANDARD_INFO standard;
FILE_ATTRIBUTE_TAG_INFO tag;
BY_HANDLE_FILE_INFORMATION before;
memset(&standard, 0, sizeof(standard));
memset(&tag, 0, sizeof(tag));
memset(&before, 0, sizeof(before));
bool success = GetFileInformationByHandleEx(file, FileStandardInfo,
&standard, sizeof(standard)) &&
GetFileInformationByHandleEx(file, FileAttributeTagInfo,
&tag, sizeof(tag)) &&
GetFileInformationByHandle(file, &before);
DWORD winError = success ? ERROR_SUCCESS : GetLastError();
if (success && (standard.Directory ||
(tag.FileAttributes &
(FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_REPARSE_POINT))))
{
setError(error, standard.Directory ?
LG_CLIPBOARD_FILE_ERROR_IS_DIRECTORY :
LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED);
success = false;
}
if (success && (!sameIdentity(&identity, &before) ||
standard.EndOfFile.QuadPart < 0 ||
(uint64_t)standard.EndOfFile.QuadPart != identity.size))
{
setError(error, LG_CLIPBOARD_FILE_ERROR_STALE);
success = false;
}
if (success && (offset > identity.size ||
length > identity.size - offset))
{
setError(error, LG_CLIPBOARD_FILE_ERROR_INVALID);
success = false;
}
const uint32_t wanted = success ? length : 0;
uint8_t * output = wanted ? malloc(wanted) : NULL;
if (success && wanted && !output)
{
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
success = false;
}
LARGE_INTEGER position;
DWORD read = 0;
position.QuadPart = (LONGLONG)offset;
if (success && (!SetFilePointerEx(file, position, NULL, FILE_BEGIN) ||
(wanted && !ReadFile(file, output, wanted, &read, NULL))))
{
winError = GetLastError();
success = false;
}
BY_HANDLE_FILE_INFORMATION after;
memset(&after, 0, sizeof(after));
if (success && !GetFileInformationByHandle(file, &after))
{
winError = GetLastError();
success = false;
}
if (success && !sameIdentity(&identity, &after))
{
setError(error, LG_CLIPBOARD_FILE_ERROR_STALE);
success = false;
}
CloseHandle(file);
if (!success || read != wanted)
{
free(output);
if (error && *error == LG_CLIPBOARD_FILE_ERROR_NONE)
*error = fileError(winError ? winError : ERROR_READ_FAULT);
return false;
}
*data = output;
*size = wanted;
return true;
}
static wchar_t * utf8ToWide(const uint8_t * data, size_t size)
{
if (!data || !size || size > INT_MAX)
{
SetLastError(ERROR_INVALID_DATA);
return NULL;
}
for (size_t index = 0; index < size; ++index)
if (!data[index])
{
SetLastError(ERROR_INVALID_DATA);
return NULL;
}
const int count = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
(const char *)data, (int)size, NULL, 0);
if (count <= 0)
return NULL;
wchar_t * output = malloc(((size_t)count + 1U) * sizeof(*output));
if (!output)
{
SetLastError(ERROR_OUTOFMEMORY);
return NULL;
}
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
(const char *)data, (int)size, output, count) != count)
{
free(output);
return NULL;
}
output[count] = L'\0';
return output;
}
static void freeRemoteEntry(WindowsClipboardRemoteEntry * entry)
{
free(entry->name);
memset(entry, 0, sizeof(*entry));
}
static void remoteManifestRetain(WindowsClipboardRemoteManifest * manifest)
{
InterlockedIncrement(&manifest->refs);
}
static void remoteManifestRelease(WindowsClipboardRemoteManifest * manifest)
{
if (InterlockedDecrement(&manifest->refs))
return;
for (size_t index = 0; index < manifest->entryCount; ++index)
freeRemoteEntry(&manifest->entries[index]);
free(manifest->entries);
DeleteCriticalSection(&manifest->lock);
free(manifest);
}
static bool reserveRemoteEntries(WindowsClipboardRemoteManifest * manifest,
size_t count)
{
if (count <= manifest->entryCapacity)
return true;
if (count > SIZE_MAX / sizeof(*manifest->entries))
return false;
size_t capacity = manifest->entryCapacity ? manifest->entryCapacity : 16U;
while (capacity < count)
{
if (capacity > SIZE_MAX / 2U)
{
capacity = count;
break;
}
capacity *= 2U;
}
if (capacity > SIZE_MAX / sizeof(*manifest->entries))
capacity = count;
void * entries = realloc(manifest->entries,
capacity * sizeof(*manifest->entries));
if (!entries)
return false;
manifest->entries = entries;
manifest->entryCapacity = capacity;
return true;
}
static ptrdiff_t findRemoteEntry(
const WindowsClipboardRemoteManifest * manifest, uint64_t node)
{
for (size_t index = 0; index < manifest->entryCount; ++index)
if (manifest->entries[index].node == node)
return (ptrdiff_t)index;
return -1;
}
WindowsClipboardRemoteManifest * windowsClipboardRemoteManifestCreate(void)
{
WindowsClipboardRemoteManifest * manifest = calloc(1, sizeof(*manifest));
if (!manifest)
return NULL;
manifest->refs = 1;
if (!InitializeCriticalSectionEx(&manifest->lock, 0, 0))
{
free(manifest);
return NULL;
}
return manifest;
}
void windowsClipboardRemoteManifestFree(
WindowsClipboardRemoteManifest * manifest)
{
if (manifest)
remoteManifestRelease(manifest);
}
bool windowsClipboardRemoteManifestAppendList(
WindowsClipboardRemoteManifest * manifest, uint64_t parent,
const void * data, size_t size, LG_ClipboardFileError * error)
{
setError(error, LG_CLIPBOARD_FILE_ERROR_INVALID);
if (!manifest || (!data && size))
return false;
EnterCriticalSection(&manifest->lock);
if (manifest->frozen)
{
LeaveCriticalSection(&manifest->lock);
return false;
}
ptrdiff_t parentIndex = -1;
uint8_t * parentState = &manifest->rootState;
if (parent)
{
parentIndex = findRemoteEntry(manifest, parent);
if (parentIndex < 0 ||
manifest->entries[parentIndex].type !=
KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY)
{
LeaveCriticalSection(&manifest->lock);
return false;
}
parentState = &manifest->entries[parentIndex].listState;
}
if (*parentState != 1U)
{
LeaveCriticalSection(&manifest->lock);
return false;
}
WindowsClipboardRemoteEntry * pending = NULL;
size_t count = 0;
size_t capacity = 0;
size_t offset = 0;
bool success = true;
while (offset < size)
{
if (size - offset < sizeof(KVMFRClipboardFileEntry))
{
success = false;
break;
}
KVMFRClipboardFileEntry wire;
memcpy(&wire, (const uint8_t *)data + offset, sizeof(wire));
const size_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 < sizeof(wire) || bytes > size - offset ||
wire.nameLength > bytes - sizeof(wire))
{
success = false;
break;
}
wchar_t * name = utf8ToWide((const uint8_t *)data + offset +
sizeof(wire), wire.nameLength);
if (!name || !validComponent(name))
{
if (!name && GetLastError() == ERROR_OUTOFMEMORY)
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
free(name);
success = false;
break;
}
for (size_t pad = sizeof(wire) + wire.nameLength; pad < bytes; ++pad)
if (((const uint8_t *)data)[offset + pad])
{
success = false;
break;
}
if (!success)
{
free(name);
break;
}
if (findRemoteEntry(manifest, wire.node) >= 0)
success = false;
for (size_t index = 0; success && index < count; ++index)
if (pending[index].node == wire.node ||
sameComponent(pending[index].name, name))
success = false;
for (size_t index = 0; success && index < manifest->entryCount; ++index)
if (manifest->entries[index].parent == parent &&
sameComponent(manifest->entries[index].name, name))
success = false;
if (!success)
{
free(name);
break;
}
if (count == capacity)
{
size_t next = capacity ? capacity * 2U : 16U;
if (next < capacity || next > SIZE_MAX / sizeof(*pending))
{
free(name);
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
success = false;
break;
}
void * result = realloc(pending, next * sizeof(*pending));
if (!result)
{
free(name);
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
success = false;
break;
}
pending = result;
memset(pending + capacity, 0,
(next - capacity) * sizeof(*pending));
capacity = next;
}
WindowsClipboardRemoteEntry * entry = &pending[count++];
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 = name;
offset += bytes;
}
if (success && offset != size)
success = false;
if (success && (manifest->entryCount > SIZE_MAX - count ||
!reserveRemoteEntries(manifest, manifest->entryCount + count)))
{
setError(error, LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
success = false;
}
if (success)
{
for (size_t index = 0; index < count; ++index)
{
manifest->entries[manifest->entryCount++] = pending[index];
memset(&pending[index], 0, sizeof(pending[index]));
}
if (parentIndex < 0)
manifest->rootState = 2U;
else
manifest->entries[parentIndex].listState = 2U;
setError(error, LG_CLIPBOARD_FILE_ERROR_NONE);
}
else if (!error || *error == LG_CLIPBOARD_FILE_ERROR_INVALID)
setError(error, LG_CLIPBOARD_FILE_ERROR_INVALID);
for (size_t index = 0; index < count; ++index)
freeRemoteEntry(&pending[index]);
free(pending);
LeaveCriticalSection(&manifest->lock);
return success;
}
bool windowsClipboardRemoteManifestNextDirectory(
WindowsClipboardRemoteManifest * manifest, uint64_t * node)
{
if (!manifest || !node)
return false;
EnterCriticalSection(&manifest->lock);
bool found = false;
if (!manifest->frozen && manifest->rootState == 0U)
{
manifest->rootState = 1U;
*node = KVMFR_CLIPBOARD_FILE_ROOT_NODE;
found = true;
}
else if (!manifest->frozen)
for (size_t index = 0; index < manifest->entryCount; ++index)
{
WindowsClipboardRemoteEntry * entry = &manifest->entries[index];
if (entry->type == KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY &&
entry->listState == 0U)
{
entry->listState = 1U;
*node = entry->node;
found = true;
break;
}
}
LeaveCriticalSection(&manifest->lock);
return found;
}
bool windowsClipboardRemoteManifestHasEntries(
const WindowsClipboardRemoteManifest * manifest)
{
if (!manifest)
return false;
WindowsClipboardRemoteManifest * mutableManifest =
(WindowsClipboardRemoteManifest *)manifest;
EnterCriticalSection(&mutableManifest->lock);
const bool result = mutableManifest->entryCount != 0;
LeaveCriticalSection(&mutableManifest->lock);
return result;
}
typedef struct WindowsClipboardFileDataObject
{
IDataObject iface;
volatile LONG refs;
WindowsClipboardRemoteManifest * manifest;
uint64_t dataset;
uint64_t acquisition;
WindowsClipboardRemoteProviderOps ops;
void * opaque;
FILEDESCRIPTORW * descriptors;
size_t descriptorCount;
UINT fileDescriptorFormat;
UINT fileContentsFormat;
UINT preferredDropEffectFormat;
}
WindowsClipboardFileDataObject;
typedef struct WindowsClipboardFileStream
{
IStream iface;
volatile LONG refs;
CRITICAL_SECTION lock;
WindowsClipboardFileDataObject * owner;
size_t entryIndex;
uint64_t offset;
}
WindowsClipboardFileStream;
typedef struct WindowsClipboardFormatEnumerator
{
IEnumFORMATETC iface;
volatile LONG refs;
FORMATETC formats[3];
ULONG count;
ULONG index;
}
WindowsClipboardFormatEnumerator;
static WindowsClipboardFileDataObject * dataObjectFromInterface(
IDataObject * iface)
{
return CONTAINING_RECORD(iface, WindowsClipboardFileDataObject, iface);
}
static WindowsClipboardFileStream * streamFromInterface(IStream * iface)
{
return CONTAINING_RECORD(iface, WindowsClipboardFileStream, iface);
}
static WindowsClipboardFormatEnumerator * formatEnumeratorFromInterface(
IEnumFORMATETC * iface)
{
return CONTAINING_RECORD(iface, WindowsClipboardFormatEnumerator, iface);
}
static HRESULT STDMETHODCALLTYPE dataObjectQueryInterface(IDataObject * self,
REFIID iid, void ** object);
static ULONG STDMETHODCALLTYPE dataObjectAddRef(IDataObject * self);
static ULONG STDMETHODCALLTYPE dataObjectRelease(IDataObject * self);
static HRESULT STDMETHODCALLTYPE dataObjectGetData(IDataObject * self,
FORMATETC * format, STGMEDIUM * medium);
static HRESULT STDMETHODCALLTYPE dataObjectGetDataHere(IDataObject * self,
FORMATETC * format, STGMEDIUM * medium);
static HRESULT STDMETHODCALLTYPE dataObjectQueryGetData(IDataObject * self,
FORMATETC * format);
static HRESULT STDMETHODCALLTYPE dataObjectGetCanonicalFormatEtc(
IDataObject * self, FORMATETC * input, FORMATETC * output);
static HRESULT STDMETHODCALLTYPE dataObjectSetData(IDataObject * self,
FORMATETC * format, STGMEDIUM * medium, BOOL release);
static HRESULT STDMETHODCALLTYPE dataObjectEnumFormatEtc(IDataObject * self,
DWORD direction, IEnumFORMATETC ** enumerator);
static HRESULT STDMETHODCALLTYPE dataObjectDAdvise(IDataObject * self,
FORMATETC * format, DWORD flags, IAdviseSink * sink,
DWORD * connection);
static HRESULT STDMETHODCALLTYPE dataObjectDUnadvise(IDataObject * self,
DWORD connection);
static HRESULT STDMETHODCALLTYPE dataObjectEnumDAdvise(IDataObject * self,
IEnumSTATDATA ** enumerator);
static const IDataObjectVtbl dataObjectVTable =
{
.QueryInterface = dataObjectQueryInterface,
.AddRef = dataObjectAddRef,
.Release = dataObjectRelease,
.GetData = dataObjectGetData,
.GetDataHere = dataObjectGetDataHere,
.QueryGetData = dataObjectQueryGetData,
.GetCanonicalFormatEtc = dataObjectGetCanonicalFormatEtc,
.SetData = dataObjectSetData,
.EnumFormatEtc = dataObjectEnumFormatEtc,
.DAdvise = dataObjectDAdvise,
.DUnadvise = dataObjectDUnadvise,
.EnumDAdvise = dataObjectEnumDAdvise,
};
static HRESULT STDMETHODCALLTYPE streamQueryInterface(IStream * self,
REFIID iid, void ** object);
static ULONG STDMETHODCALLTYPE streamAddRef(IStream * self);
static ULONG STDMETHODCALLTYPE streamRelease(IStream * self);
static HRESULT STDMETHODCALLTYPE streamRead(IStream * self, void * data,
ULONG length, ULONG * read);
static HRESULT STDMETHODCALLTYPE streamWrite(IStream * self,
const void * data, ULONG length, ULONG * written);
static HRESULT STDMETHODCALLTYPE streamSeek(IStream * self,
LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER * position);
static HRESULT STDMETHODCALLTYPE streamSetSize(IStream * self,
ULARGE_INTEGER size);
static HRESULT STDMETHODCALLTYPE streamCopyTo(IStream * self,
IStream * target, ULARGE_INTEGER count, ULARGE_INTEGER * read,
ULARGE_INTEGER * written);
static HRESULT STDMETHODCALLTYPE streamCommit(IStream * self, DWORD flags);
static HRESULT STDMETHODCALLTYPE streamRevert(IStream * self);
static HRESULT STDMETHODCALLTYPE streamLockRegion(IStream * self,
ULARGE_INTEGER offset, ULARGE_INTEGER count, DWORD type);
static HRESULT STDMETHODCALLTYPE streamUnlockRegion(IStream * self,
ULARGE_INTEGER offset, ULARGE_INTEGER count, DWORD type);
static HRESULT STDMETHODCALLTYPE streamStat(IStream * self, STATSTG * stat,
DWORD flags);
static HRESULT STDMETHODCALLTYPE streamClone(IStream * self,
IStream ** clone);
static const IStreamVtbl streamVTable =
{
.QueryInterface = streamQueryInterface,
.AddRef = streamAddRef,
.Release = streamRelease,
.Read = streamRead,
.Write = streamWrite,
.Seek = streamSeek,
.SetSize = streamSetSize,
.CopyTo = streamCopyTo,
.Commit = streamCommit,
.Revert = streamRevert,
.LockRegion = streamLockRegion,
.UnlockRegion = streamUnlockRegion,
.Stat = streamStat,
.Clone = streamClone,
};
static HRESULT STDMETHODCALLTYPE formatEnumeratorQueryInterface(
IEnumFORMATETC * self, REFIID iid, void ** object);
static ULONG STDMETHODCALLTYPE formatEnumeratorAddRef(IEnumFORMATETC * self);
static ULONG STDMETHODCALLTYPE formatEnumeratorRelease(IEnumFORMATETC * self);
static HRESULT STDMETHODCALLTYPE formatEnumeratorNext(IEnumFORMATETC * self,
ULONG count, FORMATETC * formats, ULONG * fetched);
static HRESULT STDMETHODCALLTYPE formatEnumeratorSkip(IEnumFORMATETC * self,
ULONG count);
static HRESULT STDMETHODCALLTYPE formatEnumeratorReset(IEnumFORMATETC * self);
static HRESULT STDMETHODCALLTYPE formatEnumeratorClone(IEnumFORMATETC * self,
IEnumFORMATETC ** clone);
static const IEnumFORMATETCVtbl formatEnumeratorVTable =
{
.QueryInterface = formatEnumeratorQueryInterface,
.AddRef = formatEnumeratorAddRef,
.Release = formatEnumeratorRelease,
.Next = formatEnumeratorNext,
.Skip = formatEnumeratorSkip,
.Reset = formatEnumeratorReset,
.Clone = formatEnumeratorClone,
};
static HRESULT STDMETHODCALLTYPE dataObjectQueryInterface(IDataObject * self,
REFIID iid, void ** object)
{
if (!object)
return E_POINTER;
*object = NULL;
if (IsEqualIID(iid, &IID_IUnknown) || IsEqualIID(iid, &IID_IDataObject))
{
*object = self;
IDataObject_AddRef(self);
return S_OK;
}
return E_NOINTERFACE;
}
static ULONG STDMETHODCALLTYPE dataObjectAddRef(IDataObject * self)
{
WindowsClipboardFileDataObject * object = dataObjectFromInterface(self);
return (ULONG)InterlockedIncrement(&object->refs);
}
static ULONG STDMETHODCALLTYPE dataObjectRelease(IDataObject * self)
{
WindowsClipboardFileDataObject * object = dataObjectFromInterface(self);
const LONG refs =
InterlockedDecrement(&object->refs);
if (refs)
return (ULONG)refs;
object->ops.release(object->opaque, object->dataset, object->acquisition);
remoteManifestRelease(object->manifest);
free(object->descriptors);
free(object);
return 0;
}
static bool dataObjectFormatMatches(const FORMATETC * format,
UINT clipboardFormat, DWORD medium)
{
return format && format->cfFormat == clipboardFormat &&
format->dwAspect == DVASPECT_CONTENT && format->lindex >= -1 &&
(format->tymed & medium) != 0;
}
static HRESULT createFileStream(WindowsClipboardFileDataObject * owner,
size_t entryIndex, uint64_t offset, IStream ** result)
{
*result = NULL;
WindowsClipboardFileStream * stream = calloc(1, sizeof(*stream));
if (!stream)
return E_OUTOFMEMORY;
if (!InitializeCriticalSectionEx(&stream->lock, 0, 0))
{
const DWORD winError = GetLastError();
free(stream);
return HRESULT_FROM_WIN32(winError ? winError : ERROR_OUTOFMEMORY);
}
stream->iface.lpVtbl = &streamVTable;
stream->refs = 1;
stream->owner = owner;
stream->entryIndex = entryIndex;
stream->offset = offset;
IDataObject_AddRef(&owner->iface);
*result = &stream->iface;
return S_OK;
}
static HRESULT STDMETHODCALLTYPE dataObjectGetData(IDataObject * self,
FORMATETC * format, STGMEDIUM * medium)
{
if (!format || !medium)
return E_POINTER;
memset(medium, 0, sizeof(*medium));
WindowsClipboardFileDataObject * object = dataObjectFromInterface(self);
if (dataObjectFormatMatches(format, object->fileDescriptorFormat,
TYMED_HGLOBAL) && format->lindex == -1)
{
if (object->descriptorCount > UINT_MAX ||
object->descriptorCount >
(SIZE_MAX - offsetof(FILEGROUPDESCRIPTORW, fgd)) /
sizeof(*object->descriptors))
return E_OUTOFMEMORY;
const SIZE_T bytes = offsetof(FILEGROUPDESCRIPTORW, fgd) +
object->descriptorCount * sizeof(*object->descriptors);
HGLOBAL memory = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, bytes);
if (!memory)
return E_OUTOFMEMORY;
FILEGROUPDESCRIPTORW * group = GlobalLock(memory);
if (!group)
{
GlobalFree(memory);
return E_OUTOFMEMORY;
}
group->cItems = (UINT)object->descriptorCount;
memcpy(group->fgd, object->descriptors,
object->descriptorCount * sizeof(*object->descriptors));
GlobalUnlock(memory);
medium->tymed = TYMED_HGLOBAL;
medium->hGlobal = memory;
return S_OK;
}
if (dataObjectFormatMatches(format, object->fileContentsFormat,
TYMED_ISTREAM))
{
if (format->lindex < 0 ||
(size_t)format->lindex >= object->manifest->entryCount)
return DV_E_LINDEX;
const WindowsClipboardRemoteEntry * entry =
&object->manifest->entries[(size_t)format->lindex];
if (entry->type != KVMFR_CLIPBOARD_FILE_TYPE_REGULAR)
return DV_E_LINDEX;
IStream * stream = NULL;
const HRESULT result = createFileStream(object,
(size_t)format->lindex, 0, &stream);
if (FAILED(result))
return result;
medium->tymed = TYMED_ISTREAM;
medium->pstm = stream;
return S_OK;
}
if (dataObjectFormatMatches(format, object->preferredDropEffectFormat,
TYMED_HGLOBAL) && format->lindex == -1)
{
HGLOBAL memory = GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD));
if (!memory)
return E_OUTOFMEMORY;
DWORD * effect = 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;
}
static HRESULT STDMETHODCALLTYPE dataObjectGetDataHere(IDataObject * self,
FORMATETC * format, STGMEDIUM * medium)
{
(void)self;
(void)format;
(void)medium;
return DATA_E_FORMATETC;
}
static HRESULT STDMETHODCALLTYPE dataObjectQueryGetData(IDataObject * self,
FORMATETC * format)
{
if (!format)
return E_POINTER;
WindowsClipboardFileDataObject * object = dataObjectFromInterface(self);
if (dataObjectFormatMatches(format, object->fileDescriptorFormat,
TYMED_HGLOBAL) && format->lindex == -1)
return S_OK;
if (dataObjectFormatMatches(format, object->preferredDropEffectFormat,
TYMED_HGLOBAL) && format->lindex == -1)
return S_OK;
if (dataObjectFormatMatches(format, object->fileContentsFormat,
TYMED_ISTREAM))
{
if (format->lindex == -1)
for (size_t index = 0; index < object->manifest->entryCount; ++index)
if (object->manifest->entries[index].type ==
KVMFR_CLIPBOARD_FILE_TYPE_REGULAR)
return S_OK;
if (format->lindex >= 0 &&
(size_t)format->lindex < object->manifest->entryCount &&
object->manifest->entries[(size_t)format->lindex].type ==
KVMFR_CLIPBOARD_FILE_TYPE_REGULAR)
return S_OK;
}
return DV_E_FORMATETC;
}
static HRESULT STDMETHODCALLTYPE dataObjectGetCanonicalFormatEtc(
IDataObject * self, FORMATETC * input, FORMATETC * output)
{
(void)self;
if (!input || !output)
return E_POINTER;
*output = *input;
output->ptd = NULL;
return DATA_S_SAMEFORMATETC;
}
static HRESULT STDMETHODCALLTYPE dataObjectSetData(IDataObject * self,
FORMATETC * format, STGMEDIUM * medium, BOOL release)
{
(void)self;
(void)format;
(void)medium;
(void)release;
return E_NOTIMPL;
}
static HRESULT createFormatEnumerator(
const WindowsClipboardFileDataObject * object, ULONG index,
IEnumFORMATETC ** result)
{
*result = NULL;
WindowsClipboardFormatEnumerator * enumerator =
calloc(1, sizeof(*enumerator));
if (!enumerator)
return E_OUTOFMEMORY;
enumerator->iface.lpVtbl = &formatEnumeratorVTable;
enumerator->refs = 1;
enumerator->formats[0].cfFormat =
(CLIPFORMAT)object->fileDescriptorFormat;
enumerator->formats[0].dwAspect = DVASPECT_CONTENT;
enumerator->formats[0].lindex = -1;
enumerator->formats[0].tymed = TYMED_HGLOBAL;
bool hasRegular = false;
for (size_t entry = 0; entry < object->manifest->entryCount; ++entry)
if (object->manifest->entries[entry].type ==
KVMFR_CLIPBOARD_FILE_TYPE_REGULAR)
{
hasRegular = true;
break;
}
ULONG format = 1;
if (hasRegular)
{
enumerator->formats[format].cfFormat =
(CLIPFORMAT)object->fileContentsFormat;
enumerator->formats[format].dwAspect = DVASPECT_CONTENT;
enumerator->formats[format].lindex = -1;
enumerator->formats[format].tymed = TYMED_ISTREAM;
++format;
}
enumerator->formats[format].cfFormat =
(CLIPFORMAT)object->preferredDropEffectFormat;
enumerator->formats[format].dwAspect = DVASPECT_CONTENT;
enumerator->formats[format].lindex = -1;
enumerator->formats[format].tymed = TYMED_HGLOBAL;
enumerator->count = format + 1U;
enumerator->index = index > enumerator->count ?
enumerator->count : index;
*result = &enumerator->iface;
return S_OK;
}
static HRESULT STDMETHODCALLTYPE dataObjectEnumFormatEtc(IDataObject * self,
DWORD direction, IEnumFORMATETC ** enumerator)
{
if (!enumerator)
return E_POINTER;
*enumerator = NULL;
if (direction != DATADIR_GET)
return E_NOTIMPL;
return createFormatEnumerator(dataObjectFromInterface(self), 0,
enumerator);
}
static HRESULT STDMETHODCALLTYPE dataObjectDAdvise(IDataObject * self,
FORMATETC * format, DWORD flags, IAdviseSink * sink,
DWORD * connection)
{
(void)self;
(void)format;
(void)flags;
(void)sink;
(void)connection;
return OLE_E_ADVISENOTSUPPORTED;
}
static HRESULT STDMETHODCALLTYPE streamQueryInterface(IStream * self,
REFIID iid, void ** object)
{
if (!object)
return E_POINTER;
*object = NULL;
if (IsEqualIID(iid, &IID_IUnknown) ||
IsEqualIID(iid, &IID_ISequentialStream) ||
IsEqualIID(iid, &IID_IStream))
{
*object = self;
IStream_AddRef(self);
return S_OK;
}
return E_NOINTERFACE;
}
static ULONG STDMETHODCALLTYPE streamAddRef(IStream * self)
{
WindowsClipboardFileStream * stream = streamFromInterface(self);
return (ULONG)InterlockedIncrement(&stream->refs);
}
static ULONG STDMETHODCALLTYPE streamRelease(IStream * self)
{
WindowsClipboardFileStream * stream = streamFromInterface(self);
const LONG refs = InterlockedDecrement(&stream->refs);
if (refs)
return (ULONG)refs;
IDataObject_Release(&stream->owner->iface);
DeleteCriticalSection(&stream->lock);
free(stream);
return 0;
}
static HRESULT STDMETHODCALLTYPE streamRead(IStream * self, void * data,
ULONG length, ULONG * read)
{
if (read)
*read = 0;
if (!data && length)
return STG_E_INVALIDPOINTER;
if (!length)
return S_OK;
WindowsClipboardFileStream * stream = streamFromInterface(self);
WindowsClipboardFileDataObject * owner = stream->owner;
EnterCriticalSection(&stream->lock);
const WindowsClipboardRemoteEntry * entry =
&owner->manifest->entries[stream->entryIndex];
if (stream->offset >= entry->size)
{
LeaveCriticalSection(&stream->lock);
return S_FALSE;
}
const ULONG wanted = (ULONG)((entry->size - stream->offset < length) ?
entry->size - stream->offset : length);
ULONG completed = 0;
HRESULT result = S_OK;
while (completed < wanted)
{
const ULONG chunk =
wanted - completed > KVMFR_CLIPBOARD_FILE_READ_BYTES ?
KVMFR_CLIPBOARD_FILE_READ_BYTES : wanted - completed;
ULONG actual = 0;
result = owner->ops.read(owner->opaque, owner->dataset,
owner->acquisition, entry->node, stream->offset,
(uint8_t *)data + completed, chunk, &actual);
if (FAILED(result))
break;
if (actual != chunk)
{
result = STG_E_READFAULT;
break;
}
stream->offset += actual;
completed += actual;
}
LeaveCriticalSection(&stream->lock);
if (read)
*read = completed;
if (FAILED(result))
return result;
return completed == length ? S_OK : S_FALSE;
}
static HRESULT STDMETHODCALLTYPE streamWrite(IStream * self,
const void * data, ULONG length, ULONG * written)
{
(void)self;
(void)data;
(void)length;
if (written)
*written = 0;
return STG_E_ACCESSDENIED;
}
static HRESULT STDMETHODCALLTYPE streamSeek(IStream * self,
LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER * position)
{
WindowsClipboardFileStream * stream = streamFromInterface(self);
EnterCriticalSection(&stream->lock);
const WindowsClipboardRemoteEntry * entry =
&stream->owner->manifest->entries[stream->entryIndex];
uint64_t base;
switch (origin)
{
case STREAM_SEEK_SET:
base = 0;
break;
case STREAM_SEEK_CUR:
base = stream->offset;
break;
case STREAM_SEEK_END:
base = entry->size;
break;
default:
LeaveCriticalSection(&stream->lock);
return STG_E_INVALIDFUNCTION;
}
uint64_t next;
if (move.QuadPart < 0)
{
const uint64_t amount = (uint64_t)(-(move.QuadPart + 1)) + 1U;
if (amount > base)
{
LeaveCriticalSection(&stream->lock);
return STG_E_INVALIDFUNCTION;
}
next = base - amount;
}
else
{
const uint64_t amount = (uint64_t)move.QuadPart;
if (amount > UINT64_MAX - base)
{
LeaveCriticalSection(&stream->lock);
return STG_E_INVALIDFUNCTION;
}
next = base + amount;
}
stream->offset = next;
if (position)
position->QuadPart = next;
LeaveCriticalSection(&stream->lock);
return S_OK;
}
static HRESULT STDMETHODCALLTYPE streamSetSize(IStream * self,
ULARGE_INTEGER size)
{
(void)self;
(void)size;
return STG_E_ACCESSDENIED;
}
static HRESULT STDMETHODCALLTYPE streamCopyTo(IStream * self,
IStream * target, ULARGE_INTEGER count, ULARGE_INTEGER * read,
ULARGE_INTEGER * written)
{
if (!target)
return STG_E_INVALIDPOINTER;
if (read)
read->QuadPart = 0;
if (written)
written->QuadPart = 0;
uint8_t * buffer = malloc(KVMFR_CLIPBOARD_FILE_READ_BYTES);
if (!buffer)
return E_OUTOFMEMORY;
uint64_t remaining = count.QuadPart;
HRESULT result = S_OK;
while (remaining)
{
const ULONG wanted =
(ULONG)(remaining > KVMFR_CLIPBOARD_FILE_READ_BYTES ?
KVMFR_CLIPBOARD_FILE_READ_BYTES : remaining);
ULONG got = 0;
result = IStream_Read(self, buffer, wanted, &got);
if (FAILED(result) || !got)
{
if (SUCCEEDED(result))
result = S_FALSE;
break;
}
ULONG sent = 0;
result = IStream_Write(target, buffer, got, &sent);
if (read)
read->QuadPart += got;
if (written)
written->QuadPart += sent;
if (FAILED(result) || sent != got)
{
if (SUCCEEDED(result))
result = STG_E_WRITEFAULT;
break;
}
remaining -= got;
}
free(buffer);
return result;
}
static HRESULT STDMETHODCALLTYPE streamCommit(IStream * self, DWORD flags)
{
(void)self;
(void)flags;
return S_OK;
}
static HRESULT STDMETHODCALLTYPE streamRevert(IStream * self)
{
(void)self;
return STG_E_REVERTED;
}
static HRESULT STDMETHODCALLTYPE streamLockRegion(IStream * self,
ULARGE_INTEGER offset, ULARGE_INTEGER count, DWORD type)
{
(void)self;
(void)offset;
(void)count;
(void)type;
return STG_E_INVALIDFUNCTION;
}
static HRESULT STDMETHODCALLTYPE streamUnlockRegion(IStream * self,
ULARGE_INTEGER offset, ULARGE_INTEGER count, DWORD type)
{
(void)self;
(void)offset;
(void)count;
(void)type;
return STG_E_INVALIDFUNCTION;
}
static HRESULT STDMETHODCALLTYPE streamStat(IStream * self, STATSTG * stat,
DWORD flags)
{
if (!stat)
return STG_E_INVALIDPOINTER;
memset(stat, 0, sizeof(*stat));
WindowsClipboardFileStream * stream = streamFromInterface(self);
const WindowsClipboardRemoteEntry * entry =
&stream->owner->manifest->entries[stream->entryIndex];
stat->type = STGTY_STREAM;
stat->cbSize.QuadPart = entry->size;
stat->grfMode = STGM_READ;
stat->ctime = unixNsToFileTime(entry->createdNs);
stat->mtime = unixNsToFileTime(entry->modifiedNs);
if (!(flags & STATFLAG_NONAME))
{
const size_t length = wcslen(entry->name);
if (length > (SIZE_MAX / sizeof(wchar_t)) - 1U)
return E_OUTOFMEMORY;
stat->pwcsName = CoTaskMemAlloc((length + 1U) * sizeof(wchar_t));
if (!stat->pwcsName)
return E_OUTOFMEMORY;
memcpy(stat->pwcsName, entry->name,
(length + 1U) * sizeof(wchar_t));
}
return S_OK;
}
static HRESULT STDMETHODCALLTYPE streamClone(IStream * self,
IStream ** clone)
{
if (!clone)
return E_POINTER;
*clone = NULL;
WindowsClipboardFileStream * stream = streamFromInterface(self);
EnterCriticalSection(&stream->lock);
const HRESULT result = createFileStream(stream->owner,
stream->entryIndex, stream->offset, clone);
LeaveCriticalSection(&stream->lock);
return result;
}
static HRESULT STDMETHODCALLTYPE formatEnumeratorQueryInterface(
IEnumFORMATETC * self, REFIID iid, void ** object)
{
if (!object)
return E_POINTER;
*object = NULL;
if (IsEqualIID(iid, &IID_IUnknown) ||
IsEqualIID(iid, &IID_IEnumFORMATETC))
{
*object = self;
IEnumFORMATETC_AddRef(self);
return S_OK;
}
return E_NOINTERFACE;
}
static ULONG STDMETHODCALLTYPE formatEnumeratorAddRef(IEnumFORMATETC * self)
{
WindowsClipboardFormatEnumerator * enumerator =
formatEnumeratorFromInterface(self);
return (ULONG)InterlockedIncrement(&enumerator->refs);
}
static ULONG STDMETHODCALLTYPE formatEnumeratorRelease(IEnumFORMATETC * self)
{
WindowsClipboardFormatEnumerator * enumerator =
formatEnumeratorFromInterface(self);
const LONG refs =
InterlockedDecrement(&enumerator->refs);
if (!refs)
free(enumerator);
return refs ? (ULONG)refs : 0;
}
static HRESULT STDMETHODCALLTYPE formatEnumeratorNext(IEnumFORMATETC * self,
ULONG count, FORMATETC * formats, ULONG * fetched)
{
if (fetched)
*fetched = 0;
if (!formats || (count != 1U && !fetched))
return E_INVALIDARG;
WindowsClipboardFormatEnumerator * enumerator =
formatEnumeratorFromInterface(self);
ULONG copied = 0;
while (copied < count && enumerator->index < enumerator->count)
formats[copied++] = enumerator->formats[enumerator->index++];
if (fetched)
*fetched = copied;
return copied == count ? S_OK : S_FALSE;
}
static HRESULT STDMETHODCALLTYPE formatEnumeratorSkip(IEnumFORMATETC * self,
ULONG count)
{
WindowsClipboardFormatEnumerator * enumerator =
formatEnumeratorFromInterface(self);
const ULONG available =
enumerator->count - enumerator->index;
const ULONG skipped =
count < available ? count : available;
enumerator->index += skipped;
return skipped == count ? S_OK : S_FALSE;
}
static HRESULT STDMETHODCALLTYPE formatEnumeratorReset(IEnumFORMATETC * self)
{
WindowsClipboardFormatEnumerator * enumerator =
formatEnumeratorFromInterface(self);
enumerator->index = 0;
return S_OK;
}
static HRESULT STDMETHODCALLTYPE formatEnumeratorClone(IEnumFORMATETC * self,
IEnumFORMATETC ** clone)
{
if (!clone)
return E_POINTER;
*clone = NULL;
WindowsClipboardFormatEnumerator * source =
formatEnumeratorFromInterface(self);
WindowsClipboardFormatEnumerator * result = calloc(1, sizeof(*result));
if (!result)
return E_OUTOFMEMORY;
result->iface.lpVtbl = &formatEnumeratorVTable;
result->refs = 1;
memcpy(result->formats, source->formats, sizeof(result->formats));
result->count = source->count;
result->index = source->index;
*clone = &result->iface;
return S_OK;
}
HRESULT windowsClipboardRemoteCreateDataObject(
WindowsClipboardRemoteManifest * manifest, uint64_t dataset,
uint64_t acquisition, const WindowsClipboardRemoteProviderOps * ops,
void * opaque, IDataObject ** object)
{
if (!object)
return E_POINTER;
*object = NULL;
if (!manifest || !dataset || !acquisition || !ops || !ops->read ||
!ops->release)
return E_INVALIDARG;
EnterCriticalSection(&manifest->lock);
if (manifest->rootState != 2U ||
!manifest->entryCount || manifest->entryCount > LONG_MAX)
{
LeaveCriticalSection(&manifest->lock);
return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
}
FILEDESCRIPTORW * descriptors = calloc(manifest->entryCount,
sizeof(*descriptors));
wchar_t ** paths = calloc(manifest->entryCount,
sizeof(*paths));
if (!descriptors || !paths)
{
free(descriptors);
free(paths);
LeaveCriticalSection(&manifest->lock);
return E_OUTOFMEMORY;
}
HRESULT result = S_OK;
for (size_t index = 0; index < manifest->entryCount; ++index)
{
const WindowsClipboardRemoteEntry * entry = &manifest->entries[index];
if (!entry->node || !validComponent(entry->name) ||
(entry->type == KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY &&
entry->listState != 2U))
{
result = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
break;
}
if (!entry->parent)
paths[index] = duplicateWide(entry->name);
else
{
const ptrdiff_t parent = findRemoteEntry(manifest, entry->parent);
if (parent < 0 || (size_t)parent >= index ||
manifest->entries[parent].type !=
KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY || !paths[parent])
{
result = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
break;
}
paths[index] = joinPath(paths[parent], entry->name);
}
if (!paths[index])
{
result = E_OUTOFMEMORY;
break;
}
const size_t pathLength = wcslen(paths[index]);
if (pathLength >= ARRAYSIZE(descriptors[index].cFileName))
{
result = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
break;
}
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 = (DWORD)(entry->size >> 32U);
descriptor->nFileSizeLow = (DWORD)entry->size;
}
memcpy(descriptor->cFileName, paths[index],
(pathLength + 1U) * sizeof(wchar_t));
}
for (size_t index = 0; index < manifest->entryCount; ++index)
free(paths[index]);
free(paths);
if (FAILED(result))
{
free(descriptors);
LeaveCriticalSection(&manifest->lock);
return result;
}
const UINT fileDescriptorFormat =
RegisterClipboardFormatW(L"FileGroupDescriptorW");
const UINT fileContentsFormat =
RegisterClipboardFormatW(L"FileContents");
const UINT preferredDropEffectFormat =
RegisterClipboardFormatW(L"Preferred DropEffect");
if (!fileDescriptorFormat || !fileContentsFormat ||
!preferredDropEffectFormat)
{
const DWORD winError = GetLastError();
free(descriptors);
LeaveCriticalSection(&manifest->lock);
return HRESULT_FROM_WIN32(winError ? winError : ERROR_INVALID_DATA);
}
WindowsClipboardFileDataObject * dataObject =
calloc(1, sizeof(*dataObject));
if (!dataObject)
{
free(descriptors);
LeaveCriticalSection(&manifest->lock);
return E_OUTOFMEMORY;
}
dataObject->iface.lpVtbl = &dataObjectVTable;
dataObject->refs = 1;
dataObject->manifest = manifest;
dataObject->dataset = dataset;
dataObject->acquisition = acquisition;
dataObject->ops = *ops;
dataObject->opaque = opaque;
dataObject->descriptors = descriptors;
dataObject->descriptorCount = manifest->entryCount;
dataObject->fileDescriptorFormat = fileDescriptorFormat;
dataObject->fileContentsFormat = fileContentsFormat;
dataObject->preferredDropEffectFormat = preferredDropEffectFormat;
manifest->frozen = true;
remoteManifestRetain(manifest);
LeaveCriticalSection(&manifest->lock);
*object = &dataObject->iface;
return S_OK;
}
static HRESULT STDMETHODCALLTYPE dataObjectDUnadvise(IDataObject * self,
DWORD connection)
{
(void)self;
(void)connection;
return OLE_E_ADVISENOTSUPPORTED;
}
static HRESULT STDMETHODCALLTYPE dataObjectEnumDAdvise(IDataObject * self,
IEnumSTATDATA ** enumerator)
{
(void)self;
if (enumerator)
*enumerator = NULL;
return OLE_E_ADVISENOTSUPPORTED;
}