From: Hans Leidekker <hans@codeweavers.com> Based on code from wininet. --- dlls/winhttp/Makefile.in | 3 +- dlls/winhttp/request.c | 180 ++++++++++++++++++++++++++++++++- dlls/winhttp/session.c | 45 +++++++-- dlls/winhttp/tests/winhttp.c | 53 ++++++++++ dlls/winhttp/winhttp_private.h | 1 + 5 files changed, 272 insertions(+), 10 deletions(-) diff --git a/dlls/winhttp/Makefile.in b/dlls/winhttp/Makefile.in index a3e8249aeca..6a8d5e9b90a 100644 --- a/dlls/winhttp/Makefile.in +++ b/dlls/winhttp/Makefile.in @@ -1,7 +1,8 @@ EXTRADEFS = -D_WINHTTP_INTERNAL_ MODULE = winhttp.dll IMPORTLIB = winhttp -IMPORTS = uuid jsproxy user32 advapi32 ws2_32 +IMPORTS = $(ZLIB_PE_LIBS) uuid jsproxy user32 advapi32 ws2_32 +EXTRAINCL = $(ZLIB_PE_CFLAGS) DELAYIMPORTS = oleaut32 crypt32 secur32 iphlpapi dhcpcsvc VER_FILEDESCRIPTION_STR = "Wine HTTP Library" diff --git a/dlls/winhttp/request.c b/dlls/winhttp/request.c index 84b8d98adf8..7ae05db92a5 100644 --- a/dlls/winhttp/request.c +++ b/dlls/winhttp/request.c @@ -22,6 +22,7 @@ #include <assert.h> #include <stdarg.h> #include <wchar.h> +#include <zlib.h> #define COBJMACROS #include "windef.h" @@ -361,6 +362,154 @@ const struct data_stream_vtbl chunked_stream_vtbl = chunked_destroy }; +struct gzip_stream +{ + struct data_stream data_stream; + struct data_stream *parent_stream; + z_stream zstream; + BYTE buf[READ_BUFFER_SIZE]; + DWORD buf_size; + DWORD buf_pos; + BOOL end_of_data; +}; + +static DWORD gzip_query_data( struct data_stream *stream, struct request *request ) +{ + struct gzip_stream *gzip_stream = (struct gzip_stream *)stream; + return gzip_stream->buf_size; +} + +static BOOL gzip_end_of_data( struct data_stream *stream, struct request *request ) +{ + struct gzip_stream *gzip_stream = (struct gzip_stream *)stream; + return gzip_stream->end_of_data || + (!gzip_stream->buf_size && gzip_stream->parent_stream->vtbl->end_of_data( gzip_stream->parent_stream, request )); +} + +static DWORD gzip_read( struct data_stream *stream, struct request *request, char *buf, DWORD to_read, DWORD *read ) +{ + struct gzip_stream *gzip_stream = (struct gzip_stream *)stream; + z_stream *zstream = &gzip_stream->zstream; + DWORD size, ret_read = 0; + int zres; + DWORD ret = ERROR_SUCCESS; + + while (to_read && !gzip_stream->end_of_data) + { + if (!gzip_stream->buf_size) + { + if (gzip_stream->buf_pos) + { + if (gzip_stream->buf_size) + memmove( gzip_stream->buf, gzip_stream->buf + gzip_stream->buf_pos, gzip_stream->buf_size ); + gzip_stream->buf_pos = 0; + } + ret = gzip_stream->parent_stream->vtbl->read_data( gzip_stream->parent_stream, request, + (char *)gzip_stream->buf + gzip_stream->buf_size, + sizeof(gzip_stream->buf) - gzip_stream->buf_size, &size ); + if (ret) break; + gzip_stream->buf_size += size; + if (!size) + { + WARN( "unexpected end of data\n" ); + gzip_stream->end_of_data = TRUE; + break; + } + } + + zstream->next_in = gzip_stream->buf + gzip_stream->buf_pos; + zstream->avail_in = gzip_stream->buf_size; + zstream->next_out = (Bytef *)buf + ret_read; + zstream->avail_out = to_read; + zres = inflate( &gzip_stream->zstream, 0 ); + size = to_read - zstream->avail_out; + to_read -= size; + ret_read += size; + gzip_stream->buf_size -= zstream->next_in - (gzip_stream->buf + gzip_stream->buf_pos); + gzip_stream->buf_pos = zstream->next_in - gzip_stream->buf; + if (zres == Z_STREAM_END) + { + TRACE( "end of data\n" ); + gzip_stream->end_of_data = TRUE; + inflateEnd( zstream ); + } + else if (zres != Z_OK) + { + WARN( "inflate failed %d: %s\n", zres, debugstr_a(zstream->msg) ); + if (!ret_read) ret = ERROR_NO_DATA; + break; + } + } + + if (ret_read) ret = ERROR_SUCCESS; + *read = ret_read; + return ret; +} + +static DWORD gzip_drain_content( struct data_stream *stream, struct request *request ) +{ + struct gzip_stream *gzip_stream = (struct gzip_stream *)stream; + return gzip_stream->parent_stream->vtbl->drain_data( gzip_stream->parent_stream, request ); +} + +static void gzip_destroy( struct data_stream *stream ) +{ + struct gzip_stream *gzip_stream = (struct gzip_stream *)stream; + destroy_data_stream( gzip_stream->parent_stream ); + if (!gzip_stream->end_of_data) inflateEnd( &gzip_stream->zstream ); + free( gzip_stream ); +} + +static const struct data_stream_vtbl gzip_stream_vtbl = +{ + gzip_query_data, + gzip_end_of_data, + gzip_read, + gzip_drain_content, + gzip_destroy +}; + +static voidpf gzip_zalloc( voidpf opaque, uInt items, uInt size ) +{ + return malloc( items * size ); +} + +static void gzip_zfree( voidpf opaque, voidpf address ) +{ + free( address ); +} + +static DWORD init_gzip_stream( struct request *request, BOOL is_gzip ) +{ + struct gzip_stream *gzip_stream; + int zres; + + if (!(gzip_stream = calloc( 1, sizeof(*gzip_stream) ))) return ERROR_OUTOFMEMORY; + + gzip_stream->data_stream.vtbl = &gzip_stream_vtbl; + gzip_stream->zstream.zalloc = gzip_zalloc; + gzip_stream->zstream.zfree = gzip_zfree; + + zres = inflateInit2( &gzip_stream->zstream, is_gzip ? 31 : -15 ); + if (zres != Z_OK) + { + ERR( "inflateInit failed: %d\n", zres ); + free( gzip_stream ); + return ERROR_OUTOFMEMORY; + } + + if (request->read_size) + { + memcpy( gzip_stream->buf, request->read_buf + request->read_pos, request->read_size ); + gzip_stream->buf_size = request->read_size; + request->read_pos = request->read_size = 0; + } + + gzip_stream->parent_stream = request->data_stream; + request->data_stream = &gzip_stream->data_stream; + return ERROR_SUCCESS; +} + static int request_receive_response_timeout( struct request *req ) { if (req->receive_response_timeout == -1) return ACTUAL_DEFAULT_RECEIVE_RESPONSE_TIMEOUT; @@ -733,6 +882,12 @@ static void delete_header( struct request *request, DWORD index ) memset( &request->headers[request->num_headers], 0, sizeof(struct header) ); } +static void remove_header( struct request *request, const WCHAR *header, BOOL request_only ) +{ + int index = get_header_index( request, header, 0, request_only ); + if (index != -1) delete_header( request, index ); +} + DWORD process_header( struct request *request, const WCHAR *field, const WCHAR *value, DWORD flags, BOOL request_only ) { int index; @@ -2442,6 +2597,14 @@ static DWORD send_request( struct request *request, const WCHAR *headers, DWORD TRACE( "failed to add request headers: %lu\n", ret ); return ret; } + if (request->hdr.decompression) + { + WCHAR encoding[16]; + if (request->hdr.decompression == WINHTTP_DECOMPRESSION_FLAG_ALL) wcscpy( encoding, L"gzip, deflate" ); + else if (request->hdr.decompression == WINHTTP_DECOMPRESSION_FLAG_GZIP) wcscpy( encoding, L"gzip" ); + else if (request->hdr.decompression == WINHTTP_DECOMPRESSION_FLAG_DEFLATE) wcscpy( encoding, L"deflate" ); + process_header( request, L"Accept-Encoding", encoding, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE, TRUE ); + } if (!(request->hdr.disable_flags & WINHTTP_DISABLE_COOKIES) && (ret = add_cookie_headers( request ))) { WARN( "failed to add cookie headers: %lu\n", ret ); @@ -2689,7 +2852,7 @@ static DWORD handle_authorization( struct request *request, DWORD status ) static DWORD set_content_length( struct request *request, DWORD status ) { WCHAR buf[21]; - DWORD buflen = sizeof(buf); + DWORD buflen = sizeof(buf), ret = ERROR_SUCCESS; if (status == HTTP_STATUS_NO_CONTENT || status == HTTP_STATUS_NOT_MODIFIED || status == HTTP_STATUS_SWITCH_PROTOCOLS || !wcscmp( request->verb, L"HEAD" )) @@ -2728,9 +2891,22 @@ static DWORD set_content_length( struct request *request, DWORD status ) request->data_stream = &chunked_stream->data_stream; request->content_length = ~0ull; } + + buflen = sizeof(buf); + if (!query_headers( request, WINHTTP_QUERY_CONTENT_ENCODING, NULL, buf, &buflen, NULL )) + { + if (!wcsicmp( buf, L"gzip" )) ret = init_gzip_stream( request, TRUE ); + else if (!wcsicmp( buf, L"deflate" )) ret = init_gzip_stream( request, FALSE ); + else ret = ERROR_WINHTTP_INVALID_SERVER_RESPONSE; + if (!ret) + { + remove_header( request, L"Content-Length", FALSE ); + request->content_length = ~0ull; + } + } } request->content_read = 0; - return ERROR_SUCCESS; + return ret; } /* remove some amount of data from the read buffer */ diff --git a/dlls/winhttp/session.c b/dlls/winhttp/session.c index 1f9a55431c5..a3c8bb91010 100644 --- a/dlls/winhttp/session.c +++ b/dlls/winhttp/session.c @@ -262,7 +262,6 @@ static BOOL session_set_option( struct object_header *hdr, DWORD option, void *b session->websocket_receive_buffer_size = buffer_size; return TRUE; } - case WINHTTP_OPTION_WEB_SOCKET_SEND_BUFFER_SIZE: { DWORD buffer_size; @@ -278,7 +277,25 @@ static BOOL session_set_option( struct object_header *hdr, DWORD option, void *b session->websocket_send_buffer_size = buffer_size; return TRUE; } + case WINHTTP_OPTION_DECOMPRESSION: + { + DWORD decompression; + if (buflen != sizeof(decompression)) + { + SetLastError( ERROR_INSUFFICIENT_BUFFER ); + return FALSE; + } + decompression = *(DWORD *)buffer; + if (decompression & ~WINHTTP_DECOMPRESSION_FLAG_ALL) + { + FIXME( "unknown compression types %lx\n", decompression ); + return FALSE; + } + TRACE( "%#lx\n", decompression ); + session->hdr.decompression = decompression; + return TRUE; + } default: FIXME( "unimplemented option %lu\n", option ); SetLastError( ERROR_WINHTTP_INVALID_OPTION ); @@ -620,6 +637,7 @@ HINTERNET WINAPI WinHttpConnect( HINTERNET hsession, const WCHAR *server, INTERN connect->hdr.notify_mask = session->hdr.notify_mask; connect->hdr.context = session->hdr.context; connect->hdr.redirect_policy = session->hdr.redirect_policy; + connect->hdr.decompression = session->hdr.decompression; addref_object( &session->hdr ); connect->session = session; @@ -1206,7 +1224,6 @@ static BOOL request_set_option( struct object_header *hdr, DWORD option, void *b request->websocket_receive_buffer_size = buffer_size; return TRUE; } - case WINHTTP_OPTION_WEB_SOCKET_SEND_BUFFER_SIZE: { DWORD buffer_size; @@ -1222,7 +1239,25 @@ static BOOL request_set_option( struct object_header *hdr, DWORD option, void *b TRACE( "Websocket send buffer size %lu.\n", buffer_size); return TRUE; } + case WINHTTP_OPTION_DECOMPRESSION: + { + DWORD decompression; + if (buflen != sizeof(decompression)) + { + SetLastError( ERROR_INSUFFICIENT_BUFFER ); + return FALSE; + } + decompression = *(DWORD *)buffer; + if (decompression & ~WINHTTP_DECOMPRESSION_FLAG_ALL) + { + FIXME( "unknown compression types %lx\n", decompression ); + return FALSE; + } + TRACE( "%#lx\n", decompression ); + request->hdr.decompression = decompression; + return TRUE; + } default: FIXME( "unimplemented option %lu\n", option ); SetLastError( ERROR_WINHTTP_INVALID_OPTION ); @@ -1308,6 +1343,7 @@ HINTERNET WINAPI WinHttpOpenRequest( HINTERNET hconnect, const WCHAR *verb, cons request->hdr.notify_mask = connect->hdr.notify_mask; request->hdr.context = connect->hdr.context; request->hdr.redirect_policy = connect->hdr.redirect_policy; + request->hdr.decompression = connect->hdr.decompression; init_queue( &request->queue ); addref_object( &connect->hdr ); @@ -1455,11 +1491,6 @@ static BOOL set_option( struct object_header *hdr, DWORD option, void *buffer, D hdr->context = *(DWORD_PTR *)buffer; return TRUE; } - - case WINHTTP_OPTION_DECOMPRESSION: - FIXME( "WINHTTP_OPTION_DECOMPRESSION, %#lx stub.\n", *(DWORD *)buffer ); - return TRUE; - default: if (hdr->vtbl->set_option) ret = hdr->vtbl->set_option( hdr, option, buffer, buflen ); else diff --git a/dlls/winhttp/tests/winhttp.c b/dlls/winhttp/tests/winhttp.c index 63451b55e62..b4ad4a67803 100644 --- a/dlls/winhttp/tests/winhttp.c +++ b/dlls/winhttp/tests/winhttp.c @@ -6316,6 +6316,58 @@ static void test_connection_cache(int port) WinHttpCloseHandle(ses); } +static void test_decompression(void) +{ + HINTERNET ses, req, con; + DWORD decompression, len, status, size; + BOOL ret; + + ses = WinHttpOpen( L"winetest", WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0 ); + ok( ses != NULL, "got %lu\n", GetLastError() ); + + decompression = WINHTTP_DECOMPRESSION_FLAG_ALL; + ret = WinHttpSetOption( ses, WINHTTP_OPTION_DECOMPRESSION, &decompression, sizeof(decompression) ); + ok( ret, "got %lu\n", GetLastError() ); + + con = WinHttpConnect( ses, L"test.winehq.org", 0, 0 ); + ok( con != NULL, "got %lu\n", GetLastError() ); + + req = WinHttpOpenRequest( con, NULL, L"tests/gzip.php", NULL, NULL, NULL, 0 ); + ok( req != NULL, "got %lu\n", GetLastError() ); + + ret = WinHttpSendRequest( req, NULL, 0, NULL, 0, 0, 0 ); + ok( ret, "got %lu\n", GetLastError() ); + + ret = WinHttpReceiveResponse( req, NULL ); + ok( ret, "got %lu\n", GetLastError() ); + + status = 0xdeadbeef; + size = sizeof(status); + ret = WinHttpQueryHeaders( req, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, NULL, &status, &size, NULL ); + ok( ret, "got %lu\n", GetLastError() ); + ok( status == HTTP_STATUS_OK, "got %lu\n", status ); + + len = 0xdeadbeef; + size = sizeof(len); + ret = WinHttpQueryHeaders( req, WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER, NULL, &len, &size, 0 ); + ok( !ret && GetLastError() == ERROR_WINHTTP_HEADER_NOT_FOUND, "got %lu\n", GetLastError() ); + ok( len == 0xdeadbeef, "got %lu\n", len ); + + for (;;) + { + char buf[4096]; + + size = 0; + ret = WinHttpReadData( req, buf, sizeof(buf), &size ); + ok( ret, "got %lu\n", GetLastError() ); + if (!ret || !size) break; + } + + WinHttpCloseHandle( req ); + WinHttpCloseHandle( con ); + WinHttpCloseHandle( ses ); +} + START_TEST (winhttp) { struct server_info si; @@ -6350,6 +6402,7 @@ START_TEST (winhttp) test_WinHttpGetIEProxyConfigForCurrentUser(); test_chunked_read(); test_max_http_automatic_redirects(); + test_decompression(); si.event = CreateEventW(NULL, 0, 0, NULL); si.port = 7532; diff --git a/dlls/winhttp/winhttp_private.h b/dlls/winhttp/winhttp_private.h index 119ccf4377f..6e078fcd2ec 100644 --- a/dlls/winhttp/winhttp_private.h +++ b/dlls/winhttp/winhttp_private.h @@ -46,6 +46,7 @@ struct object_header DWORD logon_policy; DWORD redirect_policy; DWORD error; + DWORD decompression; DWORD_PTR context; LONG refs; WINHTTP_STATUS_CALLBACK callback; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10346