Index: trunk/include/iprt/mangling.h
===================================================================
--- trunk/include/iprt/mangling.h	(revision 58066)
+++ trunk/include/iprt/mangling.h	(revision 58067)
@@ -1862,4 +1862,5 @@
 # define RTUriCreate                                    RT_MANGLER(RTUriCreate)
 # define RTUriFileCreate                                RT_MANGLER(RTUriFileCreate)
+# define RTUriFileCreateEx                              RT_MANGLER(RTUriFileCreateEx)
 # define RTUriFileNPath                                 RT_MANGLER(RTUriFileNPath)
 # define RTUriFilePath                                  RT_MANGLER(RTUriFilePath)
Index: trunk/include/iprt/uri.h
===================================================================
--- trunk/include/iprt/uri.h	(revision 58066)
+++ trunk/include/iprt/uri.h	(revision 58067)
@@ -275,10 +275,39 @@
  * The returned pointer must be freed using RTStrFree().
  *
- * @see RTUriCreate
- *
  * @returns The new URI on success, NULL otherwise.  Free With RTStrFree.
  * @param   pszPath             The path of the URI.
+ *
+ * @sa      RTUriFileCreateEx, RTUriCreate
  */
 RTDECL(char *) RTUriFileCreate(const char *pszPath);
+
+/**
+ * Creates an file URL for the given path.
+ *
+ * This API works like RTStrToUtf16Ex with regard to result allocation or
+ * buffering (i.e. it's a bit complicated but very flexible).
+ *
+ * @returns iprt status code.
+ * @param   pszPath         The path to convert to a file:// URL.
+ * @param   fPathStyle      The input path style, exactly one of
+ *                          RTPATH_STR_F_STYLE_HOST, RTPATH_STR_F_STYLE_DOS and
+ *                          RTPATH_STR_F_STYLE_UNIX.  Must include iprt/path.h.
+ * @param   ppszUri         If cbUri is non-zero, this must either be pointing
+ *                          to pointer to a buffer of the specified size, or
+ *                          pointer to a NULL pointer.  If *ppszUri is NULL or
+ *                          cbUri is zero a buffer of at least cbUri chars will
+ *                          be allocated to hold the URL.  If a buffer was
+ *                          requested it must be freed using RTStrFree().
+ * @param   cbUri           The buffer size in bytes (includes terminator).
+ * @param   pcchUri         Where to store the length of the URI string,
+ *                          excluding the terminator. (Optional)
+ *
+ *                          This may be set under some error conditions,
+ *                          however, only for VERR_BUFFER_OVERFLOW and
+ *                          VERR_NO_STR_MEMORY will it contain a valid string
+ *                          length that can be used to resize the buffer.
+ * @sa      RTUriCreate, RTUriFileCreate
+ */
+RTDECL(int) RTUriFileCreateEx(const char *pszPath, uint32_t fPathStyle, char **ppszUri, size_t cbUri, size_t *pcchUri);
 
 /**
Index: trunk/src/VBox/Runtime/common/misc/uri.cpp
===================================================================
--- trunk/src/VBox/Runtime/common/misc/uri.cpp	(revision 58066)
+++ trunk/src/VBox/Runtime/common/misc/uri.cpp	(revision 58067)
@@ -116,4 +116,75 @@
 
     return pszRes;
+}
+
+
+/**
+ * Calculates the encoded string length.
+ *
+ * @returns Number of chars (excluding the terminator).
+ * @param   pszString       The string to encode.
+ * @param   cchMax          The maximum string length (e.g. RTSTR_MAX).
+ * @param   fEncodeDosSlash Whether to encode DOS slashes or not.
+ */
+static size_t rtUriCalcEncodedLength(const char *pszString, size_t cchMax, bool fEncodeDosSlash)
+{
+    size_t cchEncoded = 0;
+    if (pszString)
+    {
+        size_t cchSrcLeft = RTStrNLen(pszString, cchMax);
+        while (cchSrcLeft-- > 0)
+        {
+            char const ch = *pszString++;
+            if (!URI_EXCLUDED(ch) || (ch == '\\' && !fEncodeDosSlash))
+                cchEncoded += 1;
+            else
+                cchEncoded += 3;
+        }
+    }
+    return cchEncoded;
+}
+
+
+/**
+ * Encodes an URI into a caller allocated buffer.
+ *
+ * @returns IPRT status code.
+ * @param   pszString       The string to encode.
+ * @param   cchMax          The maximum string length (e.g. RTSTR_MAX).
+ * @param   fEncodeDosSlash Whether to encode DOS slashes or not.
+ * @param   pszDst          The destination buffer.
+ * @param   cbDst           The size of the destination buffer.
+ */
+static int rtUriEncodeIntoBuffer(const char *pszString, size_t cchMax, bool fEncodeDosSlash, char *pszDst, size_t cbDst)
+{
+    AssertReturn(pszString, VERR_INVALID_POINTER);
+
+    /*
+     * We do buffer size checking up front and every time we encode a special
+     * character.  That's faster than checking for each char.
+     */
+    size_t cchSrcLeft = RTStrNLen(pszString, cchMax);
+    AssertMsgReturn(cbDst > cchSrcLeft, ("cbDst=%zu cchSrcLeft=%zu\n", cbDst, cchSrcLeft), VERR_BUFFER_OVERFLOW);
+    cbDst -= cchSrcLeft;
+
+    while (cchSrcLeft-- > 0)
+    {
+        char const ch = *pszString++;
+        if (!URI_EXCLUDED(ch) || (ch == '\\' && !fEncodeDosSlash))
+            *pszDst++ = ch;
+        else
+        {
+            AssertReturn(cbDst >= 3, VERR_BUFFER_OVERFLOW); /* 2 extra bytes + zero terminator. */
+            cbDst -= 2;
+
+            *pszDst++ = '%';
+            ssize_t cchTmp = RTStrFormatU8(pszDst, 3, (unsigned char)ch, 16, 2, 2, RTSTR_F_CAPITAL | RTSTR_F_ZEROPAD);
+            Assert(cchTmp == 2); NOREF(cchTmp);
+            pszDst += 2;
+        }
+    }
+
+    *pszDst = '\0';
+    return VINF_SUCCESS;
 }
 
