[WINHTTP] Sync with Wine Staging 3.9. CORE-14656
[reactos.git] / dll / win32 / winhttp / request.c
index e1e650b..5bb5b09 100644 (file)
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  */
 
-#include "winhttp_private.h"
+#define COBJMACROS
+#include "config.h"
+#include "wine/port.h"
 
+#include <stdarg.h>
+#include <assert.h>
 #ifdef HAVE_ARPA_INET_H
 # include <arpa/inet.h>
 #endif
 
-#include <assert.h>
-#include <winuser.h>
-#include <httprequest.h>
+#include "windef.h"
+#include "winbase.h"
+#include "ole2.h"
+#include "initguid.h"
+#include "httprequest.h"
+#include "httprequestid.h"
+#include "schannel.h"
+#include "winhttp.h"
+
+#include "winhttp_private.h"
+
+#include "wine/debug.h"
 
+WINE_DEFAULT_DEBUG_CHANNEL(winhttp);
+
+#ifdef __REACTOS__
 #include "inet_ntop.c"
+#endif
+
+#define DEFAULT_KEEP_ALIVE_TIMEOUT 30000
 
 static const WCHAR attr_accept[] = {'A','c','c','e','p','t',0};
 static const WCHAR attr_accept_charset[] = {'A','c','c','e','p','t','-','C','h','a','r','s','e','t', 0};
@@ -167,20 +186,92 @@ static const WCHAR *attribute_table[] =
     NULL                            /* WINHTTP_QUERY_PASSPORT_CONFIG            = 78 */
 };
 
-static DWORD CALLBACK task_thread( LPVOID param )
+static task_header_t *dequeue_task( request_t *request )
 {
-    task_header_t *task = param;
+    task_header_t *task;
 
-    task->proc( task );
+    EnterCriticalSection( &request->task_cs );
+    TRACE("%u tasks queued\n", list_count( &request->task_queue ));
+    task = LIST_ENTRY( list_head( &request->task_queue ), task_header_t, entry );
+    if (task) list_remove( &task->entry );
+    LeaveCriticalSection( &request->task_cs );
 
-    release_object( &task->request->hdr );
-    heap_free( task );
-    return ERROR_SUCCESS;
+    TRACE("returning task %p\n", task);
+    return task;
+}
+
+static DWORD CALLBACK task_proc( LPVOID param )
+{
+    request_t *request = param;
+    HANDLE handles[2];
+
+    handles[0] = request->task_wait;
+    handles[1] = request->task_cancel;
+    for (;;)
+    {
+        DWORD err = WaitForMultipleObjects( 2, handles, FALSE, INFINITE );
+        switch (err)
+        {
+        case WAIT_OBJECT_0:
+        {
+            task_header_t *task;
+            while ((task = dequeue_task( request )))
+            {
+                task->proc( task );
+                release_object( &task->request->hdr );
+                heap_free( task );
+            }
+            break;
+        }
+        case WAIT_OBJECT_0 + 1:
+            TRACE("exiting\n");
+            CloseHandle( request->task_cancel );
+            CloseHandle( request->task_wait );
+            request->task_cs.DebugInfo->Spare[0] = 0;
+            DeleteCriticalSection( &request->task_cs );
+            request->hdr.vtbl->destroy( &request->hdr );
+            return 0;
+
+        default:
+            ERR("wait failed %u (%u)\n", err, GetLastError());
+            break;
+        }
+    }
+    return 0;
 }
 
 static BOOL queue_task( task_header_t *task )
 {
-    return QueueUserWorkItem( task_thread, task, WT_EXECUTELONGFUNCTION );
+    request_t *request = task->request;
+
+    if (!request->task_thread)
+    {
+        if (!(request->task_wait = CreateEventW( NULL, FALSE, FALSE, NULL ))) return FALSE;
+        if (!(request->task_cancel = CreateEventW( NULL, FALSE, FALSE, NULL )))
+        {
+            CloseHandle( request->task_wait );
+            request->task_wait = NULL;
+            return FALSE;
+        }
+        if (!(request->task_thread = CreateThread( NULL, 0, task_proc, request, 0, NULL )))
+        {
+            CloseHandle( request->task_wait );
+            request->task_wait = NULL;
+            CloseHandle( request->task_cancel );
+            request->task_cancel = NULL;
+            return FALSE;
+        }
+        InitializeCriticalSection( &request->task_cs );
+        request->task_cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": request.task_cs");
+    }
+
+    EnterCriticalSection( &request->task_cs );
+    TRACE("queueing task %p\n", task );
+    list_add_tail( &request->task_queue, &task->entry );
+    LeaveCriticalSection( &request->task_cs );
+
+    SetEvent( request->task_wait );
+    return TRUE;
 }
 
 static void free_header( header_t *header )
@@ -249,12 +340,8 @@ static header_t *parse_header( LPCWSTR string )
 
     q++; /* skip past colon */
     while (*q == ' ') q++;
-    if (!*q)
-    {
-        WARN("no value in line %s\n", debugstr_w(string));
-        return header;
-    }
     len = strlenW( q );
