Index: /trunk/include/iprt/ftp.h
===================================================================
--- /trunk/include/iprt/ftp.h	(revision 82722)
+++ /trunk/include/iprt/ftp.h	(revision 82723)
@@ -31,4 +31,5 @@
 #endif
 
+#include <iprt/fs.h>
 #include <iprt/types.h>
 
@@ -130,4 +131,6 @@
     /** Not logged in. */
     RTFTPSERVER_REPLY_NOT_LOGGED_IN                  = 530,
+    /** Requested action not taken. */
+    RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN           = 550,
     /** The usual 32-bit hack. */
     RTFTPSERVER_REPLY_32BIT_HACK                     = 0x7fffffff
@@ -179,4 +182,5 @@
      *
      * @returns VBox status code.
+     * @param   pData           Pointer to generic callback data.
      * @param   pcszUser        User name.
      */
@@ -186,4 +190,5 @@
      *
      * @returns VBox status code.
+     * @param   pData           Pointer to generic callback data.
      * @param   pcszUser        User name to authenticate.
      * @param   pcszPassword    Password to authenticate with.
@@ -194,11 +199,30 @@
      *
      * @returns VBox status code.
-     * @param   pcszUser        User name which disconnected.
+     * @param   pData           Pointer to generic callback data.
      */
     DECLCALLBACKMEMBER(int,  pfnOnUserDisconnect)(PRTFTPCALLBACKDATA pData);
     /**
+     * Callback which gets invoked when the client wants to retrieve the size of a specific file.
+     *
+     * @returns VBox status code.
+     * @param   pData           Pointer to generic callback data.
+     * @param   pcszPath        Path of file to retrieve size for.
+     * @param   puSize          Where to store the file size on success.
+     */
+    DECLCALLBACKMEMBER(int,  pfnOnFileGetSize)(PRTFTPCALLBACKDATA pData, const char *pcszPath, uint64_t *puSize);
+    /**
+     * Callback which gets invoked when the client wants to retrieve information about a file.
+     *
+     * @param   pData           Pointer to generic callback data.
+     * @param   pcszPath        Path of file / directory to "stat". Optional. If NULL, the current directory will be used.
+     * @param   pFsObjInfo      Where to return the RTFSOBJINFO data on success.
+     * @returns VBox status code.
+     */
+    DECLCALLBACKMEMBER(int,  pfnOnFileStat)(PRTFTPCALLBACKDATA pData, const char *pcszPath, PRTFSOBJINFO pFsObjInfo);
+    /**
      * Callback which gets invoked when setting the current working directory.
      *
      * @returns VBox status code.
+     * @param   pData           Pointer to generic callback data.
      * @param   pcszCWD         Current working directory to set.
      */
@@ -208,4 +232,5 @@
      *
      * @returns VBox status code.
+     * @param   pData           Pointer to generic callback data.
      * @param   pszPWD          Where to store the current working directory.
      * @param   cbPWD           Size of buffer in bytes.
@@ -216,4 +241,5 @@
      *
      * @returns VBox status code.
+     * @param   pData           Pointer to generic callback data.
      */
     DECLCALLBACKMEMBER(int,  pfnOnPathUp)(PRTFTPCALLBACKDATA pData);
@@ -221,4 +247,5 @@
      * Callback which gets invoked when the client wants to list a directory or file.
      *
+     * @param   pData           Pointer to generic callback data.
      * @param   pcszPath        Path of file / directory to list. Optional. If NULL, the current directory will be listed.
      * @param   ppvData         Where to return the listing data. Must be free'd by the caller.
Index: /trunk/src/VBox/Runtime/generic/ftp-server.cpp
===================================================================
--- /trunk/src/VBox/Runtime/generic/ftp-server.cpp	(revision 82722)
+++ /trunk/src/VBox/Runtime/generic/ftp-server.cpp	(revision 82723)
@@ -2,4 +2,5 @@
 /** @file
  * Generic FTP server (RFC 959) implementation.
+ * Partly also implements RFC 3659 (Extensions to FTP, for "SIZE", ++).
  */
 
@@ -129,4 +130,6 @@
     /** Recursively gets a directory (and its contents). */
     RTFTPSERVER_CMD_RGET,
