VirtualBox

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

Last change on this file since 108623 was 108623, checked in by vboxsync, 3 weeks ago

GuestHost/SharedClipboard: clang build fixes, bugref:10391

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 48.5 KB
Line 
1/* $Id: clipboard-transfers-http.cpp 108623 2025-03-20 10:37:28Z 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-2024 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/uri.h>
56#include <iprt/uuid.h>
57#include <iprt/vfs.h>
58
59#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
60#include <iprt/log.h>
61
62#include <VBox/HostServices/VBoxClipboardSvc.h>
63#include <VBox/GuestHost/SharedClipboard-x11.h>
64#include <VBox/GuestHost/SharedClipboard-transfers.h>
65
66
67/*********************************************************************************************************************************
68* Definitations *
69*********************************************************************************************************************************/
70
71#ifdef DEBUG_andy_0
72/** When enabled, this lets the HTTP server run at a predictable URL and port for debugging:
73 * URL: http://localhost:49200/transfer<ID> */
74# define VBOX_SHCL_DEBUG_HTTPSERVER
75#endif
76
77typedef struct _SHCLHTTPSERVERTRANSFER
78{
79 /** The node list. */
80 RTLISTNODE Node;
81 /** Pointer to associated transfer. */
82 PSHCLTRANSFER pTransfer;
83 /** Critical section for serializing access. */
84 RTCRITSECT CritSect;
85 /** The handle we're going to use for this HTTP transfer. */
86 SHCLOBJHANDLE hObj;
87 /** The virtual path of the HTTP server's root directory for this transfer.
88 * Always has to start with a "/". Unescaped. */
89 char szPathVirtual[RTPATH_MAX];
90} SHCLHTTPSERVERTRANSFER;
91typedef SHCLHTTPSERVERTRANSFER *PSHCLHTTPSERVERTRANSFER;
92
93
94/*********************************************************************************************************************************
95* Prototypes *
96*********************************************************************************************************************************/
97static int shClTransferHttpServerDestroyInternal(PSHCLHTTPSERVER pThis);
98static const char *shClTransferHttpServerGetHost(PSHCLHTTPSERVER pSrv);
99static int shClTransferHttpServerDestroyTransfer(PSHCLHTTPSERVER pSrv, PSHCLHTTPSERVERTRANSFER pSrvTx);
100static SHCLHTTPSERVERSTATUS shclTransferHttpServerSetStatusLocked(PSHCLHTTPSERVER pSrv, SHCLHTTPSERVERSTATUS fStatus);
101
102
103/*********************************************************************************************************************************
104* Static assets *
105*********************************************************************************************************************************/
106
107static char s_shClHttpServerPage404[] = " \
108<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \
109 \"http://www.w3.org/TR/html4/strict.dtd\"> \
110<html> \
111 <head> \
112 <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"> \
113 <title>VirtualBox Shared Clipboard</title> \
114 </head> \
115 <body> \
116 <h1>VirtualBox Shared Clipboard</h1> \
117 <p>Error: 404</p> \
118 <p>Message: Entry not found.</p> \
119 </body> \
120</html>";
121
122
123/*********************************************************************************************************************************
124* Internal Shared Clipboard HTTP transfer functions *
125*********************************************************************************************************************************/
126
127/**
128 * Locks the critical section of a Shared Clipboard HTTP server instance.
129 *
130 * @param pSrv Shared Clipboard HTTP server instance to lock.
131 */
132DECLINLINE(void) shClTransferHttpServerLock(PSHCLHTTPSERVER pSrv)
133{
134 int rc2 = RTCritSectEnter(&pSrv->CritSect);
135 AssertRC(rc2);
136}
137
138/**
139 * Unlocks the critical section of a Shared Clipboard HTTP server instance.
140 *
141 * @param pSrv Shared Clipboard HTTP server instance to unlock.
142 */
143DECLINLINE(void) shClTransferHttpServerUnlock(PSHCLHTTPSERVER pSrv)
144{
145 int rc2 = RTCritSectLeave(&pSrv->CritSect);
146 AssertRC(rc2);
147}
148
149#if 0 /* unused */
150/**
151 * Locks an HTTP transfer.
152 *
153 * @param pSrvTx HTTP transfer to lock.
154 */
155DECLINLINE(void) shClHttpTransferLock(PSHCLHTTPSERVERTRANSFER pSrvTx)
156{
157 int rc2 = RTCritSectEnter(&pSrvTx->CritSect);
158 AssertRC(rc2);
159}
160
161/**
162 * Unlocks an HTTP transfer.
163 *
164 * @param pSrvTx HTTP transfer to unlock.
165 */
166DECLINLINE(void) shClHttpTransferUnlock(PSHCLHTTPSERVERTRANSFER pSrvTx)
167{
168 int rc2 = RTCritSectLeave(&pSrvTx->CritSect);
169 AssertRC(rc2);
170}
171#endif
172
173/**
174 * Creates an URL from a given path, extended version.
175 *
176 * @returns VBox status code.
177 * @retval VERR_INVALID_PARAMETER if the path is not valid.
178 * @param pszPath Path to create URL for.
179 * @param ppszURL Where to return the allocated URL on success.
180 * @param pchScheme Where to return the size of the full HTTP scheme including "://". Optional and can be NULL.
181 * Right now this always is sizeof("http://").
182 *
183 * @note The path is not checked on file system level.
184 */
185static int shClTransferHttpURLCreateFromPathEx(const char *pszPath, char **ppszURL, size_t *pchScheme)
186{
187 AssertRCReturn(ShClTransferValidatePath(pszPath, false /* fMustExist */), VERR_INVALID_PARAMETER);
188
189 int rc = VINF_SUCCESS;
190
191 const char szScheme[] = "http://"; /** @todo For now we only support HTTP. */
192 const size_t cchScheme = strlen(szScheme);
193
194 char *pszURL = RTStrAPrintf2("%s%s", szScheme, pszPath);
195 if (pszURL)
196 {
197 AssertReturn(strlen(pszURL) > cchScheme, VERR_INVALID_PARAMETER);
198
199 *ppszURL = pszURL;
200 if (pchScheme)
201 *pchScheme = cchScheme;
202 }
203 else
204 rc = VERR_NO_MEMORY;
205
206 return rc;
207}
208
209/**
210 * Creates an URL from a given path.
211 *
212 * @returns VBox status code.
213 * @retval VERR_INVALID_PARAMETER if the path is not valid.
214 * @param pszPath Path to create URL for.
215 * @param ppszURL Where to return the allocated URL on success.
216 *
217 * @note The path is not checked on file system level.
218 */
219static int shClTransferHttpURLCreateFromPath(const char *pszPath, char **ppszURL)
220{
221 return shClTransferHttpURLCreateFromPathEx(pszPath, ppszURL, NULL /* pchScheme */);
222}
223
224/**
225 * Return the HTTP server transfer for a specific transfer ID.
226 *
227 * @returns Pointer to HTTP server transfer if found, NULL if not found.
228 * @param pSrv HTTP server instance.
229 * @param idTransfer Transfer ID to return HTTP server transfer for.
230 */
231DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpServerGetTransferById(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer)
232{
233 PSHCLHTTPSERVERTRANSFER pSrvTx;
234 RTListForEach(&pSrv->lstTransfers, pSrvTx, SHCLHTTPSERVERTRANSFER, Node) /** @todo Slow O(n) lookup, but does it for now. */
235 {
236 if (pSrvTx->pTransfer->State.uID == idTransfer)
237 return pSrvTx;
238 }
239
240 return NULL;
241}
242
243/**
244 * Returns a HTTP server transfer from a given URL.
245 *
246 * @returns Pointer to HTTP server transfer if found, NULL if not found.
247 * @param pThis HTTP server instance data.
248 * @param pszUrl URL to validate.
249 */
250DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromUrl(PSHCLHTTPSERVER pThis, const char *pszUrl)
251{
252 AssertPtrReturn(pszUrl, NULL);
253
254 PSHCLHTTPSERVERTRANSFER pSrvTx = NULL;
255
256 PSHCLHTTPSERVERTRANSFER pSrvTxCur;
257 RTListForEach(&pThis->lstTransfers, pSrvTxCur, SHCLHTTPSERVERTRANSFER, Node)
258 {
259 AssertPtr(pSrvTxCur->pTransfer);
260
261 LogFlowFunc(("pSrvTxCur=%s\n", pSrvTxCur->szPathVirtual));
262
263 /* Be picky here, do a case sensitive comparison. */
264 if (RTStrStartsWith(pszUrl, pSrvTxCur->szPathVirtual))
265 {
266 pSrvTx = pSrvTxCur;
267 break;
268 }
269 }
270
271 if (!pSrvTx)
272 LogRel2(("Shared Clipboard: HTTP URL '%s' not valid\n", pszUrl));
273
274 LogFlowFunc(("pszUrl=%s, pSrvTx=%p\n", pszUrl, pSrvTx));
275 return pSrvTx;
276}
277
278#if 0 /* unused */
279/**
280 * Returns a HTTP server transfer from an internal HTTP handle.
281 *
282 * @returns Pointer to HTTP server transfer if found, NULL if not found.
283 * @param pThis HTTP server instance data.
284 * @param pvHandle Handle to return transfer for.
285 */
286DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromHandle(PSHCLHTTPSERVER pThis, void *pvHandle)
287{
288 AssertPtrReturn(pvHandle, NULL);
289
290 const SHCLTRANSFERID uHandle = *(uint16_t *)pvHandle;
291
292 /** @ŧodo Use a handle lookup table (map) later. */
293 PSHCLHTTPSERVERTRANSFER pSrvTxCur;
294 RTListForEach(&pThis->lstTransfers, pSrvTxCur, SHCLHTTPSERVERTRANSFER, Node)
295 {
296 AssertPtr(pSrvTxCur->pTransfer);
297
298 if (pSrvTxCur->pTransfer->State.uID == uHandle) /** @ŧodo We're using the transfer ID as handle for now. */
299 return pSrvTxCur;
300 }
301
302 return NULL;
303}
304#endif
305
306
307/*********************************************************************************************************************************
308* HTTP server callback implementations *
309*********************************************************************************************************************************/
310
311/** @copydoc RTHTTPSERVERCALLBACKS::pfnRequestBegin */
312static DECLCALLBACK(int) shClTransferHttpBegin(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq)
313{
314 PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser; RT_NOREF(pThis);
315 Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
316
317 LogRel2(("Shared Clipboard: HTTP request begin\n"));
318
319 PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpGetTransferFromUrl(pThis, pReq->pszUrl);
320 if (pSrvTx)
321 {
322 pReq->pvUser = pSrvTx;
323 }
324
325 return VINF_SUCCESS;
326}
327
328/** @copydoc RTHTTPSERVERCALLBACKS::pfnRequestEnd */
329static DECLCALLBACK(int) shClTransferHttpEnd(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq)
330{
331 PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser; RT_NOREF(pThis);
332 Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
333
334 LogRel2(("Shared Clipboard: HTTP request end\n"));
335
336 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser;
337 if (pSrvTx)
338 {
339 pReq->pvUser = NULL;
340 }
341
342 return VINF_SUCCESS;
343
344}
345
346/** @copydoc RTHTTPSERVERCALLBACKS::pfnOpen */
347static DECLCALLBACK(int) shClTransferHttpOpen(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, void **ppvHandle)
348{
349 PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser; RT_NOREF(pThis);
350 Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
351
352 int rc;
353
354 AssertPtr(pReq->pvUser);
355 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser;
356 if (pSrvTx)
357 {
358 LogRel2(("Shared Clipboard: HTTP transfer (handle %RU64) started ...\n", pSrvTx->hObj));
359
360 Assert(pSrvTx->hObj != NIL_SHCLOBJHANDLE);
361 *ppvHandle = &pSrvTx->hObj;
362 rc = VINF_SUCCESS;
363 }
364 else
365 rc = VERR_NOT_FOUND;
366
367 if (RT_FAILURE(rc))
368 LogRel(("Shared Clipboard: Error starting HTTP transfer for '%s', rc=%Rrc\n", pReq->pszUrl, rc));
369
370 LogFlowFuncLeaveRC(rc);
371 return rc;
372}
373
374/** @copydoc RTHTTPSERVERCALLBACKS::pfnRead */
375static DECLCALLBACK(int) shClTransferHttpRead(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq,
376 void *pvHandle, void *pvBuf, size_t cbBuf, size_t *pcbRead)
377{
378 RT_NOREF(pData);
379
380 if (pvHandle == NULL) /* Serve a 404 page if we got an invalid handle. */
381 {
382 Assert(cbBuf >= sizeof(s_shClHttpServerPage404)); /* Keep it simple for now. */
383 memcpy(pvBuf, &s_shClHttpServerPage404, RT_MIN(cbBuf, sizeof(s_shClHttpServerPage404)));
384 *pcbRead = sizeof(s_shClHttpServerPage404);
385 return VINF_SUCCESS;
386 }
387
388 int rc;
389
390 LogRel3(("Shared Clipboard: Reading %RU32 bytes from HTTP ...\n", cbBuf));
391
392 AssertPtr(pReq->pvUser);
393 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser;
394 if (pSrvTx)
395 {
396 PSHCLOBJHANDLE phObj = (PSHCLOBJHANDLE)pvHandle;
397 if (phObj)
398 {
399 uint32_t cbRead;
400 rc = ShClTransferObjRead(pSrvTx->pTransfer, *phObj, pvBuf, cbBuf, 0 /* fFlags */, &cbRead);
401 if (RT_SUCCESS(rc))
402 *pcbRead = (uint32_t)cbRead;
403
404 if (RT_FAILURE(rc))
405 LogRel(("Shared Clipboard: Error reading HTTP transfer (handle %RU64), rc=%Rrc\n", *phObj, rc));
406 }
407 else
408 rc = VERR_NOT_FOUND;
409 }
410 else
411 rc = VERR_NOT_FOUND;
412
413 LogFlowFuncLeaveRC(rc);
414 return rc;
415}
416
417/** @copydoc RTHTTPSERVERCALLBACKS::pfnClose */
418static DECLCALLBACK(int) shClTransferHttpClose(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, void *pvHandle)
419{
420 RT_NOREF(pData);
421
422 int rc;
423
424 AssertPtr(pReq->pvUser);
425 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser;
426 if (pSrvTx)
427 {
428 PSHCLOBJHANDLE phObj = (PSHCLOBJHANDLE)pvHandle;
429 if (phObj)
430 {
431 Assert(*phObj != NIL_SHCLOBJHANDLE);
432 rc = ShClTransferObjClose(pSrvTx->pTransfer, *phObj);
433 if (RT_SUCCESS(rc))
434 {
435 pSrvTx->hObj = NIL_SHCLOBJHANDLE;
436 LogRel2(("Shared Clipboard: HTTP transfer %RU16 done\n", pSrvTx->pTransfer->State.uID));
437 }
438
439 if (RT_FAILURE(rc))
440 LogRel(("Shared Clipboard: Error closing HTTP transfer (handle %RU64), rc=%Rrc\n", *phObj, rc));
441 }
442 else
443 rc = VERR_NOT_FOUND;
444 }
445 else
446 rc = VERR_NOT_FOUND;
447
448 LogFlowFuncLeaveRC(rc);
449 return rc;
450}
451
452/** @copydoc RTHTTPSERVERCALLBACKS::pfnQueryInfo */
453static DECLCALLBACK(int) shClTransferHttpQueryInfo(PRTHTTPCALLBACKDATA pData,
454 PRTHTTPSERVERREQ pReq, PRTFSOBJINFO pObjInfo, char **ppszMIMEHint)
455{
456 RT_NOREF(pData);
457 RT_NOREF(ppszMIMEHint);
458
459 AssertReturn(RTStrIsValidEncoding(pReq->pszUrl), VERR_INVALID_PARAMETER);
460
461 LogRel2(("Shared Clipboard: HTTP query for '%s' ...\n", pReq->pszUrl));
462
463 char *pszUrl;
464 int rc = shClTransferHttpURLCreateFromPath(pReq->pszUrl, &pszUrl);
465 AssertRCReturn(rc, rc);
466
467 RTURIPARSED Parsed;
468 rc = RTUriParse(pszUrl, &Parsed);
469 if (RT_SUCCESS(rc))
470 {
471 char *pszParsedPath = RTUriParsedPath(pszUrl, &Parsed);
472 AssertPtrReturn(pszParsedPath, VERR_NO_MEMORY); /* Should be okay, as we succeeded RTUriParse() above. */
473 size_t const cchParsedPath = strlen(pszParsedPath);
474
475 /* For now we only know the transfer -- now we need to figure out the entry we want to serve. */
476 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser;
477 if (pSrvTx)
478 {
479 size_t const cchRoot = strlen(pSrvTx->szPathVirtual) + 1 /* Skip slash separating the base from the rest */;
480 AssertStmt(cchParsedPath >= cchRoot, rc = VERR_INVALID_PARAMETER);
481 const char *pszRoot = pszParsedPath + cchRoot; /* Marks the actual root path. */
482 AssertStmt(RT_VALID_PTR(pszRoot), rc = VERR_INVALID_POINTER);
483 AssertStmt(*pszRoot != '\0', rc = VERR_INVALID_PARAMETER);
484
485 if (RT_SUCCESS(rc))
486 {
487 SHCLOBJOPENCREATEPARMS openParms;
488 rc = ShClTransferObjOpenParmsInit(&openParms);
489 if (RT_SUCCESS(rc))
490 {
491 openParms.fCreate = SHCL_OBJ_CF_ACCESS_READ
492 | SHCL_OBJ_CF_ACCESS_DENYWRITE;
493
494 PSHCLTRANSFER pTx = pSrvTx->pTransfer;
495 AssertPtr(pTx);
496
497 rc = VERR_NOT_FOUND; /* Must find the matching root entry first. */
498
499 Log3Func(("pszParsedPath=%s\n", pszParsedPath));
500
501 uint64_t const cRoots = ShClTransferRootsCount(pTx);
502 for (uint32_t i = 0; i < cRoots; i++)
503 {
504 PCSHCLLISTENTRY pEntry = ShClTransferRootsEntryGet(pTx, i);
505 AssertPtrBreakStmt(pEntry, rc = VERR_NOT_FOUND);
506
507 Log3Func(("pszRoot=%s vs. pEntry=%s\n", pszRoot, pEntry->pszName));
508
509 if (RTStrCmp(pszRoot, pEntry->pszName)) /* Case-sensitive! */
510 continue;
511
512 rc = RTStrCopy(openParms.pszPath, openParms.cbPath, pEntry->pszName);
513 if (RT_SUCCESS(rc))
514 {
515 rc = ShClTransferObjOpen(pTx, &openParms, &pSrvTx->hObj);
516 if (RT_SUCCESS(rc))
517 {
518 rc = VERR_NOT_SUPPORTED; /* Play safe by default. */
519
520 if ( pEntry->fInfo & VBOX_SHCL_INFO_F_FSOBJINFO
521 && pEntry->cbInfo == sizeof(SHCLFSOBJINFO))
522 {
523 PCSHCLFSOBJINFO pSrcObjInfo = (PSHCLFSOBJINFO)pEntry->pvInfo;
524
525 LogFlowFunc(("pszName=%s, cbInfo=%RU32, fMode=%#x (type %#x)\n",
526 pEntry->pszName, pEntry->cbInfo, pSrcObjInfo->Attr.fMode, (pSrcObjInfo->Attr.fMode & RTFS_TYPE_MASK)));
527
528 LogRel2(("Shared Clipboard: HTTP object info: fMode=%#x, cbObject=%zu\n", pSrcObjInfo->Attr.fMode, pSrcObjInfo->cbObject));
529
530 if (RTFS_IS_FILE(pSrcObjInfo->Attr.fMode))
531 {
532 memcpy(pObjInfo, pSrcObjInfo, sizeof(SHCLFSOBJINFO));
533 rc = VINF_SUCCESS;
534 }
535 }
536 else
537 LogRel2(("Shared Clipboard: Supplied entry information for '%s' not supported (fInfo=%#x, cbInfo=%RU32\n",
538 pEntry->pszName, pEntry->fInfo, pEntry->cbInfo));
539 /* Note: Directories / symlinks or other fancy stuff is not supported here (yet) -- would require using WebDAV. */
540 }
541 }
542
543 break;
544 }
545
546 ShClTransferObjOpenParmsDestroy(&openParms);
547 }
548 }
549
550 RTStrFree(pszParsedPath);
551 pszParsedPath = NULL;
552 }
553 else
554 rc = VERR_NOT_FOUND;
555 }
556
557 RTStrFree(pszUrl);
558
559 if (RT_FAILURE(rc))
560 LogRel(("Shared Clipboard: Querying info for HTTP transfer failed with %Rrc\n", rc));
561
562 LogFlowFuncLeaveRC(rc);
563 return rc;
564}
565
566
567/*********************************************************************************************************************************
568* Internal Shared Clipboard HTTP server functions *
569*********************************************************************************************************************************/
570
571/**
572 * Destroys a Shared Clipboard HTTP server instance, internal version.
573 *
574 * @returns VBox status code.
575 * @param pSrv Shared Clipboard HTTP server instance to destroy.
576 *
577 * @note Caller needs to take the critical section.
578 */
579static int shClTransferHttpServerDestroyInternal(PSHCLHTTPSERVER pSrv)
580{
581 Assert(RTCritSectIsOwner(&pSrv->CritSect));
582
583 LogFlowFuncEnter();
584
585 pSrv->fInitialized = false;
586 pSrv->fRunning = false;
587
588 int rc = VINF_SUCCESS;
589
590 PSHCLHTTPSERVERTRANSFER pSrvTx, pSrvTxNext;
591 RTListForEachSafe(&pSrv->lstTransfers, pSrvTx, pSrvTxNext, SHCLHTTPSERVERTRANSFER, Node)
592 {
593 int rc2 = shClTransferHttpServerDestroyTransfer(pSrv, pSrvTx);
594 if (RT_SUCCESS(rc))
595 rc = rc2;
596 }
597
598 RTHttpServerResponseDestroy(&pSrv->Resp);
599
600 pSrv->hHTTPServer = NIL_RTHTTPSERVER;
601
602 shClTransferHttpServerUnlock(pSrv); /* Unlock critical section taken by the caller before deleting it. */
603
604 if (RTCritSectIsInitialized(&pSrv->CritSect))
605 {
606 int rc2 = RTCritSectDelete(&pSrv->CritSect);
607 if (RT_SUCCESS(rc))
608 rc = rc2;
609 }
610
611 RTSemEventDestroy(pSrv->StatusEvent);
612 pSrv->StatusEvent = NIL_RTSEMEVENT;
613
614 LogFlowFuncLeaveRC(rc);
615 return rc;
616}
617
618/**
619 * Initializes a new Shared Clipboard HTTP server instance.
620 *
621 * @return VBox status code.
622 * @param pSrv HTTP server instance to initialize.
623 */
624static int shClTransferHttpServerInitInternal(PSHCLHTTPSERVER pSrv)
625{
626 int rc = RTCritSectInit(&pSrv->CritSect);
627 AssertRCReturn(rc, rc);
628
629 rc = RTSemEventCreate(&pSrv->StatusEvent);
630 AssertRCReturn(rc, rc);
631
632 pSrv->hHTTPServer = NIL_RTHTTPSERVER;
633 pSrv->uPort = 0;
634 RTListInit(&pSrv->lstTransfers);
635 pSrv->cTransfers = 0;
636
637 rc = RTHttpServerResponseInit(&pSrv->Resp);
638 AssertRCReturn(rc, rc);
639
640 ASMAtomicXchgBool(&pSrv->fInitialized, true);
641 ASMAtomicXchgBool(&pSrv->fRunning, false);
642
643 return rc;
644}
645
646
647/*********************************************************************************************************************************
648* Public Shared Clipboard HTTP server functions *
649*********************************************************************************************************************************/
650
651/**
652 * Initializes a new Shared Clipboard HTTP server instance.
653 *
654 * @return VBox status code.
655 * @param pSrv HTTP server instance to initialize.
656 */
657int ShClTransferHttpServerInit(PSHCLHTTPSERVER pSrv)
658{
659 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
660
661 return shClTransferHttpServerInitInternal(pSrv);
662}
663
664/**
665 * Returns whether a given TCP port is known to be buggy or not.
666 *
667 * @returns \c true if the given port is known to be buggy, or \c false if not.
668 * @param uPort TCP port to check.
669 */
670static bool shClTransferHttpServerPortIsBuggy(uint16_t uPort)
671{
672 uint16_t const aBuggyPorts[] = {
673#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
674 /* GNOME Nautilus ("Files") v43 is unable download HTTP files from this port. */
675 8080
676#else /* Prevents zero-sized arrays. */
677 0
678#endif
679 };
680
681 for (size_t i = 0; i < RT_ELEMENTS(aBuggyPorts); i++)
682 if (uPort == aBuggyPorts[i])
683 return true;
684 return false;
685}
686
687/**
688 * Starts the Shared Clipboard HTTP server instance, extended version.
689 *
690 * @returns VBox status code.
691 * @return VERR_ADDRESS_CONFLICT if the port is already taken or the port is known to be buggy.
692 * @param pSrv HTTP server instance to create.
693 * @param uPort TCP port number to use.
694 */
695int ShClTransferHttpServerStartEx(PSHCLHTTPSERVER pSrv, uint16_t uPort)
696{
697 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
698 AssertReturn(uPort, VERR_INVALID_PARAMETER);
699
700 AssertReturn(!shClTransferHttpServerPortIsBuggy(uPort), VERR_ADDRESS_CONFLICT);
701
702 shClTransferHttpServerLock(pSrv);
703
704 RTHTTPSERVERCALLBACKS Callbacks;
705 RT_ZERO(Callbacks);
706
707 Callbacks.pfnRequestBegin = shClTransferHttpBegin;
708 Callbacks.pfnRequestEnd = shClTransferHttpEnd;
709 Callbacks.pfnOpen = shClTransferHttpOpen;
710 Callbacks.pfnRead = shClTransferHttpRead;
711 Callbacks.pfnClose = shClTransferHttpClose;
712 Callbacks.pfnQueryInfo = shClTransferHttpQueryInfo;
713
714 /* Note: The server always and *only* runs against the localhost interface. */
715 int rc = RTHttpServerCreate(&pSrv->hHTTPServer, "localhost", uPort, &Callbacks,
716 pSrv, sizeof(SHCLHTTPSERVER));
717 if (RT_SUCCESS(rc))
718 {
719 pSrv->uPort = uPort;
720 ASMAtomicXchgBool(&pSrv->fRunning, true);
721
722 LogRel2(("Shared Clipboard: HTTP server started at port %RU16\n", pSrv->uPort));
723
724 rc = shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_STARTED);
725 }
726
727 shClTransferHttpServerUnlock(pSrv);
728
729 if (RT_FAILURE(rc))
730 LogRel(("Shared Clipboard: HTTP server failed to start, rc=%Rrc\n", rc));
731
732 return rc;
733}
734
735/**
736 * Returns a Shared Clipboard HTTP server status as a string.
737 *
738 * @returns Status as a string, or "Unknown" if invalid / unknown.
739 * @param enmStatus HTTP server status to return as a string.
740 */
741DECLINLINE(const char *) shClTransferHttpServerStatusToStr(SHCLHTTPSERVERSTATUS enmStatus)
742{
743 switch (enmStatus)
744 {
745 RT_CASE_RET_STR(SHCLHTTPSERVERSTATUS_NONE);
746 RT_CASE_RET_STR(SHCLHTTPSERVERSTATUS_STARTED);
747 RT_CASE_RET_STR(SHCLHTTPSERVERSTATUS_STOPPED);
748 RT_CASE_RET_STR(SHCLHTTPSERVERSTATUS_TRANSFER_REGISTERED);
749 RT_CASE_RET_STR(SHCLHTTPSERVERSTATUS_TRANSFER_UNREGISTERED);
750 }
751
752 AssertFailedReturn("Unknown");
753}
754
755/**
756 * Starts the Shared Clipboard HTTP server instance using a random port (>= 49152).
757 *
758 * This does automatic probing of TCP ports if a port already is being used.
759 *
760 * @returns VBox status code.
761 * @param pSrv HTTP server instance to create.
762 * @param cMaxAttempts Maximum number of attempts to create a HTTP server.
763 * @param puPort Where to return the TCP port number being used on success. Optional.
764 *
765 * @note Complies with RFC 6335 (IANA).
766 */
767int ShClTransferHttpServerStart(PSHCLHTTPSERVER pSrv, unsigned cMaxAttempts, uint16_t *puPort)
768{
769 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
770 AssertReturn(cMaxAttempts, VERR_INVALID_PARAMETER);
771 /* puPort is optional. */
772
773 int rc;
774#ifdef VBOX_SHCL_DEBUG_HTTPSERVER
775 uint16_t uDebugPort = 49200;
776 rc = ShClTransferHttpServerStartEx(pSrv, (uint32_t)uDebugPort);
777 if (RT_SUCCESS(rc))
778 {
779 if (puPort)
780 *puPort = uDebugPort;
781 }
782 return rc;
783#endif
784
785 RTRAND hRand;
786 rc = RTRandAdvCreateSystemFaster(&hRand); /* Should be good enough for this task. */
787 if (RT_SUCCESS(rc))
788 {
789 uint16_t uPort;
790 unsigned i;
791 for (i = 0; i < cMaxAttempts; i++)
792 {
793 /* Try some random ports >= 49152 (i.e. "dynamic ports", see RFC 6335)
794 * -- required, as VBoxClient runs as a user process on the guest. */
795 uPort = RTRandAdvU32Ex(hRand, 49152, UINT16_MAX);
796
797 /* If the port selected turns is known to be buggy for whatever reason, skip it and try another one. */
798 if (shClTransferHttpServerPortIsBuggy(uPort))
799 continue;
800
801 rc = ShClTransferHttpServerStartEx(pSrv, (uint32_t)uPort);
802 if (RT_SUCCESS(rc))
803 {
804 if (puPort)
805 *puPort = uPort;
806 break;
807 }
808 }
809
810 if ( RT_FAILURE(rc)
811 && i == cMaxAttempts)
812 LogRel(("Shared Clipboard: Maximum attempts to start HTTP server reached (%u), giving up\n", cMaxAttempts));
813
814 RTRandAdvDestroy(hRand);
815 }
816
817 return rc;
818}
819
820/**
821 * Stops a Shared Clipboard HTTP server instance.
822 *
823 * @returns VBox status code.
824 * @param pSrv HTTP server instance to stop.
825 */
826int ShClTransferHttpServerStop(PSHCLHTTPSERVER pSrv)
827{
828 LogFlowFuncEnter();
829
830 shClTransferHttpServerLock(pSrv);
831
832 int rc = VINF_SUCCESS;
833
834 if (pSrv->fRunning)
835 {
836 Assert(pSrv->hHTTPServer != NIL_RTHTTPSERVER);
837
838 rc = RTHttpServerDestroy(pSrv->hHTTPServer);
839 if (RT_SUCCESS(rc))
840 {
841 pSrv->hHTTPServer = NIL_RTHTTPSERVER;
842 pSrv->fRunning = false;
843
844 /* Let any eventual waiters know. */
845 shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_STOPPED);
846
847 LogRel2(("Shared Clipboard: HTTP server stopped\n"));
848 }
849 }
850
851 if (RT_FAILURE(rc))
852 LogRel(("Shared Clipboard: HTTP server failed to stop, rc=%Rrc\n", rc));
853
854 shClTransferHttpServerUnlock(pSrv);
855
856 LogFlowFuncLeaveRC(rc);
857 return rc;
858}
859
860/**
861 * Destroys a Shared Clipboard HTTP server instance.
862 *
863 * @returns VBox status code.
864 * @param pSrv HTTP server instance to destroy.
865 */
866int ShClTransferHttpServerDestroy(PSHCLHTTPSERVER pSrv)
867{
868 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
869
870 int rc = ShClTransferHttpServerStop(pSrv);
871 if (RT_FAILURE(rc))
872 return rc;
873
874 if (!ASMAtomicReadBool(&pSrv->fInitialized))
875 return VINF_SUCCESS;
876
877 shClTransferHttpServerLock(pSrv);
878
879 rc = shClTransferHttpServerDestroyInternal(pSrv);
880
881 /* Unlock not needed anymore, as the critical section got destroyed. */
882
883 return rc;
884}
885
886/**
887 * Returns the host name (scheme) of a HTTP server instance.
888 *
889 * @returns Host name (scheme).
890 * @param pSrv HTTP server instance to return host name (scheme) for.
891 *
892 * @note This is hardcoded to "localhost" for now.
893 */
894static const char *shClTransferHttpServerGetHost(PSHCLHTTPSERVER pSrv)
895{
896 RT_NOREF(pSrv);
897 return "http://localhost"; /* Hardcoded for now. */
898}
899
900/**
901 * Destroys a server transfer, internal version.
902 *
903 * @returns VBox status code.
904 * @param pSrv HTTP server instance to unregister transfer from.
905 * @param pSrvTx HTTP server transfer to destroy.
906 * The pointer will be invalid on success.
907 *
908 * @note Caller needs to take the server critical section.
909 */
910static int shClTransferHttpServerDestroyTransfer(PSHCLHTTPSERVER pSrv, PSHCLHTTPSERVERTRANSFER pSrvTx)
911{
912 Assert(RTCritSectIsOwner(&pSrv->CritSect));
913
914 RTListNodeRemove(&pSrvTx->Node);
915
916 Assert(pSrv->cTransfers);
917 pSrv->cTransfers--;
918
919 LogFunc(("pTransfer=%p, idTransfer=%RU16, szPath=%s -> %RU32 transfers\n",
920 pSrvTx->pTransfer, pSrvTx->pTransfer->State.uID, pSrvTx->szPathVirtual, pSrv->cTransfers));
921
922 LogRel2(("Shared Clipboard: Destroyed HTTP transfer %RU16, now %RU32 HTTP transfers total\n",
923 pSrvTx->pTransfer->State.uID, pSrv->cTransfers));
924
925 if (RTCritSectIsInitialized(&pSrvTx->CritSect))
926 {
927 int rc = RTCritSectDelete(&pSrvTx->CritSect);
928 AssertRCReturn(rc, rc);
929 }
930
931 RTMemFree(pSrvTx);
932 pSrvTx = NULL;
933
934 return VINF_SUCCESS;
935}
936
937
938/*********************************************************************************************************************************
939* Public Shared Clipboard HTTP server functions *
940*********************************************************************************************************************************/
941
942/**
943 * Registers a Shared Clipboard transfer to a HTTP server instance.
944 *
945 * @returns VBox status code.
946 * @param pSrv HTTP server instance to register transfer for.
947 * @param pTransfer Transfer to register. Needs to be on the heap.
948 */
949int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer)
950{
951 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
952 AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
953
954 AssertMsgReturn(pTransfer->State.uID, ("Transfer needs to be registered with a transfer context first\n"),
955 VERR_INVALID_PARAMETER);
956
957 uint64_t const cRoots = ShClTransferRootsCount(pTransfer);
958 AssertMsgReturn(cRoots > 0, ("Transfer has no root entries\n"), VERR_INVALID_PARAMETER);
959 /** @todo Check for directories? */
960
961 shClTransferHttpServerLock(pSrv);
962
963 PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)RTMemAllocZ(sizeof(SHCLHTTPSERVERTRANSFER));
964 AssertPtrReturn(pSrvTx, VERR_NO_MEMORY);
965
966 RTUUID Uuid;
967 int rc = RTUuidCreate(&Uuid);
968 if (RT_SUCCESS(rc))
969 {
970 char szUuid[64];
971 rc = RTUuidToStr(&Uuid, szUuid, sizeof(szUuid));
972 if (RT_SUCCESS(rc))
973 {
974 rc = RTCritSectInit(&pSrvTx->CritSect);
975 AssertRCReturn(rc, rc);
976
977 /* Create the virtual HTTP path for the transfer.
978 * Every transfer has a dedicated HTTP path (but live in the same URL namespace). */
979 char *pszPath;
980#ifdef VBOX_SHCL_DEBUG_HTTPSERVER
981# ifdef DEBUG_andy /** Too lazy to specify a different transfer ID for debugging. */
982 ssize_t cch = RTStrAPrintf(&pszPath, "/transfer");
983# else
984 ssize_t cch = RTStrAPrintf(&pszPath, "/transfer%RU16", pTransfer->State.uID);
985# endif
986#else /* Release mode */
987 ssize_t cch = RTStrAPrintf(&pszPath, "/%s/%s", SHCL_HTTPT_URL_NAMESPACE, szUuid);
988#endif
989 AssertReturn(cch, VERR_NO_MEMORY);
990
991 char *pszURI;
992 size_t cchScheme;
993 rc = shClTransferHttpURLCreateFromPathEx(pszPath, &pszURI, &cchScheme);
994 if (RT_SUCCESS(rc))
995 {
996 /* For the virtual path we only keep everything after the full scheme (e.g. "http://").
997 * The virtual path always has to start with a "/". */
998 if (RTStrPrintf2(pSrvTx->szPathVirtual, sizeof(pSrvTx->szPathVirtual), "%s", pszURI + cchScheme) <= 0)
999 rc = VERR_BUFFER_OVERFLOW;
1000
1001 RTStrFree(pszURI);
1002 pszURI = NULL;
1003 }
1004 else
1005 rc = VERR_NO_MEMORY;
1006
1007 RTStrFree(pszPath);
1008 pszPath = NULL;
1009
1010 if (RT_SUCCESS(rc))
1011 {
1012 pSrvTx->pTransfer = pTransfer;
1013 pSrvTx->hObj = NIL_SHCLOBJHANDLE;
1014
1015 RTListAppend(&pSrv->lstTransfers, &pSrvTx->Node);
1016 pSrv->cTransfers++;
1017
1018 shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_TRANSFER_REGISTERED);
1019
1020 LogFunc(("pTransfer=%p, idTransfer=%RU16, szPath=%s -> %RU32 transfers\n",
1021 pSrvTx->pTransfer, pSrvTx->pTransfer->State.uID, pSrvTx->szPathVirtual, pSrv->cTransfers));
1022
1023 LogRel2(("Shared Clipboard: Registered HTTP transfer %RU16, now %RU32 HTTP transfers total\n",
1024 pTransfer->State.uID, pSrv->cTransfers));
1025 }
1026 }
1027
1028 }
1029
1030 if (RT_FAILURE(rc))
1031 RTMemFree(pSrvTx);
1032
1033 shClTransferHttpServerUnlock(pSrv);
1034
1035 LogFlowFuncLeaveRC(rc);
1036 return rc;
1037}
1038
1039/**
1040 * Unregisters a formerly registered Shared Clipboard transfer.
1041 *
1042 * @returns VBox status code.
1043 * @param pSrv HTTP server instance to unregister transfer from.
1044 * @param pTransfer Transfer to unregister.
1045 */
1046int ShClTransferHttpServerUnregisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer)
1047{
1048 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
1049 AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
1050
1051 shClTransferHttpServerLock(pSrv);
1052
1053 int rc = VINF_SUCCESS;
1054
1055 PSHCLHTTPSERVERTRANSFER pSrvTx, pSrvTxNext;
1056 RTListForEachSafe(&pSrv->lstTransfers, pSrvTx, pSrvTxNext, SHCLHTTPSERVERTRANSFER, Node)
1057 {
1058 AssertPtr(pSrvTx->pTransfer);
1059 if (pSrvTx->pTransfer->State.uID == pTransfer->State.uID)
1060 {
1061 rc = shClTransferHttpServerDestroyTransfer(pSrv, pSrvTx);
1062 if (RT_SUCCESS(rc))
1063 shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_TRANSFER_UNREGISTERED);
1064 break;
1065 }
1066 }
1067
1068 shClTransferHttpServerUnlock(pSrv);
1069
1070 LogFlowFuncLeaveRC(rc);
1071 return rc;
1072}
1073
1074/**
1075 * Sets a new status.
1076 *
1077 * @returns New status set.
1078 * @param pSrv HTTP server instance to set status for.
1079 * @param fStatus New status to set.
1080 *
1081 * @note Caller needs to take critical section.
1082 */
1083static SHCLHTTPSERVERSTATUS shclTransferHttpServerSetStatusLocked(PSHCLHTTPSERVER pSrv, SHCLHTTPSERVERSTATUS enmStatus)
1084{
1085 Assert(RTCritSectIsOwner(&pSrv->CritSect));
1086
1087 /* Bogus checks. */
1088 Assert(!(enmStatus & SHCLHTTPSERVERSTATUS_NONE) || enmStatus == SHCLHTTPSERVERSTATUS_NONE);
1089
1090 pSrv->enmStatus = enmStatus;
1091 LogFlowFunc(("fStatus=%#x\n", pSrv->enmStatus));
1092
1093 int rc2 = RTSemEventSignal(pSrv->StatusEvent);
1094 AssertRC(rc2);
1095
1096 return pSrv->enmStatus;
1097}
1098
1099/**
1100 * Returns the first transfer in the list.
1101 *
1102 * @returns Pointer to first transfer if found, or NULL if not found.
1103 * @param pSrv HTTP server instance.
1104 */
1105PSHCLTRANSFER ShClTransferHttpServerGetTransferFirst(PSHCLHTTPSERVER pSrv)
1106{
1107 shClTransferHttpServerLock(pSrv);
1108
1109 PSHCLHTTPSERVERTRANSFER pHttpTransfer = RTListGetFirst(&pSrv->lstTransfers, SHCLHTTPSERVERTRANSFER, Node);
1110
1111 shClTransferHttpServerUnlock(pSrv);
1112
1113 return pHttpTransfer ? pHttpTransfer->pTransfer : NULL;
1114}
1115
1116/**
1117 * Returns the last transfer in the list.
1118 *
1119 * @returns Pointer to last transfer if found, or NULL if not found.
1120 * @param pSrv HTTP server instance.
1121 */
1122PSHCLTRANSFER ShClTransferHttpServerGetTransferLast(PSHCLHTTPSERVER pSrv)
1123{
1124 shClTransferHttpServerLock(pSrv);
1125
1126 PSHCLHTTPSERVERTRANSFER pHttpTransfer = RTListGetLast(&pSrv->lstTransfers, SHCLHTTPSERVERTRANSFER, Node);
1127
1128 shClTransferHttpServerUnlock(pSrv);
1129
1130 return pHttpTransfer ? pHttpTransfer->pTransfer : NULL;
1131}
1132
1133/**
1134 * Returns a transfer for a specific ID.
1135 *
1136 * @returns Pointer to the transfer if found, or NULL if not found.
1137 * @param pSrv HTTP server instance.
1138 * @param idTransfer Transfer ID of transfer to return..
1139 */
1140bool ShClTransferHttpServerGetTransfer(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer)
1141{
1142 AssertPtrReturn(pSrv, false);
1143
1144 shClTransferHttpServerLock(pSrv);
1145
1146 PSHCLHTTPSERVERTRANSFER pTransfer = shClTransferHttpServerGetTransferById(pSrv, idTransfer);
1147
1148 shClTransferHttpServerUnlock(pSrv);
1149
1150 return pTransfer;
1151}
1152
1153/**
1154 * Returns the used TCP port number of a HTTP server instance.
1155 *
1156 * @returns TCP port number. 0 if not specified yet.
1157 * @param pSrv HTTP server instance to return port for.
1158 */
1159uint16_t ShClTransferHttpServerGetPort(PSHCLHTTPSERVER pSrv)
1160{
1161 AssertPtrReturn(pSrv, 0);
1162
1163 shClTransferHttpServerLock(pSrv);
1164
1165 const uint16_t uPort = pSrv->uPort;
1166
1167 shClTransferHttpServerUnlock(pSrv);
1168
1169 return uPort;
1170}
1171
1172/**
1173 * Returns the number of registered HTTP server transfers of a HTTP server instance.
1174 *
1175 * @returns Number of registered transfers.
1176 * @param pSrv HTTP server instance to return registered transfers for.
1177 */
1178uint32_t ShClTransferHttpServerGetTransferCount(PSHCLHTTPSERVER pSrv)
1179{
1180 AssertPtrReturn(pSrv, 0);
1181
1182 shClTransferHttpServerLock(pSrv);
1183
1184 const uint32_t cTransfers = pSrv->cTransfers;
1185 LogFlowFunc(("cTransfers=%RU32\n", cTransfers));
1186
1187 shClTransferHttpServerUnlock(pSrv);
1188
1189 return cTransfers;
1190}
1191
1192/**
1193 * Returns an allocated string with a HTTP server instance's address.
1194 *
1195 * @returns Allocated string with a HTTP server instance's address, or NULL on OOM.
1196 * Needs to be free'd by the caller using RTStrFree().
1197 * @param pSrv HTTP server instance to return address for.
1198 */
1199char *ShClTransferHttpServerGetAddressA(PSHCLHTTPSERVER pSrv)
1200{
1201 AssertPtrReturn(pSrv, NULL);
1202
1203 shClTransferHttpServerLock(pSrv);
1204
1205 char *pszAddress = RTStrAPrintf2("%s:%RU16", shClTransferHttpServerGetHost(pSrv), pSrv->uPort);
1206 AssertPtr(pszAddress);
1207
1208 shClTransferHttpServerUnlock(pSrv);
1209
1210 return pszAddress;
1211}
1212
1213/**
1214 * Returns an allocated string with the URL of a given Shared Clipboard transfer ID.
1215 *
1216 * @returns Allocated string with the URL of a given Shared Clipboard transfer ID, or NULL if not found.
1217 * Needs to be free'd by the caller using RTStrFree().
1218 * @param pSrv HTTP server instance to return URL for.
1219 * @param idTransfer Transfer ID to return the URL for.
1220 * @param idxEntry Index of transfer entry to return URL for.
1221 * Specify UINT64_MAX to only return the base URL.
1222 */
1223char *ShClTransferHttpServerGetUrlA(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer, uint64_t idxEntry)
1224{
1225 AssertPtrReturn(pSrv, NULL);
1226 AssertReturn(idTransfer != NIL_SHCLTRANSFERID, NULL);
1227
1228 shClTransferHttpServerLock(pSrv);
1229
1230 PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpServerGetTransferById(pSrv, idTransfer);
1231 if (!pSrvTx)
1232 {
1233 AssertFailed();
1234 shClTransferHttpServerUnlock(pSrv);
1235 return NULL;
1236 }
1237
1238 PSHCLTRANSFER pTx = pSrvTx->pTransfer;
1239 AssertPtr(pTx);
1240
1241 char *pszUrl = NULL;
1242
1243 if (RT_LIKELY(idxEntry != UINT64_MAX))
1244 {
1245 /* For now this only supports root entries. */
1246 PCSHCLLISTENTRY pEntry = ShClTransferRootsEntryGet(pTx, idxEntry);
1247 if (pEntry)
1248 {
1249 AssertReturn(RTStrNLen(pSrvTx->szPathVirtual, RTPATH_MAX), NULL);
1250 pszUrl = RTStrAPrintf2("%s:%RU16%s/%s", shClTransferHttpServerGetHost(pSrv), pSrv->uPort, pSrvTx->szPathVirtual, pEntry->pszName);
1251 }
1252 }
1253 else /* Only return the base. */
1254 pszUrl = RTStrAPrintf2("%s:%RU16%s", shClTransferHttpServerGetHost(pSrv), pSrv->uPort, pSrvTx->szPathVirtual);
1255
1256 shClTransferHttpServerUnlock(pSrv);
1257 return pszUrl;
1258}
1259
1260/**
1261 * Converts a HTTP transfer to a string list.
1262 *
1263 * @returns VBox status code.
1264 * @param pSrv HTTP server that contains the transfer.
1265 * @param pTransfer Transfer to convert data from.
1266 * @param pszSep Separator to use for the transfer entries.
1267 * @param ppszData Where to store the string list on success.
1268 * @param pcbData Where to return the bytes of \a ppszData on success.
1269 * Includes terminator. Optional.
1270 */
1271static int shClTransferHttpConvertToStringListEx(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer, const char *pszSep,
1272 char **ppszData, size_t *pcbData)
1273{
1274 AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
1275 AssertPtrReturn(pszSep, VERR_INVALID_POINTER);
1276 AssertPtrReturn(ppszData, VERR_INVALID_POINTER);
1277 /* pcbData is optional. */
1278
1279 int rc = VINF_SUCCESS;
1280 char *pszData = NULL;
1281
1282 uint64_t const cRoots = ShClTransferRootsCount(pTransfer);
1283 for (uint32_t i = 0; i < cRoots; i++)
1284 {
1285 char *pszEntry = ShClTransferHttpServerGetUrlA(pSrv, ShClTransferGetID(pTransfer), i /* Entry index */);
1286 AssertPtrBreakStmt(pszEntry, rc = VERR_NO_MEMORY);
1287
1288 if (i > 0)
1289 {
1290 rc = RTStrAAppend(&pszData, pszSep); /* Separate entries with a newline. */
1291 AssertRCBreak(rc);
1292 }
1293
1294 rc = RTStrAAppend(&pszData, pszEntry);
1295 AssertRCBreak(rc);
1296
1297 RTStrFree(pszEntry);
1298 }
1299
1300 if (RT_FAILURE(rc))
1301 {
1302 RTStrFree(pszData);
1303 return rc;
1304 }
1305
1306 *ppszData = pszData;
1307 if (pcbData)
1308 *pcbData = RTStrNLen(pszData, RTSTR_MAX) + 1 /* Terminator. */;
1309
1310 return VINF_SUCCESS;
1311}
1312
1313/**
1314 * Converts a HTTP transfer to a string list.
1315 *
1316 * @returns VBox status code.
1317 * @param pSrv HTTP server that contains the transfer.
1318 * @param pTransfer Transfer to convert data from.
1319 * @param ppszData Where to store the string list on success.
1320 * @param pcbData Where to return the bytes of \a ppszData on success.
1321 * Includes terminator. Optional.
1322 *
1323 * @note Uses '\n' as the separator. @sa ShClTransferHttpConvertToStringListEx().
1324 */
1325int ShClTransferHttpConvertToStringList(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer, char **ppszData, size_t *pcbData)
1326{
1327 return shClTransferHttpConvertToStringListEx(pSrv, pTransfer, "\n", ppszData, pcbData);
1328}
1329
1330/**
1331 * Returns whether a given HTTP server instance is initialized or not.
1332 *
1333 * @returns \c true if running, or \c false if not.
1334 * @param pSrv HTTP server instance to check initialized state for.
1335 */
1336bool ShClTransferHttpServerIsInitialized(PSHCLHTTPSERVER pSrv)
1337{
1338 AssertPtrReturn(pSrv, false);
1339
1340 return ASMAtomicReadBool(&pSrv->fInitialized);
1341}
1342
1343/**
1344 * Returns whether a given HTTP server instance is running or not.
1345 *
1346 * @returns \c true if running, or \c false if not.
1347 * @param pSrv HTTP server instance to check running state for.
1348 */
1349bool ShClTransferHttpServerIsRunning(PSHCLHTTPSERVER pSrv)
1350{
1351 AssertPtrReturn(pSrv, false);
1352
1353 return ASMAtomicReadBool(&pSrv->fRunning);
1354}
1355
1356/**
1357 * Waits for a server status change.
1358 *
1359 * @returns VBox status code.
1360 * @retval VERR_STATE_CHANGED if the HTTP server was uninitialized.
1361 * @param pSrv HTTP server instance to wait for.
1362 * @param fStatus Status to wait for.
1363 * Multiple statuses are possible, @sa SHCLHTTPSERVERSTATUS.
1364 * @param msTimeout Timeout (in ms) to wait.
1365 */
1366int ShClTransferHttpServerWaitForStatusChange(PSHCLHTTPSERVER pSrv, SHCLHTTPSERVERSTATUS fStatus, RTMSINTERVAL msTimeout)
1367{
1368 AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
1369 AssertMsgReturn(ASMAtomicReadBool(&pSrv->fInitialized), ("Server not initialized yet\n"), VERR_WRONG_ORDER);
1370
1371 shClTransferHttpServerLock(pSrv);
1372
1373 uint64_t const tsStartMs = RTTimeMilliTS();
1374
1375 int rc = VERR_TIMEOUT;
1376
1377 LogFlowFunc(("fStatus=%#x, msTimeout=%RU32 -- current is %#x\n", fStatus, msTimeout, pSrv->enmStatus));
1378
1379 while (RTTimeMilliTS() - tsStartMs <= msTimeout)
1380 {
1381 if (!pSrv->fInitialized)
1382 {
1383 rc = VERR_STATE_CHANGED;
1384 break;
1385 }
1386
1387 shClTransferHttpServerUnlock(pSrv); /* Leave lock before waiting. */
1388
1389 rc = RTSemEventWait(pSrv->StatusEvent, msTimeout);
1390
1391 shClTransferHttpServerLock(pSrv);
1392
1393 if (RT_FAILURE(rc))
1394 break;
1395
1396 LogFlowFunc(("Current status now is: %#x\n", pSrv->enmStatus));
1397 LogRel2(("Shared Clipboard: HTTP server entered status '%s'\n", shClTransferHttpServerStatusToStr(pSrv->enmStatus)));
1398
1399 if (pSrv->enmStatus & fStatus)
1400 {
1401 rc = VINF_SUCCESS;
1402 break;
1403 }
1404 }
1405
1406 shClTransferHttpServerUnlock(pSrv);
1407
1408 LogFlowFuncLeaveRC(rc);
1409 return rc;
1410}
1411
1412
1413/*********************************************************************************************************************************
1414* Public Shared Clipboard HTTP context functions *
1415*********************************************************************************************************************************/
1416
1417/**
1418 * Starts the HTTP server, if not started already.
1419 *
1420 * @returns VBox status code.
1421 * @param pCtx HTTP context to start HTTP server for.
1422 */
1423int ShClTransferHttpServerMaybeStart(PSHCLHTTPCONTEXT pCtx)
1424{
1425 int rc = VINF_SUCCESS;
1426
1427 LogFlowFuncEnter();
1428
1429 /* Start the built-in HTTP server to serve file(s). */
1430 if (!ShClTransferHttpServerIsRunning(&pCtx->HttpServer)) /* Only one HTTP server per transfer context. */
1431 rc = ShClTransferHttpServerStart(&pCtx->HttpServer, 32 /* cMaxAttempts */, NULL /* puPort */);
1432
1433 LogFlowFuncLeaveRC(rc);
1434 return rc;
1435}
1436
1437/**
1438 * Stops the HTTP server, if no running transfers are left.
1439 *
1440 * @returns VBox status code.
1441 * @param pCtx HTTP context to stop HTTP server for.
1442 */
1443int ShClTransferHttpServerMaybeStop(PSHCLHTTPCONTEXT pCtx)
1444{
1445 int rc = VINF_SUCCESS;
1446
1447 LogFlowFuncEnter();
1448
1449 if (ShClTransferHttpServerIsRunning(&pCtx->HttpServer))
1450 {
1451 /* No more registered transfers left? Tear down the HTTP server instance then. */
1452 if (ShClTransferHttpServerGetTransferCount(&pCtx->HttpServer) == 0)
1453 rc = ShClTransferHttpServerStop(&pCtx->HttpServer);
1454 }
1455
1456 LogFlowFuncLeaveRC(rc);
1457 return rc;
1458}
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