+
     if (!(header->value = heap_alloc( (len + 1) * sizeof(WCHAR) )))
     {
         free_header( header );
@@ -288,25 +375,21 @@ static int get_header_index( request_t *request, LPCWSTR field, int requested_in
 
 static BOOL insert_header( request_t *request, header_t *header )
 {
-    DWORD count;
+    DWORD count = request->num_headers + 1;
     header_t *hdrs;
 
-    count = request->num_headers + 1;
-    if (count > 1)
+    if (request->headers)
         hdrs = heap_realloc_zero( request->headers, sizeof(header_t) * count );
     else
-        hdrs = heap_alloc_zero( sizeof(header_t) * count );
-
-    if (hdrs)
-    {
-        request->headers = hdrs;
-        request->headers[count - 1].field = strdupW( header->field );
-        request->headers[count - 1].value = strdupW( header->value );
-        request->headers[count - 1].is_request = header->is_request;
-        request->num_headers++;
-        return TRUE;
-    }
-    return FALSE;
+        hdrs = heap_alloc_zero( sizeof(header_t) );
+    if (!hdrs) return FALSE;
+
+    request->headers = hdrs;
+    request->headers[count - 1].field = strdupW( header->field );
+    request->headers[count - 1].value = strdupW( header->value );
+    request->headers[count - 1].is_request = header->is_request;
+    request->num_headers = count;
+    return TRUE;
 }
 
 static BOOL delete_header( request_t *request, DWORD index )
@@ -323,79 +406,68 @@ static BOOL delete_header( request_t *request, DWORD index )
     return TRUE;
 }
 
-static BOOL process_header( request_t *request, LPCWSTR field, LPCWSTR value, DWORD flags, BOOL request_only )
+BOOL process_header( request_t *request, LPCWSTR field, LPCWSTR value, DWORD flags, BOOL request_only )
 {
     int index;
-    header_t *header;
+    header_t hdr;
 
     TRACE("%s: %s 0x%08x\n", debugstr_w(field), debugstr_w(value), flags);
 
-    /* replace wins out over add */
-    if (flags & WINHTTP_ADDREQ_FLAG_REPLACE) flags &= ~WINHTTP_ADDREQ_FLAG_ADD;
-
-    if (flags & WINHTTP_ADDREQ_FLAG_ADD) index = -1;
-    else
-        index = get_header_index( request, field, 0, request_only );
-
-    if (index >= 0)
+    if ((index = get_header_index( request, field, 0, request_only )) >= 0)
     {
         if (flags & WINHTTP_ADDREQ_FLAG_ADD_IF_NEW) return FALSE;
-        header = &request->headers[index];
     }
-    else if (value)
+
+    if (flags & WINHTTP_ADDREQ_FLAG_REPLACE)
     {
-        header_t hdr;
+        if (index >= 0)
+        {
+            delete_header( request, index );
+            if (!value || !value[0]) return TRUE;
+        }
+        else if (!(flags & WINHTTP_ADDREQ_FLAG_ADD))
+        {
+            set_last_error( ERROR_WINHTTP_HEADER_NOT_FOUND );
+            return FALSE;
+        }
 
         hdr.field = (LPWSTR)field;
         hdr.value = (LPWSTR)value;
         hdr.is_request = request_only;
-
         return insert_header( request, &hdr );
     }
-    /* no value to delete */
-    else return TRUE;
-
-    if (flags & WINHTTP_ADDREQ_FLAG_REPLACE)
-    {
-        delete_header( request, index );
-        if (value)
-        {
-            header_t hdr;
-
-            hdr.field = (LPWSTR)field;
-            hdr.value = (LPWSTR)value;
-            hdr.is_request = request_only;
-
-            return insert_header( request, &hdr );
-        }
-        return TRUE;
-    }
-    else if (flags & (WINHTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA | WINHTTP_ADDREQ_FLAG_COALESCE_WITH_SEMICOLON))
+    else if (value)
     {
-        WCHAR sep, *tmp;
-        int len, orig_len, value_len;
 
-        orig_len = strlenW( header->value );
-        value_len = strlenW( value );
+        if ((flags & (WINHTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA | WINHTTP_ADDREQ_FLAG_COALESCE_WITH_SEMICOLON)) &&
+            index >= 0)
+        {
+            WCHAR *tmp;
+            int len, len_orig, len_value;
+            header_t *header = &request->headers[index];
 
-        if (flags & WINHTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA) sep = ',';
-        else sep = ';';
+            len_orig = strlenW( header->value );
+            len_value = strlenW( value );
 
-        len = orig_len + value_len + 2;
-        if ((tmp = heap_realloc( header->value, (len + 1) * sizeof(WCHAR) )))
-        {
+            len = len_orig + len_value + 2;
+            if (!(tmp = heap_realloc( header->value, (len + 1) * sizeof(WCHAR) ))) return FALSE;
             header->value = tmp;
+            header->value[len_orig++] = (flags & WINHTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA) ? ',' : ';';
+            header->value[len_orig++] = ' ';
 
-            header->value[orig_len] = sep;
-            orig_len++;
-            header->value[orig_len] = ' ';
-            orig_len++;
-
-            memcpy( &header->value[orig_len], value, value_len * sizeof(WCHAR) );
+            memcpy( &header->value[len_orig], value, len_value * sizeof(WCHAR) );
             header->value[len] = 0;
             return TRUE;
         }
+        else
+        {
+            hdr.field = (LPWSTR)field;
+            hdr.value = (LPWSTR)value;
+            hdr.is_request = request_only;
+            return insert_header( request, &hdr );
+        }
     }
+
     return TRUE;
 }
 
@@ -408,7 +480,8 @@ BOOL add_request_headers( request_t *request, LPCWSTR headers, DWORD len, DWORD
     if (len == ~0u) len = strlenW( headers );
     if (!len) return TRUE;
     if (!(buffer = heap_alloc( (len + 1) * sizeof(WCHAR) ))) return FALSE;
-    strcpyW( buffer, headers );
+    memcpy( buffer, headers, len * sizeof(WCHAR) );
+    buffer[len] = 0;
 
     p = buffer;
     do
@@ -450,9 +523,9 @@ BOOL WINAPI WinHttpAddRequestHeaders( HINTERNET hrequest, LPCWSTR headers, DWORD
     BOOL ret;
     request_t *request;
 
-    TRACE("%p, %s, 0x%x, 0x%08x\n", hrequest, debugstr_w(headers), len, flags);
+    TRACE("%p, %s, %u, 0x%08x\n", hrequest, debugstr_wn(headers, len), len, flags);
 
-    if (!headers)
+    if (!headers || !len)
     {
         set_last_error( ERROR_INVALID_PARAMETER );
         return FALSE;
@@ -472,6 +545,7 @@ BOOL WINAPI WinHttpAddRequestHeaders( HINTERNET hrequest, LPCWSTR headers, DWORD
     ret = add_request_headers( request, headers, len, flags );
 
     release_object( &request->hdr );
+    if (ret) set_last_error( ERROR_SUCCESS );
     return ret;
 }
 
@@ -484,7 +558,7 @@ static WCHAR *build_request_path( request_t *request )
         static const WCHAR http[] = { 'h','t','t','p',0 };
         static const WCHAR https[] = { 'h','t','t','p','s',0 };
         static const WCHAR fmt[] = { '%','s',':','/','/','%','s',0 };
-        LPCWSTR scheme = request->netconn.secure ? https : http;
+        LPCWSTR scheme = (request->netconn ? request->netconn->secure : (request->hdr.flags & WINHTTP_FLAG_SECURE)) ? https : http;
         int len;
 
         len = strlenW( scheme ) + strlenW( request->connect->hostname );
@@ -602,11 +676,8 @@ static BOOL query_headers( request_t *request, DWORD level, LPCWSTR name, LPVOID
         if (!(p = headers)) return FALSE;
         for (len = 0; *p; p++) if (*p != '\r') len++;
 
-        if (!buffer || (len + 1) * sizeof(WCHAR) > *buflen)
-        {
-            len++;
+        if (!buffer || len * sizeof(WCHAR) > *buflen)
             set_last_error( ERROR_INSUFFICIENT_BUFFER );
-        }
         else
         {
             for (p = headers, q = buffer; *p; p++, q++)
@@ -618,8 +689,8 @@ static BOOL query_headers( request_t *request, DWORD level, LPCWSTR name, LPVOID
                     p++; /* skip '\n' */
                 }
             }
-            *q = 0;
             TRACE("returning data: %s\n", debugstr_wn(buffer, len));
+            if (len) len--;
             ret = TRUE;
         }
         *buflen = len * sizeof(WCHAR);
@@ -779,920 +850,1375 @@ BOOL WINAPI WinHttpQueryHeaders( HINTERNET hrequest, DWORD level, LPCWSTR name,
     ret = query_headers( request, level, name, buffer, buflen, index );
 
     release_object( &request->hdr );
+    if (ret) set_last_error( ERROR_SUCCESS );
     return ret;
 }
 
-static LPWSTR concatenate_string_list( LPCWSTR *list, int len )
-{
-    LPCWSTR *t;
-    LPWSTR str;
-
-    for( t = list; *t ; t++  )
-        len += strlenW( *t );
-    len++;
-
-    str = heap_alloc( len * sizeof(WCHAR) );
-    if (!str) return NULL;
-    *str = 0;
+#undef ARRAYSIZE
+#define ARRAYSIZE(array) (sizeof(array) / sizeof((array)[0]))
 
-    for( t = list; *t ; t++ )
-        strcatW( str, *t );
+static const WCHAR basicW[]     = {'B','a','s','i','c',0};
+static const WCHAR ntlmW[]      = {'N','T','L','M',0};
+static const WCHAR passportW[]  = {'P','a','s','s','p','o','r','t',0};
+static const WCHAR digestW[]    = {'D','i','g','e','s','t',0};
+static const WCHAR negotiateW[] = {'N','e','g','o','t','i','a','t','e',0};
 
-    return str;
+static const struct
+{
+    const WCHAR *str;
+    unsigned int len;
+    DWORD scheme;
 }
+auth_schemes[] =
+{
+    { basicW,     ARRAYSIZE(basicW) - 1,     WINHTTP_AUTH_SCHEME_BASIC },
+    { ntlmW,      ARRAYSIZE(ntlmW) - 1,      WINHTTP_AUTH_SCHEME_NTLM },
+    { passportW,  ARRAYSIZE(passportW) - 1,  WINHTTP_AUTH_SCHEME_PASSPORT },
+    { digestW,    ARRAYSIZE(digestW) - 1,    WINHTTP_AUTH_SCHEME_DIGEST },
+    { negotiateW, ARRAYSIZE(negotiateW) - 1, WINHTTP_AUTH_SCHEME_NEGOTIATE }
+};
+static const unsigned int num_auth_schemes = sizeof(auth_schemes)/sizeof(auth_schemes[0]);
 
-static LPWSTR build_header_request_string( request_t *request, LPCWSTR verb,
-    LPCWSTR path, LPCWSTR version )
+static enum auth_scheme scheme_from_flag( DWORD flag )
 {
-    static const WCHAR crlf[] = {'\r','\n',0};
-    static const WCHAR space[] = { ' ',0 };
-    static const WCHAR colon[] = { ':',' ',0 };
-    static const WCHAR twocrlf[] = {'\r','\n','\r','\n', 0};
-    LPWSTR requestString;
-    DWORD len, n;
-    LPCWSTR *req;
-    UINT i;
-    LPWSTR p;
+    int i;
 
-    /* allocate space for an array of all the string pointers to be added */
-    len = (request->num_headers) * 4 + 10;
-    req = heap_alloc( len * sizeof(LPCWSTR) );
-    if (!req) return NULL;
+    for (i = 0; i < num_auth_schemes; i++) if (flag == auth_schemes[i].scheme) return i;
+    return SCHEME_INVALID;
+}
 
-    /* add the verb, path and HTTP version string */
-    n = 0;
-    req[n++] = verb;
-    req[n++] = space;
-    req[n++] = path;
-    req[n++] = space;
-    req[n++] = version;
+static DWORD auth_scheme_from_header( WCHAR *header )
+{
+    unsigned int i;
 
-    /* Append custom request headers */
-    for (i = 0; i < request->num_headers; i++)
+    for (i = 0; i < num_auth_schemes; i++)
     {
-        if (request->headers[i].is_request)
-        {
-            req[n++] = crlf;
-            req[n++] = request->headers[i].field;
-            req[n++] = colon;
-            req[n++] = request->headers[i].value;
-
-            TRACE("Adding custom header %s (%s)\n",
-                   debugstr_w(request->headers[i].field),
-                   debugstr_w(request->headers[i].value));
-        }
+        if (!strncmpiW( header, auth_schemes[i].str, auth_schemes[i].len ) &&
+            (header[auth_schemes[i].len] == ' ' || !header[auth_schemes[i].len])) return auth_schemes[i].scheme;
     }
-
-    if( n >= len )
-        ERR("oops. buffer overrun\n");
-
-    req[n] = NULL;
-    requestString = concatenate_string_list( req, 4 );
-    heap_free( req );
-    if (!requestString) return NULL;
-
-    /*
-     * Set (header) termination string for request
-     * Make sure there are exactly two new lines at the end of the request
-     */
-    p = &requestString[strlenW(requestString)-1];
-    while ( (*p == '\n') || (*p == '\r') )
-       p--;
-    strcpyW( p+1, twocrlf );
-
-    return requestString;
+    return 0;
 }
 
-static BOOL read_reply( request_t *request );
-
-static BOOL secure_proxy_connect( request_t *request )
+static BOOL query_auth_schemes( request_t *request, DWORD level, LPDWORD supported, LPDWORD first )
 {
-    static const WCHAR verbConnect[] = {'C','O','N','N','E','C','T',0};
-    static const WCHAR fmt[] = {'%','s',':','%','u',0};
+    DWORD index = 0, supported_schemes = 0, first_scheme = 0;
     BOOL ret = FALSE;
-    LPWSTR path;
-    connect_t *connect = request->connect;
 
-    path = heap_alloc( (strlenW( connect->hostname ) + 13) * sizeof(WCHAR) );
-    if (path)
+    for (;;)
     {
-        LPWSTR requestString;
+        WCHAR *buffer;
+        DWORD size, scheme;
 
-        sprintfW( path, fmt, connect->hostname, connect->hostport );
-        requestString = build_header_request_string( request, verbConnect,
-            path, http1_1 );
-        heap_free( path );
-        if (requestString)
+        size = 0;
+        query_headers( request, level, NULL, NULL, &size, &index );
+        if (get_last_error() != ERROR_INSUFFICIENT_BUFFER) break;
+
+        index--;
+        if (!(buffer = heap_alloc( size ))) return FALSE;
+        if (!query_headers( request, level, NULL, buffer, &size, &index ))
         {
-            LPSTR req_ascii = strdupWA( requestString );
+            heap_free( buffer );
+            return FALSE;
+        }
+        scheme = auth_scheme_from_header( buffer );
+        heap_free( buffer );
+        if (!scheme) continue;
 
-            heap_free( requestString );
-            if (req_ascii)
-            {
-                int len = strlen( req_ascii ), bytes_sent;
+        if (!first_scheme) first_scheme = scheme;
+        supported_schemes |= scheme;
 
-                ret = netconn_send( &request->netconn, req_ascii, len, &bytes_sent );
-                heap_free( req_ascii );
-                if (ret)
-                    ret = read_reply( request );
-            }
-        }
+        ret = TRUE;
+    }
+
+    if (ret)
+    {
+        *supported = supported_schemes;
+        *first = first_scheme;
     }
     return ret;
 }
 
-#ifndef INET6_ADDRSTRLEN
-#define INET6_ADDRSTRLEN 46
-#endif
-
-static WCHAR *addr_to_str( const struct sockaddr *addr )
+/***********************************************************************
+ *          WinHttpQueryAuthSchemes (winhttp.@)
+ */
+BOOL WINAPI WinHttpQueryAuthSchemes( HINTERNET hrequest, LPDWORD supported, LPDWORD first, LPDWORD target )
 {
-    char buf[INET6_ADDRSTRLEN];
-    const void *src;
+    BOOL ret = FALSE;
+    request_t *request;
+
+    TRACE("%p, %p, %p, %p\n", hrequest, supported, first, target);
 
-    switch (addr->sa_family)
+    if (!(request = (request_t *)grab_object( hrequest )))
     {
-    case AF_INET:
-        src = &((struct sockaddr_in *)addr)->sin_addr;
-        break;
-    case AF_INET6:
-        src = &((struct sockaddr_in6 *)addr)->sin6_addr;
-        break;
+        set_last_error( ERROR_INVALID_HANDLE );
+        return FALSE;
+    }
+    if (request->hdr.type != WINHTTP_HANDLE_TYPE_REQUEST)
+    {
+        release_object( &request->hdr );
+        set_last_error( ERROR_WINHTTP_INCORRECT_HANDLE_TYPE );
+        return FALSE;
+    }
+    if (!supported || !first || !target)
+    {
+        release_object( &request->hdr );
+        set_last_error( ERROR_INVALID_PARAMETER );
+        return FALSE;
+
+    }
+
+    if (query_auth_schemes( request, WINHTTP_QUERY_WWW_AUTHENTICATE, supported, first ))
+    {
+        *target = WINHTTP_AUTH_TARGET_SERVER;
+        ret = TRUE;
+    }
+    else if (query_auth_schemes( request, WINHTTP_QUERY_PROXY_AUTHENTICATE, supported, first ))
+    {
+        *target = WINHTTP_AUTH_TARGET_PROXY;
+        ret = TRUE;
+    }
+
+    release_object( &request->hdr );
+    if (ret) set_last_error( ERROR_SUCCESS );
+    return ret;
+}
+
+static UINT encode_base64( const char *bin, unsigned int len, WCHAR *base64 )
+{
+    UINT n = 0, x;
+    static const char base64enc[] =
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+    while (len > 0)
+    {
+        /* first 6 bits, all from bin[0] */
+        base64[n++] = base64enc[(bin[0] & 0xfc) >> 2];
+        x = (bin[0] & 3) << 4;
+
+        /* next 6 bits, 2 from bin[0] and 4 from bin[1] */
+        if (len == 1)
+        {
+            base64[n++] = base64enc[x];
+            base64[n++] = '=';
+            base64[n++] = '=';
+            break;
+        }
+        base64[n++] = base64enc[x | ((bin[1] & 0xf0) >> 4)];
+        x = (bin[1] & 0x0f) << 2;
+
+        /* next 6 bits 4 from bin[1] and 2 from bin[2] */
+        if (len == 2)
+        {
+            base64[n++] = base64enc[x];
+            base64[n++] = '=';
+            break;
+        }
+        base64[n++] = base64enc[x | ((bin[2] & 0xc0) >> 6)];
+
+        /* last 6 bits, all from bin [2] */
+        base64[n++] = base64enc[bin[2] & 0x3f];
+        bin += 3;
+        len -= 3;
+    }
+    base64[n] = 0;
+    return n;
+}
+
+static inline char decode_char( WCHAR c )
+{
+    if (c >= 'A' && c <= 'Z') return c - 'A';
+    if (c >= 'a' && c <= 'z') return c - 'a' + 26;
+    if (c >= '0' && c <= '9') return c - '0' + 52;
+    if (c == '+') return 62;
+    if (c == '/') return 63;
+    return 64;
+}
+
+static unsigned int decode_base64( const WCHAR *base64, unsigned int len, char *buf )
+{
+    unsigned int i = 0;
+    char c0, c1, c2, c3;
+    const WCHAR *p = base64;
+
+    while (len > 4)
+    {
+        if ((c0 = decode_char( p[0] )) > 63) return 0;
+        if ((c1 = decode_char( p[1] )) > 63) return 0;
+        if ((c2 = decode_char( p[2] )) > 63) return 0;
+        if ((c3 = decode_char( p[3] )) > 63) return 0;
+
+        if (buf)
+        {
+            buf[i + 0] = (c0 << 2) | (c1 >> 4);
+            buf[i + 1] = (c1 << 4) | (c2 >> 2);
+            buf[i + 2] = (c2 << 6) |  c3;
+        }
+        len -= 4;
+        i += 3;
+        p += 4;
+    }
+    if (p[2] == '=')
+    {
+        if ((c0 = decode_char( p[0] )) > 63) return 0;
+        if ((c1 = decode_char( p[1] )) > 63) return 0;
+
+        if (buf) buf[i] = (c0 << 2) | (c1 >> 4);
+        i++;
+    }
+    else if (p[3] == '=')
+    {
+        if ((c0 = decode_char( p[0] )) > 63) return 0;
+        if ((c1 = decode_char( p[1] )) > 63) return 0;
+        if ((c2 = decode_char( p[2] )) > 63) return 0;
+
+        if (buf)
+        {
+            buf[i + 0] = (c0 << 2) | (c1 >> 4);
+            buf[i + 1] = (c1 << 4) | (c2 >> 2);
+        }
+        i += 2;
+    }
+    else
+    {
+        if ((c0 = decode_char( p[0] )) > 63) return 0;
+        if ((c1 = decode_char( p[1] )) > 63) return 0;
+        if ((c2 = decode_char( p[2] )) > 63) return 0;
+        if ((c3 = decode_char( p[3] )) > 63) return 0;
+
+        if (buf)
+        {
+            buf[i + 0] = (c0 << 2) | (c1 >> 4);
+            buf[i + 1] = (c1 << 4) | (c2 >> 2);
+            buf[i + 2] = (c2 << 6) |  c3;
+        }
+        i += 3;
+    }
+    return i;
+}
+
+static struct authinfo *alloc_authinfo(void)
+{
+    struct authinfo *ret;
+
+    if (!(ret = heap_alloc( sizeof(*ret) ))) return NULL;
+
+    SecInvalidateHandle( &ret->cred );
+    SecInvalidateHandle( &ret->ctx );
+    memset( &ret->exp, 0, sizeof(ret->exp) );
+    ret->scheme    = 0;
+    ret->attr      = 0;
+    ret->max_token = 0;
+    ret->data      = NULL;
+    ret->data_len  = 0;
+    ret->finished  = FALSE;
+    return ret;
+}
+
+void destroy_authinfo( struct authinfo *authinfo )
+{
+    if (!authinfo) return;
+
+    if (SecIsValidHandle( &authinfo->ctx ))
+        DeleteSecurityContext( &authinfo->ctx );
+    if (SecIsValidHandle( &authinfo->cred ))
+        FreeCredentialsHandle( &authinfo->cred );
+
+    heap_free( authinfo->data );
+    heap_free( authinfo );
+}
+
+static BOOL get_authvalue( request_t *request, DWORD level, DWORD scheme, WCHAR *buffer, DWORD len )
+{
+    DWORD size, index = 0;
+    for (;;)
+    {
+        size = len;
+        if (!query_headers( request, level, NULL, buffer, &size, &index )) return FALSE;
+        if (auth_scheme_from_header( buffer ) == scheme) break;
+    }
+    return TRUE;
+}
+
+static BOOL do_authorization( request_t *request, DWORD target, DWORD scheme_flag )
+{
+    struct authinfo *authinfo, **auth_ptr;
+    enum auth_scheme scheme = scheme_from_flag( scheme_flag );
+    const WCHAR *auth_target, *username, *password;
+    WCHAR auth_value[2048], *auth_reply;
+    DWORD len = sizeof(auth_value), len_scheme, flags;
+    BOOL ret, has_auth_value;
+
+    if (scheme == SCHEME_INVALID) return FALSE;
+
+    switch (target)
+    {
+    case WINHTTP_AUTH_TARGET_SERVER:
+        has_auth_value = get_authvalue( request, WINHTTP_QUERY_WWW_AUTHENTICATE, scheme_flag, auth_value, len );
+        auth_ptr = &request->authinfo;
+        auth_target = attr_authorization;
+        if (request->creds[TARGET_SERVER][scheme].username)
+        {
+            if (scheme != SCHEME_BASIC && !has_auth_value) return FALSE;
+            username = request->creds[TARGET_SERVER][scheme].username;
+            password = request->creds[TARGET_SERVER][scheme].password;
+        }
+        else
+        {
+            if (!has_auth_value) return FALSE;
+            username = request->connect->username;
+            password = request->connect->password;
+        }
+        break;
+
+    case WINHTTP_AUTH_TARGET_PROXY:
+        if (!get_authvalue( request, WINHTTP_QUERY_PROXY_AUTHENTICATE, scheme_flag, auth_value, len ))
+            return FALSE;
+        auth_ptr = &request->proxy_authinfo;
+        auth_target = attr_proxy_authorization;
+        if (request->creds[TARGET_PROXY][scheme].username)
+        {
+            username = request->creds[TARGET_PROXY][scheme].username;
+            password = request->creds[TARGET_PROXY][scheme].password;
+        }
+        else
+        {
+            username = request->connect->session->proxy_username;
+            password = request->connect->session->proxy_password;
+        }
+        break;
+
+    default:
+        WARN("unknown target %x\n", target);
+        return FALSE;
+    }
+    authinfo = *auth_ptr;
+
+    switch (scheme)
+    {
+    case SCHEME_BASIC:
+    {
+        int userlen, passlen;
+
+        if (!username || !password) return FALSE;
+        if ((!authinfo && !(authinfo = alloc_authinfo())) || authinfo->finished) return FALSE;
+
+        userlen = WideCharToMultiByte( CP_UTF8, 0, username, strlenW( username ), NULL, 0, NULL, NULL );
+        passlen = WideCharToMultiByte( CP_UTF8, 0, password, strlenW( password ), NULL, 0, NULL, NULL );
+
+        authinfo->data_len = userlen + 1 + passlen;
+        if (!(authinfo->data = heap_alloc( authinfo->data_len ))) return FALSE;
+
+        WideCharToMultiByte( CP_UTF8, 0, username, -1, authinfo->data, userlen, NULL, NULL );
+        authinfo->data[userlen] = ':';
+        WideCharToMultiByte( CP_UTF8, 0, password, -1, authinfo->data + userlen + 1, passlen, NULL, NULL );
+
+        authinfo->scheme   = SCHEME_BASIC;
+        authinfo->finished = TRUE;
+        break;
+    }
+    case SCHEME_NTLM:
+    case SCHEME_NEGOTIATE:
+    {
+        SECURITY_STATUS status;
+        SecBufferDesc out_desc, in_desc;
+        SecBuffer out, in;
+        ULONG flags = ISC_REQ_CONNECTION|ISC_REQ_USE_DCE_STYLE|ISC_REQ_MUTUAL_AUTH|ISC_REQ_DELEGATE;
+        const WCHAR *p;
+        BOOL first = FALSE;
+
+        if (!authinfo)
+        {
+            TimeStamp exp;
+            SEC_WINNT_AUTH_IDENTITY_W id;
+            WCHAR *domain, *user;
+
+            if (!username || !password || !(authinfo = alloc_authinfo())) return FALSE;
+
+            first = TRUE;
+            domain = (WCHAR *)username;
+            user = strchrW( username, '\\' );
+
+            if (user) user++;
+            else
+            {
+                user = (WCHAR *)username;
+                domain = NULL;
+            }
+            id.Flags          = SEC_WINNT_AUTH_IDENTITY_UNICODE;
+            id.User           = user;
+            id.UserLength     = strlenW( user );
+            id.Domain         = domain;
+            id.DomainLength   = domain ? user - domain - 1 : 0;
+            id.Password       = (WCHAR *)password;
+            id.PasswordLength = strlenW( password );
+
+            status = AcquireCredentialsHandleW( NULL, (SEC_WCHAR *)auth_schemes[scheme].str,
+                                                SECPKG_CRED_OUTBOUND, NULL, &id, NULL, NULL,
+                                                &authinfo->cred, &exp );
+            if (status == SEC_E_OK)
+            {
+                PSecPkgInfoW info;
+                status = QuerySecurityPackageInfoW( (SEC_WCHAR *)auth_schemes[scheme].str, &info );
+                if (status == SEC_E_OK)
+                {
+                    authinfo->max_token = info->cbMaxToken;
+                    FreeContextBuffer( info );
+                }
+            }
+            if (status != SEC_E_OK)
+            {
+                WARN("AcquireCredentialsHandleW for scheme %s failed with error 0x%08x\n",
+                     debugstr_w(auth_schemes[scheme].str), status);
+                heap_free( authinfo );
+                return FALSE;
+            }
+            authinfo->scheme = scheme;
+        }
+        else if (authinfo->finished) return FALSE;
+
+        if ((strlenW( auth_value ) < auth_schemes[authinfo->scheme].len ||
+            strncmpiW( auth_value, auth_schemes[authinfo->scheme].str, auth_schemes[authinfo->scheme].len )))
+        {
+            ERR("authentication scheme changed from %s to %s\n",
+                debugstr_w(auth_schemes[authinfo->scheme].str), debugstr_w(auth_value));
+            destroy_authinfo( authinfo );
+            *auth_ptr = NULL;
+            return FALSE;
+        }
+        in.BufferType = SECBUFFER_TOKEN;
+        in.cbBuffer   = 0;
+        in.pvBuffer   = NULL;
+
+        in_desc.ulVersion = 0;
+        in_desc.cBuffers  = 1;
+        in_desc.pBuffers  = &in;
+
+        p = auth_value + auth_schemes[scheme].len;
+        if (*p == ' ')
+        {
+            int len = strlenW( ++p );
+            in.cbBuffer = decode_base64( p, len, NULL );
+            if (!(in.pvBuffer = heap_alloc( in.cbBuffer ))) {
+                destroy_authinfo( authinfo );
+                *auth_ptr = NULL;
+                return FALSE;
+            }
+            decode_base64( p, len, in.pvBuffer );
+        }
+        out.BufferType = SECBUFFER_TOKEN;
+        out.cbBuffer   = authinfo->max_token;
+        if (!(out.pvBuffer = heap_alloc( authinfo->max_token )))
+        {
+            heap_free( in.pvBuffer );
+            destroy_authinfo( authinfo );
+            *auth_ptr = NULL;
+            return FALSE;
+        }
+        out_desc.ulVersion = 0;
+        out_desc.cBuffers  = 1;
+        out_desc.pBuffers  = &out;
+
+        status = InitializeSecurityContextW( first ? &authinfo->cred : NULL, first ? NULL : &authinfo->ctx,
+                                             first ? request->connect->servername : NULL, flags, 0,
+                                             SECURITY_NETWORK_DREP, in.pvBuffer ? &in_desc : NULL, 0,
+                                             &authinfo->ctx, &out_desc, &authinfo->attr, &authinfo->exp );
+        heap_free( in.pvBuffer );
+        if (status == SEC_E_OK)
+        {
+            heap_free( authinfo->data );
+            authinfo->data     = out.pvBuffer;
+            authinfo->data_len = out.cbBuffer;
+            authinfo->finished = TRUE;
+            TRACE("sending last auth packet\n");
+        }
+        else if (status == SEC_I_CONTINUE_NEEDED)
+        {
+            heap_free( authinfo->data );
+            authinfo->data     = out.pvBuffer;
+            authinfo->data_len = out.cbBuffer;
+            TRACE("sending next auth packet\n");
+        }
+        else
+        {
+            ERR("InitializeSecurityContextW failed with error 0x%08x\n", status);
+            heap_free( out.pvBuffer );
+            destroy_authinfo( authinfo );
+            *auth_ptr = NULL;
+            return FALSE;
+        }
+        break;
+    }
+    default:
+        ERR("invalid scheme %u\n", scheme);
+        return FALSE;
+    }
+    *auth_ptr = authinfo;
+
+    len_scheme = auth_schemes[authinfo->scheme].len;
+    len = len_scheme + 1 + ((authinfo->data_len + 2) * 4) / 3;
+    if (!(auth_reply = heap_alloc( (len + 1) * sizeof(WCHAR) ))) return FALSE;
+
+    memcpy( auth_reply, auth_schemes[authinfo->scheme].str, len_scheme * sizeof(WCHAR) );
+    auth_reply[len_scheme] = ' ';
+    encode_base64( authinfo->data, authinfo->data_len, auth_reply + len_scheme + 1 );
+
+    flags = WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE;
+    ret = process_header( request, auth_target, auth_reply, flags, TRUE );
+    heap_free( auth_reply );
+    return ret;
+}
+
+static LPWSTR concatenate_string_list( LPCWSTR *list, int len )
+{
+    LPCWSTR *t;
+    LPWSTR str;
+
+    for( t = list; *t ; t++  )
+        len += strlenW( *t );
+    len++;
+
+    str = heap_alloc( len * sizeof(WCHAR) );
+    if (!str) return NULL;
+    *str = 0;
+
+    for( t = list; *t ; t++ )
+        strcatW( str, *t );
+
+    return str;
+}
+
+static LPWSTR build_header_request_string( request_t *request, LPCWSTR verb,
+    LPCWSTR path, LPCWSTR version )
+{
+    static const WCHAR crlf[] = {'\r','\n',0};
+    static const WCHAR space[] = { ' ',0 };
+    static const WCHAR colon[] = { ':',' ',0 };
+    static const WCHAR twocrlf[] = {'\r','\n','\r','\n', 0};
+    LPWSTR requestString;
+    DWORD len, n;
+    LPCWSTR *req;
+    UINT i;
+    LPWSTR p;
+
+    /* allocate space for an array of all the string pointers to be added */
+    len = (request->num_headers) * 4 + 10;
+    req = heap_alloc( len * sizeof(LPCWSTR) );
+    if (!req) return NULL;
+
+    /* add the verb, path and HTTP version string */
+    n = 0;
+    req[n++] = verb;
+    req[n++] = space;
+    req[n++] = path;
+    req[n++] = space;
+    req[n++] = version;
+
+    /* Append custom request headers */
+    for (i = 0; i < request->num_headers; i++)
+    {
+        if (request->headers[i].is_request)
+        {
+            req[n++] = crlf;
+            req[n++] = request->headers[i].field;
+            req[n++] = colon;
+            req[n++] = request->headers[i].value;
+
+            TRACE("Adding custom header %s (%s)\n",
+                   debugstr_w(request->headers[i].field),
+                   debugstr_w(request->headers[i].value));
+        }
+    }
+
+    if( n >= len )
+        ERR("oops. buffer overrun\n");
+
+    req[n] = NULL;
+    requestString = concatenate_string_list( req, 4 );
+    heap_free( req );
+    if (!requestString) return NULL;
+
+    /*
+     * Set (header) termination string for request
+     * Make sure there are exactly two new lines at the end of the request
+     */
+    p = &requestString[strlenW(requestString)-1];
+    while ( (*p == '\n') || (*p == '\r') )
+       p--;
+    strcpyW( p+1, twocrlf );
+
+    return requestString;
+}
+
+static BOOL read_reply( request_t *request );
+
+static BOOL secure_proxy_connect( request_t *request )
+{
+    static const WCHAR verbConnect[] = {'C','O','N','N','E','C','T',0};
+    static const WCHAR fmt[] = {'%','s',':','%','u',0};
+    BOOL ret = FALSE;
+    LPWSTR path;
+    connect_t *connect = request->connect;
+
+    path = heap_alloc( (strlenW( connect->hostname ) + 13) * sizeof(WCHAR) );
+    if (path)
+    {
+        LPWSTR requestString;
+
+        sprintfW( path, fmt, connect->hostname, connect->hostport );
+        requestString = build_header_request_string( request, verbConnect,
+            path, http1_1 );
+        heap_free( path );
+        if (requestString)
+        {
+            LPSTR req_ascii = strdupWA( requestString );
+
+            heap_free( requestString );
+            if (req_ascii)
+            {
+                int len = strlen( req_ascii ), bytes_sent;
+
+                ret = netconn_send( request->netconn, req_ascii, len, &bytes_sent );
+                heap_free( req_ascii );
+                if (ret)
+                    ret = read_reply( request );
+            }
+        }
+    }
+    return ret;
+}
+
+#ifndef INET6_ADDRSTRLEN
+#define INET6_ADDRSTRLEN 46
+#endif
+
+static WCHAR *addr_to_str( struct sockaddr_storage *addr )
+{
+    char buf[INET6_ADDRSTRLEN];
+    void *src;
+
+    switch (addr->ss_family)
+    {
+    case AF_INET:
+        src = &((struct sockaddr_in *)addr)->sin_addr;
+        break;
+    case AF_INET6:
+        src = &((struct sockaddr_in6 *)addr)->sin6_addr;
+        break;
     default:
-        WARN("unsupported address family %d\n", addr->sa_family);
+        WARN("unsupported address family %d\n", addr->ss_family);
         return NULL;
     }
-    if (!inet_ntop( addr->sa_family, src, buf, sizeof(buf) )) return NULL;
+    if (!inet_ntop( addr->ss_family, src, buf, sizeof(buf) )) return NULL;
     return strdupAW( buf );
 }
 
-static BOOL open_connection( request_t *request )
+static CRITICAL_SECTION connection_pool_cs;
+static CRITICAL_SECTION_DEBUG connection_pool_debug =
 {
-    connect_t *connect;
-    WCHAR *addressW = NULL;
-    INTERNET_PORT port;
-    socklen_t slen;
-    struct sockaddr *saddr;
-    DWORD len;
+    0, 0, &connection_pool_cs,
+    { &connection_pool_debug.ProcessLocksList, &connection_pool_debug.ProcessLocksList },
+      0, 0, { (DWORD_PTR)(__FILE__ ": connection_pool_cs") }
+};
+static CRITICAL_SECTION connection_pool_cs = { &connection_pool_debug, -1, 0, 0, 0, 0 };
 
-    if (netconn_connected( &request->netconn )) goto done;
+static struct list connection_pool = LIST_INIT( connection_pool );
 
-    connect = request->connect;
-    port = connect->serverport ? connect->serverport : (request->hdr.flags & WINHTTP_FLAG_SECURE ? 443 : 80);
-    saddr = (struct sockaddr *)&connect->sockaddr;
-    slen = sizeof(struct sockaddr);
+void release_host( hostdata_t *host )
+{
+    LONG ref;
 
-    if (!connect->resolved)
-    {
-        len = strlenW( connect->servername ) + 1;
-        send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_RESOLVING_NAME, connect->servername, len );
+    EnterCriticalSection( &connection_pool_cs );
+    if (!(ref = --host->ref)) list_remove( &host->entry );
+    LeaveCriticalSection( &connection_pool_cs );
+    if (ref) return;
 
-        if (!netconn_resolve( connect->servername, port, saddr, &slen, request->resolve_timeout )) return FALSE;
-        connect->resolved = TRUE;
+    assert( list_empty( &host->connections ) );
+    heap_free( host->hostname );
+    heap_free( host );
+}
 
-        if (!(addressW = addr_to_str( saddr ))) return FALSE;
-        len = strlenW( addressW ) + 1;
-        send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_NAME_RESOLVED, addressW, len );
-    }
-    if (!addressW && !(addressW = addr_to_str( saddr ))) return FALSE;
-    TRACE("connecting to %s:%u\n", debugstr_w(addressW), port);
+static BOOL connection_collector_running;
 
-    send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER, addressW, 0 );
+static DWORD WINAPI connection_collector(void *arg)
+{
+    unsigned int remaining_connections;
+    netconn_t *netconn, *next_netconn;
+    hostdata_t *host, *next_host;
+    ULONGLONG now;
 
-    if (!netconn_create( &request->netconn, saddr->sa_family, SOCK_STREAM, 0 ))
-    {
-        heap_free( addressW );
-        return FALSE;
-    }
-    netconn_set_timeout( &request->netconn, TRUE, request->send_timeout );
-    netconn_set_timeout( &request->netconn, FALSE, request->recv_timeout );
-    if (!netconn_connect( &request->netconn, saddr, slen, request->connect_timeout ))
-    {
-        netconn_close( &request->netconn );
-        heap_free( addressW );
-        return FALSE;
-    }
-    if (request->hdr.flags & WINHTTP_FLAG_SECURE)
+    do
     {
-        if (connect->session->proxy_server &&
-            strcmpiW( connect->hostname, connect->servername ))
+        /* FIXME: Use more sophisticated method */
+        Sleep(5000);
+        remaining_connections = 0;
+        now = GetTickCount64();
+
+        EnterCriticalSection(&connection_pool_cs);
+
+        LIST_FOR_EACH_ENTRY_SAFE(host, next_host, &connection_pool, hostdata_t, entry)
         {
-            if (!secure_proxy_connect( request ))
+            LIST_FOR_EACH_ENTRY_SAFE(netconn, next_netconn, &host->connections, netconn_t, entry)
             {
-                heap_free( addressW );
-                return FALSE;
+                if (netconn->keep_until < now)
+                {
+                    TRACE("freeing %p\n", netconn);
+                    list_remove(&netconn->entry);
+                    netconn_close(netconn);
+                }
+                else
+                {
+                    remaining_connections++;
+                }
             }
         }
-        if (!netconn_secure_connect( &request->netconn, connect->servername ))
-        {
-            netconn_close( &request->netconn );
-            heap_free( addressW );
-            return FALSE;
-        }
-    }
-
-    send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER, addressW, strlenW(addressW) + 1 );
 
-done:
-    request->read_pos = request->read_size = 0;
-    request->read_chunked = FALSE;
-    request->read_chunked_size = ~0u;
-    request->read_chunked_eof = FALSE;
-    heap_free( addressW );
-    return TRUE;
-}
+        if (!remaining_connections) connection_collector_running = FALSE;
 
-void close_connection( request_t *request )
-{
-    if (!netconn_connected( &request->netconn )) return;
+        LeaveCriticalSection(&connection_pool_cs);
+    } while(remaining_connections);
 
-    send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION, 0, 0 );
-    netconn_close( &request->netconn );
-    send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED, 0, 0 );
+    FreeLibraryAndExitThread( winhttp_instance, 0 );
 }
 
