From 11ca05f6aea51f20de2baf0c1e73a2e243b0c972 Mon Sep 17 00:00:00 2001
From: Jacek Caban <jacek@codeweavers.com>
Date: Fri, 29 May 2009 23:35:13 +0200
Subject: [PATCH] wininet: Added support for decompressing gzip encoded
 content.

---
 configure                |  84 ++++++++++++++++-
 configure.ac             |  10 +-
 dlls/wininet/Makefile.in |   2 +-
 dlls/wininet/http.c      | 197 ++++++++++++++++++++++++++++++++++++---
 dlls/wininet/internet.h  |   5 +
 include/config.h.in      |   6 ++
 6 files changed, 289 insertions(+), 15 deletions(-)

diff --git a/configure b/configure
index 413c5907635..52a81307660 100755
--- a/configure
+++ b/configure
@@ -669,6 +669,7 @@ ALSALIBS
 ESDLIBS
 ESDINCL
 ESDCONFIG
+ZLIB
 FREETYPEINCL
 FREETYPELIBS
 ft_devel
@@ -5776,6 +5777,7 @@ done
 
 
 
+
 
 
 for ac_header in \
@@ -5899,7 +5901,8 @@ for ac_header in \
 	unistd.h \
 	utime.h \
 	valgrind/memcheck.h \
-	valgrind/valgrind.h
+	valgrind/valgrind.h \
+	zlib.h
 
 do
 as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
@@ -15051,6 +15054,85 @@ done
 
 LIBS="$ac_wine_check_funcs_save_LIBS"
 
+if test "$ac_cv_header_zlib_h" = "yes"
+then
+    { $as_echo "$as_me:$LINENO: checking for inflate in -lz" >&5
+$as_echo_n "checking for inflate in -lz... " >&6; }
+if test "${ac_cv_lib_z_inflate+set}" = set; then
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lz  $LIBS"
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char inflate ();
+int
+main ()
+{
+return inflate ();
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\""
+$as_echo "$ac_try_echo") >&5
+  (eval "$ac_link") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+	 test -z "$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest$ac_exeext && {
+	 test "$cross_compiling" = yes ||
+	 $as_test_x conftest$ac_exeext
+       }; then
+  ac_cv_lib_z_inflate=yes
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	ac_cv_lib_z_inflate=no
+fi
+
+rm -rf conftest.dSYM
+rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
+      conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:$LINENO: result: $ac_cv_lib_z_inflate" >&5
+$as_echo "$ac_cv_lib_z_inflate" >&6; }
+if test "x$ac_cv_lib_z_inflate" = x""yes; then
+
+cat >>confdefs.h <<\_ACEOF
+#define HAVE_ZLIB 1
+_ACEOF
+
+                            ZLIB="-lz"
+
+fi
+
+fi
+
 if test "x$with_esd" != xno
 then
     save_CFLAGS="$CFLAGS"
diff --git a/configure.ac b/configure.ac
index 879e358bb29..beccbbd6513 100644
--- a/configure.ac
+++ b/configure.ac
@@ -377,7 +377,8 @@ AC_CHECK_HEADERS(\
 	unistd.h \
 	utime.h \
 	valgrind/memcheck.h \
-	valgrind/valgrind.h
+	valgrind/valgrind.h \
+	zlib.h
 )
 AC_HEADER_STAT()
 
@@ -1172,6 +1173,13 @@ WINE_CHECK_LIB_FUNCS(\
         pthread_get_stacksize_np,
         [$LIBPTHREAD])
 
+dnl **** Check for zlib ****
+if test "$ac_cv_header_zlib_h" = "yes"
+then
+    AC_CHECK_LIB(z,inflate,[AC_DEFINE(HAVE_ZLIB,1,[Define to 1 if you have the `z' library (-lz).])
+                            AC_SUBST(ZLIB,"-lz")])
+fi
+
 dnl **** Check for EsounD ****
 if test "x$with_esd" != xno
 then
diff --git a/dlls/wininet/Makefile.in b/dlls/wininet/Makefile.in
index 116c9fdb4e8..c7cc1e0a8c1 100644
--- a/dlls/wininet/Makefile.in
+++ b/dlls/wininet/Makefile.in
@@ -7,7 +7,7 @@ MODULE    = wininet.dll
 IMPORTLIB = wininet
 IMPORTS   = mpr shlwapi shell32 user32 advapi32 kernel32 ntdll
 DELAYIMPORTS = secur32 crypt32
-EXTRALIBS = @SOCKETLIBS@
+EXTRALIBS = @SOCKETLIBS@ @ZLIB@
 
 C_SRCS = \
 	cookie.c \
diff --git a/dlls/wininet/http.c b/dlls/wininet/http.c
index 8db3e97a737..b11ce21c416 100644
--- a/dlls/wininet/http.c
+++ b/dlls/wininet/http.c
@@ -48,6 +48,9 @@
 #endif
 #include <time.h>
 #include <assert.h>
+#ifdef HAVE_ZLIB
+#  include <zlib.h>
+#endif
 
 #include "windef.h"
 #include "winbase.h"
@@ -162,6 +165,15 @@ struct HttpAuthInfo
     BOOL finished; /* finished authenticating */
 };
 
+#ifdef HAVE_ZLIB
+
+struct gzip_stream_t {
+    z_stream zstream;
+    BYTE buf[4096];
+};
+
+#endif
+
 static BOOL HTTP_OpenConnection(LPWININETHTTPREQW lpwhr);
 static BOOL HTTP_GetResponseHeaders(LPWININETHTTPREQW lpwhr, BOOL clear);
 static BOOL HTTP_ProcessHeader(LPWININETHTTPREQW lpwhr, LPCWSTR field, LPCWSTR value, DWORD dwModifier);
@@ -188,6 +200,57 @@ static LPHTTPHEADERW HTTP_GetHeader(LPWININETHTTPREQW req, LPCWSTR head)
         return &req->pCustHeaders[HeaderIndex];
 }
 
+#ifdef HAVE_ZLIB
+
+static voidpf wininet_zalloc(voidpf opaque, uInt items, uInt size)
+{
+    return HeapAlloc(GetProcessHeap(), 0, items*size);
+}
+
+static void wininet_zfree(voidpf opaque, voidpf address)
+{
+    HeapFree(GetProcessHeap(), 0, address);
+}
+
+static void init_gzip_stream(WININETHTTPREQW *req)
+{
+    gzip_stream_t *gzip_stream;
+    int zres;
+
+    gzip_stream = HeapAlloc(GetProcessHeap(), 0, sizeof(gzip_stream_t));
+    gzip_stream->zstream.zalloc = wininet_zalloc;
+    gzip_stream->zstream.zfree = wininet_zfree;
+    gzip_stream->zstream.opaque = NULL;
+    gzip_stream->zstream.next_in = NULL;
+    gzip_stream->zstream.avail_in = 0;
+
+    zres = inflateInit2(&gzip_stream->zstream, 0x1f);
+    if(zres != Z_OK) {
+        ERR("inflateInit failed: %d\n", zres);
+        HeapFree(GetProcessHeap(), 0, gzip_stream);
+        return;
+    }
+
+    req->gzip_stream = gzip_stream;
+    req->dwContentLength = ~0u;
+
+    if(req->read_size) {
+        memcpy(gzip_stream->buf, req->read_buf + req->read_pos, req->read_size);
+        gzip_stream->zstream.next_in = gzip_stream->buf;
+        gzip_stream->zstream.avail_in = req->read_size;
+        req->read_size = 0;
+    }
+}
+
+#else
+
+static void init_gzip_stream(WININETHTTPREQW *req)
+{
+    ERR("gzip stream not supported, missing zlib.\n");
+}
+
+#endif
+
 /* set the request content length based on the headers */
 static DWORD set_content_length( LPWININETHTTPREQW lpwhr )
 {
@@ -208,6 +271,16 @@ static DWORD set_content_length( LPWININETHTTPREQW lpwhr )
         lpwhr->read_chunked = TRUE;
     }
 
+    if(lpwhr->decoding) {
+        int encoding_idx;
+
+        static const WCHAR gzipW[] = {'g','z','i','p',0};
+
+        encoding_idx = HTTP_GetCustomHeaderIndex(lpwhr, szContent_Encoding, 0, FALSE);
+        if(encoding_idx != -1 && !strcmpiW(lpwhr->pCustHeaders[encoding_idx].lpszValue, gzipW))
+            init_gzip_stream(lpwhr);
+    }
+
     return lpwhr->dwContentLength;
 }
 
@@ -1476,6 +1549,14 @@ static void HTTPREQ_CloseConnection(WININETHANDLEHEADER *hdr)
 
     TRACE("%p\n",lpwhr);
 
+#ifdef HAVE_ZLIB
+    if(lpwhr->gzip_stream) {
+        inflateEnd(&lpwhr->gzip_stream->zstream);
+        HeapFree(GetProcessHeap(), 0, lpwhr->gzip_stream);
+        lpwhr->gzip_stream = NULL;
+    }
+#endif
+
     if (!NETCON_connected(&lpwhr->netConnection))
         return;
 
@@ -1659,26 +1740,95 @@ static DWORD HTTPREQ_SetOption(WININETHANDLEHEADER *hdr, DWORD option, void *buf
         HeapFree(GetProcessHeap(), 0, req->lpHttpSession->lpszPassword);
         if (!(req->lpHttpSession->lpszPassword = WININET_strdupW(buffer))) return ERROR_OUTOFMEMORY;
         return ERROR_SUCCESS;
+    case INTERNET_OPTION_HTTP_DECODING:
+        if(size != sizeof(BOOL))
+            return ERROR_INVALID_PARAMETER;
+        req->decoding = *(BOOL*)buffer;
+        return ERROR_SUCCESS;
     }
 
     return ERROR_INTERNET_INVALID_OPTION;
 }
 
+static inline BOOL is_gzip_buf_empty(gzip_stream_t *gzip_stream)
+{
+#ifdef HAVE_ZLIB
+    return gzip_stream->zstream.avail_in == 0;
+#else
+    return TRUE;
+#endif
+}
+
+static DWORD read_gzip_data(WININETHTTPREQW *req, BYTE *buf, int size, int flags, int *read_ret)
+{
+    DWORD ret = ERROR_SUCCESS;
+    int read = 0;
+
+#ifdef HAVE_ZLIB
+    int res, len, zres;
+    z_stream *zstream;
+
+    zstream = &req->gzip_stream->zstream;
+
+    while(read < size) {
+        if(is_gzip_buf_empty(req->gzip_stream)) {
+            res = NETCON_recv( &req->netConnection, req->gzip_stream->buf,
+                               sizeof(req->gzip_stream->buf), flags, &len);
+            if(!res) {
+                if(!read)
+                    ret = INTERNET_GetLastError();
+                break;
+            }
+
+            zstream->next_in = req->gzip_stream->buf;
+            zstream->avail_in = len;
+        }
+
+        zstream->next_out = buf+read;
+        zstream->avail_out = size-read;
+        zres = inflate(zstream, Z_FULL_FLUSH);
+        read = size - zstream->avail_out;
+        if(zres == Z_STREAM_END) {
+            TRACE("end of data\n");
+            req->dwContentLength = req->dwContentRead + req->read_size + read;
+            break;
+        }else if(zres != Z_OK) {
+            WARN("inflate failed %d\n", zres);
+            if(!read)
+                ret = ERROR_INTERNET_DECODING_FAILED;
+            break;
+        }
+    }
+#endif
+
+    *read_ret = read;
+    return ret;
+}
+
 /* read some more data into the read buffer (the read section must be held) */
 static BOOL read_more_data( WININETHTTPREQW *req, int maxlen )
 {
     int len;
 
-    if (req->read_size && req->read_pos)
+    if (req->read_pos)
     {
         /* move existing data to the start of the buffer */
-        memmove( req->read_buf, req->read_buf + req->read_pos, req->read_size );
+        if(req->read_size)
+            memmove( req->read_buf, req->read_buf + req->read_pos, req->read_size );
         req->read_pos = 0;
     }
 
     if (maxlen == -1) maxlen = sizeof(req->read_buf);
-    if (!NETCON_recv( &req->netConnection, req->read_buf + req->read_size,
-                      maxlen - req->read_size, 0, &len )) return FALSE;
+
+    if (req->gzip_stream) {
+        if(read_gzip_data(req, req->read_buf + req->read_size, maxlen - req->read_size, 0, &len) != ERROR_SUCCESS)
+            return FALSE;
+    }else {
+        if(!NETCON_recv( &req->netConnection, req->read_buf + req->read_size,
+                         maxlen - req->read_size, 0, &len ))
+            return FALSE;
+    }
+
     req->read_size += len;
     return TRUE;
 }
@@ -1847,6 +1997,7 @@ static void HTTP_ReceiveRequestData(WININETHTTPREQW *req, BOOL first_notif)
 static DWORD HTTPREQ_Read(WININETHTTPREQW *req, void *buffer, DWORD size, DWORD *read, BOOL sync)
 {
     int len, bytes_read = 0;
+    DWORD ret = ERROR_SUCCESS;
 
     EnterCriticalSection( &req->read_section );
     if (req->read_chunked && (req->dwContentLength == ~0u || req->dwContentLength == req->dwContentRead))
@@ -1862,12 +2013,20 @@ static DWORD HTTPREQ_Read(WININETHTTPREQW *req, void *buffer, DWORD size, DWORD
         remove_data( req, bytes_read );
     }
 
-    if (size > bytes_read && (!bytes_read || sync))
+    if (size > bytes_read)
     {
-        if (NETCON_recv( &req->netConnection, (char *)buffer + bytes_read, size - bytes_read,
-                         sync ? MSG_WAITALL : 0, &len))
-            bytes_read += len;
-        /* always return success, even if the network layer returns an error */
+        if (req->gzip_stream) {
+            if(is_gzip_buf_empty(req->gzip_stream) || !bytes_read || sync) {
+                ret = read_gzip_data(req, (BYTE*)buffer+bytes_read, size - bytes_read, sync ? MSG_WAITALL : 0, &len);
+                if(ret == ERROR_SUCCESS)
+                    bytes_read += len;
+            }
+        }else if (!bytes_read || sync) {
+            if (NETCON_recv( &req->netConnection, (char *)buffer + bytes_read, size - bytes_read,
+                             sync ? MSG_WAITALL : 0, &len))
+                bytes_read += len;
+            /* always return success, even if the network layer returns an error */
+        }
     }
 done:
     req->dwContentRead += bytes_read;
@@ -1876,7 +2035,7 @@ done:
     TRACE( "retrieved %u bytes (%u/%u)\n", bytes_read, req->dwContentRead, req->dwContentLength );
     LeaveCriticalSection( &req->read_section );
 
-    if(req->lpszCacheFile) {
+    if(ret == ERROR_SUCCESS && req->lpszCacheFile) {
         BOOL res;
         DWORD dwBytesWritten;
 
@@ -1888,7 +2047,7 @@ done:
     if(!bytes_read && (req->dwContentRead == req->dwContentLength))
         HTTP_FinishedReading(req);
 
-    return ERROR_SUCCESS;
+    return ret;
 }
 
 
@@ -2101,7 +2260,7 @@ static DWORD HTTPREQ_QueryDataAvailable(WININETHANDLEHEADER *hdr, DWORD *availab
     }
 
 done:
-    if (*available == sizeof(req->read_buf))  /* check if we have even more pending in the socket */
+    if (*available == sizeof(req->read_buf) && !req->gzip_stream)  /* check if we have even more pending in the socket */
     {
         DWORD extra;
         if (NETCON_query_data_available(&req->netConnection, &extra))
@@ -2396,6 +2555,16 @@ static BOOL HTTP_HttpQueryInfoW( LPWININETHTTPREQW lpwhr, DWORD dwInfoLevel,
         index = HTTP_GetCustomHeaderIndex(lpwhr, lpBuffer, requested_index, request_only);
         break;
 
+    case HTTP_QUERY_CONTENT_LENGTH:
+        if(lpwhr->gzip_stream) {
+            INTERNET_SetLastError(ERROR_HTTP_HEADER_NOT_FOUND);
+            return FALSE;
+        }
+
+        index = HTTP_GetCustomHeaderIndex(lpwhr, header_lookup[level],
+                                          requested_index,request_only);
+        break;
+
     case HTTP_QUERY_RAW_HEADERS_CRLF:
         {
             LPWSTR headers;
@@ -2504,6 +2673,10 @@ static BOOL HTTP_HttpQueryInfoW( LPWININETHTTPREQW lpwhr, DWORD dwInfoLevel,
             return TRUE;
         }
         break;
+    case HTTP_QUERY_CONTENT_ENCODING:
+        index = HTTP_GetCustomHeaderIndex(lpwhr, header_lookup[lpwhr->gzip_stream ? HTTP_QUERY_CONTENT_TYPE : level],
+                requested_index,request_only);
+        break;
     default:
         assert (LAST_TABLE_HEADER == (HTTP_QUERY_UNLESS_MODIFIED_SINCE + 1));
 
diff --git a/dlls/wininet/internet.h b/dlls/wininet/internet.h
index 95632bc80d7..1d49eaa7c3f 100644
--- a/dlls/wininet/internet.h
+++ b/dlls/wininet/internet.h
@@ -183,6 +183,8 @@ typedef struct
 
 struct HttpAuthInfo;
 
+typedef struct gzip_stream_t gzip_stream_t;
+
 typedef struct
 {
     WININETHANDLEHEADER hdr;
@@ -209,6 +211,9 @@ typedef struct
     DWORD read_pos;       /* current read position in read_buf */
     DWORD read_size;      /* valid data size in read_buf */
     BYTE  read_buf[4096]; /* buffer for already read but not returned data */
+
+    BOOL decoding;
+    gzip_stream_t *gzip_stream;
 } WININETHTTPREQW, *LPWININETHTTPREQW;
 
 
diff --git a/include/config.h.in b/include/config.h.in
index 12ce108bee5..c22d1f7863f 100644
--- a/include/config.h.in
+++ b/include/config.h.in
@@ -1041,6 +1041,12 @@
 /* Define if Xrender has the XRenderSetPictureTransform function */
 #undef HAVE_XRENDERSETPICTURETRANSFORM
 
+/* Define to 1 if you have the `z' library (-lz). */
+#undef HAVE_ZLIB
+
+/* Define to 1 if you have the <zlib.h> header file. */
+#undef HAVE_ZLIB_H
+
 /* Define to 1 if you have the `_pclose' function. */
 #undef HAVE__PCLOSE
 
-- 
GitLab