VirtualBox

source: vbox/trunk/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp@ 99937

Last change on this file since 99937 was 99937, checked in by vboxsync, 22 months ago

Shared Clipboard: Added new testcase for the HTTP server in combination with the Shared Clipboard API, various updates and general improvements. bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.8 KB
Line 
1/* $Id: clipboard-transfers-http.cpp 99937 2023-05-23 15:38:52Z vboxsync $ */
2/** @file
3 * Shared Clipboard: HTTP server implementation for Shared Clipboard transfers on UNIX-y guests / hosts.
4 */
5
6/*
7 * Copyright (C) 2020-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
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#include <signal.h>
33
34#include <iprt/http.h>
35#include <iprt/http-server.h>
36
37#include <iprt/net.h> /* To make use of IPv4Addr in RTGETOPTUNION. */
38
39#include <iprt/asm.h>
40#include <iprt/assert.h>
41#include <iprt/ctype.h>
42#include <iprt/errcore.h>
43#include <iprt/file.h>
44#include <iprt/getopt.h>
45#include <iprt/initterm.h>
46#include <iprt/list.h>
47#include <iprt/mem.h>
48#include <iprt/message.h>
49#include <iprt/path.h>
50#include <iprt/rand.h>
51#include <iprt/stream.h>
52#include <iprt/string.h>
53#include <iprt/thread.h>
54#include <iprt/uuid.h>
55#include <iprt/vfs.h>
56
57#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
58#include <iprt/log.h>
59
60#include <VBox/HostServices/VBoxClipboardSvc.h>
61#include <VBox/GuestHost/SharedClipboard-x11.h>
62#include <VBox/GuestHost/SharedClipboard-transfers.h>
63
64
65/*********************************************************************************************************************************
66* Definitations *
67*********************************************************************************************************************************/
68
69typedef struct _SHCLHTTPSERVERTRANSFER
70{
71 /** The node list. */
72 RTLISTNODE Node;
73 /** Pointer to associated transfer. */
74 PSHCLTRANSFER pTransfer;
75 /** The (cached) root list of the transfer. NULL if not cached yet. */
76 PSHCLROOTLIST pRootList;
77 /** Critical section for serializing access. */
78 RTCRITSECT CritSect;
79 /** The handle we're going to use for this HTTP transfer. */
80 SHCLOBJHANDLE hObj;
81 /** The virtual path of the HTTP server's root directory for this transfer.
82 * Always has to start with a "/". */
83 char szPathVirtual[RTPATH_MAX];
84} SHCLHTTPSERVERTRANSFER;
85typedef SHCLHTTPSERVERTRANSFER *PSHCLHTTPSERVERTRANSFER;
86
87
88/*********************************************************************************************************************************
89* Prototypes *
90*********************************************************************************************************************************/
91static int shClTransferHttpServerDestroyInternal(PSHCLHTTPSERVER pThis);
92static const char *shClTransferHttpServerGetHost(PSHCLHTTPSERVER pSrv);
93static int shClTransferHttpServerDestroyTransfer(PSHCLHTTPSERVER pSrv, PSHCLHTTPSERVERTRANSFER pSrvTx);
94
95
96/*********************************************************************************************************************************
97* Internal Shared Clipboard HTTP transfer functions *
98*********************************************************************************************************************************/
99
100DECLINLINE(void) shClHttpTransferLock(PSHCLHTTPSERVERTRANSFER pSrvTx)
101{
102 int rc2 = RTCritSectEnter(&pSrvTx->CritSect);
103 AssertRC(rc2);
104}
105
106DECLINLINE(void) shClHttpTransferUnlock(PSHCLHTTPSERVERTRANSFER pSrvTx)
107{
108 int rc2 = RTCritSectLeave(&pSrvTx->CritSect);
109 AssertRC(rc2);
110}
111
112/**
113 * Return the HTTP server transfer for a specific transfer ID.
114 *
115 * @returns Pointer to HTTP server transfer if found, NULL if not found.
116 * @param pSrv HTTP server instance.
117 * @param idTransfer Transfer ID to return HTTP server transfer for.
118 */
119static PSHCLHTTPSERVERTRANSFER shClTransferHttpServerGetTransferById(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer)
120{
121 PSHCLHTTPSERVERTRANSFER pSrvTx;
122 RTListForEach(&pSrv->lstTransfers, pSrvTx, SHCLHTTPSERVERTRANSFER, Node) /** @todo Slow O(n) lookup, but does it for now. */
123 {
124 if (pSrvTx->pTransfer->State.uID == idTransfer)
125 return pSrvTx;
126 }
127
128 return NULL;
129}
130
131/**
132 * Returns a HTTP server transfer from a given URL.
133 *
134 * @returns Pointer to HTTP server transfer if found, NULL if not found.
135 * @param pThis HTTP server instance data.
136 * @param pszUrl URL to validate.
137 */
138DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromUrl(PSHCLHTTPSERVER pThis, const char *pszUrl)
139{
140 AssertPtrReturn(pszUrl, NULL);
141
142 PSHCLHTTPSERVERTRANSFER pSrvTx = NULL;
143
144 PSHCLHTTPSERVERTRANSFER pSrvTxCur;
145 RTListForEach(&pThis->lstTransfers, pSrvTxCur, SHCLHTTPSERVERTRANSFER, Node)
146 {
147 AssertPtr(pSrvTxCur->pTransfer);
148
149 LogFlowFunc(("pSrvTxCur=%s\n", pSrvTxCur->szPathVirtual));
150
151 /* Be picky here, do a case sensitive comparison. */
152 if (RTStrStartsWith(pszUrl, pSrvTxCur->szPathVirtual))
153 {
154 pSrvTx = pSrvTxCur;
155 break;
156 }
157 }
158
159 if (!pSrvTx)
160 LogRel2(("Shared Clipboard: HTTP URL '%s' not valid\n", pszUrl));
161
162 LogFlowFunc(("pszUrl=%s, pSrvTx=%p\n", pszUrl, pSrvTx));
163 return pSrvTx;
164}
165
166/**
167 * Returns a HTTP server transfer from an internal HTTP handle.
168 *
169 * @returns Pointer to HTTP server transfer if found, NULL if not found.
170 * @param pThis HTTP server instance data.
171 * @param pvHandle Handle to return transfer for.
172 */
173DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromHandle(PSHCLHTTPSERVER pThis, void *pvHandle)
174{
175 AssertPtrReturn(pvHandle, NULL);
176
177 const SHCLTRANSFERID uHandle = *(uint16_t *)pvHandle;
178
179 /** @ŧodo Use a handle lookup table (map) later. */
180 PSHCLHTTPSERVERTRANSFER pSrvTxCur;
181 RTListForEach(&pThis->lstTransfers, pSrvTxCur, SHCLHTTPSERVERTRANSFER, Node)
182 {
183 AssertPtr(pSrvTxCur->pTransfer);
184
185 if (pSrvTxCur->pTransfer->State.uID == uHandle) /** @ŧodo We're using the transfer ID as handle for now. */
186 return pSrvTxCur;
187 }
188
189 return NULL;
190}
191
192static int shClTransferHttpGetTransferRoots(PSHCLHTTPSERVER pThis, PSHCLHTTPSERVERTRANSFER pSrvTx)
193{
194 RT_NOREF(pThis);
195
196 int rc = VINF_SUCCESS;
197
198 if (pSrvTx->pRootList == NULL)
199 {
200 AssertMsgReturn(ShClTransferRootsCount(pSrvTx->pTransfer) == 1,
201 ("At the moment only single files are supported!\n"), VERR_NOT_SUPPORTED);
202
203 rc = ShClTransferRootsGet(pSrvTx->pTransfer, &pSrvTx->pRootList);
204 }
205
206 return rc;
207}
208
209
210/*********************************************************************************************************************************
211* HTTP server callback implementations *
212*********************************************************************************************************************************/
213
214/** @copydoc RTHTTPSERVERCALLBACKS::pfnRequestBegin */
215static DECLCALLBACK(int) shClTransferHttpBegin(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq)
216{
217 PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser; RT_NOREF(pThis);
218 Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
219
220 LogRel2(("Shared Clipboard: HTTP request begin\n"));
221
222 PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpGetTransferFromUrl(pThis, pReq->pszUrl);
223 if (pSrvTx)
224 {
225 shClHttpTransferLock(pSrvTx);
226 pReq->pvUser = pSrvTx;
227 }
228
229 return VINF_SUCCESS;
230}
231
232/** @copydoc RTHTTPSERVERCALLBACKS::pfnRequestEnd */
233static DECLCALLBACK(int) shClTransferHttpEnd(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq)
234{
235 PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser; RT_NOREF(pThis);
236 Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
237
238 LogRel2(("Shared Clipboard: HTTP request end\n"));
239
240 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser;
241 if (pSrvTx)
242 {
243 shClHttpTransferUnlock(pSrvTx);
244 pReq->pvUser = NULL;
245 }
246
247 return VINF_SUCCESS;
248
249}
250
251/** @copydoc RTHTTPSERVERCALLBACKS::pfnOpen */
252static DECLCALLBACK(int) shClTransferHttpOpen(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, void **ppvHandle)
253{
254 PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser; RT_NOREF(pThis);
255 Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
256
257 int rc;
258
259 AssertPtr(pReq->pvUser);
260 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser;
261 if (pSrvTx)
262 {
263 LogRel2(("Shared Clipboard: HTTP transfer (handle %RU64) started ...\n", pSrvTx->hObj));
264
265 Assert(pSrvTx->hObj != NIL_SHCLOBJHANDLE);
266 *ppvHandle = &pSrvTx->hObj;
267 }
268 else
269 rc = VERR_NOT_FOUND;
270
271 if (RT_FAILURE(rc))
272 LogRel(("Shared Clipboard: Error starting HTTP transfer for '%s', rc=%Rrc\n", pReq->pszUrl, rc));
273
274 LogFlowFuncLeaveRC(rc);
275 return rc;
276}
277
278/** @copydoc RTHTTPSERVERCALLBACKS::pfnRead */
279static DECLCALLBACK(int) shClTransferHttpRead(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq,
280 void *pvHandle, void *pvBuf, size_t cbBuf, size_t *pcbRead)
281{
282 RT_NOREF(pData);
283
284 int rc;
285
286 LogRel3(("Shared Clipboard: Reading %RU32 bytes from HTTP ...\n", cbBuf));
287
288 AssertPtr(pReq->pvUser);
289 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser;
290 if (pSrvTx)
291 {
292 PSHCLOBJHANDLE phObj = (PSHCLOBJHANDLE)pvHandle;
293 if (phObj)
294 {
295 uint32_t cbRead;
296 rc = ShClTransferObjRead(pSrvTx->pTransfer, *phObj, pvBuf, cbBuf, 0 /* fFlags */, &cbRead);
297 if (RT_SUCCESS(rc))
298 *pcbRead = (uint32_t)cbRead;
299
300 if (RT_FAILURE(rc))
301 LogRel(("Shared Clipboard: Error reading HTTP transfer (handle %RU64), rc=%Rrc\n", *phObj, rc));
302 }
303 else
304 rc = VERR_NOT_FOUND;
305 }
306 else
307 rc = VERR_NOT_FOUND;
308
309 LogFlowFuncLeaveRC(rc);
310 return rc;
311}
312
313/** @copydoc RTHTTPSERVERCALLBACKS::pfnClose */
314static DECLCALLBACK(int) shClTransferHttpClose(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, void *pvHandle)
315{
316 RT_NOREF(pData);
317
318 int rc;
319
320 AssertPtr(pReq->pvUser);
321 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser;
322 if (pSrvTx)
323 {
324 PSHCLOBJHANDLE phObj = (PSHCLOBJHANDLE)pvHandle;
325 if (phObj)
326 {
327 Assert(*phObj != NIL_SHCLOBJHANDLE);
328 rc = ShClTransferObjClose(pSrvTx->pTransfer, *phObj);
329 if (RT_SUCCESS(rc))
330 {
331 pSrvTx->hObj = NIL_SHCLOBJHANDLE;
332 LogRel2(("Shared Clipboard: HTTP transfer %RU16 done\n", pSrvTx->pTransfer->State.uID));
333 }
334
335 if (RT_FAILURE(rc))
336 LogRel(("Shared Clipboard: Error closing HTTP transfer (handle %RU64), rc=%Rrc\n", *phObj, rc));
337 }
338 else
339 rc = VERR_NOT_FOUND;
340 }
341 else
342 rc = VERR_NOT_FOUND;
343
344 LogFlowFuncLeaveRC(rc);
345 return rc;
346}
347
348/** @copydoc RTHTTPSERVERCALLBACKS::pfnQueryInfo */
349static DECLCALLBACK(int) shClTransferHttpQueryInfo(PRTHTTPCALLBACKDATA pData,
350 PRTHTTPSERVERREQ pReq, PRTFSOBJINFO pObjInfo, char **ppszMIMEHint)
351{
352 RT_NOREF(ppszMIMEHint);
353
354 PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser;
355 Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
356
357 int rc;
358
359 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser;
360 if (pSrvTx)
361 {
362 rc = shClTransferHttpGetTransferRoots(pThis, pSrvTx);
363 if (RT_SUCCESS(rc))
364 {
365 SHCLOBJOPENCREATEPARMS openParms;
366 rc = ShClTransferObjOpenParmsInit(&openParms);
367 if (RT_SUCCESS(rc))
368 {
369 openParms.fCreate = SHCL_OBJ_CF_ACCESS_READ
370 | SHCL_OBJ_CF_ACCESS_DENYWRITE;
371
372 PSHCLTRANSFER pTx = pSrvTx->pTransfer;
373 AssertPtr(pTx);
374
375 /* For now we only serve single files, hence index 0 below. */
376 SHCLROOTLISTENTRY ListEntry;
377 rc = ShClTransferRootsEntry(pTx, 0 /* First file */, &ListEntry);
378 if (RT_SUCCESS(rc))
379 {
380 rc = RTStrCopy(openParms.pszPath, openParms.cbPath, ListEntry.pszName);
381 if (RT_SUCCESS(rc))
382 {
383 rc = ShClTransferObjOpen(pTx, &openParms, &pSrvTx->hObj);
384 if (RT_SUCCESS(rc))
385 {
386 char szPath[RTPATH_MAX];
387 rc = ShClTransferGetRootPathAbs(pTx, szPath, sizeof(szPath));
388 if (RT_SUCCESS(rc))
389 {
390 rc = RTPathAppend(szPath, sizeof(szPath), openParms.pszPath);
391 if (RT_SUCCESS(rc))
392 {
393 /* Now that the object is locked, query information that we can return. */
394 rc = RTPathQueryInfo(szPath, pObjInfo, RTFSOBJATTRADD_NOTHING);
395 }
396 }
397 }
398 }
399 }
400
401 ShClTransferObjOpenParmsDestroy(&openParms);
402 }
403 }
404 }
405 else
406 rc = VERR_NOT_FOUND;
407
408 LogFlowFuncLeaveRC(rc);
409 return rc;
410}
411
412/** @copydoc RTHTTPSERVERCALLBACKS::pfnDestroy */
413static DECLCALLBACK(int) shClTransferHttpDestroy(PRTHTTPCALLBACKDATA pData)
414{
415 PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser;
416 Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
417
418 return shClTransferHttpServerDestroyInternal(pThis);
419}
420
421
422/*********************************************************************************************************************************
423* Internal Shared Clipboard HTTP server functions *
424*********************************************************************************************************************************/
425
426/**
427 * Destroys a Shared Clipboard HTTP server instance, internal version.
428 *
429 * @returns VBox status code.
430 * @param pSrv Shared Clipboard HTTP server instance to destroy.
431 */
432static int shClTransferHttpServerDestroyInternal(PSHCLHTTPSERVER pSrv)
433{
434 int rc = VINF_SUCCESS;
435
436 PSHCLHTTPSERVERTRANSFER pSrvTx, pSrvTxNext;
437 RTListForEachSafe(&pSrv->lstTransfers, pSrvTx, pSrvTxNext, SHCLHTTPSERVERTRANSFER, Node)
438 {
439 int rc2 = shClTransferHttpServerDestroyTransfer(pSrv, pSrvTx);
440 if (RT_SUCCESS(rc))
441 rc = rc2;
442 }
443
444 RTHttpServerResponseDestroy(&pSrv->Resp);
445
446 pSrv->hHTTPServer = NIL_RTHTTPSERVER;
447
448 if (RTCritSectIsInitialized(&pSrv->CritSect))
449 {
450 int rc2 = RTCritSectDelete(&pSrv->CritSect);
451 if (RT_SUCCESS(rc))
452 rc = rc2;
453 }
454
455 return rc;
456}
457
458/**
459 * Locks the critical section of a Shared Clipboard HTTP server instance.
460 *
461 * @param pSrv Shared Clipboard HTTP server instance to lock.
462 */
463DECLINLINE(void) shClTransferHttpServerLock(PSHCLHTTPSERVER pSrv)
464{
465 int rc2 = RTCritSectEnter(&pSrv->CritSect);
466 AssertRC(rc2);
467}
468
469/**
470 * Unlocks the critical section of a Shared Clipboard HTTP server instance.
471 *
472 * @param pSrv Shared Clipboard HTTP server instance to unlock.
473 */
474DECLINLINE(void) shClTransferHttpServerUnlock(PSHCLHTTPSERVER pSrv)
475{
476 int rc2 = RTCritSectLeave(&pSrv->CritSect);
477 AssertRC(rc2);
478}
479
480/**
481 * Initializes a new Shared Clipboard HTTP server instance.
482 *
483 * @param pSrv HTTP server instance to initialize.
484 */
485static void shClTransferHttpServerInitInternal(PSHCLHTTPSERVER pSrv)
486{
487 pSrv->hHTTPServer = NIL_RTHTTPSERVER;
488 pSrv->uPort = 0;
489 RTListInit(&pSrv->lstTransfers);
490 pSrv->cTransfers = 0;
491 int rc2 = RTHttpServerResponseInit(&pSrv->Resp);
492 AssertRC(rc2);
493}
494
495
496/*********************************************************************************************************************************
497* Public Shared Clipboard HTTP server functions *
498*********************************************************************************************************************************/
499
500/**
501 * Initializes a new Shared Clipboard HTTP server instance.
502 *
503 * @param pSrv HTTP server instance to initialize.
504 */
505void ShClTransferHttpServerInit(PSHCLHTTPSERVER pSrv)
506{
507 AssertPtrReturnVoid(pSrv);
508
509 shClTransferHttpServerInitInternal(pSrv);
510}
511
512/**
513 * Returns whether a given TCP port is known to be buggy or not.
514 *
515 * @returns \c true if the given port is known to be buggy, or \c false if not.
516 * @param uPort TCP port to check.
517 */
518static bool shClTransferHttpServerPortIsBuggy(uint16_t uPort)
519{
520 uint16_t const aBuggyPorts[] = {
521#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
522 /* GNOME Nautilus ("Files") v43 is unable download HTTP files from this port. */
523 8080
524#else /* Prevents zero-sized arrays. */
525 0
526#endif
527 };
528
529 for (size_t i = 0; i < RT_ELEMENTS(aBuggyPorts); i++)
530 if (uPort == aBuggyPorts[i])
531 return true;
532 return false;
533}
534
535/**
536 * Creates a new Shared Clipboard HTTP server instance, extended version.
537 *
538 * @returns VBox status code.
539 * @return VERR_ADDRESS_CONFLICT if the port is already taken or the port is known to be buggy.
540 * @param pSrv HTTP server instance to create.
541 * @param uPort TCP port number to use.
542 */
543int ShClTransferHttpServerCreateEx(PSHCLHTTPSERVER pSrv, uint16_t uPort)
544{
545 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
546 AssertReturn(uPort, VERR_INVALID_PARAMETER);
547
548 AssertReturn(!shClTransferHttpServerPortIsBuggy(uPort), VERR_ADDRESS_CONFLICT);
549
550 RTHTTPSERVERCALLBACKS Callbacks;
551 RT_ZERO(Callbacks);
552
553 Callbacks.pfnRequestBegin = shClTransferHttpBegin;
554 Callbacks.pfnRequestEnd = shClTransferHttpEnd;
555 Callbacks.pfnOpen = shClTransferHttpOpen;
556 Callbacks.pfnRead = shClTransferHttpRead;
557 Callbacks.pfnClose = shClTransferHttpClose;
558 Callbacks.pfnQueryInfo = shClTransferHttpQueryInfo;
559 Callbacks.pfnDestroy = shClTransferHttpDestroy;
560
561 /* Note: The server always and *only* runs against the localhost interface. */
562 int rc = RTHttpServerCreate(&pSrv->hHTTPServer, "localhost", uPort, &Callbacks,
563 pSrv, sizeof(SHCLHTTPSERVER));
564 if (RT_SUCCESS(rc))
565 {
566 rc = RTCritSectInit(&pSrv->CritSect);
567 AssertRCReturn(rc, rc);
568
569 pSrv->uPort = uPort;
570
571 LogRel2(("Shared Clipboard: HTTP server running at port %RU16\n", pSrv->uPort));
572 }
573 else
574 {
575 int rc2 = shClTransferHttpServerDestroyInternal(pSrv);
576 AssertRC(rc2);
577 }
578
579 if (RT_FAILURE(rc))
580 LogRel(("Shared Clipboard: HTTP server failed to run, rc=%Rrc\n", rc));
581
582 return rc;
583}
584
585/**
586 * Creates a new Shared Clipboard HTTP server instance using a random port (>= 1024).
587 *
588 * This does automatic probing of TCP ports if a port already is being used.
589 *
590 * @returns VBox status code.
591 * @param pSrv HTTP server instance to create.
592 * @param cMaxAttempts Maximum number of attempts to create a HTTP server.
593 * @param puPort Where to return the TCP port number being used on success. Optional.
594 */
595int ShClTransferHttpServerCreate(PSHCLHTTPSERVER pSrv, unsigned cMaxAttempts, uint16_t *puPort)
596{
597 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
598 AssertReturn(cMaxAttempts, VERR_INVALID_PARAMETER);
599 /* puPort is optional. */
600
601 RTRAND hRand;
602 int rc = RTRandAdvCreateSystemFaster(&hRand); /* Should be good enough for this task. */
603 if (RT_SUCCESS(rc))
604 {
605 uint16_t uPort;
606 unsigned i = 0;
607 for (i; i < cMaxAttempts; i++)
608 {
609 /* Try some random ports above 1024 (i.e. "unprivileged ports") -- required, as VBoxClient runs as a user process
610 * on the guest. */
611 uPort = RTRandAdvU32Ex(hRand, 1024, UINT16_MAX);
612
613 /* If the port selected turns is known to be buggy for whatever reason, skip it and try another one. */
614 if (shClTransferHttpServerPortIsBuggy(uPort))
615 continue;
616
617 rc = ShClTransferHttpServerCreateEx(pSrv, (uint32_t)uPort);
618 if (RT_SUCCESS(rc))
619 {
620 if (puPort)
621 *puPort = uPort;
622 break;
623 }
624 }
625
626 if ( RT_FAILURE(rc)
627 && i == cMaxAttempts)
628 LogRel(("Shared Clipboard: Maximum attempts to create HTTP server reached (%u), giving up\n", cMaxAttempts));
629
630 RTRandAdvDestroy(hRand);
631 }
632
633 return rc;
634}
635
636/**
637 * Destroys a Shared Clipboard HTTP server instance.
638 *
639 * @returns VBox status code.
640 * @param pSrv HTTP server instance to destroy.
641 */
642int ShClTransferHttpServerDestroy(PSHCLHTTPSERVER pSrv)
643{
644 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
645
646 if (pSrv->hHTTPServer == NIL_RTHTTPSERVER)
647 return VINF_SUCCESS;
648
649 int rc = RTHttpServerDestroy(pSrv->hHTTPServer);
650 if (RT_SUCCESS(rc))
651 rc = shClTransferHttpServerDestroyInternal(pSrv);
652
653 if (RT_SUCCESS(rc))
654 LogRel2(("Shared Clipboard: HTTP server stopped\n"));
655 else
656 LogRel(("Shared Clipboard: HTTP server failed to stop, rc=%Rrc\n", rc));
657
658 return rc;
659}
660
661/**
662 * Returns the host name (scheme) of a HTTP server instance.
663 *
664 * @returns Host name (scheme).
665 * @param pSrv HTTP server instance to return host name (scheme) for.
666 *
667 * @note This is hardcoded to "localhost" for now.
668 */
669static const char *shClTransferHttpServerGetHost(PSHCLHTTPSERVER pSrv)
670{
671 RT_NOREF(pSrv);
672 return "http://localhost"; /* Hardcoded for now. */
673}
674
675/**
676 * Destroys a server transfer, internal version.
677 *
678 * @returns VBox status code.
679 * @param pSrv HTTP server instance to unregister transfer from.
680 * @param pTransfer Server transfer to destroy
681 * The pointer will be invalid on success.
682 */
683static int shClTransferHttpServerDestroyTransfer(PSHCLHTTPSERVER pSrv, PSHCLHTTPSERVERTRANSFER pSrvTx)
684{
685 RTListNodeRemove(&pSrvTx->Node);
686
687 Assert(pSrv->cTransfers);
688 pSrv->cTransfers--;
689
690 LogFunc(("pTransfer=%p, idTransfer=%RU16, szPath=%s -> %RU32 transfers\n",
691 pSrvTx->pTransfer, pSrvTx->pTransfer->State.uID, pSrvTx->szPathVirtual, pSrv->cTransfers));
692
693 LogRel2(("Shared Clipboard: Destroyed HTTP transfer %RU16, now %RU32 HTTP transfers total\n",
694 pSrvTx->pTransfer->State.uID, pSrv->cTransfers));
695
696 int rc = RTCritSectDelete(&pSrvTx->CritSect);
697 AssertRCReturn(rc, rc);
698
699 RTMemFree(pSrvTx);
700 pSrvTx = NULL;
701
702 return rc;
703}
704
705
706/*********************************************************************************************************************************
707* Public Shared Clipboard HTTP server functions *
708*********************************************************************************************************************************/
709
710/**
711 * Registers a Shared Clipboard transfer to a HTTP server instance.
712 *
713 * @returns VBox status code.
714 * @param pSrv HTTP server instance to register transfer for.
715 * @param pTransfer Transfer to register. Needs to be on the heap.
716 */
717int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer)
718{
719 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
720 AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
721
722 AssertMsgReturn(pTransfer->State.uID, ("Transfer needs to be registered with a transfer context first\n"),
723 VERR_INVALID_PARAMETER);
724
725 uint32_t const cRoots = ShClTransferRootsCount(pTransfer);
726 AssertMsgReturn(cRoots > 0, ("Transfer has no root entries\n"), VERR_INVALID_PARAMETER);
727 AssertMsgReturn(cRoots == 1, ("Only single files are supported for now\n"), VERR_NOT_SUPPORTED);
728 /** @todo Check for directories? */
729
730 shClTransferHttpServerLock(pSrv);
731
732 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)RTMemAllocZ(sizeof(SHCLHTTPSERVERTRANSFER));
733 AssertPtrReturn(pSrvTx, VERR_NO_MEMORY);
734
735 RTUUID Uuid;
736 int rc = RTUuidCreate(&Uuid);
737 if (RT_SUCCESS(rc))
738 {
739 char szUuid[64];
740 rc = RTUuidToStr(&Uuid, szUuid, sizeof(szUuid));
741 if (RT_SUCCESS(rc))
742 {
743 rc = RTCritSectInit(&pSrvTx->CritSect);
744 AssertRC(rc);
745
746 SHCLROOTLISTENTRY ListEntry;
747 rc = ShClTransferRootsEntry(pTransfer, 0 /* First file */, &ListEntry);
748 if (RT_SUCCESS(rc))
749 {
750#ifdef DEBUG_andy
751 /* Create the virtual HTTP path for the transfer.
752 * Every transfer has a dedicated HTTP path (but live in the same URL namespace). */
753 ssize_t cch = RTStrPrintf2(pSrvTx->szPathVirtual, sizeof(pSrvTx->szPathVirtual), "/%s/uuid/%RU32/%s",
754 SHCL_HTTPT_URL_NAMESPACE, pSrv->cTransfers, ListEntry.pszName);
755#else
756 /* Create the virtual HTTP path for the transfer.
757 * Every transfer has a dedicated HTTP path (but live in the same URL namespace). */
758 ssize_t cch = RTStrPrintf2(pSrvTx->szPathVirtual, sizeof(pSrvTx->szPathVirtual), "/%s/%s/%RU16/%s",
759 SHCL_HTTPT_URL_NAMESPACE, szUuid, pTransfer->State.uID, ListEntry.pszName);
760#endif
761 AssertReturn(cch, VERR_BUFFER_OVERFLOW);
762
763 pSrvTx->pTransfer = pTransfer;
764 pSrvTx->pRootList = NULL;
765 pSrvTx->hObj = NIL_SHCLOBJHANDLE;
766
767 RTListAppend(&pSrv->lstTransfers, &pSrvTx->Node);
768 pSrv->cTransfers++;
769
770 LogFunc(("pTransfer=%p, idTransfer=%RU16, szPath=%s -> %RU32 transfers\n",
771 pSrvTx->pTransfer, pSrvTx->pTransfer->State.uID, pSrvTx->szPathVirtual, pSrv->cTransfers));
772
773 LogRel2(("Shared Clipboard: Registered HTTP transfer %RU16, now %RU32 HTTP transfers total\n",
774 pTransfer->State.uID, pSrv->cTransfers));
775 }
776 }
777 }
778
779 if (RT_FAILURE(rc))
780 RTMemFree(pSrvTx);
781
782 shClTransferHttpServerUnlock(pSrv);
783
784 LogFlowFuncLeaveRC(rc);
785 return rc;
786}
787
788/**
789 * Unregisters a formerly registered Shared Clipboard transfer.
790 *
791 * @returns VBox status code.
792 * @param pSrv HTTP server instance to unregister transfer from.
793 * @param pTransfer Transfer to unregister.
794 */
795int ShClTransferHttpServerUnregisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer)
796{
797 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
798 AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
799
800 shClTransferHttpServerLock(pSrv);
801
802 AssertReturn(pSrv->cTransfers, VERR_WRONG_ORDER);
803
804 int rc = VINF_SUCCESS;
805
806 PSHCLHTTPSERVERTRANSFER pSrvTx;
807 RTListForEach(&pSrv->lstTransfers, pSrvTx, SHCLHTTPSERVERTRANSFER, Node)
808 {
809 AssertPtr(pSrvTx->pTransfer);
810 if (pSrvTx->pTransfer->State.uID == pTransfer->State.uID)
811 {
812 rc = shClTransferHttpServerDestroyTransfer(pSrv, pSrvTx);
813 break;
814 }
815 }
816
817 shClTransferHttpServerUnlock(pSrv);
818
819 LogFlowFuncLeaveRC(rc);
820 return rc;
821}
822
823/**
824 * Returns whether a specific transfer ID is registered with a HTTP server instance or not.
825 *
826 * @returns \c true if the transfer ID is registered, \c false if not.
827 * @param pSrv HTTP server instance.
828 * @param idTransfer Transfer ID to check for.
829 */
830bool ShClTransferHttpServerHasTransfer(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer)
831{
832 AssertPtrReturn(pSrv, false);
833
834 shClTransferHttpServerLock(pSrv);
835
836 const bool fRc = shClTransferHttpServerGetTransferById(pSrv, idTransfer) != NULL;
837
838 shClTransferHttpServerUnlock(pSrv);
839
840 return fRc;
841}
842
843/**
844 * Returns the used TCP port number of a HTTP server instance.
845 *
846 * @returns TCP port number. 0 if not specified yet.
847 * @param pSrv HTTP server instance to return port for.
848 */
849uint16_t ShClTransferHttpServerGetPort(PSHCLHTTPSERVER pSrv)
850{
851 AssertPtrReturn(pSrv, 0);
852
853 shClTransferHttpServerLock(pSrv);
854
855 const uint16_t uPort = pSrv->uPort;
856
857 shClTransferHttpServerUnlock(pSrv);
858
859 return uPort;
860}
861
862/**
863 * Returns the number of registered HTTP server transfers of a HTTP server instance.
864 *
865 * @returns Number of registered transfers.
866 * @param pSrv HTTP server instance to return registered transfers for.
867 */
868uint32_t ShClTransferHttpServerGetTransferCount(PSHCLHTTPSERVER pSrv)
869{
870 AssertPtrReturn(pSrv, 0);
871
872 shClTransferHttpServerLock(pSrv);
873
874 const uint32_t cTransfers = pSrv->cTransfers;
875
876 shClTransferHttpServerUnlock(pSrv);
877
878 return cTransfers;
879}
880
881/**
882 * Returns an allocated string with a HTTP server instance's address.
883 *
884 * @returns Allocated string with a HTTP server instance's address, or NULL on OOM.
885 * Needs to be free'd by the caller using RTStrFree().
886 * @param pSrv HTTP server instance to return address for.
887 */
888char *ShClTransferHttpServerGetAddressA(PSHCLHTTPSERVER pSrv)
889{
890 AssertPtrReturn(pSrv, NULL);
891
892 shClTransferHttpServerLock(pSrv);
893
894 char *pszAddress = RTStrAPrintf2("%s:%RU16", shClTransferHttpServerGetHost(pSrv), pSrv->uPort);
895 AssertPtr(pszAddress);
896
897 shClTransferHttpServerUnlock(pSrv);
898
899 return pszAddress;
900}
901
902/**
903 * Returns an allocated string with the URL of a given Shared Clipboard transfer ID.
904 *
905 * @returns Allocated string with the URL of a given Shared Clipboard transfer ID, or NULL if not found.
906 * Needs to be free'd by the caller using RTStrFree().
907 * @param pSrv HTTP server instance to return URL for.
908 * @param idTransfer Transfer ID to return the URL for.
909 */
910char *ShClTransferHttpServerGetUrlA(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer)
911{
912 AssertPtrReturn(pSrv, NULL);
913 AssertReturn(idTransfer != NIL_SHCLTRANSFERID, NULL);
914
915 shClTransferHttpServerLock(pSrv);
916
917 PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpServerGetTransferById(pSrv, idTransfer);
918 if (!pSrvTx)
919 {
920 AssertFailed();
921 shClTransferHttpServerUnlock(pSrv);
922 return NULL;
923 }
924
925 AssertReturn(RTStrNLen(pSrvTx->szPathVirtual, RTPATH_MAX), NULL);
926 char *pszUrl = RTStrAPrintf2("%s:%RU16%s", shClTransferHttpServerGetHost(pSrv), pSrv->uPort, pSrvTx->szPathVirtual);
927 AssertPtr(pszUrl);
928
929 shClTransferHttpServerUnlock(pSrv);
930
931 return pszUrl;
932}
933
934/**
935 * Returns whether a given HTTP server instance is running or not.
936 *
937 * @returns \c true if running, or \c false if not.
938 * @param pSrv HTTP server instance to check running state for.
939 */
940bool ShClTransferHttpServerIsRunning(PSHCLHTTPSERVER pSrv)
941{
942 AssertPtrReturn(pSrv, false);
943
944 return (pSrv->hHTTPServer != NIL_RTHTTPSERVER); /* Seems enough for now. */
945}
946
947
948/*********************************************************************************************************************************
949* Public Shared Clipboard HTTP context functions *
950*********************************************************************************************************************************/
951
952/**
953 * Registers a Shared Clipboard transfer to a HTTP context and starts the HTTP server, if not started already.
954 *
955 * @returns VBox status code.
956 * @param pCtx HTTP context to register transfer for.
957 * @param pTransfer Transfer to register.
958 */
959int ShClHttpTransferRegisterAndMaybeStart(PSHCLHTTPCONTEXT pCtx, PSHCLTRANSFER pTransfer)
960{
961 int rc = VINF_SUCCESS;
962
963 /* Start the built-in HTTP server to serve file(s). */
964 if (!ShClTransferHttpServerIsRunning(&pCtx->HttpServer)) /* Only one HTTP server per transfer context. */
965 rc = ShClTransferHttpServerCreate(&pCtx->HttpServer, 32 /* cMaxAttempts */, NULL /* puPort */);
966
967 if (RT_SUCCESS(rc))
968 rc = ShClTransferHttpServerRegisterTransfer(&pCtx->HttpServer, pTransfer);
969
970 return rc;
971}
972
973/**
974 * Unregisters a formerly registered Shared Clipboard transfer and stops the HTTP server, if no transfers are left.
975 *
976 * @returns VBox status code.
977 * @param pCtx HTTP context to unregister transfer from.
978 * @param pTransfer Transfer to unregister.
979 */
980int ShClHttpTransferUnregisterAndMaybeStop(PSHCLHTTPCONTEXT pCtx, PSHCLTRANSFER pTransfer)
981{
982 int rc = VINF_SUCCESS;
983
984 if (ShClTransferHttpServerIsRunning(&pCtx->HttpServer))
985 {
986 /* Try unregistering transfer (if it was registered before). */
987 rc = ShClTransferHttpServerUnregisterTransfer(&pCtx->HttpServer, pTransfer);
988 if (RT_SUCCESS(rc))
989 {
990 /* No more registered transfers left? Tear down the HTTP server instance then. */
991 if (ShClTransferHttpServerGetTransferCount(&pCtx->HttpServer) == 0)
992 rc = ShClTransferHttpServerDestroy(&pCtx->HttpServer);
993 }
994 AssertRC(rc);
995 }
996
997 return rc;
998}
999
Note: See TracBrowser for help on using the repository browser.

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