+    /** Retrieves the size of a file. */
+    RTFTPSERVER_CMD_SIZE,
     /** Retrieves the current status of a transfer. */
     RTFTPSERVER_CMD_STAT,
@@ -234,4 +237,5 @@
 static FNRTFTPSERVERCMD rtFtpServerHandleRETR;
 static FNRTFTPSERVERCMD rtFtpServerHandleRGET;
+static FNRTFTPSERVERCMD rtFtpServerHandleSIZE;
 static FNRTFTPSERVERCMD rtFtpServerHandleSTAT;
 static FNRTFTPSERVERCMD rtFtpServerHandleSYST;
@@ -269,4 +273,5 @@
     { RTFTPSERVER_CMD_RETR,     "RETR",         rtFtpServerHandleRETR },
     { RTFTPSERVER_CMD_RGET,     "RGET",         rtFtpServerHandleRGET },
+    { RTFTPSERVER_CMD_SIZE,     "SIZE",         rtFtpServerHandleSIZE },
     { RTFTPSERVER_CMD_STAT,     "STAT",         rtFtpServerHandleSTAT },
     { RTFTPSERVER_CMD_SYST,     "SYST",         rtFtpServerHandleSYST },
@@ -301,18 +306,24 @@
  * @returns VBox status code.
  * @param   pClient             Client to reply to.
- * @param   pcszStr             String to reply.
- */
-static int rtFtpServerSendReplyStr(PRTFTPSERVERCLIENT pClient, const char *pcszStr)
-{
-    char *pszReply;
-    int rc = RTStrAPrintf(&pszReply, "%s\r\n", pcszStr);
-    if (RT_SUCCESS(rc))
-    {
-        rc = RTTcpWrite(pClient->hSocket, pszReply, strlen(pszReply) + 1);
-        RTStrFree(pszReply);
-        return rc;
-    }
-
-    return VERR_NO_MEMORY;
+ * @param   pcszFormat          Format to reply.
+ * @param   ...                 Format arguments.
+ */
+static int rtFtpServerSendReplyStr(PRTFTPSERVERCLIENT pClient, const char *pcszFormat, ...)
+{
+    va_list args;
+    va_start(args, pcszFormat);
+    char *psz = NULL;
+    const int cch = RTStrAPrintfV(&psz, pcszFormat, args);
+    va_end(args);
+    AssertReturn(cch > 0, VERR_NO_MEMORY);
+
+    int rc = RTStrAAppend(&psz, "\r\n");
+    AssertRCReturn(rc, rc);
+
+    rc = RTTcpWrite(pClient->hSocket, psz, strlen(psz) + 1 /* Include termination */);
+
+    RTStrFree(psz);
+
+    return rc;
 }
 
@@ -342,4 +353,93 @@
 }
 
