// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/zlib/google/zip_internal.h"

#include <stddef.h>
#include <string.h>

#include <algorithm>
#include <string_view>

#include "base/containers/fixed_flat_set.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"

#if defined(USE_SYSTEM_MINIZIP)
#include <minizip/ioapi.h>
#include <minizip/unzip.h>
#include <minizip/zip.h>
#else
#include "third_party/zlib/contrib/minizip/unzip.h"
#include "third_party/zlib/contrib/minizip/zip.h"
#if defined(OS_WIN)
#include "third_party/zlib/contrib/minizip/iowin32.h"
#elif defined(OS_POSIX)
#include "third_party/zlib/contrib/minizip/ioapi.h"
#endif  // defined(OS_POSIX)
#endif  // defined(USE_SYSTEM_MINIZIP)

namespace {

#if defined(OS_WIN)
typedef struct {
  HANDLE hf;
  int error;
} WIN32FILE_IOWIN;

// This function is derived from third_party/minizip/iowin32.c.
// Its only difference is that it treats the filename as UTF-8 and
// uses the Unicode version of CreateFile.
void* ZipOpenFunc(void* opaque, const void* filename, int mode) {
  DWORD desired_access = 0, creation_disposition = 0;
  DWORD share_mode = 0, flags_and_attributes = 0;
  HANDLE file = 0;
  void* ret = NULL;

  if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ) {
    desired_access = GENERIC_READ;
    creation_disposition = OPEN_EXISTING;
    share_mode = FILE_SHARE_READ;
  } else if (mode & ZLIB_FILEFUNC_MODE_EXISTING) {
    desired_access = GENERIC_WRITE | GENERIC_READ;
    creation_disposition = OPEN_EXISTING;
  } else if (mode & ZLIB_FILEFUNC_MODE_CREATE) {
    desired_access = GENERIC_WRITE | GENERIC_READ;
    creation_disposition = CREATE_ALWAYS;
  }

  if (filename != nullptr && desired_access != 0) {
    file = CreateFileW(
        base::UTF8ToWide(static_cast<const char*>(filename)).c_str(),
        desired_access, share_mode, nullptr, creation_disposition,
        flags_and_attributes, nullptr);
  }

  if (file == INVALID_HANDLE_VALUE)
    file = NULL;

  if (file != NULL) {
    WIN32FILE_IOWIN file_ret;
    file_ret.hf = file;
    file_ret.error = 0;
    ret = malloc(sizeof(WIN32FILE_IOWIN));
    if (ret == NULL)
      CloseHandle(file);
    else
      *(static_cast<WIN32FILE_IOWIN*>(ret)) = file_ret;
  }
  return ret;
}
#endif

#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Callback function for zlib that opens a file stream from a file descriptor.
// Since we do not own the file descriptor, dup it so that we can fdopen/fclose
// a file stream.
void* FdOpenFileFunc(void* opaque, const void* filename, int mode) {
  FILE* file = NULL;
  const char* mode_fopen = NULL;

  if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ)
    mode_fopen = "rb";
  else if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
    mode_fopen = "r+b";
  else if (mode & ZLIB_FILEFUNC_MODE_CREATE)
    mode_fopen = "wb";

  if ((filename != NULL) && (mode_fopen != NULL)) {
    int fd = dup(*static_cast<int*>(opaque));
    if (fd != -1)
      file = fdopen(fd, mode_fopen);
  }

  return file;
}

int FdCloseFileFunc(void* opaque, void* stream) {
  fclose(static_cast<FILE*>(stream));
  free(opaque);  // malloc'ed in FillFdOpenFileFunc()
  return 0;
}

// Fills |pzlib_filecunc_def| appropriately to handle the zip file
// referred to by |fd|.
void FillFdOpenFileFunc(zlib_filefunc64_def* pzlib_filefunc_def, int fd) {
  fill_fopen64_filefunc(pzlib_filefunc_def);
  pzlib_filefunc_def->zopen64_file = FdOpenFileFunc;
  pzlib_filefunc_def->zclose_file = FdCloseFileFunc;
  int* ptr_fd = static_cast<int*>(malloc(sizeof(fd)));
  *ptr_fd = fd;
  pzlib_filefunc_def->opaque = ptr_fd;
}
#endif  // defined(OS_POSIX)