-static BOOL add_host_header( request_t *request, DWORD modifier )
+static void cache_connection( netconn_t *netconn )
 {
-    BOOL ret;
-    DWORD len;
-    WCHAR *host;
-    static const WCHAR fmt[] = {'%','s',':','%','u',0};
-    connect_t *connect = request->connect;
-    INTERNET_PORT port;
+    TRACE( "caching connection %p\n", netconn );
 
-    port = connect->hostport ? connect->hostport : (request->hdr.flags & WINHTTP_FLAG_SECURE ? 443 : 80);
+    EnterCriticalSection( &connection_pool_cs );
 
-    if (port == INTERNET_DEFAULT_HTTP_PORT || port == INTERNET_DEFAULT_HTTPS_PORT)
+    netconn->keep_until = GetTickCount64() + DEFAULT_KEEP_ALIVE_TIMEOUT;
+    list_add_head( &netconn->host->connections, &netconn->entry );
+
+    if (!connection_collector_running)
     {
-        return process_header( request, attr_host, connect->hostname, modifier, TRUE );
-    }
-    len = strlenW( connect->hostname ) + 7; /* sizeof(":65335") */
-    if (!(host = heap_alloc( len * sizeof(WCHAR) ))) return FALSE;
-    sprintfW( host, fmt, connect->hostname, port );
-    ret = process_header( request, attr_host, host, modifier, TRUE );
-    heap_free( host );
-    return ret;
-}
+        HMODULE module;
+        HANDLE thread;
 
-static void clear_response_headers( request_t *request )
-{
-    unsigned int i;
+        GetModuleHandleExW( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (const WCHAR*)winhttp_instance, &module );
 
-    for (i = 0; i < request->num_headers; i++)
-    {
-        if (!request->headers[i].field) continue;
-        if (!request->headers[i].value) continue;
-        if (request->headers[i].is_request) continue;
-        delete_header( request, i );
-        i--;
+        thread = CreateThread(NULL, 0, connection_collector, NULL, 0, NULL);
+        if (thread)
+        {
+            CloseHandle( thread );
+            connection_collector_running = TRUE;
+        }
+        else
+        {
+            FreeLibrary( winhttp_instance );
+        }
     }
+
+    LeaveCriticalSection( &connection_pool_cs );
 }
 
-static BOOL send_request( request_t *request, LPCWSTR headers, DWORD headers_len, LPVOID optional,
-                          DWORD optional_len, DWORD total_len, DWORD_PTR context, BOOL async )
+static DWORD map_secure_protocols( DWORD mask )
 {
-    static const WCHAR keep_alive[] = {'K','e','e','p','-','A','l','i','v','e',0};
-    static const WCHAR no_cache[]   = {'n','o','-','c','a','c','h','e',0};
-    static const WCHAR length_fmt[] = {'%','l','d',0};
-
-    BOOL ret = FALSE;
-    connect_t *connect = request->connect;
-    session_t *session = connect->session;
-    WCHAR *req = NULL;
-    char *req_ascii;
-    int bytes_sent;
-    DWORD len, i, flags;
-
-    clear_response_headers( request );
+    DWORD ret = 0;
+    if (mask & WINHTTP_FLAG_SECURE_PROTOCOL_SSL2) ret |= SP_PROT_SSL2_CLIENT;
+    if (mask & WINHTTP_FLAG_SECURE_PROTOCOL_SSL3) ret |= SP_PROT_SSL3_CLIENT;
+    if (mask & WINHTTP_FLAG_SECURE_PROTOCOL_TLS1) ret |= SP_PROT_TLS1_CLIENT;
+    if (mask & WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1) ret |= SP_PROT_TLS1_1_CLIENT;
+    if (mask & WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2) ret |= SP_PROT_TLS1_2_CLIENT;
+    return ret;
+}
 
-    flags = WINHTTP_ADDREQ_FLAG_ADD|WINHTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA;
-    for (i = 0; i < request->num_accept_types; i++)
-    {
-        process_header( request, attr_accept, request->accept_types[i], flags, TRUE );
-    }
-    if (session->agent)
-        process_header( request, attr_user_agent, session->agent, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE );
+static BOOL ensure_cred_handle( session_t *session )
+{
+    SCHANNEL_CRED cred;
+    SECURITY_STATUS status;
 
-    if (connect->hostname)
-        add_host_header( request, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW );
+    if (session->cred_handle_initialized) return TRUE;
 
-    if (total_len || (request->verb && !strcmpW( request->verb, postW )))
-    {
-        WCHAR length[21]; /* decimal long int + null */
-        sprintfW( length, length_fmt, total_len );
-        process_header( request, attr_content_length, length, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE );
-    }
-    if (!(request->hdr.disable_flags & WINHTTP_DISABLE_KEEP_ALIVE))
-    {
-        process_header( request, attr_connection, keep_alive, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE );
-    }
-    if (request->hdr.flags & WINHTTP_FLAG_REFRESH)
-    {
-        process_header( request, attr_pragma, no_cache, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE );
-        process_header( request, attr_cache_control, no_cache, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE );
-    }
-    if (headers && !add_request_headers( request, headers, headers_len, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE ))
+    memset( &cred, 0, sizeof(cred) );
+    cred.dwVersion             = SCHANNEL_CRED_VERSION;
+    cred.grbitEnabledProtocols = map_secure_protocols( session->secure_protocols );
+    if ((status = AcquireCredentialsHandleW( NULL, (WCHAR *)UNISP_NAME_W, SECPKG_CRED_OUTBOUND, NULL, &cred,
+                                             NULL, NULL, &session->cred_handle, NULL )) != SEC_E_OK)
     {
-        TRACE("failed to add request headers\n");
-        return FALSE;
-    }
-    if (!(request->hdr.disable_flags & WINHTTP_DISABLE_COOKIES) && !add_cookie_headers( request ))
-    {
-        WARN("failed to add cookie headers\n");
+        WARN( "AcquireCredentialsHandleW failed: 0x%08x\n", status );
         return FALSE;
     }
+    session->cred_handle_initialized = TRUE;
+    return TRUE;
+}
 
-    if (context) request->hdr.context = context;
-
-    if (!(ret = open_connection( request ))) goto end;
-    if (!(req = build_request_string( request ))) goto end;
+static BOOL open_connection( request_t *request )
+{
+    BOOL is_secure = request->hdr.flags & WINHTTP_FLAG_SECURE;
+    hostdata_t *host = NULL, *iter;
+    netconn_t *netconn = NULL;
+    connect_t *connect;
+    WCHAR *addressW = NULL;
+    INTERNET_PORT port;
+    DWORD len;
 
-    if (!(req_ascii = strdupWA( req ))) goto end;
-    TRACE("full request: %s\n", debugstr_a(req_ascii));
-    len = strlen(req_ascii);
+    if (request->netconn) goto done;
 
-    send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_SENDING_REQUEST, NULL, 0 );
+    connect = request->connect;
+    port = connect->serverport ? connect->serverport : (request->hdr.flags & WINHTTP_FLAG_SECURE ? 443 : 80);
 
