// 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.
#ifndef THIRD_PARTY_ZLIB_GOOGLE_ZIP_READER_H_
#define THIRD_PARTY_ZLIB_GOOGLE_ZIP_READER_H_

#include <stddef.h>
#include <stdint.h>

#include <limits>
#include <memory>
#include <string>

#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/time/time.h"

#if defined(USE_SYSTEM_MINIZIP)
#include <minizip/unzip.h>
#else
#include "third_party/zlib/contrib/minizip/unzip.h"
#endif

namespace zip {

// A delegate interface used to stream out an entry; see
// ZipReader::ExtractCurrentEntry.
class WriterDelegate {
 public:
  virtual ~WriterDelegate() {}

  // Invoked once before any data is streamed out to pave the way (e.g., to open
  // the output file). Return false on failure to cancel extraction.
  virtual bool PrepareOutput() { return true; }

  // Invoked to write the next chunk of data. Return false on failure to cancel
  // extraction.
  virtual bool WriteBytes(const char* data, int num_bytes) { return true; }

  // Sets the last-modified time of the data.
  virtual void SetTimeModified(const base::Time& time) {}

  // Called with the POSIX file permissions of the data; POSIX implementations
  // may apply some of the permissions (for example, the executable bit) to the
  // output file.
  virtual void SetPosixFilePermissions(int mode) {}

  // Called if an error occurred while extracting the file. The WriterDelegate
  // can then remove and clean up the partially extracted data.
  virtual void OnError() {}
};

// This class is used for reading ZIP archives. A typical use case of this class
// is to scan entries in a ZIP archive and extract them. The code will look
// like:
//
//   ZipReader reader;
//   if (!reader.Open(zip_path)) {
//     // Cannot open
//     return;
//   }
//
//   while (const ZipReader::entry* entry = reader.Next()) {
//     auto writer = CreateFilePathWriterDelegate(extract_dir, entry->path);
//     if (!reader.ExtractCurrentEntry(writer)) {
//           // Cannot extract
//           return;
//     }
//   }
//
//   if (!reader.ok()) {
//     // Error while enumerating entries
//     return;
//   }
//
class ZipReader {
 public:
  // A callback that is called when the operation is successful.
  using SuccessCallback = base::OnceClosure;
  // A callback that is called when the operation fails.
  using FailureCallback = base::OnceClosure;
  // A callback that is called periodically during the operation with the number
  // of bytes that have been processed so far.
  using ProgressCallback = base::RepeatingCallback<void(int64_t)>;
  // A callback that is called periodically during the operation with the number
  // of bytes that have been processed since the previous call (i.e. delta).
  using ListenerCallback = base::RepeatingCallback<void(uint64_t)>;

  // Information of an entry (file or directory) in a ZIP archive.
  struct Entry {
    // Path of this entry, in its original encoding as it is stored in the ZIP
    // archive. The encoding is not specified here. It might or might not be
    // UTF-8, and the caller needs to use other means to determine the encoding
    // if it wants to interpret this path correctly.
    std::string path_in_original_encoding;

    // Path of the entry, converted to Unicode. This path is relative (eg
    // "foo/bar.txt"). Absolute paths (eg "/foo/bar.txt") or paths containing
    // ".." or "." components (eg "../foo/bar.txt") are converted to safe
    // relative paths. Eg:
    // (In ZIP) -> (Entry.path)
    // /foo/bar -> ROOT/foo/bar
    // ../a     -> UP/a
    // ./a      -> DOT/a
    base::FilePath path;

    // Size of the original uncompressed file, or 0 if the entry is a directory.
    // This value should not be trusted, because it is stored as metadata in the
    // ZIP archive and can be different from the real uncompressed size.
    int64_t original_size;

    // Last modified time. If the timestamp stored in the ZIP archive is not
    // valid, the Unix epoch will be returned.
    //
    // The timestamp stored in the ZIP archive uses the MS-DOS date and time
    // format.
    //
    // http://msdn.microsoft.com/en-us/library/ms724247(v=vs.85).aspx
    //
    // As such the following limitations apply:
    // * Only years from 1980 to 2107 can be represented.
    // * The timestamp has a 2-second resolution.
    // * There is no timezone information, so the time is interpreted as UTC.
    base::Time last_modified;

    // True if the entry is a directory.
    // False if the entry is a file.
    bool is_directory = false;

    // True if the entry path cannot be converted to a safe relative path. This
    // happens if a file entry (not a directory) has a filename "." or "..".
    bool is_unsafe = false;

    // True if the file content is encrypted.
    bool is_encrypted = false;

    // True if the encryption scheme is AES.
    bool uses_aes_encryption = false;

