Index: trunk/src/VBox/Runtime/r3/http-server.cpp
===================================================================
--- trunk/src/VBox/Runtime/r3/http-server.cpp	(revision 87015)
+++ trunk/src/VBox/Runtime/r3/http-server.cpp	(revision 87016)
@@ -160,5 +160,4 @@
             return pCallbacks->a_Name(&Data, __VA_ARGS__); \
         } \
-        return VERR_NOT_IMPLEMENTED; \
     } while (0)
 
@@ -355,4 +354,71 @@
 }
 
+/**
+ * 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);
+
+    if (cbBody)
+    {
+        pResp->pvBody      = RTMemAlloc(cbBody);
+        AssertPtrReturn(pResp->pvBody, VERR_NO_MEMORY);
+        pResp->cbBodyAlloc = cbBody;
+    }
+    else
+    {
+        pResp->cbBodyAlloc = 0;
+    }
+
+    pResp->cbBodyUsed = 0;
+
+    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);
+
+    if (pResp->pvBody)
+    {
+        Assert(pResp->cbBodyAlloc);
+
+        RTMemFree(pResp->pvBody);
+        pResp->pvBody = NULL;
+    }
+
+    pResp->cbBodyAlloc = 0;
+    pResp->cbBodyUsed  = 0;
+}
+
 
 /*********************************************************************************************************************************
@@ -522,12 +588,18 @@
     int rc;
 
+    /* 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->pszUrl, &fsObj);
+
+    char *pszMIMEHint = NULL;
+
+    RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq->pszUrl, &fsObj, &pszMIMEHint);
     if (RT_FAILURE(rc))
         return rc;
 
-    uint64_t uID = 0; /* Ditto. */
-    RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnOpen, pReq->pszUrl, &uID);
+    void *pvHandle;
+    RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnOpen, pReq->pszUrl, &pvHandle);
 
     if (RT_SUCCESS(rc))
@@ -545,4 +617,7 @@
             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);
@@ -555,6 +630,15 @@
             AssertRCBreak(rc);
 
-            const char *pszMIME = rtHttpServerGuessMIMEType(RTPathSuffix(pReq->pszUrl));
-            rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIME, strlen(pszMIME), RTHTTPHEADERLISTADD_F_BACK);
+            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;
+            }
             AssertRCReturn(rc, rc);
 
@@ -569,5 +653,5 @@
             while (cbToRead)
             {
-                RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRead, uID, pvBuf, RT_MIN(cbBuf, cbToRead), &cbRead);
+                RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRead, pvHandle, pvBuf, RT_MIN(cbBuf, cbToRead), &cbRead);
                 if (RT_FAILURE(rc))
                     break;
@@ -590,5 +674,5 @@
         int rc2 = rc; /* Save rc. */
 
-        RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnClose, uID);
+        RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnClose, pvHandle);
 
         if (RT_FAILURE(rc2)) /* Restore original rc on failure. */
@@ -611,9 +695,15 @@
     LogFlowFuncEnter();
 
+    /* If a low-level HEAD request handler is defined, call it and return. */
+    RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnHeadRequest, pReq);
+
     int rc;
 
     RTFSOBJINFO fsObj;
     RT_ZERO(fsObj); /* Shut up MSVC. */
-    RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq->pszUrl, &fsObj);
+
+    char *pszMIMEHint = NULL;
+
+    RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq->pszUrl, &fsObj, &pszMIMEHint);
     if (RT_SUCCESS(rc))
     {
Index: trunk/src/VBox/Runtime/tools/RTHttpServer.cpp
===================================================================
--- trunk/src/VBox/Runtime/tools/RTHttpServer.cpp	(revision 87015)
+++ trunk/src/VBox/Runtime/tools/RTHttpServer.cpp	(revision 87016)
@@ -69,11 +69,12 @@
     /** The absolute path of the HTTP server's root directory. */
     char szPathRootAbs[RTPATH_MAX];
-    /** The relative current working directory (CWD) to szRootDir. */
-    char szCWD[RTPATH_MAX];
+    RTFMODE      fMode;
     union
     {
-        RTFILE File;
-        RTDIR Dir;
+        RTFILE   File;
+        RTVFSDIR Dir;
     } h;
+    /** Cached response data. */
+    RTHTTPSERVERRESP Resp;
 } HTTPSERVERDATA;
 typedef HTTPSERVERDATA *PHTTPSERVERDATA;
