gmio/src/gmio_core/internal/zip_utils.c
2017-01-09 14:43:55 +01:00

492 lines
19 KiB
C

/****************************************************************************
** Copyright (c) 2016, Fougue Ltd. <http://www.fougue.pro>
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
**
** 2. Redistributions in binary form must reproduce the above
** copyright notice, this list of conditions and the following
** disclaimer in the documentation and/or other materials provided
** with the distribution.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
****************************************************************************/
#include "zip_utils.h"
#include "../error.h"
#include "byte_codec.h"
#include "helper_stream.h"
#include <stddef.h>
/* ----------
* Constants
* ---------- */
enum {
GMIO_ZIP_SIZE_LOCAL_FILE_HEADER = 4 + 5*2 + 3*4 + 2*2,
GMIO_ZIP_SIZE_CENTRAL_DIRECTORY_HEADER = 4 + 6*2 + 3*4 + 5*2 +2*4,
GMIO_ZIP64_SIZE_DATA_DESCRIPTOR = 4 + 2*8,
GMIO_ZIP_SIZE_DATA_DESCRIPTOR = 4 + 2*4,
GMIO_ZIP_SIZE_END_OF_CENTRAL_DIRECTORY_RECORD = 4 + 4*2 + 2*4 + 2
};
/* ----------
* Internal functions
* ---------- */
/* See http://www.vsft.com/hal/dostime.htm */
static uint32_t gmio_to_msdos_datetime(const struct tm* datetime)
{
const uint32_t msdos_datetime =
/* Time */
(datetime->tm_sec) /* bit 0-4 */
| (datetime->tm_min << 5) /* bit 5-10 */
| (datetime->tm_hour << 11) /* bit 11-15 */
/* Date */
| (datetime->tm_mday << 16) /* bit 16-20 */
| ((datetime->tm_mon + 1) << 21) /* bit 21-24 */
| ((datetime->tm_year - 80) << 25); /* bit 21-24 */
return msdos_datetime;
}
/* Returns a non-null datetime, if arg is NULL it returns current gmtime() */
static const struct tm* gmio_nonnull_datetime(const struct tm* datetime)
{
if (datetime == NULL) {
const time_t current_time = time(NULL);
return gmtime(&current_time); /* UTC time */
}
return datetime;
}
/* Helper to facilitate return from gmio_zip_write_xxx() API functions */
static size_t gmio_zip_write_returnhelper(
struct gmio_stream* stream,
size_t written,
size_t expected,
int* ptr_error)
{
if (ptr_error != NULL) {
const bool no_error = written == expected && !gmio_stream_error(stream);
*ptr_error = no_error ? GMIO_ERROR_OK : GMIO_ERROR_STREAM;
}
return written;
}
static bool gmio_zip_read_checkhelper(
struct gmio_stream* stream, size_t read, size_t expected)
{
return read == expected && !gmio_stream_error(stream);
}
/* Helper to facilitate return from gmio_zip_[read,write]_xxx() API functions */
static size_t gmio_zip_io_returnerr(size_t io_len, int error, int* ptr_error)
{
if (ptr_error != NULL)
*ptr_error = error;
return io_len;
}
/* ----------
* API functions
* ---------- */
size_t gmio_zip_read_local_file_header(
struct gmio_stream *stream,
struct gmio_zip_local_file_header *info,
int *ptr_error)
{
uint8_t bytes[GMIO_ZIP_SIZE_LOCAL_FILE_HEADER];
const uint8_t* buff = bytes;
const size_t read_len = gmio_stream_read_bytes(stream, bytes, sizeof(bytes));
if (!gmio_zip_read_checkhelper(stream, read_len, sizeof(bytes)))
return gmio_zip_io_returnerr(read_len, GMIO_ERROR_STREAM, ptr_error);
if (gmio_adv_decode_uint32_le(&buff) != 0x04034b50) {
return gmio_zip_io_returnerr(
read_len, GMIO_ZIP_UTILS_ERROR_BAD_MAGIC, ptr_error);
}
info->version_needed_to_extract = gmio_adv_decode_uint16_le(&buff);
info->general_purpose_flags = gmio_adv_decode_uint16_le(&buff);
info->compress_method = gmio_adv_decode_uint16_le(&buff);
/* 2-bytes last mod file time + 2-bytes last mod file date */
/* TODO: convert DOS datetime to struct tm */
const uint32_t dos_datetime = gmio_adv_decode_uint32_le(&buff);
GMIO_UNUSED(dos_datetime);
info->lastmod_datetime = NULL;
info->crc32 = gmio_adv_decode_uint32_le(&buff);
info->compressed_size = gmio_adv_decode_uint32_le(&buff);
info->uncompressed_size = gmio_adv_decode_uint32_le(&buff);
info->filename_len = gmio_adv_decode_uint16_le(&buff);
info->extrafield_len = gmio_adv_decode_uint16_le(&buff);
info->filename = NULL;
info->extrafield = NULL;
return gmio_zip_io_returnerr(read_len, GMIO_ERROR_OK, ptr_error);
}
size_t gmio_zip_write_data_descriptor(
struct gmio_stream *stream,
const struct gmio_zip_data_descriptor *info,
int* ptr_error)
{
const size_t bytes_len =
info->use_zip64 ?
GMIO_ZIP64_SIZE_DATA_DESCRIPTOR :
GMIO_ZIP_SIZE_DATA_DESCRIPTOR;
uint8_t bytes[GMIO_ZIP64_SIZE_DATA_DESCRIPTOR];
uint8_t* buff = bytes;
/* 4-bytes crc-32 */
gmio_adv_encode_uint32_le(info->crc32, &buff);
/* Compressed size and uncompressed size (4 or 8 bytes) */
if (info->use_zip64) {
#ifdef GMIO_HAVE_INT64_TYPE
gmio_adv_encode_uint64_le(info->compressed_size, &buff);
gmio_adv_encode_uint64_le(info->uncompressed_size, &buff);
#else
/* TODO: error code */
return gmio_zip_io_returnerr(0, GMIO_ERROR_UNKNOWN, ptr_error);
#endif
}
else {
if (info->compressed_size <= UINT32_MAX
&& info->uncompressed_size <= UINT32_MAX)
{
gmio_adv_encode_uint32_le((uint32_t)info->compressed_size, &buff);
gmio_adv_encode_uint32_le((uint32_t)info->uncompressed_size, &buff);
}
else {
/* TODO: error code */
return gmio_zip_io_returnerr(0, GMIO_ERROR_UNKNOWN, ptr_error);
}
}
/* Write to stream */
const size_t written_len =
gmio_stream_write_bytes(stream, bytes, bytes_len);
return gmio_zip_write_returnhelper(
stream, written_len, bytes_len, ptr_error);
}
size_t gmio_zip_read_central_directory_header(
struct gmio_stream *stream,
struct gmio_zip_central_directory_header *info,
int *ptr_error)
{
uint8_t bytes[GMIO_ZIP_SIZE_CENTRAL_DIRECTORY_HEADER];
const uint8_t* buff = bytes;
const size_t read_len = gmio_stream_read_bytes(stream, bytes, sizeof(bytes));
if (!gmio_zip_read_checkhelper(stream, read_len, sizeof(bytes)))
return gmio_zip_io_returnerr(read_len, GMIO_ERROR_STREAM, ptr_error);
if (gmio_adv_decode_uint32_le(&buff) != 0x02014b50) {
return gmio_zip_io_returnerr(
read_len, GMIO_ZIP_UTILS_ERROR_BAD_MAGIC, ptr_error);
}
info->version_made_by = gmio_adv_decode_uint16_le(&buff);
info->version_needed_to_extract = gmio_adv_decode_uint16_le(&buff);
info->general_purpose_flags = gmio_adv_decode_uint16_le(&buff);
info->compress_method = gmio_adv_decode_uint16_le(&buff);
/* 2-bytes last mod file time + 2-bytes last mod file date */
/* TODO: convert DOS datetime to struct tm */
const uint32_t dos_datetime = gmio_adv_decode_uint32_le(&buff);
GMIO_UNUSED(dos_datetime);
info->lastmod_datetime = NULL;
info->crc32 = gmio_adv_decode_uint32_le(&buff);
info->compressed_size = gmio_adv_decode_uint32_le(&buff);
info->uncompressed_size = gmio_adv_decode_uint32_le(&buff);
info->filename_len = gmio_adv_decode_uint16_le(&buff);
info->extrafield_len = gmio_adv_decode_uint16_le(&buff);
info->filecomment_len = gmio_adv_decode_uint16_le(&buff);
info->disk_nb_start = gmio_adv_decode_uint16_le(&buff);
info->internal_file_attrs = gmio_adv_decode_uint16_le(&buff);
info->external_file_attrs = gmio_adv_decode_uint32_le(&buff);
info->relative_offset_local_header = gmio_adv_decode_uint32_le(&buff);
info->filename = NULL;
info->extrafield = NULL;
info->filecomment = NULL;
return gmio_zip_io_returnerr(read_len, GMIO_ERROR_OK, ptr_error);
}
size_t gmio_zip_read_end_of_central_directory_record(
struct gmio_stream *stream,
struct gmio_zip_end_of_central_directory_record *info,
int *ptr_error)
{
uint8_t bytes[GMIO_ZIP_SIZE_END_OF_CENTRAL_DIRECTORY_RECORD];
const uint8_t* buff = bytes;
const size_t read_len = gmio_stream_read_bytes(stream, bytes, sizeof(bytes));
if (!gmio_zip_read_checkhelper(stream, read_len, sizeof(bytes)))
return gmio_zip_io_returnerr(read_len, GMIO_ERROR_STREAM, ptr_error);
/* 4-bytes magic */
if (gmio_adv_decode_uint32_le(&buff) != 0x06054b50) {
return gmio_zip_io_returnerr(
read_len, GMIO_ZIP_UTILS_ERROR_BAD_MAGIC, ptr_error);
}
info->disk_nb = gmio_adv_decode_uint16_le(&buff);
info->disk_nb_with_start_of_central_dir = gmio_adv_decode_uint16_le(&buff);
info->total_entry_count_in_central_dir_on_disk =
gmio_adv_decode_uint16_le(&buff);
info->total_entry_count_in_central_dir = gmio_adv_decode_uint16_le(&buff);
info->central_dir_size = gmio_adv_decode_uint32_le(&buff);
info->start_offset_central_dir_from_disk_start_nb =
gmio_adv_decode_uint32_le(&buff);
info->filecomment_len = gmio_adv_decode_uint16_le(&buff);
info->filecomment = NULL;
return gmio_zip_io_returnerr(read_len, GMIO_ERROR_OK, ptr_error);
}
size_t gmio_zip_write_local_file_header(
struct gmio_stream* stream,
const struct gmio_zip_local_file_header* info,
int* ptr_error)
{
uint8_t bytes[GMIO_ZIP_SIZE_LOCAL_FILE_HEADER];
uint8_t* buff = bytes;
const bool use_data_descriptor =
info->general_purpose_flags
& GMIO_ZIP_GENERAL_PURPOSE_FLAG_USE_DATA_DESCRIPTOR;
gmio_adv_encode_uint32_le(0x04034b50, &buff);
gmio_adv_encode_uint16_le(info->version_needed_to_extract, &buff);
gmio_adv_encode_uint16_le(info->general_purpose_flags, &buff);
gmio_adv_encode_uint16_le(info->compress_method, &buff);
/* 2-bytes last mod file time + 2-bytes last mod file date */
const struct tm* lastmod = gmio_nonnull_datetime(info->lastmod_datetime);
gmio_adv_encode_uint32_le(gmio_to_msdos_datetime(lastmod), &buff);
gmio_adv_encode_uint32_le(use_data_descriptor ? 0 : info->crc32, &buff);
gmio_adv_encode_uint32_le(
use_data_descriptor ? 0 : info->compressed_size, &buff);
gmio_adv_encode_uint32_le(
use_data_descriptor ? 0 : info->uncompressed_size, &buff);
gmio_adv_encode_uint16_le(info->filename_len, &buff);
gmio_adv_encode_uint16_le(info->extrafield_len, &buff);
/* Write to stream */
const size_t expected_written_len =
sizeof(bytes) + info->filename_len + info->extrafield_len;
size_t written_len = 0;
written_len +=
gmio_stream_write_bytes(stream, bytes, sizeof(bytes));
written_len +=
gmio_stream_write_bytes(stream, info->filename, info->filename_len);
written_len +=
gmio_stream_write_bytes(stream, info->extrafield, info->extrafield_len);
return gmio_zip_write_returnhelper(
stream, written_len, expected_written_len, ptr_error);
}
size_t gmio_zip_read_data_descriptor(
struct gmio_stream *stream,
struct gmio_zip_data_descriptor *info,
int* ptr_error)
{
uint8_t bytes[GMIO_ZIP_SIZE_DATA_DESCRIPTOR];
const uint8_t* buff = bytes;
const size_t read_len = gmio_stream_read_bytes(stream, bytes, sizeof(bytes));
if (!gmio_zip_read_checkhelper(stream, read_len, sizeof(bytes)))
return gmio_zip_io_returnerr(read_len, GMIO_ERROR_STREAM, ptr_error);
info->crc32 = gmio_adv_decode_uint32_le(&buff);
info->compressed_size = gmio_adv_decode_uint32_le(&buff);
info->uncompressed_size = gmio_adv_decode_uint32_le(&buff);
return gmio_zip_io_returnerr(read_len, GMIO_ERROR_OK, ptr_error);
}
size_t gmio_zip64_read_data_descriptor(
struct gmio_stream* stream,
struct gmio_zip_data_descriptor* info,
int* ptr_error)
{
#ifdef GMIO_HAVE_INT64_TYPE
uint8_t bytes[GMIO_ZIP64_SIZE_DATA_DESCRIPTOR];
const uint8_t* buff = bytes;
const size_t read_len = gmio_stream_read_bytes(stream, bytes, sizeof(bytes));
if (!gmio_zip_read_checkhelper(stream, read_len, sizeof(bytes)))
return gmio_zip_io_returnerr(read_len, GMIO_ERROR_STREAM, ptr_error);
info->crc32 = gmio_adv_decode_uint32_le(&buff);
info->compressed_size = gmio_adv_decode_uint64_le(&buff);
info->uncompressed_size = gmio_adv_decode_uint64_le(&buff);
return gmio_zip_io_returnerr(read_len, GMIO_ERROR_OK, ptr_error);
#else
/* TODO: error code */
return gmio_zip_io_returnerr(0, GMIO_ERROR_UNKNOWN, ptr_error);
#endif
}
size_t gmio_zip_write_central_directory_header(
struct gmio_stream *stream,
const struct gmio_zip_central_directory_header *info,
int* ptr_error)
{
uint8_t bytes[GMIO_ZIP_SIZE_CENTRAL_DIRECTORY_HEADER];
uint8_t* buff = bytes;
gmio_adv_encode_uint32_le(0x02014b50, &buff);
gmio_adv_encode_uint16_le(info->version_made_by, &buff);
gmio_adv_encode_uint16_le(info->version_needed_to_extract, &buff);
gmio_adv_encode_uint16_le(info->general_purpose_flags, &buff);
gmio_adv_encode_uint16_le(info->compress_method, &buff);
/* 2-bytes last mod file time + 2-bytes last mod file date */
const struct tm* lastmod = gmio_nonnull_datetime(info->lastmod_datetime);
gmio_adv_encode_uint32_le(gmio_to_msdos_datetime(lastmod), &buff);
gmio_adv_encode_uint32_le(info->crc32, &buff);
const uint32_t compressed_size =
info->use_zip64 ? UINT32_MAX : info->compressed_size;
const uint32_t uncompressed_size =
info->use_zip64 ? UINT32_MAX : info->uncompressed_size;
gmio_adv_encode_uint32_le(compressed_size, &buff);
gmio_adv_encode_uint32_le(uncompressed_size, &buff);
gmio_adv_encode_uint16_le(info->filename_len, &buff);
gmio_adv_encode_uint16_le(info->extrafield_len, &buff);
gmio_adv_encode_uint16_le(info->filecomment_len, &buff);
gmio_adv_encode_uint16_le(info->disk_nb_start, &buff);
gmio_adv_encode_uint16_le(info->internal_file_attrs, &buff);
gmio_adv_encode_uint32_le(info->external_file_attrs, &buff);
const uint32_t relative_offset_local_header =
info->use_zip64 ? UINT32_MAX : info->relative_offset_local_header;
gmio_adv_encode_uint32_le(relative_offset_local_header, &buff);
/* Write to stream */
const size_t expected_written_len =
sizeof(bytes)
+ info->filename_len + info->extrafield_len + info->filecomment_len;
size_t written_len = 0;
written_len +=
gmio_stream_write_bytes(stream, bytes, sizeof(bytes));
written_len +=
gmio_stream_write_bytes(stream, info->filename, info->filename_len);
written_len +=
gmio_stream_write_bytes(stream, info->extrafield, info->extrafield_len);
written_len +=
gmio_stream_write_bytes(stream, info->filecomment, info->filecomment_len);
return gmio_zip_write_returnhelper(
stream, written_len, expected_written_len, ptr_error);
}
size_t gmio_zip64_write_extrafield_extended_info(
uint8_t *buff,
size_t buff_capacity,
const struct gmio_zip64_extrablock_extended_info *info,
int* ptr_error)
{
if (buff_capacity < GMIO_ZIP64_SIZE_EXTRAFIELD_EXTENDED_INFO) {
return gmio_zip_io_returnerr(
0, GMIO_ERROR_INVALID_MEMBLOCK_SIZE, ptr_error);
}
#ifdef GMIO_HAVE_INT64_TYPE
/* Tag */
gmio_adv_encode_uint16_le(0x0001, &buff);
/* Size of the "extra" block */
gmio_adv_encode_uint16_le(GMIO_ZIP64_SIZE_EXTRAFIELD_EXTENDED_INFO - 2, &buff);
gmio_adv_encode_uint64_le(info->uncompressed_size, &buff);
gmio_adv_encode_uint64_le(info->compressed_size, &buff);
gmio_adv_encode_uint64_le(info->relative_offset_local_header, &buff);
gmio_adv_encode_uint32_le(info->disk_nb_start, &buff);
return gmio_zip_io_returnerr(
GMIO_ZIP64_SIZE_EXTRAFIELD_EXTENDED_INFO,
GMIO_ERROR_OK,
ptr_error);
#else
/* TODO: error code */
return gmio_zip_io_returnerr(0, GMIO_ERROR_UNKNOWN, error);
#endif
}
size_t gmio_zip_write_end_of_central_directory_record(
struct gmio_stream *stream,
const struct gmio_zip_end_of_central_directory_record *info,
int* ptr_error)
{
uint8_t bytes[GMIO_ZIP_SIZE_END_OF_CENTRAL_DIRECTORY_RECORD];
uint8_t* buff = bytes;
/* 4-bytes magic number 0x06054b50 */
gmio_adv_encode_uint32_le(0x06054b50, &buff);
/* 2-bytes number of this disk */
const uint16_t disk_nb = info->use_zip64 ? UINT16_MAX : info->disk_nb;
gmio_adv_encode_uint16_le(disk_nb, &buff);
/* 2-bytes number of the disk with the start of the central directory */
const uint16_t disk_nb_with_start_of_central_dir =
info->use_zip64 ?
UINT16_MAX :
info->disk_nb_with_start_of_central_dir;
gmio_adv_encode_uint16_le(disk_nb_with_start_of_central_dir, &buff);
/* 2-bytes total number of entries in the central directory on this disk */
const uint16_t total_entry_count_in_central_dir_on_disk =
info->use_zip64 ?
UINT16_MAX :
info->total_entry_count_in_central_dir_on_disk;
gmio_adv_encode_uint16_le(total_entry_count_in_central_dir_on_disk, &buff);
/* 2-bytes total number of entries in the central directory */
const uint16_t total_entry_count_in_central_dir =
info->use_zip64 ?
UINT16_MAX :
info->total_entry_count_in_central_dir;
gmio_adv_encode_uint16_le(total_entry_count_in_central_dir, &buff);
/* 4-bytes size of the central directory */
const uint32_t central_dir_size =
info->use_zip64 ? UINT32_MAX : info->central_dir_size;
gmio_adv_encode_uint32_le(central_dir_size, &buff);
/* 4-bytes offset of start of central directory with respect to the starting
* disk number */
const uint32_t start_offset_central_dir_from_disk_start_nb =
info->use_zip64 ?
UINT32_MAX :
info->start_offset_central_dir_from_disk_start_nb;
gmio_adv_encode_uint32_le(start_offset_central_dir_from_disk_start_nb, &buff);
/* 2-bytes .ZIP file comment length */
gmio_adv_encode_uint16_le(info->filecomment_len, &buff);
/* Write to stream */
const size_t expected_written_len =
sizeof(bytes) + info->filecomment_len;
size_t written_len = 0;
written_len +=
gmio_stream_write_bytes(stream, bytes, sizeof(bytes));
written_len +=
gmio_stream_write_bytes(stream, info->filecomment, info->filecomment_len);
return gmio_zip_write_returnhelper(
stream, written_len, expected_written_len, ptr_error);
}