summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/r3/http-server.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/VBox/Runtime/r3/http-server.cpp1455
1 files changed, 1455 insertions, 0 deletions
diff --git a/src/VBox/Runtime/r3/http-server.cpp b/src/VBox/Runtime/r3/http-server.cpp
new file mode 100644
index 00000000..93c3f3fa
--- /dev/null
+++ b/src/VBox/Runtime/r3/http-server.cpp
@@ -0,0 +1,1455 @@
+/* $Id: http-server.cpp $ */
+/** @file
+ * Simple HTTP server (RFC 7231) implementation.
+ *
+ * Known limitations so far:
+ * - Only HTTP 1.1.
+ * - Only supports GET + HEAD methods so far.
+ * - Only supports UTF-8 charset.
+ * - Only supports plain text and octet stream MIME types.
+ * - No content compression ("gzip", "x-gzip", ++).
+ * - No caching.
+ * - No redirections (via 302).
+ * - No encryption (TLS).
+ * - No IPv6 support.
+ * - No multi-threading.
+ *
+ * For WebDAV (optional via IPRT_HTTP_WITH_WEBDAV):
+ * - Only OPTIONS + PROPLIST methods are implemented (e.g. simple read-only support).
+ * - No pagination support for directory listings.
+ */
+
+/*
+ * Copyright (C) 2020-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_HTTP
+#include <iprt/http.h>
+#include <iprt/http-server.h>
+#include "internal/iprt.h"
+#include "internal/magics.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/circbuf.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/file.h> /* For file mode flags. */
+#include <iprt/getopt.h>
+#include <iprt/mem.h>
+#include <iprt/log.h>
+#include <iprt/path.h>
+#include <iprt/poll.h>
+#include <iprt/socket.h>
+#include <iprt/sort.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/tcp.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Internal HTTP server instance.
+ */
+typedef struct RTHTTPSERVERINTERNAL
+{
+ /** Magic value. */
+ uint32_t u32Magic;
+ /** Callback table. */
+ RTHTTPSERVERCALLBACKS Callbacks;
+ /** Pointer to TCP server instance. */
+ PRTTCPSERVER pTCPServer;
+ /** Pointer to user-specific data. Optional. */
+ void *pvUser;
+ /** Size of user-specific data. Optional. */
+ size_t cbUser;
+} RTHTTPSERVERINTERNAL;
+/** Pointer to an internal HTTP server instance. */
+typedef RTHTTPSERVERINTERNAL *PRTHTTPSERVERINTERNAL;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
+#define RTHTTPSERVER_VALID_RETURN_RC(hHttpServer, a_rc) \
+ do { \
+ AssertPtrReturn((hHttpServer), (a_rc)); \
+ AssertReturn((hHttpServer)->u32Magic == RTHTTPSERVER_MAGIC, (a_rc)); \
+ } while (0)
+
+/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
+#define RTHTTPSERVER_VALID_RETURN(hHttpServer) RTHTTPSERVER_VALID_RETURN_RC((hHttpServer), VERR_INVALID_HANDLE)
+
+/** Validates a handle and returns (void) if not valid. */
+#define RTHTTPSERVER_VALID_RETURN_VOID(hHttpServer) \
+ do { \
+ AssertPtrReturnVoid(hHttpServer); \
+ AssertReturnVoid((hHttpServer)->u32Magic == RTHTTPSERVER_MAGIC); \
+ } while (0)
+
+
+/** Handles a HTTP server callback with no arguments and returns. */
+#define RTHTTPSERVER_HANDLE_CALLBACK_RET(a_Name) \
+ do \
+ { \
+ PRTHTTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
+ if (pCallbacks->a_Name) \
+ { \
+ RTHTTPCALLBACKDATA Data = { &pClient->State }; \
+ return pCallbacks->a_Name(&Data); \
+ } \
+ return VERR_NOT_IMPLEMENTED; \
+ } while (0)
+
+/** Handles a HTTP server callback with no arguments and sets rc accordingly. */
+#define RTHTTPSERVER_HANDLE_CALLBACK(a_Name) \
+ do \
+ { \
+ PRTHTTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
+ if (pCallbacks->a_Name) \
+ { \
+ RTHTTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
+ rc = pCallbacks->a_Name(&Data); \
+ } \
+ } while (0)
+
+/** Handles a HTTP server callback with arguments and sets rc accordingly. */
+#define RTHTTPSERVER_HANDLE_CALLBACK_VA(a_Name, ...) \
+ do \
+ { \
+ PRTHTTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
+ if (pCallbacks->a_Name) \
+ { \
+ RTHTTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
+ rc = pCallbacks->a_Name(&Data, __VA_ARGS__); \
+ } \
+ } while (0)
+
+/** Handles a HTTP server callback with arguments and returns. */
+#define RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(a_Name, ...) \
+ do \
+ { \
+ PRTHTTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
+ if (pCallbacks->a_Name) \
+ { \
+ RTHTTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
+ return pCallbacks->a_Name(&Data, __VA_ARGS__); \
+ } \
+ } while (0)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Structure for maintaining an internal HTTP server client.
+ */
+typedef struct RTHTTPSERVERCLIENT
+{
+ /** Pointer to internal server state. */
+ PRTHTTPSERVERINTERNAL pServer;
+ /** Socket handle the client is bound to. */
+ RTSOCKET hSocket;
+ /** Actual client state. */
+ RTHTTPSERVERCLIENTSTATE State;
+} RTHTTPSERVERCLIENT;
+/** Pointer to an internal HTTP server client state. */
+typedef RTHTTPSERVERCLIENT *PRTHTTPSERVERCLIENT;
+
+/** Function pointer declaration for a specific HTTP server method handler. */
+typedef DECLCALLBACKTYPE(int, FNRTHTTPSERVERMETHOD,(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq));
+/** Pointer to a FNRTHTTPSERVERMETHOD(). */
+typedef FNRTHTTPSERVERMETHOD *PFNRTHTTPSERVERMETHOD;
+
+/**
+ * Static lookup table for some file extensions <-> MIME type. Add more as needed.
+ * Keep this alphabetical (file extension).
+ */
+static const struct
+{
+ /** File extension. */
+ const char *pszExt;
+ /** MIME type. */
+ const char *pszMIMEType;
+} s_aFileExtMIMEType[] = {
+ { ".arj", "application/x-arj-compressed" },
+ { ".asf", "video/x-ms-asf" },
+ { ".avi", "video/x-msvideo" },
+ { ".bmp", "image/bmp" },
+ { ".css", "text/css" },
+ { ".doc", "application/msword" },
+ { ".exe", "application/octet-stream" },
+ { ".gif", "image/gif" },
+ { ".gz", "application/x-gunzip" },
+ { ".htm", "text/html" },
+ { ".html", "text/html" },
+ { ".ico", "image/x-icon" },
+ { ".js", "application/x-javascript" },
+ { ".json", "text/json" },
+ { ".jpg", "image/jpeg" },
+ { ".jpeg", "image/jpeg" },
+ { ".ogg", "application/ogg" },
+ { ".m3u", "audio/x-mpegurl" },
+ { ".m4v", "video/x-m4v" },
+ { ".mid", "audio/mid" },
+ { ".mov", "video/quicktime" },
+ { ".mp3", "audio/x-mp3" },
+ { ".mp4", "video/mp4" },
+ { ".mpg", "video/mpeg" },
+ { ".mpeg", "video/mpeg" },
+ { ".pdf", "application/pdf" },
+ { ".png", "image/png" },
+ { ".ra", "audio/x-pn-realaudio" },
+ { ".ram", "audio/x-pn-realaudio" },
+ { ".rar", "application/x-arj-compressed" },
+ { ".rtf", "application/rtf" },
+ { ".shtm", "text/html" },
+ { ".shtml", "text/html" },
+ { ".svg", "image/svg+xml" },
+ { ".swf", "application/x-shockwave-flash" },
+ { ".torrent", "application/x-bittorrent" },
+ { ".tar", "application/x-tar" },
+ { ".tgz", "application/x-tar-gz" },
+ { ".ttf", "application/x-font-ttf" },
+ { ".txt", "text/plain" },
+ { ".wav", "audio/x-wav" },
+ { ".webm", "video/webm" },
+ { ".xml", "text/xml" },
+ { ".xls", "application/excel" },
+ { ".xsl", "application/xml" },
+ { ".xslt", "application/xml" },
+ { ".zip", "application/x-zip-compressed" },
+ { NULL, NULL }
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/** @name Method handlers.
+ * @{
+ */
+static FNRTHTTPSERVERMETHOD rtHttpServerHandleGET;
+static FNRTHTTPSERVERMETHOD rtHttpServerHandleHEAD;
+#ifdef IPRT_HTTP_WITH_WEBDAV
+ static FNRTHTTPSERVERMETHOD rtHttpServerHandleOPTIONS;
+ static FNRTHTTPSERVERMETHOD rtHttpServerHandlePROPFIND;
+#endif
+/** @} */
+
+/**
+ * Structure for maintaining a single method entry for the methods table.
+ */
+typedef struct RTHTTPSERVERMETHOD_ENTRY
+{
+ /** Method ID. */
+ RTHTTPMETHOD enmMethod;
+ /** Function pointer invoked to handle the command. */
+ PFNRTHTTPSERVERMETHOD pfnMethod;
+} RTHTTPSERVERMETHOD_ENTRY;
+/** Pointer to a command entry. */
+typedef RTHTTPSERVERMETHOD_ENTRY *PRTHTTPMETHOD_ENTRY;
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Table of handled methods.
+ */
+static const RTHTTPSERVERMETHOD_ENTRY g_aMethodMap[] =
+{
+ { RTHTTPMETHOD_GET, rtHttpServerHandleGET },
+ { RTHTTPMETHOD_HEAD, rtHttpServerHandleHEAD },
+#ifdef IPRT_HTTP_WITH_WEBDAV
+ { RTHTTPMETHOD_OPTIONS, rtHttpServerHandleOPTIONS },
+ { RTHTTPMETHOD_PROPFIND, rtHttpServerHandlePROPFIND },
+#endif
+ { RTHTTPMETHOD_END, NULL }
+};
+
+/** Maximum length in characters a HTTP server path can have (excluding termination). */
+#define RTHTTPSERVER_MAX_PATH RTPATH_MAX
+
+
+/*********************************************************************************************************************************
+* Internal functions *
+*********************************************************************************************************************************/
+
+/**
+ * Guesses the HTTP MIME type based on a given file extension.
+ *
+ * Note: Has to include the beginning dot, e.g. ".mp3" (see IPRT).
+ *
+ * @returns Guessed MIME type, or "application/octet-stream" if not found.
+ * @param pszFileExt File extension to guess MIME type for.
+ */
+static const char *rtHttpServerGuessMIMEType(const char *pszFileExt)
+{
+ if (pszFileExt)
+ {
+ size_t i = 0;
+ while (s_aFileExtMIMEType[i++].pszExt) /* Slow, but does the job for now. */
+ {
+ if (!RTStrICmp(pszFileExt, s_aFileExtMIMEType[i].pszExt))
+ return s_aFileExtMIMEType[i].pszMIMEType;
+ }
+ }
+
+ return "application/octet-stream";
+}
+
+/**
+ * Initializes a HTTP body.
+ *
+ * @param pBody Body to initialize.
+ * @param cbSize Size of body (in bytes) to allocate. Optional and can be 0.
+ */
+static int rtHttpServerBodyInit(PRTHTTPBODY pBody, size_t cbSize)
+{
+ if (cbSize)
+ {
+ pBody->pvBody = RTMemAlloc(cbSize);
+ AssertPtrReturn(pBody->pvBody, VERR_NO_MEMORY);
+ pBody->cbBodyAlloc = cbSize;
+ }
+ else
+ {
+ pBody->pvBody = NULL;
+ pBody->cbBodyAlloc = 0;
+ }
+
+ pBody->cbBodyUsed = 0;
+ pBody->offBody = 0;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a HTTP body.
+ *
+ * @param pBody Body to destroy.
+ */
+static void rtHttpServerBodyDestroy(PRTHTTPBODY pBody)
+{
+ if (!pBody)
+ return;
+
+ if (pBody->pvBody)
+ {
+ Assert(pBody->cbBodyAlloc);
+
+ RTMemFree(pBody->pvBody);
+ pBody->pvBody = NULL;
+ }
+
+ pBody->cbBodyAlloc = 0;
+ pBody->cbBodyUsed = 0;
+ pBody->offBody = 0;
+}
+
+/**
+ * Allocates and initializes a new client request.
+ *
+ * @returns Pointer to the new client request, or NULL on OOM.
+ * Needs to be free'd with rtHttpServerReqFree().
+ */
+static PRTHTTPSERVERREQ rtHttpServerReqAlloc(void)
+{
+ PRTHTTPSERVERREQ pReq = (PRTHTTPSERVERREQ)RTMemAllocZ(sizeof(RTHTTPSERVERREQ));
+ AssertPtrReturn(pReq, NULL);
+
+ int rc2 = RTHttpHeaderListInit(&pReq->hHdrLst);
+ AssertRC(rc2);
+
+ rc2 = rtHttpServerBodyInit(&pReq->Body, 0 /* cbSize */);
+ AssertRC(rc2);
+
+ return pReq;
+}
+
+/**
+ * Frees a formerly allocated client request.
+ *
+ * @param pReq Pointer to client request to free.
+ * The pointer will be invalid on return.
+ */
+static void rtHttpServerReqFree(PRTHTTPSERVERREQ pReq)
+{
+ if (!pReq)
+ return;
+
+ RTStrFree(pReq->pszUrl);
+
+ RTHttpHeaderListDestroy(pReq->hHdrLst);
+
+ rtHttpServerBodyDestroy(&pReq->Body);
+
+ RTMemFree(pReq);
+ pReq = NULL;
+}
+
+/**
+ * Initializes a HTTP server response with an allocated body size.
+ *
+ * @returns VBox status code.
+ * @param pResp HTTP server response to initialize.
+ * @param cbBody Body size (in bytes) to allocate.
+ */
+RTR3DECL(int) RTHttpServerResponseInitEx(PRTHTTPSERVERRESP pResp, size_t cbBody)
+{
+ pResp->enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET;
+
+ int rc = RTHttpHeaderListInit(&pResp->hHdrLst);
+ AssertRCReturn(rc, rc);
+
+ rc = rtHttpServerBodyInit(&pResp->Body, cbBody);
+
+ return rc;
+}
+
+/**
+ * Initializes a HTTP server response.
+ *
+ * @returns VBox status code.
+ * @param pResp HTTP server response to initialize.
+ */
+RTR3DECL(int) RTHttpServerResponseInit(PRTHTTPSERVERRESP pResp)
+{
+ return RTHttpServerResponseInitEx(pResp, 0 /* cbBody */);
+}
+
+/**
+ * Destroys a formerly initialized HTTP server response.
+ *
+ * @param pResp Pointer to HTTP server response to destroy.
+ */
+RTR3DECL(void) RTHttpServerResponseDestroy(PRTHTTPSERVERRESP pResp)
+{
+ if (!pResp)
+ return;
+
+ pResp->enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET;
+
+ RTHttpHeaderListDestroy(pResp->hHdrLst);
+
+ rtHttpServerBodyDestroy(&pResp->Body);
+}
+
+
+/*********************************************************************************************************************************
+* Protocol Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Logs the HTTP protocol communication to the debug logger (2).
+ *
+ * @param pClient Client to log communication for.
+ * @param fWrite Whether the server is writing (to client) or reading (from client).
+ * @param pszData Actual protocol communication data to log.
+ */
+static void rtHttpServerLogProto(PRTHTTPSERVERCLIENT pClient, bool fWrite, const char *pszData)
+{
+ RT_NOREF(pClient);
+
+#ifdef LOG_ENABLED
+ if (!pszData) /* Nothing to log? Bail out. */
+ return;
+
+ char **ppapszStrings;
+ size_t cStrings;
+ int rc2 = RTStrSplit(pszData, strlen(pszData), RTHTTPSERVER_HTTP11_EOL_STR, &ppapszStrings, &cStrings);
+ if (RT_SUCCESS(rc2))
+ {
+ for (size_t i = 0; i < cStrings; i++)
+ {
+ Log2(("%s %s\n", fWrite ? ">" : "<", ppapszStrings[i]));
+ RTStrFree(ppapszStrings[i]);
+ }
+
+ RTMemFree(ppapszStrings);
+ }
+#else
+ RT_NOREF(fWrite, pszData);
+#endif
+}
+
+/**
+ * Writes HTTP protocol communication data to a connected client.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to write data to.
+ * @param pszData Data to write. Must be zero-terminated.
+ */
+static int rtHttpServerWriteProto(PRTHTTPSERVERCLIENT pClient, const char *pszData)
+{
+ rtHttpServerLogProto(pClient, true /* fWrite */, pszData);
+ return RTTcpWrite(pClient->hSocket, pszData, strlen(pszData));
+}
+
+/**
+ * Main function for sending a response back to the client.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to reply to.
+ * @param enmSts Status code to send.
+ */
+static int rtHttpServerSendResponse(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts)
+{
+ char *pszResp;
+ int rc = RTStrAPrintf(&pszResp,
+ "%s %RU32 %s\r\n", RTHTTPVER_1_1_STR, enmSts, RTHttpStatusToStr(enmSts));
+ AssertRCReturn(rc, rc);
+ rc = rtHttpServerWriteProto(pClient, pszResp);
+ RTStrFree(pszResp);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Main function for sending response headers back to the client.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to reply to.
+ * @param pHdrLst Header list to send. Optional and can be NULL.
+ */
+static int rtHttpServerSendResponseHdrEx(PRTHTTPSERVERCLIENT pClient, PRTHTTPHEADERLIST pHdrLst)
+{
+ RTHTTPHEADERLIST HdrLst;
+ int rc = RTHttpHeaderListInit(&HdrLst);
+ AssertRCReturn(rc, rc);
+
+#ifdef DEBUG
+ /* Include a timestamp when running a debug build. */
+ RTTIMESPEC tsNow;
+ char szTS[64];
+ RTTimeSpecToString(RTTimeNow(&tsNow), szTS, sizeof(szTS));
+ rc = RTHttpHeaderListAdd(HdrLst, "Date", szTS, strlen(szTS), RTHTTPHEADERLISTADD_F_BACK);
+ AssertRCReturn(rc, rc);
+#endif
+
+ /* Note: Deliberately don't include the VBox version due to security reasons. */
+ rc = RTHttpHeaderListAdd(HdrLst, "Server", "Oracle VirtualBox", strlen("Oracle VirtualBox"), RTHTTPHEADERLISTADD_F_BACK);
+ AssertRCReturn(rc, rc);
+
+#ifdef IPRT_HTTP_WITH_WEBDAV
+ rc = RTHttpHeaderListAdd(HdrLst, "Allow", "GET, HEAD, PROPFIND", strlen("GET, HEAD, PROPFIND"), RTHTTPHEADERLISTADD_F_BACK);
+ AssertRCReturn(rc, rc);
+ rc = RTHttpHeaderListAdd(HdrLst, "DAV", "1", strlen("1"), RTHTTPHEADERLISTADD_F_BACK); /* Note: v1 is sufficient for read-only access. */
+ AssertRCReturn(rc, rc);
+#endif
+
+ char *pszHdr = NULL;
+
+ size_t i = 0;
+ const char *pszEntry;
+ while ((pszEntry = RTHttpHeaderListGetByOrdinal(HdrLst, i++)) != NULL)
+ {
+ rc = RTStrAAppend(&pszHdr, pszEntry);
+ AssertRCBreak(rc);
+ rc = RTStrAAppend(&pszHdr, RTHTTPSERVER_HTTP11_EOL_STR);
+ AssertRCBreak(rc);
+ }
+
+ /* Append optional headers, if any. */
+ if (pHdrLst)
+ {
+ i = 0;
+ while ((pszEntry = RTHttpHeaderListGetByOrdinal(*pHdrLst, i++)) != NULL)
+ {
+ rc = RTStrAAppend(&pszHdr, pszEntry);
+ AssertRCBreak(rc);
+ rc = RTStrAAppend(&pszHdr, RTHTTPSERVER_HTTP11_EOL_STR);
+ AssertRCBreak(rc);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Append trailing EOL. */
+ rc = RTStrAAppend(&pszHdr, RTHTTPSERVER_HTTP11_EOL_STR);
+ if (RT_SUCCESS(rc))
+ rc = rtHttpServerWriteProto(pClient, pszHdr);
+ }
+
+ RTStrFree(pszHdr);
+
+ RTHttpHeaderListDestroy(HdrLst);
+
+ LogFlowFunc(("rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Replies with (three digit) response status back to the client, extended version.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to reply to.
+ * @param enmSts Status code to send.
+ * @param pHdrLst Header list to send. Optional and can be NULL.
+ */
+static int rtHttpServerSendResponseEx(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts, PRTHTTPHEADERLIST pHdrLst)
+{
+ int rc = rtHttpServerSendResponse(pClient, enmSts);
+ if (RT_SUCCESS(rc))
+ rc = rtHttpServerSendResponseHdrEx(pClient, pHdrLst);
+
+ return rc;
+}
+
+/**
+ * Replies with (three digit) response status back to the client.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to reply to.
+ * @param enmSts Status code to send.
+ */
+static int rtHttpServerSendResponseSimple(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts)
+{
+ return rtHttpServerSendResponseEx(pClient, enmSts, NULL /* pHdrLst */);
+}
+
+/**
+ * Sends a chunk of the response body to the client.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to send body to.
+ * @param pvBuf Data buffer to send.
+ * @param cbBuf Size (in bytes) of data buffer to send.
+ * @param pcbSent Where to store the sent bytes. Optional and can be NULL.
+ */
+static int rtHttpServerSendResponseBody(PRTHTTPSERVERCLIENT pClient, void *pvBuf, size_t cbBuf, size_t *pcbSent)
+{
+ int rc = RTTcpWrite(pClient->hSocket, pvBuf, cbBuf);
+ if ( RT_SUCCESS(rc)
+ && pcbSent)
+ *pcbSent = cbBuf;
+
+ return rc;
+}
+
+/**
+ * Resolves a VBox status code to a HTTP status code.
+ *
+ * @returns Resolved HTTP status code, or RTHTTPSTATUS_INTERNALSERVERERROR if not able to resolve.
+ * @param rc VBox status code to resolve.
+ */
+static RTHTTPSTATUS rtHttpServerRcToStatus(int rc)
+{
+ switch (rc)
+ {
+ case VINF_SUCCESS: return RTHTTPSTATUS_OK;
+ case VERR_INVALID_PARAMETER: return RTHTTPSTATUS_BADREQUEST;
+ case VERR_INVALID_POINTER: return RTHTTPSTATUS_BADREQUEST;
+ case VERR_NOT_IMPLEMENTED: return RTHTTPSTATUS_NOTIMPLEMENTED;
+ case VERR_NOT_SUPPORTED: return RTHTTPSTATUS_NOTIMPLEMENTED;
+ case VERR_PATH_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
+ case VERR_FILE_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
+ case VERR_IS_A_DIRECTORY: return RTHTTPSTATUS_FORBIDDEN;
+ case VERR_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
+ default:
+ break;
+ }
+
+ AssertMsgFailed(("rc=%Rrc not handled for HTTP status\n", rc));
+ return RTHTTPSTATUS_INTERNALSERVERERROR;
+}
+
+
+/*********************************************************************************************************************************
+* Command Protocol Handlers *
+*********************************************************************************************************************************/
+
+/**
+ * Handler for the GET method.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to handle GET method for.
+ * @param pReq Client request to handle.
+ */
+static DECLCALLBACK(int) rtHttpServerHandleGET(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
+{
+ LogFlowFuncEnter();
+
+ int rc = VINF_SUCCESS;
+
+ /* If a low-level GET request handler is defined, call it and return. */
+ RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnGetRequest, pReq);
+
+ RTFSOBJINFO fsObj;
+ RT_ZERO(fsObj); /* Shut up MSVC. */
+
+ char *pszMIMEHint = NULL;
+
+ RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq, &fsObj, &pszMIMEHint);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ void *pvHandle = NULL;
+ RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnOpen, pReq, &pvHandle);
+
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbBuf = _64K;
+ void *pvBuf = RTMemAlloc(cbBuf);
+ AssertPtrReturn(pvBuf, VERR_NO_MEMORY);
+
+ for (;;)
+ {
+ RTHTTPHEADERLIST HdrLst;
+ rc = RTHttpHeaderListInit(&HdrLst);
+ AssertRCReturn(rc, rc);
+
+ char szVal[16];
+
+ /* Note: For directories fsObj.cbObject contains the actual size (in bytes)
+ * of the body data for the directory listing. */
+
+ ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
+ AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
+ rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
+ AssertRCBreak(rc);
+
+ cch = RTStrPrintf2(szVal, sizeof(szVal), "identity");
+ AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
+ rc = RTHttpHeaderListAdd(HdrLst, "Content-Encoding", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
+ AssertRCBreak(rc);
+
+ if (pszMIMEHint == NULL)
+ {
+ const char *pszMIME = rtHttpServerGuessMIMEType(RTPathSuffix(pReq->pszUrl));
+ rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIME, strlen(pszMIME), RTHTTPHEADERLISTADD_F_BACK);
+ }
+ else
+ {
+ rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIMEHint, strlen(pszMIMEHint), RTHTTPHEADERLISTADD_F_BACK);
+ RTStrFree(pszMIMEHint);
+ pszMIMEHint = NULL;
+ }
+ AssertRCBreak(rc);
+
+ if (pClient->State.msKeepAlive)
+ {
+ /* If the client requested to keep alive the connection,
+ * always override this with 30s and report this back to the client. */
+ pClient->State.msKeepAlive = RT_MS_30SEC; /** @todo Make this configurable. */
+#ifdef DEBUG_andy
+ pClient->State.msKeepAlive = 5000;
+#endif
+ cch = RTStrPrintf2(szVal, sizeof(szVal), "timeout=%RU64", pClient->State.msKeepAlive / RT_MS_1SEC); /** @todo No pipelining support here yet. */
+ AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
+ rc = RTHttpHeaderListAdd(HdrLst, "Keep-Alive", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
+ AssertRCReturn(rc, rc);
+ }
+
+ rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, &HdrLst);
+
+ RTHttpHeaderListDestroy(HdrLst);
+
+ if (rc == VERR_BROKEN_PIPE) /* Could happen on fast reloads. */
+ break;
+ AssertRCReturn(rc, rc);
+
+ size_t cbToRead = fsObj.cbObject;
+ size_t cbRead = 0; /* Shut up GCC. */
+ size_t cbWritten = 0; /* Ditto. */
+ while (cbToRead)
+ {
+ RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRead, pvHandle, pvBuf, RT_MIN(cbBuf, cbToRead), &cbRead);
+ if (RT_FAILURE(rc))
+ break;
+ rc = rtHttpServerSendResponseBody(pClient, pvBuf, cbRead, &cbWritten);
+ AssertBreak(cbToRead >= cbWritten);
+ cbToRead -= cbWritten;
+ if (rc == VERR_NET_CONNECTION_RESET_BY_PEER) /* Clients often apruptly abort the connection when done. */
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+ AssertRCBreak(rc);
+ }
+
+ break;
+ } /* for (;;) */
+
+ RTMemFree(pvBuf);
+
+ int rc2 = rc; /* Save rc. */
+
+ RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnClose, pvHandle);
+
+ if (RT_FAILURE(rc2)) /* Restore original rc on failure. */
+ rc = rc2;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Handler for the HEAD method.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to handle HEAD method for.
+ * @param pReq Client request to handle.
+ */
+static DECLCALLBACK(int) rtHttpServerHandleHEAD(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
+{
+ LogFlowFuncEnter();
+
+ /* If a low-level HEAD request handler is defined, call it and return. */
+ RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnHeadRequest, pReq);
+
+ int rc = VINF_SUCCESS;
+
+ RTFSOBJINFO fsObj;
+ RT_ZERO(fsObj); /* Shut up MSVC. */
+
+ RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq, &fsObj, NULL /* pszMIMEHint */);
+ if (RT_SUCCESS(rc))
+ {
+ RTHTTPHEADERLIST HdrLst;
+ rc = RTHttpHeaderListInit(&HdrLst);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Note: A response to a HEAD request does not have a body.
+ * All entity headers below are assumed to describe the the response a similar GET
+ * request would return (but then with a body).
+ */
+ char szVal[16];
+
+ ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
+ AssertReturn(cch, VERR_BUFFER_OVERFLOW);
+ rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
+ AssertRCReturn(rc, rc);
+
+ cch = RTStrPrintf2(szVal, sizeof(szVal), "identity");
+ AssertReturn(cch, VERR_BUFFER_OVERFLOW);
+ rc = RTHttpHeaderListAdd(HdrLst, "Content-Encoding", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
+ AssertRCReturn(rc, rc);
+
+ const char *pszMIME = rtHttpServerGuessMIMEType(RTPathSuffix(pReq->pszUrl));
+ rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIME, strlen(pszMIME), RTHTTPHEADERLISTADD_F_BACK);
+ AssertRCReturn(rc, rc);
+
+ rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, &HdrLst);
+ AssertRCReturn(rc, rc);
+
+ RTHttpHeaderListDestroy(HdrLst);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+#ifdef IPRT_HTTP_WITH_WEBDAV
+/**
+ * Handler for the OPTIONS method.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to handle OPTIONS method for.
+ * @param pReq Client request to handle.
+ */
+static DECLCALLBACK(int) rtHttpServerHandleOPTIONS(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
+{
+ LogFlowFuncEnter();
+
+ RT_NOREF(pReq);
+
+ int rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, NULL /* pHdrLst */);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Handler for the PROPFIND (WebDAV) method.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to handle PROPFIND method for.
+ * @param pReq Client request to handle.
+ */
+static DECLCALLBACK(int) rtHttpServerHandlePROPFIND(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
+{
+ LogFlowFuncEnter();
+
+ int rc = VINF_SUCCESS;
+
+ /* If a low-level GET request handler is defined, call it and return. */
+ RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnGetRequest, pReq);
+
+ RTFSOBJINFO fsObj;
+ RT_ZERO(fsObj); /* Shut up MSVC. */
+
+ RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq, &fsObj, NULL /* pszMIMEHint */);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ void *pvHandle = NULL;
+ RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnOpen, pReq, &pvHandle);
+
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbBuf = _64K;
+ void *pvBuf = RTMemAlloc(cbBuf);
+ AssertPtrReturn(pvBuf, VERR_NO_MEMORY);
+
+ for (;;)
+ {
+ RTHTTPHEADERLIST HdrLst;
+ rc = RTHttpHeaderListInit(&HdrLst);
+ AssertRCReturn(rc, rc);
+
+ char szVal[16];
+
+ rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", "text/xml; charset=utf-8", strlen("text/xml; charset=utf-8"), RTHTTPHEADERLISTADD_F_BACK);
+ AssertRCBreak(rc);
+
+ /* Note: For directories fsObj.cbObject contains the actual size (in bytes)
+ * of the body data for the directory listing. */
+
+ ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
+ AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
+ rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
+ AssertRCBreak(rc);
+
+ rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_MULTISTATUS, &HdrLst);
+ AssertRCReturn(rc, rc);
+
+ RTHttpHeaderListDestroy(HdrLst);
+
+ size_t cbToRead = fsObj.cbObject;
+ size_t cbRead = 0; /* Shut up GCC. */
+ size_t cbWritten = 0; /* Ditto. */
+ while (cbToRead)
+ {
+ RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRead, pvHandle, pvBuf, RT_MIN(cbBuf, cbToRead), &cbRead);
+ if (RT_FAILURE(rc))
+ break;
+ //rtHttpServerLogProto(pClient, true /* fWrite */, (const char *)pvBuf);
+ rc = rtHttpServerSendResponseBody(pClient, pvBuf, cbRead, &cbWritten);
+ AssertBreak(cbToRead >= cbWritten);
+ cbToRead -= cbWritten;
+ if (rc == VERR_NET_CONNECTION_RESET_BY_PEER) /* Clients often apruptly abort the connection when done. */
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+ AssertRCBreak(rc);
+ }
+
+ break;
+ } /* for (;;) */
+
+ RTMemFree(pvBuf);
+
+ int rc2 = rc; /* Save rc. */
+
+ RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnClose, pvHandle);
+
+ if (RT_FAILURE(rc2)) /* Restore original rc on failure. */
+ rc = rc2;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+#endif /* IPRT_HTTP_WITH_WEBDAV */
+
+/**
+ * Validates if a given path is valid or not.
+ *
+ * @returns \c true if path is valid, or \c false if not.
+ * @param pszPath Path to check.
+ * @param fIsAbsolute Whether the path to check is an absolute path or not.
+ */
+static bool rtHttpServerPathIsValid(const char *pszPath, bool fIsAbsolute)
+{
+ if (!pszPath)
+ return false;
+
+ bool fIsValid = strlen(pszPath)
+ && RTStrIsValidEncoding(pszPath)
+ && RTStrStr(pszPath, "..") == NULL; /** @todo Very crude for now -- improve this. */
+ if ( fIsValid
+ && fIsAbsolute)
+ {
+ RTFSOBJINFO objInfo;
+ int rc2 = RTPathQueryInfo(pszPath, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc2))
+ {
+ fIsValid = RTFS_IS_DIRECTORY(objInfo.Attr.fMode)
+ || RTFS_IS_FILE(objInfo.Attr.fMode);
+
+ /* No symlinks and other stuff not allowed. */
+ }
+ else
+ fIsValid = false;
+ }
+
+ LogFlowFunc(("pszPath=%s -> %RTbool\n", pszPath, fIsValid));
+ return fIsValid;
+
+}
+
+/**
+ * Parses headers and sets (replaces) a given header list.
+ *
+ * @returns VBox status code.
+ * @param hList Header list to fill parsed headers in.
+ * @param cStrings Number of strings to parse for \a papszStrings.
+ * @param papszStrings Array of strings to parse.
+ */
+static int rtHttpServerParseHeaders(RTHTTPHEADERLIST hList, size_t cStrings, char **papszStrings)
+{
+ /* Nothing to parse left? Bail out early. */
+ if ( !cStrings
+ || !papszStrings)
+ return VINF_SUCCESS;
+
+#ifdef LOG_ENABLED
+ for (size_t i = 0; i < cStrings; i++)
+ LogFlowFunc(("Header: %s\n", papszStrings[i]));
+#endif
+
+ int rc = RTHttpHeaderListSet(hList, cStrings, papszStrings);
+
+ LogFlowFunc(("rc=%Rrc, cHeaders=%zu\n", rc, cStrings));
+ return rc;
+}
+
+/**
+ * Main function for parsing and allocating a client request.
+ *
+ * See: https://tools.ietf.org/html/rfc2616#section-2.2
+ *
+ * @returns VBox status code.
+ * @param pClient Client to parse request from.
+ * @param pszReq Request string with headers to parse.
+ * @param cbReq Size (in bytes) of request string to parse.
+ * @param ppReq Where to store the allocated client request on success.
+ * Needs to be free'd via rtHttpServerReqFree().
+ */
+static int rtHttpServerParseRequest(PRTHTTPSERVERCLIENT pClient, const char *pszReq, size_t cbReq,
+ PRTHTTPSERVERREQ *ppReq)
+{
+ RT_NOREF(pClient);
+
+ AssertPtrReturn(pszReq, VERR_INVALID_POINTER);
+ AssertReturn(cbReq, VERR_INVALID_PARAMETER);
+
+ /* We only support UTF-8 charset for now. */
+ AssertReturn(RTStrIsValidEncoding(pszReq), VERR_INVALID_PARAMETER);
+
+ char **ppapszReq = NULL;
+ size_t cReq = 0;
+ int rc = RTStrSplit(pszReq, cbReq, RTHTTPSERVER_HTTP11_EOL_STR, &ppapszReq, &cReq);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (!cReq)
+ return VERR_INVALID_PARAMETER;
+
+#ifdef LOG_ENABLED
+ for (size_t i = 0; i < cReq; i++)
+ LogFlowFunc(("%s\n", ppapszReq[i]));
+#endif
+
+ PRTHTTPSERVERREQ pReq = NULL;
+
+ char **ppapszFirstLine = NULL;
+ size_t cFirstLine = 0;
+ rc = RTStrSplit(ppapszReq[0], strlen(ppapszReq[0]), " ", &ppapszFirstLine, &cFirstLine);
+ if (RT_SUCCESS(rc))
+ {
+ if (cFirstLine < 3) /* At leat the method, path and version has to be present. */
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ while (RT_SUCCESS(rc)) /* To use break. */
+ {
+ pReq = rtHttpServerReqAlloc();
+ AssertPtrBreakStmt(pReq, rc = VERR_NO_MEMORY);
+
+ /*
+ * Parse method to use. Method names are case sensitive.
+ */
+ const char *pszMethod = ppapszFirstLine[0];
+ if (!RTStrCmp(pszMethod, "GET")) pReq->enmMethod = RTHTTPMETHOD_GET;
+ else if (!RTStrCmp(pszMethod, "HEAD")) pReq->enmMethod = RTHTTPMETHOD_HEAD;
+#ifdef IPRT_HTTP_WITH_WEBDAV
+ else if (!RTStrCmp(pszMethod, "OPTIONS")) pReq->enmMethod = RTHTTPMETHOD_OPTIONS;
+ else if (!RTStrCmp(pszMethod, "PROPFIND")) pReq->enmMethod = RTHTTPMETHOD_PROPFIND;
+#endif
+ else
+ {
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ /*
+ * Parse requested path.
+ */
+ /** @todo Do URL unescaping here. */
+ const char *pszPath = ppapszFirstLine[1];
+ if (!rtHttpServerPathIsValid(pszPath, false /* fIsAbsolute */))
+ {
+ rc = VERR_PATH_NOT_FOUND;
+ break;
+ }
+
+ pReq->pszUrl = RTStrDup(pszPath);
+
+ /*
+ * Parse HTTP version to use.
+ * We're picky heree: Only HTTP 1.1 is supported by now.
+ */
+ const char *pszVer = ppapszFirstLine[2];
+ if (RTStrCmp(pszVer, RTHTTPVER_1_1_STR)) /** @todo Use RTStrVersionCompare. Later. */
+ {
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ /** @todo Anything else needed for the first line here? */
+
+ /*
+ * Process headers, if any.
+ */
+ if (cReq > 1)
+ {
+ rc = rtHttpServerParseHeaders(pReq->hHdrLst, cReq - 1, &ppapszReq[1]);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTHttpHeaderListGet(pReq->hHdrLst, "Connection", RTSTR_MAX))
+ pClient->State.msKeepAlive = RT_MS_30SEC; /** @todo Insert the real value here. */
+ }
+ }
+ break;
+ } /* for (;;) */
+
+ /*
+ * Cleanup.
+ */
+
+ for (size_t i = 0; i < cFirstLine; i++)
+ RTStrFree(ppapszFirstLine[i]);
+ RTMemFree(ppapszFirstLine);
+
+ for (size_t i = 0; i < cReq; i++)
+ RTStrFree(ppapszReq[i]);
+ RTMemFree(ppapszReq);
+
+ if (RT_FAILURE(rc))
+ {
+ rtHttpServerReqFree(pReq);
+ pReq = NULL;
+ }
+ else
+ *ppReq = pReq;
+
+ return rc;
+}
+
+/**
+ * Main function for processing client requests.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to process request for.
+ * @param pszReq Request string to parse and handle.
+ * @param cbReq Size (in bytes) of request string.
+ */
+static int rtHttpServerProcessRequest(PRTHTTPSERVERCLIENT pClient, char *pszReq, size_t cbReq)
+{
+ RTHTTPSTATUS enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET;
+
+ PRTHTTPSERVERREQ pReq = NULL; /* Shut up GCC. */
+ int rc = rtHttpServerParseRequest(pClient, pszReq, cbReq, &pReq);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("Request %s %s\n", RTHttpMethodToStr(pReq->enmMethod), pReq->pszUrl));
+
+ unsigned i = 0;
+ for (; i < RT_ELEMENTS(g_aMethodMap); i++)
+ {
+ const RTHTTPSERVERMETHOD_ENTRY *pMethodEntry = &g_aMethodMap[i];
+ if (pReq->enmMethod == pMethodEntry->enmMethod)
+ {
+ /* Hand in request to method handler. */
+ int rcMethod = pMethodEntry->pfnMethod(pClient, pReq);
+ if (RT_FAILURE(rcMethod))
+ LogFunc(("Request %s %s failed with %Rrc\n", RTHttpMethodToStr(pReq->enmMethod), pReq->pszUrl, rcMethod));
+
+ enmSts = rtHttpServerRcToStatus(rcMethod);
+ break;
+ }
+ }
+
+ if (i == RT_ELEMENTS(g_aMethodMap))
+ enmSts = RTHTTPSTATUS_NOTIMPLEMENTED;
+
+ rtHttpServerReqFree(pReq);
+ }
+ else
+ enmSts = RTHTTPSTATUS_BADREQUEST;
+
+ if (enmSts != RTHTTPSTATUS_INTERNAL_NOT_SET)
+ {
+ int rc2 = rtHttpServerSendResponseSimple(pClient, enmSts);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Main loop for processing client requests.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to process requests for.
+ */
+static int rtHttpServerClientMain(PRTHTTPSERVERCLIENT pClient)
+{
+ int rc;
+
+ char szReq[RTHTTPSERVER_MAX_REQ_LEN + 1];
+
+ LogFlowFunc(("Client connected\n"));
+
+ /* Initialize client state. */
+ pClient->State.msKeepAlive = 0;
+
+ RTMSINTERVAL cWaitMs = RT_INDEFINITE_WAIT; /* The first wait always waits indefinitely. */
+ uint64_t tsLastReadMs = 0;
+
+ for (;;)
+ {
+ rc = RTTcpSelectOne(pClient->hSocket, cWaitMs);
+ if (RT_FAILURE(rc))
+ {
+ LogFlowFunc(("RTTcpSelectOne=%Rrc (cWaitMs=%RU64)\n", rc, cWaitMs));
+ if (rc == VERR_TIMEOUT)
+ {
+ if (pClient->State.msKeepAlive) /* Keep alive handling needed? */
+ {
+ if (!tsLastReadMs)
+ tsLastReadMs = RTTimeMilliTS();
+ const uint64_t tsDeltaMs = pClient->State.msKeepAlive - (RTTimeMilliTS() - tsLastReadMs);
+ LogFlowFunc(("tsLastReadMs=%RU64, tsDeltaMs=%RU64\n", tsLastReadMs, tsDeltaMs));
+ Log3Func(("Keep alive active (%RU32ms): %RU64ms remaining\n", pClient->State.msKeepAlive, tsDeltaMs));
+ if ( tsDeltaMs > cWaitMs
+ && tsDeltaMs < pClient->State.msKeepAlive)
+ continue;
+
+ LogFunc(("Keep alive active: Client did not respond within %RU32ms, closing\n", pClient->State.msKeepAlive));
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+
+ break;
+ }
+
+ LogFlowFunc(("Reading client request ...\n"));
+
+ tsLastReadMs = RTTimeMilliTS();
+ cWaitMs = 200; /* All consequtive waits do busy waiting for now. */
+
+ char *pszReq = szReq;
+ size_t cbRead;
+ size_t cbToRead = sizeof(szReq);
+ size_t cbReadTotal = 0;
+
+ do
+ {
+ rc = RTTcpReadNB(pClient->hSocket, pszReq, cbToRead, &cbRead);
+ if (RT_FAILURE(rc))
+ break;
+
+ if (!cbRead)
+ break;
+
+ /* Make sure to terminate string read so far. */
+ pszReq[cbRead] = '\0';
+
+ /* End of request reached? */
+ /** @todo BUGBUG Improve this. */
+ char *pszEOR = RTStrStr(&pszReq[cbReadTotal], "\r\n\r\n");
+ if (pszEOR)
+ {
+ cbReadTotal = pszEOR - pszReq;
+ *pszEOR = '\0';
+ break;
+ }
+
+ AssertBreak(cbToRead >= cbRead);
+ cbToRead -= cbRead;
+ cbReadTotal += cbRead;
+ AssertBreak(cbReadTotal <= sizeof(szReq));
+ pszReq += cbRead;
+
+ } while (cbToRead);
+
+ if ( RT_SUCCESS(rc)
+ && cbReadTotal)
+ {
+ LogFlowFunc(("Received client request (%zu bytes)\n", cbReadTotal));
+
+ rtHttpServerLogProto(pClient, false /* fWrite */, szReq);
+
+ rc = rtHttpServerProcessRequest(pClient, szReq, cbReadTotal);
+ }
+ else
+ break;
+
+ } /* for */
+
+ if (RT_FAILURE(rc))
+ {
+ switch (rc)
+ {
+ case VERR_NET_CONNECTION_RESET_BY_PEER:
+ {
+ LogFunc(("Client closed the connection\n"));
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ default:
+ LogFunc(("Client processing failed with %Rrc\n", rc));
+ break;
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Per-client thread for serving the server's control connection.
+ *
+ * @returns VBox status code.
+ * @param hSocket Socket handle to use for the control connection.
+ * @param pvUser User-provided arguments. Of type PRTHTTPSERVERINTERNAL.
+ */
+static DECLCALLBACK(int) rtHttpServerClientThread(RTSOCKET hSocket, void *pvUser)
+{
+ PRTHTTPSERVERINTERNAL pThis = (PRTHTTPSERVERINTERNAL)pvUser;
+ RTHTTPSERVER_VALID_RETURN(pThis);
+
+ LogFlowFuncEnter();
+
+ RTHTTPSERVERCLIENT Client;
+ RT_ZERO(Client);
+
+ Client.pServer = pThis;
+ Client.hSocket = hSocket;
+
+ return rtHttpServerClientMain(&Client);
+}
+
+RTR3DECL(int) RTHttpServerCreate(PRTHTTPSERVER hHttpServer, const char *pszAddress, uint16_t uPort,
+ PRTHTTPSERVERCALLBACKS pCallbacks, void *pvUser, size_t cbUser)
+{
+ AssertPtrReturn(hHttpServer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszAddress, VERR_INVALID_POINTER);
+ AssertReturn (uPort, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER);
+ /* pvUser is optional. */
+
+ int rc;
+
+ PRTHTTPSERVERINTERNAL pThis = (PRTHTTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTHTTPSERVERINTERNAL));
+ if (pThis)
+ {
+ pThis->u32Magic = RTHTTPSERVER_MAGIC;
+ pThis->Callbacks = *pCallbacks;
+ pThis->pvUser = pvUser;
+ pThis->cbUser = cbUser;
+
+ rc = RTTcpServerCreate(pszAddress, uPort, RTTHREADTYPE_DEFAULT, "httpsrv",
+ rtHttpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer);
+ if (RT_SUCCESS(rc))
+ {
+ *hHttpServer = (RTHTTPSERVER)pThis;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+RTR3DECL(int) RTHttpServerDestroy(RTHTTPSERVER hHttpServer)
+{
+ if (hHttpServer == NIL_RTHTTPSERVER)
+ return VINF_SUCCESS;
+
+ PRTHTTPSERVERINTERNAL pThis = hHttpServer;
+ RTHTTPSERVER_VALID_RETURN(pThis);
+
+ AssertPtr(pThis->pTCPServer);
+
+ int rc = VINF_SUCCESS;
+
+ PRTHTTPSERVERCALLBACKS pCallbacks = &pThis->Callbacks;
+ if (pCallbacks->pfnDestroy)
+ {
+ RTHTTPCALLBACKDATA Data = { NULL /* pClient */, pThis->pvUser, pThis->cbUser };
+ rc = pCallbacks->pfnDestroy(&Data);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTTcpServerDestroy(pThis->pTCPServer);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->u32Magic = RTHTTPSERVER_MAGIC_DEAD;
+
+ RTMemFree(pThis);
+ }
+ }
+
+ return rc;
+}