[WINHTTP] Sync with Wine Staging 3.9. CORE-14656
[reactos.git] / dll / win32 / winhttp / url.c
index 6ead33c..32b30da 100644 (file)
@@ -34,39 +34,147 @@ WINE_DEFAULT_DEBUG_CHANNEL(winhttp);
 static const WCHAR scheme_http[] = {'h','t','t','p',0};
 static const WCHAR scheme_https[] = {'h','t','t','p','s',0};
 
-static BOOL set_component( WCHAR **str, DWORD *str_len, WCHAR *value, DWORD len, DWORD flags )
+static DWORD set_component( WCHAR **str, DWORD *str_len, WCHAR *value, DWORD len, DWORD flags, BOOL *overflow )
 {
+    if (*str && !*str_len) return ERROR_INVALID_PARAMETER;
+    if (!*str_len) return ERROR_SUCCESS;
     if (!*str)
     {
-        if (len && (flags & ICU_DECODE))
-        {
-            set_last_error( ERROR_INVALID_PARAMETER );
-            return FALSE;
-        }
+        if (len && *str_len && (flags & (ICU_DECODE|ICU_ESCAPE))) return ERROR_INVALID_PARAMETER;
         *str = value;
         *str_len = len;
     }
     else
     {
-        if (len > (*str_len) - 1)
+        if (len >= *str_len)
         {
-            *str_len = len + 1;
-            set_last_error( ERROR_INSUFFICIENT_BUFFER );
-            return FALSE;
+            *str_len = len+1;
+            *overflow = TRUE;
+            return ERROR_SUCCESS;
         }
         memcpy( *str, value, len * sizeof(WCHAR) );
         (*str)[len] = 0;
         *str_len = len;
     }
-    return TRUE;
+    return ERROR_SUCCESS;
+}
+
+static WCHAR *decode_url( LPCWSTR url, DWORD *len )
+{
+    const WCHAR *p = url;
+    WCHAR hex[3], *q, *ret;
+
+    if (!(ret = heap_alloc( *len * sizeof(WCHAR) ))) return NULL;
+    q = ret;
+    while (*len > 0)
+    {
+        if (p[0] == '%' && isxdigitW( p[1] ) && isxdigitW( p[2] ))
+        {
+            hex[0] = p[1];
+            hex[1] = p[2];
+            hex[2] = 0;
+            *q++ = strtolW( hex, NULL, 16 );
+            p += 3;
+            *len -= 3;
+        }
+        else
+        {
+            *q++ = *p++;
+            *len -= 1;
+        }
+    }
+    *len = q - ret;
+    return ret;
+}
+
+static BOOL need_escape( WCHAR c )
+{
+    if (isalnumW( c )) return FALSE;
+
+    if (c <= 31 || c >= 127) return TRUE;
+    else
+    {
+        switch (c)
+        {
+        case ' ':
+        case '"':
+        case '#':
+        case '%':
+        case '<':
+        case '>':
+        case ']':
+        case '\\':
+        case '[':
+        case '^':
+        case '`':
+        case '{':
+        case '|':
+        case '}':
+        case '~':
+            return TRUE;
+        default:
+            return FALSE;
+        }
+    }
+}
+
+static DWORD copy_escape( WCHAR *dst, const WCHAR *src, DWORD len )
+{
+    static const WCHAR hex[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
+    DWORD ret = len;
+    unsigned int i;
+    WCHAR *p = dst;
+
+    for (i = 0; i < len; i++, p++)
+    {
+        if (need_escape( src[i] ))
+        {
+            p[0] = '%';
+            p[1] = hex[(src[i] >> 4) & 0xf];
+            p[2] = hex[src[i] & 0xf];
+            ret += 2;
+            p += 2;
+        }
+        else *p = src[i];
+    }
+    dst[ret] = 0;
+    return ret;
 }
 
-static BOOL decode_url( LPCWSTR url, LPWSTR buffer, LPDWORD buflen )
+static WCHAR *escape_url( LPCWSTR url, DWORD *len )
 {
-    HRESULT hr = UrlCanonicalizeW( url, buffer, buflen, URL_WININET_COMPATIBILITY | URL_UNESCAPE );
-    if (hr == E_POINTER) set_last_error( ERROR_INSUFFICIENT_BUFFER );
-    if (hr == E_INVALIDARG) set_last_error( ERROR_INVALID_PARAMETER );
-    return (SUCCEEDED(hr)) ? TRUE : FALSE;
+    WCHAR *ret;
+    const WCHAR *p, *q;
+
+    if ((p = q = strrchrW( url, '/' )))
+    {
+        while (*q)
+        {
+            if (need_escape( *q )) *len += 2;
+            q++;
+        }
+    }
+    if (!(ret = heap_alloc( (*len + 1) * sizeof(WCHAR) ))) return NULL;
+    if (!p) strcpyW( ret, url );
+    else
+    {
+        memcpy( ret, url, (p - url) * sizeof(WCHAR) );
+        copy_escape( ret + (p - url), p, q - p );
+    }
+    return ret;
+}
+
+static DWORD parse_port( const WCHAR *str, DWORD len, INTERNET_PORT *ret )
+{
+    const WCHAR *p = str;
+    DWORD port = 0;
+    while (len && isdigitW( *p ))
+    {
+        if ((port = port * 10 + *p - '0') > 65535) return ERROR_WINHTTP_INVALID_URL;
+        p++; len--;
+    }
+    *ret = port;
+    return ERROR_SUCCESS;
 }
 
 /***********************************************************************
@@ -74,13 +182,12 @@ static BOOL decode_url( LPCWSTR url, LPWSTR buffer, LPDWORD buflen )
  */
 BOOL WINAPI WinHttpCrackUrl( LPCWSTR url, DWORD len, DWORD flags, LPURL_COMPONENTSW uc )
 {
-    BOOL ret = FALSE;
-    WCHAR *p, *q, *r;
-    WCHAR *url_decoded = NULL;
-
-    TRACE("%s, %d, %x, %p\n", debugstr_w(url), len, flags, uc);
+    WCHAR *p, *q, *r, *url_decoded = NULL, *url_escaped = NULL;
+    INTERNET_SCHEME scheme = 0;
+    BOOL overflow = FALSE;
+    DWORD err;
 
-    if (flags & ICU_ESCAPE) FIXME("flag ICU_ESCAPE not supported\n");
+    TRACE("%s, %d, %x, %p\n", debugstr_wn(url, len), len, flags, uc);
 
     if (!url || !uc || uc->dwStructSize != sizeof(URL_COMPONENTS))
     {
@@ -89,126 +196,129 @@ BOOL WINAPI WinHttpCrackUrl( LPCWSTR url, DWORD len, DWORD flags, LPURL_COMPONEN
     }
     if (!len) len = strlenW( url );
 
-    if (flags & ICU_DECODE)
+    if (flags & ICU_ESCAPE)
     {
-        WCHAR *url_tmp;
-        DWORD url_len = len + 1;
-
-        if (!(url_tmp = HeapAlloc( GetProcessHeap(), 0, url_len * sizeof(WCHAR) )))
+        if (!(url_escaped = escape_url( url, &len )))
         {
             set_last_error( ERROR_OUTOFMEMORY );
             return FALSE;
         }
-        memcpy( url_tmp, url, len * sizeof(WCHAR) );
-        url_tmp[len] = 0;
-        if (!(url_decoded = HeapAlloc( GetProcessHeap(), 0, url_len * sizeof(WCHAR) )))
+        url = url_escaped;
+    }
+    else if (flags & ICU_DECODE)
+    {
+        if (!(url_decoded = decode_url( url, &len )))
         {
-            HeapFree( GetProcessHeap(), 0, url_tmp );
             set_last_error( ERROR_OUTOFMEMORY );
             return FALSE;
         }
-        if (decode_url( url_tmp, url_decoded, &url_len ))
-        {
-            len = url_len;
-            url = url_decoded;
-        }
-        HeapFree( GetProcessHeap(), 0, url_tmp );
+        url = url_decoded;
     }
     if (!(p = strchrW( url, ':' )))
     {
         set_last_error( ERROR_WINHTTP_UNRECOGNIZED_SCHEME );
         return FALSE;
     }
-    if (p - url == 4 && !strncmpiW( url, scheme_http, 4 )) uc->nScheme = INTERNET_SCHEME_HTTP;
-    else if (p - url == 5 && !strncmpiW( url, scheme_https, 5 )) uc->nScheme = INTERNET_SCHEME_HTTPS;
+    if (p - url == 4 && !strncmpiW( url, scheme_http, 4 )) scheme = INTERNET_SCHEME_HTTP;
+    else if (p - url == 5 && !strncmpiW( url, scheme_https, 5 )) scheme = INTERNET_SCHEME_HTTPS;
     else
     {
-        set_last_error( ERROR_WINHTTP_UNRECOGNIZED_SCHEME );
+        err = ERROR_WINHTTP_UNRECOGNIZED_SCHEME;
         goto exit;
     }
-    if (!(set_component( &uc->lpszScheme, &uc->dwSchemeLength, (WCHAR *)url, p - url, flags ))) goto exit;
+
+    if ((err = set_component( &uc->lpszScheme, &uc->dwSchemeLength, (WCHAR *)url, p - url, flags, &overflow ))) goto exit;
 
     p++; /* skip ':' */
-    if (!p[0] || p[0] != '/' || p[1] != '/') goto exit;
+    if (!p[0] || p[0] != '/' || p[1] != '/')
+    {
+        err = ERROR_WINHTTP_INVALID_URL;
+        goto exit;
+    }
     p += 2;
-
-    if (!p[0]) goto exit;
+    if (!p[0])
+    {
+        err = ERROR_WINHTTP_INVALID_URL;
+        goto exit;
+    }
     if ((q = memchrW( p, '@', len - (p - url) )) && !(memchrW( p, '/', q - p )))
     {
         if ((r = memchrW( p, ':', q - p )))
         {
-            if (!(set_component( &uc->lpszUserName, &uc->dwUserNameLength, p, r - p, flags ))) goto exit;
+            if ((err = set_component( &uc->lpszUserName, &uc->dwUserNameLength, p, r - p, flags, &overflow ))) goto exit;
             r++;
-            if (!(set_component( &uc->lpszPassword, &uc->dwPasswordLength, r, q - r, flags ))) goto exit;
+            if ((err = set_component( &uc->lpszPassword, &uc->dwPasswordLength, r, q - r, flags, &overflow ))) goto exit;
         }
         else
         {
-            if (!(set_component( &uc->lpszUserName, &uc->dwUserNameLength, p, q - p, flags ))) goto exit;
-            if (!(set_component( &uc->lpszPassword, &uc->dwPasswordLength, NULL, 0, flags ))) goto exit;
+            if ((err = set_component( &uc->lpszUserName, &uc->dwUserNameLength, p, q - p, flags, &overflow ))) goto exit;
+            if ((err = set_component( &uc->lpszPassword, &uc->dwPasswordLength, NULL, 0, flags, &overflow ))) goto exit;
         }
         p = q + 1;
     }
     else
     {
-        if (!(set_component( &uc->lpszUserName, &uc->dwUserNameLength, NULL, 0, flags ))) goto exit;
-        if (!(set_component( &uc->lpszPassword, &uc->dwPasswordLength, NULL, 0, flags ))) goto exit;
+        if ((err = set_component( &uc->lpszUserName, &uc->dwUserNameLength, NULL, 0, flags, &overflow ))) goto exit;
+        if ((err = set_component( &uc->lpszPassword, &uc->dwPasswordLength, NULL, 0, flags, &overflow ))) goto exit;
     }
     if ((q = memchrW( p, '/', len - (p - url) )))
     {
         if ((r = memchrW( p, ':', q - p )))
         {
-            if (!(set_component( &uc->lpszHostName, &uc->dwHostNameLength, p, r - p, flags ))) goto exit;
+            if ((err = set_component( &uc->lpszHostName, &uc->dwHostNameLength, p, r - p, flags, &overflow ))) goto exit;
             r++;
-            uc->nPort = atoiW( r );
+            if ((err = parse_port( r, q - r, &uc->nPort ))) goto exit;
         }
         else
         {
-            if (!(set_component( &uc->lpszHostName, &uc->dwHostNameLength, p, q - p, flags ))) goto exit;
-            if (uc->nScheme == INTERNET_SCHEME_HTTP) uc->nPort = INTERNET_DEFAULT_HTTP_PORT;
-            if (uc->nScheme == INTERNET_SCHEME_HTTPS) uc->nPort = INTERNET_DEFAULT_HTTPS_PORT;
+            if ((err = set_component( &uc->lpszHostName, &uc->dwHostNameLength, p, q - p, flags, &overflow ))) goto exit;
+            if (scheme == INTERNET_SCHEME_HTTP) uc->nPort = INTERNET_DEFAULT_HTTP_PORT;
+            if (scheme == INTERNET_SCHEME_HTTPS) uc->nPort = INTERNET_DEFAULT_HTTPS_PORT;
         }
 
         if ((r = memchrW( q, '?', len - (q - url) )))
         {
-            if (!(set_component( &uc->lpszUrlPath, &uc->dwUrlPathLength, q, r - q, flags ))) goto exit;
-            if (!(set_component( &uc->lpszExtraInfo, &uc->dwExtraInfoLength, r, len - (r - url), flags ))) goto exit;
+            if ((err = set_component( &uc->lpszUrlPath, &uc->dwUrlPathLength, q, r - q, flags, &overflow ))) goto exit;
+            if ((err = set_component( &uc->lpszExtraInfo, &uc->dwExtraInfoLength, r, len - (r - url), flags, &overflow ))) goto exit;
         }
         else
         {
-            if (!(set_component( &uc->lpszUrlPath, &uc->dwUrlPathLength, q, len - (q - url), flags ))) goto exit;
-            if (!(set_component( &uc->lpszExtraInfo, &uc->dwExtraInfoLength, (WCHAR *)url + len, 0, flags ))) goto exit;
+            if ((err = set_component( &uc->lpszUrlPath, &uc->dwUrlPathLength, q, len - (q - url), flags, &overflow ))) goto exit;
+            if ((err = set_component( &uc->lpszExtraInfo, &uc->dwExtraInfoLength, (WCHAR *)url + len, 0, flags, &overflow ))) goto exit;
         }
     }
     else
     {
         if ((r = memchrW( p, ':', len - (p - url) )))
         {
-            if (!(set_component( &uc->lpszHostName, &uc->dwHostNameLength, p, r - p, flags ))) goto exit;
+            if ((err = set_component( &uc->lpszHostName, &uc->dwHostNameLength, p, r - p, flags, &overflow ))) goto exit;
             r++;
-            uc->nPort = atoiW( r );
+            if ((err = parse_port( r, len - (r - url), &uc->nPort ))) goto exit;
         }
         else
         {
-            if (!(set_component( &uc->lpszHostName, &uc->dwHostNameLength, p, len - (p - url), flags ))) goto exit;
-            if (uc->nScheme == INTERNET_SCHEME_HTTP) uc->nPort = INTERNET_DEFAULT_HTTP_PORT;
-            if (uc->nScheme == INTERNET_SCHEME_HTTPS) uc->nPort = INTERNET_DEFAULT_HTTPS_PORT;
+            if ((err = set_component( &uc->lpszHostName, &uc->dwHostNameLength, p, len - (p - url), flags, &overflow ))) goto exit;
+            if (scheme == INTERNET_SCHEME_HTTP) uc->nPort = INTERNET_DEFAULT_HTTP_PORT;
+            if (scheme == INTERNET_SCHEME_HTTPS) uc->nPort = INTERNET_DEFAULT_HTTPS_PORT;
         }
-        if (!(set_component( &uc->lpszUrlPath, &uc->dwUrlPathLength, (WCHAR *)url + len, 0, flags ))) goto exit;
-        if (!(set_component( &uc->lpszExtraInfo, &uc->dwExtraInfoLength, (WCHAR *)url + len, 0, flags ))) goto exit;
+        if ((err = set_component( &uc->lpszUrlPath, &uc->dwUrlPathLength, (WCHAR *)url + len, 0, flags, &overflow ))) goto exit;
+        if ((err = set_component( &uc->lpszExtraInfo, &uc->dwExtraInfoLength, (WCHAR *)url + len, 0, flags, &overflow ))) goto exit;
     }
 
-    ret = TRUE;
-
-    TRACE("scheme(%s) host(%s) port(%d) path(%s) extra(%s)\n",
-          debugstr_wn( uc->lpszScheme, uc->dwSchemeLength ),
-          debugstr_wn( uc->lpszHostName, uc->dwHostNameLength ),
-          uc->nPort,
-          debugstr_wn( uc->lpszUrlPath, uc->dwUrlPathLength ),
+    TRACE("scheme(%s) host(%s) port(%d) path(%s) extra(%s)\n", debugstr_wn( uc->lpszScheme, uc->dwSchemeLength ),
+          debugstr_wn( uc->lpszHostName, uc->dwHostNameLength ), uc->nPort, debugstr_wn( uc->lpszUrlPath, uc->dwUrlPathLength ),
           debugstr_wn( uc->lpszExtraInfo, uc->dwExtraInfoLength ));
 
 exit:
-    HeapFree( GetProcessHeap(), 0, url_decoded );
-    return ret;
+    if (!err)
+    {
+        if (overflow) err = ERROR_INSUFFICIENT_BUFFER;
+        uc->nScheme = scheme;
+    }
+    heap_free( url_decoded );
+    heap_free( url_escaped );
+    set_last_error( err );
+    return !err;
 }
 
 static INTERNET_SCHEME get_scheme( const WCHAR *scheme, DWORD len )
@@ -232,60 +342,6 @@ static BOOL uses_default_port( INTERNET_SCHEME scheme, INTERNET_PORT port )
     return FALSE;
 }
 
-static BOOL need_escape( WCHAR c )
-{
-    if (isalnumW( c )) return FALSE;
-
-    if (c <= 31 || c >= 127) return TRUE;
-    else
-    {
-        switch (c)
-        {
-        case ' ':
-        case '"':
-        case '#':
-        case '%':
-        case '<':
-        case '>':
-        case ']':
-        case '\\':
-        case '[':
-        case '^':
-        case '`':
-        case '{':
-        case '|':
-        case '}':
-        case '~':
-            return TRUE;
-        default:
-            return FALSE;
-        }
-    }
-}
-
-static DWORD copy_escape( WCHAR *dst, const WCHAR *src, DWORD len )
-{
-    static const WCHAR hex[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
-    DWORD ret = len;
-    unsigned int i;
-    WCHAR *p = dst;
-
-    for (i = 0; i < len; i++, p++)
-    {
-        if (need_escape( src[i] ))
-        {
-            p[0] = '%';
-            p[1] = hex[(src[i] >> 4) & 0xf];
-            p[2] = hex[src[i] & 0xf];
-            ret += 2;
-            p += 2;
-        }
-        else *p = src[i];
-    }
-    dst[ret] = 0;
-    return ret;
-}
-
 static DWORD comp_length( DWORD len, DWORD flags, WCHAR *comp )
 {
     DWORD ret;
@@ -344,8 +400,7 @@ static BOOL calc_length( URL_COMPONENTS *uc, DWORD flags, LPDWORD len )
         {
             WCHAR port[sizeof("65535")];
 
-            sprintfW( port, formatW, uc->nPort );
-            *len += strlenW( port );
+            *len += sprintfW( port, formatW, uc->nPort );
             *len += 1; /* ":" */
         }
         if (uc->lpszUrlPath && *uc->lpszUrlPath != '/') *len += 1; /* '/' */
@@ -362,13 +417,12 @@ BOOL WINAPI WinHttpCreateUrl( LPURL_COMPONENTS uc, DWORD flags, LPWSTR url, LPDW
 {
     static const WCHAR formatW[] = {'%','u',0};
     static const WCHAR twoslashW[] = {'/','/'};
-
     DWORD len;
     INTERNET_SCHEME scheme;
 
     TRACE("%p, 0x%08x, %p, %p\n", uc, flags, url, required);
 
-    if (!uc || uc->dwStructSize != sizeof(URL_COMPONENTS) || !required || !url)
+    if (!uc || uc->dwStructSize != sizeof(URL_COMPONENTS) || !required)
     {
         set_last_error( ERROR_INVALID_PARAMETER );
         return FALSE;
@@ -382,6 +436,11 @@ BOOL WINAPI WinHttpCreateUrl( LPURL_COMPONENTS uc, DWORD flags, LPWSTR url, LPDW
         set_last_error( ERROR_INSUFFICIENT_BUFFER );
         return FALSE;
     }
+    if (!url)
+    {
+        set_last_error( ERROR_INVALID_PARAMETER );
+        return FALSE;
+    }
 
     url[0] = 0;
     *required = len;
@@ -441,15 +500,10 @@ BOOL WINAPI WinHttpCreateUrl( LPURL_COMPONENTS uc, DWORD flags, LPWSTR url, LPDW
 
         if (!uses_default_port( scheme, uc->nPort ))
         {
-            WCHAR port[sizeof("65535")];
-
-            sprintfW( port, formatW, uc->nPort );
             *url = ':';
             url++;
 
-            len = strlenW( port );
-            memcpy( url, port, len * sizeof(WCHAR) );
-            url += len;
+            url += sprintfW( url, formatW, uc->nPort );
         }
 
         /* add slash between hostname and path if necessary */
@@ -480,5 +534,6 @@ BOOL WINAPI WinHttpCreateUrl( LPURL_COMPONENTS uc, DWORD flags, LPWSTR url, LPDW
         }
     }
     *url = 0;
+    set_last_error( ERROR_SUCCESS );
     return TRUE;
 }