blob: 7995e74d3b71966c9db27813f8d7cd6a99a27f04 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/resources/chromeos/zip_archiver/cpp/compressor_archive_minizip.h"
#include <algorithm>
#include <cerrno>
#include <cstring>
#include <utility>
#include "base/time/time.h"
#include "chrome/browser/resources/chromeos/zip_archiver/cpp/compressor_io_javascript_stream.h"
#include "chrome/browser/resources/chromeos/zip_archiver/cpp/compressor_stream.h"
#include "ppapi/cpp/logging.h"
namespace {
const char kCreateArchiveError[] = "Failed to create archive.";
const char kAddToArchiveError[] = "Failed to add entry to archive.";
const char kCloseArchiveError[] = "Failed to close archive.";
// We need at least 256KB for MiniZip.
const int64_t kMaximumDataChunkSize = 512 * 1024;
uint32_t UnixToDosdate(const base::Time datetime) {
base::Time::Exploded exploded;
datetime.LocalExplode(&exploded);
return (exploded.year - 1980) << 25 | exploded.month << 21 |
exploded.day_of_month << 16 | exploded.hour << 11 |
exploded.minute << 5 | exploded.second >> 1;
}
void* MinizipOpen(void* compressor, const char* /*filename*/, int /*mode*/) {
return compressor;
}
uint32_t MinizipRead(void* /*compressor*/,
void* /*stream*/,
void* /*buffur*/,
uint32_t /*size*/) {
NOTREACHED();
return 0;
}
int MinizipClose(void* /*compressor*/, void* /*stream*/) {
return 0;
}
int MinizipError(void* /*compressor*/, void* /*stream*/) {
return 0;
}
}; // namespace
// Called when data chunk must be written on the archive. It copies data
// from the given buffer processed by minizip to an array buffer and passes
// it to compressor_stream.
uint32_t CompressorArchiveMinizip::MinizipWrite(void* compressor,
void* /*stream*/,
const void* zip_buffer,
uint32_t zip_length) {
return static_cast<CompressorArchiveMinizip*>(compressor)
->StreamWrite(zip_buffer, zip_length);
}
uint32_t CompressorArchiveMinizip::StreamWrite(const void* zip_buffer,
uint32_t zip_length) {
int64_t written_bytes = compressor_stream()->Write(
offset_, zip_length, static_cast<const char*>(zip_buffer));
if (written_bytes != zip_length)
return 0 /* Error */;
// Update offset_ and length_.
offset_ += written_bytes;
if (offset_ > length_)
length_ = offset_;
return static_cast<uint32_t>(written_bytes);
}
// Returns the offset from the beginning of the data.
long CompressorArchiveMinizip::MinizipTell(void* compressor, void* /*stream*/) {
return static_cast<CompressorArchiveMinizip*>(compressor)->StreamTell();
}
long CompressorArchiveMinizip::StreamTell() {
return static_cast<long>(offset_);
}
// Moves the current offset to the specified position.
long CompressorArchiveMinizip::MinizipSeek(void* compressor,
void* /*stream*/,
uint32_t offset,
int origin) {
return static_cast<CompressorArchiveMinizip*>(compressor)
->StreamSeek(offset, origin);
}
long CompressorArchiveMinizip::StreamSeek(uint32_t offset, int origin) {
if (origin == ZLIB_FILEFUNC_SEEK_CUR) {
offset_ = std::min(offset_ + static_cast<int64_t>(offset), length_);
return 0 /* Success */;
}
if (origin == ZLIB_FILEFUNC_SEEK_END) {
offset_ = std::max(length_ - static_cast<int64_t>(offset),
static_cast<int64_t>(0));
return 0 /* Success */;
}
if (origin == ZLIB_FILEFUNC_SEEK_SET) {
offset_ = std::min(static_cast<int64_t>(offset), length_);
return 0 /* Success */;
}
return -1 /* Error */;
}
CompressorArchiveMinizip::CompressorArchiveMinizip(
CompressorStream* compressor_stream)
: CompressorArchive(compressor_stream),
compressor_stream_(compressor_stream),
zip_file_(nullptr),
destination_buffer_(std::make_unique<char[]>(kMaximumDataChunkSize)),
offset_(0),
length_(0) {}
CompressorArchiveMinizip::~CompressorArchiveMinizip() = default;
bool CompressorArchiveMinizip::CreateArchive() {
// Set up archive object.
zlib_filefunc_def zip_funcs;
zip_funcs.zopen_file = MinizipOpen;
zip_funcs.zread_file = MinizipRead;
zip_funcs.zwrite_file = MinizipWrite;
zip_funcs.ztell_file = MinizipTell;
zip_funcs.zseek_file = MinizipSeek;
zip_funcs.zclose_file = MinizipClose;
zip_funcs.zerror_file = MinizipError;
zip_funcs.opaque = this;
zip_file_ = zipOpen2(nullptr /* pathname */, APPEND_STATUS_CREATE,
nullptr /* globalcomment */, &zip_funcs);
if (!zip_file_) {
set_error_message(kCreateArchiveError);
return false /* Error */;
}
return true /* Success */;
}
bool CompressorArchiveMinizip::AddToArchive(const std::string& filename,
int64_t file_size,
base::Time modification_time,
bool is_directory) {
// Minizip takes filenames that end with '/' as directories.
std::string normalized_filename = filename;
if (is_directory)
normalized_filename += "/";
// Fill zipfileMetadata with modification_time.
zip_fileinfo zipfileMetadata;
zipfileMetadata.dos_date = UnixToDosdate(modification_time);
// 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;
// Indicates the compatibility of the file attribute information.
// Attributes of files are not avaiable in the FileSystem API. Therefore
// we don't store file attributes to an archive. However, other apps may use
// this field to determine the line record format for text files etc.
const int HOST_SYSTEM_CODE = 3; // UNIX
// PKWARE .ZIP File Format Specification version 6.3.x
const int ZIP_SPECIFICATION_VERSION_CODE = 63;
const int VERSION_MADE_BY =
HOST_SYSTEM_CODE << 8 | ZIP_SPECIFICATION_VERSION_CODE;
int open_result =
zipOpenNewFileInZip4(zip_file_, // file
normalized_filename.c_str(), // filename
&zipfileMetadata, // zipfi
nullptr, // extrafield_local
0u, // size_extrafield_local
nullptr, // extrafield_global
0u, // size_extrafield_global
nullptr, // comment
Z_DEFLATED, // method
Z_DEFAULT_COMPRESSION, // level
0, // raw
-MAX_WBITS, // windowBits
DEF_MEM_LEVEL, // memLevel
Z_DEFAULT_STRATEGY, // strategy
nullptr, // password
0, // crcForCrypting
VERSION_MADE_BY, // versionMadeBy
LANGUAGE_ENCODING_FLAG); // flagBase
if (open_result != ZIP_OK) {
CloseArchive(true /* has_error */);
set_error_message(kAddToArchiveError);
return false /* Error */;
}
bool has_error = false;
if (!is_directory) {
int64_t remaining_size = file_size;
while (remaining_size > 0) {
int64_t chunk_size = std::min(remaining_size, kMaximumDataChunkSize);
PP_DCHECK(chunk_size > 0);
int64_t read_bytes =
compressor_stream_->Read(chunk_size, destination_buffer_.get());
// Negative read_bytes indicates an error occurred when reading chunks.
// 0 just means there is no more data available, but here we need positive
// length of bytes, so this is also an error here.
if (read_bytes <= 0) {
has_error = true;
break;
}
if (canceled_) {
break;
}
if (zipWriteInFileInZip(zip_file_, destination_buffer_.get(),
read_bytes) != ZIP_OK) {
has_error = true;
break;
}
remaining_size -= read_bytes;
}
}
if (!has_error && zipCloseFileInZip(zip_file_) != ZIP_OK)
has_error = true;
if (has_error) {
CloseArchive(true /* has_error */);
set_error_message(kAddToArchiveError);
return false /* Error */;
}
if (canceled_) {
CloseArchive(true /* has_error */);
return false /* Error */;
}
return true /* Success */;
}
bool CompressorArchiveMinizip::CloseArchive(bool has_error) {
if (zipClose(zip_file_, nullptr /* global_comment */) != ZIP_OK) {
set_error_message(kCloseArchiveError);
return false /* Error */;
}
if (!has_error) {
if (compressor_stream()->Flush() < 0) {
set_error_message(kCloseArchiveError);
return false /* Error */;
}
}
return true /* Success */;
}
void CompressorArchiveMinizip::CancelArchive() {
canceled_ = true;
}