    // Entry POSIX permissions (POSIX systems only).
    int posix_mode;
  };

  ZipReader();

  ZipReader(const ZipReader&) = delete;
  ZipReader& operator=(const ZipReader&) = delete;

  ~ZipReader();

  // Opens the ZIP archive specified by |zip_path|. Returns true on
  // success.
  bool Open(const base::FilePath& zip_path);

  // Opens the ZIP archive referred to by the platform file |zip_fd|, without
  // taking ownership of |zip_fd|. Returns true on success.
  bool OpenFromPlatformFile(base::PlatformFile zip_fd);

  // Opens the zip data stored in |data|. This class uses a weak reference to
  // the given sring while extracting files, i.e. the caller should keep the
  // string until it finishes extracting files.
  bool OpenFromString(const std::string& data);

  // Closes the currently opened ZIP archive. This function is called in the
  // destructor of the class, so you usually don't need to call this.
  void Close();

  // Sets the encoding of entry paths in the ZIP archive.
  // By default, paths are assumed to be in UTF-8.
  void SetEncoding(std::string encoding) { encoding_ = std::move(encoding); }

  // Sets the decryption password that will be used to decrypt encrypted file in
  // the ZIP archive.
  void SetPassword(std::string password) { password_ = std::move(password); }

  // Gets the next entry. Returns null if there is no more entry, or if an error
  // occurred while scanning entries. The returned Entry is owned by this
  // ZipReader, and is valid until Next() is called again or until this
  // ZipReader is closed.
  //
  // This function should be called before operations over the current entry
  // like ExtractCurrentEntryToFile().
  //
  // while (const ZipReader::Entry* entry = reader.Next()) {
  //   // Do something with the current entry here.
  //   ...
  // }
  //
  // // Finished scanning entries.
  // // Check if the scanning stopped because of an error.
  // if (!reader.ok()) {
  //   // There was an error.
  //   ...
  // }
  const Entry* Next();

  // Returns true if the enumeration of entries was successful, or false if it
  // stopped because of an error.
  bool ok() const { return ok_; }

  // Extracts |num_bytes_to_extract| bytes of the current entry to |delegate|,
  // starting from the beginning of the entry.
  //
  // Returns true if the entire file was extracted without error.
  //
  // Precondition: Next() returned a non-null Entry.
  bool ExtractCurrentEntry(WriterDelegate* delegate,
                           uint64_t num_bytes_to_extract =
                               std::numeric_limits<uint64_t>::max()) const;

  // Extracts the current entry to |delegate|, starting from the beginning
  // of the entry, calling |listener_callback| regularly with the number of
  // bytes extracted.
  //
  // Returns true if the entire file was extracted without error.
  //
  // Precondition: Next() returned a non-null Entry.
  bool ExtractCurrentEntryWithListener(
      WriterDelegate* delegate,
      ListenerCallback listener_callback) const;

  // Asynchronously extracts the current entry to the given output file path. If
  // the current entry is a directory it just creates the directory
  // synchronously instead.
  //
  // |success_callback| will be called on success and |failure_callback| will be
  // called on failure. |progress_callback| will be called at least once.
  // Callbacks will be posted to the current MessageLoop in-order.
  //
  // Precondition: Next() returned a non-null Entry.
  void ExtractCurrentEntryToFilePathAsync(
      const base::FilePath& output_file_path,
      SuccessCallback success_callback,
      FailureCallback failure_callback,
      ProgressCallback progress_callback);

  // Extracts the current entry into memory. If the current entry is a
  // directory, |*output| is set to the empty string. If the current entry is a
  // file, |*output| is filled with its contents.
  //
  // The value in |Entry::original_size| cannot be trusted, so the real size of
  // the uncompressed contents can be different. |max_read_bytes| limits the
  // amount of memory used to carry the entry.
  //
  // Returns true if the entire content is read without error. If the content is
  // bigger than |max_read_bytes|, this function returns false and |*output| is
  // filled with |max_read_bytes| of data. If an error occurs, this function
  // returns false and |*output| contains the content extracted so far, which
  // might be garbage data.
  //
  // Precondition: Next() returned a non-null Entry.
  bool ExtractCurrentEntryToString(uint64_t max_read_bytes,
                                   std::string* output) const;

  bool ExtractCurrentEntryToString(std::string* output) const {
    return ExtractCurrentEntryToString(
        base::checked_cast<uint64_t>(output->max_size()), output);
  }

  // Returns the number of entries in the ZIP archive.
  //
  // Precondition: one of the Open() methods returned true.
  int num_entries() const { return num_entries_; }

