VirtualBox

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

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

Shared Clipboard: Unified root list entry code to also use the generic list entry code, a lot of updates for the cross OS transfer handling code, more updates for HTTP server transfer handling.

This also changed the handling of how that transfers are being initiated, as we needed to have this for X11: Before, transfers were initiated as soon as on side announced the URI list format -- now we postpone initiating the transfer until the receiving side requests the data as URI list.

bugref:9437

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