VirtualBox

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

Last change on this file since 106199 was 106061, checked in by vboxsync, 2 months ago

Copyright year updates by scm.

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

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