VirtualBox

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

Last change on this file since 95830 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

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