VirtualBox

source: vbox/trunk/src/VBox/Runtime/tools/RTHttpServer.cpp@ 87015

Last change on this file since 87015 was 87004, checked in by vboxsync, 4 years ago

Shared Clipboard/Transfers: Initial commit for HTTP server. Work in progress. bugref:9874

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.6 KB
Line 
1/* $Id: RTHttpServer.cpp 87004 2020-11-27 16:18:47Z vboxsync $ */
2/** @file
3 * IPRT - Utility for running a (simple) HTTP server.
4 *
5 * Use this setup to best see what's going on:
6 * VBOX_LOG=rt_http=~0
7 * VBOX_LOG_DEST="nofile stderr"
8 * VBOX_LOG_FLAGS="unbuffered enabled thread msprog"
9 *
10 */
11
12/*
13 * Copyright (C) 2020 Oracle Corporation
14 *
15 * This file is part of VirtualBox Open Source Edition (OSE), as
16 * available from http://www.virtualbox.org. This file is free software;
17 * you can redistribute it and/or modify it under the terms of the GNU
18 * General Public License (GPL) as published by the Free Software
19 * Foundation, in version 2 as it comes in the "COPYING" file of the
20 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
21 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
22 *
23 * The contents of this file may alternatively be used under the terms
24 * of the Common Development and Distribution License Version 1.0
25 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
26 * VirtualBox OSE distribution, in which case the provisions of the
27 * CDDL are applicable instead of those of the GPL.
28 *
29 * You may elect to license modified versions of this file under the
30 * terms and conditions of either the GPL or the CDDL or both.
31 */
32
33
34/*********************************************************************************************************************************
35* Header Files *
36*********************************************************************************************************************************/
37#include <signal.h>
38
39#include <iprt/http.h>
40#include <iprt/http-server.h>
41
42#include <iprt/net.h> /* To make use of IPv4Addr in RTGETOPTUNION. */
43
44#include <iprt/asm.h>
45#include <iprt/assert.h>
46#include <iprt/ctype.h>
47#include <iprt/err.h>
48#include <iprt/file.h>
49#include <iprt/getopt.h>
50#include <iprt/initterm.h>
51#include <iprt/mem.h>
52#include <iprt/message.h>
53#include <iprt/path.h>
54#include <iprt/stream.h>
55#include <iprt/string.h>
56#include <iprt/thread.h>
57#include <iprt/vfs.h>
58
59#ifdef RT_OS_WINDOWS
60# include <iprt/win/windows.h>
61#endif
62
63
64/*********************************************************************************************************************************
65* Definitations *
66*********************************************************************************************************************************/
67typedef struct HTTPSERVERDATA
68{
69 /** The absolute path of the HTTP server's root directory. */
70 char szPathRootAbs[RTPATH_MAX];
71 /** The relative current working directory (CWD) to szRootDir. */
72 char szCWD[RTPATH_MAX];
73 union
74 {
75 RTFILE File;
76 RTDIR Dir;
77 } h;
78} HTTPSERVERDATA;
79typedef HTTPSERVERDATA *PHTTPSERVERDATA;
80
81/**
82 * Enumeration specifying the VFS handle type of the HTTP server.
83 */
84typedef enum HTTPSERVERVFSHANDLETYPE
85{
86 HTTPSERVERVFSHANDLETYPE_INVALID = 0,
87 HTTPSERVERVFSHANDLETYPE_FILE,
88 HTTPSERVERVFSHANDLETYPE_DIR,
89 /** The usual 32-bit hack. */
90 HTTPSERVERVFSHANDLETYPE_32BIT_HACK = 0x7fffffff
91} HTTPSERVERVFSHANDLETYPE;
92
93/**
94 * Structure for keeping a VFS handle of the HTTP server.
95 */
96typedef struct HTTPSERVERVFSHANDLE
97{
98 /** The type of the handle, stored in the union below. */
99 HTTPSERVERVFSHANDLETYPE enmType;
100 union
101 {
102 /** The VFS (chain) handle to use for this file. */
103 RTVFSFILE hVfsFile;
104 /** The VFS (chain) handle to use for this directory. */
105 RTVFSDIR hVfsDir;
106 } u;
107} HTTPSERVERVFSHANDLE;
108typedef HTTPSERVERVFSHANDLE *PHTTPSERVERVFSHANDLE;
109
110/**
111 * HTTP directory entry.
112 */
113typedef struct RTHTTPDIRENTRY
114{
115 /** The information about the entry. */
116 RTFSOBJINFO Info;
117 /** Symbolic link target (allocated after the name). */
118 const char *pszTarget;
119 /** Owner if applicable (allocated after the name). */
120 const char *pszOwner;
121 /** Group if applicable (allocated after the name). */
122 const char *pszGroup;
123 /** The length of szName. */
124 size_t cchName;
125 /** The entry name. */
126 RT_FLEXIBLE_ARRAY_EXTENSION
127 char szName[RT_FLEXIBLE_ARRAY];
128} RTHTTPDIRENTRY;
129/** Pointer to a HTTP directory entry. */
130typedef RTHTTPDIRENTRY *PRTHTTPDIRENTRY;
131/** Pointer to a HTTP directory entry pointer. */
132typedef PRTHTTPDIRENTRY *PPRTHTTPDIRENTRY;
133
134/**
135 * Collection of HTTP directory entries.
136 * Used for also caching stuff.
137 */
138typedef struct RTHTTPDIRCOLLECTION
139{
140 /** Current size of papEntries. */
141 size_t cEntries;
142 /** Memory allocated for papEntries. */
143 size_t cEntriesAllocated;
144 /** Current entries pending sorting and display. */
145 PPRTHTTPDIRENTRY papEntries;
146
147 /** Total number of bytes allocated for the above entries. */
148 uint64_t cbTotalAllocated;
149 /** Total number of file content bytes. */
150 uint64_t cbTotalFiles;
151
152} RTHTTPDIRCOLLECTION;
153/** Pointer to a directory collection. */
154typedef RTHTTPDIRCOLLECTION *PRTHTTPDIRCOLLECTION;
155/** Pointer to a directory entry collection pointer. */
156typedef PRTHTTPDIRCOLLECTION *PPRTHTTPDIRCOLLECTION;
157
158
159/*********************************************************************************************************************************
160* Global Variables *
161*********************************************************************************************************************************/
162/** Set by the signal handler when the HTTP server shall be terminated. */
163static volatile bool g_fCanceled = false;
164static HTTPSERVERDATA g_HttpServerData;
165
166
167#ifdef RT_OS_WINDOWS
168static BOOL WINAPI signalHandler(DWORD dwCtrlType) RT_NOTHROW_DEF
169{
170 bool fEventHandled = FALSE;
171 switch (dwCtrlType)
172 {
173 /* User pressed CTRL+C or CTRL+BREAK or an external event was sent
174 * via GenerateConsoleCtrlEvent(). */
175 case CTRL_BREAK_EVENT:
176 case CTRL_CLOSE_EVENT:
177 case CTRL_C_EVENT:
178 ASMAtomicWriteBool(&g_fCanceled, true);
179 fEventHandled = TRUE;
180 break;
181 default:
182 break;
183 /** @todo Add other events here. */
184 }
185
186 return fEventHandled;
187}
188#else /* !RT_OS_WINDOWS */
189/**
190 * Signal handler that sets g_fCanceled.
191 *
192 * This can be executed on any thread in the process, on Windows it may even be
193 * a thread dedicated to delivering this signal. Don't do anything
194 * unnecessary here.
195 */
196static void signalHandler(int iSignal) RT_NOTHROW_DEF
197{
198 NOREF(iSignal);
199 ASMAtomicWriteBool(&g_fCanceled, true);
200}
201#endif
202
203/**
204 * Installs a custom signal handler to get notified
205 * whenever the user wants to intercept the program.
206 *
207 * @todo Make this handler available for all VBoxManage modules?
208 */
209static int signalHandlerInstall(void)
210{
211 g_fCanceled = false;
212
213 int rc = VINF_SUCCESS;
214#ifdef RT_OS_WINDOWS
215 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)signalHandler, TRUE /* Add handler */))
216 {
217 rc = RTErrConvertFromWin32(GetLastError());
218 RTMsgError("Unable to install console control handler, rc=%Rrc\n", rc);
219 }
220#else
221 signal(SIGINT, signalHandler);
222 signal(SIGTERM, signalHandler);
223# ifdef SIGBREAK
224 signal(SIGBREAK, signalHandler);
225# endif
226#endif
227 return rc;
228}
229
230/**
231 * Uninstalls a previously installed signal handler.
232 */
233static int signalHandlerUninstall(void)
234{
235 int rc = VINF_SUCCESS;
236#ifdef RT_OS_WINDOWS
237 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */))
238 {
239 rc = RTErrConvertFromWin32(GetLastError());
240 RTMsgError("Unable to uninstall console control handler, rc=%Rrc\n", rc);
241 }
242#else
243 signal(SIGINT, SIG_DFL);
244 signal(SIGTERM, SIG_DFL);
245# ifdef SIGBREAK
246 signal(SIGBREAK, SIG_DFL);
247# endif
248#endif
249 return rc;
250}
251
252static int dirOpen(const char *pszPathAbs, PRTVFSDIR phVfsDir)
253{
254 return RTVfsChainOpenDir(pszPathAbs, 0 /*fFlags*/, phVfsDir, NULL /* poffError */, NULL /* pErrInfo */);
255}
256
257static int dirClose(RTVFSDIR hVfsDir)
258{
259 RTVfsDirRelease(hVfsDir);
260
261 return VINF_SUCCESS;
262}
263
264static int dirRead(RTVFSDIR hVfsDir, char **ppszEntry, PRTFSOBJINFO pInfo)
265{
266 size_t cbDirEntryAlloced = sizeof(RTDIRENTRYEX);
267 PRTDIRENTRYEX pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
268 if (!pDirEntry)
269 return VERR_NO_MEMORY;
270
271 int rc;
272
273 for (;;)
274 {
275 size_t cbDirEntry = cbDirEntryAlloced;
276 rc = RTVfsDirReadEx(hVfsDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX);
277 if (RT_FAILURE(rc))
278 {
279 if (rc == VERR_BUFFER_OVERFLOW)
280 {
281 RTMemTmpFree(pDirEntry);
282 cbDirEntryAlloced = RT_ALIGN_Z(RT_MIN(cbDirEntry, cbDirEntryAlloced) + 64, 64);
283 pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
284 if (pDirEntry)
285 continue;
286 }
287 else if (rc != VERR_NO_MORE_FILES)
288 break;
289 }
290
291 *ppszEntry = RTStrDup(pDirEntry->szName);
292 AssertPtrReturn(*ppszEntry, VERR_NO_MEMORY);
293
294 *pInfo = pDirEntry->Info;
295
296 break;
297
298 } /* for */
299
300 RTMemTmpFree(pDirEntry);
301 pDirEntry = NULL;
302
303 return rc;
304}
305
306static int dirEntryWrite(char *pszBuf, size_t cbBuf,
307 const char *pszEntry, const PRTFSOBJINFO pInfo, size_t *pcbWritten)
308{
309 RT_NOREF(pInfo);
310
311 ssize_t cch = RTStrPrintf2(pszBuf, cbBuf, "201: %s\r\n", pszEntry);
312 if (cch <= 0)
313 return VERR_BUFFER_OVERFLOW;
314
315 /*
316 Content-type:
317 Last-Modified:
318 Content-Length:
319 */
320
321 *pcbWritten = (size_t)cch;
322
323 return VINF_SUCCESS;
324}
325
326static DECLCALLBACK(int) onGetRequest(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq)
327{
328 PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
329 Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
330
331 /* Construct absolute path. */
332 char *pszPathAbs = NULL;
333 if (RTStrAPrintf(&pszPathAbs, "%s/%s", pThis->szPathRootAbs, pReq->pszUrl) <= 0)
334 return VERR_NO_MEMORY;
335
336 RTVFSDIR hVfsDir;
337 int rc = dirOpen(pszPathAbs, &hVfsDir);
338 if (RT_SUCCESS(rc))
339 {
340 pReq->pvBody = RTMemAlloc(_4K);
341 AssertPtrReturn(pReq->pvBody, VERR_NO_MEMORY); /** @todo Leaks stuff. */
342 pReq->cbBodyAlloc = _4K;
343 pReq->cbBodyUsed = 0;
344
345 char *pszEntry = NULL;
346 RTFSOBJINFO fsObjInfo;
347
348 while (RT_SUCCESS(rc = dirRead(hVfsDir, &pszEntry, &fsObjInfo)))
349 {
350 char *pszBody = (char *)pReq->pvBody;
351
352 size_t cbWritten;
353 rc = dirEntryWrite(&pszBody[pReq->cbBodyUsed], pReq->cbBodyAlloc - pReq->cbBodyUsed, pszEntry, &fsObjInfo, &cbWritten);
354 if (rc == VERR_BUFFER_OVERFLOW)
355 {
356 pReq->cbBodyAlloc *= 2; /** @todo Improve this. */
357 pReq->pvBody = RTMemRealloc(pReq->pvBody, pReq->cbBodyAlloc);
358 AssertPtrBreakStmt(pReq->pvBody, rc = VERR_NO_MEMORY);
359
360 pszBody = (char *)pReq->pvBody;
361
362 rc = dirEntryWrite(&pszBody[pReq->cbBodyUsed], pReq->cbBodyAlloc - pReq->cbBodyUsed, pszEntry, &fsObjInfo, &cbWritten);
363 }
364
365 if (RT_SUCCESS(rc))
366 pReq->cbBodyUsed += cbWritten;
367
368 RTStrFree(pszEntry);
369
370 if (RT_FAILURE(rc))
371 break;
372 }
373
374 if (rc == VERR_NO_MORE_FILES) /* All entries consumed? */
375 rc = VINF_SUCCESS;
376
377 dirClose(hVfsDir);
378 }
379
380 RTStrFree(pszPathAbs);
381 return rc;
382}
383
384static DECLCALLBACK(int) onHeadRequest(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq)
385{
386 RT_NOREF(pData, pReq);
387
388 return VINF_SUCCESS;
389}
390
391DECLCALLBACK(int) onOpen(PRTHTTPCALLBACKDATA pData, const char *pszUrl, uint64_t *pidObj)
392{
393 PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
394 Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
395
396 /* Construct absolute path. */
397 char *pszPathAbs = NULL;
398 if (RTStrAPrintf(&pszPathAbs, "%s/%s", pThis->szPathRootAbs, pszUrl) <= 0)
399 return VERR_NO_MEMORY;
400
401 int rc = RTFileOpen(&pThis->h.File, pszPathAbs,
402 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
403 if (RT_SUCCESS(rc))
404 *pidObj = 42;
405
406 RTStrFree(pszPathAbs);
407 return rc;
408}
409
410DECLCALLBACK(int) onRead(PRTHTTPCALLBACKDATA pData, uint64_t idObj, void *pvBuf, size_t cbBuf, size_t *pcbRead)
411{
412 PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
413 Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
414
415 AssertReturn(idObj == 42, VERR_NOT_FOUND);
416
417 return RTFileRead(pThis->h.File, pvBuf, cbBuf, pcbRead);
418}
419
420DECLCALLBACK(int) onClose(PRTHTTPCALLBACKDATA pData, uint64_t idObj)
421{
422 PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
423 Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
424
425 AssertReturn(idObj == 42, VERR_NOT_FOUND);
426
427 int rc = RTFileClose(pThis->h.File);
428 if (RT_SUCCESS(rc))
429 pThis->h.File = NIL_RTFILE;
430
431 return rc;
432}
433
434DECLCALLBACK(int) onQueryInfo(PRTHTTPCALLBACKDATA pData, const char *pszUrl, PRTFSOBJINFO pObjInfo)
435{
436 PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
437 Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
438
439 /* Construct absolute path. */
440 char *pszPathAbs = NULL;
441 if (RTStrAPrintf(&pszPathAbs, "%s/%s", pThis->szPathRootAbs, pszUrl) <= 0)
442 return VERR_NO_MEMORY;
443
444 RTFILE hFile;
445 int rc = RTFileOpen(&hFile, pszPathAbs,
446 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
447 if (RT_SUCCESS(rc))
448 {
449 rc = RTFileQueryInfo(hFile, pObjInfo, RTFSOBJATTRADD_NOTHING);
450
451 RTFileClose(hFile);
452 }
453
454 return rc;
455}
456
457int main(int argc, char **argv)
458{
459 int rc = RTR3InitExe(argc, &argv, 0);
460 if (RT_FAILURE(rc))
461 return RTMsgInitFailure(rc);
462
463 /* Use some sane defaults. */
464 char szAddress[64] = "localhost";
465 uint16_t uPort = 8080;
466
467 RT_ZERO(g_HttpServerData);
468
469 /*
470 * Parse arguments.
471 */
472 static const RTGETOPTDEF s_aOptions[] =
473 {
474 { "--address", 'a', RTGETOPT_REQ_IPV4ADDR }, /** @todo Use a string for DNS hostnames? */
475 /** @todo Implement IPv6 support? */
476 { "--port", 'p', RTGETOPT_REQ_UINT16 },
477 { "--root-dir", 'r', RTGETOPT_REQ_STRING },
478 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
479 };
480
481 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
482 unsigned uVerbosityLevel = 1;
483
484 RTGETOPTUNION ValueUnion;
485 RTGETOPTSTATE GetState;
486 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
487 while ((rc = RTGetOpt(&GetState, &ValueUnion)))
488 {
489 switch (rc)
490 {
491 case 'a':
492 RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8", /** @todo Improve this. */
493 ValueUnion.IPv4Addr.au8[0], ValueUnion.IPv4Addr.au8[1], ValueUnion.IPv4Addr.au8[2], ValueUnion.IPv4Addr.au8[3]);
494 break;
495
496 case 'p':
497 uPort = ValueUnion.u16;
498 break;
499
500 case 'r':
501 RTStrCopy(g_HttpServerData.szPathRootAbs, sizeof(g_HttpServerData.szPathRootAbs), ValueUnion.psz);
502 break;
503
504 case 'v':
505 uVerbosityLevel++;
506 break;
507
508 case 'h':
509 RTPrintf("Usage: %s [options]\n"
510 "\n"
511 "Options:\n"
512 " -a, --address (default: localhost)\n"
513 " Specifies the address to use for listening.\n"
514 " -p, --port (default: 8080)\n"
515 " Specifies the port to use for listening.\n"
516 " -r, --root-dir (default: current dir)\n"
517 " Specifies the root directory being served.\n"
518 " -v, --verbose\n"
519 " Controls the verbosity level.\n"
520 " -h, -?, --help\n"
521 " Display this help text and exit successfully.\n"
522 " -V, --version\n"
523 " Display the revision and exit successfully.\n"
524 , RTPathFilename(argv[0]));
525 return RTEXITCODE_SUCCESS;
526
527 case 'V':
528 RTPrintf("$Revision: 87004 $\n");
529 return RTEXITCODE_SUCCESS;
530
531 default:
532 return RTGetOptPrintError(rc, &ValueUnion);
533 }
534 }
535
536 if (!strlen(g_HttpServerData.szPathRootAbs))
537 {
538 /* By default use the current directory as serving root directory. */
539 rc = RTPathGetCurrent(g_HttpServerData.szPathRootAbs, sizeof(g_HttpServerData.szPathRootAbs));
540 if (RT_FAILURE(rc))
541 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Retrieving current directory failed: %Rrc", rc);
542 }
543
544 /* Initialize CWD. */
545 RTStrPrintf2(g_HttpServerData.szCWD, sizeof(g_HttpServerData.szCWD), "/");
546
547 /* Install signal handler. */
548 rc = signalHandlerInstall();
549 if (RT_SUCCESS(rc))
550 {
551 /*
552 * Create the HTTP server instance.
553 */
554 RTHTTPSERVERCALLBACKS Callbacks;
555 RT_ZERO(Callbacks);
556
557 Callbacks.pfnOpen = onOpen;
558 Callbacks.pfnRead = onRead;
559 Callbacks.pfnClose = onClose;
560 Callbacks.pfnQueryInfo = onQueryInfo;
561 Callbacks.pfnOnGetRequest = onGetRequest;
562 Callbacks.pfnOnHeadRequest = onHeadRequest;
563
564 g_HttpServerData.h.File = NIL_RTFILE;
565 g_HttpServerData.h.Dir = NIL_RTDIR;
566
567 RTHTTPSERVER hHTTPServer;
568 rc = RTHttpServerCreate(&hHTTPServer, szAddress, uPort, &Callbacks,
569 &g_HttpServerData, sizeof(g_HttpServerData));
570 if (RT_SUCCESS(rc))
571 {
572 RTPrintf("Starting HTTP server at %s:%RU16 ...\n", szAddress, uPort);
573 RTPrintf("Root directory is '%s'\n", g_HttpServerData.szPathRootAbs);
574
575 RTPrintf("Running HTTP server ...\n");
576
577 for (;;)
578 {
579 RTThreadSleep(200);
580
581 if (g_fCanceled)
582 break;
583 }
584
585 RTPrintf("Stopping HTTP server ...\n");
586
587 int rc2 = RTHttpServerDestroy(hHTTPServer);
588 if (RT_SUCCESS(rc))
589 rc = rc2;
590
591 RTPrintf("Stopped HTTP server\n");
592 }
593 else
594 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTHttpServerCreate failed: %Rrc", rc);
595
596 int rc2 = signalHandlerUninstall();
597 if (RT_SUCCESS(rc))
598 rc = rc2;
599 }
600
601 /* Set rcExit on failure in case we forgot to do so before. */
602 if (RT_FAILURE(rc))
603 rcExit = RTEXITCODE_FAILURE;
604
605 return rcExit;
606}
607
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