+/**
+ * Converts a RTFSOBJINFO struct to a string.
+ *
+ * @returns VBox status code.
+ * @param   pObjInfo            RTFSOBJINFO object to convert.
+ * @param   pszFsObjInfo        Where to store the output string.
+ * @param   cbFsObjInfo         Size of the output string in bytes.
+ */
+static int rtFtpServerFsObjInfoToStr(PRTFSOBJINFO pObjInfo, char *pszFsObjInfo, size_t cbFsObjInfo)
+{
+    RTFMODE fMode = pObjInfo->Attr.fMode;
+    char chFileType;
+    switch (fMode & RTFS_TYPE_MASK)
+    {
+        case RTFS_TYPE_FIFO:        chFileType = 'f'; break;
+        case RTFS_TYPE_DEV_CHAR:    chFileType = 'c'; break;
+        case RTFS_TYPE_DIRECTORY:   chFileType = 'd'; break;
+        case RTFS_TYPE_DEV_BLOCK:   chFileType = 'b'; break;
+        case RTFS_TYPE_FILE:        chFileType = '-'; break;
+        case RTFS_TYPE_SYMLINK:     chFileType = 'l'; break;
+        case RTFS_TYPE_SOCKET:      chFileType = 's'; break;
+        case RTFS_TYPE_WHITEOUT:    chFileType = 'w'; break;
+        default:                    chFileType = '?'; break;
+    }
+
+    char szTimeBirth[RTTIME_STR_LEN];
+    char szTimeChange[RTTIME_STR_LEN];
+    char szTimeModification[RTTIME_STR_LEN];
+    char szTimeAccess[RTTIME_STR_LEN];
+
+#define INFO_TO_STR(a_Format, ...) \
+    do \
+    { \
+        const ssize_t cchSize = RTStrPrintf2(szTemp, sizeof(szTemp), a_Format, __VA_ARGS__); \
+        AssertReturn(cchSize > 0, VERR_BUFFER_OVERFLOW); \
+        const int rc2 = RTStrCat(pszFsObjInfo, cbFsObjInfo, szTemp); \
+        AssertRCReturn(rc2, rc2); \
+    } while (0);
+
+    char szTemp[32];
+
+    INFO_TO_STR("%c", chFileType);
+    INFO_TO_STR("%c%c%c",
+                fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
+                fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
+                fMode & RTFS_UNIX_IXUSR ? 'x' : '-');
+    INFO_TO_STR("%c%c%c",
+                fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
+                fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
+                fMode & RTFS_UNIX_IXGRP ? 'x' : '-');
+    INFO_TO_STR("%c%c%c",
+                fMode & RTFS_UNIX_IROTH ? 'r' : '-',
+                fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
+                fMode & RTFS_UNIX_IXOTH ? 'x' : '-');
+
+    INFO_TO_STR( " %c%c%c%c%c%c%c%c%c%c%c%c%c%c",
+                fMode & RTFS_DOS_READONLY          ? 'R' : '-',
+                fMode & RTFS_DOS_HIDDEN            ? 'H' : '-',
+                fMode & RTFS_DOS_SYSTEM            ? 'S' : '-',
+                fMode & RTFS_DOS_DIRECTORY         ? 'D' : '-',
+                fMode & RTFS_DOS_ARCHIVED          ? 'A' : '-',
+                fMode & RTFS_DOS_NT_DEVICE         ? 'd' : '-',
+                fMode & RTFS_DOS_NT_NORMAL         ? 'N' : '-',
+                fMode & RTFS_DOS_NT_TEMPORARY      ? 'T' : '-',
+                fMode & RTFS_DOS_NT_SPARSE_FILE    ? 'P' : '-',
+                fMode & RTFS_DOS_NT_REPARSE_POINT  ? 'J' : '-',
+                fMode & RTFS_DOS_NT_COMPRESSED     ? 'C' : '-',
+                fMode & RTFS_DOS_NT_OFFLINE        ? 'O' : '-',
+                fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-',
+                fMode & RTFS_DOS_NT_ENCRYPTED      ? 'E' : '-');
+
+    INFO_TO_STR( " %d %4d %4d %10lld %10lld",
+                pObjInfo->Attr.u.Unix.cHardlinks,
+                pObjInfo->Attr.u.Unix.uid,
+                pObjInfo->Attr.u.Unix.gid,
+                pObjInfo->cbObject,
+                pObjInfo->cbAllocated);
+
+    INFO_TO_STR( " %s %s %s %s",
+                RTTimeSpecToString(&pObjInfo->BirthTime,        szTimeBirth,        sizeof(szTimeBirth)),
+                RTTimeSpecToString(&pObjInfo->ChangeTime,       szTimeChange,       sizeof(szTimeChange)),
+                RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)),
+                RTTimeSpecToString(&pObjInfo->AccessTime,       szTimeAccess,       sizeof(szTimeAccess)) );
+
+#undef INFO_TO_STR
+
+    return VINF_SUCCESS;
+}
+
 
 /*********************************************************************************************************************************
@@ -497,10 +597,69 @@
 }
 
+static int rtFtpServerHandleSIZE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
+{
+    if (cArgs != 1)
+        return VERR_INVALID_PARAMETER;
+
+    int rc;
+
+    const char *pcszPath = apcszArgs[0];
+    uint64_t uSize = 0;
+
+    RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileGetSize, pcszPath, &uSize);
+
+    if (RT_SUCCESS(rc))
+    {
+        rc = rtFtpServerSendReplyStr(pClient, "213 %RU64\r\n", uSize);
+    }
+    else
+    {
+        int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
+        AssertRC(rc2);
+    }
+
+    return rc;
+}
+
 static int rtFtpServerHandleSTAT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
 {
-    RT_NOREF(pClient, cArgs, apcszArgs);
-
-    /** @todo Anything to do here? */
-    return VERR_NOT_IMPLEMENTED;
+    if (cArgs != 1)
+        return VERR_INVALID_PARAMETER;
+
+    int rc;
+
+    RTFSOBJINFO objInfo;
+    RT_ZERO(objInfo);
+
+    const char *pcszPath = apcszArgs[0];
+
+    RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pcszPath, &objInfo);
+
+    if (RT_SUCCESS(rc))
+    {
+        char szFsObjInfo[_4K]; /** @todo Check this size. */
+        rc = rtFtpServerFsObjInfoToStr(&objInfo, szFsObjInfo, sizeof(szFsObjInfo));
+        if (RT_SUCCESS(rc))
+        {
+            char szFsPathInfo[RTPATH_MAX + 16];
+            const ssize_t cchPathInfo = RTStrPrintf2(szFsPathInfo, sizeof(szFsPathInfo), " %2zu %s\n", strlen(pcszPath), pcszPath);
+            if (cchPathInfo > 0)
+            {
+                rc = RTStrCat(szFsObjInfo, sizeof(szFsObjInfo), szFsPathInfo);
+                if (RT_SUCCESS(rc))
+                    rc = rtFtpServerSendReplyStr(pClient, szFsObjInfo);
+            }
+            else
+                rc = VERR_BUFFER_OVERFLOW;
+        }
+    }
+
+    if (RT_FAILURE(rc))
+    {
+        int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
+        AssertRC(rc2);
+    }
+
+    return rc;
 }
 
