Sync with trunk r63174.
[reactos.git] / dll / win32 / urlmon / uri.c
index f12f826..052af22 100644 (file)
  */
 
 #include "urlmon_main.h"
-#include "wine/debug.h"
 
-#define NO_SHLWAPI_REG
-#include "shlwapi.h"
-
-#include "strsafe.h"
-
-#define UINT_MAX 0xffffffff
-#define USHORT_MAX 0xffff
+#include <strsafe.h>
 
 #define URI_DISPLAY_NO_ABSOLUTE_URI         0x1
 #define URI_DISPLAY_NO_DEFAULT_PORT_AUTH    0x2
 
 #define COMBINE_URI_FORCE_FLAG_USE  0x1
 
-WINE_DEFAULT_DEBUG_CHANNEL(urlmon);
-
 static const IID IID_IUriObj = {0x4b364760,0x9f51,0x11df,{0x98,0x1c,0x08,0x00,0x20,0x0c,0x9a,0x66}};
 
 typedef struct {
     IUri                IUri_iface;
     IUriBuilderFactory  IUriBuilderFactory_iface;
+    IPersistStream      IPersistStream_iface;
+    IMarshal            IMarshal_iface;
 
     LONG ref;
 
@@ -155,6 +148,7 @@ typedef struct {
     BOOL            has_implicit_scheme;
     BOOL            has_implicit_ip;
     UINT            implicit_ipv4;
+    BOOL            must_have_path;
 
     const WCHAR     *scheme;
     DWORD           scheme_len;
@@ -233,7 +227,7 @@ static const struct {
     {URL_SCHEME_HTTPS,  443},
 };
 
-/* List of 3 character top level domain names Windows seems to recognize.
+/* List of 3-character top level domain names Windows seems to recognize.
  * There might be more, but, these are the only ones I've found so far.
  */
 static const struct {
@@ -270,7 +264,7 @@ static inline BOOL is_drive_path(const WCHAR *str) {
 }
 
 static inline BOOL is_unc_path(const WCHAR *str) {
-    return (str[0] == '\\' && str[0] == '\\');
+    return (str[0] == '\\' && str[1] == '\\');
 }
 
 static inline BOOL is_forbidden_dos_path_char(WCHAR val) {
@@ -278,7 +272,7 @@ static inline BOOL is_forbidden_dos_path_char(WCHAR val) {
 }
 
 /* A URI is implicitly a file path if it begins with
- * a drive letter (eg X:) or starts with "\\" (UNC path).
+ * a drive letter (e.g. X:) or starts with "\\" (UNC path).
  */
 static inline BOOL is_implicit_file_path(const WCHAR *str) {
     return (is_unc_path(str) || (is_alpha(str[0]) && str[1] == ':'));
@@ -328,7 +322,7 @@ static inline BOOL is_gendelim(WCHAR val) {
 
 /* Characters that delimit the end of the authority
  * section of a URI. Sometimes a '\\' is considered
- * an authority delimeter.
+ * an authority delimiter.
  */
 static inline BOOL is_auth_delim(WCHAR val, BOOL acceptSlash) {
     return (val == '#' || val == '/' || val == '?' ||
@@ -403,12 +397,12 @@ static void apply_default_flags(DWORD *flags) {
 /* Determines if the URI is hierarchical using the information already parsed into
  * data and using the current location of parsing in the URI string.
  *
- * Windows considers a URI hierarchical if on of the following is true:
+ * Windows considers a URI hierarchical if one of the following is true:
  *  A.) It's a wildcard scheme.
  *  B.) It's an implicit file scheme.
  *  C.) It's a known hierarchical scheme and it has two '\\' after the scheme name.
  *      (the '\\' will be converted into "//" during canonicalization).
- *  D.) It's not a relative URI and "//" appears after the scheme name.
+ *  D.) "//" appears after the scheme name (or at the beginning if no scheme is given).
  */
 static inline BOOL is_hierarchical_uri(const WCHAR **ptr, const parse_data *data) {
     const WCHAR *start = *ptr;
@@ -420,114 +414,16 @@ static inline BOOL is_hierarchical_uri(const WCHAR **ptr, const parse_data *data
     else if(is_hierarchical_scheme(data->scheme_type) && (*ptr)[0] == '\\' && (*ptr)[1] == '\\') {
         *ptr += 2;
         return TRUE;
-    } else if(!data->is_relative && check_hierarchical(ptr))
+    } else if(data->scheme_type != URL_SCHEME_MAILTO && check_hierarchical(ptr))
         return TRUE;
 
     *ptr = start;
     return FALSE;
 }
 
-/* Checks if the two Uri's are logically equivalent. It's a simple
- * comparison, since they are both of type Uri, and it can access
- * the properties of each Uri directly without the need to go
- * through the "IUri_Get*" interface calls.
- */
-static BOOL are_equal_simple(const Uri *a, const Uri *b) {
-    if(a->scheme_type == b->scheme_type) {
-        const BOOL known_scheme = a->scheme_type != URL_SCHEME_UNKNOWN;
-        const BOOL are_hierarchical =
-                (a->authority_start > -1 && b->authority_start > -1);
-
-        if(a->scheme_type == URL_SCHEME_FILE) {
-            if(a->canon_len == b->canon_len)
-                return !StrCmpIW(a->canon_uri, b->canon_uri);
-        }
-
-        /* Only compare the scheme names (if any) if their unknown scheme types. */
-        if(!known_scheme) {
-            if((a->scheme_start > -1 && b->scheme_start > -1) &&
-               (a->scheme_len == b->scheme_len)) {
-                /* Make sure the schemes are the same. */
-                if(StrCmpNW(a->canon_uri+a->scheme_start, b->canon_uri+b->scheme_start, a->scheme_len))
-                    return FALSE;
-            } else if(a->scheme_len != b->scheme_len)
-                /* One of the Uri's has a scheme name, while the other doesn't. */
-                return FALSE;
-        }
-
-        /* If they have a userinfo component, perform case sensitive compare. */
-        if((a->userinfo_start > -1 && b->userinfo_start > -1) &&
-           (a->userinfo_len == b->userinfo_len)) {
-            if(StrCmpNW(a->canon_uri+a->userinfo_start, b->canon_uri+b->userinfo_start, a->userinfo_len))
-                return FALSE;
-        } else if(a->userinfo_len != b->userinfo_len)
-            /* One of the Uri's had a userinfo, while the other one doesn't. */
-            return FALSE;
-
-        /* Check if they have a host name. */
-        if((a->host_start > -1 && b->host_start > -1) &&
-           (a->host_len == b->host_len)) {
-            /* Perform a case insensitive compare if they are a known scheme type. */
-            if(known_scheme) {
-                if(StrCmpNIW(a->canon_uri+a->host_start, b->canon_uri+b->host_start, a->host_len))
-                    return FALSE;
-            } else if(StrCmpNW(a->canon_uri+a->host_start, b->canon_uri+b->host_start, a->host_len))
-                return FALSE;
-        } else if(a->host_len != b->host_len)
-            /* One of the Uri's had a host, while the other one didn't. */
-            return FALSE;
-
-        if(a->has_port && b->has_port) {
-            if(a->port != b->port)
-                return FALSE;
-        } else if(a->has_port || b->has_port)
-            /* One had a port, while the other one didn't. */
-            return FALSE;
-
-        /* Windows is weird with how it handles paths. For example
-         * One URI could be "http://google.com" (after canonicalization)
-         * and one could be "http://google.com/" and the IsEqual function
-         * would still evaluate to TRUE, but, only if they are both hierarchical
-         * URIs.
-         */
-        if((a->path_start > -1 && b->path_start > -1) &&
-           (a->path_len == b->path_len)) {
-            if(StrCmpNW(a->canon_uri+a->path_start, b->canon_uri+b->path_start, a->path_len))
-                return FALSE;
-        } else if(are_hierarchical && a->path_len == -1 && b->path_len == 0) {
-            if(*(a->canon_uri+a->path_start) != '/')
-                return FALSE;
-        } else if(are_hierarchical && b->path_len == 1 && a->path_len == 0) {
-            if(*(b->canon_uri+b->path_start) != '/')
-                return FALSE;
-        } else if(a->path_len != b->path_len)
-            return FALSE;
-
-        /* Compare the query strings of the two URIs. */
-        if((a->query_start > -1 && b->query_start > -1) &&
-           (a->query_len == b->query_len)) {
-            if(StrCmpNW(a->canon_uri+a->query_start, b->canon_uri+b->query_start, a->query_len))
-                return FALSE;
-        } else if(a->query_len != b->query_len)
-            return FALSE;
-
-        if((a->fragment_start > -1 && b->fragment_start > -1) &&
-           (a->fragment_len == b->fragment_len)) {
-            if(StrCmpNW(a->canon_uri+a->fragment_start, b->canon_uri+b->fragment_start, a->fragment_len))
-                return FALSE;
-        } else if(a->fragment_len != b->fragment_len)
-            return FALSE;
-
-        /* If we get here, the two URIs are equivalent. */
-        return TRUE;
-    }
-
-    return FALSE;
-}
-
 /* Computes the size of the given IPv6 address.
- * Each h16 component is 16bits, if there is an IPv4 address, it's
- * 32bits. If there's an elision it can be 16bits to 128bits, depending
+ * Each h16 component is 16 bits. If there is an IPv4 address, it's
+ * 32 bits. If there's an elision it can be 16 to 128 bits, depending
  * on the number of other components.
  *
  * Modeled after google-url's CheckIPv6ComponentsSize function
@@ -541,7 +437,7 @@ static void compute_ipv6_comps_size(ipv6_address *address) {
 
     if(address->elision) {
         /* An elision can be anywhere from 2 bytes up to 16 bytes.
-         * It size depends on the size of the h16 and IPv4 components.
+         * Its size depends on the size of the h16 and IPv4 components.
          */
         address->elision_size = 16 - address->components_size;
         if(address->elision_size < 2)
@@ -567,7 +463,7 @@ static int hex_to_int(WCHAR val) {
  * the two characters following the '%' aren't valid hex values then
  * this function returns the NULL character.
  *
- * Eg.
+ * E.g.
  *  "%2E" will result in '.' being returned by this function.
  */
 static WCHAR decode_pct_val(const WCHAR *ptr) {
@@ -616,7 +512,7 @@ void find_domain_name(const WCHAR *host, DWORD host_len,
     *domain_start = -1;
 
     /* There has to be at least enough room for a '.' followed by a
-     * 3 character TLD for a domain to even exist in the host name.
+     * 3-character TLD for a domain to even exist in the host name.
      */
     if(host_len < 4)
         return;
@@ -640,7 +536,7 @@ void find_domain_name(const WCHAR *host, DWORD host_len,
         } else if(last_tld-host == 3) {
             DWORD i;
 
-            /* If there's three characters in front of last_tld and
+            /* If there are three characters in front of last_tld and
              * they are on the list of recognized TLDs, then this
              * host doesn't have a domain (since the host only contains
              * a TLD name.
@@ -670,7 +566,7 @@ void find_domain_name(const WCHAR *host, DWORD host_len,
         DWORD i;
         /* If the sec_last_tld is 3 characters long it HAS to be on the list of
          * recognized to still be considered part of the TLD name, otherwise
-         * its considered the domain name.
+         * it's considered the domain name.
          *  Ex: www.google.com.uk -> google.com.uk as the domain name.
          *      www.google.foo.uk -> foo.uk as the domain name.
          */
@@ -789,12 +685,12 @@ static INT find_file_extension(const WCHAR *path, DWORD path_len) {
 /* Computes the location where the elision should occur in the IPv6
  * address using the numerical values of each component stored in
  * 'values'. If the address shouldn't contain an elision then 'index'
- * is assigned -1 as it's value. Otherwise 'index' will contain the
+ * is assigned -1 as its value. Otherwise 'index' will contain the
  * starting index (into values) where the elision should be, and 'count'
  * will contain the number of cells the elision covers.
  *
  * NOTES:
- *  Windows will expand an elision if the elision only represents 1 h16
+ *  Windows will expand an elision if the elision only represents one h16
  *  component of the address.
  *
  *  Ex: [1::2:3:4:5:6:7] -> [1:0:2:3:4:5:6:7]
@@ -857,35 +753,35 @@ static void compute_elision_location(const ipv6_address *address, const USHORT v
  * characters inside of the URI string.
  */
 static BSTR pre_process_uri(LPCWSTR uri) {
-    BSTR ret;
+    const WCHAR *start, *end, *ptr;
+    WCHAR *ptr2;
     DWORD len;
-    const WCHAR *start, *end;
-    WCHAR *buf, *ptr;
-
-    len = lstrlenW(uri);
+    BSTR ret;
 
     start = uri;
     /* Skip leading controls and whitespace. */
-    while(iscntrlW(*start) || isspaceW(*start)) ++start;
+    while(*start && (iscntrlW(*start) || isspaceW(*start))) ++start;
 
-    end = uri+len-1;
-    if(start == end)
-        /* URI consisted only of control/whitespace. */
-        ret = SysAllocStringLen(NULL, 0);
-    else {
-        while(iscntrlW(*end) || isspaceW(*end)) --end;
+    /* URI consisted only of control/whitespace. */
+    if(!*start)
+        return SysAllocStringLen(NULL, 0);
 
-        buf = heap_alloc(((end+1)-start)*sizeof(WCHAR));
-        if(!buf)
-            return NULL;
+    end = start + strlenW(start);
+    while(--end > start && (iscntrlW(*end) || isspaceW(*end)));
 
-        for(ptr = buf; start < end+1; ++start) {
-            if(!iscntrlW(*start))
-                *ptr++ = *start;
-        }
+    len = ++end - start;
+    for(ptr = start; ptr < end; ptr++) {
+        if(iscntrlW(*ptr))
+            len--;
+    }
+
+    ret = SysAllocStringLen(NULL, len);
+    if(!ret)
+        return NULL;
 
-        ret = SysAllocStringLen(buf, ptr-buf);
-        heap_free(buf);
+    for(ptr = start, ptr2=ret; ptr < end; ptr++) {
+        if(!iscntrlW(*ptr))
+            *ptr2++ = *ptr;
     }
 
     return ret;
@@ -915,7 +811,7 @@ static UINT ipv4toui(const WCHAR *ip, DWORD len) {
     return ret;
 }
 
-/* Converts an IPv4 address in numerical form into it's fully qualified
+/* Converts an IPv4 address in numerical form into its fully qualified
  * string form. This function returns the number of characters written
  * to 'dest'. If 'dest' is NULL this function will return the number of
  * characters that would have been written.
@@ -956,7 +852,7 @@ static DWORD ui2str(WCHAR *dest, UINT value) {
     return ret;
 }
 
-/* Converts an h16 component (from an IPv6 address) into it's
+/* Converts a h16 component (from an IPv6 address) into its
  * numerical value.
  *
  * This function assumes that the h16 component has already been validated.
@@ -987,7 +883,7 @@ static BOOL ipv6_to_number(const ipv6_address *address, USHORT number[8]) {
                 /* Means we just passed the elision and need to add its values to
                  * 'number' before we do anything else.
                  */
-                DWORD j = 0;
+                INT j;
                 for(j = 0; j < address->elision_size; j+=2)
                     number[cur_component++] = 0;
 
@@ -1000,7 +896,8 @@ static BOOL ipv6_to_number(const ipv6_address *address, USHORT number[8]) {
 
     /* Case when the elision appears after the h16 components. */
     if(!already_passed_elision && address->elision) {
-        for(i = 0; i < address->elision_size; i+=2)
+        INT j;
+        for(j = 0; j < address->elision_size; j+=2)
             number[cur_component++] = 0;
     }
 
@@ -1063,7 +960,7 @@ static BOOL check_dec_octet(const WCHAR **ptr) {
     ++(*ptr);
 
     c2 = *ptr;
-    /* Since the 1 digit requirment was meet, it doesn't
+    /* Since the 1-digit requirement was met, it doesn't
      * matter if this is a DIGIT value, it's considered a
      * dec-octet.
      */
@@ -1270,7 +1167,7 @@ static BOOL parse_scheme(const WCHAR **ptr, parse_data *data, DWORD flags, DWORD
 
             TRACE("(%p %p %x): URI is an implicit file path.\n", ptr, data, flags);
         } else {
-            /* Window's does not consider anything that can implicitly be a file
+            /* Windows does not consider anything that can implicitly be a file
              * path to be a valid URI if the ALLOW_IMPLICIT_FILE_SCHEME flag is not set...
              */
             TRACE("(%p %p %x): URI is implicitly a file path, but, the ALLOW_IMPLICIT_FILE_SCHEME flag wasn't set.\n",
@@ -1278,10 +1175,10 @@ static BOOL parse_scheme(const WCHAR **ptr, parse_data *data, DWORD flags, DWORD
             return FALSE;
         }
     } else if(!parse_scheme_name(ptr, data, extras)) {
-        /* No Scheme was found, this means it could be:
+        /* No scheme was found, this means it could be:
          *      a) an implicit Wildcard scheme
          *      b) a relative URI
-         *      c) a invalid URI.
+         *      c) an invalid URI.
          */
         if(flags & Uri_CREATE_ALLOW_IMPLICIT_WILDCARD_SCHEME) {
             data->scheme = wildcardW;
@@ -1379,10 +1276,10 @@ static BOOL parse_password(const WCHAR **ptr, parse_data *data, DWORD flags, DWO
  *      ex:
  *          ftp://user:pass:word@winehq.org
  *
- *      Would yield, "user" as the username and "pass:word" as the password.
+ *      would yield "user" as the username and "pass:word" as the password.
  *
  *  2)  Windows allows any character to appear in the "userinfo" part of
- *      a URI, as long as it's not an authority delimeter character set.
+ *      a URI, as long as it's not an authority delimiter character set.
  */
 static void parse_userinfo(const WCHAR **ptr, parse_data *data, DWORD flags) {
     const WCHAR *start = *ptr;
@@ -1446,7 +1343,7 @@ static BOOL parse_port(const WCHAR **ptr, parse_data *data, DWORD flags) {
 
         port = port*10 + (**ptr-'0');
 
-        if(port > USHORT_MAX) {
+        if(port > USHRT_MAX) {
             *ptr = data->port;
             data->port = NULL;
             return FALSE;
@@ -1467,7 +1364,7 @@ static BOOL parse_port(const WCHAR **ptr, parse_data *data, DWORD flags) {
 /* Attempts to parse a IPv4 address from the URI.
  *
  * NOTES:
- *  Window's normalizes IPv4 addresses, This means there's three
+ *  Windows normalizes IPv4 addresses, This means there are three
  *  possibilities for the URI to contain an IPv4 address.
  *      1)  A well formed address (ex. 192.2.2.2).
  *      2)  A partially formed address. For example "192.0" would
@@ -1493,6 +1390,9 @@ static BOOL parse_ipv4address(const WCHAR **ptr, parse_data *data, DWORD flags)
             data->has_implicit_ip = TRUE;
     }
 
+    data->host_len = *ptr - data->host;
+    data->host_type = Uri_HOST_IPV4;
+
     /* Check if what we found is the only part of the host name (if it isn't
      * we don't have an IPv4 address).
      */
@@ -1504,16 +1404,13 @@ static BOOL parse_ipv4address(const WCHAR **ptr, parse_data *data, DWORD flags)
             return FALSE;
         }
     } else if(!is_auth_delim(**ptr, !is_unknown)) {
-        /* Found more data which belongs the host, so this isn't an IPv4. */
+        /* Found more data which belongs to the host, so this isn't an IPv4. */
         *ptr = data->host;
         data->host = NULL;
         data->has_implicit_ip = FALSE;
         return FALSE;
     }
 
-    data->host_len = *ptr - data->host;
-    data->host_type = Uri_HOST_IPV4;
-
     TRACE("(%p %p %x): IPv4 address found. host=%s host_len=%d host_type=%d\n",
         ptr, data, flags, debugstr_wn(data->host, data->host_len),
         data->host_len, data->host_type);
@@ -1535,8 +1432,8 @@ static BOOL parse_ipv4address(const WCHAR **ptr, parse_data *data, DWORD flags)
  *  Windows doesn't like host names which start with '[' and end with ']'
  *  and don't contain a valid IP literal address in between them.
  *
- *  On Windows if an '[' is encountered in the host name the ':' no longer
- *  counts as a delimiter until you reach the next ']' or an "authority delimeter".
+ *  On Windows if a '[' is encountered in the host name the ':' no longer
+ *  counts as a delimiter until you reach the next ']' or an "authority delimiter".
  *
  *  A reg-name CAN be empty.
  */
@@ -1557,7 +1454,7 @@ static BOOL parse_reg_name(const WCHAR **ptr, parse_data *data, DWORD flags, DWO
          * path can also have a '|' instead of a ':' after the drive letter.
          */
         if(is_drive_path(*ptr)) {
-            /* Regular old drive paths don't have a host type (or host name). */
+            /* Regular old drive paths have no host type (or host name). */
             data->host_type = Uri_HOST_UNKNOWN;
             data->host = *ptr;
             data->host_len = 0;
@@ -1589,7 +1486,7 @@ static BOOL parse_reg_name(const WCHAR **ptr, parse_data *data, DWORD flags, DWO
                         return FALSE;
                     } else
                         /* Windows gives up on trying to parse a port when it
-                         * encounters 1 invalid port.
+                         * encounters an invalid port.
                          */
                         ignore_col = TRUE;
                 } else {
@@ -1713,7 +1610,7 @@ static BOOL parse_ipv6address(const WCHAR **ptr, parse_data *data, DWORD flags)
                 /* An IPv6 address can have no more than 8 h16 components. */
                 if(ip.h16_count >= 8) {
                     *ptr = start;
-                    TRACE("(%p %p %x): Not a IPv6 address, to many h16 components.\n",
+                    TRACE("(%p %p %x): Not a IPv6 address, too many h16 components.\n",
                         ptr, data, flags);
                     return FALSE;
                 }
@@ -1781,7 +1678,7 @@ static BOOL parse_ipv6address(const WCHAR **ptr, parse_data *data, DWORD flags)
 
     if(ip.elision_size == 2) {
         /* For some reason on Windows if an elision that represents
-         * only 1 h16 component is encountered at the very begin or
+         * only one h16 component is encountered at the very begin or
          * end of an IPv6 address, Windows does not consider it a
          * valid IPv6 address.
          *
@@ -1938,10 +1835,7 @@ static BOOL parse_path_hierarchical(const WCHAR **ptr, parse_data *data, DWORD f
     const BOOL is_file = data->scheme_type == URL_SCHEME_FILE;
 
     if(is_path_delim(**ptr)) {
-        if(data->scheme_type == URL_SCHEME_WILDCARD) {
-            /* Wildcard schemes don't get a '/' attached if their path is
-             * empty.
-             */
+        if(data->scheme_type == URL_SCHEME_WILDCARD && !data->must_have_path) {
             data->path = NULL;
             data->path_len = 0;
         } else if(!(flags & Uri_CREATE_NO_CANONICALIZE)) {
@@ -2002,7 +1896,7 @@ static BOOL parse_path_hierarchical(const WCHAR **ptr, parse_data *data, DWORD f
     return TRUE;
 }
 
-/* Parses the path of a opaque URI (much less strict then the parser
+/* Parses the path of an opaque URI (much less strict than the parser
  * for a hierarchical URI).
  *
  * NOTE:
@@ -2015,8 +1909,15 @@ static BOOL parse_path_hierarchical(const WCHAR **ptr, parse_data *data, DWORD f
 static BOOL parse_path_opaque(const WCHAR **ptr, parse_data *data, DWORD flags) {
     const BOOL known_scheme = data->scheme_type != URL_SCHEME_UNKNOWN;
     const BOOL is_file = data->scheme_type == URL_SCHEME_FILE;
+    const BOOL is_mailto = data->scheme_type == URL_SCHEME_MAILTO;
 
-    data->path = *ptr;
+    if (is_mailto && (*ptr)[0] == '/' && (*ptr)[1] == '/')
+    {
+        if ((*ptr)[2]) data->path = *ptr + 2;
+        else data->path = NULL;
+    }
+    else
+        data->path = *ptr;
 
     while(!is_path_delim(**ptr)) {
         if(**ptr == '%' && known_scheme) {
@@ -2036,7 +1937,7 @@ static BOOL parse_path_opaque(const WCHAR **ptr, parse_data *data, DWORD flags)
         ++(*ptr);
     }
 
-    data->path_len = *ptr - data->path;
+    if (data->path) data->path_len = *ptr - data->path;
     TRACE("(%p %p %x): Parsed opaque URI path %s len=%d\n", ptr, data, flags,
         debugstr_wn(data->path, data->path_len), data->path_len);
     return TRUE;
@@ -2044,7 +1945,7 @@ static BOOL parse_path_opaque(const WCHAR **ptr, parse_data *data, DWORD flags)
 
 /* Determines how the URI should be parsed after the scheme information.
  *
- * If the scheme is followed, by "//" then, it is treated as an hierarchical URI
+ * If the scheme is followed by "//", then it is treated as a hierarchical URI
  * which then the authority and path information will be parsed out. Otherwise, the
  * URI will be treated as an opaque URI which the authority information is not parsed
  * out.
@@ -2062,12 +1963,23 @@ static BOOL parse_path_opaque(const WCHAR **ptr, parse_data *data, DWORD flags)
  * NOTES:
  *  If the URI is of an unknown scheme type and has a "//" following the scheme then it
  *  is treated as a hierarchical URI, but, if the CREATE_NO_CRACK_UNKNOWN_SCHEMES flag is
- *  set then it is considered an opaque URI reguardless of what follows the scheme information
+ *  set then it is considered an opaque URI regardless of what follows the scheme information
  *  (per MSDN documentation).
  */
 static BOOL parse_hierpart(const WCHAR **ptr, parse_data *data, DWORD flags) {
     const WCHAR *start = *ptr;
 
+    data->must_have_path = FALSE;
+
+    /* For javascript: URIs, simply set everything as a path */
+    if(data->scheme_type == URL_SCHEME_JAVASCRIPT) {
+        data->path = *ptr;
+        data->path_len = strlenW(*ptr);
+        data->is_opaque = TRUE;
+        *ptr += data->path_len;
+        return TRUE;
+    }
+
     /* Checks if the authority information needs to be parsed. */
     if(is_hierarchical_uri(ptr, data)) {
         /* Only treat it as a hierarchical URI if the scheme_type is known or
@@ -2078,13 +1990,20 @@ static BOOL parse_hierpart(const WCHAR **ptr, parse_data *data, DWORD flags) {
             TRACE("(%p %p %x): Treating URI as an hierarchical URI.\n", ptr, data, flags);
             data->is_opaque = FALSE;
 
+            if(data->scheme_type == URL_SCHEME_WILDCARD && !data->has_implicit_scheme) {
+                if(**ptr == '/' && *(*ptr+1) == '/') {
+                    data->must_have_path = TRUE;
+                    *ptr += 2;
+                }
+            }
+
             /* TODO: Handle hierarchical URI's, parse authority then parse the path. */
             if(!parse_authority(ptr, data, flags))
                 return FALSE;
 
             return parse_path_hierarchical(ptr, data, flags);
         } else
-            /* Reset ptr to it's starting position so opaque path parsing
+            /* Reset ptr to its starting position so opaque path parsing
              * begins at the correct location.
              */
             *ptr = start;
@@ -2107,8 +2026,8 @@ static BOOL parse_hierpart(const WCHAR **ptr, parse_data *data, DWORD flags) {
  *
  * NOTES:
  *  If NO_DECODE_EXTRA_INFO flag is set, then invalid percent encoded
- *  data is allowed appear in the query string. For unknown scheme types
- *  invalid percent encoded data is allowed to appear reguardless.
+ *  data is allowed to appear in the query string. For unknown scheme types
+ *  invalid percent encoded data is allowed to appear regardless.
  */
 static BOOL parse_query(const WCHAR **ptr, parse_data *data, DWORD flags) {
     const BOOL known_scheme = data->scheme_type != URL_SCHEME_UNKNOWN;
@@ -2146,8 +2065,8 @@ static BOOL parse_query(const WCHAR **ptr, parse_data *data, DWORD flags) {
  *
  * NOTES:
  *  If NO_DECODE_EXTRA_INFO flag is set, then invalid percent encoded
- *  data is allowed appear in the query string. For unknown scheme types
- *  invalid percent encoded data is allowed to appear reguardless.
+ *  data is allowed to appear in the query string. For unknown scheme types
+ *  invalid percent encoded data is allowed to appear regardless.
  */
 static BOOL parse_fragment(const WCHAR **ptr, parse_data *data, DWORD flags) {
     const BOOL known_scheme = data->scheme_type != URL_SCHEME_UNKNOWN;
@@ -2224,7 +2143,7 @@ static BOOL canonicalize_username(const parse_data *data, Uri *uri, DWORD flags,
         if(*ptr == '%') {
             /* Only decode % encoded values for known scheme types. */
             if(data->scheme_type != URL_SCHEME_UNKNOWN) {
-                /* See if the value really needs decoded. */
+                /* See if the value really needs decoding. */
                 WCHAR val = decode_pct_val(ptr);
                 if(is_unreserved(val)) {
                     if(!computeOnly)
@@ -2282,7 +2201,7 @@ static BOOL canonicalize_password(const parse_data *data, Uri *uri, DWORD flags,
         if(*ptr == '%') {
             /* Only decode % encoded values for known scheme types. */
             if(data->scheme_type != URL_SCHEME_UNKNOWN) {
-                /* See if the value really needs decoded. */
+                /* See if the value really needs decoding. */
                 WCHAR val = decode_pct_val(ptr);
                 if(is_unreserved(val)) {
                     if(!computeOnly)
@@ -2358,7 +2277,7 @@ static BOOL canonicalize_userinfo(const parse_data *data, Uri *uri, DWORD flags,
  * Things that happen:
  *  1)  If Uri_CREATE_NO_CANONICALIZE flag is not set, then the reg_name is
  *      lower cased. Unless it's an unknown scheme type, which case it's
- *      no lower cased reguardless.
+ *      no lower cased regardless.
  *
  *  2)  Unreserved % encoded characters are decoded for known
  *      scheme types.
@@ -2404,7 +2323,7 @@ static BOOL canonicalize_reg_name(const parse_data *data, Uri *uri,
         if(*ptr == '%' && known_scheme) {
             WCHAR val = decode_pct_val(ptr);
             if(is_unreserved(val)) {
-                /* If NO_CANONICALZE is not set, then windows lower cases the
+                /* If NO_CANONICALIZE is not set, then windows lower cases the
                  * decoded value.
                  */
                 if(!(flags & Uri_CREATE_NO_CANONICALIZE) && isupperW(val)) {
@@ -2474,8 +2393,8 @@ static BOOL canonicalize_implicit_ipv4address(const parse_data *data, Uri *uri,
     uri->host_start = uri->canon_len;
 
     TRACE("%u\n", data->implicit_ipv4);
-    /* For unknown scheme types Window's doesn't convert
-     * the value into an IP address, but, it still considers
+    /* For unknown scheme types Windows doesn't convert
+     * the value into an IP address, but it still considers
      * it an IPv4 address.
      */
     if(data->scheme_type == URL_SCHEME_UNKNOWN) {
@@ -2506,7 +2425,7 @@ static BOOL canonicalize_implicit_ipv4address(const parse_data *data, Uri *uri,
  * If the parse_data represents a URI that has an implicit IPv4 address
  * (ex. http://256/, this function will convert 256 into 0.0.1.0). If
  * the implicit IP address exceeds the value of UINT_MAX (maximum value
- * for an IPv4 address) it's canonicalized as if were a reg-name.
+ * for an IPv4 address) it's canonicalized as if it were a reg-name.
  *
  * If the parse_data contains a partial or full IPv4 address it normalizes it.
  * A partial IPv4 address is something like "192.0" and would be normalized to
@@ -2514,7 +2433,7 @@ static BOOL canonicalize_implicit_ipv4address(const parse_data *data, Uri *uri,
  * be normalized to "192.2.1.3".
  *
  * NOTES:
- *  Window's ONLY normalizes IPv4 address for known scheme types (one that isn't
+ *  Windows ONLY normalizes IPv4 address for known scheme types (one that isn't
  *  URL_SCHEME_UNKNOWN). For unknown scheme types, it simply copies the data from
  *  the original URI into the canonicalized URI, but, it still recognizes URI's
  *  host type as HOST_IPV4.
@@ -2596,11 +2515,11 @@ static BOOL canonicalize_ipv4address(const parse_data *data, Uri *uri, DWORD fla
 /* Attempts to canonicalize the IPv6 address of the URI.
  *
  * Multiple things happen during the canonicalization of an IPv6 address:
- *  1)  Any leading zero's in an h16 component are removed.
+ *  1)  Any leading zero's in a h16 component are removed.
  *      Ex: [0001:0022::] -> [1:22::]
  *
  *  2)  The longest sequence of zero h16 components are compressed
- *      into a "::" (elision). If there's a tie, the first is choosen.
+ *      into a "::" (elision). If there's a tie, the first is chosen.
  *
  *      Ex: [0:0:0:0:1:6:7:8]   -> [::1:6:7:8]
  *          [0:0:0:0:1:2::]     -> [::1:2:0:0]
@@ -2610,7 +2529,7 @@ static BOOL canonicalize_ipv4address(const parse_data *data, Uri *uri, DWORD fla
  *      also normalized.
  *      Ex: [::001.002.022.000] -> [::1.2.22.0]
  *
- *  4)  If an elision is present, but, only represents 1 h16 component
+ *  4)  If an elision is present, but, only represents one h16 component
  *      it's expanded.
  *
  *      Ex: [1::2:3:4:5:6:7] -> [1:0:2:3:4:5:6:7]
@@ -2886,7 +2805,7 @@ static BOOL canonicalize_authority(const parse_data *data, Uri *uri, DWORD flags
  * Things that happen:
  *  1). Forbidden characters are percent encoded, unless the NO_ENCODE_FORBIDDEN
  *      flag is set or it's a file URI. Forbidden characters are always encoded
- *      for file schemes reguardless and forbidden characters are never encoded
+ *      for file schemes regardless and forbidden characters are never encoded
  *      for unknown scheme types.
  *
  *  2). For known scheme types '\\' are changed to '/'.
@@ -2910,142 +2829,131 @@ static BOOL canonicalize_authority(const parse_data *data, Uri *uri, DWORD flags
  *      file:///c:/test%20test  -> file:///c:/test%20test
  *      file:///c:/test%test    -> file:///c:/test%25test
  */
-static BOOL canonicalize_path_hierarchical(const parse_data *data, Uri *uri,
-                                           DWORD flags, BOOL computeOnly) {
+static DWORD canonicalize_path_hierarchical(const WCHAR *path, DWORD path_len, URL_SCHEME scheme_type, BOOL has_host, DWORD flags,
+        WCHAR *ret_path) {
+    const BOOL known_scheme = scheme_type != URL_SCHEME_UNKNOWN;
+    const BOOL is_file = scheme_type == URL_SCHEME_FILE;
+    const BOOL is_res = scheme_type == URL_SCHEME_RES;
     const WCHAR *ptr;
-    const BOOL known_scheme = data->scheme_type != URL_SCHEME_UNKNOWN;
-    const BOOL is_file = data->scheme_type == URL_SCHEME_FILE;
-    const BOOL is_res = data->scheme_type == URL_SCHEME_RES;
-
     BOOL escape_pct = FALSE;
+    DWORD len = 0;
 
-    if(!data->path) {
-        uri->path_start = -1;
-        uri->path_len = 0;
-        return TRUE;
-    }
+    if(!path)
+        return 0;
 
-    uri->path_start = uri->canon_len;
-    ptr = data->path;
+    ptr = path;
 
-    if(is_file && uri->host_start == -1) {
+    if(is_file && !has_host) {
         /* Check if a '/' needs to be appended for the file scheme. */
-        if(data->path_len > 1 && is_drive_path(ptr) && !(flags & Uri_CREATE_FILE_USE_DOS_PATH)) {
-            if(!computeOnly)
-                uri->canon_uri[uri->canon_len] = '/';
-            uri->canon_len++;
+        if(path_len > 1 && is_drive_path(ptr) && !(flags & Uri_CREATE_FILE_USE_DOS_PATH)) {
+            if(ret_path)
+                ret_path[len] = '/';
+            len++;
             escape_pct = TRUE;
         } else if(*ptr == '/') {
             if(!(flags & Uri_CREATE_FILE_USE_DOS_PATH)) {
                 /* Copy the extra '/' over. */
-                if(!computeOnly)
-                    uri->canon_uri[uri->canon_len] = '/';
-                ++uri->canon_len;
+                if(ret_path)
+                    ret_path[len] = '/';
+                len++;
             }
             ++ptr;
         }
 
         if(is_drive_path(ptr)) {
-            if(!computeOnly) {
-                uri->canon_uri[uri->canon_len] = *ptr;
-                /* If theres a '|' after the drive letter, convert it to a ':'. */
-                uri->canon_uri[uri->canon_len+1] = ':';
+            if(ret_path) {
+                ret_path[len] = *ptr;
+                /* If there's a '|' after the drive letter, convert it to a ':'. */
+                ret_path[len+1] = ':';
             }
             ptr += 2;
-            uri->canon_len += 2;
+            len += 2;
         }
     }
 
-    if(!is_file && *(data->path) && *(data->path) != '/') {
+    if(!is_file && *path && *path != '/') {
         /* Prepend a '/' to the path if it doesn't have one. */
-        if(!computeOnly)
-            uri->canon_uri[uri->canon_len] = '/';
-        ++uri->canon_len;
+        if(ret_path)
+            ret_path[len] = '/';
+        len++;
     }
 
-    for(; ptr < data->path+data->path_len; ++ptr) {
+    for(; ptr < path+path_len; ++ptr) {
         BOOL do_default_action = TRUE;
 
         if(*ptr == '%' && !is_res) {
             const WCHAR *tmp = ptr;
             WCHAR val;
 
-            /* Check if the % represents a valid encoded char, or if it needs encoded. */
+            /* Check if the % represents a valid encoded char, or if it needs encoding. */
             BOOL force_encode = !check_pct_encoded(&tmp) && is_file && !(flags&Uri_CREATE_FILE_USE_DOS_PATH);
             val = decode_pct_val(ptr);
 
             if(force_encode || escape_pct) {
                 /* Escape the percent sign in the file URI. */
-                if(!computeOnly)
-                    pct_encode_val(*ptr, uri->canon_uri+uri->canon_len);
-                uri->canon_len += 3;
+                if(ret_path)
+                    pct_encode_val(*ptr, ret_path+len);
+                len += 3;
                 do_default_action = FALSE;
             } else if((is_unreserved(val) && known_scheme) ||
                       (is_file && (is_unreserved(val) || is_reserved(val) ||
                       (val && flags&Uri_CREATE_FILE_USE_DOS_PATH && !is_forbidden_dos_path_char(val))))) {
-                if(!computeOnly)
-                    uri->canon_uri[uri->canon_len] = val;
-                ++uri->canon_len;
+                if(ret_path)
+                    ret_path[len] = val;
+                len++;
 
                 ptr += 2;
                 continue;
             }
         } else if(*ptr == '/' && is_file && (flags & Uri_CREATE_FILE_USE_DOS_PATH)) {
             /* Convert the '/' back to a '\\'. */
-            if(!computeOnly)
-                uri->canon_uri[uri->canon_len] = '\\';
-            ++uri->canon_len;
+            if(ret_path)
+                ret_path[len] = '\\';
+            len++;
             do_default_action = FALSE;
         } else if(*ptr == '\\' && known_scheme) {
             if(!(is_file && (flags & Uri_CREATE_FILE_USE_DOS_PATH))) {
                 /* Convert '\\' into a '/'. */
-                if(!computeOnly)
-                    uri->canon_uri[uri->canon_len] = '/';
-                ++uri->canon_len;
+                if(ret_path)
+                    ret_path[len] = '/';
+                len++;
                 do_default_action = FALSE;
             }
         } else if(known_scheme && !is_res && !is_unreserved(*ptr) && !is_reserved(*ptr) &&
                   (!(flags & Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS) || is_file)) {
             if(!(is_file && (flags & Uri_CREATE_FILE_USE_DOS_PATH))) {
                 /* Escape the forbidden character. */
-                if(!computeOnly)
-                    pct_encode_val(*ptr, uri->canon_uri+uri->canon_len);
-                uri->canon_len += 3;
+                if(ret_path)
+                    pct_encode_val(*ptr, ret_path+len);
+                len += 3;
                 do_default_action = FALSE;
             }
         }
 
         if(do_default_action) {
-            if(!computeOnly)
-                uri->canon_uri[uri->canon_len] = *ptr;
-            ++uri->canon_len;
+            if(ret_path)
+                ret_path[len] = *ptr;
+            len++;
         }
     }
 
-    uri->path_len = uri->canon_len - uri->path_start;
-
     /* Removing the dot segments only happens when it's not in
      * computeOnly mode and it's not a wildcard scheme. File schemes
      * with USE_DOS_PATH set don't get dot segments removed.
      */
     if(!(is_file && (flags & Uri_CREATE_FILE_USE_DOS_PATH)) &&
-       data->scheme_type != URL_SCHEME_WILDCARD) {
-        if(!(flags & Uri_CREATE_NO_CANONICALIZE) && !computeOnly) {
+       scheme_type != URL_SCHEME_WILDCARD) {
+        if(!(flags & Uri_CREATE_NO_CANONICALIZE) && ret_path) {
             /* Remove the dot segments (if any) and reset everything to the new
              * correct length.
              */
-            DWORD new_len = remove_dot_segments(uri->canon_uri+uri->path_start, uri->path_len);
-            uri->canon_len -= uri->path_len-new_len;
-            uri->path_len = new_len;
+            len = remove_dot_segments(ret_path, len);
         }
     }
 
-    if(!computeOnly)
-        TRACE("Canonicalized path %s len=%d\n",
-            debugstr_wn(uri->canon_uri+uri->path_start, uri->path_len),
-            uri->path_len);
-
-    return TRUE;
+    if(ret_path)
+        TRACE("Canonicalized path %s len=%d\n", debugstr_wn(ret_path, len), len);
+    return len;
 }
 
 /* Attempts to canonicalize the path for an opaque URI.
@@ -3070,6 +2978,7 @@ static BOOL canonicalize_path_opaque(const parse_data *data, Uri *uri, DWORD fla
     const WCHAR *ptr;
     const BOOL known_scheme = data->scheme_type != URL_SCHEME_UNKNOWN;
     const BOOL is_file = data->scheme_type == URL_SCHEME_FILE;
+    const BOOL is_mk = data->scheme_type == URL_SCHEME_MK;
 
     if(!data->path) {
         uri->path_start = -1;
@@ -3079,6 +2988,21 @@ static BOOL canonicalize_path_opaque(const parse_data *data, Uri *uri, DWORD fla
 
     uri->path_start = uri->canon_len;
 
+    if(is_mk){
+        /* hijack this flag for SCHEME_MK to tell the function when to start
+         * converting slashes */
+        flags |= Uri_CREATE_FILE_USE_DOS_PATH;
+    }
+
+    /* For javascript: URIs, simply copy path part without any canonicalization */
+    if(data->scheme_type == URL_SCHEME_JAVASCRIPT) {
+        if(!computeOnly)
+            memcpy(uri->canon_uri+uri->canon_len, data->path, data->path_len*sizeof(WCHAR));
+        uri->path_len = data->path_len;
+        uri->canon_len += data->path_len;
+        return TRUE;
+    }
+
     /* Windows doesn't allow a "//" to appear after the scheme
      * of a URI, if it's an opaque URI.
      */
@@ -3112,13 +3036,15 @@ static BOOL canonicalize_path_opaque(const parse_data *data, Uri *uri, DWORD fla
             ++uri->canon_len;
             do_default_action = FALSE;
         } else if(*ptr == '\\') {
-            if(is_file && !(flags & Uri_CREATE_FILE_USE_DOS_PATH)) {
+            if((data->is_relative || is_mk || is_file) && !(flags & Uri_CREATE_FILE_USE_DOS_PATH)) {
                 /* Convert to a '/'. */
                 if(!computeOnly)
                     uri->canon_uri[uri->canon_len] = '/';
                 ++uri->canon_len;
                 do_default_action = FALSE;
             }
+        } else if(is_mk && *ptr == ':' && ptr + 1 < data->path + data->path_len && *(ptr + 1) == ':') {
+            flags &= ~Uri_CREATE_FILE_USE_DOS_PATH;
         } else if(known_scheme && !is_unreserved(*ptr) && !is_reserved(*ptr) &&
                   !(flags & Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS)) {
             if(!(is_file && (flags & Uri_CREATE_FILE_USE_DOS_PATH))) {
@@ -3136,7 +3062,7 @@ static BOOL canonicalize_path_opaque(const parse_data *data, Uri *uri, DWORD fla
         }
     }
 
-    if(data->scheme_type == URL_SCHEME_MK && !computeOnly && !(flags & Uri_CREATE_NO_CANONICALIZE)) {
+    if(is_mk && !computeOnly && !(flags & Uri_CREATE_NO_CANONICALIZE)) {
         DWORD new_len = remove_dot_segments(uri->canon_uri + uri->path_start,
                                             uri->canon_len - uri->path_start);
         uri->canon_len = uri->path_start + new_len;
@@ -3166,6 +3092,9 @@ static BOOL canonicalize_hierpart(const parse_data *data, Uri *uri, DWORD flags,
          */
         if((data->is_relative && (data->host || data->has_port)) ||
            (!data->is_relative && data->scheme_type != URL_SCHEME_WILDCARD)) {
+            if(data->scheme_type == URL_SCHEME_WILDCARD)
+                FIXME("Here\n");
+
             if(!computeOnly) {
                 INT pos = uri->canon_len;
 
@@ -3182,8 +3111,13 @@ static BOOL canonicalize_hierpart(const parse_data *data, Uri *uri, DWORD flags,
             if(!canonicalize_path_opaque(data, uri, flags, computeOnly))
                 return FALSE;
         } else {
-            if(!canonicalize_path_hierarchical(data, uri, flags, computeOnly))
-                return FALSE;
+            if(!computeOnly)
+                uri->path_start = uri->canon_len;
+            uri->path_len = canonicalize_path_hierarchical(data->path, data->path_len, data->scheme_type, data->host_len != 0,
+                    flags, computeOnly ? NULL : uri->canon_uri+uri->canon_len);
+            uri->canon_len += uri->path_len;
+            if(!computeOnly && !uri->path_len)
+                uri->path_start = -1;
         }
     } else {
         /* Opaque URI's don't have an authority. */
@@ -3367,7 +3301,7 @@ static BOOL canonicalize_scheme(const parse_data *data, Uri *uri, DWORD flags, B
             uri->scheme_start = pos;
 
             TRACE("(%p %p %x): Canonicalized scheme=%s, len=%d.\n", data, uri, flags,
-                    debugstr_wn(uri->canon_uri,  uri->scheme_len), data->scheme_len);
+                    debugstr_wn(uri->canon_uri+uri->scheme_start,  data->scheme_len), data->scheme_len);
         }
 
         /* This happens in both computation modes. */
@@ -3377,7 +3311,7 @@ static BOOL canonicalize_scheme(const parse_data *data, Uri *uri, DWORD flags, B
     return TRUE;
 }
 
-/* Compute's what the length of the URI specified by the parse_data will be
+/* Computes what the length of the URI specified by the parse_data will be
  * after canonicalization occurs using the specified flags.
  *
  * This function will return a non-zero value indicating the length of the canonicalized
@@ -3417,11 +3351,11 @@ static int compute_canonicalized_length(const parse_data *data, DWORD flags) {
 }
 
 /* Canonicalizes the URI data specified in the parse_data, using the given flags. If the
- * canonicalization succeededs it will store all the canonicalization information
+ * canonicalization succeeds it will store all the canonicalization information
  * in the pointer to the Uri.
  *
  * To canonicalize a URI this function first computes what the length of the URI
- * specified by the parse_data will be. Once this is done it will then perfom the actual
+ * specified by the parse_data will be. Once this is done it will then perform the actual
  * canonicalization of the URI.
  */
 static HRESULT canonicalize_uri(const parse_data *data, Uri *uri, DWORD flags) {
@@ -3473,7 +3407,7 @@ static HRESULT canonicalize_uri(const parse_data *data, Uri *uri, DWORD flags) {
      */
     if(uri->canon_len < uri->canon_size) {
         /* This happens if the URI is hierarchical and dot
-         * segments were removed from it's path.
+         * segments were removed from its path.
          */
         WCHAR *tmp = heap_realloc(uri->canon_uri, (uri->canon_len+1)*sizeof(WCHAR));
         if(!tmp)
@@ -3785,6 +3719,7 @@ static HRESULT validate_path(const UriBuilder *builder, parse_data *data, DWORD
         static const WCHAR nullW[] = {0};
         ptr = nullW;
         check_len = FALSE;
+        expected_len = -1;
     }
 
     component = ptr;
@@ -3798,7 +3733,7 @@ static HRESULT validate_path(const UriBuilder *builder, parse_data *data, DWORD
 
     if(!valid || (check_len && expected_len != data->path_len)) {
         TRACE("(%p %p %x): Invalid path component %s.\n", builder, data, flags,
-            debugstr_wn(component, check_len ? expected_len : -1) );
+            debugstr_wn(component, expected_len) );
         return INET_E_INVALID_URL;
     }
 
@@ -3903,7 +3838,8 @@ static HRESULT validate_components(const UriBuilder *builder, parse_data *data,
 
     /* The URI is opaque if it doesn't have an authority component. */
     if(!data->is_relative)
-        data->is_opaque = !data->username && !data->password && !data->host && !data->has_port;
+        data->is_opaque = !data->username && !data->password && !data->host && !data->has_port
+            && data->scheme_type != URL_SCHEME_FILE;
     else
         data->is_opaque = !data->host && !data->has_port;
 
@@ -3924,6 +3860,150 @@ static HRESULT validate_components(const UriBuilder *builder, parse_data *data,
     return S_OK;
 }
 
+static HRESULT compare_file_paths(const Uri *a, const Uri *b, BOOL *ret)
+{
+    WCHAR *canon_path_a, *canon_path_b;
+    DWORD len_a, len_b;
+
+    if(!a->path_len) {
+        *ret = !b->path_len;
+        return S_OK;
+    }
+
+    if(!b->path_len) {
+        *ret = FALSE;
+        return S_OK;
+    }
+
+    /* Fast path */
+    if(a->path_len == b->path_len && !memicmpW(a->canon_uri+a->path_start, b->canon_uri+b->path_start, a->path_len)) {
+        *ret = TRUE;
+        return S_OK;
+    }
+
+    len_a = canonicalize_path_hierarchical(a->canon_uri+a->path_start, a->path_len, a->scheme_type, FALSE, 0, NULL);
+    len_b = canonicalize_path_hierarchical(b->canon_uri+b->path_start, b->path_len, b->scheme_type, FALSE, 0, NULL);
+
+    canon_path_a = heap_alloc(len_a*sizeof(WCHAR));
+    if(!canon_path_a)
+        return E_OUTOFMEMORY;
+    canon_path_b = heap_alloc(len_b*sizeof(WCHAR));
+    if(!canon_path_b) {
+        heap_free(canon_path_a);
+        return E_OUTOFMEMORY;
+    }
+
+    len_a = canonicalize_path_hierarchical(a->canon_uri+a->path_start, a->path_len, a->scheme_type, FALSE, 0, canon_path_a);
+    len_b = canonicalize_path_hierarchical(b->canon_uri+b->path_start, b->path_len, b->scheme_type, FALSE, 0, canon_path_b);
+
+    *ret = len_a == len_b && !memicmpW(canon_path_a, canon_path_b, len_a);
+
+    heap_free(canon_path_a);
+    heap_free(canon_path_b);
+    return S_OK;
+}
+
+/* Checks if the two Uri's are logically equivalent. It's a simple
+ * comparison, since they are both of type Uri, and it can access
+ * the properties of each Uri directly without the need to go
+ * through the "IUri_Get*" interface calls.
+ */
+static HRESULT compare_uris(const Uri *a, const Uri *b, BOOL *ret) {
+    const BOOL known_scheme = a->scheme_type != URL_SCHEME_UNKNOWN;
+    const BOOL are_hierarchical = a->authority_start > -1 && b->authority_start > -1;
+    HRESULT hres;
+
+    *ret = FALSE;
+
+    if(a->scheme_type != b->scheme_type)
+        return S_OK;
+
+    /* Only compare the scheme names (if any) if their unknown scheme types. */
+    if(!known_scheme) {
+        if((a->scheme_start > -1 && b->scheme_start > -1) &&
+           (a->scheme_len == b->scheme_len)) {
+            /* Make sure the schemes are the same. */
+            if(StrCmpNW(a->canon_uri+a->scheme_start, b->canon_uri+b->scheme_start, a->scheme_len))
+                return S_OK;
+        } else if(a->scheme_len != b->scheme_len)
+            /* One of the Uri's has a scheme name, while the other doesn't. */
+            return S_OK;
+    }
+
+    /* If they have a userinfo component, perform case sensitive compare. */
+    if((a->userinfo_start > -1 && b->userinfo_start > -1) &&
+       (a->userinfo_len == b->userinfo_len)) {
+        if(StrCmpNW(a->canon_uri+a->userinfo_start, b->canon_uri+b->userinfo_start, a->userinfo_len))
+            return S_OK;
+    } else if(a->userinfo_len != b->userinfo_len)
+        /* One of the Uri's had a userinfo, while the other one doesn't. */
+        return S_OK;
+
+    /* Check if they have a host name. */
+    if((a->host_start > -1 && b->host_start > -1) &&
+       (a->host_len == b->host_len)) {
+        /* Perform a case insensitive compare if they are a known scheme type. */
+        if(known_scheme) {
+            if(StrCmpNIW(a->canon_uri+a->host_start, b->canon_uri+b->host_start, a->host_len))
+                return S_OK;
+        } else if(StrCmpNW(a->canon_uri+a->host_start, b->canon_uri+b->host_start, a->host_len))
+            return S_OK;
+    } else if(a->host_len != b->host_len)
+        /* One of the Uri's had a host, while the other one didn't. */
+        return S_OK;
+
+    if(a->has_port && b->has_port) {
+        if(a->port != b->port)
+            return S_OK;
+    } else if(a->has_port || b->has_port)
+        /* One had a port, while the other one didn't. */
+        return S_OK;
+
+    /* Windows is weird with how it handles paths. For example
+     * One URI could be "http://google.com" (after canonicalization)
+     * and one could be "http://google.com/" and the IsEqual function
+     * would still evaluate to TRUE, but, only if they are both hierarchical
+     * URIs.
+     */
+    if(a->scheme_type == URL_SCHEME_FILE) {
+        BOOL cmp;
+
+        hres = compare_file_paths(a, b, &cmp);
+        if(FAILED(hres) || !cmp)
+            return hres;
+    } else if((a->path_start > -1 && b->path_start > -1) &&
+       (a->path_len == b->path_len)) {
+        if(StrCmpNW(a->canon_uri+a->path_start, b->canon_uri+b->path_start, a->path_len))
+            return S_OK;
+    } else if(are_hierarchical && a->path_len == -1 && b->path_len == 0) {
+        if(*(a->canon_uri+a->path_start) != '/')
+            return S_OK;
+    } else if(are_hierarchical && b->path_len == 1 && a->path_len == 0) {
+        if(*(b->canon_uri+b->path_start) != '/')
+            return S_OK;
+    } else if(a->path_len != b->path_len)
+        return S_OK;
+
+    /* Compare the query strings of the two URIs. */
+    if((a->query_start > -1 && b->query_start > -1) &&
+       (a->query_len == b->query_len)) {
+        if(StrCmpNW(a->canon_uri+a->query_start, b->canon_uri+b->query_start, a->query_len))
+            return S_OK;
+    } else if(a->query_len != b->query_len)
+        return S_OK;
+
+    if((a->fragment_start > -1 && b->fragment_start > -1) &&
+       (a->fragment_len == b->fragment_len)) {
+        if(StrCmpNW(a->canon_uri+a->fragment_start, b->canon_uri+b->fragment_start, a->fragment_len))
+            return S_OK;
+    } else if(a->fragment_len != b->fragment_len)
+        return S_OK;
+
+    /* If we get here, the two URIs are equivalent. */
+    *ret = TRUE;
+    return S_OK;
+}
+
 static void convert_to_dos_path(const WCHAR *path, DWORD path_len,
                                 WCHAR *output, DWORD *output_len)
 {
@@ -4116,7 +4196,7 @@ static inline Uri* impl_from_IUri(IUri *iface)
     return CONTAINING_RECORD(iface, Uri, IUri_iface);
 }
 
-static inline void destory_uri_obj(Uri *This)
+static inline void destroy_uri_obj(Uri *This)
 {
     SysFreeString(This->raw_uri);
     heap_free(This->canon_uri);
@@ -4134,8 +4214,14 @@ static HRESULT WINAPI Uri_QueryInterface(IUri *iface, REFIID riid, void **ppv)
         TRACE("(%p)->(IID_IUri %p)\n", This, ppv);
         *ppv = &This->IUri_iface;
     }else if(IsEqualGUID(&IID_IUriBuilderFactory, riid)) {
-        TRACE("(%p)->(IID_IUriBuilderFactory %p)\n", This, riid);
+        TRACE("(%p)->(IID_IUriBuilderFactory %p)\n", This, ppv);
         *ppv = &This->IUriBuilderFactory_iface;
+    }else if(IsEqualGUID(&IID_IPersistStream, riid)) {
+        TRACE("(%p)->(IID_IPersistStream %p)\n", This, ppv);
+        *ppv = &This->IPersistStream_iface;
+    }else if(IsEqualGUID(&IID_IMarshal, riid)) {
+        TRACE("(%p)->(IID_IMarshal %p)\n", This, ppv);
+        *ppv = &This->IMarshal_iface;
     }else if(IsEqualGUID(&IID_IUriObj, riid)) {
         TRACE("(%p)->(IID_IUriObj %p)\n", This, ppv);
         *ppv = This;
@@ -4168,7 +4254,7 @@ static ULONG WINAPI Uri_Release(IUri *iface)
     TRACE("(%p) ref=%d\n", This, ref);
 
     if(!ref)
-        destory_uri_obj(This);
+        destroy_uri_obj(This);
 
     return ref;
 }
@@ -4177,22 +4263,24 @@ static HRESULT WINAPI Uri_GetPropertyBSTR(IUri *iface, Uri_PROPERTY uriProp, BST
 {
     Uri *This = impl_from_IUri(iface);
     HRESULT hres;
-    TRACE("(%p)->(%d %p %x)\n", This, uriProp, pbstrProperty, dwFlags);
+    TRACE("(%p %s)->(%d %p %x)\n", This, debugstr_w(This->canon_uri), uriProp, pbstrProperty, dwFlags);
 
+    if(!This->create_flags)
+        return E_UNEXPECTED;
     if(!pbstrProperty)
         return E_POINTER;
 
     if(uriProp > Uri_PROPERTY_STRING_LAST) {
-        /* Windows allocates an empty BSTR for invalid Uri_PROPERTY's. */
-        *pbstrProperty = SysAllocStringLen(NULL, 0);
-        if(!(*pbstrProperty))
-            return E_OUTOFMEMORY;
-
         /* It only returns S_FALSE for the ZONE property... */
-        if(uriProp == Uri_PROPERTY_ZONE)
+        if(uriProp == Uri_PROPERTY_ZONE) {
+            *pbstrProperty = SysAllocStringLen(NULL, 0);
+            if(!(*pbstrProperty))
+                return E_OUTOFMEMORY;
             return S_FALSE;
-        else
-            return S_OK;
+        }
+
+        *pbstrProperty = NULL;
+        return E_INVALIDARG;
     }
 
     /* Don't have support for flags yet. */
@@ -4467,8 +4555,10 @@ static HRESULT WINAPI Uri_GetPropertyLength(IUri *iface, Uri_PROPERTY uriProp, D
 {
     Uri *This = impl_from_IUri(iface);
     HRESULT hres;
-    TRACE("(%p)->(%d %p %x)\n", This, uriProp, pcchProperty, dwFlags);
+    TRACE("(%p %s)->(%d %p %x)\n", This, debugstr_w(This->canon_uri), uriProp, pcchProperty, dwFlags);
 
+    if(!This->create_flags)
+        return E_UNEXPECTED;
     if(!pcchProperty)
         return E_INVALIDARG;
 
@@ -4602,8 +4692,10 @@ static HRESULT WINAPI Uri_GetPropertyDWORD(IUri *iface, Uri_PROPERTY uriProp, DW
     Uri *This = impl_from_IUri(iface);
     HRESULT hres;
 
-    TRACE("(%p)->(%d %p %x)\n", This, uriProp, pcchProperty, dwFlags);
+    TRACE("(%p %s)->(%d %p %x)\n", This, debugstr_w(This->canon_uri), uriProp, pcchProperty, dwFlags);
 
+    if(!This->create_flags)
+        return E_UNEXPECTED;
     if(!pcchProperty)
         return E_INVALIDARG;
 
@@ -4652,7 +4744,8 @@ static HRESULT WINAPI Uri_GetPropertyDWORD(IUri *iface, Uri_PROPERTY uriProp, DW
 static HRESULT WINAPI Uri_HasProperty(IUri *iface, Uri_PROPERTY uriProp, BOOL *pfHasProperty)
 {
     Uri *This = impl_from_IUri(iface);
-    TRACE("(%p)->(%d %p)\n", This, uriProp, pfHasProperty);
+
+    TRACE("(%p %s)->(%d %p)\n", This, debugstr_w(This->canon_uri), uriProp, pfHasProperty);
 
     if(!pfHasProperty)
         return E_INVALIDARG;
@@ -4843,8 +4936,10 @@ static HRESULT WINAPI Uri_GetZone(IUri *iface, DWORD *pdwZone)
 static HRESULT WINAPI Uri_GetProperties(IUri *iface, DWORD *pdwProperties)
 {
     Uri *This = impl_from_IUri(iface);
-    TRACE("(%p)->(%p)\n", This, pdwProperties);
+    TRACE("(%p %s)->(%p)\n", This, debugstr_w(This->canon_uri), pdwProperties);
 
+    if(!This->create_flags)
+        return E_UNEXPECTED;
     if(!pdwProperties)
         return E_INVALIDARG;
 
@@ -4893,8 +4988,10 @@ static HRESULT WINAPI Uri_IsEqual(IUri *iface, IUri *pUri, BOOL *pfEqual)
     Uri *This = impl_from_IUri(iface);
     Uri *other;
 
-    TRACE("(%p)->(%p %p)\n", This, pUri, pfEqual);
+    TRACE("(%p %s)->(%p %p)\n", This, debugstr_w(This->canon_uri), pUri, pfEqual);
 
+    if(!This->create_flags)
+        return E_UNEXPECTED;
     if(!pfEqual)
         return E_POINTER;
 
@@ -4906,15 +5003,13 @@ static HRESULT WINAPI Uri_IsEqual(IUri *iface, IUri *pUri, BOOL *pfEqual)
     }
 
     /* Try to convert it to a Uri (allows for a more simple comparison). */
-    if((other = get_uri_obj(pUri)))
-        *pfEqual = are_equal_simple(This, other);
-    else {
-        /* Do it the hard way. */
+    if(!(other = get_uri_obj(pUri))) {
         FIXME("(%p)->(%p %p) No support for unknown IUri's yet.\n", iface, pUri, pfEqual);
         return E_NOTIMPL;
     }
 
-    return S_OK;
+    TRACE("comparing to %s\n", debugstr_w(other->canon_uri));
+    return compare_uris(This, other, pfEqual);
 }
 
 static const IUriVtbl UriVtbl = {
@@ -4956,47 +5051,19 @@ static inline Uri* impl_from_IUriBuilderFactory(IUriBuilderFactory *iface)
 static HRESULT WINAPI UriBuilderFactory_QueryInterface(IUriBuilderFactory *iface, REFIID riid, void **ppv)
 {
     Uri *This = impl_from_IUriBuilderFactory(iface);
-
-    if(IsEqualGUID(&IID_IUnknown, riid)) {
-        TRACE("(%p)->(IID_IUnknown %p)\n", This, ppv);
-        *ppv = &This->IUriBuilderFactory_iface;
-    }else if(IsEqualGUID(&IID_IUriBuilderFactory, riid)) {
-        TRACE("(%p)->(IID_IUriBuilderFactory %p)\n", This, ppv);
-        *ppv = &This->IUriBuilderFactory_iface;
-    }else if(IsEqualGUID(&IID_IUri, riid)) {
-        TRACE("(%p)->(IID_IUri %p)\n", This, ppv);
-        *ppv = &This->IUri_iface;
-    }else {
-        TRACE("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppv);
-        *ppv = NULL;
-        return E_NOINTERFACE;
-    }
-
-    IUnknown_AddRef((IUnknown*)*ppv);
-    return S_OK;
+    return IUri_QueryInterface(&This->IUri_iface, riid, ppv);
 }
 
 static ULONG WINAPI UriBuilderFactory_AddRef(IUriBuilderFactory *iface)
 {
     Uri *This = impl_from_IUriBuilderFactory(iface);
-    LONG ref = InterlockedIncrement(&This->ref);
-
-    TRACE("(%p) ref=%d\n", This, ref);
-
-    return ref;
+    return IUri_AddRef(&This->IUri_iface);
 }
 
 static ULONG WINAPI UriBuilderFactory_Release(IUriBuilderFactory *iface)
 {
     Uri *This = impl_from_IUriBuilderFactory(iface);
-    LONG ref = InterlockedDecrement(&This->ref);
-
-    TRACE("(%p) ref=%d\n", This, ref);
-
-    if(!ref)
-        destory_uri_obj(This);
-
-    return ref;
+    return IUri_Release(&This->IUri_iface);
 }
 
 static HRESULT WINAPI UriBuilderFactory_CreateIUriBuilder(IUriBuilderFactory *iface,
@@ -5045,15 +5112,553 @@ static const IUriBuilderFactoryVtbl UriBuilderFactoryVtbl = {
     UriBuilderFactory_CreateInitializedIUriBuilder
 };
 
-static Uri* create_uri_obj(void) {
-    Uri *ret = heap_alloc_zero(sizeof(Uri));
-    if(ret) {
-        ret->IUri_iface.lpVtbl = &UriVtbl;
-        ret->IUriBuilderFactory_iface.lpVtbl = &UriBuilderFactoryVtbl;
-        ret->ref = 1;
+static inline Uri* impl_from_IPersistStream(IPersistStream *iface)
+{
+    return CONTAINING_RECORD(iface, Uri, IPersistStream_iface);
+}
+
+static HRESULT WINAPI PersistStream_QueryInterface(IPersistStream *iface, REFIID riid, void **ppvObject)
+{
+    Uri *This = impl_from_IPersistStream(iface);
+    return IUri_QueryInterface(&This->IUri_iface, riid, ppvObject);
+}
+
+static ULONG WINAPI PersistStream_AddRef(IPersistStream *iface)
+{
+    Uri *This = impl_from_IPersistStream(iface);
+    return IUri_AddRef(&This->IUri_iface);
+}
+
+static ULONG WINAPI PersistStream_Release(IPersistStream *iface)
+{
+    Uri *This = impl_from_IPersistStream(iface);
+    return IUri_Release(&This->IUri_iface);
+}
+
+static HRESULT WINAPI PersistStream_GetClassID(IPersistStream *iface, CLSID *pClassID)
+{
+    Uri *This = impl_from_IPersistStream(iface);
+    TRACE("(%p)->(%p)\n", This, pClassID);
+
+    if(!pClassID)
+        return E_INVALIDARG;
+
+    *pClassID = CLSID_CUri;
+    return S_OK;
+}
+
+static HRESULT WINAPI PersistStream_IsDirty(IPersistStream *iface)
+{
+    Uri *This = impl_from_IPersistStream(iface);
+    TRACE("(%p)\n", This);
+    return S_FALSE;
+}
+
+struct persist_uri {
+    DWORD size;
+    DWORD unk1[2];
+    DWORD create_flags;
+    DWORD unk2[3];
+    DWORD fields_no;
+    BYTE data[1];
+};
+
+static HRESULT WINAPI PersistStream_Load(IPersistStream *iface, IStream *pStm)
+{
+    Uri *This = impl_from_IPersistStream(iface);
+    struct persist_uri *data;
+    parse_data parse;
+    DWORD size;
+    HRESULT hr;
+
+    TRACE("(%p)->(%p)\n", This, pStm);
+
+    if(This->create_flags)
+        return E_UNEXPECTED;
+    if(!pStm)
+        return E_INVALIDARG;
+
+    hr = IStream_Read(pStm, &size, sizeof(DWORD), NULL);
+    if(FAILED(hr))
+        return hr;
+    data = heap_alloc(size);
+    if(!data)
+        return E_OUTOFMEMORY;
+    hr = IStream_Read(pStm, data->unk1, size-sizeof(DWORD)-2, NULL);
+    if(FAILED(hr)) {
+        heap_free(data);
+        return hr;
     }
 
-    return ret;
+    if(size < sizeof(struct persist_uri)) {
+        heap_free(data);
+        return S_OK;
+    }
+
+    if(*(DWORD*)data->data != Uri_PROPERTY_RAW_URI) {
+        heap_free(data);
+        ERR("Can't find raw_uri\n");
+        return E_UNEXPECTED;
+    }
+
+    This->raw_uri = SysAllocString((WCHAR*)(data->data+sizeof(DWORD)*2));
+    if(!This->raw_uri) {
+        heap_free(data);
+        return E_OUTOFMEMORY;
+    }
+    This->create_flags = data->create_flags;
+    heap_free(data);
+    TRACE("%x %s\n", This->create_flags, debugstr_w(This->raw_uri));
+
+    memset(&parse, 0, sizeof(parse_data));
+    parse.uri = This->raw_uri;
+    if(!parse_uri(&parse, This->create_flags)) {
+        SysFreeString(This->raw_uri);
+        This->create_flags = 0;
+        return E_UNEXPECTED;
+    }
+
+    hr = canonicalize_uri(&parse, This, This->create_flags);
+    if(FAILED(hr)) {
+        SysFreeString(This->raw_uri);
+        This->create_flags = 0;
+        return hr;
+    }
+
+    return S_OK;
+}
+
+static inline BYTE* persist_stream_add_strprop(Uri *This, BYTE *p, DWORD type, DWORD len, WCHAR *data)
+{
+    len *= sizeof(WCHAR);
+    *(DWORD*)p = type;
+    p += sizeof(DWORD);
+    *(DWORD*)p = len+sizeof(WCHAR);
+    p += sizeof(DWORD);
+    memcpy(p, data, len);
+    p += len;
+    *(WCHAR*)p = 0;
+    return p+sizeof(WCHAR);
+}
+
+static inline void persist_stream_save(Uri *This, IStream *pStm, BOOL marshal, struct persist_uri *data)
+{
+    BYTE *p = NULL;
+
+    data->create_flags = This->create_flags;
+
+    if(This->create_flags) {
+        data->fields_no = 1;
+        p = persist_stream_add_strprop(This, data->data, Uri_PROPERTY_RAW_URI,
+                SysStringLen(This->raw_uri), This->raw_uri);
+    }
+    if(This->scheme_type!=URL_SCHEME_HTTP && This->scheme_type!=URL_SCHEME_HTTPS
+            && This->scheme_type!=URL_SCHEME_FTP)
+        return;
+
+    if(This->fragment_len) {
+        data->fields_no++;
+        p = persist_stream_add_strprop(This, p, Uri_PROPERTY_FRAGMENT,
+                This->fragment_len, This->canon_uri+This->fragment_start);
+    }
+
+    if(This->host_len) {
+        data->fields_no++;
+        if(This->host_type == Uri_HOST_IPV6)
+            p = persist_stream_add_strprop(This, p, Uri_PROPERTY_HOST,
+                    This->host_len-2, This->canon_uri+This->host_start+1);
+        else
+            p = persist_stream_add_strprop(This, p, Uri_PROPERTY_HOST,
+                    This->host_len, This->canon_uri+This->host_start);
+    }
+
+    if(This->userinfo_split > -1) {
+        data->fields_no++;
+        p = persist_stream_add_strprop(This, p, Uri_PROPERTY_PASSWORD,
+                This->userinfo_len-This->userinfo_split-1,
+                This->canon_uri+This->userinfo_start+This->userinfo_split+1);
+    }
+
+    if(This->path_len) {
+        data->fields_no++;
+        p = persist_stream_add_strprop(This, p, Uri_PROPERTY_PATH,
+                This->path_len, This->canon_uri+This->path_start);
+    } else if(marshal) {
+        WCHAR no_path = '/';
+        data->fields_no++;
+        p = persist_stream_add_strprop(This, p, Uri_PROPERTY_PATH, 1, &no_path);
+    }
+
+    if(This->has_port) {
+        data->fields_no++;
+        *(DWORD*)p = Uri_PROPERTY_PORT;
+        p += sizeof(DWORD);
+        *(DWORD*)p = sizeof(DWORD);
+        p += sizeof(DWORD);
+        *(DWORD*)p = This->port;
+        p += sizeof(DWORD);
+    }
+
+    if(This->query_len) {
+        data->fields_no++;
+        p = persist_stream_add_strprop(This, p, Uri_PROPERTY_QUERY,
+                This->query_len, This->canon_uri+This->query_start);
+    }
+
+    if(This->scheme_len) {
+        data->fields_no++;
+        p = persist_stream_add_strprop(This, p, Uri_PROPERTY_SCHEME_NAME,
+                This->scheme_len, This->canon_uri+This->scheme_start);
+    }
+
+    if(This->userinfo_start>-1 && This->userinfo_split!=0) {
+        data->fields_no++;
+        if(This->userinfo_split > -1)
+            p = persist_stream_add_strprop(This, p, Uri_PROPERTY_USER_NAME,
+                    This->userinfo_split, This->canon_uri+This->userinfo_start);
+        else
+            p = persist_stream_add_strprop(This, p, Uri_PROPERTY_USER_NAME,
+                    This->userinfo_len, This->canon_uri+This->userinfo_start);
+    }
+}
+
+static HRESULT WINAPI PersistStream_Save(IPersistStream *iface, IStream *pStm, BOOL fClearDirty)
+{
+    Uri *This = impl_from_IPersistStream(iface);
+    struct persist_uri *data;
+    ULARGE_INTEGER size;
+    HRESULT hres;
+
+    TRACE("(%p)->(%p %x)\n", This, pStm, fClearDirty);
+
+    if(!pStm)
+        return E_INVALIDARG;
+
+    hres = IPersistStream_GetSizeMax(&This->IPersistStream_iface, &size);
+    if(FAILED(hres))
+        return hres;
+
+    data = heap_alloc_zero(size.u.LowPart);
+    if(!data)
+        return E_OUTOFMEMORY;
+    data->size = size.u.LowPart;
+    persist_stream_save(This, pStm, FALSE, data);
+
+    hres = IStream_Write(pStm, data, data->size-2, NULL);
+    heap_free(data);
+    return hres;
+}
+
+static HRESULT WINAPI PersistStream_GetSizeMax(IPersistStream *iface, ULARGE_INTEGER *pcbSize)
+{
+    Uri *This = impl_from_IPersistStream(iface);
+    TRACE("(%p)->(%p)\n", This, pcbSize);
+
+    if(!pcbSize)
+        return E_INVALIDARG;
+
+    pcbSize->u.LowPart = 2+sizeof(struct persist_uri);
+    pcbSize->u.HighPart = 0;
+    if(This->create_flags)
+        pcbSize->u.LowPart += (SysStringLen(This->raw_uri)+1)*sizeof(WCHAR) + 2*sizeof(DWORD);
+    else /* there's no place for fields no */
+        pcbSize->u.LowPart -= sizeof(DWORD);
+    if(This->scheme_type!=URL_SCHEME_HTTP && This->scheme_type!=URL_SCHEME_HTTPS
+            && This->scheme_type!=URL_SCHEME_FTP)
+        return S_OK;
+
+    if(This->fragment_len)
+        pcbSize->u.LowPart += (This->fragment_len+1)*sizeof(WCHAR) + 2*sizeof(DWORD);
+    if(This->host_len) {
+        if(This->host_type == Uri_HOST_IPV6)
+            pcbSize->u.LowPart += (This->host_len-1)*sizeof(WCHAR) + 2*sizeof(DWORD);
+        else
+            pcbSize->u.LowPart += (This->host_len+1)*sizeof(WCHAR) + 2*sizeof(DWORD);
+    }
+    if(This->userinfo_split > -1)
+        pcbSize->u.LowPart += (This->userinfo_len-This->userinfo_split)*sizeof(WCHAR) + 2*sizeof(DWORD);
+    if(This->path_len)
+        pcbSize->u.LowPart += (This->path_len+1)*sizeof(WCHAR) + 2*sizeof(DWORD);
+    if(This->has_port)
+        pcbSize->u.LowPart += 3*sizeof(DWORD);
+    if(This->query_len)
+        pcbSize->u.LowPart += (This->query_len+1)*sizeof(WCHAR) + 2*sizeof(DWORD);
+    if(This->scheme_len)
+        pcbSize->u.LowPart += (This->scheme_len+1)*sizeof(WCHAR) + 2*sizeof(DWORD);
+    if(This->userinfo_start>-1 && This->userinfo_split!=0) {
+        if(This->userinfo_split > -1)
+            pcbSize->u.LowPart += (This->userinfo_split+1)*sizeof(WCHAR) + 2*sizeof(DWORD);
+        else
+            pcbSize->u.LowPart += (This->userinfo_len+1)*sizeof(WCHAR) + 2*sizeof(DWORD);
+    }
+    return S_OK;
+}
+
+static const IPersistStreamVtbl PersistStreamVtbl = {
+    PersistStream_QueryInterface,
+    PersistStream_AddRef,
+    PersistStream_Release,
+    PersistStream_GetClassID,
+    PersistStream_IsDirty,
+    PersistStream_Load,
+    PersistStream_Save,
+    PersistStream_GetSizeMax
+};
+
+static inline Uri* impl_from_IMarshal(IMarshal *iface)
+{
+    return CONTAINING_RECORD(iface, Uri, IMarshal_iface);
+}
+
+static HRESULT WINAPI Marshal_QueryInterface(IMarshal *iface, REFIID riid, void **ppvObject)
+{
+    Uri *This = impl_from_IMarshal(iface);
+    return IUri_QueryInterface(&This->IUri_iface, riid, ppvObject);
+}
+
+static ULONG WINAPI Marshal_AddRef(IMarshal *iface)
+{
+    Uri *This = impl_from_IMarshal(iface);
+    return IUri_AddRef(&This->IUri_iface);
+}
+
+static ULONG WINAPI Marshal_Release(IMarshal *iface)
+{
+    Uri *This = impl_from_IMarshal(iface);
+    return IUri_Release(&This->IUri_iface);
+}
+
+static HRESULT WINAPI Marshal_GetUnmarshalClass(IMarshal *iface, REFIID riid, void *pv,
+        DWORD dwDestContext, void *pvDestContext, DWORD mshlflags, CLSID *pCid)
+{
+    Uri *This = impl_from_IMarshal(iface);
+    TRACE("(%p)->(%s %p %x %p %x %p)\n", This, debugstr_guid(riid), pv,
+            dwDestContext, pvDestContext, mshlflags, pCid);
+
+    if(!pCid || (dwDestContext!=MSHCTX_LOCAL && dwDestContext!=MSHCTX_NOSHAREDMEM
+                && dwDestContext!=MSHCTX_INPROC))
+        return E_INVALIDARG;
+
+    *pCid = CLSID_CUri;
+    return S_OK;
+}
+
+struct inproc_marshal_uri {
+    DWORD size;
+    DWORD mshlflags;
+    DWORD unk[4]; /* process identifier? */
+    Uri *uri;
+};
+
+static HRESULT WINAPI Marshal_GetMarshalSizeMax(IMarshal *iface, REFIID riid, void *pv,
+        DWORD dwDestContext, void *pvDestContext, DWORD mshlflags, DWORD *pSize)
+{
+    Uri *This = impl_from_IMarshal(iface);
+    ULARGE_INTEGER size;
+    HRESULT hres;
+    TRACE("(%p)->(%s %p %x %p %x %p)\n", This, debugstr_guid(riid), pv,
+            dwDestContext, pvDestContext, mshlflags, pSize);
+
+    if(!pSize || (dwDestContext!=MSHCTX_LOCAL && dwDestContext!=MSHCTX_NOSHAREDMEM
+                && dwDestContext!=MSHCTX_INPROC))
+        return E_INVALIDARG;
+
+    if(dwDestContext == MSHCTX_INPROC) {
+        *pSize = sizeof(struct inproc_marshal_uri);
+        return S_OK;
+    }
+
+    hres = IPersistStream_GetSizeMax(&This->IPersistStream_iface, &size);
+    if(FAILED(hres))
+        return hres;
+    if(!This->path_len && (This->scheme_type==URL_SCHEME_HTTP
+                || This->scheme_type==URL_SCHEME_HTTPS
+                || This->scheme_type==URL_SCHEME_FTP))
+        size.u.LowPart += 3*sizeof(DWORD);
+    *pSize = size.u.LowPart+2*sizeof(DWORD);
+    return S_OK;
+}
+
+static HRESULT WINAPI Marshal_MarshalInterface(IMarshal *iface, IStream *pStm, REFIID riid,
+        void *pv, DWORD dwDestContext, void *pvDestContext, DWORD mshlflags)
+{
+    Uri *This = impl_from_IMarshal(iface);
+    DWORD *data;
+    DWORD size;
+    HRESULT hres;
+
+    TRACE("(%p)->(%p %s %p %x %p %x)\n", This, pStm, debugstr_guid(riid), pv,
+            dwDestContext, pvDestContext, mshlflags);
+
+    if(!pStm || mshlflags!=MSHLFLAGS_NORMAL || (dwDestContext!=MSHCTX_LOCAL
+                && dwDestContext!=MSHCTX_NOSHAREDMEM && dwDestContext!=MSHCTX_INPROC))
+        return E_INVALIDARG;
+
+    if(dwDestContext == MSHCTX_INPROC) {
+        struct inproc_marshal_uri data;
+
+        data.size = sizeof(data);
+        data.mshlflags = MSHCTX_INPROC;
+        data.unk[0] = 0;
+        data.unk[1] = 0;
+        data.unk[2] = 0;
+        data.unk[3] = 0;
+        data.uri = This;
+
+        hres = IStream_Write(pStm, &data, data.size, NULL);
+        if(FAILED(hres))
+            return hres;
+
+        IUri_AddRef(&This->IUri_iface);
+        return S_OK;
+    }
+
+    hres = IMarshal_GetMarshalSizeMax(iface, riid, pv, dwDestContext,
+            pvDestContext, mshlflags, &size);
+    if(FAILED(hres))
+        return hres;
+
+    data = heap_alloc_zero(size);
+    if(!data)
+        return E_OUTOFMEMORY;
+
+    data[0] = size;
+    data[1] = dwDestContext;
+    data[2] = size-2*sizeof(DWORD);
+    persist_stream_save(This, pStm, TRUE, (struct persist_uri*)(data+2));
+
+    hres = IStream_Write(pStm, data, data[0]-2, NULL);
+    heap_free(data);
+    return hres;
+}
+
+static HRESULT WINAPI Marshal_UnmarshalInterface(IMarshal *iface,
+        IStream *pStm, REFIID riid, void **ppv)
+{
+    Uri *This = impl_from_IMarshal(iface);
+    DWORD header[2];
+    HRESULT hres;
+
+    TRACE("(%p)->(%p %s %p)\n", This, pStm, debugstr_guid(riid), ppv);
+
+    if(This->create_flags)
+        return E_UNEXPECTED;
+    if(!pStm || !riid || !ppv)
+        return E_INVALIDARG;
+
+    hres = IStream_Read(pStm, header, sizeof(header), NULL);
+    if(FAILED(hres))
+        return hres;
+
+    if(header[1]!=MSHCTX_LOCAL && header[1]!=MSHCTX_NOSHAREDMEM
+            && header[1]!=MSHCTX_INPROC)
+        return E_UNEXPECTED;
+
+    if(header[1] == MSHCTX_INPROC) {
+        struct inproc_marshal_uri data;
+        parse_data parse;
+
+        hres = IStream_Read(pStm, data.unk, sizeof(data)-2*sizeof(DWORD), NULL);
+        if(FAILED(hres))
+            return hres;
+
+        This->raw_uri = SysAllocString(data.uri->raw_uri);
+        if(!This->raw_uri) {
+            return E_OUTOFMEMORY;
+        }
+
+        memset(&parse, 0, sizeof(parse_data));
+        parse.uri = This->raw_uri;
+
+        if(!parse_uri(&parse, data.uri->create_flags))
+            return E_INVALIDARG;
+
+        hres = canonicalize_uri(&parse, This, data.uri->create_flags);
+        if(FAILED(hres))
+            return hres;
+
+        This->create_flags = data.uri->create_flags;
+        IUri_Release(&data.uri->IUri_iface);
+
+        return IUri_QueryInterface(&This->IUri_iface, riid, ppv);
+    }
+
+    hres = IPersistStream_Load(&This->IPersistStream_iface, pStm);
+    if(FAILED(hres))
+        return hres;
+
+    return IUri_QueryInterface(&This->IUri_iface, riid, ppv);
+}
+
+static HRESULT WINAPI Marshal_ReleaseMarshalData(IMarshal *iface, IStream *pStm)
+{
+    Uri *This = impl_from_IMarshal(iface);
+    LARGE_INTEGER off;
+    DWORD header[2];
+    HRESULT hres;
+
+    TRACE("(%p)->(%p)\n", This, pStm);
+
+    if(!pStm)
+        return E_INVALIDARG;
+
+    hres = IStream_Read(pStm, header, 2*sizeof(DWORD), NULL);
+    if(FAILED(hres))
+        return hres;
+
+    if(header[1] == MSHCTX_INPROC) {
+        struct inproc_marshal_uri data;
+
+        hres = IStream_Read(pStm, data.unk, sizeof(data)-2*sizeof(DWORD), NULL);
+        if(FAILED(hres))
+            return hres;
+
+        IUri_Release(&data.uri->IUri_iface);
+        return S_OK;
+    }
+
+    off.u.LowPart = header[0]-sizeof(header)-2;
+    off.u.HighPart = 0;
+    return IStream_Seek(pStm, off, STREAM_SEEK_CUR, NULL);
+}
+
+static HRESULT WINAPI Marshal_DisconnectObject(IMarshal *iface, DWORD dwReserved)
+{
+    Uri *This = impl_from_IMarshal(iface);
+    TRACE("(%p)->(%x)\n", This, dwReserved);
+    return S_OK;
+}
+
+static const IMarshalVtbl MarshalVtbl = {
+    Marshal_QueryInterface,
+    Marshal_AddRef,
+    Marshal_Release,
+    Marshal_GetUnmarshalClass,
+    Marshal_GetMarshalSizeMax,
+    Marshal_MarshalInterface,
+    Marshal_UnmarshalInterface,
+    Marshal_ReleaseMarshalData,
+    Marshal_DisconnectObject
+};
+
+HRESULT Uri_Construct(IUnknown *pUnkOuter, LPVOID *ppobj)
+{
+    Uri *ret = heap_alloc_zero(sizeof(Uri));
+
+    TRACE("(%p %p)\n", pUnkOuter, ppobj);
+
+    *ppobj = ret;
+    if(!ret)
+        return E_OUTOFMEMORY;
+
+    ret->IUri_iface.lpVtbl = &UriVtbl;
+    ret->IUriBuilderFactory_iface.lpVtbl = &UriBuilderFactoryVtbl;
+    ret->IPersistStream_iface.lpVtbl = &PersistStreamVtbl;
+    ret->IMarshal_iface.lpVtbl = &MarshalVtbl;
+    ret->ref = 1;
+
+    *ppobj = &ret->IUri_iface;
+    return S_OK;
 }
 
 /***********************************************************************
@@ -5071,8 +5676,8 @@ static Uri* create_uri_obj(void) {
  *
  * RETURNS
  *  Success: Returns S_OK. ppURI contains the pointer to the newly allocated IUri.
- *  Failure: E_INVALIDARG if there's invalid flag combinations in dwFlags, or an
- *           invalid parameters, or pwzURI doesn't represnt a valid URI.
+ *  Failure: E_INVALIDARG if there are invalid flag combinations in dwFlags, or an
+ *           invalid parameter, or pwzURI doesn't represent a valid URI.
  *           E_OUTOFMEMORY if any memory allocation fails.
  *
  * NOTES
@@ -5096,7 +5701,7 @@ HRESULT WINAPI CreateUri(LPCWSTR pwzURI, DWORD dwFlags, DWORD_PTR dwReserved, IU
     if(!ppURI)
         return E_INVALIDARG;
 
-    if(!pwzURI || !*pwzURI) {
+    if(!pwzURI) {
         *ppURI = NULL;
         return E_INVALIDARG;
     }
@@ -5111,10 +5716,10 @@ HRESULT WINAPI CreateUri(LPCWSTR pwzURI, DWORD dwFlags, DWORD_PTR dwReserved, IU
     if(dwFlags & ~supported_flags)
         FIXME("Ignoring unsupported flag(s) %x\n", dwFlags & ~supported_flags);
 
-    ret = create_uri_obj();
-    if(!ret) {
+    hr = Uri_Construct(NULL, (void**)&ret);
+    if(FAILED(hr)) {
         *ppURI = NULL;
-        return E_OUTOFMEMORY;
+        return hr;
     }
 
     /* Explicitly set the default flags if it doesn't cause a flag conflict. */
@@ -5134,7 +5739,7 @@ HRESULT WINAPI CreateUri(LPCWSTR pwzURI, DWORD dwFlags, DWORD_PTR dwReserved, IU
     memset(&data, 0, sizeof(parse_data));
     data.uri = ret->raw_uri;
 
-    /* Validate and parse the URI into it's components. */
+    /* Validate and parse the URI into its components. */
     if(!parse_uri(&data, dwFlags)) {
         /* Encountered an unsupported or invalid URI */
         IUri_Release(&ret->IUri_iface);
@@ -5164,7 +5769,7 @@ HRESULT WINAPI CreateUri(LPCWSTR pwzURI, DWORD dwFlags, DWORD_PTR dwReserved, IU
  *
  * PARAMS
  *  pwzURI      [I] The URI to parse and perform canonicalization on.
- *  pwzFragment [I] The explict fragment string which should be added to pwzURI.
+ *  pwzFragment [I] The explicit fragment string which should be added to pwzURI.
  *  dwFlags     [I] The flags which will be passed to CreateUri.
  *  dwReserved  [I] Reserved (not used).
  *  ppURI       [O] The resulting IUri after parsing/canonicalization.
@@ -5173,7 +5778,7 @@ HRESULT WINAPI CreateUri(LPCWSTR pwzURI, DWORD dwFlags, DWORD_PTR dwReserved, IU
  *  Success: S_OK. ppURI contains the pointer to the newly allocated IUri.
  *  Failure: E_INVALIDARG if pwzURI already contains a fragment and pwzFragment
  *           isn't NULL. Will also return E_INVALIDARG for the same reasons as
- *           CreateUri will. E_OUTOFMEMORY if any allocations fail.
+ *           CreateUri will. E_OUTOFMEMORY if any allocation fails.
  */
 HRESULT WINAPI CreateUriWithFragment(LPCWSTR pwzURI, LPCWSTR pwzFragment, DWORD dwFlags,
                                      DWORD_PTR dwReserved, IUri **ppURI)
@@ -5271,10 +5876,10 @@ static HRESULT build_uri(const UriBuilder *builder, IUri **uri, DWORD create_fla
         return hr;
     }
 
-    ret = create_uri_obj();
-    if(!ret) {
+    hr = Uri_Construct(NULL, (void**)&ret);
+    if(FAILED(hr)) {
         *uri = NULL;
-        return E_OUTOFMEMORY;
+        return hr;
     }
 
     hr = generate_uri(builder, &data, ret, create_flags);
@@ -5426,7 +6031,7 @@ static HRESULT WINAPI UriBuilder_SetIUri(IUriBuilder *iface, IUri *pIUri)
         Uri *uri;
 
         if((uri = get_uri_obj(pIUri))) {
-            /* Only reset the builder if it's Uri isn't the same as
+            /* Only reset the builder if its Uri isn't the same as
              * the Uri passed to the function.
              */
             if(This->uri != uri) {
@@ -5443,7 +6048,7 @@ static HRESULT WINAPI UriBuilder_SetIUri(IUriBuilder *iface, IUri *pIUri)
             return E_NOTIMPL;
         }
     } else if(This->uri)
-        /* Only reset the builder if it's Uri isn't NULL. */
+        /* Only reset the builder if its Uri isn't NULL. */
         reset_builder(This);
 
     return S_OK;
@@ -5753,6 +6358,10 @@ HRESULT WINAPI CreateIUriBuilder(IUri *pIUri, DWORD dwFlags, DWORD_PTR dwReserve
         Uri *uri;
 
         if((uri = get_uri_obj(pIUri))) {
+            if(!uri->create_flags) {
+                heap_free(ret);
+                return E_UNEXPECTED;
+            }
             IUri_AddRef(pIUri);
             ret->uri = uri;
 
@@ -5784,13 +6393,25 @@ static HRESULT merge_paths(parse_data *data, const WCHAR *base, DWORD base_len,
     WCHAR *ptr;
 
     if(base_len) {
-        /* Find the characters the will be copied over from
-         * the base path.
-         */
-        end = memrchrW(base, '/', base_len);
-        if(!end && data->scheme_type == URL_SCHEME_FILE)
-            /* Try looking for a '\\'. */
-            end = memrchrW(base, '\\', base_len);
+        if(data->scheme_type == URL_SCHEME_MK && *relative == '/') {
+            /* Find '::' segment */
+            for(end = base; end < base+base_len-1; end++) {
+                if(end[0] == ':' && end[1] == ':') {
+                    end++;
+                    break;
+                }
+            }
+
+            /* If not found, try finding the end of @xxx: */
+            if(end == base+base_len-1)
+                end = *base == '@' ? memchr(base, ':', base_len) : NULL;
+        }else {
+            /* Find the characters that will be copied over from the base path. */
+            end = memrchrW(base, '/', base_len);
+            if(!end && data->scheme_type == URL_SCHEME_FILE)
+                /* Try looking for a '\\'. */
+                end = memrchrW(base, '\\', base_len);
+        }
     }
 
     if(end) {
@@ -5815,6 +6436,7 @@ static HRESULT merge_paths(parse_data *data, const WCHAR *base, DWORD base_len,
     *ptr = '\0';
 
     *result_len = (ptr-*result);
+    TRACE("ret %s\n", debugstr_wn(*result, *result_len));
     return S_OK;
 }
 
@@ -5822,6 +6444,7 @@ static HRESULT combine_uri(Uri *base, Uri *relative, DWORD flags, IUri **result,
     Uri *ret;
     HRESULT hr;
     parse_data data;
+    Uri *proc_uri = base;
     DWORD create_flags = 0, len = 0;
 
     memset(&data, 0, sizeof(parse_data));
@@ -5837,12 +6460,12 @@ static HRESULT combine_uri(Uri *base, Uri *relative, DWORD flags, IUri **result,
             return E_OUTOFMEMORY;
         }
 
-        parse_uri(&data, 0);
+        parse_uri(&data, Uri_CREATE_ALLOW_IMPLICIT_FILE_SCHEME);
 
-        ret = create_uri_obj();
-        if(!ret) {
+        hr = Uri_Construct(NULL, (void**)&ret);
+        if(FAILED(hr)) {
             *result = NULL;
-            return E_OUTOFMEMORY;
+            return hr;
         }
 
         if(extras & COMBINE_URI_FORCE_FLAG_USE) {
@@ -5878,49 +6501,52 @@ static HRESULT combine_uri(Uri *base, Uri *relative, DWORD flags, IUri **result,
             create_flags |= Uri_CREATE_ALLOW_RELATIVE;
         }
 
-        if(base->authority_start > -1) {
-            if(base->userinfo_start > -1 && base->userinfo_split != 0) {
-                data.username = base->canon_uri+base->userinfo_start;
-                data.username_len = (base->userinfo_split > -1) ? base->userinfo_split : base->userinfo_len;
+        if(relative->authority_start > -1)
+            proc_uri = relative;
+
+        if(proc_uri->authority_start > -1) {
+            if(proc_uri->userinfo_start > -1 && proc_uri->userinfo_split != 0) {
+                data.username = proc_uri->canon_uri+proc_uri->userinfo_start;
+                data.username_len = (proc_uri->userinfo_split > -1) ? proc_uri->userinfo_split : proc_uri->userinfo_len;
             }
 
-            if(base->userinfo_split > -1) {
-                data.password = base->canon_uri+base->userinfo_start+base->userinfo_split+1;
-                data.password_len = base->userinfo_len-base->userinfo_split-1;
+            if(proc_uri->userinfo_split > -1) {
+                data.password = proc_uri->canon_uri+proc_uri->userinfo_start+proc_uri->userinfo_split+1;
+                data.password_len = proc_uri->userinfo_len-proc_uri->userinfo_split-1;
             }
 
-            if(base->host_start > -1) {
-                data.host = base->canon_uri+base->host_start;
-                data.host_len = base->host_len;
-                data.host_type = base->host_type;
+            if(proc_uri->host_start > -1) {
+                data.host = proc_uri->canon_uri+proc_uri->host_start;
+                data.host_len = proc_uri->host_len;
+                data.host_type = proc_uri->host_type;
             }
 
-            if(base->has_port) {
+            if(proc_uri->has_port) {
                 data.has_port = TRUE;
-                data.port_value = base->port;
+                data.port_value = proc_uri->port;
             }
         } else if(base->scheme_type != URL_SCHEME_FILE)
             data.is_opaque = TRUE;
 
-        if(relative->path_start == -1 || !relative->path_len) {
-            if(base->path_start > -1) {
-                data.path = base->canon_uri+base->path_start;
-                data.path_len = base->path_len;
-            } else if((base->path_start == -1 || !base->path_len) && !data.is_opaque) {
+        if(proc_uri == relative || relative->path_start == -1 || !relative->path_len) {
+            if(proc_uri->path_start > -1) {
+                data.path = proc_uri->canon_uri+proc_uri->path_start;
+                data.path_len = proc_uri->path_len;
+            } else if(!data.is_opaque) {
                 /* Just set the path as a '/' if the base didn't have
-                 * one and if it's an hierarchical URI.
+                 * one and if it's a hierarchical URI.
                  */
                 static const WCHAR slashW[] = {'/',0};
                 data.path = slashW;
                 data.path_len = 1;
             }
 
-            if(relative->query_start > -1) {
-                data.query = relative->canon_uri+relative->query_start;
-                data.query_len = relative->query_len;
-            } else if(base->query_start > -1) {
-                data.query = base->canon_uri+base->query_start;
-                data.query_len = base->query_len;
+            if(relative->query_start > -1)
+                proc_uri = relative;
+
+            if(proc_uri->query_start > -1) {
+                data.query = proc_uri->canon_uri+proc_uri->query_start;
+                data.query_len = proc_uri->query_len;
             }
         } else {
             const WCHAR *ptr, **pptr;
@@ -5932,7 +6558,7 @@ static HRESULT combine_uri(Uri *base, Uri *relative, DWORD flags, IUri **result,
              * relative path doesn't begin with a '/' then the base path and relative
              * path are merged together.
              */
-            if(relative->path_len && *(relative->canon_uri+relative->path_start) == '/') {
+            if(relative->path_len && *(relative->canon_uri+relative->path_start) == '/' && data.scheme_type != URL_SCHEME_MK) {
                 WCHAR *tmp = NULL;
                 BOOL copy_drive_path = FALSE;
 
@@ -6043,12 +6669,12 @@ static HRESULT combine_uri(Uri *base, Uri *relative, DWORD flags, IUri **result,
 
         generate_raw_uri(&data, data.uri, raw_flags);
 
-        ret = create_uri_obj();
-        if(!ret) {
+        hr = Uri_Construct(NULL, (void**)&ret);
+        if(FAILED(hr)) {
             SysFreeString(data.uri);
             heap_free(path);
             *result = NULL;
-            return E_OUTOFMEMORY;
+            return hr;
         }
 
         if(flags & URL_DONT_SIMPLIFY)
@@ -6173,7 +6799,7 @@ HRESULT WINAPI CoInternetCombineUrlEx(IUri *pBaseUri, LPCWSTR pwzRelativeUrl, DW
         }
     }
 
-    hr = CreateUri(pwzRelativeUrl, Uri_CREATE_ALLOW_RELATIVE, 0, &relative);
+    hr = CreateUri(pwzRelativeUrl, Uri_CREATE_ALLOW_RELATIVE|Uri_CREATE_ALLOW_IMPLICIT_FILE_SCHEME, 0, &relative);
     if(FAILED(hr)) {
         *ppCombinedUri = NULL;
         return hr;
@@ -6279,7 +6905,7 @@ static HRESULT parse_canonicalize(const Uri *uri, DWORD flags, LPWSTR output,
 
     buffer[len++] = 0;
 
-    /* The null terminator isn't included the length. */
+    /* The null terminator isn't included in the length. */
     *result_len = len-1;
     if(len > output_len)
         return STRSAFE_E_INSUFFICIENT_BUFFER;