/*
 * gz.c - tiny zlib wrapper for reading in-memory GZ archives.
 *
 * Copyright (C) 2014-2020 Mateusz Viste
 *
 * MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <stdlib.h>   /* calloc(), free() */
#include <string.h>   /* memset() */
#include <zlib.h>

#include "gz.h" /* include self for control */

/* decompress a gz file in memory. returns a pointer to a newly allocated memory chunk (holding uncompressed data), or NULL on error. */
void *ungz(const void *memgzsrc, long memgzlen, long *resultlen) {
  const unsigned char *memgz = memgzsrc;
  int extract_res;
  void *result;
  unsigned long filelen;
  z_stream zlibstream;

  /* validate arguments */
  if ((resultlen == NULL) || (memgzsrc == NULL) || (memgzlen < 16)) return(NULL);

  *resultlen = 0;

  /* Check the magic bytes of the gz stream before starting anything */
  if (memgz[0] != 0x1F) return(NULL);
  if (memgz[1] != 0x8B) return(NULL);

  /* read the uncompressed file length */
  filelen = memgz[memgzlen - 1];
  filelen <<= 8;
  filelen |= memgz[memgzlen - 2];
  filelen <<= 8;
  filelen |= memgz[memgzlen - 3];
  filelen <<= 8;
  filelen |= memgz[memgzlen - 4];

  /* abort if file appears to be over 1 GB - this is certainly an anomaly */
  if (filelen > 1024*1024*1024) return(NULL);

  /* allocate memory for uncompressed content */
  result = calloc(1, filelen + 1); /* +1 so it is guaranteed to end with a zero byte */
  if (result == NULL) return(NULL);  /* failed to alloc memory for the result */

  memset(&zlibstream, 0, sizeof(zlibstream));
  zlibstream.zalloc = Z_NULL;
  zlibstream.zfree = Z_NULL;
  zlibstream.opaque = Z_NULL;
  zlibstream.avail_in = 0;
  zlibstream.next_in = Z_NULL;
  if (inflateInit2(&zlibstream, 31) != Z_OK) { /* 31 means "this is gzip data" (as opposed to a zlib stream or raw deflate) */
    free(result);
    return(NULL);
  }

  zlibstream.next_in = (Bytef *)memgzsrc; /* the zlib API sadly does not declare input as CONST */
  zlibstream.avail_in = (uInt)memgzlen;
  zlibstream.next_out = result;
  zlibstream.avail_out = (uInt)filelen;

  extract_res = inflate(&zlibstream, Z_FINISH); /* Z_FINISH informs zlib that it may use shortcuts because everything is expected to be done within this one single call */
  inflateEnd(&zlibstream);

  if (extract_res != Z_STREAM_END) {
    free(result);
    return(NULL);
  }

  *resultlen = (long)filelen; /* safe because file is never bigger than 1 GB anyway */
  return(result);
}
