VirtualBox

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

Last change on this file since 103914 was 99937, checked in by vboxsync, 18 months ago

Shared Clipboard: Added new testcase for the HTTP server in combination with the Shared Clipboard API, various updates and general improvements. bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 28.3 KB
Line 
1/* $Id: RTHttpServer.cpp 99937 2023-05-23 15:38:52Z 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-2023 Oracle and/or its affiliates.
14 *
15 * This file is part of VirtualBox base platform packages, as
16 * available from https://www.virtualbox.org.
17 *
18 * This program is free software; you can redistribute it and/or
19 * modify it under the terms of the GNU General Public License
20 * as published by the Free Software Foundation, in version 3 of the
21 * License.
22 *
23 * This program is distributed in the hope that it will be useful, but
24 * WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26 * General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, see <https://www.gnu.org/licenses>.
30 *
31 * The contents of this file may alternatively be used under the terms
32 * of the Common Development and Distribution License Version 1.0
33 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
34 * in the VirtualBox distribution, in which case the provisions of the
35 * CDDL are applicable instead of those of the GPL.
36 *
37 * You may elect to license modified versions of this file under the
38 * terms and conditions of either the GPL or the CDDL or both.
39 *
40 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
41 */
42
43
44/*********************************************************************************************************************************
45* Header Files *
46*********************************************************************************************************************************/
47#include <signal.h>
48
49#include <iprt/http.h>
50#include <iprt/http-server.h>
51
52#include <iprt/net.h> /* To make use of IPv4Addr in RTGETOPTUNION. */
53
54#include <iprt/asm.h>
55#include <iprt/assert.h>
56#include <iprt/ctype.h>
57#include <iprt/err.h>
58#include <iprt/file.h>
59#include <iprt/getopt.h>
60#include <iprt/initterm.h>
61#define LOG_GROUP RTLOGGROUP_HTTP
62#include <iprt/log.h>
63#include <iprt/mem.h>
64#include <iprt/message.h>
65#include <iprt/path.h>
66#include <iprt/stream.h>
67#include <iprt/string.h>
68#include <iprt/thread.h>
69#include <iprt/vfs.h>
70
71#ifdef RT_OS_WINDOWS
72# include <iprt/win/windows.h>
73#endif
74
75
76/*********************************************************************************************************************************
77* Definitations *
78*********************************************************************************************************************************/
79typedef struct HTTPSERVERDATA
80{
81 /** The absolute path of the HTTP server's root directory. */
82 char szPathRootAbs[RTPATH_MAX];
83 RTFMODE fMode;
84 union
85 {
86 RTFILE File;
87 RTVFSDIR Dir;
88 } h;
89 /** Cached response data. */
90 RTHTTPSERVERRESP Resp;
91} HTTPSERVERDATA;
92typedef HTTPSERVERDATA *PHTTPSERVERDATA;
93
94/**
95 * Enumeration specifying the VFS handle type of the HTTP server.
96 */
97typedef enum HTTPSERVERVFSHANDLETYPE
98{
99 HTTPSERVERVFSHANDLETYPE_INVALID = 0,
100 HTTPSERVERVFSHANDLETYPE_FILE,
101 HTTPSERVERVFSHANDLETYPE_DIR,
102 /** The usual 32-bit hack. */
103 HTTPSERVERVFSHANDLETYPE_32BIT_HACK = 0x7fffffff
104} HTTPSERVERVFSHANDLETYPE;
105
106/**
107 * Structure for keeping a VFS handle of the HTTP server.
108 */
109typedef struct HTTPSERVERVFSHANDLE
110{
111 /** The type of the handle, stored in the union below. */
112 HTTPSERVERVFSHANDLETYPE enmType;
113 union
114 {
115 /** The VFS (chain) handle to use for this file. */
116 RTVFSFILE hVfsFile;
117 /** The VFS (chain) handle to use for this directory. */
118 RTVFSDIR hVfsDir;
119 } u;
120} HTTPSERVERVFSHANDLE;
121typedef HTTPSERVERVFSHANDLE *PHTTPSERVERVFSHANDLE;
122
123/**
124 * HTTP directory entry.
125 */
126typedef struct RTHTTPDIRENTRY
127{
128 /** The information about the entry. */
129 RTFSOBJINFO Info;
130 /** Symbolic link target (allocated after the name). */
131 const char *pszTarget;
132 /** Owner if applicable (allocated after the name). */
133 const char *pszOwner;
134 /** Group if applicable (allocated after the name). */
135 const char *pszGroup;
136 /** The length of szName. */
137 size_t cchName;
138 /** The entry name. */
139 RT_FLEXIBLE_ARRAY_EXTENSION
140 char szName[RT_FLEXIBLE_ARRAY];
141} RTHTTPDIRENTRY;
142/** Pointer to a HTTP directory entry. */
143typedef RTHTTPDIRENTRY *PRTHTTPDIRENTRY;
144/** Pointer to a HTTP directory entry pointer. */
145typedef PRTHTTPDIRENTRY *PPRTHTTPDIRENTRY;
146
147/**
148 * Collection of HTTP directory entries.
149 * Used for also caching stuff.
150 */
151typedef struct RTHTTPDIRCOLLECTION
152{
153 /** Current size of papEntries. */
154 size_t cEntries;
155 /** Memory allocated for papEntries. */
156 size_t cEntriesAllocated;
157 /** Current entries pending sorting and display. */
158 PPRTHTTPDIRENTRY papEntries;
159
160 /** Total number of bytes allocated for the above entries. */
161 uint64_t cbTotalAllocated;
162 /** Total number of file content bytes. */
163 uint64_t cbTotalFiles;
164
165} RTHTTPDIRCOLLECTION;
166/** Pointer to a directory collection. */
167typedef RTHTTPDIRCOLLECTION *PRTHTTPDIRCOLLECTION;
168/** Pointer to a directory entry collection pointer. */
169typedef PRTHTTPDIRCOLLECTION *PPRTHTTPDIRCOLLECTION;
170
171
172/*********************************************************************************************************************************
173* Global Variables *
174*********************************************************************************************************************************/
175/** Set by the signal handler when the HTTP server shall be terminated. */
176static volatile bool g_fCanceled = false;
177static HTTPSERVERDATA g_HttpServerData;
178
179
180#ifdef RT_OS_WINDOWS
181static BOOL WINAPI signalHandler(DWORD dwCtrlType) RT_NOTHROW_DEF
182{
183 bool fEventHandled = FALSE;
184 switch (dwCtrlType)
185 {
186 /* User pressed CTRL+C or CTRL+BREAK or an external event was sent
187 * via GenerateConsoleCtrlEvent(). */
188 case CTRL_BREAK_EVENT:
189 case CTRL_CLOSE_EVENT:
190 case CTRL_C_EVENT:
191 ASMAtomicWriteBool(&g_fCanceled, true);
192 fEventHandled = TRUE;
193 break;
194 default:
195 break;
196 /** @todo Add other events here. */
197 }
198
199 return fEventHandled;
200}
201#else /* !RT_OS_WINDOWS */
202/**
203 * Signal handler that sets g_fCanceled.
204 *
205 * This can be executed on any thread in the process, on Windows it may even be
206 * a thread dedicated to delivering this signal. Don't do anything
207 * unnecessary here.
208 */
209static void signalHandler(int iSignal) RT_NOTHROW_DEF
210{
211 NOREF(iSignal);
212 ASMAtomicWriteBool(&g_fCanceled, true);
213}
214#endif
215
216/**
217 * Installs a custom signal handler to get notified
218 * whenever the user wants to intercept the program.
219 *
220 * @todo Make this handler available for all VBoxManage modules?
221 */
222static int signalHandlerInstall(void)
223{
224 g_fCanceled = false;
225
226 int rc = VINF_SUCCESS;
227#ifdef RT_OS_WINDOWS
228 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)signalHandler, TRUE /* Add handler */))
229 {
230 rc = RTErrConvertFromWin32(GetLastError());
231 RTMsgError("Unable to install console control handler, rc=%Rrc\n", rc);
232 }
233#else
234 signal(SIGINT, signalHandler);
235 signal(SIGTERM, signalHandler);
236# ifdef SIGBREAK
237 signal(SIGBREAK, signalHandler);
238# endif
239#endif
240 return rc;
241}
242
243/**
244 * Uninstalls a previously installed signal handler.
245 */
246static int signalHandlerUninstall(void)
247{
248 int rc = VINF_SUCCESS;
249#ifdef RT_OS_WINDOWS
250 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */))
251 {
252 rc = RTErrConvertFromWin32(GetLastError());
253 RTMsgError("Unable to uninstall console control handler, rc=%Rrc\n", rc);
254 }
255#else
256 signal(SIGINT, SIG_DFL);
257 signal(SIGTERM, SIG_DFL);
258# ifdef SIGBREAK
259 signal(SIGBREAK, SIG_DFL);
260# endif
261#endif
262 return rc;
263}
264
265static int dirOpen(const char *pszPathAbs, PRTVFSDIR phVfsDir)
266{
267 return RTVfsChainOpenDir(pszPathAbs, 0 /*fFlags*/, phVfsDir, NULL /* poffError */, NULL /* pErrInfo */);
268}
269
270static int dirClose(RTVFSDIR hVfsDir)
271{
272 RTVfsDirRelease(hVfsDir);
273
274 return VINF_SUCCESS;
275}
276
277static int dirRead(RTVFSDIR hVfsDir, char **ppszEntry, PRTFSOBJINFO pInfo)
278{
279 size_t cbDirEntryAlloced = sizeof(RTDIRENTRYEX);
280 PRTDIRENTRYEX pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
281 if (!pDirEntry)
282 return VERR_NO_MEMORY;
283
284 int rc;
285
286 for (;;)
287 {
288 size_t cbDirEntry = cbDirEntryAlloced;
289 rc = RTVfsDirReadEx(hVfsDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX);
290 if (RT_FAILURE(rc))
291 {
292 if (rc == VERR_BUFFER_OVERFLOW)
293 {
294 RTMemTmpFree(pDirEntry);
295 cbDirEntryAlloced = RT_ALIGN_Z(RT_MIN(cbDirEntry, cbDirEntryAlloced) + 64, 64);
296 pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
297 if (pDirEntry)
298 continue;
299 }
300 else
301 break;
302 }
303
304 /* Skip dot directories. */
305 if (RTDirEntryExIsStdDotLink(pDirEntry))
306 continue;
307
308 *ppszEntry = RTStrDup(pDirEntry->szName);
309 AssertPtrReturn(*ppszEntry, VERR_NO_MEMORY);
310
311 *pInfo = pDirEntry->Info;
312
313 break;
314
315 } /* for */
316
317 RTMemTmpFree(pDirEntry);
318 pDirEntry = NULL;
319
320 return rc;
321}
322
323#ifdef IPRT_HTTP_WITH_WEBDAV
324static int dirEntryWriteDAV(char *pszBuf, size_t cbBuf,
325 const char *pszEntry, const PRTFSOBJINFO pObjInfo, size_t *pcbWritten)
326{
327 char szBirthTime[32];
328 if (RTTimeSpecToString(&pObjInfo->BirthTime, szBirthTime, sizeof(szBirthTime)) == NULL)
329 return VERR_BUFFER_UNDERFLOW;
330
331 char szModTime[32];
332 if (RTTimeSpecToString(&pObjInfo->ModificationTime, szModTime, sizeof(szModTime)) == NULL)
333 return VERR_BUFFER_UNDERFLOW;
334
335 int rc = VINF_SUCCESS;
336
337 /**
338 * !!! HACK ALERT !!!
339 ** @todo Build up and use a real XML DOM here. Works with Gnome / Gvfs-compatible apps though.
340 * !!! HACK ALERT !!!
341 */
342 ssize_t cch = RTStrPrintf(pszBuf, cbBuf,
343"<d:response>"
344"<d:href>%s</d:href>"
345"<d:propstat>"
346"<d:status>HTTP/1.1 200 OK</d:status>"
347"<d:prop>"
348"<d:displayname>%s</d:displayname>"
349"<d:getcontentlength>%RU64</d:getcontentlength>"
350"<d:getcontenttype>%s</d:getcontenttype>"
351"<d:creationdate>%s</d:creationdate>"
352"<d:getlastmodified>%s</d:getlastmodified>"
353"<d:getetag/>"
354"<d:resourcetype><d:collection/></d:resourcetype>"
355"</d:prop>"
356"</d:propstat>"
357"</d:response>",
358 pszEntry, pszEntry, pObjInfo->cbObject, "application/octet-stream", szBirthTime, szModTime);
359
360 if (cch <= 0)
361 rc = VERR_BUFFER_OVERFLOW;
362
363 *pcbWritten = cch;
364
365 return rc;
366}
367
368static int writeHeaderDAV(PRTHTTPSERVERREQ pReq, PRTFSOBJINFO pObjInfo, char *pszBuf, size_t cbBuf, size_t *pcbWritten)
369{
370 /**
371 * !!! HACK ALERT !!!
372 ** @todo Build up and use a real XML DOM here. Works with Gnome / Gvfs-compatible apps though.
373 * !!! HACK ALERT !!!
374 */
375
376 size_t cbWritten = 0;
377
378 ssize_t cch = RTStrPrintf2(pszBuf, cbBuf - cbWritten, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n");
379 AssertReturn(cch, VERR_BUFFER_UNDERFLOW);
380 pszBuf += cch;
381 cbWritten += cch;
382
383 cch = RTStrPrintf2(pszBuf, cbBuf - cbWritten, "<d:multistatus xmlns:d=\"DAV:\">\r\n");
384 AssertReturn(cch, VERR_BUFFER_UNDERFLOW);
385 pszBuf += cch;
386 cbWritten += cch;
387
388 int rc = dirEntryWriteDAV(pszBuf, cbBuf - cbWritten, pReq->pszUrl, pObjInfo, (size_t *)&cch);
389 AssertRC(rc);
390 pszBuf += cch;
391 cbWritten += cch;
392
393 *pcbWritten += cbWritten;
394
395 return rc;
396}
397
398static int writeFooterDAV(PRTHTTPSERVERREQ pReq, char *pszBuf, size_t cbBuf, size_t *pcbWritten)
399{
400 RT_NOREF(pReq, pcbWritten);
401
402 /**
403 * !!! HACK ALERT !!!
404 ** @todo Build up and use a real XML DOM here. Works with Gnome / Gvfs-compatible apps though.
405 * !!! HACK ALERT !!!
406 */
407 ssize_t cch = RTStrPrintf2(pszBuf, cbBuf, "</d:multistatus>");
408 AssertReturn(cch, VERR_BUFFER_UNDERFLOW);
409 RT_NOREF(cch);
410
411 return VINF_SUCCESS;
412}
413#endif /* IPRT_HTTP_WITH_WEBDAV */
414
415static int dirEntryWrite(RTHTTPMETHOD enmMethod, char *pszBuf, size_t cbBuf,
416 const char *pszEntry, const PRTFSOBJINFO pObjInfo, size_t *pcbWritten)
417{
418 char szModTime[32];
419 if (RTTimeSpecToString(&pObjInfo->ModificationTime, szModTime, sizeof(szModTime)) == NULL)
420 return VERR_BUFFER_UNDERFLOW;
421
422 int rc = VINF_SUCCESS;
423
424 ssize_t cch = 0;
425
426 if (enmMethod == RTHTTPMETHOD_GET)
427 {
428 cch = RTStrPrintf2(pszBuf, cbBuf, "201: %s %RU64 %s %s\r\n",
429 pszEntry, pObjInfo->cbObject, szModTime,
430 /** @todo Very crude; only files and directories are supported for now. */
431 RTFS_IS_FILE(pObjInfo->Attr.fMode) ? "FILE" : "DIRECTORY");
432 if (cch <= 0)
433 rc = VERR_BUFFER_OVERFLOW;
434 }
435#ifdef IPRT_HTTP_WITH_WEBDAV
436 else if (enmMethod == RTHTTPMETHOD_PROPFIND)
437 {
438 char szBuf[RTPATH_MAX + _4K]; /** @todo Just a rough guesstimate. */
439 rc = dirEntryWriteDAV(szBuf, sizeof(szBuf), pszEntry, pObjInfo, (size_t *)&cch);
440 if (RT_SUCCESS(rc))
441 rc = RTStrCat(pszBuf, cbBuf, szBuf);
442 AssertRC(rc);
443 }
444#endif /* IPRT_HTTP_WITH_WEBDAV */
445 else
446 rc = VERR_NOT_SUPPORTED;
447
448 if (RT_SUCCESS(rc))
449 {
450 *pcbWritten = (size_t)cch;
451 }
452
453 return rc;
454}
455
456/**
457 * Resolves (and validates) a given URL to an absolute (local) path.
458 *
459 * @returns VBox status code.
460 * @param pThis HTTP server instance data.
461 * @param pszUrl URL to resolve.
462 * @param ppszPathAbs Where to store the resolved absolute path on success.
463 * Needs to be free'd with RTStrFree().
464 */
465static int pathResolve(PHTTPSERVERDATA pThis, const char *pszUrl, char **ppszPathAbs)
466{
467 /* Construct absolute path. */
468 char *pszPathAbs = NULL;
469 if (RTStrAPrintf(&pszPathAbs, "%s/%s", pThis->szPathRootAbs, pszUrl) <= 0)
470 return VERR_NO_MEMORY;
471
472#ifdef VBOX_STRICT
473 RTFSOBJINFO objInfo;
474 int rc2 = RTPathQueryInfo(pszPathAbs, &objInfo, RTFSOBJATTRADD_NOTHING);
475 AssertRCReturn(rc2, rc2); RT_NOREF(rc2);
476 AssertReturn(!RTFS_IS_SYMLINK(objInfo.Attr.fMode), VERR_NOT_SUPPORTED);
477#endif
478
479 *ppszPathAbs = pszPathAbs;
480
481 return VINF_SUCCESS;
482}
483
484static DECLCALLBACK(int) onOpen(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, void **ppvHandle)
485{
486 PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
487 Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
488
489 char *pszPathAbs = NULL;
490 int rc = pathResolve(pThis, pReq->pszUrl, &pszPathAbs);
491 if (RT_SUCCESS(rc))
492 {
493 RTFSOBJINFO objInfo;
494 rc = RTPathQueryInfo(pszPathAbs, &objInfo, RTFSOBJATTRADD_NOTHING);
495 AssertRCReturn(rc, rc);
496 if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
497 {
498 /* Nothing to do here;
499 * The directory listing has been cached already in onQueryInfo(). */
500 }
501 else if (RTFS_IS_FILE(objInfo.Attr.fMode))
502 {
503 rc = RTFileOpen(&pThis->h.File, pszPathAbs,
504 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
505 }
506
507 if (RT_SUCCESS(rc))
508 {
509 pThis->fMode = objInfo.Attr.fMode;
510
511 uint64_t *puHandle = (uint64_t *)RTMemAlloc(sizeof(uint64_t));
512 *puHandle = 42; /** @todo Fudge. */
513 *ppvHandle = puHandle;
514 }
515
516 RTStrFree(pszPathAbs);
517 }
518
519 LogFlowFuncLeaveRC(rc);
520 return rc;
521}
522
523static DECLCALLBACK(int) onRead(PRTHTTPCALLBACKDATA pData,
524 PRTHTTPSERVERREQ pReq, void *pvHandle, void *pvBuf, size_t cbBuf, size_t *pcbRead)
525{
526 RT_NOREF(pReq);
527
528 PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
529 Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
530
531 AssertReturn(*(uint64_t *)pvHandle == 42 /** @todo Fudge. */, VERR_NOT_FOUND);
532
533 int rc;
534
535 if (RTFS_IS_DIRECTORY(pThis->fMode))
536 {
537 PRTHTTPSERVERRESP pResp = &pThis->Resp;
538
539 const size_t cbToCopy = RT_MIN(cbBuf, pResp->Body.cbBodyUsed - pResp->Body.offBody);
540 memcpy(pvBuf, (uint8_t *)pResp->Body.pvBody + pResp->Body.offBody, cbToCopy);
541 Assert(pResp->Body.cbBodyUsed >= cbToCopy);
542 pResp->Body.offBody += cbToCopy;
543
544 *pcbRead = cbToCopy;
545
546 rc = VINF_SUCCESS;
547 }
548 else if (RTFS_IS_FILE(pThis->fMode))
549 {
550 rc = RTFileRead(pThis->h.File, pvBuf, cbBuf, pcbRead);
551 }
552 else
553 rc = VERR_NOT_SUPPORTED;
554
555 LogFlowFuncLeaveRC(rc);
556 return rc;
557}
558
559static DECLCALLBACK(int) onClose(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, void *pvHandle)
560{
561 RT_NOREF(pReq);
562
563 PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
564 Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
565
566 AssertReturn(*(uint64_t *)pvHandle == 42 /** @todo Fudge. */, VERR_NOT_FOUND);
567
568 int rc;
569
570 if (RTFS_IS_FILE(pThis->fMode))
571 {
572 rc = RTFileClose(pThis->h.File);
573 if (RT_SUCCESS(rc))
574 pThis->h.File = NIL_RTFILE;
575 }
576 else
577 rc = VINF_SUCCESS;
578
579 RTMemFree(pvHandle);
580 pvHandle = NULL;
581
582 LogFlowFuncLeaveRC(rc);
583 return rc;
584}
585
586static DECLCALLBACK(int) onQueryInfo(PRTHTTPCALLBACKDATA pData,
587 PRTHTTPSERVERREQ pReq, PRTFSOBJINFO pObjInfo, char **ppszMIMEHint)
588{
589 PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
590 Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
591
592 /** !!!! WARNING !!!
593 **
594 ** Not production-ready code below!
595 ** @todo Use something like bodyAdd() instead of the RTStrPrintf2() hacks.
596 **
597 ** !!!! WARNING !!! */
598
599 char *pszPathAbs = NULL;
600 int rc = pathResolve(pThis, pReq->pszUrl, &pszPathAbs);
601 if (RT_SUCCESS(rc))
602 {
603 RTFSOBJINFO objInfo;
604 rc = RTPathQueryInfo(pszPathAbs, &objInfo, RTFSOBJATTRADD_NOTHING);
605 if (RT_SUCCESS(rc))
606 {
607 if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
608 {
609 PRTHTTPSERVERRESP pResp = &pThis->Resp; /* Only one request a time for now. */
610
611 RTVFSDIR hVfsDir;
612 rc = dirOpen(pszPathAbs, &hVfsDir);
613 if (RT_SUCCESS(rc))
614 {
615 RTHttpServerResponseDestroy(pResp);
616 RTHttpServerResponseInitEx(pResp, _64K); /** @todo Make this more dynamic. */
617
618 char *pszBody = (char *)pResp->Body.pvBody;
619 size_t cbBodyLeft = pResp->Body.cbBodyAlloc;
620
621 /*
622 * Write body header.
623 */
624 if (pReq->enmMethod == RTHTTPMETHOD_GET)
625 {
626 ssize_t cch = RTStrPrintf2(pszBody, cbBodyLeft,
627 "300: file://%s\r\n"
628 "200: filename content-length last-modified file-type\r\n",
629 pReq->pszUrl);
630 Assert(cch);
631 pszBody += cch;
632 cbBodyLeft -= cch;
633 }
634#ifdef IPRT_HTTP_WITH_WEBDAV
635 else if (pReq->enmMethod == RTHTTPMETHOD_PROPFIND)
636 {
637 size_t cbWritten = 0;
638 rc = writeHeaderDAV(pReq, &objInfo, pszBody, cbBodyLeft, &cbWritten);
639 if (RT_SUCCESS(rc))
640 {
641 Assert(cbBodyLeft >= cbWritten);
642 cbBodyLeft -= cbWritten;
643 }
644
645 }
646#endif /* IPRT_HTTP_WITH_WEBDAV */
647 /*
648 * Write body entries.
649 */
650 char *pszEntry = NULL;
651 RTFSOBJINFO fsObjInfo;
652 while (RT_SUCCESS(rc = dirRead(hVfsDir, &pszEntry, &fsObjInfo)))
653 {
654 LogFlowFunc(("Entry '%s'\n", pszEntry));
655
656 size_t cbWritten = 0;
657 rc = dirEntryWrite(pReq->enmMethod, pszBody, cbBodyLeft, pszEntry, &fsObjInfo, &cbWritten);
658 if (rc == VERR_BUFFER_OVERFLOW)
659 {
660 pResp->Body.cbBodyAlloc += _4K; /** @todo Improve this. */
661 pResp->Body.pvBody = RTMemRealloc(pResp->Body.pvBody, pResp->Body.cbBodyAlloc);
662 AssertPtrBreakStmt(pResp->Body.pvBody, rc = VERR_NO_MEMORY);
663
664 pszBody = (char *)pResp->Body.pvBody;
665 cbBodyLeft += _4K; /** @todo Ditto. */
666
667 rc = dirEntryWrite(pReq->enmMethod, pszBody, cbBodyLeft, pszEntry, &fsObjInfo, &cbWritten);
668 }
669
670 if ( RT_SUCCESS(rc)
671 && cbWritten)
672 {
673 pszBody += cbWritten;
674 Assert(cbBodyLeft > cbWritten);
675 cbBodyLeft -= cbWritten;
676 }
677
678 RTStrFree(pszEntry);
679
680 if (RT_FAILURE(rc))
681 break;
682 }
683
684 if (rc == VERR_NO_MORE_FILES) /* All entries consumed? */
685 rc = VINF_SUCCESS;
686
687 dirClose(hVfsDir);
688
689 /*
690 * Write footers, if any.
691 */
692 if (RT_SUCCESS(rc))
693 {
694 if (pReq->enmMethod == RTHTTPMETHOD_GET)
695 {
696 if (ppszMIMEHint)
697 rc = RTStrAPrintf(ppszMIMEHint, "text/plain");
698 }
699#ifdef IPRT_HTTP_WITH_WEBDAV
700 else if (pReq->enmMethod == RTHTTPMETHOD_PROPFIND)
701 {
702 rc = writeFooterDAV(pReq, pszBody, cbBodyLeft, NULL);
703 }
704#endif /* IPRT_HTTP_WITH_WEBDAV */
705
706 pResp->Body.cbBodyUsed = strlen((char *)pResp->Body.pvBody);
707
708 pObjInfo->cbObject = pResp->Body.cbBodyUsed;
709 }
710 }
711 }
712 else if (RTFS_IS_FILE(objInfo.Attr.fMode))
713 {
714 RTFILE hFile;
715 rc = RTFileOpen(&hFile, pszPathAbs,
716 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
717 if (RT_SUCCESS(rc))
718 {
719 rc = RTFileQueryInfo(hFile, pObjInfo, RTFSOBJATTRADD_NOTHING);
720
721 RTFileClose(hFile);
722 }
723 }
724 else
725 rc = VERR_NOT_SUPPORTED;
726 }
727
728 RTStrFree(pszPathAbs);
729 }
730
731 LogFlowFuncLeaveRC(rc);
732 return rc;
733}
734
735static DECLCALLBACK(int) onDestroy(PRTHTTPCALLBACKDATA pData)
736{
737 PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
738 Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
739
740 RTHttpServerResponseDestroy(&pThis->Resp);
741
742 return VINF_SUCCESS;
743}
744
745int main(int argc, char **argv)
746{
747 int rc = RTR3InitExe(argc, &argv, 0);
748 if (RT_FAILURE(rc))
749 return RTMsgInitFailure(rc);
750
751 /* Use some sane defaults. */
752 char szAddress[64] = "localhost";
753 uint16_t uPort = 8080;
754
755 RT_ZERO(g_HttpServerData);
756
757 /*
758 * Parse arguments.
759 */
760 static const RTGETOPTDEF s_aOptions[] =
761 {
762 { "--address", 'a', RTGETOPT_REQ_IPV4ADDR }, /** @todo Use a string for DNS hostnames? */
763 /** @todo Implement IPv6 support? */
764 { "--port", 'p', RTGETOPT_REQ_UINT16 },
765 { "--root-dir", 'r', RTGETOPT_REQ_STRING },
766 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
767 };
768
769 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
770 unsigned uVerbosityLevel = 1;
771
772 RTGETOPTUNION ValueUnion;
773 RTGETOPTSTATE GetState;
774 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
775 while ((rc = RTGetOpt(&GetState, &ValueUnion)))
776 {
777 switch (rc)
778 {
779 case 'a':
780 RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8", /** @todo Improve this. */
781 ValueUnion.IPv4Addr.au8[0], ValueUnion.IPv4Addr.au8[1], ValueUnion.IPv4Addr.au8[2], ValueUnion.IPv4Addr.au8[3]);
782 break;
783
784 case 'p':
785 uPort = ValueUnion.u16;
786 break;
787
788 case 'r':
789 RTStrCopy(g_HttpServerData.szPathRootAbs, sizeof(g_HttpServerData.szPathRootAbs), ValueUnion.psz);
790 break;
791
792 case 'v':
793 uVerbosityLevel++;
794 break;
795
796 case 'h':
797 RTPrintf("Usage: %s [options]\n"
798 "\n"
799 "Options:\n"
800 " -a, --address (default: localhost)\n"
801 " Specifies the address to use for listening.\n"
802 " -p, --port (default: 8080)\n"
803 " Specifies the port to use for listening.\n"
804 " -r, --root-dir (default: current dir)\n"
805 " Specifies the root directory being served.\n"
806 " -v, --verbose\n"
807 " Controls the verbosity level.\n"
808 " -h, -?, --help\n"
809 " Display this help text and exit successfully.\n"
810 " -V, --version\n"
811 " Display the revision and exit successfully.\n"
812 , RTPathFilename(argv[0]));
813 return RTEXITCODE_SUCCESS;
814
815 case 'V':
816 RTPrintf("$Revision: 99937 $\n");
817 return RTEXITCODE_SUCCESS;
818
819 default:
820 return RTGetOptPrintError(rc, &ValueUnion);
821 }
822 }
823
824 if (!strlen(g_HttpServerData.szPathRootAbs))
825 {
826 /* By default use the current directory as serving root directory. */
827 rc = RTPathGetCurrent(g_HttpServerData.szPathRootAbs, sizeof(g_HttpServerData.szPathRootAbs));
828 if (RT_FAILURE(rc))
829 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Retrieving current directory failed: %Rrc", rc);
830 }
831
832 /* Install signal handler. */
833 rc = signalHandlerInstall();
834 if (RT_SUCCESS(rc))
835 {
836 /*
837 * Create the HTTP server instance.
838 */
839 RTHTTPSERVERCALLBACKS Callbacks;
840 RT_ZERO(Callbacks);
841
842 Callbacks.pfnOpen = onOpen;
843 Callbacks.pfnRead = onRead;
844 Callbacks.pfnClose = onClose;
845 Callbacks.pfnQueryInfo = onQueryInfo;
846 Callbacks.pfnDestroy = onDestroy;
847
848 g_HttpServerData.h.File = NIL_RTFILE;
849 g_HttpServerData.h.Dir = NIL_RTVFSDIR;
850
851 rc = RTHttpServerResponseInit(&g_HttpServerData.Resp);
852 AssertRC(rc);
853
854 RTHTTPSERVER hHTTPServer;
855 rc = RTHttpServerCreate(&hHTTPServer, szAddress, uPort, &Callbacks,
856 &g_HttpServerData, sizeof(g_HttpServerData));
857 if (RT_SUCCESS(rc))
858 {
859 RTPrintf("Starting HTTP server at %s:%RU16 ...\n", szAddress, uPort);
860 RTPrintf("Root directory is '%s'\n", g_HttpServerData.szPathRootAbs);
861
862 RTPrintf("Running HTTP server ...\n");
863
864 for (;;)
865 {
866 RTThreadSleep(200);
867
868 if (g_fCanceled)
869 break;
870 }
871
872 RTPrintf("Stopping HTTP server ...\n");
873
874 int rc2 = RTHttpServerDestroy(hHTTPServer);
875 if (RT_SUCCESS(rc))
876 rc = rc2;
877
878 RTPrintf("Stopped HTTP server\n");
879 }
880 else
881 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTHttpServerCreate failed: %Rrc", rc);
882
883 int rc2 = signalHandlerUninstall();
884 if (RT_SUCCESS(rc))
885 rc = rc2;
886 }
887
888 /* Set rcExit on failure in case we forgot to do so before. */
889 if (RT_FAILURE(rc))
890 rcExit = RTEXITCODE_FAILURE;
891
892 return rcExit;
893}
894
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