-    ret = netconn_send( &request->netconn, req_ascii, len, &bytes_sent );
-    heap_free( req_ascii );
-    if (!ret) goto end;
+    EnterCriticalSection( &connection_pool_cs );
 
-    if (optional_len)
+    LIST_FOR_EACH_ENTRY( iter, &connection_pool, hostdata_t, entry )
     {
-        if (!netconn_send( &request->netconn, optional, optional_len, &bytes_sent )) goto end;
-        request->optional = optional;
-        request->optional_len = optional_len;
-        len += optional_len;
+        if (iter->port == port && !strcmpW( connect->servername, iter->hostname ) && !is_secure == !iter->secure)
+        {
+            host = iter;
+            host->ref++;
+            break;
+        }
     }
-    send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_REQUEST_SENT, &len, sizeof(len) );
 
-end:
-    if (async)
+    if (!host)
     {
-        if (ret) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE, NULL, 0 );
-        else
+        if ((host = heap_alloc( sizeof(*host) )))
         {
-            WINHTTP_ASYNC_RESULT result;
-            result.dwResult = API_SEND_REQUEST;
-            result.dwError  = get_last_error();
-            send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_REQUEST_ERROR, &result, sizeof(result) );
+            host->ref = 1;
+            host->secure = is_secure;
+            host->port = port;
+            list_init( &host->connections );
+            if ((host->hostname = strdupW( connect->servername )))
+            {
+                list_add_head( &connection_pool, &host->entry );
+            }
+            else
+            {
+                heap_free( host );
+                host = NULL;
+            }
         }
     }
-    heap_free( req );
-    return ret;
-}
 
-static void task_send_request( task_header_t *task )
-{
-    send_request_t *s = (send_request_t *)task;
-    send_request( s->hdr.request, s->headers, s->headers_len, s->optional, s->optional_len, s->total_len, s->context, TRUE );
-    heap_free( s->headers );
-}
+    LeaveCriticalSection( &connection_pool_cs );
 
-/***********************************************************************
- *          WinHttpSendRequest (winhttp.@)
- */
-BOOL WINAPI WinHttpSendRequest( HINTERNET hrequest, LPCWSTR headers, DWORD headers_len,
-                                LPVOID optional, DWORD optional_len, DWORD total_len, DWORD_PTR context )
-{
-    BOOL ret;
-    request_t *request;
+    if (!host) return FALSE;
 
-    TRACE("%p, %s, 0x%x, %u, %u, %lx\n",
-          hrequest, debugstr_w(headers), headers_len, optional_len, total_len, context);
+    for (;;)
+    {
+        EnterCriticalSection( &connection_pool_cs );
+        if (!list_empty( &host->connections ))
+        {
+            netconn = LIST_ENTRY( list_head( &host->connections ), netconn_t, entry );
+            list_remove( &netconn->entry );
+        }
+        LeaveCriticalSection( &connection_pool_cs );
+        if (!netconn) break;
 
-    if (!(request = (request_t *)grab_object( hrequest )))
+        if (netconn_is_alive( netconn )) break;
+        TRACE("connection %p no longer alive, closing\n", netconn);
+        netconn_close( netconn );
+        netconn = NULL;
+    }
+
+    if (!connect->resolved && netconn)
     {
-        set_last_error( ERROR_INVALID_HANDLE );
-        return FALSE;
+        connect->sockaddr = netconn->sockaddr;
+        connect->resolved = TRUE;
     }
-    if (request->hdr.type != WINHTTP_HANDLE_TYPE_REQUEST)
+
+    if (!connect->resolved)
     {
-        release_object( &request->hdr );
-        set_last_error( ERROR_WINHTTP_INCORRECT_HANDLE_TYPE );
-        return FALSE;
+        len = strlenW( host->hostname ) + 1;
+        send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_RESOLVING_NAME, host->hostname, len );
+
+        if (!netconn_resolve( host->hostname, port, &connect->sockaddr, request->resolve_timeout ))
+        {
+            release_host( host );
+            return FALSE;
+        }
+        connect->resolved = TRUE;
+
+        if (!(addressW = addr_to_str( &connect->sockaddr )))
+        {
+            release_host( host );
+            return FALSE;
+        }
+        len = strlenW( addressW ) + 1;
+        send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_NAME_RESOLVED, addressW, len );
     }
 
-    if (request->connect->hdr.flags & WINHTTP_FLAG_ASYNC)
+    if (!netconn)
     {
-        send_request_t *s;
+        if (!addressW && !(addressW = addr_to_str( &connect->sockaddr )))
+        {
+            release_host( host );
+            return FALSE;
+        }
 
-        if (!(s = heap_alloc( sizeof(send_request_t) ))) return FALSE;
-        s->hdr.request  = request;
-        s->hdr.proc     = task_send_request;
-        s->headers      = strdupW( headers );
-        s->headers_len  = headers_len;
-        s->optional     = optional;
-        s->optional_len = optional_len;
-        s->total_len    = total_len;
-        s->context      = context;
+        TRACE("connecting to %s:%u\n", debugstr_w(addressW), port);
 
-        addref_object( &request->hdr );
-        ret = queue_task( (task_header_t *)s );
-    }
-    else
-        ret = send_request( request, headers, headers_len, optional, optional_len, total_len, context, FALSE );
+        send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER, addressW, 0 );
 
-    release_object( &request->hdr );
-    return ret;
-}
+        if (!(netconn = netconn_create( host, &connect->sockaddr, request->connect_timeout )))
+        {
+            heap_free( addressW );
+            release_host( host );
+            return FALSE;
+        }
+        netconn_set_timeout( netconn, TRUE, request->send_timeout );
+        netconn_set_timeout( netconn, FALSE, request->recv_timeout );
+        if (is_secure)
+        {
+            if (connect->session->proxy_server &&
+                strcmpiW( connect->hostname, connect->servername ))
+            {
+                if (!secure_proxy_connect( request ))
+                {
+                    heap_free( addressW );
+                    netconn_close( netconn );
+                    return FALSE;
+                }
+            }
+            if (!ensure_cred_handle( connect->session ) ||
+                !netconn_secure_connect( netconn, connect->hostname, request->security_flags,
+                                         &connect->session->cred_handle ))
+            {
+                heap_free( addressW );
+                netconn_close( netconn );
+                return FALSE;
+            }
+        }
 
-#define ARRAYSIZE(array) (sizeof(array) / sizeof((array)[0]))
+        request->netconn = netconn;
+        send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER, addressW, strlenW(addressW) + 1 );
+    }
+    else
+    {
+        TRACE("using connection %p\n", netconn);
 
-static const WCHAR basicW[]     = {'B','a','s','i','c',0};
-static const WCHAR ntlmW[]      = {'N','T','L','M',0};
-static const WCHAR passportW[]  = {'P','a','s','s','p','o','r','t',0};
-static const WCHAR digestW[]    = {'D','i','g','e','s','t',0};
-static const WCHAR negotiateW[] = {'N','e','g','o','t','i','a','t','e',0};
+        netconn_set_timeout( netconn, TRUE, request->send_timeout );
+        netconn_set_timeout( netconn, FALSE, request->recv_timeout );
+        request->netconn = netconn;
+    }
 
-static const struct
-{
-    const WCHAR *str;
-    unsigned int len;
-    DWORD scheme;
+done:
+    request->read_pos = request->read_size = 0;
+    request->read_chunked = FALSE;
+    request->read_chunked_size = ~0u;
+    request->read_chunked_eof = FALSE;
+    heap_free( addressW );
+    return TRUE;
 }
-auth_schemes[] =
-{
-    { basicW,     ARRAYSIZE(basicW) - 1,     WINHTTP_AUTH_SCHEME_BASIC },
-    { ntlmW,      ARRAYSIZE(ntlmW) - 1,      WINHTTP_AUTH_SCHEME_NTLM },
-    { passportW,  ARRAYSIZE(passportW) - 1,  WINHTTP_AUTH_SCHEME_PASSPORT },
-    { digestW,    ARRAYSIZE(digestW) - 1,    WINHTTP_AUTH_SCHEME_DIGEST },
-    { negotiateW, ARRAYSIZE(negotiateW) - 1, WINHTTP_AUTH_SCHEME_NEGOTIATE }
-};
-static const unsigned int num_auth_schemes = sizeof(auth_schemes)/sizeof(auth_schemes[0]);
 
-static enum auth_scheme scheme_from_flag( DWORD flag )
+void close_connection( request_t *request )
 {
-    int i;
+    if (!request->netconn) return;
 
-    for (i = 0; i < num_auth_schemes; i++) if (flag == auth_schemes[i].scheme) return i;
-    return SCHEME_INVALID;
+    send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION, 0, 0 );
+    netconn_close( request->netconn );
+    request->netconn = NULL;
+    send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED, 0, 0 );
 }
 
-static DWORD auth_scheme_from_header( WCHAR *header )
+static BOOL add_host_header( request_t *request, DWORD modifier )
 {
-    unsigned int i;
+    BOOL ret;
+    DWORD len;
+    WCHAR *host;
+    static const WCHAR fmt[] = {'%','s',':','%','u',0};
+    connect_t *connect = request->connect;
+    INTERNET_PORT port;
 
-    for (i = 0; i < num_auth_schemes; i++)
+    port = connect->hostport ? connect->hostport : (request->hdr.flags & WINHTTP_FLAG_SECURE ? 443 : 80);
+
+    if (port == INTERNET_DEFAULT_HTTP_PORT || port == INTERNET_DEFAULT_HTTPS_PORT)
     {
-        if (!strncmpiW( header, auth_schemes[i].str, auth_schemes[i].len ) &&
-            (header[auth_schemes[i].len] == ' ' || !header[auth_schemes[i].len])) return auth_schemes[i].scheme;
+        return process_header( request, attr_host, connect->hostname, modifier, TRUE );
     }
-    return 0;
+    len = strlenW( connect->hostname ) + 7; /* sizeof(":65335") */
+    if (!(host = heap_alloc( len * sizeof(WCHAR) ))) return FALSE;
+    sprintfW( host, fmt, connect->hostname, port );
+    ret = process_header( request, attr_host, host, modifier, TRUE );
+    heap_free( host );
+    return ret;
 }
 
-static BOOL query_auth_schemes( request_t *request, DWORD level, LPDWORD supported, LPDWORD first )
+static void clear_response_headers( request_t *request )
 {
-    DWORD index = 0;
-    BOOL ret = FALSE;
+    unsigned int i;
 
-    for (;;)
+    for (i = 0; i < request->num_headers; i++)
     {
-        WCHAR *buffer;
-        DWORD size, scheme;
-
-        size = 0;
-        query_headers( request, level, NULL, NULL, &size, &index );
-        if (get_last_error() != ERROR_INSUFFICIENT_BUFFER) break;
-
-        index--;
-        if (!(buffer = heap_alloc( size ))) return FALSE;
-        if (!query_headers( request, level, NULL, buffer, &size, &index ))
-        {
-            heap_free( buffer );
-            return FALSE;
-        }
-        scheme = auth_scheme_from_header( buffer );
-        heap_free( buffer );
-        if (!scheme) break;
-
-        if (first && index == 1)
-            *first = *supported = scheme;
-        else
-            *supported |= scheme;
-
-        ret = TRUE;
+        if (!request->headers[i].field) continue;
+        if (!request->headers[i].value) continue;
+        if (request->headers[i].is_request) continue;
+        delete_header( request, i );
+        i--;
     }
-    return ret;
 }
 
-/***********************************************************************
- *          WinHttpQueryAuthSchemes (winhttp.@)
- */
-BOOL WINAPI WinHttpQueryAuthSchemes( HINTERNET hrequest, LPDWORD supported, LPDWORD first, LPDWORD target )
+/* remove some amount of data from the read buffer */
+static void remove_data( request_t *request, int count )
 {
-    BOOL ret = FALSE;
-    request_t *request;
+    if (!(request->read_size -= count)) request->read_pos = 0;
+    else request->read_pos += count;
+}
 
-    TRACE("%p, %p, %p, %p\n", hrequest, supported, first, target);
+/* read some more data into the read buffer */
+static BOOL read_more_data( request_t *request, int maxlen, BOOL notify )
+{
+    int len;
+    BOOL ret;
 
-    if (!(request = (request_t *)grab_object( hrequest )))
-    {
-        set_last_error( ERROR_INVALID_HANDLE );
-        return FALSE;
-    }
-    if (request->hdr.type != WINHTTP_HANDLE_TYPE_REQUEST)
+    if (request->read_chunked_eof) return FALSE;
+
+    if (request->read_size && request->read_pos)
     {
-        release_object( &request->hdr );
-        set_last_error( ERROR_WINHTTP_INCORRECT_HANDLE_TYPE );
-        return FALSE;
+        /* move existing data to the start of the buffer */
+        memmove( request->read_buf, request->read_buf + request->read_pos, request->read_size );
+        request->read_pos = 0;
     }
-    if (!supported || !first || !target)
-    {
-        release_object( &request->hdr );
-        set_last_error( ERROR_INVALID_PARAMETER );
-        return FALSE;
+    if (maxlen == -1) maxlen = sizeof(request->read_buf);
 
-    }
+    if (notify) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE, NULL, 0 );
 
-    if (query_auth_schemes( request, WINHTTP_QUERY_WWW_AUTHENTICATE, supported, first ))
-    {
-        *target = WINHTTP_AUTH_TARGET_SERVER;
-        ret = TRUE;
-    }
-    else if (query_auth_schemes( request, WINHTTP_QUERY_PROXY_AUTHENTICATE, supported, first ))
-    {
-        *target = WINHTTP_AUTH_TARGET_PROXY;
-        ret = TRUE;
-    }
+    ret = netconn_recv( request->netconn, request->read_buf + request->read_size,
+                        maxlen - request->read_size, 0, &len );
 
-    release_object( &request->hdr );
+    if (notify) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED, &len, sizeof(len) );
+
+    request->read_size += len;
     return ret;
 }
 
-static UINT encode_base64( const char *bin, unsigned int len, WCHAR *base64 )
+/* discard data contents until we reach end of line */
+static BOOL discard_eol( request_t *request, BOOL notify )
 {
-    UINT n = 0, x;
-    static const char base64enc[] =
-        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-
-    while (len > 0)
+    do
     {
-        /* first 6 bits, all from bin[0] */
-        base64[n++] = base64enc[(bin[0] & 0xfc) >> 2];
-        x = (bin[0] & 3) << 4;
-
-        /* next 6 bits, 2 from bin[0] and 4 from bin[1] */
-        if (len == 1)
+        char *eol = memchr( request->read_buf + request->read_pos, '\n', request->read_size );
+        if (eol)
         {
-            base64[n++] = base64enc[x];
-            base64[n++] = '=';
-            base64[n++] = '=';
+            remove_data( request, (eol + 1) - (request->read_buf + request->read_pos) );
             break;
         }
-        base64[n++] = base64enc[x | ((bin[1] & 0xf0) >> 4)];
-        x = (bin[1] & 0x0f) << 2;
+        request->read_pos = request->read_size = 0;  /* discard everything */
+        if (!read_more_data( request, -1, notify )) return FALSE;
+    } while (request->read_size);
+    return TRUE;
+}
 
-        /* next 6 bits 4 from bin[1] and 2 from bin[2] */
-        if (len == 2)
+/* read the size of the next chunk */
+static BOOL start_next_chunk( request_t *request, BOOL notify )
+{
+    DWORD chunk_size = 0;
+
+    assert(!request->read_chunked_size || request->read_chunked_size == ~0u);
+
+    if (request->read_chunked_eof) return FALSE;
+
+    /* read terminator for the previous chunk */
+    if (!request->read_chunked_size && !discard_eol( request, notify )) return FALSE;
+
+    for (;;)
+    {
+        while (request->read_size)
         {
-            base64[n++] = base64enc[x];
-            base64[n++] = '=';
-            break;
+            char ch = request->read_buf[request->read_pos];
+            if (ch >= '0' && ch <= '9') chunk_size = chunk_size * 16 + ch - '0';
+            else if (ch >= 'a' && ch <= 'f') chunk_size = chunk_size * 16 + ch - 'a' + 10;
+            else if (ch >= 'A' && ch <= 'F') chunk_size = chunk_size * 16 + ch - 'A' + 10;
+            else if (ch == ';' || ch == '\r' || ch == '\n')
+            {
+                TRACE("reading %u byte chunk\n", chunk_size);
+
+                if (request->content_length == ~0u) request->content_length = chunk_size;
+                else request->content_length += chunk_size;
+
+                request->read_chunked_size = chunk_size;
+                if (!chunk_size) request->read_chunked_eof = TRUE;
+
+                return discard_eol( request, notify );
+            }
+            remove_data( request, 1 );
+        }
+        if (!read_more_data( request, -1, notify )) return FALSE;
+        if (!request->read_size)
+        {
+            request->content_length = request->content_read = 0;
+            request->read_chunked_size = 0;
+            return TRUE;
         }
-        base64[n++] = base64enc[x | ((bin[2] & 0xc0) >> 6)];
-
-        /* last 6 bits, all from bin [2] */
-        base64[n++] = base64enc[bin[2] & 0x3f];
-        bin += 3;
-        len -= 3;
     }
-    base64[n] = 0;
-    return n;
-}
-
-static inline char decode_char( WCHAR c )
-{
-    if (c >= 'A' && c <= 'Z') return c - 'A';
-    if (c >= 'a' && c <= 'z') return c - 'a' + 26;
-    if (c >= '0' && c <= '9') return c - '0' + 52;
-    if (c == '+') return 62;
-    if (c == '/') return 63;
-    return 64;
 }
 