#if defined(OS_WIN)
// Callback function for zlib that opens a file stream from a Windows handle.
// Does not take ownership of the handle.
void* HandleOpenFileFunc(void* opaque, const void* /*filename*/, int mode) {
  WIN32FILE_IOWIN file_ret;
  file_ret.hf = static_cast<HANDLE>(opaque);
  file_ret.error = 0;
  if (file_ret.hf == INVALID_HANDLE_VALUE)
    return NULL;

  void* ret = malloc(sizeof(WIN32FILE_IOWIN));
  if (ret != NULL)
    *(static_cast<WIN32FILE_IOWIN*>(ret)) = file_ret;
  return ret;
}

int HandleCloseFileFunc(void* opaque, void* stream) {
  free(stream);  // malloc'ed in HandleOpenFileFunc()
  return 0;
}
#endif

// A struct that contains data required for zlib functions to extract files from
// a zip archive stored in memory directly. The following I/O API functions
// expect their opaque parameters refer to this struct.
struct ZipBuffer {
  const char* data;  // weak
  ZPOS64_T length;
  ZPOS64_T offset;
};

// Opens the specified file. When this function returns a non-NULL pointer, zlib
// uses this pointer as a stream parameter while compressing or uncompressing
// data. (Returning NULL represents an error.) This function initializes the
// given opaque parameter and returns it because this parameter stores all
// information needed for uncompressing data. (This function does not support
// writing compressed data and it returns NULL for this case.)
void* OpenZipBuffer(void* opaque, const void* /*filename*/, int mode) {
  if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) != ZLIB_FILEFUNC_MODE_READ) {
    NOTREACHED();
  }
  ZipBuffer* buffer = static_cast<ZipBuffer*>(opaque);
  if (!buffer || !buffer->data || !buffer->length)
    return NULL;
  buffer->offset = 0;
  return opaque;
}

// Reads compressed data from the specified stream. This function copies data
// refered by the opaque parameter and returns the size actually copied.
uLong ReadZipBuffer(void* opaque, void* /*stream*/, void* buf, uLong size) {
  ZipBuffer* buffer = static_cast<ZipBuffer*>(opaque);
  DCHECK_LE(buffer->offset, buffer->length);
  ZPOS64_T remaining_bytes = buffer->length - buffer->offset;
  if (!buffer || !buffer->data || !remaining_bytes)
    return 0;
  if (size > remaining_bytes)
    size = remaining_bytes;
  memcpy(buf, &buffer->data[buffer->offset], size);
  buffer->offset += size;
  return size;
}

// Writes compressed data to the stream. This function always returns zero
// because this implementation is only for reading compressed data.
uLong WriteZipBuffer(void* /*opaque*/,
                     void* /*stream*/,
                     const void* /*buf*/,
                     uLong /*size*/) {
  NOTREACHED();
}

// Returns the offset from the beginning of the data.
ZPOS64_T GetOffsetOfZipBuffer(void* opaque, void* /*stream*/) {
  ZipBuffer* buffer = static_cast<ZipBuffer*>(opaque);
  if (!buffer)
    return -1;
  return buffer->offset;
}

// Moves the current offset to the specified position.
long SeekZipBuffer(void* opaque,
                   void* /*stream*/,
                   ZPOS64_T offset,
                   int origin) {
  ZipBuffer* buffer = static_cast<ZipBuffer*>(opaque);
  if (!buffer)
    return -1;
  if (origin == ZLIB_FILEFUNC_SEEK_CUR) {
    buffer->offset = std::min(buffer->offset + offset, buffer->length);
    return 0;
  }
  if (origin == ZLIB_FILEFUNC_SEEK_END) {
    buffer->offset = (buffer->length > offset) ? buffer->length - offset : 0;
    return 0;
  }
  if (origin == ZLIB_FILEFUNC_SEEK_SET) {
    buffer->offset = std::min(buffer->length, offset);
    return 0;
  }
  NOTREACHED();
}

// Closes the input offset and deletes all resources used for compressing or
// uncompressing data. This function deletes the ZipBuffer object referred by
// the opaque parameter since zlib deletes the unzFile object and it does not
// use this object any longer.
int CloseZipBuffer(void* opaque, void* /*stream*/) {
  if (opaque)
    free(opaque);
  return 0;
}

// Returns the last error happened when reading or writing data. This function
// always returns zero, which means there are not any errors.
int GetErrorOfZipBuffer(void* /*opaque*/, void* /*stream*/) {
  return 0;
}

