summaryrefslogtreecommitdiffstats
path: root/src/VBox/HostServices/SharedClipboard/testcase
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/VBox/HostServices/SharedClipboard/testcase/.scm-settings29
-rw-r--r--src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk147
-rw-r--r--src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.h165
-rw-r--r--src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.txtbin0 -> 1952 bytes
-rw-r--r--src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.h152
-rw-r--r--src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.txt1
-rw-r--r--src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp735
-rw-r--r--src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp336
-rw-r--r--src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp205
-rw-r--r--src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp389
10 files changed, 2159 insertions, 0 deletions
diff --git a/src/VBox/HostServices/SharedClipboard/testcase/.scm-settings b/src/VBox/HostServices/SharedClipboard/testcase/.scm-settings
new file mode 100644
index 00000000..9fe67d17
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/testcase/.scm-settings
@@ -0,0 +1,29 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for the host HGCM services.
+#
+
+#
+# Copyright (C) 2019-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# 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, in version 3 of the
+# License.
+#
+# 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, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+--filter-out-files *.txt
+/*.h: --guard-relative-to-dir .
diff --git a/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk b/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk
new file mode 100644
index 00000000..66160a26
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk
@@ -0,0 +1,147 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the Shared Clipboard Host Service testcases.
+#
+
+#
+# Copyright (C) 2011-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# 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, in version 3 of the
+# License.
+#
+# 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, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK)
+
+ #
+ # Testcase which mocks HGCM to also test the VbglR3-side of Shared Clipboard.
+ #
+ # Goal is to use and test as much guest side code as possible as a self-contained
+ # binary on the host here.
+ #
+ # Note: No #ifdef TESTCASE hacks or similar allowed, has to run
+ # without #ifdef modifications to the core code!
+ #
+ tstClipboardMockHGCM_TEMPLATE = VBoxR3TstExe
+ tstClipboardMockHGCM_DEFS = VBOX_WITH_HGCM VBOX_WITH_SHARED_CLIPBOARD
+ tstClipboardMockHGCM_SOURCES = \
+ ../VBoxSharedClipboardSvc.cpp \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \
+ $(PATH_ROOT)/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp \
+ $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp \
+ tstClipboardMockHGCM.cpp
+ tstClipboardMockHGCM_LIBS = $(LIB_RUNTIME)
+
+ if1of ($(KBUILD_TARGET), linux solaris)
+ PROGRAMS += tstClipboardMockHGCM
+ tstClipboardMockHGCM_SOURCES += \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp \
+ ../VBoxSharedClipboardSvc-x11.cpp
+ tstClipboardMockHGCM_LIBPATH = \
+ $(VBOX_LIBPATH_X11)
+ tstClipboardMockHGCM_LIBS += \
+ Xt \
+ X11
+ endif
+ if1of ($(KBUILD_TARGET), win)
+ PROGRAMS += tstClipboardMockHGCM
+ tstClipboardMockHGCM_SOURCES += \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp \
+ ../VBoxSharedClipboardSvc-win.cpp
+ endif
+
+ tstClipboardMockHGCM_CLEAN = $(tstClipboardMockHGCM_0_OUTDIR)/tstClipboardMockHGCM.run
+
+ if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS)
+ tstClipboardMockHGCM_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ tstClipboardMockHGCM_SOURCES += \
+ ../VBoxSharedClipboardSvc-transfers.cpp \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp
+ endif
+
+ if 0 # Enable this if you want automatic runs after compilation.
+ $$(tstClipboardMockHGCM_0_OUTDIR)/tstClipboardMockHGCM.run: $$(tstClipboardMockHGCM_1_STAGE_TARGET)
+ export VBOX_LOG_DEST=nofile; $(tstClipboardMockHGCM_1_STAGE_TARGET) quiet
+ $(QUIET)$(APPEND) -t "$@" "done"
+ OTHERS += $(tstClipboardMockHGCM_0_OUTDIR)/tstClipboardMockHGCM.run
+ endif
+
+ #
+ #
+ #
+ PROGRAMS += tstClipboardServiceHost
+ tstClipboardServiceHost_TEMPLATE = VBoxR3TstExe
+ tstClipboardServiceHost_DEFS = VBOX_WITH_HGCM UNIT_TEST
+ tstClipboardServiceHost_SOURCES = \
+ ../VBoxSharedClipboardSvc.cpp \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \
+ $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp \
+ tstClipboardServiceHost.cpp
+ tstClipboardServiceHost_LIBS = $(LIB_RUNTIME)
+ tstClipboardServiceHost_CLEAN = $(tstClipboardServiceHost_0_OUTDIR)/tstClipboardServiceHost.run
+
+ if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS)
+ tstClipboardServiceHost_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ tstClipboardServiceHost_SOURCES += \
+ ../VBoxSharedClipboardSvc-transfers.cpp \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp
+ endif
+
+ #
+ #
+ #
+ PROGRAMS += tstClipboardServiceImpl
+ tstClipboardServiceImpl_TEMPLATE = VBoxR3TstExe
+ tstClipboardServiceImpl_DEFS = VBOX_WITH_HGCM UNIT_TEST
+ tstClipboardServiceImpl_SOURCES = \
+ ../VBoxSharedClipboardSvc.cpp \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \
+ $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp \
+ tstClipboardServiceImpl.cpp
+ tstClipboardServiceImpl_SOURCES.win = \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp
+ tstClipboardServiceImpl_LIBS = $(LIB_RUNTIME)
+ tstClipboardServiceImpl_CLEAN = $(tstClipboardServiceImpl_0_OUTDIR)/tstClipboardServiceImpl.run
+
+ if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS)
+ #
+ #
+ #
+ PROGRAMS += tstClipboardTransfers
+ tstClipboardTransfers_TEMPLATE = VBoxR3TstExe
+ tstClipboardTransfers_DEFS = VBOX_WITH_HGCM UNIT_TEST VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ tstClipboardTransfers_SOURCES = \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \
+ tstClipboardTransfers.cpp
+ endif
+endif
+
+#
+# List of above testcases that will be included in the ValKit.
+#
+ifdef VBOX_WITH_VALIDATIONKIT_UNITTESTS_PACKING
+ if1of ($(KBUILD_TARGET), linux solaris win)
+ VALKIT_UNITTESTS_WHITELIST_GUEST_ADDITIONS += \
+ tstClipboardMockHGCM
+ endif
+endif # VBOX_WITH_VALIDATIONKIT_UNITTESTS_PACKING
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.h b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.h
new file mode 100644
index 00000000..35791604
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.h
@@ -0,0 +1,165 @@
+/* $Id: VBoxOrgCfHtml1.h $ */
+/** @file
+ * Shared Clipboard host service test case C data file of VBoxOrgCfHtml1.txt.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * 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, in version 3 of the
+ * License.
+ *
+ * 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, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_VBoxOrgCfHtml1_h
+#define VBOX_INCLUDED_SRC_VBoxOrgCfHtml1_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+
+const unsigned char g_abVBoxOrgCfHtml1[] =
+{
+ 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x30, 0x2e, 0x39, 0x0d, 0x0a, 0x53, 0x74, 0x61, /* 0x00000000: Version:0.9..Sta */
+ 0x72, 0x74, 0x48, 0x54, 0x4d, 0x4c, 0x3a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x34, /* 0x00000010: rtHTML:000000014 */
+ 0x34, 0x0d, 0x0a, 0x45, 0x6e, 0x64, 0x48, 0x54, 0x4d, 0x4c, 0x3a, 0x30, 0x30, 0x30, 0x30, 0x30, /* 0x00000020: 4..EndHTML:00000 */
+ 0x30, 0x31, 0x39, 0x36, 0x31, 0x0d, 0x0a, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, 0x72, 0x61, 0x67, /* 0x00000030: 01961..StartFrag */
+ 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x38, 0x30, 0x0d, /* 0x00000040: ment:0000000180. */
+ 0x0a, 0x45, 0x6e, 0x64, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x30, 0x30, 0x30, /* 0x00000050: .EndFragment:000 */
+ 0x30, 0x30, 0x30, 0x31, 0x39, 0x32, 0x35, 0x0d, 0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, /* 0x00000060: 0001925..SourceU */
+ 0x52, 0x4c, 0x3a, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, /* 0x00000070: RL:https:..www.v */
+ 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x62, 0x6f, 0x78, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x0d, 0x0a, /* 0x00000080: irtualbox.org... */
+ 0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x0d, 0x0a, 0x3c, 0x62, 0x6f, 0x64, 0x79, 0x3e, 0x0d, 0x0a, /* 0x00000090: <html>..<body>.. */
+ 0x3c, 0x21, 0x2d, 0x2d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, /* 0x000000a0: <!--StartFragmen */
+ 0x74, 0x2d, 0x2d, 0x3e, 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, /* 0x000000b0: t--><span style= */
+ 0x22, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x30, 0x2c, 0x20, 0x30, /* 0x000000c0: "color: rgb(0, 0 */
+ 0x2c, 0x20, 0x30, 0x29, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c, /* 0x000000d0: , 0); font-famil */
+ 0x79, 0x3a, 0x20, 0x56, 0x65, 0x72, 0x64, 0x61, 0x6e, 0x61, 0x2c, 0x20, 0x26, 0x71, 0x75, 0x6f, /* 0x000000e0: y: Verdana, &quo */
+ 0x74, 0x3b, 0x42, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x56, 0x65, 0x72, 0x61, /* 0x000000f0: t;Bitstream Vera */
+ 0x20, 0x53, 0x61, 0x6e, 0x73, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x2c, 0x20, 0x73, 0x61, 0x6e, /* 0x00000100: Sans&quot;, san */
+ 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, /* 0x00000110: s-serif; font-si */
+ 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x33, 0x70, 0x78, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, /* 0x00000120: ze: 13px; font-s */
+ 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, /* 0x00000130: tyle: normal; fo */
+ 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x6c, 0x69, 0x67, 0x61, 0x74, /* 0x00000140: nt-variant-ligat */
+ 0x75, 0x72, 0x65, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, /* 0x00000150: ures: normal; fo */
+ 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x63, 0x61, 0x70, 0x73, 0x3a, /* 0x00000160: nt-variant-caps: */
+ 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, /* 0x00000170: normal; font-we */
+ 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x30, 0x30, 0x3b, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, /* 0x00000180: ight: 400; lette */
+ 0x72, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, /* 0x00000190: r-spacing: norma */
+ 0x6c, 0x3b, 0x20, 0x6f, 0x72, 0x70, 0x68, 0x61, 0x6e, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x74, /* 0x000001a0: l; orphans: 2; t */
+ 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, /* 0x000001b0: ext-align: start */
+ 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x69, 0x6e, 0x64, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x30, /* 0x000001c0: ; text-indent: 0 */
+ 0x70, 0x78, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, /* 0x000001d0: px; text-transfo */
+ 0x72, 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, /* 0x000001e0: rm: none; white- */
+ 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x77, /* 0x000001f0: space: normal; w */
+ 0x69, 0x64, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x73, /* 0x00000200: idows: 2; word-s */
+ 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x2d, 0x77, 0x65, /* 0x00000210: pacing: 0px; -we */
+ 0x62, 0x6b, 0x69, 0x74, 0x2d, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x6f, 0x6b, 0x65, /* 0x00000220: bkit-text-stroke */
+ 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x62, 0x61, 0x63, /* 0x00000230: -width: 0px; bac */
+ 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, /* 0x00000240: kground-color: r */
+ 0x67, 0x62, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, /* 0x00000250: gb(255, 255, 255 */
+ 0x29, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, /* 0x00000260: ); text-decorati */
+ 0x6f, 0x6e, 0x2d, 0x74, 0x68, 0x69, 0x63, 0x6b, 0x6e, 0x65, 0x73, 0x73, 0x3a, 0x20, 0x69, 0x6e, /* 0x00000270: on-thickness: in */
+ 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, /* 0x00000280: itial; text-deco */
+ 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x69, 0x6e, /* 0x00000290: ration-style: in */
+ 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, /* 0x000002a0: itial; text-deco */
+ 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x69, 0x6e, /* 0x000002b0: ration-color: in */
+ 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, /* 0x000002c0: itial; display: */
+ 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x21, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, /* 0x000002d0: inline !importan */
+ 0x74, 0x3b, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x22, /* 0x000002e0: t; float: none;" */
+ 0x3e, 0x53, 0x65, 0x65, 0x20, 0x22, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x3c, 0x61, 0x20, /* 0x000002f0: >See "<.span><a */
+ 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x77, 0x69, 0x6b, 0x69, 0x22, 0x20, 0x68, 0x72, 0x65, /* 0x00000300: class="wiki" hre */
+ 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, /* 0x00000310: f="https:..www.v */
+ 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x62, 0x6f, 0x78, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x77, 0x69, /* 0x00000320: irtualbox.org.wi */
+ 0x6b, 0x69, 0x2f, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x42, 0x6f, 0x78, 0x22, 0x20, 0x73, /* 0x00000330: ki.VirtualBox" s */
+ 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, /* 0x00000340: tyle="text-decor */
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x63, 0x6f, 0x6c, /* 0x00000350: ation: none; col */
+ 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x31, 0x39, /* 0x00000360: or: rgb(0, 0, 19 */
+ 0x32, 0x29, 0x3b, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, /* 0x00000370: 2); border-botto */
+ 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, /* 0x00000380: m: none; font-fa */
+ 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x56, 0x65, 0x72, 0x64, 0x61, 0x6e, 0x61, 0x2c, 0x20, 0x26, /* 0x00000390: mily: Verdana, & */
+ 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x42, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x56, /* 0x000003a0: quot;Bitstream V */
+ 0x65, 0x72, 0x61, 0x20, 0x53, 0x61, 0x6e, 0x73, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x2c, 0x20, /* 0x000003b0: era Sans&quot;, */
+ 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, /* 0x000003c0: sans-serif; font */
+ 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x33, 0x70, 0x78, 0x3b, 0x20, 0x66, 0x6f, 0x6e, /* 0x000003d0: -size: 13px; fon */
+ 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, /* 0x000003e0: t-style: normal; */
+ 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x6c, 0x69, /* 0x000003f0: font-variant-li */
+ 0x67, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, /* 0x00000400: gatures: normal; */
+ 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x63, 0x61, /* 0x00000410: font-variant-ca */
+ 0x70, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, /* 0x00000420: ps: normal; font */
+ 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x30, 0x30, 0x3b, 0x20, 0x6c, 0x65, /* 0x00000430: -weight: 400; le */
+ 0x74, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x6e, 0x6f, /* 0x00000440: tter-spacing: no */
+ 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x6f, 0x72, 0x70, 0x68, 0x61, 0x6e, 0x73, 0x3a, 0x20, 0x32, /* 0x00000450: rmal; orphans: 2 */
+ 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x73, 0x74, /* 0x00000460: ; text-align: st */
+ 0x61, 0x72, 0x74, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x69, 0x6e, 0x64, 0x65, 0x6e, 0x74, /* 0x00000470: art; text-indent */
+ 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, /* 0x00000480: : 0px; text-tran */
+ 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x77, 0x68, 0x69, /* 0x00000490: sform: none; whi */
+ 0x74, 0x65, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, /* 0x000004a0: te-space: normal */
+ 0x3b, 0x20, 0x77, 0x69, 0x64, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x77, 0x6f, 0x72, /* 0x000004b0: ; widows: 2; wor */
+ 0x64, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, /* 0x000004c0: d-spacing: 0px; */
+ 0x2d, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2d, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x73, 0x74, 0x72, /* 0x000004d0: -webkit-text-str */
+ 0x6f, 0x6b, 0x65, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, /* 0x000004e0: oke-width: 0px; */
+ 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, /* 0x000004f0: background-color */
+ 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, /* 0x00000500: : rgb(255, 255, */
+ 0x32, 0x35, 0x35, 0x29, 0x3b, 0x22, 0x3e, 0x41, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x56, 0x69, 0x72, /* 0x00000510: 255);">About Vir */
+ 0x74, 0x75, 0x61, 0x6c, 0x42, 0x6f, 0x78, 0x3c, 0x2f, 0x61, 0x3e, 0x3c, 0x73, 0x70, 0x61, 0x6e, /* 0x00000520: tualBox<.a><span */
+ 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, /* 0x00000530: style="color: r */
+ 0x67, 0x62, 0x28, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x29, 0x3b, 0x20, 0x66, 0x6f, 0x6e, /* 0x00000540: gb(0, 0, 0); fon */
+ 0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x56, 0x65, 0x72, 0x64, 0x61, 0x6e, /* 0x00000550: t-family: Verdan */
+ 0x61, 0x2c, 0x20, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x42, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, /* 0x00000560: a, &quot;Bitstre */
+ 0x61, 0x6d, 0x20, 0x56, 0x65, 0x72, 0x61, 0x20, 0x53, 0x61, 0x6e, 0x73, 0x26, 0x71, 0x75, 0x6f, /* 0x00000570: am Vera Sans&quo */
+ 0x74, 0x3b, 0x2c, 0x20, 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x3b, 0x20, /* 0x00000580: t;, sans-serif; */
+ 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x33, 0x70, 0x78, 0x3b, /* 0x00000590: font-size: 13px; */
+ 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, /* 0x000005a0: font-style: nor */
+ 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, /* 0x000005b0: mal; font-varian */
+ 0x74, 0x2d, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, /* 0x000005c0: t-ligatures: nor */
+ 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, /* 0x000005d0: mal; font-varian */
+ 0x74, 0x2d, 0x63, 0x61, 0x70, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, /* 0x000005e0: t-caps: normal; */
+ 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x30, 0x30, /* 0x000005f0: font-weight: 400 */
+ 0x3b, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, /* 0x00000600: ; letter-spacing */
+ 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x6f, 0x72, 0x70, 0x68, 0x61, 0x6e, /* 0x00000610: : normal; orphan */
+ 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, /* 0x00000620: s: 2; text-align */
+ 0x3a, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x69, 0x6e, /* 0x00000630: : start; text-in */
+ 0x64, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, /* 0x00000640: dent: 0px; text- */
+ 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, /* 0x00000650: transform: none; */
+ 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, /* 0x00000660: white-space: no */
+ 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x77, 0x69, 0x64, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x32, 0x3b, /* 0x00000670: rmal; widows: 2; */
+ 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, /* 0x00000680: word-spacing: 0 */
+ 0x70, 0x78, 0x3b, 0x20, 0x2d, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2d, 0x74, 0x65, 0x78, 0x74, /* 0x00000690: px; -webkit-text */
+ 0x2d, 0x73, 0x74, 0x72, 0x6f, 0x6b, 0x65, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, /* 0x000006a0: -stroke-width: 0 */
+ 0x70, 0x78, 0x3b, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, /* 0x000006b0: px; background-c */
+ 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, /* 0x000006c0: olor: rgb(255, 2 */
+ 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, /* 0x000006d0: 55, 255); text-d */
+ 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x74, 0x68, 0x69, 0x63, 0x6b, 0x6e, /* 0x000006e0: ecoration-thickn */
+ 0x65, 0x73, 0x73, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, /* 0x000006f0: ess: initial; te */
+ 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x73, 0x74, /* 0x00000700: xt-decoration-st */
+ 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, /* 0x00000710: yle: initial; te */
+ 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, /* 0x00000720: xt-decoration-co */
+ 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x64, 0x69, /* 0x00000730: lor: initial; di */
+ 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x21, 0x69, /* 0x00000740: splay: inline !i */
+ 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, 0x74, 0x3b, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, /* 0x00000750: mportant; float: */
+ 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x22, 0x3e, 0x22, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6e, /* 0x00000760: none;">" for an */
+ 0x20, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x3c, 0x2f, /* 0x00000770: introduction.<. */
+ 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x3c, 0x21, 0x2d, 0x2d, 0x45, 0x6e, 0x64, 0x46, 0x72, 0x61, 0x67, /* 0x00000780: span><!--EndFrag */
+ 0x6d, 0x65, 0x6e, 0x74, 0x2d, 0x2d, 0x3e, 0x0d, 0x0a, 0x3c, 0x2f, 0x62, 0x6f, 0x64, 0x79, 0x3e, /* 0x00000790: ment-->..<.body> */
+ 0x0d, 0x0a, 0x3c, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x00, /* 0x000007a0: ..<.html>. */
+};
+
+const unsigned g_cbVBoxOrgCfHtml1 = sizeof(g_abVBoxOrgCfHtml1);
+
+#endif /* !VBOX_INCLUDED_SRC_VBoxOrgCfHtml1_h */
diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.txt b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.txt
new file mode 100644
index 00000000..39a83822
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.txt
Binary files differ
diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.h b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.h
new file mode 100644
index 00000000..a2a0fe41
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.h
@@ -0,0 +1,152 @@
+/* $Id: VBoxOrgMimeHtml1.h $ */
+/** @file
+ * Shared Clipboard host service test case C data file of VBoxOrgMimeHtml1.txt.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * 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, in version 3 of the
+ * License.
+ *
+ * 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, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_VBoxOrgMimeHtml1_h
+#define VBOX_INCLUDED_SRC_VBoxOrgMimeHtml1_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+
+const unsigned char g_abVBoxOrgMimeHtml1[] =
+{
+ 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x63, 0x6f, 0x6c, /* 0x00000000: <span style="col */
+ 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x29, /* 0x00000010: or: rgb(0, 0, 0) */
+ 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x56, /* 0x00000020: ; font-family: V */
+ 0x65, 0x72, 0x64, 0x61, 0x6e, 0x61, 0x2c, 0x20, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x42, 0x69, /* 0x00000030: erdana, &quot;Bi */
+ 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x56, 0x65, 0x72, 0x61, 0x20, 0x53, 0x61, 0x6e, /* 0x00000040: tstream Vera San */
+ 0x73, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x2c, 0x20, 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, /* 0x00000050: s&quot;, sans-se */
+ 0x72, 0x69, 0x66, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, /* 0x00000060: rif; font-size: */
+ 0x31, 0x33, 0x70, 0x78, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, /* 0x00000070: 13px; font-style */
+ 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, /* 0x00000080: : normal; font-v */
+ 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, /* 0x00000090: ariant-ligatures */
+ 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, /* 0x000000a0: : normal; font-v */
+ 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x63, 0x61, 0x70, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, /* 0x000000b0: ariant-caps: nor */
+ 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, /* 0x000000c0: mal; font-weight */
+ 0x3a, 0x20, 0x34, 0x30, 0x30, 0x3b, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x70, /* 0x000000d0: : 400; letter-sp */
+ 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x6f, /* 0x000000e0: acing: normal; o */
+ 0x72, 0x70, 0x68, 0x61, 0x6e, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, /* 0x000000f0: rphans: 2; text- */
+ 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x3b, 0x20, 0x74, 0x65, /* 0x00000100: align: start; te */
+ 0x78, 0x74, 0x2d, 0x69, 0x6e, 0x64, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, /* 0x00000110: xt-indent: 0px; */
+ 0x74, 0x65, 0x78, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, /* 0x00000120: text-transform: */
+ 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, 0x73, 0x70, 0x61, 0x63, /* 0x00000130: none; white-spac */
+ 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x77, 0x69, 0x64, 0x6f, 0x77, /* 0x00000140: e: normal; widow */
+ 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, /* 0x00000150: s: 2; word-spaci */
+ 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x2d, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, /* 0x00000160: ng: 0px; -webkit */
+ 0x2d, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x6f, 0x6b, 0x65, 0x2d, 0x77, 0x69, 0x64, /* 0x00000170: -text-stroke-wid */
+ 0x74, 0x68, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, /* 0x00000180: th: 0px; backgro */
+ 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x32, /* 0x00000190: und-color: rgb(2 */
+ 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, 0x3b, 0x20, 0x74, /* 0x000001a0: 55, 255, 255); t */
+ 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x74, /* 0x000001b0: ext-decoration-t */
+ 0x68, 0x69, 0x63, 0x6b, 0x6e, 0x65, 0x73, 0x73, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, /* 0x000001c0: hickness: initia */
+ 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, /* 0x000001d0: l; text-decorati */
+ 0x6f, 0x6e, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, /* 0x000001e0: on-style: initia */
+ 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, /* 0x000001f0: l; text-decorati */
+ 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, /* 0x00000200: on-color: initia */
+ 0x6c, 0x3b, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69, /* 0x00000210: l; display: inli */
+ 0x6e, 0x65, 0x20, 0x21, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, 0x74, 0x3b, 0x20, 0x66, /* 0x00000220: ne !important; f */
+ 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x22, 0x3e, 0x53, 0x65, 0x65, /* 0x00000230: loat: none;">See */
+ 0x20, 0x22, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x3c, 0x61, 0x20, 0x63, 0x6c, 0x61, 0x73, /* 0x00000240: "<.span><a clas */
+ 0x73, 0x3d, 0x22, 0x77, 0x69, 0x6b, 0x69, 0x22, 0x20, 0x68, 0x72, 0x65, 0x66, 0x3d, 0x22, 0x68, /* 0x00000250: s="wiki" href="h */
+ 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x69, 0x72, 0x74, 0x75, /* 0x00000260: ttps:..www.virtu */
+ 0x61, 0x6c, 0x62, 0x6f, 0x78, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x77, 0x69, 0x6b, 0x69, 0x2f, 0x56, /* 0x00000270: albox.org.wiki.V */
+ 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x42, 0x6f, 0x78, 0x22, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, /* 0x00000280: irtualBox" style */
+ 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, /* 0x00000290: ="text-decoratio */
+ 0x6e, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, /* 0x000002a0: n: none; color: */
+ 0x72, 0x67, 0x62, 0x28, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x31, 0x39, 0x32, 0x29, 0x3b, 0x20, /* 0x000002b0: rgb(0, 0, 192); */
+ 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x6e, /* 0x000002c0: border-bottom: n */
+ 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, /* 0x000002d0: one; font-family */
+ 0x3a, 0x20, 0x56, 0x65, 0x72, 0x64, 0x61, 0x6e, 0x61, 0x2c, 0x20, 0x26, 0x71, 0x75, 0x6f, 0x74, /* 0x000002e0: : Verdana, &quot */
+ 0x3b, 0x42, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x56, 0x65, 0x72, 0x61, 0x20, /* 0x000002f0: ;Bitstream Vera */
+ 0x53, 0x61, 0x6e, 0x73, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x2c, 0x20, 0x73, 0x61, 0x6e, 0x73, /* 0x00000300: Sans&quot;, sans */
+ 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, /* 0x00000310: -serif; font-siz */
+ 0x65, 0x3a, 0x20, 0x31, 0x33, 0x70, 0x78, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x74, /* 0x00000320: e: 13px; font-st */
+ 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, /* 0x00000330: yle: normal; fon */
+ 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x75, /* 0x00000340: t-variant-ligatu */
+ 0x72, 0x65, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, /* 0x00000350: res: normal; fon */
+ 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x63, 0x61, 0x70, 0x73, 0x3a, 0x20, /* 0x00000360: t-variant-caps: */
+ 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, 0x69, /* 0x00000370: normal; font-wei */
+ 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x30, 0x30, 0x3b, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, /* 0x00000380: ght: 400; letter */
+ 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, /* 0x00000390: -spacing: normal */
+ 0x3b, 0x20, 0x6f, 0x72, 0x70, 0x68, 0x61, 0x6e, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x74, 0x65, /* 0x000003a0: ; orphans: 2; te */
+ 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x3b, /* 0x000003b0: xt-align: start; */
+ 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x69, 0x6e, 0x64, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x30, 0x70, /* 0x000003c0: text-indent: 0p */
+ 0x78, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, /* 0x000003d0: x; text-transfor */
+ 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, 0x73, /* 0x000003e0: m: none; white-s */
+ 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x77, 0x69, /* 0x000003f0: pace: normal; wi */
+ 0x64, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x73, 0x70, /* 0x00000400: dows: 2; word-sp */
+ 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x2d, 0x77, 0x65, 0x62, /* 0x00000410: acing: 0px; -web */
+ 0x6b, 0x69, 0x74, 0x2d, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x6f, 0x6b, 0x65, 0x2d, /* 0x00000420: kit-text-stroke- */
+ 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x62, 0x61, 0x63, 0x6b, /* 0x00000430: width: 0px; back */
+ 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, /* 0x00000440: ground-color: rg */
+ 0x62, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, /* 0x00000450: b(255, 255, 255) */
+ 0x3b, 0x22, 0x3e, 0x41, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, /* 0x00000460: ;">About Virtual */
+ 0x42, 0x6f, 0x78, 0x3c, 0x2f, 0x61, 0x3e, 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, /* 0x00000470: Box<.a><span sty */
+ 0x6c, 0x65, 0x3d, 0x22, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x30, /* 0x00000480: le="color: rgb(0 */
+ 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x29, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, /* 0x00000490: , 0, 0); font-fa */
+ 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x56, 0x65, 0x72, 0x64, 0x61, 0x6e, 0x61, 0x2c, 0x20, 0x26, /* 0x000004a0: mily: Verdana, & */
+ 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x42, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x56, /* 0x000004b0: quot;Bitstream V */
+ 0x65, 0x72, 0x61, 0x20, 0x53, 0x61, 0x6e, 0x73, 0x26, 0x71, 0x75, 0x6f, 0x74, 0x3b, 0x2c, 0x20, /* 0x000004c0: era Sans&quot;, */
+ 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, /* 0x000004d0: sans-serif; font */
+ 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x33, 0x70, 0x78, 0x3b, 0x20, 0x66, 0x6f, 0x6e, /* 0x000004e0: -size: 13px; fon */
+ 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, /* 0x000004f0: t-style: normal; */
+ 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x6c, 0x69, /* 0x00000500: font-variant-li */
+ 0x67, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, /* 0x00000510: gatures: normal; */
+ 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x2d, 0x63, 0x61, /* 0x00000520: font-variant-ca */
+ 0x70, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x66, 0x6f, 0x6e, 0x74, /* 0x00000530: ps: normal; font */
+ 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x30, 0x30, 0x3b, 0x20, 0x6c, 0x65, /* 0x00000540: -weight: 400; le */
+ 0x74, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x6e, 0x6f, /* 0x00000550: tter-spacing: no */
+ 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x20, 0x6f, 0x72, 0x70, 0x68, 0x61, 0x6e, 0x73, 0x3a, 0x20, 0x32, /* 0x00000560: rmal; orphans: 2 */
+ 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x73, 0x74, /* 0x00000570: ; text-align: st */
+ 0x61, 0x72, 0x74, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x69, 0x6e, 0x64, 0x65, 0x6e, 0x74, /* 0x00000580: art; text-indent */
+ 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, /* 0x00000590: : 0px; text-tran */
+ 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x77, 0x68, 0x69, /* 0x000005a0: sform: none; whi */
+ 0x74, 0x65, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, /* 0x000005b0: te-space: normal */
+ 0x3b, 0x20, 0x77, 0x69, 0x64, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x20, 0x77, 0x6f, 0x72, /* 0x000005c0: ; widows: 2; wor */
+ 0x64, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, /* 0x000005d0: d-spacing: 0px; */
+ 0x2d, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2d, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x73, 0x74, 0x72, /* 0x000005e0: -webkit-text-str */
+ 0x6f, 0x6b, 0x65, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x20, /* 0x000005f0: oke-width: 0px; */
+ 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, /* 0x00000600: background-color */
+ 0x3a, 0x20, 0x72, 0x67, 0x62, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, /* 0x00000610: : rgb(255, 255, */
+ 0x32, 0x35, 0x35, 0x29, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, /* 0x00000620: 255); text-decor */
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x74, 0x68, 0x69, 0x63, 0x6b, 0x6e, 0x65, 0x73, 0x73, 0x3a, /* 0x00000630: ation-thickness: */
+ 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, /* 0x00000640: initial; text-d */
+ 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, /* 0x00000650: ecoration-style: */
+ 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, /* 0x00000660: initial; text-d */
+ 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, /* 0x00000670: ecoration-color: */
+ 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x3b, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, /* 0x00000680: initial; displa */
+ 0x79, 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x21, 0x69, 0x6d, 0x70, 0x6f, 0x72, /* 0x00000690: y: inline !impor */
+ 0x74, 0x61, 0x6e, 0x74, 0x3b, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, /* 0x000006a0: tant; float: non */
+ 0x65, 0x3b, 0x22, 0x3e, 0x22, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6e, 0x20, 0x69, 0x6e, 0x74, /* 0x000006b0: e;">" for an int */
+ 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, /* 0x000006c0: roduction.<.span */
+ 0x3e, 0x00 /* 0x000006d0: > */
+};
+
+const unsigned g_cbVBoxOrgMimeHtml1 = sizeof(g_abVBoxOrgMimeHtml1);
+
+#endif /* !VBOX_INCLUDED_SRC_VBoxOrgMimeHtml1_h */
diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.txt b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.txt
new file mode 100644
index 00000000..6d5b4901
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.txt
@@ -0,0 +1 @@
+<span style="color: rgb(0, 0, 0); font-family: Verdana, &quot;Bitstream Vera Sans&quot;, sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">See "</span><a class="wiki" href="https://www.virtualbox.org/wiki/VirtualBox" style="text-decoration: none; color: rgb(0, 0, 192); border-bottom: none; font-family: Verdana, &quot;Bitstream Vera Sans&quot;, sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255);">About VirtualBox</a><span style="color: rgb(0, 0, 0); font-family: Verdana, &quot;Bitstream Vera Sans&quot;, sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">" for an introduction.</span> \ No newline at end of file
diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp
new file mode 100644
index 00000000..ec35905c
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp
@@ -0,0 +1,735 @@
+/* $Id: tstClipboardMockHGCM.cpp $ */
+/** @file
+ * Shared Clipboard host service test case.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * 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, in version 3 of the
+ * License.
+ *
+ * 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, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "../VBoxSharedClipboardSvc-internal.h"
+
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/VBoxGuestLib.h>
+#ifdef RT_OS_LINUX
+# include <VBox/GuestHost/SharedClipboard-x11.h>
+#endif
+#ifdef RT_OS_WINDOWS
+# include <VBox/GuestHost/SharedClipboard-win.h>
+#endif
+
+#include <VBox/GuestHost/HGCMMock.h>
+#include <VBox/GuestHost/HGCMMockUtils.h>
+
+#include <iprt/assert.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/rand.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+#include <iprt/utf16.h>
+
+
+/*********************************************************************************************************************************
+* Static globals *
+*********************************************************************************************************************************/
+static RTTEST g_hTest;
+
+
+/*********************************************************************************************************************************
+* Shared Clipboard testing *
+*********************************************************************************************************************************/
+struct CLIPBOARDTESTDESC;
+/** Pointer to a test description. */
+typedef CLIPBOARDTESTDESC *PTESTDESC;
+
+struct CLIPBOARDTESTCTX;
+/** Pointer to a test context. */
+typedef CLIPBOARDTESTCTX *PCLIPBOARDTESTCTX;
+
+/** Pointer a test descriptor. */
+typedef CLIPBOARDTESTDESC *PTESTDESC;
+
+typedef DECLCALLBACKTYPE(int, FNTESTSETUP,(PCLIPBOARDTESTCTX pTstCtx, void **ppvCtx));
+/** Pointer to a test setup callback. */
+typedef FNTESTSETUP *PFNTESTSETUP;
+
+typedef DECLCALLBACKTYPE(int, FNTESTEXEC,(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx));
+/** Pointer to a test exec callback. */
+typedef FNTESTEXEC *PFNTESTEXEC;
+
+typedef DECLCALLBACKTYPE(int, FNTESTDESTROY,(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx));
+/** Pointer to a test destroy callback. */
+typedef FNTESTDESTROY *PFNTESTDESTROY;
+
+
+/**
+ * Structure for keeping a clipboard test task.
+ */
+typedef struct CLIPBOARDTESTTASK
+{
+ SHCLFORMATS enmFmtHst;
+ SHCLFORMATS enmFmtGst;
+ /** For testing chunked reads / writes. */
+ size_t cbChunk;
+ /** Data buffer to read / write for this task.
+ * Can be NULL if not needed. */
+ void *pvData;
+ /** Size (in bytes) of \a pvData. */
+ size_t cbData;
+ /** Number of bytes read / written from / to \a pvData. */
+ size_t cbProcessed;
+} CLIPBOARDTESTTASK;
+typedef CLIPBOARDTESTTASK *PCLIPBOARDTESTTASK;
+
+/**
+ * Structure for keeping a clipboard test context.
+ */
+typedef struct CLIPBOARDTESTCTX
+{
+ /** The HGCM Mock utils context. */
+ TSTHGCMUTILSCTX HGCM;
+ /** Clipboard-specific task data. */
+ CLIPBOARDTESTTASK Task;
+ struct
+ {
+ /** The VbglR3 Shared Clipboard context to work on. */
+ VBGLR3SHCLCMDCTX CmdCtx;
+ } Guest;
+} CLIPBOARDTESTCTX;
+
+/** The one and only clipboard test context. One at a time. */
+CLIPBOARDTESTCTX g_TstCtx;
+
+/**
+ * Structure for keeping a clipboard test description.
+ */
+typedef struct CLIPBOARDTESTDESC
+{
+ /** The setup callback. */
+ PFNTESTSETUP pfnSetup;
+ /** The exec callback. */
+ PFNTESTEXEC pfnExec;
+ /** The destruction callback. */
+ PFNTESTDESTROY pfnDestroy;
+} CLIPBOARDTESTDESC;
+
+typedef struct SHCLCONTEXT
+{
+} SHCLCONTEXT;
+
+
+static int tstSetModeRc(PTSTHGCMMOCKSVC pSvc, uint32_t uMode, int rcExpected)
+{
+ VBOXHGCMSVCPARM aParms[2];
+ HGCMSvcSetU32(&aParms[0], uMode);
+ int rc2 = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, aParms);
+ RTTESTI_CHECK_MSG_RET(rcExpected == rc2, ("Expected %Rrc, got %Rrc\n", rcExpected, rc2), rc2);
+ if (RT_SUCCESS(rcExpected))
+ {
+ uint32_t const uModeRet = ShClSvcGetMode();
+ RTTESTI_CHECK_MSG_RET(uMode == uModeRet, ("Expected mode %RU32, got %RU32\n", uMode, uModeRet), VERR_WRONG_TYPE);
+ }
+ return rc2;
+}
+
+static int tstClipboardSetMode(PTSTHGCMMOCKSVC pSvc, uint32_t uMode)
+{
+ return tstSetModeRc(pSvc, uMode, VINF_SUCCESS);
+}
+
+static bool tstClipboardGetMode(PTSTHGCMMOCKSVC pSvc, uint32_t uModeExpected)
+{
+ RT_NOREF(pSvc);
+ RTTESTI_CHECK_RET(ShClSvcGetMode() == uModeExpected, false);
+ return true;
+}
+
+static void tstOperationModes(void)
+{
+ struct VBOXHGCMSVCPARM parms[2];
+ uint32_t u32Mode;
+ int rc;
+
+ RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_MODE");
+
+ PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst();
+
+ /* Reset global variable which doesn't reset itself. */
+ HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_OFF);
+ rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ u32Mode = ShClSvcGetMode();
+ RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_OFF, ("u32Mode=%u\n", (unsigned) u32Mode));
+
+ rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 0, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+
+ rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 2, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+
+ HGCMSvcSetU64(&parms[0], 99);
+ rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+
+ tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_HOST_TO_GUEST);
+ tstSetModeRc(pSvc, 99, VERR_NOT_SUPPORTED);
+ tstClipboardGetMode(pSvc, VBOX_SHCL_MODE_OFF);
+}
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+static void testSetTransferMode(void)
+{
+ RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE");
+
+ PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst();
+
+ /* Invalid parameter. */
+ VBOXHGCMSVCPARM parms[2];
+ HGCMSvcSetU64(&parms[0], 99);
+ int rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+
+ /* Invalid mode. */
+ HGCMSvcSetU32(&parms[0], 99);
+ rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_FLAGS);
+
+ /* Enable transfers. */
+ HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_ENABLED);
+ rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms);
+ RTTESTI_CHECK_RC(rc, VINF_SUCCESS);
+
+ /* Disable transfers again. */
+ HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_DISABLED);
+ rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms);
+ RTTESTI_CHECK_RC(rc, VINF_SUCCESS);
+}
+#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
+
+static void testGuestSimple(void)
+{
+ RTTestISub("Testing client (guest) API - Simple");
+
+ PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst();
+
+ /* Preparations. */
+ VBGLR3SHCLCMDCTX Ctx;
+ RT_ZERO(Ctx);
+
+ /*
+ * Multiple connects / disconnects.
+ */
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, VBOX_SHCL_GF_0_CONTEXT_ID));
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx));
+ /* Report bogus guest features while connecting. */
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, 0xdeadbeef));
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx));
+
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, VBOX_SHCL_GF_0_CONTEXT_ID));
+
+ /*
+ * Feature tests.
+ */
+
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFeatures(Ctx.idClient, 0x0, NULL /* pfHostFeatures */));
+ /* Report bogus features to the host. */
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFeatures(Ctx.idClient, 0xdeadb33f, NULL /* pfHostFeatures */));
+
+ /*
+ * Access denied tests.
+ */
+
+ /* Try reading data from host. */
+ uint8_t abData[32]; uint32_t cbIgnored;
+ RTTESTI_CHECK_RC(VbglR3ClipboardReadData(Ctx.idClient, VBOX_SHCL_FMT_UNICODETEXT,
+ abData, sizeof(abData), &cbIgnored), VERR_ACCESS_DENIED);
+ /* Try writing data without reporting formats before (legacy). */
+ RTTESTI_CHECK_RC(VbglR3ClipboardWriteData(Ctx.idClient, 0xdeadb33f, abData, sizeof(abData)), VERR_ACCESS_DENIED);
+ /* Try writing data without reporting formats before. */
+ RTTESTI_CHECK_RC(VbglR3ClipboardWriteDataEx(&Ctx, 0xdeadb33f, abData, sizeof(abData)), VERR_ACCESS_DENIED);
+ /* Report bogus formats to the host. */
+ RTTESTI_CHECK_RC(VbglR3ClipboardReportFormats(Ctx.idClient, 0xdeadb33f), VERR_ACCESS_DENIED);
+ /* Report supported formats to host. */
+ RTTESTI_CHECK_RC(VbglR3ClipboardReportFormats(Ctx.idClient,
+ VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_BITMAP | VBOX_SHCL_FMT_HTML),
+ VERR_ACCESS_DENIED);
+ /*
+ * Access allowed tests.
+ */
+ tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_BIDIRECTIONAL);
+
+ /* Try writing data without reporting formats before. */
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardWriteDataEx(&Ctx, 0xdeadb33f, abData, sizeof(abData)));
+ /* Try reading data from host. */
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardReadData(Ctx.idClient, VBOX_SHCL_FMT_UNICODETEXT,
+ abData, sizeof(abData), &cbIgnored));
+ /* Report bogus formats to the host. */
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFormats(Ctx.idClient, 0xdeadb33f));
+ /* Report supported formats to host. */
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFormats(Ctx.idClient,
+ VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_BITMAP | VBOX_SHCL_FMT_HTML));
+ /* Tear down. */
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx));
+}
+
+static RTUTF16 tstGetRandUtf8(void)
+{
+ return RTRandU32Ex(0x20, 0x7A);
+}
+
+static char *tstGenerateUtf8StringA(uint32_t uCch)
+{
+ char * pszRand = (char *)RTMemAlloc(uCch + 1);
+ for (uint32_t i = 0; i < uCch; i++)
+ pszRand[i] = tstGetRandUtf8();
+ pszRand[uCch] = 0;
+ return pszRand;
+}
+
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+static RTUTF16 tstGetRandUtf16(void)
+{
+ RTUTF16 wc;
+ do
+ {
+ wc = (RTUTF16)RTRandU32Ex(1, 0xfffd);
+ } while (wc >= 0xd800 && wc <= 0xdfff);
+ return wc;
+}
+
+static PRTUTF16 tstGenerateUtf16StringA(uint32_t uCch)
+{
+ PRTUTF16 pwszRand = (PRTUTF16)RTMemAlloc((uCch + 1) * sizeof(RTUTF16));
+ for (uint32_t i = 0; i < uCch; i++)
+ pwszRand[i] = tstGetRandUtf16();
+ pwszRand[uCch] = 0;
+ return pwszRand;
+}
+#endif /* RT_OS_WINDOWS) || RT_OS_OS2 */
+
+static void testSetHeadless(void)
+{
+ RTTestISub("Testing HOST_FN_SET_HEADLESS");
+
+ PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst();
+
+ VBOXHGCMSVCPARM parms[2];
+ HGCMSvcSetU32(&parms[0], false);
+ int rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ bool fHeadless = ShClSvcGetHeadless();
+ RTTESTI_CHECK_MSG(fHeadless == false, ("fHeadless=%RTbool\n", fHeadless));
+ rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 0, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+ rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 2, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+ HGCMSvcSetU64(&parms[0], 99);
+ rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+ HGCMSvcSetU32(&parms[0], true);
+ rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ fHeadless = ShClSvcGetHeadless();
+ RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless));
+ HGCMSvcSetU32(&parms[0], 99);
+ rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ fHeadless = ShClSvcGetHeadless();
+ RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless));
+}
+
+static void testHostCall(void)
+{
+ tstOperationModes();
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ testSetTransferMode();
+#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
+ testSetHeadless();
+}
+
+
+/*********************************************************************************************************************************
+ * Test: Guest reading from host *
+ ********************************************************************************************************************************/
+#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS)
+/* Called from SHCLX11 thread. */
+static DECLCALLBACK(int) tstTestReadFromHost_ReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser)
+{
+ RT_NOREF(pCtx, fFormats, pvUser);
+
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "tstTestReadFromHost_SvcReportFormatsCallback: fFormats=%#x\n", fFormats);
+ return VINF_SUCCESS;
+}
+
+/* Called by the backend, e.g. for X11 in the SHCLX11 thread. */
+static DECLCALLBACK(int) tstTestReadFromHost_OnClipboardReadCallback(PSHCLCONTEXT pCtx,
+ SHCLFORMAT uFmt, void **ppv, size_t *pcb, void *pvUser)
+{
+ RT_NOREF(pCtx, uFmt, pvUser);
+
+ PCLIPBOARDTESTTASK pTask = (PCLIPBOARDTESTTASK)TstHGCMUtilsTaskGetCurrent(&g_TstCtx.HGCM)->pvUser;
+
+ void *pvData = NULL;
+ size_t cbData = pTask->cbData - pTask->cbProcessed;
+ if (cbData)
+ {
+ pvData = RTMemDup((uint8_t *)pTask->pvData + pTask->cbProcessed, cbData);
+ AssertPtr(pvData);
+ }
+
+ RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Host reporting back %RU32 bytes of data\n", cbData);
+
+ *ppv = pvData;
+ *pcb = cbData;
+
+ return VINF_SUCCESS;
+}
+#endif /* (RT_OS_LINUX) || defined (RT_OS_SOLARIS) */
+
+typedef struct TSTUSERMOCK
+{
+#if defined(RT_OS_LINUX)
+ SHCLX11CTX X11Ctx;
+#endif
+ PSHCLCONTEXT pCtx;
+} TSTUSERMOCK;
+typedef TSTUSERMOCK *PTSTUSERMOCK;
+
+static void tstTestReadFromHost_MockInit(PTSTUSERMOCK pUsrMock, const char *pszName)
+{
+#if defined(RT_OS_LINUX)
+ SHCLCALLBACKS Callbacks;
+ RT_ZERO(Callbacks);
+ Callbacks.pfnReportFormats = tstTestReadFromHost_ReportFormatsCallback;
+ Callbacks.pfnOnClipboardRead = tstTestReadFromHost_OnClipboardReadCallback;
+
+ pUsrMock->pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT));
+ AssertPtrReturnVoid(pUsrMock->pCtx);
+
+ ShClX11Init(&pUsrMock->X11Ctx, &Callbacks, pUsrMock->pCtx, false);
+ ShClX11ThreadStartEx(&pUsrMock->X11Ctx, pszName, false /* fGrab */);
+ /* Give the clipboard time to synchronise. */
+ RTThreadSleep(500);
+#else
+ RT_NOREF(pUsrMock, pszName);
+#endif /* RT_OS_LINUX */
+}
+
+static void tstTestReadFromHost_MockDestroy(PTSTUSERMOCK pUsrMock)
+{
+#if defined(RT_OS_LINUX)
+ ShClX11ThreadStop(&pUsrMock->X11Ctx);
+ ShClX11Destroy(&pUsrMock->X11Ctx);
+ RTMemFree(pUsrMock->pCtx);
+#else
+ RT_NOREF(pUsrMock);
+#endif
+}
+
+static int tstTestReadFromHost_DoIt(PCLIPBOARDTESTCTX pCtx, PCLIPBOARDTESTTASK pTask)
+{
+ size_t cbDst = RT_MAX(_64K, pTask->cbData);
+ uint8_t *pabDst = (uint8_t *)RTMemAllocZ(cbDst);
+ AssertPtrReturn(pabDst, VERR_NO_MEMORY);
+
+ AssertPtr(pTask->pvData); /* Racing condition with host thread? */
+ Assert(pTask->cbChunk); /* Buggy test? */
+ Assert(pTask->cbChunk <= pTask->cbData); /* Ditto. */
+
+ size_t cbToRead = pTask->cbData;
+ switch (pTask->enmFmtGst)
+ {
+ case VBOX_SHCL_FMT_UNICODETEXT:
+#ifndef RT_OS_WINDOWS /** @todo Not sure about OS/2. */
+ cbToRead *= sizeof(RTUTF16);
+#endif
+ break;
+
+ default:
+ break;
+ }
+
+ PVBGLR3SHCLCMDCTX pCmdCtx = &pCtx->Guest.CmdCtx;
+
+ /* Do random chunked reads. */
+ uint32_t const cChunkedReads = RTRandU32Ex(1, 16);
+ RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "%RU32 chunked reads\n", cChunkedReads);
+ for (uint32_t i = 0; i < cChunkedReads; i++)
+ {
+ /* Note! VbglR3ClipboardReadData() currently does not support chunked reads!
+ * It in turn returns VINF_BUFFER_OVERFLOW when the supplied buffer was too small. */
+
+ uint32_t cbChunk = RTRandU32Ex(1, (uint32_t)(pTask->cbData / cChunkedReads));
+ uint32_t cbRead = 0;
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Guest trying to read %RU32 bytes\n", cbChunk);
+ int vrc2 = VbglR3ClipboardReadData(pCmdCtx->idClient, pTask->enmFmtGst, pabDst, cbChunk, &cbRead);
+ if ( vrc2 == VINF_SUCCESS
+ && cbRead == 0) /* No data there yet? */
+ {
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "No data (yet) from host\n");
+ RTThreadSleep(10);
+ continue;
+ }
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Trying reading host clipboard data with a %RU32 buffer -> %Rrc (%RU32)\n", cbChunk, vrc2, cbRead);
+ RTTEST_CHECK_MSG(g_hTest, vrc2 == VINF_BUFFER_OVERFLOW, (g_hTest, "Got %Rrc, expected VINF_BUFFER_OVERFLOW\n", vrc2));
+ }
+
+ /* Last read: Read the data with a buffer big enough. This must succeed. */
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Reading full data (%zu)\n", pTask->cbData);
+ uint32_t cbRead = 0;
+ int vrc2 = VbglR3ClipboardReadData(pCmdCtx->idClient, pTask->enmFmtGst, pabDst, (uint32_t)cbDst, &cbRead);
+ RTTEST_CHECK_MSG(g_hTest, vrc2 == VINF_SUCCESS, (g_hTest, "Got %Rrc, expected VINF_SUCCESS\n", vrc2));
+ RTTEST_CHECK_MSG(g_hTest, cbRead == cbToRead, (g_hTest, "Read %RU32 bytes, expected %zu\n", cbRead, cbToRead));
+
+ if (pTask->enmFmtGst == VBOX_SHCL_FMT_UNICODETEXT)
+ RTTEST_CHECK_MSG(g_hTest, RTUtf16ValidateEncoding((PRTUTF16)pabDst) == VINF_SUCCESS, (g_hTest, "Read data is not valid UTF-16\n"));
+ if (cbRead == cbToRead)
+ {
+#ifndef RT_OS_WINDOWS /** @todo Not sure about OS/2. */
+ PRTUTF16 pwszSrc = NULL;
+ RTTEST_CHECK(g_hTest, RT_SUCCESS(RTStrToUtf16((const char *)pTask->pvData, &pwszSrc)));
+ RTTEST_CHECK_MSG(g_hTest, memcmp(pwszSrc, pabDst, cbRead) == 0, (g_hTest, "Read data does not match host data\n"));
+ RTUtf16Free(pwszSrc);
+#else
+ RTTEST_CHECK_MSG(g_hTest, memcmp(pTask->pvData, pabDst, cbRead) == 0, (g_hTest, "Read data does not match host data\n"));
+#endif
+ }
+
+ RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Read data from host:\n%.*Rhxd\n", cbRead, pabDst);
+
+ RTMemFree(pabDst);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstTestReadFromHost_ThreadGuest(PTSTHGCMUTILSCTX pCtx, void *pvCtx)
+{
+ RTThreadSleep(1000); /* Fudge; wait until the host has prepared the data for the clipboard. */
+
+ PCLIPBOARDTESTCTX pTstCtx = (PCLIPBOARDTESTCTX)pvCtx;
+ AssertPtr(pTstCtx);
+
+ RT_ZERO(pTstCtx->Guest.CmdCtx);
+ RTTEST_CHECK_RC_OK(g_hTest, VbglR3ClipboardConnectEx(&pTstCtx->Guest.CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID));
+
+ RTThreadSleep(1000); /* Fudge; wait until the host has prepared the data for the clipboard. */
+
+ PCLIPBOARDTESTTASK pTstTask = (PCLIPBOARDTESTTASK)pCtx->Task.pvUser;
+ AssertPtr(pTstTask);
+ tstTestReadFromHost_DoIt(pTstCtx, pTstTask);
+
+ /* Signal that the task ended. */
+ TstHGCMUtilsTaskSignal(&pCtx->Task, VINF_SUCCESS);
+
+ RTTEST_CHECK_RC_OK(g_hTest, VbglR3ClipboardDisconnectEx(&pTstCtx->Guest.CmdCtx));
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstTestReadFromHost_ClientConnectedCallback(PTSTHGCMUTILSCTX pCtx, PTSTHGCMMOCKCLIENT pClient,
+ void *pvUser)
+{
+ RT_NOREF(pCtx, pClient);
+
+ PCLIPBOARDTESTCTX pTstCtx = (PCLIPBOARDTESTCTX)pvUser;
+ AssertPtr(pTstCtx); RT_NOREF(pTstCtx);
+
+ RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Client %RU32 connected\n", pClient->idClient);
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstTestReadFromHostSetup(PCLIPBOARDTESTCTX pTstCtx, void **ppvCtx)
+{
+ RT_NOREF(ppvCtx);
+
+ /* Set the right clipboard mode, so that the guest can read from the host. */
+ tstClipboardSetMode(TstHgcmMockSvcInst(), VBOX_SHCL_MODE_BIDIRECTIONAL);
+
+ /* Start the host thread first, so that the guest thread can connect to it later. */
+ TSTHGCMUTILSHOSTCALLBACKS HostCallbacks;
+ RT_ZERO(HostCallbacks);
+ HostCallbacks.pfnOnClientConnected = tstTestReadFromHost_ClientConnectedCallback;
+ TstHGCMUtilsHostThreadStart(&pTstCtx->HGCM, &HostCallbacks, pTstCtx /* pvUser */);
+
+ PCLIPBOARDTESTTASK pTask = &pTstCtx->Task;
+ AssertPtr(pTask);
+ pTask->enmFmtGst = VBOX_SHCL_FMT_UNICODETEXT;
+ pTask->enmFmtHst = pTask->enmFmtGst;
+ pTask->cbChunk = RTRandU32Ex(1, 512);
+ pTask->cbData = RT_ALIGN_32(pTask->cbChunk * RTRandU32Ex(1, 16), 2);
+ Assert(pTask->cbData % sizeof(RTUTF16) == 0);
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+ pTask->pvData = tstGenerateUtf8StringA(pTask->cbData);
+ pTask->cbData++; /* Add terminating zero. */
+#else
+ pTask->pvData = tstGenerateUtf16StringA((uint32_t)(pTask->cbData /* We use bytes == chars here */));
+ pTask->cbData *= sizeof(RTUTF16);
+ pTask->cbData += sizeof(RTUTF16); /* Add terminating zero. */
+#endif
+ pTask->cbProcessed = 0;
+
+ int rc = VINF_SUCCESS;
+
+#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS)
+ /* Initialize the Shared Clipboard backend callbacks. */
+ PSHCLBACKEND pBackend = ShClSvcGetBackend();
+
+ SHCLCALLBACKS ShClCallbacks;
+ RT_ZERO(ShClCallbacks);
+ ShClCallbacks.pfnReportFormats = tstTestReadFromHost_ReportFormatsCallback;
+ ShClCallbacks.pfnOnClipboardRead = tstTestReadFromHost_OnClipboardReadCallback;
+ ShClBackendSetCallbacks(pBackend, &ShClCallbacks);
+#elif defined (RT_OS_WINDOWS)
+ rc = SharedClipboardWinOpen(GetDesktopWindow());
+ if (RT_SUCCESS(rc))
+ {
+ rc = SharedClipboardWinDataWrite(CF_UNICODETEXT, pTask->pvData, (uint32_t)pTask->cbData);
+ SharedClipboardWinClose();
+ }
+#endif /* defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) */
+
+ RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Host data (%RU32):\n%.*Rhxd\n", pTask->cbData, pTask->cbData, pTask->pvData);
+ return rc;
+}
+
+static DECLCALLBACK(int) tstTestReadFromHostExec(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx)
+{
+ RT_NOREF(pvCtx);
+
+ TstHGCMUtilsGuestThreadStart(&pTstCtx->HGCM, tstTestReadFromHost_ThreadGuest, pTstCtx);
+
+ PTSTHGCMUTILSTASK pTask = (PTSTHGCMUTILSTASK)TstHGCMUtilsTaskGetCurrent(&pTstCtx->HGCM);
+
+ bool fUseMock = false;
+ TSTUSERMOCK UsrMock;
+ if (fUseMock)
+ tstTestReadFromHost_MockInit(&UsrMock, "tstX11Hst");
+
+ /* Wait until the task has been finished. */
+ TstHGCMUtilsTaskWait(pTask, RT_MS_30SEC);
+
+ if (fUseMock)
+ tstTestReadFromHost_MockDestroy(&UsrMock);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstTestReadFromHostDestroy(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx)
+{
+ RT_NOREF(pvCtx);
+
+ int vrc = TstHGCMUtilsGuestThreadStop(&pTstCtx->HGCM);
+ AssertRC(vrc);
+ vrc = TstHGCMUtilsHostThreadStop(&pTstCtx->HGCM);
+ AssertRC(vrc);
+
+ return vrc;
+}
+
+
+/*********************************************************************************************************************************
+ * Main *
+ ********************************************************************************************************************************/
+
+/** Test definition table. */
+CLIPBOARDTESTDESC g_aTests[] =
+{
+ /* Tests guest reading clipboard data from the host. */
+ { tstTestReadFromHostSetup, tstTestReadFromHostExec, tstTestReadFromHostDestroy }
+};
+/** Number of tests defined. */
+unsigned g_cTests = RT_ELEMENTS(g_aTests);
+
+static int tstOne(PTESTDESC pTstDesc)
+{
+ PCLIPBOARDTESTCTX pTstCtx = &g_TstCtx;
+
+ void *pvCtx;
+ int rc = pTstDesc->pfnSetup(pTstCtx, &pvCtx);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pTstDesc->pfnExec(pTstCtx, pvCtx);
+
+ int rc2 = pTstDesc->pfnDestroy(pTstCtx, pvCtx);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ return rc;
+}
+
+int main(int argc, char *argv[])
+{
+ /*
+ * Init the runtime, test and say hello.
+ */
+ const char *pcszExecName;
+ NOREF(argc);
+ pcszExecName = strrchr(argv[0], '/');
+ pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0];
+ RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &g_hTest);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ RTTestBanner(g_hTest);
+
+#ifndef DEBUG_andy
+ /* Don't let assertions in the host service panic (core dump) the test cases. */
+ RTAssertSetMayPanic(false);
+#endif
+
+ PTSTHGCMMOCKSVC const pSvc = TstHgcmMockSvcInst();
+ TstHgcmMockSvcCreate(pSvc, sizeof(SHCLCLIENT));
+ TstHgcmMockSvcStart(pSvc);
+
+ /*
+ * Run the tests.
+ */
+ if (0)
+ {
+ testGuestSimple();
+ testHostCall();
+ }
+
+ RT_ZERO(g_TstCtx);
+
+ PTSTHGCMUTILSCTX pCtx = &g_TstCtx.HGCM;
+ TstHGCMUtilsCtxInit(pCtx, pSvc);
+
+ PTSTHGCMUTILSTASK pTask = (PTSTHGCMUTILSTASK)TstHGCMUtilsTaskGetCurrent(pCtx);
+ TstHGCMUtilsTaskInit(pTask);
+ pTask->pvUser = &g_TstCtx.Task;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++)
+ tstOne(&g_aTests[i]);
+
+ TstHGCMUtilsTaskDestroy(pTask);
+
+ TstHgcmMockSvcStop(pSvc);
+ TstHgcmMockSvcDestroy(pSvc);
+
+ /*
+ * Summary
+ */
+ return RTTestSummaryAndDestroy(g_hTest);
+}
+
diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp
new file mode 100644
index 00000000..28130531
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp
@@ -0,0 +1,336 @@
+/* $Id: tstClipboardServiceHost.cpp $ */
+/** @file
+ * Shared Clipboard host service test case.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * 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, in version 3 of the
+ * License.
+ *
+ * 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, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "../VBoxSharedClipboardSvc-internal.h"
+
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+
+extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad (VBOXHGCMSVCFNTABLE *ptable);
+
+static SHCLCLIENT g_Client;
+static VBOXHGCMSVCHELPERS g_Helpers = { NULL };
+
+/** Simple call handle structure for the guest call completion callback */
+struct VBOXHGCMCALLHANDLE_TYPEDEF
+{
+ /** Where to store the result code */
+ int32_t rc;
+};
+
+/** Call completion callback for guest calls. */
+static DECLCALLBACK(int) callComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc)
+{
+ callHandle->rc = rc;
+ return VINF_SUCCESS;
+}
+
+static int setupTable(VBOXHGCMSVCFNTABLE *pTable)
+{
+ pTable->cbSize = sizeof(*pTable);
+ pTable->u32Version = VBOX_HGCM_SVC_VERSION;
+ g_Helpers.pfnCallComplete = callComplete;
+ pTable->pHelpers = &g_Helpers;
+ return VBoxHGCMSvcLoad(pTable);
+}
+
+static void testSetMode(void)
+{
+ struct VBOXHGCMSVCPARM parms[2];
+ VBOXHGCMSVCFNTABLE table;
+ uint32_t u32Mode;
+ int rc;
+
+ RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_MODE");
+ rc = setupTable(&table);
+ RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc));
+
+ /* Reset global variable which doesn't reset itself. */
+ HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_OFF);
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ u32Mode = ShClSvcGetMode();
+ RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_OFF, ("u32Mode=%u\n", (unsigned) u32Mode));
+
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 0, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 2, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+
+ HGCMSvcSetU64(&parms[0], 99);
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+
+ HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_HOST_TO_GUEST);
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ u32Mode = ShClSvcGetMode();
+ RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_HOST_TO_GUEST, ("u32Mode=%u\n", (unsigned) u32Mode));
+
+ HGCMSvcSetU32(&parms[0], 99);
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms);
+ RTTESTI_CHECK_RC(rc, VERR_NOT_SUPPORTED);
+
+ u32Mode = ShClSvcGetMode();
+ RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_OFF, ("u32Mode=%u\n", (unsigned) u32Mode));
+ table.pfnUnload(NULL);
+}
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+static void testSetTransferMode(void)
+{
+ struct VBOXHGCMSVCPARM parms[2];
+ VBOXHGCMSVCFNTABLE table;
+
+ RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE");
+ int rc = setupTable(&table);
+ RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc));
+
+ /* Invalid parameter. */
+ HGCMSvcSetU64(&parms[0], 99);
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+
+ /* Invalid mode. */
+ HGCMSvcSetU32(&parms[0], 99);
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_FLAGS);
+
+ /* Enable transfers. */
+ HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_ENABLED);
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms);
+ RTTESTI_CHECK_RC(rc, VINF_SUCCESS);
+
+ /* Disable transfers again. */
+ HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_DISABLED);
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms);
+ RTTESTI_CHECK_RC(rc, VINF_SUCCESS);
+}
+#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
+
+/* Adds a host data read request message to the client's message queue. */
+static void testMsgAddReadData(PSHCLCLIENT pClient, SHCLFORMATS fFormats)
+{
+ int rc = ShClSvcGuestDataRequest(pClient, fFormats, NULL /* pidEvent */);
+ RTTESTI_CHECK_RC_OK(rc);
+}
+
+/* Does testing of VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, needed for providing compatibility to older Guest Additions clients. */
+static void testGetHostMsgOld(void)
+{
+ struct VBOXHGCMSVCPARM parms[2];
+ VBOXHGCMSVCFNTABLE table;
+ VBOXHGCMCALLHANDLE_TYPEDEF call;
+ int rc;
+
+ RTTestISub("Setting up VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT test");
+ rc = setupTable(&table);
+ RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc));
+ /* Unless we are bidirectional the host message requests will be dropped. */
+ HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL);
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+
+
+ RTTestISub("Testing one format, waiting guest call.");
+ RT_ZERO(g_Client);
+ HGCMSvcSetU32(&parms[0], 0);
+ HGCMSvcSetU32(&parms[1], 0);
+ call.rc = VERR_IPE_UNINITIALIZED_STATUS;
+ table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0);
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0);
+ RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This should get updated only when the guest call completes. */
+ testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_UNICODETEXT);
+ RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA);
+ RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT);
+ RTTESTI_CHECK_RC_OK(call.rc);
+ call.rc = VERR_IPE_UNINITIALIZED_STATUS;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0);
+ RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */
+ table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client);
+
+ RTTestISub("Testing one format, no waiting guest calls.");
+ RT_ZERO(g_Client);
+ table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0);
+ testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_HTML);
+ HGCMSvcSetU32(&parms[0], 0);
+ HGCMSvcSetU32(&parms[1], 0);
+ call.rc = VERR_IPE_UNINITIALIZED_STATUS;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0);
+ RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA);
+ RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_HTML);
+ RTTESTI_CHECK_RC_OK(call.rc);
+ call.rc = VERR_IPE_UNINITIALIZED_STATUS;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0);
+ RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */
+ table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client);
+
+ RTTestISub("Testing two formats, waiting guest call.");
+ RT_ZERO(g_Client);
+ table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0);
+ HGCMSvcSetU32(&parms[0], 0);
+ HGCMSvcSetU32(&parms[1], 0);
+ call.rc = VERR_IPE_UNINITIALIZED_STATUS;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0);
+ RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This should get updated only when the guest call completes. */
+ testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML);
+ RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA);
+ RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT);
+ RTTESTI_CHECK_RC_OK(call.rc);
+ call.rc = VERR_IPE_UNINITIALIZED_STATUS;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0);
+ RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA);
+ RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_HTML);
+ RTTESTI_CHECK_RC_OK(call.rc);
+ call.rc = VERR_IPE_UNINITIALIZED_STATUS;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0);
+ RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */
+ table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client);
+
+ RTTestISub("Testing two formats, no waiting guest calls.");
+ RT_ZERO(g_Client);
+ table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0);
+ testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML);
+ HGCMSvcSetU32(&parms[0], 0);
+ HGCMSvcSetU32(&parms[1], 0);
+ call.rc = VERR_IPE_UNINITIALIZED_STATUS;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0);
+ RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA);
+ RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT);
+ RTTESTI_CHECK_RC_OK(call.rc);
+ call.rc = VERR_IPE_UNINITIALIZED_STATUS;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0);
+ RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA);
+ RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_HTML);
+ RTTESTI_CHECK_RC_OK(call.rc);
+ call.rc = VERR_IPE_UNINITIALIZED_STATUS;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0);
+ RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */
+ table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client);
+ table.pfnUnload(NULL);
+}
+
+static void testSetHeadless(void)
+{
+ struct VBOXHGCMSVCPARM parms[2];
+ VBOXHGCMSVCFNTABLE table;
+ bool fHeadless;
+ int rc;
+
+ RTTestISub("Testing HOST_FN_SET_HEADLESS");
+ rc = setupTable(&table);
+ RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc));
+ /* Reset global variable which doesn't reset itself. */
+ HGCMSvcSetU32(&parms[0], false);
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS,
+ 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ fHeadless = ShClSvcGetHeadless();
+ RTTESTI_CHECK_MSG(fHeadless == false, ("fHeadless=%RTbool\n", fHeadless));
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS,
+ 0, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS,
+ 2, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+ HGCMSvcSetU64(&parms[0], 99);
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS,
+ 1, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+ HGCMSvcSetU32(&parms[0], true);
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS,
+ 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ fHeadless = ShClSvcGetHeadless();
+ RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless));
+ HGCMSvcSetU32(&parms[0], 99);
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS,
+ 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ fHeadless = ShClSvcGetHeadless();
+ RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless));
+ table.pfnUnload(NULL);
+}
+
+static void testHostCall(void)
+{
+ testSetMode();
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ testSetTransferMode();
+#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
+ testSetHeadless();
+}
+
+int main(int argc, char *argv[])
+{
+ /*
+ * Init the runtime, test and say hello.
+ */
+ const char *pcszExecName;
+ NOREF(argc);
+ pcszExecName = strrchr(argv[0], '/');
+ pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0];
+ RTTEST hTest;
+ RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &hTest);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ RTTestBanner(hTest);
+
+ /* Don't let assertions in the host service panic (core dump) the test cases. */
+ RTAssertSetMayPanic(false);
+
+ /*
+ * Run the tests.
+ */
+ testHostCall();
+ testGetHostMsgOld();
+
+ /*
+ * Summary
+ */
+ return RTTestSummaryAndDestroy(hTest);
+}
+
+int ShClBackendInit(PSHCLBACKEND, VBOXHGCMSVCFNTABLE *) { return VINF_SUCCESS; }
+void ShClBackendDestroy(PSHCLBACKEND) { }
+int ShClBackendDisconnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; }
+int ShClBackendConnect(PSHCLBACKEND, PSHCLCLIENT, bool) { return VINF_SUCCESS; }
+int ShClBackendReportFormats(PSHCLBACKEND, PSHCLCLIENT, SHCLFORMATS) { AssertFailed(); return VINF_SUCCESS; }
+int ShClBackendReadData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t, unsigned int *) { AssertFailed(); return VERR_WRONG_ORDER; }
+int ShClBackendWriteData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t) { AssertFailed(); return VINF_SUCCESS; }
+int ShClBackendSync(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; }
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+int ShClBackendTransferCreate(PSHCLBACKEND, PSHCLCLIENT, PSHCLTRANSFER) { return VINF_SUCCESS; }
+int ShClBackendTransferDestroy(PSHCLBACKEND, PSHCLCLIENT, PSHCLTRANSFER) { return VINF_SUCCESS; }
+int ShClBackendTransferGetRoots(PSHCLBACKEND, PSHCLCLIENT, PSHCLTRANSFER) { return VINF_SUCCESS; }
+#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
+
diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp
new file mode 100644
index 00000000..cfb4b665
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp
@@ -0,0 +1,205 @@
+/* $Id: tstClipboardServiceImpl.cpp $ */
+/** @file
+ * Shared Clipboard host service implementation (backend) test case.
+ */
+
+/*
+ * Copyright (C) 2020-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * 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, in version 3 of the
+ * License.
+ *
+ * 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, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "../VBoxSharedClipboardSvc-internal.h"
+
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#ifdef RT_OS_WINDOWS
+# include <VBox/GuestHost/SharedClipboard-win.h>
+#endif
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+
+extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *ptable);
+
+static SHCLCLIENT g_Client;
+static VBOXHGCMSVCHELPERS g_Helpers = { NULL };
+
+/** Simple call handle structure for the guest call completion callback */
+struct VBOXHGCMCALLHANDLE_TYPEDEF
+{
+ /** Where to store the result code */
+ int32_t rc;
+};
+
+/** Call completion callback for guest calls. */
+static DECLCALLBACK(int) callComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc)
+{
+ callHandle->rc = rc;
+ return VINF_SUCCESS;
+}
+
+static int setupTable(VBOXHGCMSVCFNTABLE *pTable)
+{
+ pTable->cbSize = sizeof(*pTable);
+ pTable->u32Version = VBOX_HGCM_SVC_VERSION;
+ g_Helpers.pfnCallComplete = callComplete;
+ pTable->pHelpers = &g_Helpers;
+ return VBoxHGCMSvcLoad(pTable);
+}
+
+int ShClBackendInit(PSHCLBACKEND, VBOXHGCMSVCFNTABLE *) { return VINF_SUCCESS; }
+void ShClBackendDestroy(PSHCLBACKEND) { }
+int ShClBackendDisconnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; }
+int ShClBackendConnect(PSHCLBACKEND, PSHCLCLIENT, bool) { return VINF_SUCCESS; }
+int ShClBackendReportFormats(PSHCLBACKEND, PSHCLCLIENT, SHCLFORMATS) { AssertFailed(); return VINF_SUCCESS; }
+int ShClBackendReadData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t, unsigned int *) { AssertFailed(); return VERR_WRONG_ORDER; }
+int ShClBackendWriteData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t) { AssertFailed(); return VINF_SUCCESS; }
+int ShClBackendSync(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; }
+
+static void testAnnounceAndReadData(void)
+{
+ struct VBOXHGCMSVCPARM parms[2];
+ VBOXHGCMSVCFNTABLE table;
+ int rc;
+
+ RTTestISub("Setting up client ...");
+ RTTestIDisableAssertions();
+
+ rc = setupTable(&table);
+ RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc));
+ /* Unless we are bidirectional the host message requests will be dropped. */
+ HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL);
+ rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ rc = shClSvcClientInit(&g_Client, 1 /* clientId */);
+ RTTESTI_CHECK_RC_OK(rc);
+
+ RTTestIRestoreAssertions();
+}
+
+#ifdef RT_OS_WINDOWS
+# include "VBoxOrgCfHtml1.h" /* From chrome 97.0.4692.71 */
+# include "VBoxOrgMimeHtml1.h"
+
+static void testHtmlCf(void)
+{
+ RTTestISub("CF_HTML");
+
+ char *pszOutput = NULL;
+ uint32_t cbOutput = UINT32_MAX/2;
+ RTTestIDisableAssertions();
+ RTTESTI_CHECK_RC(SharedClipboardWinConvertCFHTMLToMIME("", 0, &pszOutput, &cbOutput), VERR_INVALID_PARAMETER);
+ RTTestIRestoreAssertions();
+
+ pszOutput = NULL;
+ cbOutput = UINT32_MAX/2;
+ RTTESTI_CHECK_RC(SharedClipboardWinConvertCFHTMLToMIME((char *)&g_abVBoxOrgCfHtml1[0], g_cbVBoxOrgCfHtml1,
+ &pszOutput, &cbOutput), VINF_SUCCESS);
+ RTTESTI_CHECK(cbOutput == g_cbVBoxOrgMimeHtml1);
+ RTTESTI_CHECK(memcmp(pszOutput, g_abVBoxOrgMimeHtml1, cbOutput) == 0);
+ RTMemFree(pszOutput);
+
+
+ static RTSTRTUPLE const s_aRoundTrips[] =
+ {
+ { RT_STR_TUPLE("") },
+ { RT_STR_TUPLE("1") },
+ { RT_STR_TUPLE("12") },
+ { RT_STR_TUPLE("123") },
+ { RT_STR_TUPLE("1234") },
+ { RT_STR_TUPLE("12345") },
+ { RT_STR_TUPLE("123456") },
+ { RT_STR_TUPLE("1234567") },
+ { RT_STR_TUPLE("12345678") },
+ { RT_STR_TUPLE("123456789") },
+ { RT_STR_TUPLE("1234567890") },
+ { RT_STR_TUPLE("<h2>asdfkjhasdflhj</h2>") },
+ { RT_STR_TUPLE("<h2>asdfkjhasdflhj</h2>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0") },
+ { (const char *)g_abVBoxOrgMimeHtml1, sizeof(g_abVBoxOrgMimeHtml1) },
+ };
+
+ for (size_t i = 0; i < RT_ELEMENTS(s_aRoundTrips); i++)
+ {
+ int rc;
+ char *pszCfHtml = NULL;
+ uint32_t cbCfHtml = UINT32_MAX/2;
+ rc = SharedClipboardWinConvertMIMEToCFHTML(s_aRoundTrips[i].psz, s_aRoundTrips[i].cch + 1, &pszCfHtml, &cbCfHtml);
+ if (rc == VINF_SUCCESS)
+ {
+ if (strlen(pszCfHtml) + 1 != cbCfHtml)
+ RTTestIFailed("#%u: SharedClipboardWinConvertMIMEToCFHTML(%s, %#zx,,) returned incorrect length: %#x, actual %#zx",
+ i, s_aRoundTrips[i].psz, s_aRoundTrips[i].cch, cbCfHtml, strlen(pszCfHtml) + 1);
+
+ char *pszHtml = NULL;
+ uint32_t cbHtml = UINT32_MAX/4;
+ rc = SharedClipboardWinConvertCFHTMLToMIME(pszCfHtml, (uint32_t)strlen(pszCfHtml), &pszHtml, &cbHtml);
+ if (rc == VINF_SUCCESS)
+ {
+ if (strlen(pszHtml) + 1 != cbHtml)
+ RTTestIFailed("#%u: SharedClipboardWinConvertCFHTMLToMIME(%s, %#zx,,) returned incorrect length: %#x, actual %#zx",
+ i, pszHtml, strlen(pszHtml), cbHtml, strlen(pszHtml) + 1);
+ if (strcmp(pszHtml, s_aRoundTrips[i].psz) != 0)
+ RTTestIFailed("#%u: roundtrip for '%s' LB %#zx failed, ended up with '%s'",
+ i, s_aRoundTrips[i].psz, s_aRoundTrips[i].cch, pszHtml);
+ RTMemFree(pszHtml);
+ }
+ else
+ RTTestIFailed("#%u: SharedClipboardWinConvertCFHTMLToMIME(%s, %#zx,,) returned %Rrc, expected VINF_SUCCESS",
+ i, pszCfHtml, strlen(pszCfHtml), rc);
+ RTMemFree(pszCfHtml);
+ }
+ else
+ RTTestIFailed("#%u: SharedClipboardWinConvertMIMEToCFHTML(%s, %#zx,,) returned %Rrc, expected VINF_SUCCESS",
+ i, s_aRoundTrips[i].psz, s_aRoundTrips[i].cch, rc);
+ }
+}
+
+#endif /* RT_OS_WINDOWS */
+
+
+int main(int argc, char *argv[])
+{
+ /*
+ * Init the runtime, test and say hello.
+ */
+ const char *pcszExecName;
+ NOREF(argc);
+ pcszExecName = strrchr(argv[0], '/');
+ pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0];
+ RTTEST hTest;
+ RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &hTest);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ RTTestBanner(hTest);
+
+ /*
+ * Run the tests.
+ */
+ testAnnounceAndReadData();
+#ifdef RT_OS_WINDOWS
+ testHtmlCf();
+#endif
+
+ /*
+ * Summary
+ */
+ return RTTestSummaryAndDestroy(hTest);
+}
+
diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp
new file mode 100644
index 00000000..dfe363ee
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp
@@ -0,0 +1,389 @@
+/* $Id: tstClipboardTransfers.cpp $ */
+/** @file
+ * Shared Clipboard transfers test case.
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * 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, in version 3 of the
+ * License.
+ *
+ * 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, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "../VBoxSharedClipboardSvc-internal.h"
+
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+
+#include <iprt/assert.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+
+
+static int testCreateTempDir(RTTEST hTest, const char *pszTestcase, char *pszTempDir, size_t cbTempDir)
+{
+ char szTempDir[RTPATH_MAX];
+ int rc = RTPathTemp(szTempDir, sizeof(szTempDir));
+ RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc);
+
+ rc = RTPathAppend(szTempDir, sizeof(szTempDir), "tstClipboardTransfers");
+ RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc);
+
+ rc = RTDirCreate(szTempDir, 0700, 0);
+ if (rc == VERR_ALREADY_EXISTS)
+ rc = VINF_SUCCESS;
+ RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc);
+
+ rc = RTPathAppend(szTempDir, sizeof(szTempDir), "XXXXX");
+ RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc);
+
+ rc = RTDirCreateTemp(szTempDir, 0700);
+ RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc);
+
+ rc = RTPathJoin(pszTempDir, cbTempDir, szTempDir, pszTestcase);
+ RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc);
+
+ RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Created temporary directory: %s\n", pszTempDir);
+
+ return rc;
+}
+
+static int testRemoveTempDir(RTTEST hTest)
+{
+ char szTempDir[RTPATH_MAX];
+ int rc = RTPathTemp(szTempDir, sizeof(szTempDir));
+ RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc);
+
+ rc = RTPathAppend(szTempDir, sizeof(szTempDir), "tstClipboardTransfers");
+ RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc);
+
+ rc = RTDirRemoveRecursive(szTempDir, RTDIRRMREC_F_CONTENT_AND_DIR);
+ RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc);
+
+ RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Removed temporary directory: %s\n", szTempDir);
+
+ return rc;
+}
+
+static int testCreateDir(RTTEST hTest, const char *pszPathToCreate)
+{
+ RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Creating directory: %s\n", pszPathToCreate);
+
+ int rc = RTDirCreateFullPath(pszPathToCreate, 0700);
+ if (rc == VERR_ALREADY_EXISTS)
+ rc = VINF_SUCCESS;
+ RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc);
+
+ return rc;
+}
+
+static int testCreateFile(RTTEST hTest, const char *pszTempDir, const char *pszFileName, uint32_t fOpen, size_t cbSize,
+ char **ppszFilePathAbs)
+{
+ char szFilePath[RTPATH_MAX];
+
+ int rc = RTStrCopy(szFilePath, sizeof(szFilePath), pszTempDir);
+ RTTESTI_CHECK_RC_OK_RET(rc, rc);
+
+ rc = RTPathAppend(szFilePath, sizeof(szFilePath), pszFileName);
+ RTTESTI_CHECK_RC_OK_RET(rc, rc);
+
+ char *pszDirToCreate = RTStrDup(szFilePath);
+ RTTESTI_CHECK_RET(pszDirToCreate, VERR_NO_MEMORY);
+
+ RTPathStripFilename(pszDirToCreate);
+
+ rc = testCreateDir(hTest, pszDirToCreate);
+ RTTESTI_CHECK_RC_OK_RET(rc, rc);
+
+ RTStrFree(pszDirToCreate);
+ pszDirToCreate = NULL;
+
+ if (!fOpen)
+ fOpen = RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE;
+
+ RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Creating file: %s\n", szFilePath);
+
+ RTFILE hFile;
+ rc = RTFileOpen(&hFile, szFilePath, fOpen);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbSize)
+ {
+ /** @todo Fill in some random stuff. */
+ }
+
+ rc = RTFileClose(hFile);
+ RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc);
+ }
+
+ if (ppszFilePathAbs)
+ *ppszFilePathAbs = RTStrDup(szFilePath);
+
+ return rc;
+}
+
+typedef struct TESTTRANSFERROOTENTRY
+{
+ TESTTRANSFERROOTENTRY(const RTCString &a_strPath)
+ : strPath(a_strPath) { }
+
+ RTCString strPath;
+} TESTTRANSFERROOTENTRY;
+
+static int testAddRootEntry(RTTEST hTest, const char *pszTempDir,
+ const TESTTRANSFERROOTENTRY &rootEntry, char **ppszRoots)
+{
+ char *pszRoots = NULL;
+
+ const char *pszPath = rootEntry.strPath.c_str();
+
+ char *pszPathAbs;
+ int rc = testCreateFile(hTest, pszTempDir, pszPath, 0, 0, &pszPathAbs);
+ RTTESTI_CHECK_RC_OK_RET(rc, rc);
+
+ rc = RTStrAAppend(&pszRoots, pszPathAbs);
+ RTTESTI_CHECK_RC_OK(rc);
+
+ rc = RTStrAAppend(&pszRoots, "\r\n");
+ RTTESTI_CHECK_RC_OK(rc);
+
+ RTStrFree(pszPathAbs);
+
+ *ppszRoots = pszRoots;
+
+ return rc;
+}
+
+static int testAddRootEntries(RTTEST hTest, const char *pszTempDir,
+ RTCList<TESTTRANSFERROOTENTRY> &lstBase, RTCList<TESTTRANSFERROOTENTRY> lstToExtend,
+ char **ppszRoots)
+{
+ int rc = VINF_SUCCESS;
+
+ char *pszRoots = NULL;
+
+ for (size_t i = 0; i < lstBase.size(); ++i)
+ {
+ char *pszEntry = NULL;
+ rc = testAddRootEntry(hTest, pszTempDir, lstBase.at(i), &pszEntry);
+ RTTESTI_CHECK_RC_OK_BREAK(rc);
+ rc = RTStrAAppend(&pszRoots, pszEntry);
+ RTTESTI_CHECK_RC_OK_BREAK(rc);
+ RTStrFree(pszEntry);
+ }
+
+ for (size_t i = 0; i < lstToExtend.size(); ++i)
+ {
+ char *pszEntry = NULL;
+ rc = testAddRootEntry(hTest, pszTempDir, lstToExtend.at(i), &pszEntry);
+ RTTESTI_CHECK_RC_OK_BREAK(rc);
+ rc = RTStrAAppend(&pszRoots, pszEntry);
+ RTTESTI_CHECK_RC_OK_BREAK(rc);
+ RTStrFree(pszEntry);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppszRoots = pszRoots;
+
+ return rc;
+}
+
+static void testTransferRootsSetSingle(RTTEST hTest,
+ RTCList<TESTTRANSFERROOTENTRY> &lstBase, RTCList<TESTTRANSFERROOTENTRY> lstToExtend,
+ int rcExpected)
+{
+ PSHCLTRANSFER pTransfer;
+ int rc = ShClTransferCreate(&pTransfer);
+ RTTESTI_CHECK_RC_OK(rc);
+
+ char szTestTransferRootsSetDir[RTPATH_MAX];
+ rc = testCreateTempDir(hTest, "testTransferRootsSet", szTestTransferRootsSetDir, sizeof(szTestTransferRootsSetDir));
+ RTTESTI_CHECK_RC_OK_RETV(rc);
+
+ /* This is the file we're trying to access (but not supposed to). */
+ rc = testCreateFile(hTest, szTestTransferRootsSetDir, "must-not-access-this", 0, 0, NULL);
+ RTTESTI_CHECK_RC_OK(rc);
+
+ char *pszRoots;
+ rc = testAddRootEntries(hTest, szTestTransferRootsSetDir, lstBase, lstToExtend, &pszRoots);
+ RTTESTI_CHECK_RC_OK_RETV(rc);
+
+ rc = ShClTransferRootsSet(pTransfer, pszRoots, strlen(pszRoots) + 1);
+ RTTESTI_CHECK_RC(rc, rcExpected);
+
+ RTStrFree(pszRoots);
+
+ rc = ShClTransferDestroy(pTransfer);
+ RTTESTI_CHECK_RC_OK(rc);
+}
+
+static void testTransferObjOpenSingle(RTTEST hTest,
+ RTCList<TESTTRANSFERROOTENTRY> &lstRoots, const char *pszObjPath, int rcExpected)
+{
+ RT_NOREF(hTest);
+
+ PSHCLTRANSFER pTransfer;
+ int rc = ShClTransferCreate(&pTransfer);
+ RTTESTI_CHECK_RC_OK(rc);
+
+ rc = ShClTransferInit(pTransfer, SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_LOCAL);
+ RTTESTI_CHECK_RC_OK(rc);
+
+ char szTestTransferObjOpenDir[RTPATH_MAX];
+ rc = testCreateTempDir(hTest, "testTransferObjOpen", szTestTransferObjOpenDir, sizeof(szTestTransferObjOpenDir));
+ RTTESTI_CHECK_RC_OK_RETV(rc);
+
+ /* This is the file we're trying to access (but not supposed to). */
+ rc = testCreateFile(hTest, szTestTransferObjOpenDir, "file1.txt", 0, 0, NULL);
+ RTTESTI_CHECK_RC_OK(rc);
+
+ RTCList<TESTTRANSFERROOTENTRY> lstToExtendEmpty;
+
+ char *pszRoots;
+ rc = testAddRootEntries(hTest, szTestTransferObjOpenDir, lstRoots, lstToExtendEmpty, &pszRoots);
+ RTTESTI_CHECK_RC_OK_RETV(rc);
+
+ rc = ShClTransferRootsSet(pTransfer, pszRoots, strlen(pszRoots) + 1);
+ RTTESTI_CHECK_RC_OK(rc);
+
+ RTStrFree(pszRoots);
+
+ SHCLOBJOPENCREATEPARMS openCreateParms;
+ rc = ShClTransferObjOpenParmsInit(&openCreateParms);
+ RTTESTI_CHECK_RC_OK(rc);
+
+ rc = RTStrCopy(openCreateParms.pszPath, openCreateParms.cbPath, pszObjPath);
+ RTTESTI_CHECK_RC_OK(rc);
+
+ SHCLOBJHANDLE hObj;
+ rc = ShClTransferObjOpen(pTransfer, &openCreateParms, &hObj);
+ RTTESTI_CHECK_RC(rc, rcExpected);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ShClTransferObjClose(pTransfer, hObj);
+ RTTESTI_CHECK_RC_OK(rc);
+ }
+
+ rc = ShClTransferDestroy(pTransfer);
+ RTTESTI_CHECK_RC_OK(rc);
+}
+
+static void testTransferBasics(RTTEST hTest)
+{
+ RT_NOREF(hTest);
+
+ RTTestISub("Testing transfer basics");
+
+ SHCLEVENTSOURCE Source;
+ int rc = ShClEventSourceCreate(&Source, 0);
+ RTTESTI_CHECK_RC_OK(rc);
+ rc = ShClEventSourceDestroy(&Source);
+ RTTESTI_CHECK_RC_OK(rc);
+ PSHCLTRANSFER pTransfer;
+ rc = ShClTransferCreate(&pTransfer);
+ RTTESTI_CHECK_RC_OK(rc);
+ rc = ShClTransferDestroy(pTransfer);
+ RTTESTI_CHECK_RC_OK(rc);
+}
+
+static void testTransferRootsSet(RTTEST hTest)
+{
+ RTTestISub("Testing setting transfer roots");
+
+ /* Define the (valid) transfer root set. */
+ RTCList<TESTTRANSFERROOTENTRY> lstBase;
+ lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/file1.txt"));
+ lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/file1.txt"));
+ lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/sub1/file1.txt"));
+ lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/file1.txt"));
+ lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/sub1/file1.txt"));
+
+ RTCList<TESTTRANSFERROOTENTRY> lstBreakout;
+ testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VINF_SUCCESS);
+
+ lstBreakout.clear();
+ lstBase.append(TESTTRANSFERROOTENTRY("../must-not-access-this"));
+ testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER);
+
+ lstBreakout.clear();
+ lstBase.append(TESTTRANSFERROOTENTRY("does-not-exist/file1.txt"));
+ testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER);
+
+ lstBreakout.clear();
+ lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/../must-not-access-this"));
+ testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER);
+
+ lstBreakout.clear();
+ lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/./../must-not-access-this"));
+ testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER);
+
+ lstBreakout.clear();
+ lstBase.append(TESTTRANSFERROOTENTRY("../does-not-exist"));
+ testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER);
+}
+
+static void testTransferObjOpen(RTTEST hTest)
+{
+ RTTestISub("Testing setting transfer object open");
+
+ /* Define the (valid) transfer root set. */
+ RTCList<TESTTRANSFERROOTENTRY> lstRoots;
+ lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/file1.txt"));
+ lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/file1.txt"));
+ lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/sub1/file1.txt"));
+ lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/file1.txt"));
+ lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/sub1/file1.txt"));
+
+ testTransferObjOpenSingle(hTest, lstRoots, "file1.txt", VINF_SUCCESS);
+ testTransferObjOpenSingle(hTest, lstRoots, "does-not-exist.txt", VERR_PATH_NOT_FOUND);
+ testTransferObjOpenSingle(hTest, lstRoots, "dir1/does-not-exist.txt", VERR_PATH_NOT_FOUND);
+ testTransferObjOpenSingle(hTest, lstRoots, "../must-not-access-this.txt", VERR_INVALID_PARAMETER);
+ testTransferObjOpenSingle(hTest, lstRoots, "dir1/../../must-not-access-this.txt", VERR_INVALID_PARAMETER);
+}
+
+int main(int argc, char *argv[])
+{
+ /*
+ * Init the runtime, test and say hello.
+ */
+ const char *pcszExecName;
+ NOREF(argc);
+ pcszExecName = strrchr(argv[0], '/');
+ pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0];
+ RTTEST hTest;
+ RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &hTest);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ RTTestBanner(hTest);
+
+ testTransferBasics(hTest);
+ testTransferRootsSet(hTest);
+ testTransferObjOpen(hTest);
+
+ int rc = testRemoveTempDir(hTest);
+ RTTESTI_CHECK_RC(rc, VINF_SUCCESS);
+
+ /*
+ * Summary
+ */
+ return RTTestSummaryAndDestroy(hTest);
+}
+