-static unsigned int decode_base64( const WCHAR *base64, unsigned int len, char *buf )
+static BOOL refill_buffer( request_t *request, BOOL notify )
 {
-    unsigned int i = 0;
-    char c0, c1, c2, c3;
-    const WCHAR *p = base64;
+    int len = sizeof(request->read_buf);
 
-    while (len > 4)
+    if (request->read_chunked)
     {
-        if ((c0 = decode_char( p[0] )) > 63) return 0;
-        if ((c1 = decode_char( p[1] )) > 63) return 0;
-        if ((c2 = decode_char( p[2] )) > 63) return 0;
-        if ((c3 = decode_char( p[3] )) > 63) return 0;
-
-        if (buf)
+        if (request->read_chunked_eof) return FALSE;
+        if (request->read_chunked_size == ~0u || !request->read_chunked_size)
         {
-            buf[i + 0] = (c0 << 2) | (c1 >> 4);
-            buf[i + 1] = (c1 << 4) | (c2 >> 2);
-            buf[i + 2] = (c2 << 6) |  c3;
+            if (!start_next_chunk( request, notify )) return FALSE;
         }
-        len -= 4;
-        i += 3;
-        p += 4;
+        len = min( len, request->read_chunked_size );
     }
-    if (p[2] == '=')
+    else if (request->content_length != ~0u)
     {
-        if ((c0 = decode_char( p[0] )) > 63) return 0;
-        if ((c1 = decode_char( p[1] )) > 63) return 0;
-
-        if (buf) buf[i] = (c0 << 2) | (c1 >> 4);
-        i++;
+        len = min( len, request->content_length - request->content_read );
     }
-    else if (p[3] == '=')
-    {
-        if ((c0 = decode_char( p[0] )) > 63) return 0;
-        if ((c1 = decode_char( p[1] )) > 63) return 0;
-        if ((c2 = decode_char( p[2] )) > 63) return 0;
 
-        if (buf)
-        {
-            buf[i + 0] = (c0 << 2) | (c1 >> 4);
-            buf[i + 1] = (c1 << 4) | (c2 >> 2);
-        }
-        i += 2;
+    if (len <= request->read_size) return TRUE;
+    if (!read_more_data( request, len, notify )) return FALSE;
+    if (!request->read_size) request->content_length = request->content_read = 0;
+    return TRUE;
+}
+
+static void finished_reading( request_t *request )
+{
+    static const WCHAR closeW[] = {'c','l','o','s','e',0};
+
+    BOOL close = FALSE;
+    WCHAR connection[20];
+    DWORD size = sizeof(connection);
+
+    if (!request->netconn) return;
+
+    if (request->hdr.disable_flags & WINHTTP_DISABLE_KEEP_ALIVE) close = TRUE;
+    else if (query_headers( request, WINHTTP_QUERY_CONNECTION, NULL, connection, &size, NULL ) ||
+             query_headers( request, WINHTTP_QUERY_PROXY_CONNECTION, NULL, connection, &size, NULL ))
+    {
+        if (!strcmpiW( connection, closeW )) close = TRUE;
     }
-    else
+    else if (!strcmpW( request->version, http1_0 )) close = TRUE;
+    if (close)
     {
-        if ((c0 = decode_char( p[0] )) > 63) return 0;
-        if ((c1 = decode_char( p[1] )) > 63) return 0;
-        if ((c2 = decode_char( p[2] )) > 63) return 0;
-        if ((c3 = decode_char( p[3] )) > 63) return 0;
-
-        if (buf)
-        {
-            buf[i + 0] = (c0 << 2) | (c1 >> 4);
-            buf[i + 1] = (c1 << 4) | (c2 >> 2);
-            buf[i + 2] = (c2 << 6) |  c3;
-        }
-        i += 3;
+        close_connection( request );
+        return;
     }
-    return i;
+
+    cache_connection( request->netconn );
+    request->netconn = NULL;
 }
 
-static struct authinfo *alloc_authinfo(void)
+/* return the size of data available to be read immediately */
+static DWORD get_available_data( request_t *request )
 {
-    struct authinfo *ret;
-
-    if (!(ret = heap_alloc( sizeof(*ret) ))) return NULL;
+    if (request->read_chunked) return min( request->read_chunked_size, request->read_size );
+    return request->read_size;
+}
 
-    SecInvalidateHandle( &ret->cred );
-    SecInvalidateHandle( &ret->ctx );
-    memset( &ret->exp, 0, sizeof(ret->exp) );
-    ret->scheme    = 0;
-    ret->attr      = 0;
-    ret->max_token = 0;
-    ret->data      = NULL;
-    ret->data_len  = 0;
-    ret->finished  = FALSE;
-    return ret;
+/* check if we have reached the end of the data to read */
+static BOOL end_of_read_data( request_t *request )
+{
+    if (!request->content_length) return TRUE;
+    if (request->read_chunked) return request->read_chunked_eof;
+    if (request->content_length == ~0u) return FALSE;
+    return (request->content_length == request->content_read);
 }
 
-void destroy_authinfo( struct authinfo *authinfo )
+static BOOL read_data( request_t *request, void *buffer, DWORD size, DWORD *read, BOOL async )
 {
-    if (!authinfo) return;
+    int count, bytes_read = 0;
 
-    if (SecIsValidHandle( &authinfo->ctx ))
-        DeleteSecurityContext( &authinfo->ctx );
-    if (SecIsValidHandle( &authinfo->cred ))
-        FreeCredentialsHandle( &authinfo->cred );
+    if (end_of_read_data( request )) goto done;
 
-    heap_free( authinfo->data );
-    heap_free( authinfo );
+    while (size)
+    {
+        if (!(count = get_available_data( request )))
+        {
+            if (!refill_buffer( request, async )) goto done;
+            if (!(count = get_available_data( request ))) goto done;
+        }
+        count = min( count, size );
+        memcpy( (char *)buffer + bytes_read, request->read_buf + request->read_pos, count );
+        remove_data( request, count );
+        if (request->read_chunked) request->read_chunked_size -= count;
+        size -= count;
+        bytes_read += count;
+        request->content_read += count;
+        if (end_of_read_data( request )) goto done;
+    }
+    if (request->read_chunked && !request->read_chunked_size) refill_buffer( request, async );
+
+done:
+    TRACE( "retrieved %u bytes (%u/%u)\n", bytes_read, request->content_read, request->content_length );
+
+    if (async) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_READ_COMPLETE, buffer, bytes_read );
+    if (read) *read = bytes_read;
+    if (end_of_read_data( request )) finished_reading( request );
+    return TRUE;
 }
 
-static BOOL get_authvalue( request_t *request, DWORD level, DWORD scheme, WCHAR *buffer, DWORD len )
+/* read any content returned by the server so that the connection can be reused */
+static void drain_content( request_t *request )
 {
-    DWORD size, index = 0;
+    DWORD size, bytes_read, bytes_total = 0, bytes_left = request->content_length - request->content_read;
+    char buffer[2048];
+
+    refill_buffer( request, FALSE );
     for (;;)
     {
-        size = len;
-        if (!query_headers( request, level, NULL, buffer, &size, &index )) return FALSE;
-        if (auth_scheme_from_header( buffer ) == scheme) break;
+        if (request->read_chunked) size = sizeof(buffer);
+        else
+        {
+            if (bytes_total >= bytes_left) return;
+            size = min( sizeof(buffer), bytes_left - bytes_total );
+        }
+        if (!read_data( request, buffer, size, &bytes_read, FALSE ) || !bytes_read) return;
+        bytes_total += bytes_read;
     }
-    return TRUE;
 }
 
-static BOOL do_authorization( request_t *request, DWORD target, DWORD scheme_flag )
+static BOOL send_request( request_t *request, LPCWSTR headers, DWORD headers_len, LPVOID optional,
+                          DWORD optional_len, DWORD total_len, DWORD_PTR context, BOOL async )
 {
-    struct authinfo *authinfo, **auth_ptr;
-    enum auth_scheme scheme = scheme_from_flag( scheme_flag );
-    const WCHAR *auth_target, *username, *password;
-    WCHAR auth_value[2048], *auth_reply;
-    DWORD len = sizeof(auth_value), len_scheme, flags;
-    BOOL ret;
+    static const WCHAR keep_alive[] = {'K','e','e','p','-','A','l','i','v','e',0};
+    static const WCHAR no_cache[]   = {'n','o','-','c','a','c','h','e',0};
+    static const WCHAR length_fmt[] = {'%','l','d',0};
 
-    if (scheme == SCHEME_INVALID) return FALSE;
+    BOOL ret = FALSE;
+    connect_t *connect = request->connect;
+    session_t *session = connect->session;
+    WCHAR *req = NULL;
+    char *req_ascii;
+    int bytes_sent;
+    DWORD len;
 
-    switch (target)
-    {
-    case WINHTTP_AUTH_TARGET_SERVER:
-        if (!get_authvalue( request, WINHTTP_QUERY_WWW_AUTHENTICATE, scheme_flag, auth_value, len ))
-            return FALSE;
-        auth_ptr = &request->authinfo;
-        auth_target = attr_authorization;
-        username = request->connect->username;
-        password = request->connect->password;
-        break;
+    clear_response_headers( request );
+    drain_content( request );
 
-    case WINHTTP_AUTH_TARGET_PROXY:
-        if (!get_authvalue( request, WINHTTP_QUERY_PROXY_AUTHENTICATE, scheme_flag, auth_value, len ))
-            return FALSE;
-        auth_ptr = &request->proxy_authinfo;
-        auth_target = attr_proxy_authorization;
-        username = request->connect->session->proxy_username;
-        password = request->connect->session->proxy_password;
-        break;
+    if (session->agent)
+        process_header( request, attr_user_agent, session->agent, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE );
 
-    default:
-        WARN("unknown target %x\n", target);
-        return FALSE;
-    }
-    authinfo = *auth_ptr;
+    if (connect->hostname)
+        add_host_header( request, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW );
 
-    switch (scheme)
+    if (request->creds[TARGET_SERVER][SCHEME_BASIC].username)
+        do_authorization( request, WINHTTP_AUTH_TARGET_SERVER, WINHTTP_AUTH_SCHEME_BASIC );
+
+    if (total_len || (request->verb && !strcmpW( request->verb, postW )))
     {
-    case SCHEME_BASIC:
+        WCHAR length[21]; /* decimal long int + null */
+        sprintfW( length, length_fmt, total_len );
+        process_header( request, attr_content_length, length, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE );
+    }
+    if (!(request->hdr.disable_flags & WINHTTP_DISABLE_KEEP_ALIVE))
     {
-        int userlen, passlen;
-
-        if (!username || !password) return FALSE;
-        if ((!authinfo && !(authinfo = alloc_authinfo())) || authinfo->finished) return FALSE;
-
-        userlen = WideCharToMultiByte( CP_UTF8, 0, username, strlenW( username ), NULL, 0, NULL, NULL );
-        passlen = WideCharToMultiByte( CP_UTF8, 0, password, strlenW( password ), NULL, 0, NULL, NULL );
-
-        authinfo->data_len = userlen + 1 + passlen;
-        if (!(authinfo->data = heap_alloc( authinfo->data_len ))) return FALSE;
-
-        WideCharToMultiByte( CP_UTF8, 0, username, -1, authinfo->data, userlen, NULL, NULL );
-        authinfo->data[userlen] = ':';
-        WideCharToMultiByte( CP_UTF8, 0, password, -1, authinfo->data + userlen + 1, passlen, NULL, NULL );
-
-        authinfo->scheme   = SCHEME_BASIC;
-        authinfo->finished = TRUE;
-        break;
+        process_header( request, attr_connection, keep_alive, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE );
     }
-    case SCHEME_NTLM:
-    case SCHEME_NEGOTIATE:
+    if (request->hdr.flags & WINHTTP_FLAG_REFRESH)
+    {
+        process_header( request, attr_pragma, no_cache, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE );
+        process_header( request, attr_cache_control, no_cache, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE );
+    }
+    if (headers && !add_request_headers( request, headers, headers_len, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE ))
+    {
+        TRACE("failed to add request headers\n");
+        return FALSE;
+    }
+    if (!(request->hdr.disable_flags & WINHTTP_DISABLE_COOKIES) && !add_cookie_headers( request ))
     {
-        SECURITY_STATUS status;
-        SecBufferDesc out_desc, in_desc;
-        SecBuffer out, in;
-        ULONG flags = ISC_REQ_CONNECTION|ISC_REQ_USE_DCE_STYLE|ISC_REQ_MUTUAL_AUTH|ISC_REQ_DELEGATE;
-        const WCHAR *p;
-        BOOL first = FALSE;
+        WARN("failed to add cookie headers\n");
+        return FALSE;
+    }
 
-        if (!authinfo)
-        {
-            TimeStamp exp;
-            SEC_WINNT_AUTH_IDENTITY_W id;
-            WCHAR *domain, *user;
+    if (context) request->hdr.context = context;
 
-            if (!username || !password || !(authinfo = alloc_authinfo())) return FALSE;
+    if (!(ret = open_connection( request ))) goto end;
+    if (!(req = build_request_string( request ))) goto end;
 
-            first = TRUE;
-            domain = (WCHAR *)username;
-            user = strchrW( username, '\\' );
+    if (!(req_ascii = strdupWA( req ))) goto end;
+    TRACE("full request: %s\n", debugstr_a(req_ascii));
+    len = strlen(req_ascii);
 
-            if (user) user++;
-            else
-            {
-                user = (WCHAR *)username;
-                domain = NULL;
-            }
-            id.Flags          = SEC_WINNT_AUTH_IDENTITY_UNICODE;
-            id.User           = user;
-            id.UserLength     = strlenW( user );
-            id.Domain         = domain;
-            id.DomainLength   = domain ? user - domain - 1 : 0;
-            id.Password       = (WCHAR *)password;
-            id.PasswordLength = strlenW( password );
+    send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_SENDING_REQUEST, NULL, 0 );
 
-            status = AcquireCredentialsHandleW( NULL, (SEC_WCHAR *)auth_schemes[scheme].str,
-                                                SECPKG_CRED_OUTBOUND, NULL, &id, NULL, NULL,
-                                                &authinfo->cred, &exp );
-            if (status == SEC_E_OK)
-            {
-                PSecPkgInfoW info;
-                status = QuerySecurityPackageInfoW( (SEC_WCHAR *)auth_schemes[scheme].str, &info );
-                if (status == SEC_E_OK)
-                {
-                    authinfo->max_token = info->cbMaxToken;
-                    FreeContextBuffer( info );
-                }
-            }
-            if (status != SEC_E_OK)
-            {
-                WARN("AcquireCredentialsHandleW for scheme %s failed with error 0x%08x\n",
-                     debugstr_w(auth_schemes[scheme].str), status);
-                heap_free( authinfo );
-                return FALSE;
-            }
-            authinfo->scheme = scheme;
-        }
-        else if (authinfo->finished) return FALSE;
+    ret = netconn_send( request->netconn, req_ascii, len, &bytes_sent );
+    heap_free( req_ascii );
+    if (!ret) goto end;
 
-        if ((strlenW( auth_value ) < auth_schemes[authinfo->scheme].len ||
-            strncmpiW( auth_value, auth_schemes[authinfo->scheme].str, auth_schemes[authinfo->scheme].len )))
+    if (optional_len)
+    {
+        if (!netconn_send( request->netconn, optional, optional_len, &bytes_sent )) goto end;
+        request->optional = optional;
+        request->optional_len = optional_len;
+        len += optional_len;
+    }
+    send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_REQUEST_SENT, &len, sizeof(len) );
+
+end:
+    if (async)
+    {
+        if (ret) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE, NULL, 0 );
+        else
         {
-            ERR("authentication scheme changed from %s to %s\n",
-                debugstr_w(auth_schemes[authinfo->scheme].str), debugstr_w(auth_value));
-            destroy_authinfo( authinfo );
-            *auth_ptr = NULL;
-            return FALSE;
+            WINHTTP_ASYNC_RESULT result;
+            result.dwResult = API_SEND_REQUEST;
+            result.dwError  = get_last_error();
+            send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_REQUEST_ERROR, &result, sizeof(result) );
         }
-        in.BufferType = SECBUFFER_TOKEN;
-        in.cbBuffer   = 0;
-        in.pvBuffer   = NULL;
+    }
+    heap_free( req );
+    return ret;
+}
 
-        in_desc.ulVersion = 0;
-        in_desc.cBuffers  = 1;
-        in_desc.pBuffers  = &in;
+static void task_send_request( task_header_t *task )
+{
+    send_request_t *s = (send_request_t *)task;
+    send_request( s->hdr.request, s->headers, s->headers_len, s->optional, s->optional_len, s->total_len, s->context, TRUE );
+    heap_free( s->headers );
+}
 
