VirtualBox

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

Last change on this file since 99844 was 98103, checked in by vboxsync, 2 years ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 28.2 KB
Line 
1/* $Id: RTHttpServer.cpp 98103 2023-01-17 14:15:46Z 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, void *pvHandle, void *pvBuf, size_t cbBuf, size_t *pcbRead)
524{
525 PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
526 Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
527
528 AssertReturn(*(uint64_t *)pvHandle == 42 /** @todo Fudge. */, VERR_NOT_FOUND);
529
530 int rc;
531
532 if (RTFS_IS_DIRECTORY(pThis->fMode))
533 {
534 PRTHTTPSERVERRESP pResp = &pThis->Resp;
535
536 const size_t cbToCopy = RT_MIN(cbBuf, pResp->Body.cbBodyUsed - pResp->Body.offBody);
537 memcpy(pvBuf, (uint8_t *)pResp->Body.pvBody + pResp->Body.offBody, cbToCopy);
538 Assert(pResp->Body.cbBodyUsed >= cbToCopy);
539 pResp->Body.offBody += cbToCopy;
540
541 *pcbRead = cbToCopy;
542
543 rc = VINF_SUCCESS;
544 }
545 else if (RTFS_IS_FILE(pThis->fMode))
546 {
547 rc = RTFileRead(pThis->h.File, pvBuf, cbBuf, pcbRead);
548 }
549 else
550 rc = VERR_NOT_SUPPORTED;
551
552 LogFlowFuncLeaveRC(rc);
553 return rc;
554}
555
556static DECLCALLBACK(int) onClose(PRTHTTPCALLBACKDATA pData, void *pvHandle)
557{
558 PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
559 Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
560
561 AssertReturn(*(uint64_t *)pvHandle == 42 /** @todo Fudge. */, VERR_NOT_FOUND);
562
563 int rc;
564
565 if (RTFS_IS_FILE(pThis->fMode))
566 {
567 rc = RTFileClose(pThis->h.File);
568 if (RT_SUCCESS(rc))
569 pThis->h.File = NIL_RTFILE;
570 }
571 else
572 rc = VINF_SUCCESS;
573
574 RTMemFree(pvHandle);
575 pvHandle = NULL;
576
577 LogFlowFuncLeaveRC(rc);
578 return rc;
579}
580
581static DECLCALLBACK(int) onQueryInfo(PRTHTTPCALLBACKDATA pData,
582 PRTHTTPSERVERREQ pReq, PRTFSOBJINFO pObjInfo, char **ppszMIMEHint)
583{
584 PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
585 Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
586
587 /** !!!! WARNING !!!
588 **
589 ** Not production-ready code below!
590 ** @todo Use something like bodyAdd() instead of the RTStrPrintf2() hacks.
591 **
592 ** !!!! WARNING !!! */
593
594 char *pszPathAbs = NULL;
595 int rc = pathResolve(pThis, pReq->pszUrl, &pszPathAbs);
596 if (RT_SUCCESS(rc))
597 {
598 RTFSOBJINFO objInfo;
599 rc = RTPathQueryInfo(pszPathAbs, &objInfo, RTFSOBJATTRADD_NOTHING);
600 if (RT_SUCCESS(rc))
601 {
602 if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
603 {
604 PRTHTTPSERVERRESP pResp = &pThis->Resp; /* Only one request a time for now. */
605
606 RTVFSDIR hVfsDir;
607 rc = dirOpen(pszPathAbs, &hVfsDir);
608 if (RT_SUCCESS(rc))
609 {
610 RTHttpServerResponseDestroy(pResp);
611 RTHttpServerResponseInitEx(pResp, _64K); /** @todo Make this more dynamic. */
612
613 char *pszBody = (char *)pResp->Body.pvBody;
614 size_t cbBodyLeft = pResp->Body.cbBodyAlloc;
615
616 /*
617 * Write body header.
618 */
619 if (pReq->enmMethod == RTHTTPMETHOD_GET)
620 {
621 ssize_t cch = RTStrPrintf2(pszBody, cbBodyLeft,
622 "300: file://%s\r\n"
623 "200: filename content-length last-modified file-type\r\n",
624 pReq->pszUrl);
625 Assert(cch);
626 pszBody += cch;
627 cbBodyLeft -= cch;
628 }
629#ifdef IPRT_HTTP_WITH_WEBDAV
630 else if (pReq->enmMethod == RTHTTPMETHOD_PROPFIND)
631 {
632 size_t cbWritten = 0;
633 rc = writeHeaderDAV(pReq, &objInfo, pszBody, cbBodyLeft, &cbWritten);
634 if (RT_SUCCESS(rc))
635 {
636 Assert(cbBodyLeft >= cbWritten);
637 cbBodyLeft -= cbWritten;
638 }
639
640 }
641#endif /* IPRT_HTTP_WITH_WEBDAV */
642 /*
643 * Write body entries.
644 */
645 char *pszEntry = NULL;
646 RTFSOBJINFO fsObjInfo;
647 while (RT_SUCCESS(rc = dirRead(hVfsDir, &pszEntry, &fsObjInfo)))
648 {
649 LogFlowFunc(("Entry '%s'\n", pszEntry));
650
651 size_t cbWritten = 0;
652 rc = dirEntryWrite(pReq->enmMethod, pszBody, cbBodyLeft, pszEntry, &fsObjInfo, &cbWritten);
653 if (rc == VERR_BUFFER_OVERFLOW)
654 {
655 pResp->Body.cbBodyAlloc += _4K; /** @todo Improve this. */
656 pResp->Body.pvBody = RTMemRealloc(pResp->Body.pvBody, pResp->Body.cbBodyAlloc);
657 AssertPtrBreakStmt(pResp->Body.pvBody, rc = VERR_NO_MEMORY);
658
659 pszBody = (char *)pResp->Body.pvBody;
660 cbBodyLeft += _4K; /** @todo Ditto. */
661
662 rc = dirEntryWrite(pReq->enmMethod, pszBody, cbBodyLeft, pszEntry, &fsObjInfo, &cbWritten);
663 }
664
665 if ( RT_SUCCESS(rc)
666 && cbWritten)
667 {
668 pszBody += cbWritten;
669 Assert(cbBodyLeft > cbWritten);
670 cbBodyLeft -= cbWritten;
671 }
672
673 RTStrFree(pszEntry);
674
675 if (RT_FAILURE(rc))
676 break;
677 }
678
679 if (rc == VERR_NO_MORE_FILES) /* All entries consumed? */
680 rc = VINF_SUCCESS;
681
682 dirClose(hVfsDir);
683
684 /*
685 * Write footers, if any.
686 */
687 if (RT_SUCCESS(rc))
688 {
689 if (pReq->enmMethod == RTHTTPMETHOD_GET)
690 {
691 if (ppszMIMEHint)
692 rc = RTStrAPrintf(ppszMIMEHint, "text/plain");
693 }
694#ifdef IPRT_HTTP_WITH_WEBDAV
695 else if (pReq->enmMethod == RTHTTPMETHOD_PROPFIND)
696 {
697 rc = writeFooterDAV(pReq, pszBody, cbBodyLeft, NULL);
698 }
699#endif /* IPRT_HTTP_WITH_WEBDAV */
700
701 pResp->Body.cbBodyUsed = strlen((char *)pResp->Body.pvBody);
702
703 pObjInfo->cbObject = pResp->Body.cbBodyUsed;
704 }
705 }
706 }
707 else if (RTFS_IS_FILE(objInfo.Attr.fMode))
708 {
709 RTFILE hFile;
710 rc = RTFileOpen(&hFile, pszPathAbs,
711 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
712 if (RT_SUCCESS(rc))
713 {
714 rc = RTFileQueryInfo(hFile, pObjInfo, RTFSOBJATTRADD_NOTHING);
715
716 RTFileClose(hFile);
717 }
718 }
719 else
720 rc = VERR_NOT_SUPPORTED;
721 }
722
723 RTStrFree(pszPathAbs);
724 }
725
726 LogFlowFuncLeaveRC(rc);
727 return rc;
728}
729
730static DECLCALLBACK(int) onDestroy(PRTHTTPCALLBACKDATA pData)
731{
732 PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
733 Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
734
735 RTHttpServerResponseDestroy(&pThis->Resp);
736
737 return VINF_SUCCESS;
738}
739
740int main(int argc, char **argv)
741{
742 int rc = RTR3InitExe(argc, &argv, 0);
743 if (RT_FAILURE(rc))
744 return RTMsgInitFailure(rc);
745
746 /* Use some sane defaults. */
747 char szAddress[64] = "localhost";
748 uint16_t uPort = 8080;
749
750 RT_ZERO(g_HttpServerData);
751
752 /*
753 * Parse arguments.
754 */
755 static const RTGETOPTDEF s_aOptions[] =
756 {
757 { "--address", 'a', RTGETOPT_REQ_IPV4ADDR }, /** @todo Use a string for DNS hostnames? */
758 /** @todo Implement IPv6 support? */
759 { "--port", 'p', RTGETOPT_REQ_UINT16 },
760 { "--root-dir", 'r', RTGETOPT_REQ_STRING },
761 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
762 };
763
764 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
765 unsigned uVerbosityLevel = 1;
766
767 RTGETOPTUNION ValueUnion;
768 RTGETOPTSTATE GetState;
769 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
770 while ((rc = RTGetOpt(&GetState, &ValueUnion)))
771 {
772 switch (rc)
773 {
774 case 'a':
775 RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8", /** @todo Improve this. */
776 ValueUnion.IPv4Addr.au8[0], ValueUnion.IPv4Addr.au8[1], ValueUnion.IPv4Addr.au8[2], ValueUnion.IPv4Addr.au8[3]);
777 break;
778
779 case 'p':
780 uPort = ValueUnion.u16;
781 break;
782
783 case 'r':
784 RTStrCopy(g_HttpServerData.szPathRootAbs, sizeof(g_HttpServerData.szPathRootAbs), ValueUnion.psz);
785 break;
786
787 case 'v':
788 uVerbosityLevel++;
789 break;
790
791 case 'h':
792 RTPrintf("Usage: %s [options]\n"
793 "\n"
794 "Options:\n"
795 " -a, --address (default: localhost)\n"
796 " Specifies the address to use for listening.\n"
797 " -p, --port (default: 8080)\n"
798 " Specifies the port to use for listening.\n"
799 " -r, --root-dir (default: current dir)\n"
800 " Specifies the root directory being served.\n"
801 " -v, --verbose\n"
802 " Controls the verbosity level.\n"
803 " -h, -?, --help\n"
804 " Display this help text and exit successfully.\n"
805 " -V, --version\n"
806 " Display the revision and exit successfully.\n"
807 , RTPathFilename(argv[0]));
808 return RTEXITCODE_SUCCESS;
809
810 case 'V':
811 RTPrintf("$Revision: 98103 $\n");
812 return RTEXITCODE_SUCCESS;
813
814 default:
815 return RTGetOptPrintError(rc, &ValueUnion);
816 }
817 }
818
819 if (!strlen(g_HttpServerData.szPathRootAbs))
820 {
821 /* By default use the current directory as serving root directory. */
822 rc = RTPathGetCurrent(g_HttpServerData.szPathRootAbs, sizeof(g_HttpServerData.szPathRootAbs));
823 if (RT_FAILURE(rc))
824 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Retrieving current directory failed: %Rrc", rc);
825 }
826
827 /* Install signal handler. */
828 rc = signalHandlerInstall();
829 if (RT_SUCCESS(rc))
830 {
831 /*
832 * Create the HTTP server instance.
833 */
834 RTHTTPSERVERCALLBACKS Callbacks;
835 RT_ZERO(Callbacks);
836
837 Callbacks.pfnOpen = onOpen;
838 Callbacks.pfnRead = onRead;
839 Callbacks.pfnClose = onClose;
840 Callbacks.pfnQueryInfo = onQueryInfo;
841 Callbacks.pfnDestroy = onDestroy;
842
843 g_HttpServerData.h.File = NIL_RTFILE;
844 g_HttpServerData.h.Dir = NIL_RTVFSDIR;
845
846 rc = RTHttpServerResponseInit(&g_HttpServerData.Resp);
847 AssertRC(rc);
848
849 RTHTTPSERVER hHTTPServer;
850 rc = RTHttpServerCreate(&hHTTPServer, szAddress, uPort, &Callbacks,
851 &g_HttpServerData, sizeof(g_HttpServerData));
852 if (RT_SUCCESS(rc))
853 {
854 RTPrintf("Starting HTTP server at %s:%RU16 ...\n", szAddress, uPort);
855 RTPrintf("Root directory is '%s'\n", g_HttpServerData.szPathRootAbs);
856
857 RTPrintf("Running HTTP server ...\n");
858
859 for (;;)
860 {
861 RTThreadSleep(200);
862
863 if (g_fCanceled)
864 break;
865 }
866
867 RTPrintf("Stopping HTTP server ...\n");
868
869 int rc2 = RTHttpServerDestroy(hHTTPServer);
870 if (RT_SUCCESS(rc))
871 rc = rc2;
872
873 RTPrintf("Stopped HTTP server\n");
874 }
875 else
876 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTHttpServerCreate failed: %Rrc", rc);
877
878 int rc2 = signalHandlerUninstall();
879 if (RT_SUCCESS(rc))
880 rc = rc2;
881 }
882
883 /* Set rcExit on failure in case we forgot to do so before. */
884 if (RT_FAILURE(rc))
885 rcExit = RTEXITCODE_FAILURE;
886
887 return rcExit;
888}
889
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