 private:
  // Common code used both in Open and OpenFromFd.
  bool OpenInternal();

  // Resets the internal state.
  void Reset();

  // Opens the current entry in the ZIP archive. On success, returns true and
  // updates the current entry state |entry_|.
  //
  // Note that there is no matching CloseEntry(). The current entry state is
  // reset automatically as needed.
  bool OpenEntry();

  // Normalizes the given path passed as UTF-16 string piece. Sets entry_.path,
  // entry_.is_directory and entry_.is_unsafe.
  void Normalize(base::StringPiece16 in);

  // Runs the ListenerCallback at a throttled rate.
  void ReportProgress(ListenerCallback listener_callback, uint64_t bytes) const;

  // Extracts |num_bytes_to_extract| bytes of the current entry to |delegate|,
  // starting from the beginning of the entry calling |listener_callback| if
  // its supplied.
  //
  // Returns true if the entire file was extracted without error.
  //
  // Precondition: Next() returned a non-null Entry.
  bool ExtractCurrentEntry(WriterDelegate* delegate,
                           ListenerCallback listener_callback,
                           uint64_t num_bytes_to_extract =
                               std::numeric_limits<uint64_t>::max()) const;

  // Extracts a chunk of the file to the target.  Will post a task for the next
  // chunk and success/failure/progress callbacks as necessary.
  void ExtractChunk(base::File target_file,
                    SuccessCallback success_callback,
                    FailureCallback failure_callback,
                    ProgressCallback progress_callback,
                    const int64_t offset);

  std::string encoding_;
  std::string password_;
  unzFile zip_file_;
  int num_entries_;
  int next_index_;
  bool reached_end_;
  bool ok_;
  Entry entry_;

  // Next time to report progress.
  mutable base::TimeTicks next_progress_report_time_ = base::TimeTicks::Now();

  // Progress time delta.
  // TODO(crbug.com/953256) Add this as parameter to the unzip options.
  base::TimeDelta progress_period_ = base::Milliseconds(1000);

  // Number of bytes read since last progress report callback executed.
  mutable uint64_t delta_bytes_read_ = 0;

  base::WeakPtrFactory<ZipReader> weak_ptr_factory_{this};
};

// A writer delegate that writes to a given File. It is recommended that this
// file be initially empty.
class FileWriterDelegate : public WriterDelegate {
 public:
  // Constructs a FileWriterDelegate that manipulates |file|. The delegate will
  // not own |file|, therefore the caller must guarantee |file| will outlive the
  // delegate.
  explicit FileWriterDelegate(base::File* file);

  // Constructs a FileWriterDelegate that takes ownership of |file|.
  explicit FileWriterDelegate(base::File owned_file);

  FileWriterDelegate(const FileWriterDelegate&) = delete;
  FileWriterDelegate& operator=(const FileWriterDelegate&) = delete;

  ~FileWriterDelegate() override;

  // Returns true if the file handle passed to the constructor is valid.
  bool PrepareOutput() override;

  // Writes |num_bytes| bytes of |data| to the file, returning false on error or
  // if not all bytes could be written.
  bool WriteBytes(const char* data, int num_bytes) override;

  // Sets the last-modified time of the data.
  void SetTimeModified(const base::Time& time) override;

  // On POSIX systems, sets the file to be executable if the source file was
  // executable.
  void SetPosixFilePermissions(int mode) override;

  // Empties the file to avoid leaving garbage data in it.
  void OnError() override;

  // Gets the number of bytes written into the file.
  int64_t file_length() { return file_length_; }

 protected:
  // The delegate can optionally own the file it modifies, in which case
  // owned_file_ is set and file_ is an alias for owned_file_.
  base::File owned_file_;

  // The file the delegate modifies.
  base::File* const file_ = &owned_file_;

  int64_t file_length_ = 0;
};

// A writer delegate that creates and writes a file at a given path. This does
// not overwrite any existing file.
class FilePathWriterDelegate : public FileWriterDelegate {
 public:
  explicit FilePathWriterDelegate(base::FilePath output_file_path);

  FilePathWriterDelegate(const FilePathWriterDelegate&) = delete;
  FilePathWriterDelegate& operator=(const FilePathWriterDelegate&) = delete;

  ~FilePathWriterDelegate() override;

  // Creates the output file and any necessary intermediate directories. Does
  // not overwrite any existing file, and returns false if the output file
  // cannot be created because another file conflicts with it.
  bool PrepareOutput() override;

  // Deletes the output file.
  void OnError() override;

 private:
  const base::FilePath output_file_path_;
};

}  // namespace zip

#endif  // THIRD_PARTY_ZLIB_GOOGLE_ZIP_READER_H_
