VirtualBox

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

Last change on this file since 100347 was 100288, checked in by vboxsync, 21 months ago

Shared Clipboard: More bugfixes for transfers HTTP server. bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 37.5 KB
Line 
1/* $Id: clipboard-transfers-http.cpp 100288 2023-06-26 08:02:07Z 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/semaphore.h>
52#include <iprt/stream.h>
53#include <iprt/string.h>
54#include <iprt/thread.h>
55#include <iprt/uuid.h>
56#include <iprt/vfs.h>
57
58#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
59#include <iprt/log.h>
60
61#include <VBox/HostServices/VBoxClipboardSvc.h>
62#include <VBox/GuestHost/SharedClipboard-x11.h>
63#include <VBox/GuestHost/SharedClipboard-transfers.h>
64
65
66/*********************************************************************************************************************************
67* Definitations *
68*********************************************************************************************************************************/
69
70typedef struct _SHCLHTTPSERVERTRANSFER
71{
72 /** The node list. */
73 RTLISTNODE Node;
74 /** Pointer to associated transfer. */
75 PSHCLTRANSFER pTransfer;
76 /** The (cached) root list of the transfer. NULL if not cached yet. */
77 PSHCLLIST pRootList;
78 /** Critical section for serializing access. */
79 RTCRITSECT CritSect;
80 /** The handle we're going to use for this HTTP transfer. */
81 SHCLOBJHANDLE hObj;
82 /** The virtual path of the HTTP server's root directory for this transfer.
83 * Always has to start with a "/". */
84 char szPathVirtual[RTPATH_MAX];
85} SHCLHTTPSERVERTRANSFER;
86typedef SHCLHTTPSERVERTRANSFER *PSHCLHTTPSERVERTRANSFER;
87
88
89/*********************************************************************************************************************************
90* Prototypes *
91*********************************************************************************************************************************/
92static int shClTransferHttpServerDestroyInternal(PSHCLHTTPSERVER pThis);
93static const char *shClTransferHttpServerGetHost(PSHCLHTTPSERVER pSrv);
94static int shClTransferHttpServerDestroyTransfer(PSHCLHTTPSERVER pSrv, PSHCLHTTPSERVERTRANSFER pSrvTx);
95static SHCLHTTPSERVERSTATUS shclTransferHttpServerSetStatusLocked(PSHCLHTTPSERVER pSrv, SHCLHTTPSERVERSTATUS fStatus);
96
97
98/*********************************************************************************************************************************
99* Internal Shared Clipboard HTTP transfer functions *
100*********************************************************************************************************************************/
101
102/**
103 * Locks the critical section of a Shared Clipboard HTTP server instance.
104 *
105 * @param pSrv Shared Clipboard HTTP server instance to lock.
106 */
107DECLINLINE(void) shClTransferHttpServerLock(PSHCLHTTPSERVER pSrv)
108{
109 int rc2 = RTCritSectEnter(&pSrv->CritSect);
110 AssertRC(rc2);
111}
112
113/**
114 * Unlocks the critical section of a Shared Clipboard HTTP server instance.
115 *
116 * @param pSrv Shared Clipboard HTTP server instance to unlock.
117 */
118DECLINLINE(void) shClTransferHttpServerUnlock(PSHCLHTTPSERVER pSrv)
119{
120 int rc2 = RTCritSectLeave(&pSrv->CritSect);
121 AssertRC(rc2);
122}
123
124/**
125 * Locks an HTTP transfer.
126 *
127 * @param pSrvTx HTTP transfer to lock.
128 */
129DECLINLINE(void) shClHttpTransferLock(PSHCLHTTPSERVERTRANSFER pSrvTx)
130{
131 int rc2 = RTCritSectEnter(&pSrvTx->CritSect);
132 AssertRC(rc2);
133}
134
135/**
136 * Unlocks an HTTP transfer.
137 *
138 * @param pSrvTx HTTP transfer to unlock.
139 */
140DECLINLINE(void) shClHttpTransferUnlock(PSHCLHTTPSERVERTRANSFER pSrvTx)
141{
142 int rc2 = RTCritSectLeave(&pSrvTx->CritSect);
143 AssertRC(rc2);
144}
145
146/**
147 * Return the HTTP server transfer for a specific transfer ID.
148 *
149 * @returns Pointer to HTTP server transfer if found, NULL if not found.
150 * @param pSrv HTTP server instance.
151 * @param idTransfer Transfer ID to return HTTP server transfer for.
152 */
153static PSHCLHTTPSERVERTRANSFER shClTransferHttpServerGetTransferById(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer)
154{
155 PSHCLHTTPSERVERTRANSFER pSrvTx;
156 RTListForEach(&pSrv->lstTransfers, pSrvTx, SHCLHTTPSERVERTRANSFER, Node) /** @todo Slow O(n) lookup, but does it for now. */
157 {
158 if (pSrvTx->pTransfer->State.uID == idTransfer)
159 return pSrvTx;
160 }
161
162 return NULL;
163}
164
165/**
166 * Returns a HTTP server transfer from a given URL.
167 *
168 * @returns Pointer to HTTP server transfer if found, NULL if not found.
169 * @param pThis HTTP server instance data.
170 * @param pszUrl URL to validate.
171 */
172DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromUrl(PSHCLHTTPSERVER pThis, const char *pszUrl)
173{
174 AssertPtrReturn(pszUrl, NULL);
175
176 PSHCLHTTPSERVERTRANSFER pSrvTx = NULL;
177
178 PSHCLHTTPSERVERTRANSFER pSrvTxCur;
179 RTListForEach(&pThis->lstTransfers, pSrvTxCur, SHCLHTTPSERVERTRANSFER, Node)
180 {
181 AssertPtr(pSrvTxCur->pTransfer);
182
183 LogFlowFunc(("pSrvTxCur=%s\n", pSrvTxCur->szPathVirtual));
184
185 /* Be picky here, do a case sensitive comparison. */
186 if (RTStrStartsWith(pszUrl, pSrvTxCur->szPathVirtual))
187 {
188 pSrvTx = pSrvTxCur;
189 break;
190 }
191 }
192
193 if (!pSrvTx)
194 LogRel2(("Shared Clipboard: HTTP URL '%s' not valid\n", pszUrl));
195
196 LogFlowFunc(("pszUrl=%s, pSrvTx=%p\n", pszUrl, pSrvTx));
197 return pSrvTx;
198}
199
200/**
201 * Returns a HTTP server transfer from an internal HTTP handle.
202 *
203 * @returns Pointer to HTTP server transfer if found, NULL if not found.
204 * @param pThis HTTP server instance data.
205 * @param pvHandle Handle to return transfer for.
206 */
207DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromHandle(PSHCLHTTPSERVER pThis, void *pvHandle)
208{
209 AssertPtrReturn(pvHandle, NULL);
210
211 const SHCLTRANSFERID uHandle = *(uint16_t *)pvHandle;
212
213 /** @ŧodo Use a handle lookup table (map) later. */
214 PSHCLHTTPSERVERTRANSFER pSrvTxCur;
215 RTListForEach(&pThis->lstTransfers, pSrvTxCur, SHCLHTTPSERVERTRANSFER, Node)
216 {
217 AssertPtr(pSrvTxCur->pTransfer);
218
219 if (pSrvTxCur->pTransfer->State.uID == uHandle) /** @ŧodo We're using the transfer ID as handle for now. */
220 return pSrvTxCur;
221 }
222
223 return NULL;
224}
225
226
227/*********************************************************************************************************************************
228* HTTP server callback implementations *
229*********************************************************************************************************************************/
230
231/** @copydoc RTHTTPSERVERCALLBACKS::pfnRequestBegin */
232static DECLCALLBACK(int) shClTransferHttpBegin(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq)
233{
234 PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser; RT_NOREF(pThis);
235 Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
236
237 LogRel2(("Shared Clipboard: HTTP request begin\n"));
238
239 PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpGetTransferFromUrl(pThis, pReq->pszUrl);
240 if (pSrvTx)
241 {
242 shClHttpTransferLock(pSrvTx);
243 pReq->pvUser = pSrvTx;
244 }
245
246 return VINF_SUCCESS;
247}
248
249/** @copydoc RTHTTPSERVERCALLBACKS::pfnRequestEnd */
250static DECLCALLBACK(int) shClTransferHttpEnd(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq)
251{
252 PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser; RT_NOREF(pThis);
253 Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
254
255 LogRel2(("Shared Clipboard: HTTP request end\n"));
256
257 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser;
258 if (pSrvTx)
259 {
260 shClHttpTransferUnlock(pSrvTx);
261 pReq->pvUser = NULL;
262 }
263
264 return VINF_SUCCESS;
265
266}
267
268/** @copydoc RTHTTPSERVERCALLBACKS::pfnOpen */
269static DECLCALLBACK(int) shClTransferHttpOpen(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, void **ppvHandle)
270{
271 PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser; RT_NOREF(pThis);
272 Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
273
274 int rc;
275
276 AssertPtr(pReq->pvUser);
277 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser;
278 if (pSrvTx)
279 {
280 LogRel2(("Shared Clipboard: HTTP transfer (handle %RU64) started ...\n", pSrvTx->hObj));
281
282 Assert(pSrvTx->hObj != NIL_SHCLOBJHANDLE);
283 *ppvHandle = &pSrvTx->hObj;
284 }
285 else
286 rc = VERR_NOT_FOUND;
287
288 if (RT_FAILURE(rc))
289 LogRel(("Shared Clipboard: Error starting HTTP transfer for '%s', rc=%Rrc\n", pReq->pszUrl, rc));
290
291 LogFlowFuncLeaveRC(rc);
292 return rc;
293}
294
295/** @copydoc RTHTTPSERVERCALLBACKS::pfnRead */
296static DECLCALLBACK(int) shClTransferHttpRead(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq,
297 void *pvHandle, void *pvBuf, size_t cbBuf, size_t *pcbRead)
298{
299 RT_NOREF(pData);
300
301 int rc;
302
303 LogRel3(("Shared Clipboard: Reading %RU32 bytes from HTTP ...\n", cbBuf));
304
305 AssertPtr(pReq->pvUser);
306 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser;
307 if (pSrvTx)
308 {
309 PSHCLOBJHANDLE phObj = (PSHCLOBJHANDLE)pvHandle;
310 if (phObj)
311 {
312 uint32_t cbRead;
313 rc = ShClTransferObjRead(pSrvTx->pTransfer, *phObj, pvBuf, cbBuf, 0 /* fFlags */, &cbRead);
314 if (RT_SUCCESS(rc))
315 *pcbRead = (uint32_t)cbRead;
316
317 if (RT_FAILURE(rc))
318 LogRel(("Shared Clipboard: Error reading HTTP transfer (handle %RU64), rc=%Rrc\n", *phObj, rc));
319 }
320 else
321 rc = VERR_NOT_FOUND;
322 }
323 else
324 rc = VERR_NOT_FOUND;
325
326 LogFlowFuncLeaveRC(rc);
327 return rc;
328}
329
330/** @copydoc RTHTTPSERVERCALLBACKS::pfnClose */
331static DECLCALLBACK(int) shClTransferHttpClose(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, void *pvHandle)
332{
333 RT_NOREF(pData);
334
335 int rc;
336
337 AssertPtr(pReq->pvUser);
338 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser;
339 if (pSrvTx)
340 {
341 PSHCLOBJHANDLE phObj = (PSHCLOBJHANDLE)pvHandle;
342 if (phObj)
343 {
344 Assert(*phObj != NIL_SHCLOBJHANDLE);
345 rc = ShClTransferObjClose(pSrvTx->pTransfer, *phObj);
346 if (RT_SUCCESS(rc))
347 {
348 pSrvTx->hObj = NIL_SHCLOBJHANDLE;
349 LogRel2(("Shared Clipboard: HTTP transfer %RU16 done\n", pSrvTx->pTransfer->State.uID));
350 }
351
352 if (RT_FAILURE(rc))
353 LogRel(("Shared Clipboard: Error closing HTTP transfer (handle %RU64), rc=%Rrc\n", *phObj, rc));
354 }
355 else
356 rc = VERR_NOT_FOUND;
357 }
358 else
359 rc = VERR_NOT_FOUND;
360
361 LogFlowFuncLeaveRC(rc);
362 return rc;
363}
364
365/** @copydoc RTHTTPSERVERCALLBACKS::pfnQueryInfo */
366static DECLCALLBACK(int) shClTransferHttpQueryInfo(PRTHTTPCALLBACKDATA pData,
367 PRTHTTPSERVERREQ pReq, PRTFSOBJINFO pObjInfo, char **ppszMIMEHint)
368{
369 RT_NOREF(pData);
370 RT_NOREF(ppszMIMEHint);
371
372 int rc;
373
374 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser;
375 if (pSrvTx)
376 {
377 SHCLOBJOPENCREATEPARMS openParms;
378 rc = ShClTransferObjOpenParmsInit(&openParms);
379 if (RT_SUCCESS(rc))
380 {
381 openParms.fCreate = SHCL_OBJ_CF_ACCESS_READ
382 | SHCL_OBJ_CF_ACCESS_DENYWRITE;
383
384 PSHCLTRANSFER pTx = pSrvTx->pTransfer;
385 AssertPtr(pTx);
386
387 /* For now we only serve single files, hence index 0 below. */
388 PCSHCLLISTENTRY pEntry = ShClTransferRootsEntryGet(pTx, 0 /* First file */);
389 if (pEntry)
390 {
391 rc = RTStrCopy(openParms.pszPath, openParms.cbPath, pEntry->pszName);
392 if (RT_SUCCESS(rc))
393 {
394 rc = ShClTransferObjOpen(pTx, &openParms, &pSrvTx->hObj);
395 if (RT_SUCCESS(rc))
396 {
397 rc = VERR_NOT_SUPPORTED; /* Play safe by default. */
398
399 if ( pEntry->fInfo & VBOX_SHCL_INFO_F_FSOBJINFO
400 && pEntry->cbInfo == sizeof(SHCLFSOBJINFO))
401 {
402 PCSHCLFSOBJINFO pSrcObjInfo = (PSHCLFSOBJINFO)pEntry->pvInfo;
403
404 LogFlowFunc(("pszName=%s, cbInfo=%RU32, fMode=0x%x (type %#x)\n",
405 pEntry->pszName, pEntry->cbInfo, pSrcObjInfo->Attr.fMode, (pSrcObjInfo->Attr.fMode & RTFS_TYPE_MASK)));
406
407 if (RTFS_IS_FILE(pSrcObjInfo->Attr.fMode))
408 {
409 memcpy(pObjInfo, pSrcObjInfo, sizeof(SHCLFSOBJINFO));
410 rc = VINF_SUCCESS;
411 }
412 }
413 }
414 }
415 }
416 else
417 rc = VERR_NOT_FOUND;
418
419 ShClTransferObjOpenParmsDestroy(&openParms);
420 }
421 }
422 else
423 rc = VERR_NOT_FOUND;
424
425 if (RT_FAILURE(rc))
426 LogRel(("Shared Clipboard: Querying info for HTTP transfer failed with %Rrc\n", rc));
427
428 LogFlowFuncLeaveRC(rc);
429 return rc;
430}
431
432
433/*********************************************************************************************************************************
434* Internal Shared Clipboard HTTP server functions *
435*********************************************************************************************************************************/
436
437/**
438 * Destroys a Shared Clipboard HTTP server instance, internal version.
439 *
440 * @returns VBox status code.
441 * @param pSrv Shared Clipboard HTTP server instance to destroy.
442 *
443 * @note Caller needs to take the critical section.
444 */
445static int shClTransferHttpServerDestroyInternal(PSHCLHTTPSERVER pSrv)
446{
447 Assert(RTCritSectIsOwner(&pSrv->CritSect));
448
449 LogFlowFuncEnter();
450
451 ASMAtomicXchgBool(&pSrv->fInitialized, false);
452
453 int rc = VINF_SUCCESS;
454
455 PSHCLHTTPSERVERTRANSFER pSrvTx, pSrvTxNext;
456 RTListForEachSafe(&pSrv->lstTransfers, pSrvTx, pSrvTxNext, SHCLHTTPSERVERTRANSFER, Node)
457 {
458 int rc2 = shClTransferHttpServerDestroyTransfer(pSrv, pSrvTx);
459 if (RT_SUCCESS(rc))
460 rc = rc2;
461 }
462
463 RTHttpServerResponseDestroy(&pSrv->Resp);
464
465 pSrv->hHTTPServer = NIL_RTHTTPSERVER;
466
467 shClTransferHttpServerUnlock(pSrv); /* Unlock critical section taken by the caller before deleting it. */
468
469 if (RTCritSectIsInitialized(&pSrv->CritSect))
470 {
471 int rc2 = RTCritSectDelete(&pSrv->CritSect);
472 if (RT_SUCCESS(rc))
473 rc = rc2;
474 }
475
476 LogFlowFuncLeaveRC(rc);
477 return rc;
478}
479
480/**
481 * Initializes a new Shared Clipboard HTTP server instance.
482 *
483 * @return VBox status code.
484 * @param pSrv HTTP server instance to initialize.
485 */
486static int shClTransferHttpServerInitInternal(PSHCLHTTPSERVER pSrv)
487{
488 int rc = RTCritSectInit(&pSrv->CritSect);
489 AssertRCReturn(rc, rc);
490
491 rc = RTSemEventCreate(&pSrv->StatusEvent);
492 AssertRCReturn(rc, rc);
493
494 pSrv->hHTTPServer = NIL_RTHTTPSERVER;
495 pSrv->uPort = 0;
496 RTListInit(&pSrv->lstTransfers);
497 pSrv->cTransfers = 0;
498
499 rc = RTHttpServerResponseInit(&pSrv->Resp);
500 AssertRCReturn(rc, rc);
501
502 ASMAtomicXchgBool(&pSrv->fInitialized, true);
503 ASMAtomicXchgBool(&pSrv->fRunning, false);
504
505 return rc;
506}
507
508
509/*********************************************************************************************************************************
510* Public Shared Clipboard HTTP server functions *
511*********************************************************************************************************************************/
512
513/**
514 * Initializes a new Shared Clipboard HTTP server instance.
515 *
516 * @return VBox status code.
517 * @param pSrv HTTP server instance to initialize.
518 */
519int ShClTransferHttpServerInit(PSHCLHTTPSERVER pSrv)
520{
521 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
522
523 return shClTransferHttpServerInitInternal(pSrv);
524}
525
526/**
527 * Returns whether a given TCP port is known to be buggy or not.
528 *
529 * @returns \c true if the given port is known to be buggy, or \c false if not.
530 * @param uPort TCP port to check.
531 */
532static bool shClTransferHttpServerPortIsBuggy(uint16_t uPort)
533{
534 uint16_t const aBuggyPorts[] = {
535#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
536 /* GNOME Nautilus ("Files") v43 is unable download HTTP files from this port. */
537 8080
538#else /* Prevents zero-sized arrays. */
539 0
540#endif
541 };
542
543 for (size_t i = 0; i < RT_ELEMENTS(aBuggyPorts); i++)
544 if (uPort == aBuggyPorts[i])
545 return true;
546 return false;
547}
548
549/**
550 * Starts the Shared Clipboard HTTP server instance, extended version.
551 *
552 * @returns VBox status code.
553 * @return VERR_ADDRESS_CONFLICT if the port is already taken or the port is known to be buggy.
554 * @param pSrv HTTP server instance to create.
555 * @param uPort TCP port number to use.
556 */
557int ShClTransferHttpServerStartEx(PSHCLHTTPSERVER pSrv, uint16_t uPort)
558{
559 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
560 AssertReturn(uPort, VERR_INVALID_PARAMETER);
561
562 AssertReturn(!shClTransferHttpServerPortIsBuggy(uPort), VERR_ADDRESS_CONFLICT);
563
564 shClTransferHttpServerLock(pSrv);
565
566 RTHTTPSERVERCALLBACKS Callbacks;
567 RT_ZERO(Callbacks);
568
569 Callbacks.pfnRequestBegin = shClTransferHttpBegin;
570 Callbacks.pfnRequestEnd = shClTransferHttpEnd;
571 Callbacks.pfnOpen = shClTransferHttpOpen;
572 Callbacks.pfnRead = shClTransferHttpRead;
573 Callbacks.pfnClose = shClTransferHttpClose;
574 Callbacks.pfnQueryInfo = shClTransferHttpQueryInfo;
575
576 /* Note: The server always and *only* runs against the localhost interface. */
577 int rc = RTHttpServerCreate(&pSrv->hHTTPServer, "localhost", uPort, &Callbacks,
578 pSrv, sizeof(SHCLHTTPSERVER));
579 if (RT_SUCCESS(rc))
580 {
581 pSrv->uPort = uPort;
582 ASMAtomicXchgBool(&pSrv->fRunning, true);
583
584 LogRel2(("Shared Clipboard: HTTP server started at port %RU16\n", pSrv->uPort));
585
586 rc = shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_STARTED);
587 }
588
589 shClTransferHttpServerUnlock(pSrv);
590
591 if (RT_FAILURE(rc))
592 LogRel(("Shared Clipboard: HTTP server failed to start, rc=%Rrc\n", rc));
593
594 return rc;
595}
596
597/**
598 * Starts the Shared Clipboard HTTP server instance using a random port (>= 49152).
599 *
600 * This does automatic probing of TCP ports if a port already is being used.
601 *
602 * @returns VBox status code.
603 * @param pSrv HTTP server instance to create.
604 * @param cMaxAttempts Maximum number of attempts to create a HTTP server.
605 * @param puPort Where to return the TCP port number being used on success. Optional.
606 *
607 * @note Complies with RFC 6335 (IANA).
608 */
609int ShClTransferHttpServerStart(PSHCLHTTPSERVER pSrv, unsigned cMaxAttempts, uint16_t *puPort)
610{
611 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
612 AssertReturn(cMaxAttempts, VERR_INVALID_PARAMETER);
613 /* puPort is optional. */
614
615 RTRAND hRand;
616 int rc = RTRandAdvCreateSystemFaster(&hRand); /* Should be good enough for this task. */
617 if (RT_SUCCESS(rc))
618 {
619 uint16_t uPort;
620 unsigned i = 0;
621 for (i; i < cMaxAttempts; i++)
622 {
623 /* Try some random ports >= 49152 (i.e. "dynamic ports", see RFC 6335)
624 * -- required, as VBoxClient runs as a user process on the guest. */
625 uPort = RTRandAdvU32Ex(hRand, 49152, UINT16_MAX);
626
627 /* If the port selected turns is known to be buggy for whatever reason, skip it and try another one. */
628 if (shClTransferHttpServerPortIsBuggy(uPort))
629 continue;
630
631 rc = ShClTransferHttpServerStartEx(pSrv, (uint32_t)uPort);
632 if (RT_SUCCESS(rc))
633 {
634 if (puPort)
635 *puPort = uPort;
636 break;
637 }
638 }
639
640 if ( RT_FAILURE(rc)
641 && i == cMaxAttempts)
642 LogRel(("Shared Clipboard: Maximum attempts to start HTTP server reached (%u), giving up\n", cMaxAttempts));
643
644 RTRandAdvDestroy(hRand);
645 }
646
647 return rc;
648}
649
650/**
651 * Stops a Shared Clipboard HTTP server instance.
652 *
653 * @returns VBox status code.
654 * @param pSrv HTTP server instance to stop.
655 */
656int ShClTransferHttpServerStop(PSHCLHTTPSERVER pSrv)
657{
658 LogFlowFuncEnter();
659
660 shClTransferHttpServerLock(pSrv);
661
662 int rc = VINF_SUCCESS;
663
664 if (ASMAtomicReadBool(&pSrv->fRunning))
665 {
666 Assert(pSrv->hHTTPServer != NIL_RTHTTPSERVER);
667
668 rc = RTHttpServerDestroy(pSrv->hHTTPServer);
669 if (RT_SUCCESS(rc))
670 {
671 pSrv->hHTTPServer = NIL_RTHTTPSERVER;
672 pSrv->fRunning = false;
673
674 /* Let any eventual waiters know. */
675 shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_STOPPED);
676
677 LogRel2(("Shared Clipboard: HTTP server stopped\n"));
678 }
679 }
680
681 if (RT_FAILURE(rc))
682 LogRel(("Shared Clipboard: HTTP server failed to stop, rc=%Rrc\n", rc));
683
684 shClTransferHttpServerUnlock(pSrv);
685
686 LogFlowFuncLeaveRC(rc);
687 return rc;
688}
689
690/**
691 * Destroys a Shared Clipboard HTTP server instance.
692 *
693 * @returns VBox status code.
694 * @param pSrv HTTP server instance to destroy.
695 */
696int ShClTransferHttpServerDestroy(PSHCLHTTPSERVER pSrv)
697{
698 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
699
700 int rc = ShClTransferHttpServerStop(pSrv);
701 if (RT_FAILURE(rc))
702 return rc;
703
704 if (!ASMAtomicReadBool(&pSrv->fInitialized))
705 return VINF_SUCCESS;
706
707 shClTransferHttpServerLock(pSrv);
708
709 rc = shClTransferHttpServerDestroyInternal(pSrv);
710
711 /* Unlock not needed anymore, as the critical section got destroyed. */
712
713 return rc;
714}
715
716/**
717 * Returns the host name (scheme) of a HTTP server instance.
718 *
719 * @returns Host name (scheme).
720 * @param pSrv HTTP server instance to return host name (scheme) for.
721 *
722 * @note This is hardcoded to "localhost" for now.
723 */
724static const char *shClTransferHttpServerGetHost(PSHCLHTTPSERVER pSrv)
725{
726 RT_NOREF(pSrv);
727 return "http://localhost"; /* Hardcoded for now. */
728}
729
730/**
731 * Destroys a server transfer, internal version.
732 *
733 * @returns VBox status code.
734 * @param pSrv HTTP server instance to unregister transfer from.
735 * @param pTransfer Server transfer to destroy
736 * The pointer will be invalid on success.
737 *
738 * @note Caller needs to take the server critical section.
739 */
740static int shClTransferHttpServerDestroyTransfer(PSHCLHTTPSERVER pSrv, PSHCLHTTPSERVERTRANSFER pSrvTx)
741{
742 RTListNodeRemove(&pSrvTx->Node);
743
744 Assert(pSrv->cTransfers);
745 pSrv->cTransfers--;
746
747 LogFunc(("pTransfer=%p, idTransfer=%RU16, szPath=%s -> %RU32 transfers\n",
748 pSrvTx->pTransfer, pSrvTx->pTransfer->State.uID, pSrvTx->szPathVirtual, pSrv->cTransfers));
749
750 LogRel2(("Shared Clipboard: Destroyed HTTP transfer %RU16, now %RU32 HTTP transfers total\n",
751 pSrvTx->pTransfer->State.uID, pSrv->cTransfers));
752
753 int rc = RTCritSectDelete(&pSrvTx->CritSect);
754 AssertRCReturn(rc, rc);
755
756 RTMemFree(pSrvTx);
757 pSrvTx = NULL;
758
759 return rc;
760}
761
762
763/*********************************************************************************************************************************
764* Public Shared Clipboard HTTP server functions *
765*********************************************************************************************************************************/
766
767/**
768 * Registers a Shared Clipboard transfer to a HTTP server instance.
769 *
770 * @returns VBox status code.
771 * @param pSrv HTTP server instance to register transfer for.
772 * @param pTransfer Transfer to register. Needs to be on the heap.
773 */
774int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer)
775{
776 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
777 AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
778
779 AssertMsgReturn(pTransfer->State.uID, ("Transfer needs to be registered with a transfer context first\n"),
780 VERR_INVALID_PARAMETER);
781
782 uint64_t const cRoots = ShClTransferRootsCount(pTransfer);
783 AssertMsgReturn(cRoots > 0, ("Transfer has no root entries\n"), VERR_INVALID_PARAMETER);
784 AssertMsgReturn(cRoots == 1, ("Only single files are supported for now\n"), VERR_NOT_SUPPORTED);
785 /** @todo Check for directories? */
786
787 shClTransferHttpServerLock(pSrv);
788
789 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)RTMemAllocZ(sizeof(SHCLHTTPSERVERTRANSFER));
790 AssertPtrReturn(pSrvTx, VERR_NO_MEMORY);
791
792 RTUUID Uuid;
793 int rc = RTUuidCreate(&Uuid);
794 if (RT_SUCCESS(rc))
795 {
796 char szUuid[64];
797 rc = RTUuidToStr(&Uuid, szUuid, sizeof(szUuid));
798 if (RT_SUCCESS(rc))
799 {
800 rc = RTCritSectInit(&pSrvTx->CritSect);
801 AssertRC(rc);
802
803 PCSHCLLISTENTRY pEntry = ShClTransferRootsEntryGet(pTransfer, 0 /* First file */);
804 if (pEntry)
805 {
806 /* Create the virtual HTTP path for the transfer.
807 * Every transfer has a dedicated HTTP path (but live in the same URL namespace). */
808 ssize_t cch = RTStrPrintf2(pSrvTx->szPathVirtual, sizeof(pSrvTx->szPathVirtual), "/%s/%s/%s",
809 SHCL_HTTPT_URL_NAMESPACE, szUuid, pEntry->pszName);
810 AssertReturn(cch, VERR_BUFFER_OVERFLOW);
811
812 pSrvTx->pTransfer = pTransfer;
813 pSrvTx->pRootList = NULL;
814 pSrvTx->hObj = NIL_SHCLOBJHANDLE;
815
816 RTListAppend(&pSrv->lstTransfers, &pSrvTx->Node);
817 pSrv->cTransfers++;
818
819 shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_TRANSFER_REGISTERED);
820
821 LogFunc(("pTransfer=%p, idTransfer=%RU16, szPath=%s -> %RU32 transfers\n",
822 pSrvTx->pTransfer, pSrvTx->pTransfer->State.uID, pSrvTx->szPathVirtual, pSrv->cTransfers));
823
824 LogRel2(("Shared Clipboard: Registered HTTP transfer %RU16, now %RU32 HTTP transfers total\n",
825 pTransfer->State.uID, pSrv->cTransfers));
826 }
827 }
828 }
829
830 if (RT_FAILURE(rc))
831 RTMemFree(pSrvTx);
832
833 shClTransferHttpServerUnlock(pSrv);
834
835 LogFlowFuncLeaveRC(rc);
836 return rc;
837}
838
839/**
840 * Unregisters a formerly registered Shared Clipboard transfer.
841 *
842 * @returns VBox status code.
843 * @param pSrv HTTP server instance to unregister transfer from.
844 * @param pTransfer Transfer to unregister.
845 */
846int ShClTransferHttpServerUnregisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer)
847{
848 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
849 AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
850
851 shClTransferHttpServerLock(pSrv);
852
853 int rc = VINF_SUCCESS;
854
855 PSHCLHTTPSERVERTRANSFER pSrvTx, pSrvTxNext;
856 RTListForEachSafe(&pSrv->lstTransfers, pSrvTx, pSrvTxNext, SHCLHTTPSERVERTRANSFER, Node)
857 {
858 AssertPtr(pSrvTx->pTransfer);
859 if (pSrvTx->pTransfer->State.uID == pTransfer->State.uID)
860 {
861 rc = shClTransferHttpServerDestroyTransfer(pSrv, pSrvTx);
862 if (RT_SUCCESS(rc))
863 shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_TRANSFER_UNREGISTERED);
864 break;
865 }
866 }
867
868 shClTransferHttpServerUnlock(pSrv);
869
870 LogFlowFuncLeaveRC(rc);
871 return rc;
872}
873
874/**
875 * Sets a new status.
876 *
877 * @returns New status set.
878 * @param pSrv HTTP server instance to set status for.
879 * @param fStatus New status to set.
880 *
881 * @note Caller needs to take critical section.
882 */
883static SHCLHTTPSERVERSTATUS shclTransferHttpServerSetStatusLocked(PSHCLHTTPSERVER pSrv, SHCLHTTPSERVERSTATUS enmStatus)
884{
885 Assert(RTCritSectIsOwner(&pSrv->CritSect));
886
887 /* Bogus checks. */
888 Assert(!(enmStatus & SHCLHTTPSERVERSTATUS_NONE) || enmStatus == SHCLHTTPSERVERSTATUS_NONE);
889
890 pSrv->enmStatus = enmStatus;
891 LogFlowFunc(("fStatus=%#x\n", pSrv->enmStatus));
892
893 int rc2 = RTSemEventSignal(pSrv->StatusEvent);
894 AssertRC(rc2);
895
896 return pSrv->enmStatus;
897}
898
899/**
900 * Returns the first transfer in the list.
901 *
902 * @returns Pointer to first transfer if found, or NULL if not found.
903 * @param pSrv HTTP server instance.
904 */
905PSHCLTRANSFER ShClTransferHttpServerGetTransferFirst(PSHCLHTTPSERVER pSrv)
906{
907 shClTransferHttpServerLock(pSrv);
908
909 PSHCLHTTPSERVERTRANSFER pHttpTransfer = RTListGetFirst(&pSrv->lstTransfers, SHCLHTTPSERVERTRANSFER, Node);
910
911 shClTransferHttpServerUnlock(pSrv);
912
913 return pHttpTransfer ? pHttpTransfer->pTransfer : NULL;
914}
915
916/**
917 * Returns the last transfer in the list.
918 *
919 * @returns Pointer to last transfer if found, or NULL if not found.
920 * @param pSrv HTTP server instance.
921 */
922PSHCLTRANSFER ShClTransferHttpServerGetTransferLast(PSHCLHTTPSERVER pSrv)
923{
924 shClTransferHttpServerLock(pSrv);
925
926 PSHCLHTTPSERVERTRANSFER pHttpTransfer = RTListGetLast(&pSrv->lstTransfers, SHCLHTTPSERVERTRANSFER, Node);
927
928 shClTransferHttpServerUnlock(pSrv);
929
930 return pHttpTransfer ? pHttpTransfer->pTransfer : NULL;
931}
932
933/**
934 * Returns a transfer for a specific ID.
935 *
936 * @returns Pointer to the transfer if found, or NULL if not found.
937 * @param pSrv HTTP server instance.
938 * @param idTransfer Transfer ID of transfer to return..
939 */
940bool ShClTransferHttpServerGetTransfer(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer)
941{
942 AssertPtrReturn(pSrv, false);
943
944 shClTransferHttpServerLock(pSrv);
945
946 PSHCLHTTPSERVERTRANSFER pTransfer = shClTransferHttpServerGetTransferById(pSrv, idTransfer);
947
948 shClTransferHttpServerUnlock(pSrv);
949
950 return pTransfer;
951}
952
953/**
954 * Returns the used TCP port number of a HTTP server instance.
955 *
956 * @returns TCP port number. 0 if not specified yet.
957 * @param pSrv HTTP server instance to return port for.
958 */
959uint16_t ShClTransferHttpServerGetPort(PSHCLHTTPSERVER pSrv)
960{
961 AssertPtrReturn(pSrv, 0);
962
963 shClTransferHttpServerLock(pSrv);
964
965 const uint16_t uPort = pSrv->uPort;
966
967 shClTransferHttpServerUnlock(pSrv);
968
969 return uPort;
970}
971
972/**
973 * Returns the number of registered HTTP server transfers of a HTTP server instance.
974 *
975 * @returns Number of registered transfers.
976 * @param pSrv HTTP server instance to return registered transfers for.
977 */
978uint32_t ShClTransferHttpServerGetTransferCount(PSHCLHTTPSERVER pSrv)
979{
980 AssertPtrReturn(pSrv, 0);
981
982 shClTransferHttpServerLock(pSrv);
983
984 const uint32_t cTransfers = pSrv->cTransfers;
985 LogFlowFunc(("cTransfers=%RU32\n", cTransfers));
986
987 shClTransferHttpServerUnlock(pSrv);
988
989 return cTransfers;
990}
991
992/**
993 * Returns an allocated string with a HTTP server instance's address.
994 *
995 * @returns Allocated string with a HTTP server instance's address, or NULL on OOM.
996 * Needs to be free'd by the caller using RTStrFree().
997 * @param pSrv HTTP server instance to return address for.
998 */
999char *ShClTransferHttpServerGetAddressA(PSHCLHTTPSERVER pSrv)
1000{
1001 AssertPtrReturn(pSrv, NULL);
1002
1003 shClTransferHttpServerLock(pSrv);
1004
1005 char *pszAddress = RTStrAPrintf2("%s:%RU16", shClTransferHttpServerGetHost(pSrv), pSrv->uPort);
1006 AssertPtr(pszAddress);
1007
1008 shClTransferHttpServerUnlock(pSrv);
1009
1010 return pszAddress;
1011}
1012
1013/**
1014 * Returns an allocated string with the URL of a given Shared Clipboard transfer ID.
1015 *
1016 * @returns Allocated string with the URL of a given Shared Clipboard transfer ID, or NULL if not found.
1017 * Needs to be free'd by the caller using RTStrFree().
1018 * @param pSrv HTTP server instance to return URL for.
1019 * @param idTransfer Transfer ID to return the URL for.
1020 */
1021char *ShClTransferHttpServerGetUrlA(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer)
1022{
1023 AssertPtrReturn(pSrv, NULL);
1024 AssertReturn(idTransfer != NIL_SHCLTRANSFERID, NULL);
1025
1026 shClTransferHttpServerLock(pSrv);
1027
1028 PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpServerGetTransferById(pSrv, idTransfer);
1029 if (!pSrvTx)
1030 {
1031 AssertFailed();
1032 shClTransferHttpServerUnlock(pSrv);
1033 return NULL;
1034 }
1035
1036 AssertReturn(RTStrNLen(pSrvTx->szPathVirtual, RTPATH_MAX), NULL);
1037 char *pszUrl = RTStrAPrintf2("%s:%RU16%s", shClTransferHttpServerGetHost(pSrv), pSrv->uPort, pSrvTx->szPathVirtual);
1038 AssertPtr(pszUrl);
1039
1040 shClTransferHttpServerUnlock(pSrv);
1041
1042 return pszUrl;
1043}
1044
1045/**
1046 * Returns whether a given HTTP server instance is running or not.
1047 *
1048 * @returns \c true if running, or \c false if not.
1049 * @param pSrv HTTP server instance to check running state for.
1050 */
1051bool ShClTransferHttpServerIsRunning(PSHCLHTTPSERVER pSrv)
1052{
1053 AssertPtrReturn(pSrv, false);
1054
1055 return ASMAtomicReadBool(&pSrv->fRunning);
1056}
1057
1058/**
1059 * Waits for a status change.
1060 *
1061 * @returns VBox status code.
1062 * @retval VERR_STATE_CHANGED if the HTTP server status has changed (not running anymore).
1063 * @param pSrv HTTP server instance to wait for.
1064 * @param fStatus Status to wait for.
1065 * Multiple statuses are possible, @sa SHCLHTTPSERVERSTATUS.
1066 * @param msTimeout Timeout (in ms) to wait.
1067 */
1068int ShClTransferHttpServerWaitForStatusChange(PSHCLHTTPSERVER pSrv, SHCLHTTPSERVERSTATUS fStatus, RTMSINTERVAL msTimeout)
1069{
1070 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
1071 AssertMsgReturn(ASMAtomicReadBool(&pSrv->fInitialized), ("Server not initialized yet\n"), VERR_WRONG_ORDER);
1072
1073 shClTransferHttpServerLock(pSrv);
1074
1075 uint64_t const tsStartMs = RTTimeMilliTS();
1076
1077 int rc = VERR_TIMEOUT;
1078
1079 LogFlowFunc(("fStatus=%#x, msTimeout=%RU32\n", fStatus, msTimeout));
1080
1081 while (RTTimeMilliTS() - tsStartMs <= msTimeout)
1082 {
1083 if ( !pSrv->fInitialized
1084 || !pSrv->fRunning)
1085 {
1086 rc = VERR_STATE_CHANGED;
1087 break;
1088 }
1089
1090 shClTransferHttpServerUnlock(pSrv); /* Leave lock before waiting. */
1091
1092 rc = RTSemEventWait(pSrv->StatusEvent, msTimeout);
1093
1094 shClTransferHttpServerLock(pSrv);
1095
1096 if (RT_FAILURE(rc))
1097 break;
1098
1099 LogFlowFunc(("Current status now is: %#x\n", pSrv->enmStatus));
1100
1101 if (pSrv->enmStatus & fStatus)
1102 {
1103 rc = VINF_SUCCESS;
1104 break;
1105 }
1106 }
1107
1108 shClTransferHttpServerUnlock(pSrv);
1109
1110 LogFlowFuncLeaveRC(rc);
1111 return rc;
1112}
1113
1114
1115/*********************************************************************************************************************************
1116* Public Shared Clipboard HTTP context functions *
1117*********************************************************************************************************************************/
1118
1119/**
1120 * Starts the HTTP server, if not started already.
1121 *
1122 * @returns VBox status code.
1123 * @param pCtx HTTP context to start HTTP server for.
1124 */
1125int ShClTransferHttpServerMaybeStart(PSHCLHTTPCONTEXT pCtx)
1126{
1127 int rc = VINF_SUCCESS;
1128
1129 LogFlowFuncEnter();
1130
1131 /* Start the built-in HTTP server to serve file(s). */
1132 if (!ShClTransferHttpServerIsRunning(&pCtx->HttpServer)) /* Only one HTTP server per transfer context. */
1133 rc = ShClTransferHttpServerStart(&pCtx->HttpServer, 32 /* cMaxAttempts */, NULL /* puPort */);
1134
1135 return rc;
1136}
1137
1138/**
1139 * Stops the HTTP server, if no running transfers are left.
1140 *
1141 * @returns VBox status code.
1142 * @param pCtx HTTP context to stop HTTP server for.
1143 */
1144int ShClTransferHttpServerMaybeStop(PSHCLHTTPCONTEXT pCtx)
1145{
1146 int rc = VINF_SUCCESS;
1147
1148 LogFlowFuncEnter();
1149
1150 if (ShClTransferHttpServerIsRunning(&pCtx->HttpServer))
1151 {
1152 /* No more registered transfers left? Tear down the HTTP server instance then. */
1153 if (ShClTransferHttpServerGetTransferCount(&pCtx->HttpServer) == 0)
1154 rc = ShClTransferHttpServerStop(&pCtx->HttpServer);
1155 }
1156
1157 return rc;
1158}
1159
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