@@ -289,4 +290,8 @@
         }
 
+        /* Skip dot directories. */
+        if (RTDirEntryExIsStdDotLink(pDirEntry))
+            continue;
+
         *ppszEntry = RTStrDup(pDirEntry->szName);
         AssertPtrReturn(*ppszEntry, VERR_NO_MEMORY);
@@ -305,93 +310,38 @@
 
 static int dirEntryWrite(char *pszBuf, size_t cbBuf,
-                         const char *pszEntry, const PRTFSOBJINFO pInfo, size_t *pcbWritten)
-{
-    RT_NOREF(pInfo);
-
-    ssize_t cch = RTStrPrintf2(pszBuf, cbBuf, "201: %s\r\n", pszEntry);
+                         const char *pszEntry, const PRTFSOBJINFO pObjInfo, size_t *pcbWritten)
+{
+    char szModTime[32];
+    if (RTTimeSpecToString(&pObjInfo->ModificationTime, szModTime, sizeof(szModTime)) == NULL)
+        return VERR_BUFFER_UNDERFLOW;
+
+    int rc = VINF_SUCCESS;
+
+    ssize_t cch = RTStrPrintf2(pszBuf, cbBuf, "201: %s %RU64 %s %s\r\n",
+                               pszEntry, pObjInfo->cbObject, szModTime,
+                               /** @todo Very crude; only files and directories are supported for now. */
+                               RTFS_IS_FILE(pObjInfo->Attr.fMode) ? "FILE" : "DIRECTORY");
     if (cch <= 0)
-        return VERR_BUFFER_OVERFLOW;
-
-    /*
-    Content-type:
-    Last-Modified:
-    Content-Length:
-    */
-
-    *pcbWritten = (size_t)cch;
-
-    return VINF_SUCCESS;
-}
-
-static DECLCALLBACK(int) onGetRequest(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq)
-{
-    PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
-    Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
-
-    /* Construct absolute path. */
-    char *pszPathAbs = NULL;
-    if (RTStrAPrintf(&pszPathAbs, "%s/%s", pThis->szPathRootAbs, pReq->pszUrl) <= 0)
-        return VERR_NO_MEMORY;
-
-    RTVFSDIR hVfsDir;
-    int rc = dirOpen(pszPathAbs, &hVfsDir);
+        rc = VERR_BUFFER_OVERFLOW;
+
     if (RT_SUCCESS(rc))
     {
-        pReq->pvBody      = RTMemAlloc(_4K);
-        AssertPtrReturn(pReq->pvBody, VERR_NO_MEMORY); /** @todo Leaks stuff. */
-        pReq->cbBodyAlloc = _4K;
-        pReq->cbBodyUsed  =  0;
-
-        char *pszEntry  = NULL;
-        RTFSOBJINFO fsObjInfo;
-
-        while (RT_SUCCESS(rc = dirRead(hVfsDir, &pszEntry, &fsObjInfo)))
-        {
-            char *pszBody = (char *)pReq->pvBody;
-
-            size_t cbWritten;
-            rc = dirEntryWrite(&pszBody[pReq->cbBodyUsed], pReq->cbBodyAlloc - pReq->cbBodyUsed, pszEntry, &fsObjInfo, &cbWritten);
-            if (rc == VERR_BUFFER_OVERFLOW)
-            {
-                pReq->cbBodyAlloc *= 2; /** @todo Improve this. */
-                pReq->pvBody       = RTMemRealloc(pReq->pvBody, pReq->cbBodyAlloc);
-                AssertPtrBreakStmt(pReq->pvBody, rc = VERR_NO_MEMORY);
-
-                pszBody = (char *)pReq->pvBody;
-
-                rc = dirEntryWrite(&pszBody[pReq->cbBodyUsed], pReq->cbBodyAlloc - pReq->cbBodyUsed, pszEntry, &fsObjInfo, &cbWritten);
-            }
-
-            if (RT_SUCCESS(rc))
-                pReq->cbBodyUsed += cbWritten;
-
-            RTStrFree(pszEntry);
-
-            if (RT_FAILURE(rc))
-                break;
-        }
-
-        if (rc == VERR_NO_MORE_FILES) /* All entries consumed? */
-            rc = VINF_SUCCESS;
-
-        dirClose(hVfsDir);
-    }
-
-    RTStrFree(pszPathAbs);
+        *pcbWritten = (size_t)cch;
+    }
+
     return rc;
 }
 