@@ -677,47 +748,91 @@
 
 
+RTDECL(int) RTUriFileCreateEx(const char *pszPath, uint32_t fPathStyle, char **ppszUri, size_t cbUri, size_t *pcchUri)
+{
+    /*
+     * Validate and adjust input. (RTPathParse check pszPath out for us)
+     */
+    if (pcchUri)
+    {
+        AssertPtrReturn(pcchUri, VERR_INVALID_POINTER);
+        *pcchUri = ~(size_t)0;
+    }
+    AssertPtrReturn(ppszUri, VERR_INVALID_POINTER);
+    AssertReturn(!(fPathStyle & ~RTPATH_STR_F_STYLE_MASK) && fPathStyle != RTPATH_STR_F_STYLE_RESERVED, VERR_INVALID_FLAGS);
+    if (fPathStyle == RTPATH_STR_F_STYLE_HOST)
+        fPathStyle = RTPATH_STYLE;
+
+    /*
+     * Let the RTPath code parse the stuff (no reason to duplicate path parsing
+     * and get it slightly wrong here).
+     */
+    RTPATHPARSED ParsedPath;
+    int rc = RTPathParse(pszPath, &ParsedPath, sizeof(ParsedPath), fPathStyle);
+    if (RT_SUCCESS(rc) || rc == VERR_BUFFER_OVERFLOW)
+    {
+        /* Skip leading slashes. */
+        if (ParsedPath.fProps & RTPATH_PROP_ROOT_SLASH)
+        {
+            if (fPathStyle == RTPATH_STR_F_STYLE_DOS)
+                while (pszPath[0] == '/' || pszPath[0] == '\\')
+                    pszPath++;
+            else
+                while (pszPath[0] == '/')
+                    pszPath++;
+        }
+        const size_t cchPath = strlen(pszPath);
+
+        /*
+         * Calculate the encoded length and figure destination buffering.
+         */
+        static const char s_szPrefix[] = "file:///";
+        size_t const      cchPrefix    = sizeof(s_szPrefix) - (ParsedPath.fProps & RTPATH_PROP_UNC ? 2 : 1);
+        size_t cchEncoded = rtUriCalcEncodedLength(pszPath, cchPath, fPathStyle != RTPATH_STR_F_STYLE_DOS);
+
+        if (pcchUri)
+            *pcchUri = cchEncoded;
+
+        char  *pszDst;
+        char  *pszFreeMe = NULL;
+        if (!cbUri || *ppszUri == NULL)
+        {
+            cbUri = RT_MAX(cbUri, cchPrefix + cchEncoded + 1);
+            *ppszUri = pszFreeMe = pszDst = RTStrAlloc(cbUri);
+            AssertReturn(pszDst, VERR_NO_STR_MEMORY);
+        }
+        else if (cchEncoded < cbUri)
+            pszDst = *ppszUri;
+        else
+            return VERR_BUFFER_OVERFLOW;
+
+        /*
+         * Construct the URI.
+         */
+        memcpy(pszDst, s_szPrefix, cchPrefix);
+        pszDst[cchPrefix] = '\0';
+        rc = rtUriEncodeIntoBuffer(pszPath, cchPath, fPathStyle != RTPATH_STR_F_STYLE_DOS, &pszDst[cchPrefix], cbUri - cchPrefix);
+        if (RT_SUCCESS(rc))
+        {
+            Assert(strlen(pszDst) == cbUri - 1);
+            if (fPathStyle == RTPATH_STR_F_STYLE_DOS)
+                RTPathChangeToUnixSlashes(pszDst, true /*fForce*/);
+            return VINF_SUCCESS;
+        }
+
+        AssertRC(rc); /* Impossible! rtUriCalcEncodedLength or something above is busted! */
+        if (pszFreeMe)
+            RTStrFree(pszFreeMe);
+    }
+    return rc;
+}
+
+
 RTDECL(char *) RTUriFileCreate(const char *pszPath)
 {
-    /* Empty paths are invalid. */
-    AssertPtrReturn(pszPath, NULL);
-    AssertReturn(*pszPath, NULL);
-
-    char *pszResult = NULL;
-    if (pszPath)
-    {
-        /* Check if it's an UNC path. Skip any leading slashes. */
-        while (pszPath)
-        {
-            if (   *pszPath != '\\'
-                && *pszPath != '/')
-                break;
-            pszPath++;
-        }
-
-        /* Create the percent encoded strings and calculate the necessary URI length. */
-        char *pszPath1 = rtUriPercentEncodeN(pszPath, RTSTR_MAX);
-        if (pszPath1)
-        {
-            /* Always change DOS slashes to Unix slashes. */
-            RTPathChangeToUnixSlashes(pszPath1, true); /** @todo Flags? */
-
-            size_t cbSize = 7 /* file:// */ + strlen(pszPath1) + 1; /* plus zero byte */
-            if (pszPath1[0] != '/')
-                ++cbSize;
-            char *pszTmp = pszResult = RTStrAlloc(cbSize);
-            if (pszResult)
-            {
-                /* Compose the target URI string. */
-                *pszTmp = '\0';
-                RTStrCatP(&pszTmp, &cbSize, "file://");
-                if (pszPath1[0] != '/')
-                    RTStrCatP(&pszTmp, &cbSize, "/");
-                RTStrCatP(&pszTmp, &cbSize, pszPath1);
-            }
-            RTStrFree(pszPath1);
-        }
-    }
-
-    return pszResult;
+    char *pszUri = NULL;
+    int rc = RTUriFileCreateEx(pszPath, RTPATH_STR_F_STYLE_HOST, &pszUri, 0 /*cbUri*/, NULL /*pcchUri*/);
+    if (RT_SUCCESS(rc))
+        return pszUri;
+    return NULL;
 }
 
