VirtualBox

source: vbox/trunk/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp@ 102141

Last change on this file since 102141 was 102141, checked in by vboxsync, 15 months ago

Shared Clipboard/Transfers: Implemented support for percent-encoded URLs for HTTP downloads. Extended testcase. bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 14.1 KB
Line 
1/* $Id: tstClipboardHttpServer.cpp 102141 2023-11-17 15:27:32Z vboxsync $ */
2/** @file
3 * Shared Clipboard HTTP server test case.
4 */
5
6/*
7 * Copyright (C) 2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28#include <iprt/assert.h>
29#include <iprt/dir.h>
30#include <iprt/file.h>
31#include <iprt/getopt.h>
32#include <iprt/http.h>
33#include <iprt/message.h>
34#include <iprt/path.h>
35#include <iprt/rand.h>
36#include <iprt/string.h>
37#include <iprt/test.h>
38
39#include <VBox/GuestHost/SharedClipboard-transfers.h>
40
41
42/** The release logger. */
43static PRTLOGGER g_pRelLogger;
44/** The current logging verbosity level. */
45static unsigned g_uVerbosity = 0;
46/** Maximum HTTP server runtime (in ms). */
47static RTMSINTERVAL g_msRuntime = RT_MS_30SEC;
48/** Shutdown indicator. */
49static bool g_fShutdown = false;
50/** Manual mode indicator; allows manual testing w/ other HTTP clients. */
51static bool g_fManual = false;
52
53/** Test files to handle + download.
54 * All files reside in a common temporary directory. */
55static struct
56{
57 /** File name to serve via HTTP server. */
58 const char *pszFileName;
59 /** URL to use for downloading the file via RTHttp APIs. */
60 const char *pszUrl;
61 /** File allocation size.
62 * Specify UINT64_MAX for random size. */
63 size_t cbSize;
64 /** Expected test result. */
65 int rc;
66} g_aTests[] =
67{
68 "file1.txt", "file1.txt", UINT64_MAX, VINF_SUCCESS,
69 /* Note: For RTHttpGetFile() the URL needs to be percent-encoded. */
70 "file2 with spaces.txt", "file2%20with%20spaces.txt", UINT64_MAX, VINF_SUCCESS,
71 "bigfile.bin", "bigfile.bin", _1G, VINF_SUCCESS
72};
73
74/* Worker thread for the HTTP server. */
75static DECLCALLBACK(int) tstSrvWorker(RTTHREAD hThread, void *pvUser)
76{
77 RT_NOREF(pvUser);
78
79 int rc = RTThreadUserSignal(hThread);
80 AssertRCReturn(rc, rc);
81
82 uint32_t const msStartTS = RTTimeMilliTS();
83 while (RTTimeMilliTS() - msStartTS < g_msRuntime)
84 {
85 if (g_fShutdown)
86 break;
87 RTThreadSleep(100); /* Wait a little. */
88 }
89
90 return RTTimeMilliTS() - msStartTS <= g_msRuntime ? VINF_SUCCESS : VERR_TIMEOUT;
91}
92
93static void tstCreateTransferSingle(RTTEST hTest, PSHCLTRANSFERCTX pTransferCtx, PSHCLHTTPSERVER pSrv,
94 const char *pszFile, PSHCLTXPROVIDER pProvider)
95{
96 PSHCLTRANSFER pTx;
97 RTTEST_CHECK_RC_OK(hTest, ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTx));
98 RTTEST_CHECK_RC_OK(hTest, ShClTransferSetProvider(pTx, pProvider));
99 RTTEST_CHECK_RC_OK(hTest, ShClTransferInit(pTx));
100 RTTEST_CHECK_RC_OK(hTest, ShClTransferRootsInitFromFile(pTx, pszFile));
101 RTTEST_CHECK_RC_OK(hTest, ShClTransferCtxRegister(pTransferCtx, pTx, NULL));
102 RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerRegisterTransfer(pSrv, pTx));
103}
104
105int main(int argc, char *argv[])
106{
107 /*
108 * Init the runtime, test and say hello.
109 */
110 RTTEST hTest;
111 RTEXITCODE rcExit = RTTestInitAndCreate("tstClipboardHttpServer", &hTest);
112 if (rcExit != RTEXITCODE_SUCCESS)
113 return rcExit;
114 RTTestBanner(hTest);
115
116 /*
117 * Process options.
118 */
119 static const RTGETOPTDEF aOpts[] =
120 {
121 { "--port", 'p', RTGETOPT_REQ_UINT16 },
122 { "--manual", 'm', RTGETOPT_REQ_NOTHING },
123 { "--max-time", 't', RTGETOPT_REQ_UINT32 },
124 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
125 };
126
127 RTGETOPTSTATE GetState;
128 int rc = RTGetOptInit(&GetState, argc, argv, aOpts, RT_ELEMENTS(aOpts), 1 /*idxFirst*/, 0 /*fFlags - must not sort! */);
129 AssertRCReturn(rc, RTEXITCODE_INIT);
130
131 uint16_t uPort = 0;
132
133 int ch;
134 RTGETOPTUNION ValueUnion;
135 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
136 {
137 switch (ch)
138 {
139 case 'p':
140 uPort = ValueUnion.u16;
141 break;
142
143 case 't':
144 g_msRuntime = ValueUnion.u32 * RT_MS_1SEC; /* Convert s to ms. */
145 break;
146
147 case 'm':
148 g_fManual = true;
149 break;
150
151 case 'v':
152 g_uVerbosity++;
153 break;
154
155 case VINF_GETOPT_NOT_OPTION:
156 continue;
157
158 default:
159 return RTGetOptPrintError(ch, &ValueUnion);
160 }
161 }
162
163 /*
164 * Configure release logging to go to stdout.
165 */
166 RTUINT fFlags = RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG;
167#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
168 fFlags |= RTLOGFLAGS_USECRLF;
169#endif
170 static const char * const s_apszLogGroups[] = VBOX_LOGGROUP_NAMES;
171 rc = RTLogCreate(&g_pRelLogger, fFlags, "all.e.l", "TST_CLIPBOARDR_HTTPSERVER_RELEASE_LOG",
172 RT_ELEMENTS(s_apszLogGroups), s_apszLogGroups, RTLOGDEST_STDOUT, NULL /*"vkat-release.log"*/);
173 if (RT_SUCCESS(rc))
174 {
175 RTLogSetDefaultInstance(g_pRelLogger);
176 if (g_uVerbosity)
177 {
178 RTMsgInfo("Setting verbosity logging to level %u\n", g_uVerbosity);
179 switch (g_uVerbosity) /* Not very elegant, but has to do it for now. */
180 {
181 case 1:
182 rc = RTLogGroupSettings(g_pRelLogger, "shared_clipboard.e.l+http.e.l");
183 break;
184
185 case 2:
186 rc = RTLogGroupSettings(g_pRelLogger, "shared_clipboard.e.l.l2+http.e.l.l2");
187 break;
188
189 case 3:
190 rc = RTLogGroupSettings(g_pRelLogger, "shared_clipboard.e.l.l2.l3+http.e.l.l2.l3");
191 break;
192
193 case 4:
194 RT_FALL_THROUGH();
195 default:
196 rc = RTLogGroupSettings(g_pRelLogger, "shared_clipboard.e.l.l2.l3.l4.f+http.e.l.l2.l3.l4.f");
197 break;
198 }
199 if (RT_FAILURE(rc))
200 RTMsgError("Setting debug logging failed, rc=%Rrc\n", rc);
201 }
202 }
203 else
204 RTMsgWarning("Failed to create release logger: %Rrc", rc);
205
206 /*
207 * Create HTTP server.
208 */
209 SHCLHTTPSERVER HttpSrv;
210 ShClTransferHttpServerInit(&HttpSrv);
211 ShClTransferHttpServerStop(&HttpSrv); /* Try to stop a non-running server twice. */
212 ShClTransferHttpServerStop(&HttpSrv);
213 RTTEST_CHECK(hTest, ShClTransferHttpServerIsRunning(&HttpSrv) == false);
214 if (uPort)
215 rc = ShClTransferHttpServerStartEx(&HttpSrv, uPort);
216 else
217 rc = ShClTransferHttpServerStart(&HttpSrv, 32 /* cMaxAttempts */, &uPort);
218 RTTEST_CHECK_RC_OK(hTest, rc);
219 RTTEST_CHECK(hTest, ShClTransferHttpServerGetTransfer(&HttpSrv, 0) == false);
220 RTTEST_CHECK(hTest, ShClTransferHttpServerGetTransfer(&HttpSrv, 42) == false);
221
222 char *pszSrvAddr = ShClTransferHttpServerGetAddressA(&HttpSrv);
223 RTTEST_CHECK(hTest, pszSrvAddr != NULL);
224 RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "HTTP server running: %s (for %RU32ms) ...\n", pszSrvAddr, g_msRuntime);
225 RTMemFree(pszSrvAddr);
226 pszSrvAddr = NULL;
227
228 SHCLTRANSFERCTX TxCtx;
229 RTTEST_CHECK_RC_OK(hTest, ShClTransferCtxInit(&TxCtx));
230
231 /* Query the local transfer provider. */
232 SHCLTXPROVIDER Provider;
233 RTTESTI_CHECK(ShClTransferProviderLocalQueryInterface(&Provider) != NULL);
234
235 /* Parse options again, but this time we only fetch all files we want to serve.
236 * Only can be done after we initialized the HTTP server above. */
237 RT_ZERO(GetState);
238 rc = RTGetOptInit(&GetState, argc, argv, aOpts, RT_ELEMENTS(aOpts), 1 /*idxFirst*/, 0 /*fFlags - must not sort! */);
239 AssertRCReturn(rc, RTEXITCODE_INIT);
240 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
241 {
242 switch (ch)
243 {
244 case VINF_GETOPT_NOT_OPTION:
245 {
246 tstCreateTransferSingle(hTest, &TxCtx, &HttpSrv, ValueUnion.psz, &Provider);
247 break;
248 }
249
250 default:
251 continue;
252 }
253 }
254
255 char szTempDir[RTPATH_MAX];
256 RTTEST_CHECK_RC_OK(hTest, RTPathTemp(szTempDir, sizeof(szTempDir)));
257 RTTEST_CHECK_RC_OK(hTest, RTPathAppend(szTempDir, sizeof(szTempDir), "tstClipboardHttpServer-XXXXXX"));
258 RTTEST_CHECK_RC_OK(hTest, RTDirCreateTemp(szTempDir, 0700));
259
260 if (!g_fManual)
261 {
262 char szFilePath[RTPATH_MAX];
263 for (size_t i = 0; i < RT_ELEMENTS(g_aTests); i++)
264 {
265 RTTEST_CHECK (hTest, RTStrPrintf(szFilePath, sizeof(szFilePath), szTempDir));
266 RTTEST_CHECK_RC_OK(hTest, RTPathAppend(szFilePath, sizeof(szFilePath), g_aTests[i].pszFileName));
267
268 size_t cbSize = g_aTests[i].cbSize == UINT64_MAX ? RTRandU32Ex(0, _256M) : g_aTests[i].cbSize;
269
270 RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Random test file (%zu bytes): %s\n", cbSize, szFilePath);
271
272 RTFILE hFile;
273 rc = RTFileOpen(&hFile, szFilePath, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE);
274 if (RT_SUCCESS(rc))
275 {
276 uint8_t abBuf[_64K] = { 42 };
277
278 while (cbSize > 0)
279 {
280 size_t cbToWrite = sizeof(abBuf);
281 if (cbToWrite > cbSize)
282 cbToWrite = cbSize;
283 rc = RTFileWrite(hFile, abBuf, cbToWrite, NULL);
284 if (RT_FAILURE(rc))
285 {
286 RTTestIFailed("RTFileWrite(%#x) -> %Rrc\n", cbToWrite, rc);
287 break;
288 }
289 cbSize -= cbToWrite;
290 }
291
292 RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS);
293
294 if (RT_SUCCESS(rc))
295 tstCreateTransferSingle(hTest, &TxCtx, &HttpSrv, szFilePath, &Provider);
296 }
297 else
298 RTTestIFailed("RTFileOpen(%s) -> %Rrc\n", szFilePath, rc);
299 }
300 }
301
302 /* Don't bail out here to prevent cleaning up after ourselves on failure. */
303 if (RTTestErrorCount(hTest) == 0)
304 {
305 /* Create thread for our HTTP server. */
306 RTTHREAD hThread;
307 rc = RTThreadCreate(&hThread, tstSrvWorker, NULL, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE,
308 "tstClpHttpSrv");
309 RTTEST_CHECK_RC_OK(hTest, rc);
310 if (RT_SUCCESS(rc))
311 {
312 rc = RTThreadUserWait(hThread, RT_MS_30SEC);
313 RTTEST_CHECK_RC_OK(hTest, rc);
314 }
315
316 if (RT_SUCCESS(rc))
317 {
318 if (g_fManual)
319 {
320 uint32_t const cTx = ShClTransferCtxGetTotalTransfers(&TxCtx);
321 for (uint32_t i = 0; i < cTx; i++)
322 {
323 PSHCLTRANSFER pTx = ShClTransferCtxGetTransferByIndex(&TxCtx, i);
324
325 uint16_t const uID = ShClTransferGetID(pTx);
326 char *pszURL = ShClTransferHttpServerGetUrlA(&HttpSrv, uID, 0 /* Entry index */);
327 RTTEST_CHECK(hTest, pszURL != NULL);
328 RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "URL #%02RU32: %s\n", i, pszURL);
329 RTStrFree(pszURL);
330 }
331 }
332 else /* Download all files to a temp file using our HTTP client. */
333 {
334 RTHTTP hClient;
335 rc = RTHttpCreate(&hClient);
336 if (RT_SUCCESS(rc))
337 {
338 char szURL[RTPATH_MAX];
339 for (size_t i = 0; i < RT_ELEMENTS(g_aTests); i++)
340 {
341 PSHCLTRANSFER pTx = ShClTransferCtxGetTransferByIndex(&TxCtx, i);
342 char *pszUrlBase = ShClTransferHttpServerGetUrlA(&HttpSrv, ShClTransferGetID(pTx), UINT64_MAX);
343
344 RTTEST_CHECK(hTest, RTStrPrintf2(szURL, sizeof(szURL), "%s/%s", pszUrlBase, g_aTests[i].pszUrl));
345
346 RTStrFree(pszUrlBase);
347
348 char szTempFile[RTPATH_MAX];
349 RTTEST_CHECK_RC_OK(hTest, RTPathTemp(szTempFile, sizeof(szTempFile)));
350 RTTEST_CHECK_RC_OK(hTest, RTPathAppend(szTempFile, sizeof(szTempFile), "tstClipboardHttpServer-XXXXXX"));
351 RTTEST_CHECK_RC_OK(hTest, RTFileCreateTemp(szTempFile, 0600));
352
353 RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Downloading '%s' -> '%s'\n", szURL, szTempFile);
354
355 RTTEST_CHECK_RC_OK(hTest, RTHttpGetFile(hClient, szURL, szTempFile));
356
357 RTTEST_CHECK_RC_OK(hTest, RTFileDelete(szTempFile));
358 }
359
360 RTTEST_CHECK_RC_OK(hTest, RTHttpDestroy(hClient));
361 }
362
363 /* This is supposed to run unattended, so shutdown automatically. */
364 ASMAtomicXchgBool(&g_fShutdown, true); /* Set shutdown indicator. */
365 }
366 }
367
368 int rcThread;
369 RTTEST_CHECK_RC_OK(hTest, RTThreadWait(hThread, g_msRuntime, &rcThread));
370 RTTEST_CHECK_RC_OK(hTest, rcThread);
371
372 RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerDestroy(&HttpSrv));
373 ShClTransferCtxDestroy(&TxCtx);
374 }
375
376 /*
377 * Cleanup
378 */
379 char szFilePath[RTPATH_MAX];
380 for (size_t i = 0; i < RT_ELEMENTS(g_aTests); i++)
381 {
382 RTTEST_CHECK (hTest, RTStrPrintf(szFilePath, sizeof(szFilePath), szTempDir));
383 RTTEST_CHECK_RC_OK(hTest, RTPathAppend(szFilePath, sizeof(szFilePath), g_aTests[i].pszFileName));
384 RTTEST_CHECK_RC_OK(hTest, RTFileDelete(szFilePath));
385 }
386 RTTEST_CHECK_RC_OK(hTest, RTDirRemove(szTempDir));
387
388 /*
389 * Summary
390 */
391 return RTTestSummaryAndDestroy(hTest);
392}
393
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette