Rajib Chy
Rajib Chy

Reputation: 880

zlib gzip invalid response defined in web browser (c++)

I've a FastCGI application with C++. I like to send my response with gzip compression to client.
(ZLIB VERSION "1.2.11")
Here is the sample of my source code:

#pragma warning (disable : 4231)
#pragma warning(disable : 4996)
//3:45 PM 11/24/2018
#if !(defined(_WIN32)||defined(_WIN64)) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))
#error Have to check !TODO
#else
#if !defined(_IOSTREAM_)
#include <iostream>
#endif//!_IOSTREAM_
#ifndef _WINDOWS_
#include <windows.h>
#endif//!_WINDOWS_
#endif//_WIN32||_WIN64/__unix__
#if !defined(_INC_STDIO)
#include <stdio.h>  /* defines FILENAME_MAX, printf, sprintf */
#endif//!_INC_STDIO
#ifndef _XSTRING_
#include <string>// !_XSTRING_// memcpy, memset
#endif //!_XSTRING_
#if !defined(ZLIB_H)
#include <zlib.h>
#endif//!ZLIB_H
#if !defined(_SSTREAM_)
#include <sstream> // std::stringstream
#endif//_SSTREAM_
#if !defined(CHUNK)
#define CHUNK 16384
#endif//!CHUNK
#ifndef OS_CODE
#  define OS_CODE  0x03  /* assume Unix */
#endif//!OS_CODE
#if MAX_MEM_LEVEL >= 8
#  define DEF_MEM_LEVEL 8
#else
#  define DEF_MEM_LEVEL  MAX_MEM_LEVEL
#endif//!MAX_MEM_LEVEL
#if !defined(assert)
#define assert(expression) ((void)0)
#endif//!assert

static int gz_magic[2] = { 0x1f, 0x8b }; /* gzip magic header */
void __write_magic_header(std::stringstream&output) {
    char*dest = (char*)malloc(10);
    sprintf(dest, "%c%c%c%c%c%c%c%c%c%c", gz_magic[0], gz_magic[1], Z_DEFLATED, 0 /*flags*/, 0, 0, 0, 0 /*time*/, 0 /*xflags*/, OS_CODE);
    output.write(const_cast<const char*>(dest), 10);
    free(dest);
};
int ____def_strm(std::stringstream&source, std::stringstream&dest, int level = Z_BEST_SPEED) {
    //6:00 AM 1/18/2019
    int ret, flush;
    unsigned have;
    z_stream strm;
    /* allocate deflate state */
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    ret = deflateInit2_(&strm, level, Z_DEFLATED,
        -MAX_WBITS,
        DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
        ZLIB_VERSION, (int)sizeof(z_stream));
    if (ret != Z_OK)
        return ret;
    /* compress until end of stream */
    std::streamsize n;
    source.seekg(0, std::ios::end);//Go to end of stream
    std::streamoff size = source.tellg();
    source.seekg(0, std::ios::beg);//Back to begain of stream
    int write_len = 0;
    do {
        char in[CHUNK];
        n = source.rdbuf()->sgetn(in, CHUNK);
        strm.avail_in = (uInt)n;
        size -= n;
        flush = size <= 0 ? Z_FINISH : Z_NO_FLUSH;
        strm.next_in = (Bytef*)in;
        /* run deflate() on input until output buffer not full, finish
          compression if all of source has been read in */
        do {
            char out[CHUNK];
            strm.avail_out = CHUNK;
            strm.next_out = (Bytef*)out;
            ret = deflate(&strm, flush);    /* no bad return value */
            assert(ret != Z_STREAM_ERROR);  /* state not clobbered */
            have = CHUNK - strm.avail_out;
            dest.write(out, have);
            write_len += have;
        } while (strm.avail_out == 0);
        assert(strm.avail_in == 0);     /* all input will be used */
         /* done when last data in file processed */
    } while (flush != Z_FINISH);
    assert(ret == Z_STREAM_END);        /* stream will be complete */
     /* clean up and return */
    (void)deflateEnd(&strm);
    return write_len;
};
void compress_gzip (std::stringstream&source, std::stringstream&output) {
    __write_magic_header(output);
    ____def_strm(source, output);
    return;
};
void gzip_test(int loop) {
    std::stringstream body(std::stringstream::in | std::stringstream::out | std::stringstream::binary);
    for (int i = 0; i < loop; i++) {
        body << "<b>Hello World</b><br/>";
        body << "<a href=\"/wiki/General-purpose_programming_language\" title=\"General-purpose programming language\">general-purpose programming language</a>";
        body << "\r\n";
    }
    std::stringstream compressed(std::stringstream::in | std::stringstream::out | std::stringstream::binary);
    compress_gzip(body, compressed);
    std::stringstream().swap(body);
    std::cout << compressed.str();
    std::stringstream().swap(compressed);
};
void write_header(const char* ct) {
    std::cout << "Content-Type:" << ct << "\n";
    std::cout << "Accept-Ranges:bytes\n";
};
int main(int argc, char *argv[], char*envp[]) {
    //100 problem ==> ERR_CONTENT_DECODING_FAILED
    //1000 problem ==> ERR_CONTENT_DECODING_FAILED
    //10000 Ok
    write_header("text/plain");
    std::cout << "Content-Encoding:gzip\n";
    std::cout << "\r\n";
    gzip_test(10000);
    return EXIT_SUCCESS;
};

Its working but I think this program has bug, but i'm unable to figure it out.
Problems are shown bellow:
if gzip_test(10000); then OK
if gzip_test(100); browser shown ERR_CONTENT_DECODING_FAILED
if gzip_test(1000); browser shown ERR_CONTENT_DECODING_FAILED
Please help me to figure it out this bug.

Success response:
Success response

Error response:
Error response

Upvotes: 0

Views: 219

Answers (1)

Alan Birtles
Alan Birtles

Reputation: 36379

You aren't writing the gzip footer containing the CRC and data length:

std::streamoff size = source.tellg();
int totalSize = size;
int tcrc = 0;
...
  n = source.rdbuf()->sgetn( in, CHUNK );
  strm.avail_in = (uInt)n;
  tcrc = crc32( tcrc, (uint8_t*)in, n );
...
(void)deflateEnd( &strm );
dest.write( (char*)&tcrc, sizeof( tcrc ) );
dest.write( (char*)&totalSize, sizeof( totalSize ) );
return write_len;

Your __write_magic_header method is also incorrect as it only allocates 10 bytes but then writes 10 characters with sprintf which will actually write 11 bytes overflowing your buffer.

On windows you can't send binary data through std::cout, you have the same issue as opening a file with ofstream without specifying binary. To fix this call the following before using std::cout:

_setmode( _fileno( stdout ), _O_BINARY );

Some other points unrelated to your issue:

  1. don't wrap your includes with #ifdef, the macros you are using are implementation details and should provide no/negligable performance difference on a modern compiler.
  2. don't use "__" at the beginning of method names or other identifiers, these names (along with "_" followed by a capital letter) are reserved for use by the compiler.

Upvotes: 1

Related Questions