-static DECLCALLBACK(int) onHeadRequest(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq)
-{
-    RT_NOREF(pData, pReq);
-
-    return VINF_SUCCESS;
-}
-
-DECLCALLBACK(int) onOpen(PRTHTTPCALLBACKDATA pData, const char *pszUrl, uint64_t *pidObj)
-{
-    PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
-    Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
-
+/**
+ * Resolves (and validates) a given URL to an absolute (local) path.
+ *
+ * @returns VBox status code.
+ * @param   pThis               HTTP server instance data.
+ * @param   pszUrl              URL to resolve.
+ * @param   ppszPathAbs         Where to store the resolved absolute path on success.
+ *                              Needs to be free'd with RTStrFree().
+ */
+static int pathResolve(PHTTPSERVERDATA pThis, const char *pszUrl, char **ppszPathAbs)
+{
     /* Construct absolute path. */
     char *pszPathAbs = NULL;
@@ -399,55 +349,198 @@
         return VERR_NO_MEMORY;
 
-    int rc = RTFileOpen(&pThis->h.File, pszPathAbs,
-                        RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
-    if (RT_SUCCESS(rc))
-        *pidObj = 42;
-
-    RTStrFree(pszPathAbs);
-    return rc;
-}
-
-DECLCALLBACK(int) onRead(PRTHTTPCALLBACKDATA pData, uint64_t idObj, void *pvBuf, size_t cbBuf, size_t *pcbRead)
+#ifdef VBOX_STRICT
+    RTFSOBJINFO objInfo;
+    int rc2 = RTPathQueryInfo(pszPathAbs, &objInfo, RTFSOBJATTRADD_NOTHING);
+    AssertRCReturn(rc, rc);
+    AssertReturn(!RTFS_IS_SYMLINK(objInfo.Attr.fMode), VERR_NOT_SUPPORTED);
+#endif
+
+    *ppszPathAbs = pszPathAbs;
+
+    return VINF_SUCCESS;
+}
+
+DECLCALLBACK(int) onOpen(PRTHTTPCALLBACKDATA pData, const char *pszUrl, void **ppvHandle)
 {
     PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
     Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
 
-    AssertReturn(idObj == 42, VERR_NOT_FOUND);
-
-    return RTFileRead(pThis->h.File, pvBuf, cbBuf, pcbRead);
-}
-
-DECLCALLBACK(int) onClose(PRTHTTPCALLBACKDATA pData, uint64_t idObj)
+    char *pszPathAbs = NULL;
+    int rc = pathResolve(pThis, pszUrl, &pszPathAbs);
+    if (RT_SUCCESS(rc))
+    {
+        RTFSOBJINFO objInfo;
+        rc = RTPathQueryInfo(pszPathAbs, &objInfo, RTFSOBJATTRADD_NOTHING);
+        AssertRCReturn(rc, rc);
+        if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+        {
+            /* Nothing to do here;
+             * The directory listing has been cached already in onQueryInfo(). */
+        }
+        else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+        {
+            rc = RTFileOpen(&pThis->h.File, pszPathAbs,
+                            RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+        }
+
+        if (RT_SUCCESS(rc))
+        {
+            pThis->fMode = objInfo.Attr.fMode;
+
+             uint64_t *puHandle = (uint64_t *)RTMemAlloc(sizeof(uint64_t));
+             *puHandle  = 42; /** @todo Fudge. */
+             *ppvHandle = puHandle;
+        }
+
+        RTStrFree(pszPathAbs);
+    }
+    return rc;
+}
+
+DECLCALLBACK(int) onRead(PRTHTTPCALLBACKDATA pData, void *pvHandle, void *pvBuf, size_t cbBuf, size_t *pcbRead)
 {
     PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
     Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
 
-    AssertReturn(idObj == 42, VERR_NOT_FOUND);
-
-    int rc = RTFileClose(pThis->h.File);
-    if (RT_SUCCESS(rc))
-        pThis->h.File = NIL_RTFILE;
-
-    return rc;
-}
-
-DECLCALLBACK(int) onQueryInfo(PRTHTTPCALLBACKDATA pData, const char *pszUrl, PRTFSOBJINFO pObjInfo)
+    AssertReturn(*(uint64_t *)pvHandle == 42 /** @todo Fudge. */, VERR_NOT_FOUND);
+
+    if (RTFS_IS_DIRECTORY(pThis->fMode))
+    {
+        PRTHTTPSERVERRESP pResp = &pThis->Resp;
+
+        const size_t cbToCopy = RT_MIN(cbBuf, pResp->cbBodyUsed);
+        memcpy(pvBuf, pResp->pvBody, cbToCopy);
+        Assert(pResp->cbBodyUsed >= cbToCopy);
+        pResp->cbBodyUsed -= cbToCopy;
+
+        *pcbRead = cbToCopy;
+
+        return VINF_SUCCESS;
+    }
+    else if (RTFS_IS_FILE(pThis->fMode))
+        return RTFileRead(pThis->h.File, pvBuf, cbBuf, pcbRead);
+
+    return VERR_NOT_IMPLEMENTED; /* Never reached. */
+}
+
+DECLCALLBACK(int) onClose(PRTHTTPCALLBACKDATA pData, void *pvHandle)
 {
     PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
     Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
 
-    /* Construct absolute path. */
+    AssertReturn(*(uint64_t *)pvHandle == 42 /** @todo Fudge. */, VERR_NOT_FOUND);
+
+    int rc;
+
+    if (RTFS_IS_FILE(pThis->fMode))
+    {
+        rc = RTFileClose(pThis->h.File);
+        if (RT_SUCCESS(rc))
+            pThis->h.File = NIL_RTFILE;
+    }
+    else
+        rc = VINF_SUCCESS;
+
+    RTMemFree(pvHandle);
+    pvHandle = NULL;
+
+    return rc;
+}
+
+DECLCALLBACK(int) onQueryInfo(PRTHTTPCALLBACKDATA pData,
+                              const char *pszUrl, PRTFSOBJINFO pObjInfo, char **ppszMIMEHint)
+{
+    PHTTPSERVERDATA pThis = (PHTTPSERVERDATA)pData->pvUser;
+    Assert(pData->cbUser == sizeof(HTTPSERVERDATA));
+
     char *pszPathAbs = NULL;
-    if (RTStrAPrintf(&pszPathAbs, "%s/%s", pThis->szPathRootAbs, pszUrl) <= 0)
-        return VERR_NO_MEMORY;
-
-    RTFILE hFile;
-    int rc = RTFileOpen(&hFile, pszPathAbs,
-                        RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+    int rc = pathResolve(pThis, pszUrl, &pszPathAbs);
     if (RT_SUCCESS(rc))
     {
-        rc = RTFileQueryInfo(hFile, pObjInfo, RTFSOBJATTRADD_NOTHING);
-
-        RTFileClose(hFile);
+        RTFSOBJINFO objInfo;
+        rc = RTPathQueryInfo(pszPathAbs, &objInfo, RTFSOBJATTRADD_NOTHING);
+        if (RT_SUCCESS(rc))
+        {
+            if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+            {
+                PRTHTTPSERVERRESP pResp = &pThis->Resp;
+
+                RTVFSDIR hVfsDir;
+                rc = dirOpen(pszPathAbs, &hVfsDir);
+                if (RT_SUCCESS(rc))
+                {
+                    RTHttpServerResponseDestroy(pResp);
+                    RTHttpServerResponseInitEx(pResp, _4K);
+
+                    /*
+                     * Write body header.
+                     */
+                    ssize_t cch = RTStrPrintf2((char *)pResp->pvBody, pResp->cbBodyAlloc,
+                                               "300: file://%s\r\n"
+                                               "200: filename content-length last-modified file-type\r\n",
+                                               pszUrl);
+                    Assert(cch);
+
+                    pResp->cbBodyUsed = strlen((char *)pResp->pvBody);
+
+                    /*
+                     * Write body entries.
+                     */
+                    char       *pszEntry = NULL;
+                    RTFSOBJINFO fsObjInfo;
+                    while (RT_SUCCESS(rc = dirRead(hVfsDir, &pszEntry, &fsObjInfo)))
+                    {
+                        char *pszBody = (char *)pResp->pvBody;
+
+                        size_t cbWritten;
+                        rc = dirEntryWrite(&pszBody[pResp->cbBodyUsed], pResp->cbBodyAlloc - pResp->cbBodyUsed, pszEntry, &fsObjInfo, &cbWritten);
+                        if (rc == VERR_BUFFER_OVERFLOW)
+                        {
+                            pResp->cbBodyAlloc *= 2; /** @todo Improve this. */
+                            pResp->pvBody       = RTMemRealloc(pResp->pvBody, pResp->cbBodyAlloc);
+                            AssertPtrBreakStmt(pResp->pvBody, rc = VERR_NO_MEMORY);
+
+                            pszBody = (char *)pResp->pvBody;
+
+                            rc = dirEntryWrite(&pszBody[pResp->cbBodyUsed], pResp->cbBodyAlloc - pResp->cbBodyUsed, pszEntry, &fsObjInfo, &cbWritten);
+                        }
+
+                        if (RT_SUCCESS(rc))
+                            pResp->cbBodyUsed += cbWritten;
+
+                        RTStrFree(pszEntry);
+
+                        if (RT_FAILURE(rc))
+                            break;
+                    }
+
+                    if (rc == VERR_NO_MORE_FILES) /* All entries consumed? */
+                        rc = VINF_SUCCESS;
+
+                    dirClose(hVfsDir);
+
+                    if (RT_SUCCESS(rc))
+                    {
+                        rc = RTStrAPrintf(ppszMIMEHint, "text/plain");
+                        if (RT_SUCCESS(rc))
+                            pObjInfo->cbObject = pResp->cbBodyUsed;
+                    }
+                }
+            }
+            else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+            {
+                RTFILE hFile;
+                rc = RTFileOpen(&hFile, pszPathAbs,
+                                RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+                if (RT_SUCCESS(rc))
+                {
+                    rc = RTFileQueryInfo(hFile, pObjInfo, RTFSOBJATTRADD_NOTHING);
+
+                    RTFileClose(hFile);
+                }
+            }
+        }
+
+        RTStrFree(pszPathAbs);
     }
 
@@ -542,7 +635,4 @@
     }
 
-    /* Initialize CWD. */
-    RTStrPrintf2(g_HttpServerData.szCWD, sizeof(g_HttpServerData.szCWD), "/");
-
     /* Install signal handler. */
     rc = signalHandlerInstall();
@@ -559,9 +649,10 @@
         Callbacks.pfnClose         = onClose;
         Callbacks.pfnQueryInfo     = onQueryInfo;
-        Callbacks.pfnOnGetRequest  = onGetRequest;
-        Callbacks.pfnOnHeadRequest = onHeadRequest;
 
         g_HttpServerData.h.File = NIL_RTFILE;
-        g_HttpServerData.h.Dir  = NIL_RTDIR;
+        g_HttpServerData.h.Dir  = NIL_RTVFSDIR;
+
+        rc = RTHttpServerResponseInit(&g_HttpServerData.Resp);
+        AssertRC(rc);
 
         RTHTTPSERVER hHTTPServer;