-        p = auth_value + auth_schemes[scheme].len;
-        if (*p == ' ')
-        {
-            int len = strlenW( ++p );
-            in.cbBuffer = decode_base64( p, len, NULL );
-            if (!(in.pvBuffer = heap_alloc( in.cbBuffer ))) {
-                destroy_authinfo( authinfo );
-                *auth_ptr = NULL;
-                return FALSE;
-            }
-            decode_base64( p, len, in.pvBuffer );
-        }
-        out.BufferType = SECBUFFER_TOKEN;
-        out.cbBuffer   = authinfo->max_token;
-        if (!(out.pvBuffer = heap_alloc( authinfo->max_token )))
-        {
-            heap_free( in.pvBuffer );
-            destroy_authinfo( authinfo );
-            *auth_ptr = NULL;
-            return FALSE;
-        }
-        out_desc.ulVersion = 0;
-        out_desc.cBuffers  = 1;
-        out_desc.pBuffers  = &out;
+/***********************************************************************
+ *          WinHttpSendRequest (winhttp.@)
+ */
+BOOL WINAPI WinHttpSendRequest( HINTERNET hrequest, LPCWSTR headers, DWORD headers_len,
+                                LPVOID optional, DWORD optional_len, DWORD total_len, DWORD_PTR context )
+{
+    BOOL ret;
+    request_t *request;
 
-        status = InitializeSecurityContextW( first ? &authinfo->cred : NULL, first ? NULL : &authinfo->ctx,
-                                             first ? request->connect->servername : NULL, flags, 0,
-                                             SECURITY_NETWORK_DREP, in.pvBuffer ? &in_desc : NULL, 0,
-                                             &authinfo->ctx, &out_desc, &authinfo->attr, &authinfo->exp );
-        heap_free( in.pvBuffer );
-        if (status == SEC_E_OK)
-        {
-            heap_free( authinfo->data );
-            authinfo->data     = out.pvBuffer;
-            authinfo->data_len = out.cbBuffer;
-            authinfo->finished = TRUE;
-            TRACE("sending last auth packet\n");
-        }
-        else if (status == SEC_I_CONTINUE_NEEDED)
-        {
-            heap_free( authinfo->data );
-            authinfo->data     = out.pvBuffer;
-            authinfo->data_len = out.cbBuffer;
-            TRACE("sending next auth packet\n");
-        }
-        else
-        {
-            ERR("InitializeSecurityContextW failed with error 0x%08x\n", status);
-            heap_free( out.pvBuffer );
-            destroy_authinfo( authinfo );
-            *auth_ptr = NULL;
-            return FALSE;
-        }
-        break;
+    TRACE("%p, %s, %u, %u, %u, %lx\n", hrequest, debugstr_wn(headers, headers_len), headers_len, optional_len,
+          total_len, context);
+
+    if (!(request = (request_t *)grab_object( hrequest )))
+    {
+        set_last_error( ERROR_INVALID_HANDLE );
+        return FALSE;
     }
-    default:
-        ERR("invalid scheme %u\n", scheme);
+    if (request->hdr.type != WINHTTP_HANDLE_TYPE_REQUEST)
+    {
+        release_object( &request->hdr );
+        set_last_error( ERROR_WINHTTP_INCORRECT_HANDLE_TYPE );
         return FALSE;
     }
-    *auth_ptr = authinfo;
 
-    len_scheme = auth_schemes[authinfo->scheme].len;
-    len = len_scheme + 1 + ((authinfo->data_len + 2) * 4) / 3;
-    if (!(auth_reply = heap_alloc( (len + 1) * sizeof(WCHAR) ))) return FALSE;
+    if (headers && !headers_len) headers_len = strlenW( headers );
 
-    memcpy( auth_reply, auth_schemes[authinfo->scheme].str, len_scheme * sizeof(WCHAR) );
-    auth_reply[len_scheme] = ' ';
-    encode_base64( authinfo->data, authinfo->data_len, auth_reply + len_scheme + 1 );
+    if (request->connect->hdr.flags & WINHTTP_FLAG_ASYNC)
+    {
+        send_request_t *s;
 
-    flags = WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE;
-    ret = process_header( request, auth_target, auth_reply, flags, TRUE );
-    heap_free( auth_reply );
+        if (!(s = heap_alloc( sizeof(send_request_t) ))) return FALSE;
+        s->hdr.request  = request;
+        s->hdr.proc     = task_send_request;
+        s->headers      = strdupW( headers );
+        s->headers_len  = headers_len;
+        s->optional     = optional;
+        s->optional_len = optional_len;
+        s->total_len    = total_len;
+        s->context      = context;
+
+        addref_object( &request->hdr );
+        ret = queue_task( (task_header_t *)s );
+    }
+    else
+        ret = send_request( request, headers, headers_len, optional, optional_len, total_len, context, FALSE );
+
+    release_object( &request->hdr );
+    if (ret) set_last_error( ERROR_SUCCESS );
     return ret;
 }
 
-static BOOL set_credentials( request_t *request, DWORD target, DWORD scheme, const WCHAR *username,
+static BOOL set_credentials( request_t *request, DWORD target, DWORD scheme_flag, const WCHAR *username,
                              const WCHAR *password )
 {
-    if ((scheme == WINHTTP_AUTH_SCHEME_BASIC || scheme == WINHTTP_AUTH_SCHEME_DIGEST) &&
-        (!username || !password))
+    enum auth_scheme scheme = scheme_from_flag( scheme_flag );
+
+    if (scheme == SCHEME_INVALID || ((scheme == SCHEME_BASIC || scheme == SCHEME_DIGEST) && (!username || !password)))
     {
         set_last_error( ERROR_INVALID_PARAMETER );
         return FALSE;
@@ -1701,24 +2227,24 @@ static BOOL set_credentials( request_t *request, DWORD target, DWORD scheme, con
     {
     case WINHTTP_AUTH_TARGET_SERVER:
     {
-        heap_free( request->connect->username );
-        if (!username) request->connect->username = NULL;
-        else if (!(request->connect->username = strdupW( username ))) return FALSE;
+        heap_free( request->creds[TARGET_SERVER][scheme].username );
+        if (!username) request->creds[TARGET_SERVER][scheme].username = NULL;
+        else if (!(request->creds[TARGET_SERVER][scheme].username = strdupW( username ))) return FALSE;
 
-        heap_free( request->connect->password );
-        if (!password) request->connect->password = NULL;
-        else if (!(request->connect->password = strdupW( password ))) return FALSE;
+        heap_free( request->creds[TARGET_SERVER][scheme].password );
+        if (!password) request->creds[TARGET_SERVER][scheme].password = NULL;
+        else if (!(request->creds[TARGET_SERVER][scheme].password = strdupW( password ))) return FALSE;
         break;
     }
     case WINHTTP_AUTH_TARGET_PROXY:
     {
-        heap_free( request->connect->session->proxy_username );
-        if (!username) request->connect->session->proxy_username = NULL;
-        else if (!(request->connect->session->proxy_username = strdupW( username ))) return FALSE;
+        heap_free( request->creds[TARGET_PROXY][scheme].username );
+        if (!username) request->creds[TARGET_PROXY][scheme].username = NULL;
+        else if (!(request->creds[TARGET_PROXY][scheme].username = strdupW( username ))) return FALSE;
 
-        heap_free( request->connect->session->proxy_password );
-        if (!password) request->connect->session->proxy_password = NULL;
-        else if (!(request->connect->session->proxy_password = strdupW( password ))) return FALSE;
+        heap_free( request->creds[TARGET_PROXY][scheme].password );
+        if (!password) request->creds[TARGET_PROXY][scheme].password = NULL;
+        else if (!(request->creds[TARGET_PROXY][scheme].password = strdupW( password ))) return FALSE;
         break;
     }
     default:
@@ -1754,6 +2280,7 @@ BOOL WINAPI WinHttpSetCredentials( HINTERNET hrequest, DWORD target, DWORD schem
     ret = set_credentials( request, target, scheme, username, password );
 
     release_object( &request->hdr );
+    if (ret) set_last_error( ERROR_SUCCESS );
     return ret;
 }
 
@@ -1769,83 +2296,53 @@ static BOOL handle_authorization( request_t *request, DWORD status )
         break;
 
     case HTTP_STATUS_PROXY_AUTH_REQ:
-        target = WINHTTP_AUTH_TARGET_PROXY;
-        level  = WINHTTP_QUERY_PROXY_AUTHENTICATE;
-        break;
-
-    default:
-        WARN("unhandled status %u\n", status);
-        return FALSE;
-    }
-
-    if (!query_auth_schemes( request, level, &schemes, &first )) return FALSE;
-    if (do_authorization( request, target, first )) return TRUE;
-
-    schemes &= ~first;
-    for (i = 0; i < num_auth_schemes; i++)
-    {
-        if (!(schemes & auth_schemes[i].scheme)) continue;
-        if (do_authorization( request, target, auth_schemes[i].scheme )) return TRUE;
-    }
-    return FALSE;
-}
-
-/* set the request content length based on the headers */
-static DWORD set_content_length( request_t *request )
-{
-    WCHAR encoding[20];
-    DWORD buflen;
-
-    buflen = sizeof(request->content_length);
-    if (!query_headers( request, WINHTTP_QUERY_CONTENT_LENGTH|WINHTTP_QUERY_FLAG_NUMBER,
-                        NULL, &request->content_length, &buflen, NULL ))
-        request->content_length = ~0u;
-
-    buflen = sizeof(encoding);
-    if (query_headers( request, WINHTTP_QUERY_TRANSFER_ENCODING, NULL, encoding, &buflen, NULL ) &&
-        !strcmpiW( encoding, chunkedW ))
-    {
-        request->content_length = ~0u;
-        request->read_chunked = TRUE;
-        request->read_chunked_size = ~0u;
-        request->read_chunked_eof = FALSE;
-    }
-    request->content_read = 0;
-    return request->content_length;
-}
-
-/* read some more data into the read buffer */
-static BOOL read_more_data( request_t *request, int maxlen, BOOL notify )
-{
-    int len;
-    BOOL ret;
-
-    if (request->read_chunked_eof) return FALSE;
-
-    if (request->read_size && request->read_pos)
-    {
-        /* move existing data to the start of the buffer */
-        memmove( request->read_buf, request->read_buf + request->read_pos, request->read_size );
-        request->read_pos = 0;
-    }
-    if (maxlen == -1) maxlen = sizeof(request->read_buf);
-
-    if (notify) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE, NULL, 0 );
+        target = WINHTTP_AUTH_TARGET_PROXY;
+        level  = WINHTTP_QUERY_PROXY_AUTHENTICATE;
+        break;
 
-    ret = netconn_recv( &request->netconn, request->read_buf + request->read_size,
-                        maxlen - request->read_size, 0, &len );
+    default:
+        WARN("unhandled status %u\n", status);
+        return FALSE;
+    }
 
-    if (notify) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED, &len, sizeof(len) );
+    if (!query_auth_schemes( request, level, &schemes, &first )) return FALSE;
+    if (do_authorization( request, target, first )) return TRUE;
 
-    request->read_size += len;
-    return ret;
+    schemes &= ~first;
+    for (i = 0; i < num_auth_schemes; i++)
+    {
+        if (!(schemes & auth_schemes[i].scheme)) continue;
+        if (do_authorization( request, target, auth_schemes[i].scheme )) return TRUE;
+    }
+    return FALSE;
 }
 
-/* remove some amount of data from the read buffer */
-static void remove_data( request_t *request, int count )
+/* set the request content length based on the headers */
+static DWORD set_content_length( request_t *request, DWORD status )
 {
-    if (!(request->read_size -= count)) request->read_pos = 0;
-    else request->read_pos += count;
+    WCHAR encoding[20];
+    DWORD buflen = sizeof(request->content_length);
+
+    if (status == HTTP_STATUS_NO_CONTENT || status == HTTP_STATUS_NOT_MODIFIED || !strcmpW( request->verb, headW ))
+        request->content_length = 0;
+    else
+    {
+        if (!query_headers( request, WINHTTP_QUERY_CONTENT_LENGTH|WINHTTP_QUERY_FLAG_NUMBER,
+                            NULL, &request->content_length, &buflen, NULL ))
+            request->content_length = ~0u;
+
+        buflen = sizeof(encoding);
+        if (query_headers( request, WINHTTP_QUERY_TRANSFER_ENCODING, NULL, encoding, &buflen, NULL ) &&
+            !strcmpiW( encoding, chunkedW ))
+        {
+            request->content_length = ~0u;
+            request->read_chunked = TRUE;
+            request->read_chunked_size = ~0u;
+            request->read_chunked_eof = FALSE;
+        }
+    }
+    request->content_read = 0;
+    return request->content_length;
 }
 
 static BOOL read_line( request_t *request, char *buffer, DWORD *len )
@@ -1886,102 +2383,6 @@ static BOOL read_line( request_t *request, char *buffer, DWORD *len )
     return TRUE;
 }
 
-/* discard data contents until we reach end of line */
-static BOOL discard_eol( request_t *request, BOOL notify )
-{
-    do
-    {
-        char *eol = memchr( request->read_buf + request->read_pos, '\n', request->read_size );
-        if (eol)
-        {
-            remove_data( request, (eol + 1) - (request->read_buf + request->read_pos) );
-            break;
-        }
-        request->read_pos = request->read_size = 0;  /* discard everything */
-        if (!read_more_data( request, -1, notify )) return FALSE;
-    } while (request->read_size);
-    return TRUE;
-}
-
-/* read the size of the next chunk */
-static BOOL start_next_chunk( request_t *request, BOOL notify )
-{
-    DWORD chunk_size = 0;
-
-    assert(!request->read_chunked_size || request->read_chunked_size == ~0u);
-
-    if (request->read_chunked_eof) return FALSE;
-
-    /* read terminator for the previous chunk */
-    if (!request->read_chunked_size && !discard_eol( request, notify )) return FALSE;
-
-    for (;;)
-    {
-        while (request->read_size)
-        {
-            char ch = request->read_buf[request->read_pos];
-            if (ch >= '0' && ch <= '9') chunk_size = chunk_size * 16 + ch - '0';
-            else if (ch >= 'a' && ch <= 'f') chunk_size = chunk_size * 16 + ch - 'a' + 10;
-            else if (ch >= 'A' && ch <= 'F') chunk_size = chunk_size * 16 + ch - 'A' + 10;
-            else if (ch == ';' || ch == '\r' || ch == '\n')
-            {
-                TRACE("reading %u byte chunk\n", chunk_size);
-
-                if (request->content_length == ~0u) request->content_length = chunk_size;
-                else request->content_length += chunk_size;
-
-                request->read_chunked_size = chunk_size;
-                if (!chunk_size) request->read_chunked_eof = TRUE;
-
-                return discard_eol( request, notify );
-            }
-            remove_data( request, 1 );
-        }
-        if (!read_more_data( request, -1, notify )) return FALSE;
-        if (!request->read_size)
-        {
-            request->content_length = request->content_read = 0;
-            request->read_chunked_size = 0;
-            return TRUE;
-        }
-    }
-}
-
-/* return the size of data available to be read immediately */
-static DWORD get_available_data( request_t *request )
-{
-    if (request->read_chunked) return min( request->read_chunked_size, request->read_size );
-    return request->read_size;
-}
-
-/* check if we have reached the end of the data to read */
-static BOOL end_of_read_data( request_t *request )
-{
-    if (request->read_chunked) return request->read_chunked_eof;
-    if (request->content_length == ~0u) return FALSE;
-    return (request->content_length == request->content_read);
-}
-
-static BOOL refill_buffer( request_t *request, BOOL notify )
-{
-    int len = sizeof(request->read_buf);
-
-    if (request->read_chunked)
-    {
-        if (request->read_chunked_eof) return FALSE;
-        if (request->read_chunked_size == ~0u || !request->read_chunked_size)
-        {
-            if (!start_next_chunk( request, notify )) return FALSE;
-        }
-    }
-    if (!request->read_chunked && request->content_length != ~0u)
-        len = min( len, request->content_length - request->content_read );
-    if (len <= request->read_size) return TRUE;
-    if (!read_more_data( request, len, notify )) return FALSE;
-    if (!request->read_size) request->content_length = request->content_read = 0;
-    return TRUE;
-}
-
 #define MAX_REPLY_LEN   1460
 #define INITIAL_HEADER_BUFFER_LEN  512
 
@@ -1990,19 +2391,17 @@ static BOOL read_reply( request_t *request )
     static const WCHAR crlf[] = {'\r','\n',0};
 
     char buffer[MAX_REPLY_LEN];
-    DWORD buflen, len, offset, received_len, crlf_len = 2; /* strlenW(crlf) */
+    DWORD buflen, len, offset, crlf_len = 2; /* strlenW(crlf) */
     char *status_code, *status_text;
     WCHAR *versionW, *status_textW, *raw_headers;
     WCHAR status_codeW[4]; /* sizeof("nnn") */
 
-    if (!netconn_connected( &request->netconn )) return FALSE;
+    if (!request->netconn) return FALSE;
 
-    received_len = 0;
     do
     {
         buflen = MAX_REPLY_LEN;
         if (!read_line( request, buffer, &buflen )) return FALSE;
-        received_len += buflen;
 
         /* first line should look like 'HTTP/1.x nnn OK' where nnn is the status code */
         if (!(status_code = strchr( buffer, ' ' ))) return FALSE;
@@ -2021,7 +2420,9 @@ static BOOL read_reply( request_t *request )
     /*  we rely on the fact that the protocol is ascii */
     MultiByteToWideChar( CP_ACP, 0, status_code, len, status_codeW, len );
     status_codeW[len] = 0;
-    if (!(process_header( request, attr_status, status_codeW, WINHTTP_ADDREQ_FLAG_REPLACE, FALSE ))) return FALSE;
+    if (!(process_header( request, attr_status, status_codeW,
+                          WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE, FALSE )))
+        return FALSE;
 
     len = status_code - buffer;
     if (!(versionW = heap_alloc( len * sizeof(WCHAR) ))) return FALSE;
@@ -2053,8 +2454,7 @@ static BOOL read_reply( request_t *request )
 
         buflen = MAX_REPLY_LEN;
         if (!read_line( request, buffer, &buflen )) return TRUE;
-        received_len += buflen;
-        if (!*buffer) break;
+        if (!*buffer) buflen = 1;
 
         while (len - offset < buflen + crlf_len)
         {
@@ -2063,6 +2463,11 @@ static BOOL read_reply( request_t *request )
             if (!(tmp = heap_realloc( raw_headers, len * sizeof(WCHAR) ))) return FALSE;
             request->raw_headers = raw_headers = tmp;
         }
+        if (!*buffer)
+        {
+            memcpy( raw_headers + offset, crlf, sizeof(crlf) );
+            break;
+        }
         MultiByteToWideChar( CP_ACP, 0, buffer, buflen, raw_headers + offset, buflen );
 
         if (!(header = parse_header( raw_headers + offset ))) break;
@@ -2080,69 +2485,6 @@ static BOOL read_reply( request_t *request )
     return TRUE;
 }
 