Index: /trunk/src/VBox/Runtime/tools/RTFTPServer.cpp
===================================================================
--- /trunk/src/VBox/Runtime/tools/RTFTPServer.cpp	(revision 82722)
+++ /trunk/src/VBox/Runtime/tools/RTFTPServer.cpp	(revision 82723)
@@ -48,4 +48,5 @@
 #include <iprt/ctype.h>
 #include <iprt/errcore.h>
+#include <iprt/file.h>
 #include <iprt/getopt.h>
 #include <iprt/initterm.h>
@@ -191,4 +192,40 @@
 
     return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) onFileGetSize(PRTFTPCALLBACKDATA pData, const char *pcszPath, uint64_t *puSize)
+{
+    RT_NOREF(pData);
+
+    RTPrintf("Retrieving file size for '%s' ...\n", pcszPath);
+
+    RTFILE hFile;
+    int rc = RTFileOpen(&hFile, pcszPath,
+                        RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+    if (RT_SUCCESS(rc))
+    {
+        rc = RTFileQuerySize(hFile, puSize);
+        if (RT_SUCCESS(rc))
+            RTPrintf("File size is: %RU64\n", *puSize);
+        RTFileClose(hFile);
+    }
+
+    return rc;
+}
+
+static DECLCALLBACK(int) onFileStat(PRTFTPCALLBACKDATA pData, const char *pcszPath, PRTFSOBJINFO pFsObjInfo)
+{
+    RT_NOREF(pData);
+
+    RTFILE hFile;
+    int rc = RTFileOpen(&hFile, pcszPath,
+                        RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+    if (RT_SUCCESS(rc))
+    {
+        rc = RTFileQueryInfo(hFile, pFsObjInfo, RTFSOBJATTRADD_NOTHING);
+        RTFileClose(hFile);
+    }
+
+    return rc;
 }
 
@@ -337,8 +374,9 @@
         Callbacks.pfnOnUserAuthenticate = onUserAuthenticate;
         Callbacks.pfnOnUserDisconnect   = onUserDisonnect;
+        Callbacks.pfnOnFileGetSize      = onFileGetSize;
+        Callbacks.pfnOnFileStat         = onFileStat;
         Callbacks.pfnOnPathSetCurrent   = onPathSetCurrent;
         Callbacks.pfnOnPathGetCurrent   = onPathGetCurrent;
         Callbacks.pfnOnPathUp           = onPathUp;
-        Callbacks.pfnOnList             = onList;
         Callbacks.pfnOnList             = onList;
 