Index: trunk/src/VBox/Runtime/testcase/tstRTUri.cpp
===================================================================
--- trunk/src/VBox/Runtime/testcase/tstRTUri.cpp	(revision 58066)
+++ trunk/src/VBox/Runtime/testcase/tstRTUri.cpp	(revision 58067)
@@ -34,4 +34,5 @@
 #include <iprt/err.h>
 #include <iprt/mem.h>
+#include <iprt/path.h>
 #include <iprt/test.h>
 
@@ -312,10 +313,10 @@
     {   /* #0: */
         /* .pszPath          =*/ "C:\\over\\ <>#%\"{}|^[]`\\there",
-        /* .pszUri           =*/ "file:///C:%5Cover%5C%20%3C%3E%23%25%22%7B%7D%7C%5E%5B%5D%60%5Cthere",
+        /* .pszUri           =*/ "file:///C:%5Cover/%20%3C%3E%23%25%22%7B%7D%7C%5E%5B%5D%60%5Cthere",
         /* .uFormat          =*/ URI_FILE_FORMAT_WIN,
         /* .pszCreatedPath   =*/ "C:\\over\\ <>#%\"{}|^[]`\\there",
-        /* .pszCreatedUri    =*/ "file:///C:%5Cover%5C%20%3C%3E%23%25%22%7B%7D%7C%5E%5B%5D%60%5Cthere",
+        /* .pszCreatedUri    =*/ "file:///C:/over/%20%3C%3E%23%25%22%7B%7D%7C%5E%5B%5D%60/there",
         /* PathCreateFromUrl =   "C:\\over\\ <>#%\"{}|^[]`\\there" - same */
-        /* UrlCreateFromPath =   "file:///C:/over/%20%3C%3E%23%25%22%7B%7D%7C%5E%5B%5D%60/there" - differs */
+        /* UrlCreateFromPath =   "file:///C:/over/%20%3C%3E%23%25%22%7B%7D%7C%5E%5B%5D%60/there" - same */
     },
     {   /* #1: */
@@ -378,7 +379,7 @@
         /* .uFormat          =*/ URI_FILE_FORMAT_WIN,
         /* .pszCreatedPath   =*/ "\\foo\\bar",
-        /* .pszCreatedUri    =*/ "file:///foo%5Cbar",
+        /* .pszCreatedUri    =*/ "file:///foo/bar",
         /* PathCreateFromUrl =   "\\foo\\bar" - same */
-        /* UrlCreateFromPath =   "file:///foo/bar" - differs */
+        /* UrlCreateFromPath =   "file:///foo/bar" - same */
     },
     {   /* #8: */
@@ -393,10 +394,10 @@
     {   /* #9: */
         /* .pszPath          =*/ "\\over\\ <>#%\"{}|^[]`\\there",
-        /* .pszUri           =*/ "file:///over%5C%20%3C%3E%23%25%22%7B%7D%7C%5E%5B%5D%60%5Cthere",
+        /* .pszUri           =*/ "file:///over/%20%3C%3E%23%25%22%7B%7D%7C%5E%5B%5D%60/there",
         /* .uFormat          =*/ URI_FILE_FORMAT_WIN,
         /* .pszCreatedPath   =*/ "\\over\\ <>#%\"{}|^[]`\\there",
-        /* .pszCreatedUri    =*/ "file:///over%5C%20%3C%3E%23%25%22%7B%7D%7C%5E%5B%5D%60%5Cthere",
+        /* .pszCreatedUri    =*/ "file:///over/%20%3C%3E%23%25%22%7B%7D%7C%5E%5B%5D%60/there",
         /* PathCreateFromUrl =   "\\over\\ <>#%\"{}|^[]`\\there" - same */
-        /* UrlCreateFromPath =   "file:///over/%20%3C%3E%23%25%22%7B%7D%7C%5E%5B%5D%60/there" - differs */
+        /* UrlCreateFromPath =   "file:///over/%20%3C%3E%23%25%22%7B%7D%7C%5E%5B%5D%60/there" - same */
     },
     {   /* #10: */
@@ -411,10 +412,10 @@
     {   /* #11: */
         /* .pszPath          =*/ "\\usr\\bin\\grep",
-        /* .pszUri           =*/ "file:///usr%5Cbin%5Cgrep",
+        /* .pszUri           =*/ "file:///usr/bin/grep",
         /* .uFormat          =*/ URI_FILE_FORMAT_WIN,
         /* .pszCreatedPath   =*/ "\\usr\\bin\\grep",
-        /* .pszCreatedUri    =*/ "file:///usr%5Cbin%5Cgrep",
+        /* .pszCreatedUri    =*/ "file:///usr/bin/grep",
         /* PathCreateFromUrl =   "\\usr\\bin\\grep" - same */
-        /* UrlCreateFromPath =   "file:///usr/bin/grep" - differs */
+        /* UrlCreateFromPath =   "file:///usr/bin/grep" - same */
     },
     {   /* #12: */
@@ -429,10 +430,10 @@
     {   /* #13: */
         /* .pszPath          =*/ "\\not-a-cifsserver\\isos\\files.lst",
-        /* .pszUri           =*/ "file:///not-a-cifsserver%5Cisos%5Cfiles.lst",
+        /* .pszUri           =*/ "file:///not-a-cifsserver/isos/files.lst",
         /* .uFormat          =*/ URI_FILE_FORMAT_WIN,
         /* .pszCreatedPath   =*/ "\\not-a-cifsserver\\isos\\files.lst",
-        /* .pszCreatedUri    =*/ "file:///not-a-cifsserver%5Cisos%5Cfiles.lst",
+        /* .pszCreatedUri    =*/ "file:///not-a-cifsserver/isos/files.lst",
         /* PathCreateFromUrl =   "\\not-a-cifsserver\\isos\\files.lst" - same */
-        /* UrlCreateFromPath =   "file:///not-a-cifsserver/isos/files.lst" - differs */
+        /* UrlCreateFromPath =   "file:///not-a-cifsserver/isos/files.lst" - same */
     },
     {   /* #14: */
@@ -447,20 +448,19 @@
     {   /* #15: */
         /* .pszPath          =*/ "\\not-a-cifsserver-either\\isos\\files.lst",
-        /* .pszUri           =*/ "file:///not-a-cifsserver-either%5Cisos%5Cfiles.lst",
+        /* .pszUri           =*/ "file:///not-a-cifsserver-either/isos/files.lst",
         /* .uFormat          =*/ URI_FILE_FORMAT_WIN,
         /* .pszCreatedPath   =*/ "\\not-a-cifsserver-either\\isos\\files.lst",
-        /* .pszCreatedUri    =*/ "file:///not-a-cifsserver-either%5Cisos%5Cfiles.lst",
+        /* .pszCreatedUri    =*/ "file:///not-a-cifsserver-either/isos/files.lst",
         /* PathCreateFromUrl =   "\\not-a-cifsserver-either\\isos\\files.lst" - same */
-        /* UrlCreateFromPath =   "file:///not-a-cifsserver-either/isos/files.lst" - differs */
-    },
-#if 0 /** @todo r=bird: this ain't working right. It's in the wikipedia article on file:// ... */
+        /* UrlCreateFromPath =   "file:///not-a-cifsserver-either/isos/files.lst" - same */
+    },
     {   /* #16: */
         /* .pszPath          =*/ "\\\\cifsserver\\isos\\files.lst",
         /* .pszUri           =*/ "file:////cifsserver/isos/files.lst",
         /* .uFormat          =*/ URI_FILE_FORMAT_WIN,
-        /* .pszCreatedPath   =*/ NULL,    /* Same as pszPath. */
+        /* .pszCreatedPath   =*/ "\\\\cifsserver\\isos\\files.lst",
         /* .pszCreatedUri    =*/ "file://cifsserver/isos/files.lst",
         /* PathCreateFromUrl =   "\\\\cifsserver\\isos\\files.lst" - same */
-        /* UrlCreateFromPath =   "file:////cifsserver/isos/files.lst" - differs */
+        /* UrlCreateFromPath =   "file://cifsserver/isos/files.lst" - same */
     },
     {   /* #17: */
@@ -468,5 +468,5 @@
         /* .pszUri           =*/ "file://localhost/c:boot.ini",
         /* .uFormat          =*/ URI_FILE_FORMAT_WIN,
-        /* .pszCreatedPath   =*/ NULL,    /* Same as pszPath. */
+        /* .pszCreatedPath   =*/ "c:boot.ini",
         /* .pszCreatedUri    =*/ "file:///c:boot.ini",
         /* PathCreateFromUrl =   "c:boot.ini" - same */
@@ -477,10 +477,9 @@
         /* .pszUri           =*/ "file:///c|boot.ini",
         /* .uFormat          =*/ URI_FILE_FORMAT_WIN,
-        /* .pszCreatedPath   =*/ NULL,    /* Same as pszPath. */
+        /* .pszCreatedPath   =*/ "c:boot.ini",
         /* .pszCreatedUri    =*/ "file:///c:boot.ini",
         /* PathCreateFromUrl =   "c:boot.ini" - same */
         /* UrlCreateFromPath =   "file:///c:boot.ini" - same */
     },
-#endif
 };
 
@@ -561,5 +560,5 @@
             RTPrintf("        /* PathCreateFromUrl =   ");
             tstPrintCString(pszResult);
-            if (   g_aCreateFileURIs[i].pszPath
+            if (   g_aCreateFileURIs[i].pszCreatedPath
                 && strcmp(pszResult, g_aCreateFileURIs[i].pszCreatedPath) == 0)
                 RTPrintf(" - same */\n");
@@ -593,5 +592,5 @@
                 RTPrintf("        /* UrlCreateFromPath =   ");
                 tstPrintCString(pszResult);
-                if (   g_aCreateFileURIs[i].pszUri
+                if (   g_aCreateFileURIs[i].pszCreatedUri
                     && strcmp(pszResult, g_aCreateFileURIs[i].pszCreatedUri) == 0)
                     RTPrintf(" - same */\n");
@@ -610,4 +609,35 @@
 
 #endif /* TSTRTURI_WITH_WINDOWS_REFERENCE_RESULTS */
+
+
+static void tstRTUriFileCreateEx(void)
+{
+    RTTestISub("RTUriFileCreateEx");
+    for (size_t i = 0; i < RT_ELEMENTS(g_aCreateFileURIs); ++i)
+    {
+        uint32_t const fPathStyle = g_aCreateFileURIs[i].uFormat == URI_FILE_FORMAT_WIN
+                                  ? RTPATH_STR_F_STYLE_DOS : RTPATH_STR_F_STYLE_UNIX;
+        char *pszUri = (char *)&pszUri;
+        int rc = RTUriFileCreateEx(g_aCreateFileURIs[i].pszPath, fPathStyle, &pszUri, 0, NULL);
+        if (RT_SUCCESS(rc))
+        {
+            if (g_aCreateFileURIs[i].pszCreatedUri)
+            {
+                if (strcmp(pszUri, g_aCreateFileURIs[i].pszCreatedUri) == 0)
+                {
+                    /** @todo check out the other variations of the API. */
+                }
+                else
+                    RTTestIFailed("#%u: '%s'/%#x => '%s', expected '%s'", i,
+                                  g_aCreateFileURIs[i].pszPath, fPathStyle,
+                                  pszUri, g_aCreateFileURIs[i].pszCreatedUri);
+            }
+            else
+                RTTestIFailed("#%u: bad testcase; pszCreatedUri is NULL\n", i);
+        }
+        else if (rc != VERR_INVALID_POINTER || g_aCreateFileURIs[i].pszPath != NULL)
+            RTTestIFailed("#%u: '%s'/%#x => %Rrc", i, g_aCreateFileURIs[i].pszPath, fPathStyle, rc);
+    }
+}
 
 
@@ -672,12 +702,10 @@
     /* File Uri path */
     RTTestISub("RTUriFilePath");
-    for (size_t i = 0; i < RT_ELEMENTS(g_aCreateFileURIs); ++i)
+    for (size_t i = 0; i < RT_ELEMENTS(g_aCreateFileURIs) - 3; ++i) /** @todo r=bird: Fixed last three tests in RTUriFilePath */
         CHECK_STR_API(RTUriFilePath(g_aCreateFileURIs[i].pszUri, g_aCreateFileURIs[i].uFormat),
                       g_aCreateFileURIs[i].pszCreatedPath);
 
     /* File Uri creation */
-    RTTestISub("RTUriFileCreate");
-    for (size_t i = 0; i < RT_ELEMENTS(g_aCreateFileURIs); ++i)
-        CHECK_STR_API(RTUriFileCreate(g_aCreateFileURIs[i].pszPath), g_aCreateFileURIs[i].pszCreatedUri);
+    tstRTUriFileCreateEx();
 
     RTAssertSetMayPanic(fSavedMayPanic);