-static void finished_reading( request_t *request )
-{
-    static const WCHAR closeW[] = {'c','l','o','s','e',0};
-
-    BOOL close = FALSE;
-    WCHAR connection[20];
-    DWORD size = sizeof(connection);
-
-    if (request->hdr.disable_flags & WINHTTP_DISABLE_KEEP_ALIVE) close = TRUE;
-    else if (query_headers( request, WINHTTP_QUERY_CONNECTION, NULL, connection, &size, NULL ) ||
-             query_headers( request, WINHTTP_QUERY_PROXY_CONNECTION, NULL, connection, &size, NULL ))
-    {
-        if (!strcmpiW( connection, closeW )) close = TRUE;
-    }
-    else if (!strcmpW( request->version, http1_0 )) close = TRUE;
-    if (close) close_connection( request );
-}
-
-static BOOL read_data( request_t *request, void *buffer, DWORD size, DWORD *read, BOOL async )
-{
-    int count, bytes_read = 0;
-
-    if (end_of_read_data( request )) goto done;
-
-    while (size)
-    {
-        if (!(count = get_available_data( request )))
-        {
-            if (!refill_buffer( request, async )) goto done;
-            if (!(count = get_available_data( request ))) goto done;
-        }
-        count = min( count, size );
-        memcpy( (char *)buffer + bytes_read, request->read_buf + request->read_pos, count );
-        remove_data( request, count );
-        if (request->read_chunked) request->read_chunked_size -= count;
-        size -= count;
-        bytes_read += count;
-        request->content_read += count;
-        if (end_of_read_data( request )) goto done;
-    }
-    if (request->read_chunked && !request->read_chunked_size) refill_buffer( request, async );
-
-done:
-    TRACE( "retrieved %u bytes (%u/%u)\n", bytes_read, request->content_read, request->content_length );
-
-    if (async) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_READ_COMPLETE, buffer, bytes_read );
-    if (read) *read = bytes_read;
-    if (end_of_read_data( request )) finished_reading( request );
-    return TRUE;
-}
-
-/* read any content returned by the server so that the connection can be reused */
-static void drain_content( request_t *request )
-{
-    DWORD bytes_read;
-    char buffer[2048];
-
-    for (;;)
-    {
-        if (!read_data( request, buffer, sizeof(buffer), &bytes_read, FALSE ) || !bytes_read) return;
-    }
-}
-
 static void record_cookies( request_t *request )
 {
     unsigned int i;
@@ -2191,17 +2533,24 @@ static BOOL handle_redirect( request_t *request, DWORD status )
     {
         WCHAR *path, *p;
 
-        len = strlenW( location ) + 1;
-        if (location[0] != '/') len++;
-        if (!(p = path = heap_alloc( len * sizeof(WCHAR) ))) goto end;
-
-        if (location[0] != '/') *p++ = '/';
-        strcpyW( p, location );
-
+        if (location[0] == '/')
+        {
+            len = strlenW( location );
+            if (!(path = heap_alloc( (len + 1) * sizeof(WCHAR) ))) goto end;
+            strcpyW( path, location );
+        }
+        else
+        {
+            if ((p = strrchrW( request->path, '/' ))) *p = 0;
+            len = strlenW( request->path ) + 1 + strlenW( location );
+            if (!(path = heap_alloc( (len + 1) * sizeof(WCHAR) ))) goto end;
+            strcpyW( path, request->path );
+            strcatW( path, slashW );
+            strcatW( path, location );
+        }
         heap_free( request->path );
         request->path = path;
 
-        drain_content( request );
         send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_REDIRECT, location, len_url + 1 );
     }
     else
@@ -2218,7 +2567,6 @@ static BOOL handle_redirect( request_t *request, DWORD status )
             request->hdr.flags |= WINHTTP_FLAG_SECURE;
         }
 
-        drain_content( request );
         send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_REDIRECT, location, len_url + 1 );
 
         len = uc.dwHostNameLength;
@@ -2234,13 +2582,15 @@ static BOOL handle_redirect( request_t *request, DWORD status )
             connect->hostport = port;
             if (!(ret = set_server_for_hostname( connect, hostname, port ))) goto end;
 
-            netconn_close( &request->netconn );
-            if (!(ret = netconn_init( &request->netconn ))) goto end;
+            netconn_close( request->netconn );
+            request->netconn = NULL;
+            request->content_length = request->content_read = 0;
             request->read_pos = request->read_size = 0;
-            request->read_chunked = FALSE;
-            request->read_chunked_eof = FALSE;
+            request->read_chunked = request->read_chunked_eof = FALSE;
         }
-        if (!(ret = add_host_header( request, WINHTTP_ADDREQ_FLAG_REPLACE ))) goto end;
+        else heap_free( hostname );
+
+        if (!(ret = add_host_header( request, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE ))) goto end;
         if (!(ret = open_connection( request ))) goto end;
 
         heap_free( request->path );
@@ -2268,7 +2618,6 @@ static BOOL handle_redirect( request_t *request, DWORD status )
     ret = TRUE;
 
 end:
-    if (!ret) heap_free( hostname );
     heap_free( location );
     return ret;
 }
@@ -2289,7 +2638,7 @@ static BOOL receive_response( request_t *request, BOOL async )
         query = WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER;
         if (!(ret = query_headers( request, query, NULL, &status, &size, NULL ))) break;
 
-        set_content_length( request );
+        set_content_length( request, status );
 
         if (!(request->hdr.disable_flags & WINHTTP_DISABLE_COOKIES)) record_cookies( request );
 
@@ -2301,27 +2650,21 @@ static BOOL receive_response( request_t *request, BOOL async )
             if (!(ret = handle_redirect( request, status ))) break;
 
             /* recurse synchronously */
-            send_request( request, NULL, 0, request->optional, request->optional_len, 0, 0, FALSE );
-            continue;
+            if ((ret = send_request( request, NULL, 0, request->optional, request->optional_len, 0, 0, FALSE ))) continue;
         }
         else if (status == HTTP_STATUS_DENIED || status == HTTP_STATUS_PROXY_AUTH_REQ)
         {
             if (request->hdr.disable_flags & WINHTTP_DISABLE_AUTHENTICATION) break;
 
-            drain_content( request );
-            if (!handle_authorization( request, status ))
-            {
-                ret = TRUE;
-                break;
-            }
+            if (!handle_authorization( request, status )) break;
+
             /* recurse synchronously */
-            send_request( request, NULL, 0, request->optional, request->optional_len, 0, 0, FALSE );
-            continue;
+            if ((ret = send_request( request, NULL, 0, request->optional, request->optional_len, 0, 0, FALSE ))) continue;
         }
         break;
     }
 
-    if (ret) refill_buffer( request, FALSE );
+    if (request->content_length) refill_buffer( request, FALSE );
 
     if (async)
     {
@@ -2380,23 +2723,28 @@ BOOL WINAPI WinHttpReceiveResponse( HINTERNET hrequest, LPVOID reserved )
         ret = receive_response( request, FALSE );
 
     release_object( &request->hdr );
+    if (ret) set_last_error( ERROR_SUCCESS );
     return ret;
 }
 
 static BOOL query_data_available( request_t *request, DWORD *available, BOOL async )
 {
-    DWORD count = get_available_data( request );
+    DWORD count = 0;
+
+    if (end_of_read_data( request )) goto done;
 
-    if (!request->read_chunked)
-        count += netconn_query_data_available( &request->netconn );
+    count = get_available_data( request );
+    if (!request->read_chunked && request->netconn)
+        count += netconn_query_data_available( request->netconn );
     if (!count)
     {
         refill_buffer( request, async );
         count = get_available_data( request );
-        if (!request->read_chunked)
-            count += netconn_query_data_available( &request->netconn );
+        if (!request->read_chunked && request->netconn)
+            count += netconn_query_data_available( request->netconn );
     }
 
+done:
     if (async) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE, &count, sizeof(count) );
     TRACE("%u bytes available\n", count);
     if (available) *available = count;
@@ -2447,6 +2795,7 @@ BOOL WINAPI WinHttpQueryDataAvailable( HINTERNET hrequest, LPDWORD available )
         ret = query_data_available( request, available, FALSE );
 
     release_object( &request->hdr );
+    if (ret) set_last_error( ERROR_SUCCESS );
     return ret;
 }
 
@@ -2496,6 +2845,7 @@ BOOL WINAPI WinHttpReadData( HINTERNET hrequest, LPVOID buffer, DWORD to_read, L
         ret = read_data( request, buffer, to_read, read, FALSE );
 
     release_object( &request->hdr );
+    if (ret) set_last_error( ERROR_SUCCESS );
     return ret;
 }
 
@@ -2504,7 +2854,7 @@ static BOOL write_data( request_t *request, LPCVOID buffer, DWORD to_write, LPDW
     BOOL ret;
     int num_bytes;
 
-    ret = netconn_send( &request->netconn, buffer, to_write, &num_bytes );
+    ret = netconn_send( request->netconn, buffer, to_write, &num_bytes );
 
     if (async)
     {
@@ -2567,6 +2917,7 @@ BOOL WINAPI WinHttpWriteData( HINTERNET hrequest, LPCVOID buffer, DWORD to_write
         ret = write_data( request, buffer, to_write, written, FALSE );
 
     release_object( &request->hdr );
+    if (ret) set_last_error( ERROR_SUCCESS );
     return ret;
 }
 
@@ -2606,6 +2957,8 @@ struct winhttp_request
     LONG send_timeout;
     LONG receive_timeout;
     WINHTTP_PROXY_INFO proxy;
+    BOOL async;
+    UINT url_codepage;
 };
 
 static inline struct winhttp_request *impl_from_IWinHttpRequest( IWinHttpRequest *iface )
@@ -2624,8 +2977,20 @@ static ULONG WINAPI winhttp_request_AddRef(
 static void cancel_request( struct winhttp_request *request )
 {
     if (request->state <= REQUEST_STATE_CANCELLED) return;
-    if (request->thread) SetEvent( request->cancel );
+
+    SetEvent( request->cancel );
+    LeaveCriticalSection( &request->cs );
+    WaitForSingleObject( request->thread, INFINITE );
+    EnterCriticalSection( &request->cs );
+
     request->state = REQUEST_STATE_CANCELLED;
+
+    CloseHandle( request->thread );
+    request->thread = NULL;
+    CloseHandle( request->wait );
+    request->wait = NULL;
+    CloseHandle( request->cancel );
+    request->cancel = NULL;
 }
 
 /* critical section must be held */
@@ -2745,9 +3110,22 @@ static HRESULT get_typeinfo( enum type_id tid, ITypeInfo **ret )
             ITypeInfo_Release( typeinfo );
     }
     *ret = winhttp_typeinfo[tid];
+    ITypeInfo_AddRef(winhttp_typeinfo[tid]);
     return S_OK;
 }
 