// Returns a zip_fileinfo struct with the time represented by |file_time|.
zip_fileinfo TimeToZipFileInfo(const base::Time& file_time) {
  base::Time::Exploded file_time_parts;
  file_time.UTCExplode(&file_time_parts);

  zip_fileinfo zip_info = {};
  if (file_time_parts.year >= 1980) {
    // This if check works around the handling of the year value in
    // contrib/minizip/zip.c in function zip64local_TmzDateToDosDate
    // It assumes that dates below 1980 are in the double digit format.
    // Hence the fail safe option is to leave the date unset. Some programs
    // might show the unset date as 1980-0-0 which is invalid.
    zip_info.tmz_date.tm_year = file_time_parts.year;
    zip_info.tmz_date.tm_mon = file_time_parts.month - 1;
    zip_info.tmz_date.tm_mday = file_time_parts.day_of_month;
    zip_info.tmz_date.tm_hour = file_time_parts.hour;
    zip_info.tmz_date.tm_min = file_time_parts.minute;
    zip_info.tmz_date.tm_sec = file_time_parts.second;
  }

  return zip_info;
}
}  // namespace

namespace zip {
namespace internal {

unzFile OpenForUnzipping(const std::string& file_name_utf8) {
  zlib_filefunc64_def* zip_func_ptrs = nullptr;
#if defined(OS_WIN)
  zlib_filefunc64_def zip_funcs;
  fill_win32_filefunc64(&zip_funcs);
  zip_funcs.zopen64_file = ZipOpenFunc;
  zip_func_ptrs = &zip_funcs;
#endif
  return unzOpen2_64(file_name_utf8.c_str(), zip_func_ptrs);
}

#if defined(OS_POSIX) || defined(OS_FUCHSIA)
unzFile OpenFdForUnzipping(int zip_fd) {
  zlib_filefunc64_def zip_funcs;
  FillFdOpenFileFunc(&zip_funcs, zip_fd);
  // Passing dummy "fd" filename to zlib.
  return unzOpen2_64("fd", &zip_funcs);
}
#endif

#if defined(OS_WIN)
unzFile OpenHandleForUnzipping(HANDLE zip_handle) {
  zlib_filefunc64_def zip_funcs;
  fill_win32_filefunc64(&zip_funcs);
  zip_funcs.zopen64_file = HandleOpenFileFunc;
  zip_funcs.zclose_file = HandleCloseFileFunc;
  zip_funcs.opaque = zip_handle;
  return unzOpen2_64("fd", &zip_funcs);
}
#endif

// static
unzFile PrepareMemoryForUnzipping(const std::string& data) {
  if (data.empty())
    return NULL;

  ZipBuffer* buffer = static_cast<ZipBuffer*>(malloc(sizeof(ZipBuffer)));
  if (!buffer)
    return NULL;
  buffer->data = data.data();
  buffer->length = data.length();
  buffer->offset = 0;

  zlib_filefunc64_def zip_functions;
  zip_functions.zopen64_file = OpenZipBuffer;
  zip_functions.zread_file = ReadZipBuffer;
  zip_functions.zwrite_file = WriteZipBuffer;
  zip_functions.ztell64_file = GetOffsetOfZipBuffer;
  zip_functions.zseek64_file = SeekZipBuffer;
  zip_functions.zclose_file = CloseZipBuffer;
  zip_functions.zerror_file = GetErrorOfZipBuffer;
  zip_functions.opaque = buffer;
  return unzOpen2_64(nullptr, &zip_functions);
}

zipFile OpenForZipping(const std::string& file_name_utf8, int append_flag) {
  zlib_filefunc64_def* zip_func_ptrs = nullptr;
#if defined(OS_WIN)
  zlib_filefunc64_def zip_funcs;
  fill_win32_filefunc64(&zip_funcs);
  zip_funcs.zopen64_file = ZipOpenFunc;
  zip_func_ptrs = &zip_funcs;
#endif
  return zipOpen2_64(file_name_utf8.c_str(), append_flag, nullptr,
                     zip_func_ptrs);
}

#if defined(OS_POSIX) || defined(OS_FUCHSIA)
zipFile OpenFdForZipping(int zip_fd, int append_flag) {
  zlib_filefunc64_def zip_funcs;
  FillFdOpenFileFunc(&zip_funcs, zip_fd);
  // Passing dummy "fd" filename to zlib.
  return zipOpen2_64("fd", append_flag, nullptr, &zip_funcs);
}
#endif

bool ZipOpenNewFileInZip(zipFile zip_file,
                         const std::string& str_path,
                         base::Time last_modified_time,
                         Compression compression) {
  // Section 4.4.4 http://www.pkware.com/documents/casestudies/APPNOTE.TXT
  // Setting the Language encoding flag so the file is told to be in utf-8.
  const uLong LANGUAGE_ENCODING_FLAG = 0x1 << 11;

  const zip_fileinfo file_info = TimeToZipFileInfo(last_modified_time);
  const int err = zipOpenNewFileInZip4_64(
      /*file=*/zip_file,
      /*filename=*/str_path.c_str(),
      /*zip_fileinfo=*/&file_info,
      /*extrafield_local=*/nullptr,
      /*size_extrafield_local=*/0u,
      /*extrafield_global=*/nullptr,
      /*size_extrafield_global=*/0u,
      /*comment=*/nullptr,
      /*method=*/compression,
      /*level=*/Z_DEFAULT_COMPRESSION,
      /*raw=*/0,
      /*windowBits=*/-MAX_WBITS,
      /*memLevel=*/DEF_MEM_LEVEL,
      /*strategy=*/Z_DEFAULT_STRATEGY,
      /*password=*/nullptr,
      /*crcForCrypting=*/0,
      /*versionMadeBy=*/0,
      /*flagBase=*/LANGUAGE_ENCODING_FLAG,
      /*zip64=*/1);

  if (err != ZIP_OK) {
    DLOG(ERROR) << "Cannot open ZIP file entry '" << str_path
                << "': zipOpenNewFileInZip4_64 returned " << err;
    return false;
  }

  return true;
}

Compression GetCompressionMethod(const base::FilePath& path) {
  // Get the filename extension in lower case.
  const base::FilePath::StringType ext =
      base::ToLowerASCII(path.FinalExtension());

  if (ext.empty())
    return kDeflated;


  // Skip the leading dot.
  base::FilePath::StringViewType ext_without_dot = ext;
  DCHECK_EQ(ext_without_dot.front(), FILE_PATH_LITERAL('.'));
  ext_without_dot.remove_prefix(1);

  // Well known filename extensions of files that a likely to be already
  // compressed. The extensions are in lower case without the leading dot.
  static constexpr auto kExts =
      base::MakeFixedFlatSet<base::FilePath::StringViewType>({
          FILE_PATH_LITERAL("3g2"),   //
          FILE_PATH_LITERAL("3gp"),   //
          FILE_PATH_LITERAL("7z"),    //
          FILE_PATH_LITERAL("7zip"),  //
          FILE_PATH_LITERAL("aac"),   //
          FILE_PATH_LITERAL("avi"),   //
          FILE_PATH_LITERAL("bz"),    //
          FILE_PATH_LITERAL("bz2"),   //
          FILE_PATH_LITERAL("crx"),   //
          FILE_PATH_LITERAL("gif"),   //
          FILE_PATH_LITERAL("gz"),    //
          FILE_PATH_LITERAL("jar"),   //
          FILE_PATH_LITERAL("jpeg"),  //
          FILE_PATH_LITERAL("jpg"),   //
          FILE_PATH_LITERAL("lz"),    //
          FILE_PATH_LITERAL("m2v"),   //
          FILE_PATH_LITERAL("m4p"),   //
          FILE_PATH_LITERAL("m4v"),   //
          FILE_PATH_LITERAL("mng"),   //
          FILE_PATH_LITERAL("mov"),   //
          FILE_PATH_LITERAL("mp2"),   //
          FILE_PATH_LITERAL("mp3"),   //
          FILE_PATH_LITERAL("mp4"),   //
          FILE_PATH_LITERAL("mpe"),   //
          FILE_PATH_LITERAL("mpeg"),  //
          FILE_PATH_LITERAL("mpg"),   //
          FILE_PATH_LITERAL("mpv"),   //
          FILE_PATH_LITERAL("ogg"),   //
          FILE_PATH_LITERAL("ogv"),   //
          FILE_PATH_LITERAL("png"),   //
          FILE_PATH_LITERAL("qt"),    //
          FILE_PATH_LITERAL("rar"),   //
          FILE_PATH_LITERAL("taz"),   //
          FILE_PATH_LITERAL("tb2"),   //
          FILE_PATH_LITERAL("tbz"),   //
          FILE_PATH_LITERAL("tbz2"),  //
          FILE_PATH_LITERAL("tgz"),   //
          FILE_PATH_LITERAL("tlz"),   //
          FILE_PATH_LITERAL("tz"),    //
          FILE_PATH_LITERAL("tz2"),   //
          FILE_PATH_LITERAL("vob"),   //
          FILE_PATH_LITERAL("webm"),  //
          FILE_PATH_LITERAL("wma"),   //
          FILE_PATH_LITERAL("wmv"),   //
          FILE_PATH_LITERAL("xz"),    //
          FILE_PATH_LITERAL("z"),     //
          FILE_PATH_LITERAL("zip"),   //
      });

  if (kExts.count(ext_without_dot)) {
    return kStored;
  }

  return kDeflated;
}

}  // namespace internal
}  // namespace zip
