/* Copyright 2018 the unarr project authors (see AUTHORS file). License: LGPLv3 */ #include "_7z.h" #ifdef HAVE_7Z static void *gSzAlloc_Alloc(ISzAllocPtr self, size_t size) { (void)self; return malloc(size); } static void gSzAlloc_Free(ISzAllocPtr self, void *ptr) { (void)self; free(ptr); } static ISzAlloc gSzAlloc = { gSzAlloc_Alloc, gSzAlloc_Free }; static SRes CSeekStream_Read(const ISeekInStream *p, void *data, size_t *size) { struct CSeekStream *stm = (struct CSeekStream *) p; *size = ar_read(stm->stream, data, *size); return SZ_OK; } static SRes CSeekStream_Seek(const ISeekInStream *p, Int64 *pos, ESzSeek origin) { struct CSeekStream *stm = (struct CSeekStream *) p; if (!ar_seek(stm->stream, *pos, (int)origin)) return SZ_ERROR_FAIL; *pos = ar_tell(stm->stream); return SZ_OK; } static void CSeekStream_CreateVTable(struct CSeekStream *in_stream, ar_stream *stream) { in_stream->super.Read = CSeekStream_Read; in_stream->super.Seek = CSeekStream_Seek; in_stream->stream = stream; } #ifndef USE_7Z_CRC32 UInt32 Z7_FASTCALL CrcCalc(const void *data, size_t size) { return ar_crc32(0, data, size); } #endif static void _7z_close(ar_archive *ar) { ar_archive_7z *_7z = (ar_archive_7z *)ar; free(_7z->entry_name); SzArEx_Free(&_7z->data, &gSzAlloc); IAlloc_Free(&gSzAlloc, _7z->uncomp.buffer); IAlloc_Free(&gSzAlloc, _7z->look_stream.buf); } static const char *_7z_get_name(ar_archive *ar, bool raw); static bool _7z_parse_entry(ar_archive *ar, off64_t offset) { ar_archive_7z *_7z = (ar_archive_7z *)ar; //const CSzFileItem *item = _7z->data.db.PackPositions + offset; if (offset < 0 || offset > _7z->data.NumFiles) { warn("Offsets must be between 0 and %u", _7z->data.NumFiles); return false; } if (offset == _7z->data.NumFiles) { ar->at_eof = true; return false; } ar->entry_offset = offset; ar->entry_offset_next = offset + 1; ar->entry_size_uncompressed = (size_t)SzArEx_GetFileSize(&_7z->data, offset); ar->entry_filetime = SzBitWithVals_Check(&_7z->data.MTime, offset) ? (time64_t)(_7z->data.MTime.Vals[offset].Low | ((time64_t)_7z->data.MTime.Vals[offset].High << 32)) : 0; free(_7z->entry_name); _7z->entry_name = NULL; _7z->uncomp.initialized = false; if (SzArEx_IsDir(&_7z->data, offset)) { log("Skipping directory entry \"%s\"", _7z_get_name(ar, false)); return _7z_parse_entry(ar, offset + 1); } return true; } static char *SzArEx_GetFileNameUtf8(const CSzArEx *p, UInt32 fileIndex) { size_t len = p->FileNameOffsets[fileIndex + 1] - p->FileNameOffsets[fileIndex]; const Byte *src = p->FileNames + p->FileNameOffsets[fileIndex] * 2; const Byte *srcEnd = src + len * 2; size_t size = len * 3; char *str, *out; if (size == (size_t)-1) return NULL; str = malloc(size + 1); if (!str) return NULL; for (out = str; src < srcEnd - 1; src += 2) { out += ar_conv_rune_to_utf8(src[0] | src[1] << 8, out, str + size - out); } *out = '\0'; return str; } static const char *_7z_get_name(ar_archive *ar, bool raw) { if (raw) return NULL; ar_archive_7z *_7z = (ar_archive_7z *)ar; if (!_7z->entry_name && ar->entry_offset_next && !ar->at_eof) { _7z->entry_name = SzArEx_GetFileNameUtf8(&_7z->data, (UInt32)ar->entry_offset); /* normalize path separators */ if (_7z->entry_name) { char *p = _7z->entry_name; while ((p = strchr(p, '\\')) != NULL) { *p = '/'; } } } return _7z->entry_name; } static bool _7z_uncompress(ar_archive *ar, void *buffer, size_t buffer_size) { ar_archive_7z *_7z = (ar_archive_7z *)ar; struct ar_archive_7z_uncomp *uncomp = &_7z->uncomp; if (!uncomp->initialized) { /* TODO: this uncompresses all data for solid compressions */ SRes res = SzArEx_Extract(&_7z->data, &_7z->look_stream.vt, (UInt32)ar->entry_offset, &uncomp->folder_index, &uncomp->buffer, &uncomp->buffer_size, &uncomp->offset, &uncomp->bytes_left, &gSzAlloc, &gSzAlloc); if (res != SZ_OK) { warn("Failed to extract file at index %" PRIi64 " (failed with error %d)", ar->entry_offset, res); return false; } if (uncomp->bytes_left != ar->entry_size_uncompressed) { warn("Uncompressed sizes don't match (%" PRIuPTR " != %" PRIuPTR ")", uncomp->bytes_left, ar->entry_size_uncompressed); return false; } uncomp->initialized = true; } if (buffer_size > uncomp->bytes_left) { warn("Requesting too much data (%" PRIuPTR " < %" PRIuPTR ")", uncomp->bytes_left, buffer_size); return false; } memcpy(buffer, uncomp->buffer + uncomp->offset + ar->entry_size_uncompressed - uncomp->bytes_left, buffer_size); uncomp->bytes_left -= buffer_size; return true; } ar_archive *ar_open_7z_archive(ar_stream *stream) { ar_archive *ar; ar_archive_7z *_7z; SRes res; if (!ar_seek(stream, 0, SEEK_SET)) return NULL; ar = ar_open_archive(stream, sizeof(ar_archive_7z), _7z_close, _7z_parse_entry, _7z_get_name, _7z_uncompress, NULL, 0); if (!ar) return NULL; _7z = (ar_archive_7z *)ar; CSeekStream_CreateVTable(&_7z->in_stream, stream); LookToRead2_CreateVTable(&_7z->look_stream, False); _7z->look_stream.realStream = &_7z->in_stream.super; _7z->look_stream.buf = ISzAlloc_Alloc(&gSzAlloc, 1 << 18); _7z->look_stream.bufSize = 1 << 18; LookToRead2_INIT(&_7z->look_stream); #ifdef USE_7Z_CRC32 CrcGenerateTable(); #endif SzArEx_Init(&_7z->data); res = SzArEx_Open(&_7z->data, &_7z->look_stream.vt, &gSzAlloc, &gSzAlloc); if (res != SZ_OK) { if (res != SZ_ERROR_NO_ARCHIVE) warn("Invalid 7z archive (failed with error %d)", res); ISzAlloc_Free(&gSzAlloc, _7z->look_stream.buf); free(ar); return NULL; } return ar; } #else ar_archive *ar_open_7z_archive(ar_stream *stream) { (void)stream; warn("7z support requires 7z SDK (define HAVE_7Z)"); return NULL; } #endif