+void release_typelib(void)
+{
+    unsigned i;
+
+    for (i = 0; i < sizeof(winhttp_typeinfo)/sizeof(*winhttp_typeinfo); i++)
+        if (winhttp_typeinfo[i])
+            ITypeInfo_Release(winhttp_typeinfo[i]);
+
+    if (winhttp_typelib)
+        ITypeLib_Release(winhttp_typelib);
+}
+
 static HRESULT WINAPI winhttp_request_GetTypeInfo(
     IWinHttpRequest *iface,
     UINT index,
@@ -2803,6 +3181,48 @@ static HRESULT WINAPI winhttp_request_Invoke(
     TRACE("%p, %d, %s, %d, %d, %p, %p, %p, %p\n", request, member, debugstr_guid(riid),
           lcid, flags, params, result, excep_info, arg_err);
 
+    if (!IsEqualIID( riid, &IID_NULL )) return DISP_E_UNKNOWNINTERFACE;
+
+    if (member == DISPID_HTTPREQUEST_OPTION)
+    {
+        VARIANT ret_value, option;
+        UINT err_pos;
+
+        if (!result) result = &ret_value;
+        if (!arg_err) arg_err = &err_pos;
+
+        VariantInit( &option );
+        VariantInit( result );
+
+        if (!flags) return S_OK;
+
+        if (flags == DISPATCH_PROPERTYPUT)
+        {
+            hr = DispGetParam( params, 0, VT_I4, &option, arg_err );
+            if (FAILED(hr)) return hr;
+
+            hr = IWinHttpRequest_put_Option( &request->IWinHttpRequest_iface, V_I4( &option ), params->rgvarg[0] );
+            if (FAILED(hr))
+                WARN("put_Option(%d) failed: %x\n", V_I4( &option ), hr);
+            return hr;
+        }
+        else if (flags & (DISPATCH_PROPERTYGET | DISPATCH_METHOD))
+        {
+            hr = DispGetParam( params, 0, VT_I4, &option, arg_err );
+            if (FAILED(hr)) return hr;
+
+            hr = IWinHttpRequest_get_Option( &request->IWinHttpRequest_iface, V_I4( &option ), result );
+            if (FAILED(hr))
+                WARN("get_Option(%d) failed: %x\n", V_I4( &option ), hr);
+            return hr;
+        }
+
+        FIXME("unsupported flags %x\n", flags);
+        return E_NOTIMPL;
+    }
+
+    /* fallback to standard implementation */
+
     hr = get_typeinfo( IWinHttpRequest_tid, &typeinfo );
     if (SUCCEEDED(hr))
     {
@@ -2876,7 +3296,7 @@ static HRESULT WINAPI winhttp_request_SetCredentials(
     DWORD target, scheme = WINHTTP_AUTH_SCHEME_BASIC; /* FIXME: query supported schemes */
     DWORD err = ERROR_SUCCESS;
 
-    TRACE("%p, %s, %p\n", request, debugstr_w(username), password);
+    TRACE("%p, %s, %p, 0x%08x\n", request, debugstr_w(username), password, flags);
 
     EnterCriticalSection( &request->cs );
     if (request->state < REQUEST_STATE_OPEN)
@@ -2919,8 +3339,9 @@ static void initialize_request( struct winhttp_request *request )
     request->bytes_available = 0;
     request->bytes_read = 0;
     request->error = ERROR_SUCCESS;
+    request->async = FALSE;
     request->logon_policy = WINHTTP_AUTOLOGON_SECURITY_LEVEL_MEDIUM;
-    request->disable_feature = WINHTTP_DISABLE_AUTHENTICATION;
+    request->disable_feature = 0;
     request->proxy.dwAccessType = WINHTTP_ACCESS_TYPE_DEFAULT_PROXY;
     request->proxy.lpszProxy = NULL;
     request->proxy.lpszProxyBypass = NULL;
@@ -2928,10 +3349,32 @@ static void initialize_request( struct winhttp_request *request )
     request->connect_timeout = 60000;
     request->send_timeout    = 30000;
     request->receive_timeout = 30000;
+    request->url_codepage = CP_UTF8;
     VariantInit( &request->data );
     request->state = REQUEST_STATE_INITIALIZED;
 }
 
+static void reset_request( struct winhttp_request *request )
+{
+    cancel_request( request );
+    WinHttpCloseHandle( request->hrequest );
+    request->hrequest = NULL;
+    WinHttpCloseHandle( request->hconnect );
+    request->hconnect = NULL;
+    heap_free( request->buffer );
+    request->buffer   = NULL;
+    heap_free( request->verb );
+    request->verb     = NULL;
+    request->offset   = 0;
+    request->bytes_available = 0;
+    request->bytes_read = 0;
+    request->error    = ERROR_SUCCESS;
+    request->async    = FALSE;
+    request->url_codepage = CP_UTF8;
+    VariantClear( &request->data );
+    request->state = REQUEST_STATE_INITIALIZED;
+}
+
 static HRESULT WINAPI winhttp_request_Open(
     IWinHttpRequest *iface,
     BSTR method,
@@ -2946,10 +3389,9 @@ static HRESULT WINAPI winhttp_request_Open(
         'W','i','n','3','2',';',' ','W','i','n','H','t','t','p','.','W','i','n','H','t','t','p',
         'R','e','q','u','e','s','t','.','5',')',0};
     struct winhttp_request *request = impl_from_IWinHttpRequest( iface );
-    HINTERNET hsession = NULL, hconnect = NULL, hrequest;
     URL_COMPONENTS uc;
     WCHAR *hostname, *path = NULL, *verb = NULL;
-    DWORD err = ERROR_OUTOFMEMORY, len, flags = 0, request_flags = 0;
+    DWORD err = ERROR_OUTOFMEMORY, len, flags = 0;
 
     TRACE("%p, %s, %s, %s\n", request, debugstr_w(method), debugstr_w(url),
           debugstr_variant(&async));
@@ -2965,12 +3407,9 @@ static HRESULT WINAPI winhttp_request_Open(
     if (!WinHttpCrackUrl( url, 0, 0, &uc )) return HRESULT_FROM_WIN32( get_last_error() );
 
     EnterCriticalSection( &request->cs );
-    if (request->state != REQUEST_STATE_INITIALIZED)
-    {
-        cancel_request( request );
-        free_request( request );
-        initialize_request( request );
-    }
+    if (request->state < REQUEST_STATE_INITIALIZED) initialize_request( request );
+    else reset_request( request );
+
     if (!(hostname = heap_alloc( (uc.dwHostNameLength + 1) * sizeof(WCHAR) ))) goto error;
     memcpy( hostname, uc.lpszHostName, uc.dwHostNameLength * sizeof(WCHAR) );
     hostname[uc.dwHostNameLength] = 0;
@@ -2980,37 +3419,44 @@ static HRESULT WINAPI winhttp_request_Open(
     path[uc.dwUrlPathLength + uc.dwExtraInfoLength] = 0;
 
     if (!(verb = strdupW( method ))) goto error;
-    if (V_BOOL( &async )) flags |= WINHTTP_FLAG_ASYNC;
-    if (!(hsession = WinHttpOpen( user_agentW, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, flags )))
+    if (SUCCEEDED( VariantChangeType( &async, &async, 0, VT_BOOL )) && V_BOOL( &async )) request->async = TRUE;
+    else request->async = FALSE;
+
+    if (!request->hsession)
     {
-        err = get_last_error();
-        goto error;
+        if (!(request->hsession = WinHttpOpen( user_agentW, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL,
+                                               WINHTTP_FLAG_ASYNC )))
+        {
+            err = get_last_error();
+            goto error;
+        }
+        if (!(request->hconnect = WinHttpConnect( request->hsession, hostname, uc.nPort, 0 )))
+        {
+            WinHttpCloseHandle( request->hsession );
+            request->hsession = NULL;
+            err = get_last_error();
+            goto error;
+        }
     }
-    if (!(hconnect = WinHttpConnect( hsession, hostname, uc.nPort, 0 )))
+    else if (!(request->hconnect = WinHttpConnect( request->hsession, hostname, uc.nPort, 0 )))
     {
         err = get_last_error();
         goto error;
     }
+
     len = sizeof(httpsW) / sizeof(WCHAR);
     if (uc.dwSchemeLength == len && !memcmp( uc.lpszScheme, httpsW, len * sizeof(WCHAR) ))
     {
-        request_flags |= WINHTTP_FLAG_SECURE;
+        flags |= WINHTTP_FLAG_SECURE;
     }
-    if (!(hrequest = WinHttpOpenRequest( hconnect, method, path, NULL, NULL, acceptW, request_flags )))
+    if (!(request->hrequest = WinHttpOpenRequest( request->hconnect, method, path, NULL, NULL, acceptW, flags )))
     {
         err = get_last_error();
         goto error;
     }
-    if (flags & WINHTTP_FLAG_ASYNC)
-    {
-        request->wait   = CreateEventW( NULL, FALSE, FALSE, NULL );
-        request->cancel = CreateEventW( NULL, FALSE, FALSE, NULL );
-        WinHttpSetOption( hrequest, WINHTTP_OPTION_CONTEXT_VALUE, &request, sizeof(request) );
-    }
+    WinHttpSetOption( request->hrequest, WINHTTP_OPTION_CONTEXT_VALUE, &request, sizeof(request) );
+
     request->state = REQUEST_STATE_OPEN;
-    request->hsession = hsession;
-    request->hconnect = hconnect;
-    request->hrequest = hrequest;
     request->verb = verb;
     heap_free( hostname );
     heap_free( path );
@@ -3018,8 +3464,8 @@ static HRESULT WINAPI winhttp_request_Open(
     return S_OK;
 
 error:
-    WinHttpCloseHandle( hconnect );
-    WinHttpCloseHandle( hsession );
+    WinHttpCloseHandle( request->hconnect );
+    request->hconnect = NULL;
     heap_free( hostname );
     heap_free( path );
     heap_free( verb );
@@ -3061,7 +3507,8 @@ static HRESULT WINAPI winhttp_request_SetRequestHeader(
         goto done;
     }
     sprintfW( str, fmtW, header, value ? value : emptyW );
-    if (!WinHttpAddRequestHeaders( request->hrequest, str, len, WINHTTP_ADDREQ_FLAG_REPLACE ))
+    if (!WinHttpAddRequestHeaders( request->hrequest, str, len,
+                                   WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE ))
     {
         err = get_last_error();
     }
@@ -3181,22 +3628,14 @@ static void CALLBACK wait_status_callback( HINTERNET handle, DWORD_PTR context,
 
 static void wait_set_status_callback( struct winhttp_request *request, DWORD status )
 {
-    if (!request->wait) return;
     status |= WINHTTP_CALLBACK_STATUS_REQUEST_ERROR;
     WinHttpSetStatusCallback( request->hrequest, wait_status_callback, status, 0 );
 }
 
 static DWORD wait_for_completion( struct winhttp_request *request )
 {
-    HANDLE handles[2];
+    HANDLE handles[2] = { request->wait, request->cancel };
 
-    if (!request->wait)
-    {
-        request->error = ERROR_SUCCESS;
-        return ERROR_SUCCESS;
-    }
-    handles[0] = request->wait;
-    handles[1] = request->cancel;
     switch (WaitForMultipleObjects( 2, handles, FALSE, INFINITE ))
     {
     case WAIT_OBJECT_0:
@@ -3340,7 +3779,7 @@ static HRESULT request_send( struct winhttp_request *request )
         {
             sa = V_ARRAY( &data );
             if ((hr = SafeArrayAccessData( sa, (void **)&ptr )) != S_OK) return hr;
-            if ((hr = SafeArrayGetUBound( sa, 1, &size ) != S_OK))
+            if ((hr = SafeArrayGetUBound( sa, 1, &size )) != S_OK)
             {
                 SafeArrayUnaccessData( sa );
                 return hr;
@@ -3379,6 +3818,39 @@ static DWORD CALLBACK send_and_receive_proc( void *arg )
     return request_send_and_receive( request );
 }
 
+/* critical section must be held */
+static DWORD request_wait( struct winhttp_request *request, DWORD timeout )
+{
+    HANDLE thread = request->thread;
+    DWORD err, ret;
+
+    LeaveCriticalSection( &request->cs );
+    while ((err = MsgWaitForMultipleObjects( 1, &thread, FALSE, timeout, QS_ALLINPUT )) == WAIT_OBJECT_0 + 1)
+    {
+        MSG msg;
+        while (PeekMessageW( &msg, NULL, 0, 0, PM_REMOVE ))
+        {
+            TranslateMessage( &msg );
+            DispatchMessageW( &msg );
+        }
+    }
+    switch (err)
+    {
+    case WAIT_OBJECT_0:
+        ret = ERROR_SUCCESS;
+        break;
+    case WAIT_TIMEOUT:
+        ret = ERROR_TIMEOUT;
+        break;
+    case WAIT_FAILED:
+    default:
+        ret = get_last_error();
+        break;
+    }
+    EnterCriticalSection( &request->cs );
+    return ret;
+}
+
 static HRESULT WINAPI winhttp_request_Send(
     IWinHttpRequest *iface,
     VARIANT body )
@@ -3400,20 +3872,22 @@ static HRESULT WINAPI winhttp_request_Send(
         return S_OK;
     }
     VariantClear( &request->data );
-    if ((hr = VariantCopyInd( &request->data, &body )) != S_OK) {
+    if ((hr = VariantCopyInd( &request->data, &body )) != S_OK)
+    {
         LeaveCriticalSection( &request->cs );
         return hr;
     }
-
-    if (request->wait) /* async request */
+    if (!(request->thread = CreateThread( NULL, 0, send_and_receive_proc, request, 0, NULL )))
     {
-        if (!(request->thread = CreateThread( NULL, 0, send_and_receive_proc, request, 0, NULL )))
-        {
-            LeaveCriticalSection( &request->cs );
-            return HRESULT_FROM_WIN32( get_last_error() );
-        }
+        LeaveCriticalSection( &request->cs );
+        return HRESULT_FROM_WIN32( get_last_error() );
+    }
+    request->wait = CreateEventW( NULL, FALSE, FALSE, NULL );
+    request->cancel = CreateEventW( NULL, FALSE, FALSE, NULL );
+    if (!request->async)
+    {
+        hr = HRESULT_FROM_WIN32( request_wait( request, INFINITE ) );
     }
-    else hr = request_send_and_receive( request );
     LeaveCriticalSection( &request->cs );
     return hr;
 }
@@ -3567,6 +4041,11 @@ static HRESULT WINAPI winhttp_request_get_ResponseBody(
     if (!body) return E_INVALIDARG;
 
     EnterCriticalSection( &request->cs );
+    if (request->state < REQUEST_STATE_SENT)
+    {
+        err = ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND;
+        goto done;
+    }
     if (!(sa = SafeArrayCreateVector( VT_UI1, 0, request->offset )))
     {
         err = ERROR_OUTOFMEMORY;
@@ -3593,12 +4072,203 @@ done:
     return HRESULT_FROM_WIN32( err );
 }
 
+struct stream
+{
+    IStream IStream_iface;
+    LONG    refs;
+    char   *data;
+    ULARGE_INTEGER pos, size;
+};
+
+static inline struct stream *impl_from_IStream( IStream *iface )
+{
+    return CONTAINING_RECORD( iface, struct stream, IStream_iface );
+}
+
+static HRESULT WINAPI stream_QueryInterface( IStream *iface, REFIID riid, void **obj )
+{
+    struct stream *stream = impl_from_IStream( iface );
+
+    TRACE("%p, %s, %p\n", stream, debugstr_guid(riid), obj);
+
+    if (IsEqualGUID( riid, &IID_IStream ) || IsEqualGUID( riid, &IID_IUnknown ))
+    {
+        *obj = iface;
+    }
+    else
+    {
+        FIXME("interface %s not implemented\n", debugstr_guid(riid));
+        return E_NOINTERFACE;
+    }
+    IStream_AddRef( iface );
+    return S_OK;
+}
+
+static ULONG WINAPI stream_AddRef( IStream *iface )
+{
+    struct stream *stream = impl_from_IStream( iface );
+    return InterlockedIncrement( &stream->refs );
+}
+
+static ULONG WINAPI stream_Release( IStream *iface )
+{
+    struct stream *stream = impl_from_IStream( iface );
+    LONG refs = InterlockedDecrement( &stream->refs );
+    if (!refs)
+    {
+        heap_free( stream->data );
+        heap_free( stream );
+    }
+    return refs;
+}
+
+static HRESULT WINAPI stream_Read( IStream *iface, void *buf, ULONG len, ULONG *read )
+{
+    struct stream *stream = impl_from_IStream( iface );
+    ULONG size;
+
+    if (stream->pos.QuadPart >= stream->size.QuadPart)
+    {
+        *read = 0;
+        return S_FALSE;
+    }
+
+    size = min( stream->size.QuadPart - stream->pos.QuadPart, len );
+    memcpy( buf, stream->data + stream->pos.QuadPart, size );
+    stream->pos.QuadPart += size;
+    *read = size;
+
+    return S_OK;
+}
+
+static HRESULT WINAPI stream_Write( IStream *iface, const void *buf, ULONG len, ULONG *written )
+{
+    FIXME("\n");
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI stream_Seek( IStream *iface, LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER *newpos )
+{
+    struct stream *stream = impl_from_IStream( iface );
+
+    if (origin == STREAM_SEEK_SET)
+        stream->pos.QuadPart = move.QuadPart;
+    else if (origin == STREAM_SEEK_CUR)
+        stream->pos.QuadPart += move.QuadPart;
+    else if (origin == STREAM_SEEK_END)
+        stream->pos.QuadPart = stream->size.QuadPart - move.QuadPart;
+
+    if (newpos) newpos->QuadPart = stream->pos.QuadPart;
+    return S_OK;
+}
+
+static HRESULT WINAPI stream_SetSize( IStream *iface, ULARGE_INTEGER newsize )
+{
+    FIXME("\n");
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI stream_CopyTo( IStream *iface, IStream *stream, ULARGE_INTEGER len, ULARGE_INTEGER *read,
+                                     ULARGE_INTEGER *written )
+{
+    FIXME("\n");
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI stream_Commit( IStream *iface, DWORD flags )
+{
+    FIXME("\n");
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI stream_Revert( IStream *iface )
+{
+    FIXME("\n");
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI stream_LockRegion( IStream *iface, ULARGE_INTEGER offset, ULARGE_INTEGER len, DWORD locktype )
+{
+    FIXME("\n");
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI stream_UnlockRegion( IStream *iface, ULARGE_INTEGER offset, ULARGE_INTEGER len, DWORD locktype )
+{
+    FIXME("\n");
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI stream_Stat( IStream *iface, STATSTG *stg, DWORD flag )
+{
+    FIXME("\n");
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI stream_Clone( IStream *iface, IStream **stream )
+{
+    FIXME("\n");
+    return E_NOTIMPL;
+}
+
+static const IStreamVtbl stream_vtbl =
+{
+    stream_QueryInterface,
+    stream_AddRef,
+    stream_Release,
+    stream_Read,
+    stream_Write,
+    stream_Seek,
+    stream_SetSize,
+    stream_CopyTo,
+    stream_Commit,
+    stream_Revert,
+    stream_LockRegion,
+    stream_UnlockRegion,
+    stream_Stat,
+    stream_Clone
+};
+
 static HRESULT WINAPI winhttp_request_get_ResponseStream(
     IWinHttpRequest *iface,
     VARIANT *body )
 {
-    FIXME("\n");
-    return E_NOTIMPL;
+    struct winhttp_request *request = impl_from_IWinHttpRequest( iface );
+    DWORD err = ERROR_SUCCESS;
+    struct stream *stream;
+
+    TRACE("%p, %p\n", request, body);
+
+    if (!body) return E_INVALIDARG;
+
+    EnterCriticalSection( &request->cs );
+    if (request->state < REQUEST_STATE_SENT)
+    {
+        err = ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND;
+        goto done;
+    }
+    if (!(stream = heap_alloc( sizeof(*stream) )))
+    {
+        err = ERROR_OUTOFMEMORY;
+        goto done;
+    }
+    stream->IStream_iface.lpVtbl = &stream_vtbl;
+    stream->refs = 1;
+    if (!(stream->data = heap_alloc( request->offset )))
+    {
+        heap_free( stream );
+        err = ERROR_OUTOFMEMORY;
+        goto done;
+    }
+    memcpy( stream->data, request->buffer, request->offset );
+    stream->pos.QuadPart = 0;
+    stream->size.QuadPart = request->offset;
+    V_VT( body ) = VT_UNKNOWN;
+    V_UNKNOWN( body ) = (IUnknown *)&stream->IStream_iface;
+
+done:
+    LeaveCriticalSection( &request->cs );
+    return HRESULT_FROM_WIN32( err );
 }
 
 static HRESULT WINAPI winhttp_request_get_Option(
@@ -3606,8 +4276,25 @@ static HRESULT WINAPI winhttp_request_get_Option(
     WinHttpRequestOption option,
     VARIANT *value )
 {
-    FIXME("\n");
-    return E_NOTIMPL;
+    struct winhttp_request *request = impl_from_IWinHttpRequest( iface );
+    HRESULT hr = S_OK;
+
+    TRACE("%p, %u, %p\n", request, option, value);
+
+    EnterCriticalSection( &request->cs );
+    switch (option)
+    {
+    case WinHttpRequestOption_URLCodePage:
+        V_VT( value ) = VT_I4;
+        V_I4( value ) = request->url_codepage;
+        break;
+    default:
+        FIXME("unimplemented option %u\n", option);
+        hr = E_NOTIMPL;
+        break;
+    }
+    LeaveCriticalSection( &request->cs );
+    return hr;
 }
 
 static HRESULT WINAPI winhttp_request_put_Option(
@@ -3631,46 +4318,35 @@ static HRESULT WINAPI winhttp_request_put_Option(
             request->disable_feature |= WINHTTP_DISABLE_REDIRECTS;
         break;
     }
-    default:
-        FIXME("unimplemented option %u\n", option);
-        hr = E_NOTIMPL;
-        break;
-    }
-    LeaveCriticalSection( &request->cs );
-    return hr;
-}
-
-/* critical section must be held */
-static DWORD wait_for_response( struct winhttp_request *request, DWORD timeout )
-{
-    HANDLE thread = request->thread;
-    DWORD err, ret;
-
-    LeaveCriticalSection( &request->cs );
-    while ((err = MsgWaitForMultipleObjects( 1, &thread, FALSE, timeout, QS_ALLINPUT )) == WAIT_OBJECT_0 + 1)
+    case WinHttpRequestOption_URLCodePage:
     {
-        MSG msg;
-        while (PeekMessageW( &msg, NULL, 0, 0, PM_REMOVE ))
+        static const WCHAR utf8W[] = {'u','t','f','-','8',0};
+        VARIANT cp;
+
+        VariantInit( &cp );
+        hr = VariantChangeType( &cp, &value, 0, VT_UI4 );
+        if (SUCCEEDED( hr ))
         {
-            TranslateMessage( &msg );
-            DispatchMessageW( &msg );
+            request->url_codepage = V_UI4( &cp );
+            TRACE("URL codepage: %u\n", request->url_codepage);
         }
-    }
-    switch (err)
-    {
-    case WAIT_OBJECT_0:
-        ret = ERROR_SUCCESS;
-        break;
-    case WAIT_TIMEOUT:
-        ret = ERROR_TIMEOUT;
+        else if (V_VT( &value ) == VT_BSTR && !strcmpiW( V_BSTR( &value ), utf8W ))
+        {
+            TRACE("URL codepage: UTF-8\n");
+            request->url_codepage = CP_UTF8;
+            hr = S_OK;
+        }
+        else
+            FIXME("URL codepage %s is not recognized\n", debugstr_variant( &value ));
         break;
-    case WAIT_FAILED:
+    }
     default:
-        ret = get_last_error();
+        FIXME("unimplemented option %u\n", option);
+        hr = E_NOTIMPL;
         break;
     }
-    EnterCriticalSection( &request->cs );
-    return ret;
+    LeaveCriticalSection( &request->cs );
+    return hr;
 }
 
 static HRESULT WINAPI winhttp_request_WaitForResponse(
@@ -3684,17 +4360,12 @@ static HRESULT WINAPI winhttp_request_WaitForResponse(
     TRACE("%p, %s, %p\n", request, debugstr_variant(&timeout), succeeded);
 
     EnterCriticalSection( &request->cs );
-    if (!request->thread)
-    {
-        LeaveCriticalSection( &request->cs );
-        return S_OK;
-    }
     if (request->state >= REQUEST_STATE_RESPONSE_RECEIVED)
     {
         LeaveCriticalSection( &request->cs );
         return S_OK;
     }
-    switch ((err = wait_for_response( request, msecs )))
+    switch ((err = request_wait( request, msecs )))
     {
     case ERROR_TIMEOUT:
         if (succeeded) *succeeded = VARIANT_FALSE;
@@ -3822,6 +4493,7 @@ HRESULT WinHttpRequest_create( void **obj )
     request->state = REQUEST_STATE_UNINITIALIZED;
     request->proxy.lpszProxy = NULL;
     request->proxy.lpszProxyBypass = NULL;
+    request->url_codepage = CP_UTF8;
     InitializeCriticalSection( &request->cs );
     request->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": winhttp_request.cs");