testdisk/src/file_gz.c
2022-11-16 07:40:25 +01:00

419 lines
12 KiB
C

/*
File: file_gz.c
Copyright (C) 2006-2008 Christophe GRENIER <grenier@cgsecurity.org>
This software is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write the Free Software Foundation, Inc., 51
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#if defined(DISABLED_FOR_FRAMAC)
#undef HAVE_LIBZ
#undef HAVE_ZLIB_H
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#include <stdio.h>
#include "types.h"
#include "file_gz.h"
#ifdef HAVE_ZLIB_H
#include <zlib.h>
#endif
#if !defined(SINGLE_FORMAT) || defined(SINGLE_FORMAT_gz)
#include "filegen.h"
#include "common.h"
/*@ requires valid_register_header_check(file_stat); */
static void register_header_check_gz(file_stat_t *file_stat);
#ifndef SINGLE_FORMAT
extern const file_hint_t file_hint_doc;
#endif
const file_hint_t file_hint_gz= {
.extension="gz",
.description="gzip compressed data",
.max_filesize=PHOTOREC_MAX_FILE_SIZE,
.recover=1,
.enable_by_default=1,
.register_header_check=&register_header_check_gz
};
struct gzip_header
{
uint16_t id;
uint8_t compression_method;
uint8_t flags;
uint32_t mtime;
uint8_t extra_flags;
uint8_t os;
} __attribute__ ((gcc_struct, __packed__));
/* flags:
bit 0 FTEXT
bit 1 FHCRC
bit 2 FEXTRA
bit 3 FNAME
bit 4 FCOMMENT
bit 5 reserved
bit 6 reserved
bit 7 reserved
*/
#define GZ_FTEXT 1
#define GZ_FHCRC 2
#define GZ_FEXTRA 4
#define GZ_FNAME 8
#define GZ_FCOMMENT 0x10
/*@
@ requires file_recovery->file_rename==&file_rename_gz;
@ requires valid_file_rename_param(file_recovery);
@ ensures valid_file_rename_result(file_recovery);
@*/
static void file_rename_gz(file_recovery_t *file_recovery)
{
unsigned char buffer[512];
FILE *file;
int buffer_size;
if((file=fopen(file_recovery->filename, "rb"))==NULL)
return;
buffer_size=fread(buffer, 1, sizeof(buffer), file);
fclose(file);
if(buffer_size<10)
return;
/*@ assert \initialized(buffer+(0..10)); */
if(!(buffer[0]==0x1F && buffer[1]==0x8B && buffer[2]==0x08 && (buffer[3]&0xe0)==0))
return ;
{
const unsigned int flags=buffer[3];
int off=10;
if((flags&GZ_FEXTRA)!=0)
{
if(buffer_size<12)
return;
/*@ assert \initialized(buffer + (0 .. 12)); */
off+=2;
off+=buffer[10]|(buffer[11]<<8);
}
if((flags&GZ_FNAME)!=0)
{
file_rename(file_recovery, buffer, buffer_size, off, NULL, 1);
}
}
}
#if defined(HAVE_ZLIB_H) && defined(HAVE_LIBZ)
/*@ assigns \nothing; */
static void file_check_bgzf(file_recovery_t *file_recovery)
{
}
/*@
@ requires buffer_size >= sizeof(struct gzip_header);
@ requires \valid_read(buffer+(0..buffer_size-1));
@ requires \valid_read(buffer_uncompr + (0 .. 4-1));
@ requires \valid(file_recovery_new);
@ requires separation: \separated(buffer+(..), file_recovery_new);
@ requires valid_file_recovery(file_recovery_new);
@ ensures \result == 1;
@ ensures valid_header_check_result(\result, file_recovery_new);
@ assigns *file_recovery_new;
@*/
static int header_check_bgzf(const unsigned char *buffer, const unsigned char *buffer_uncompr, const unsigned int buffer_size, file_recovery_t *file_recovery_new)
{
const struct gzip_header *gz=(const struct gzip_header *)buffer;
reset_file_recovery(file_recovery_new);
file_recovery_new->min_filesize=22;
file_recovery_new->time=le32(gz->mtime);
file_recovery_new->file_rename=&file_rename_gz;
file_recovery_new->file_check=&file_check_bgzf;
if(memcmp(buffer_uncompr, "BAI\1", 4)==0)
{
/* https://github.com/samtools/hts-specs SAM/BAM and related high-throughput sequencing file formats */
file_recovery_new->extension="bai";
/*@ assert valid_file_recovery(file_recovery_new); */
return 1;
}
if(memcmp(buffer_uncompr, "BAM\1", 4)==0)
{
/* https://github.com/samtools/hts-specs SAM/BAM and related high-throughput sequencing file formats */
file_recovery_new->extension="bam";
/*@ assert valid_file_recovery(file_recovery_new); */
return 1;
}
if(memcmp(buffer_uncompr, "CSI\1", 4)==0)
{
/* https://github.com/samtools/hts-specs SAM/BAM and related high-throughput sequencing file formats */
file_recovery_new->extension="csi";
/*@ assert valid_file_recovery(file_recovery_new); */
return 1;
}
file_recovery_new->extension="bgz";
/*@ assert valid_file_recovery(file_recovery_new); */
return 1;
}
#endif
/*@
@ requires buffer_size >= 16;
@ requires separation: \separated(&file_hint_gz, buffer+(..), file_recovery, file_recovery_new);
@ requires valid_header_check_param(buffer, buffer_size, safe_header_only, file_recovery, file_recovery_new);
@ ensures valid_header_check_result(\result, file_recovery_new);
@*/
static int header_check_gz(const unsigned char *buffer, const unsigned int buffer_size, const unsigned int safe_header_only, const file_recovery_t *file_recovery, file_recovery_t *file_recovery_new)
{
unsigned int off=10;
const unsigned int flags=buffer[3];
const struct gzip_header *gz=(const struct gzip_header *)buffer;
int bgzf=0;
/* gzip file format:
* a 10-byte header, containing a magic number, a version number and a timestamp
* optional extra headers, such as the original file name,
* a body, containing a deflate-compressed payload
* a CRC-32 checksum
* the length (32 bits) of the original uncompressed data
*/
/* gzip, deflate */
if(!(buffer[0]==0x1F && buffer[1]==0x8B && buffer[2]==0x08 && (buffer[3]&0xe0)==0))
return 0;
/*
* 4,5,6,7: mtime
* 8: xfl/extra flags
* 9: OS 3 - Unix, 7 - Macintosh, 11 - NTFS filesystem (NT)
*/
if((flags&GZ_FEXTRA)!=0)
{
off+=2;
off+=buffer[10]|(buffer[11]<<8);
if(buffer[12]=='B' && buffer[13]=='C' && buffer[14]==2 && buffer[15]==0)
bgzf=1;
}
if((flags&GZ_FNAME)!=0)
{
for(; off<buffer_size && buffer[off]!='\0'; off++)
{
}
off++;
}
if((flags&GZ_FCOMMENT)!=0)
{
for(; off<buffer_size && buffer[off]!='\0'; off++)
{
}
off++;
}
if((flags&GZ_FHCRC)!=0)
{
off+=2;
}
if(off >= 512 || off >= buffer_size)
return 0;
/*@ assert off < 512; */
/*@ assert off < buffer_size ; */
#if defined(HAVE_ZLIB_H) && defined(HAVE_LIBZ)
{
static const unsigned char schematic_header[12]={ 0x0a, 0x00, 0x09,
'S', 'c', 'h', 'e', 'm', 'a', 't', 'i', 'c'};
static const unsigned char tar_header_posix[8] = { 'u','s','t','a','r',' ',' ',0x00};
const unsigned char *buffer_compr=buffer+off;
unsigned char buffer_uncompr[4096];
const unsigned int uncomprLen=sizeof(buffer_uncompr)-1;
const unsigned int bs=td_max(512U,file_recovery_new->blocksize);
/*@ assert bs >=512; */
const unsigned int comprLen=td_min(buffer_size,bs)-off;
/*@ assert comprLen > 0; */
int err;
z_stream d_stream; /* decompression stream */
d_stream.zalloc = (alloc_func)0;
d_stream.zfree = (free_func)0;
d_stream.opaque = (voidpf)0;
d_stream.next_in = (Bytef*)buffer_compr;
d_stream.avail_in = 0;
d_stream.next_out = buffer_uncompr;
err = inflateInit2(&d_stream, -MAX_WBITS);
if(err!=Z_OK)
return 0;
while (d_stream.total_out < uncomprLen && d_stream.total_in < comprLen) {
d_stream.avail_in = d_stream.avail_out = 1; /* force small buffers */
err = inflate(&d_stream, Z_NO_FLUSH);
if (err == Z_STREAM_END) break;
if(err!=Z_OK)
{
/* Decompression has failed, free ressources */
inflateEnd(&d_stream);
return 0;
}
}
err = inflateEnd(&d_stream);
if(err!=Z_OK)
return 0;
/* Probably too small to be a file */
if(d_stream.total_out < 16)
return 0;
#ifndef SINGLE_FORMAT
if(file_recovery->file_stat!=NULL &&
file_recovery->file_stat->file_hint==&file_hint_doc)
{
if(header_ignored_adv(file_recovery, file_recovery_new)==0)
return 0;
}
#endif
if(file_recovery->file_check==&file_check_bgzf)
{
/*@ assert \valid_function(file_recovery->file_check); */
header_ignored(file_recovery_new);
return 0;
}
buffer_uncompr[d_stream.total_out]='\0';
if(bgzf!=0)
{
return header_check_bgzf(buffer, buffer_uncompr, d_stream.total_out, file_recovery_new);
}
reset_file_recovery(file_recovery_new);
file_recovery_new->min_filesize=22;
file_recovery_new->time=le32(gz->mtime);
file_recovery_new->file_rename=&file_rename_gz;
if(d_stream.avail_in==0 && d_stream.total_in < comprLen && d_stream.total_out < uncomprLen)
{
/* an 8-byte footer, containing a CRC-32 checksum and
* the length of the original uncompressed data, modulo 2^32
*/
file_recovery_new->calculated_file_size=off+d_stream.total_in+8;
file_recovery_new->data_check=&data_check_size;
file_recovery_new->file_check=&file_check_size;
}
if(memcmp(buffer_uncompr, "PVP ", 4)==0)
{
/* php Video Pro */
file_recovery_new->extension="pvp";
return 1;
}
if(memcmp(buffer_uncompr, "<?xml version=\"1.0\" standalone=\"no\"?>\n<xournal", 46)==0)
{
/* Xournal, http://xournal.sourceforge.net/ */
file_recovery_new->extension="xoj";
return 1;
}
if( memcmp(buffer_uncompr, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<Ableton", 0x30)==0 ||
memcmp(buffer_uncompr, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Ableton", 0x2f)==0)
{
/* Ableton Liveset */
file_recovery_new->extension="als";
return 1;
}
if(memcmp(buffer_uncompr, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<PremiereData", 0x34)==0)
{
/* Adobe Premiere */
file_recovery_new->extension="prproj";
return 1;
}
if(memcmp(buffer_uncompr, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<gnc-v2", 47)==0)
{
/* GnuCash, http://gnucash.org/ */
file_recovery_new->extension="gnucash";
return 1;
}
if(strstr((const char*)&buffer_uncompr, "<!DOCTYPE KMYMONEY-FILE>")!=NULL)
{
file_recovery_new->extension="kmy";
return 1;
}
#ifndef DJGPP
if(memcmp(buffer_uncompr, "RDX2", 4)==0)
{
/* R - language and environment for statistical computing and graphics */
file_recovery_new->extension="RData";
return 1;
}
if(memcmp(buffer_uncompr, "<?xml version=", 14) == 0)
{
file_recovery_new->extension="xml.gz";
return 1;
}
if(memcmp(buffer_uncompr, schematic_header, sizeof(schematic_header))==0)
{
/* Minecraft Schematic File */
file_recovery_new->extension="schematic";
return 1;
}
{
unsigned int i;
for(i=0; i<d_stream.total_out && i< 256; i++)
{
if(buffer_uncompr[i]=='<')
{
if(strncasecmp((const char*)&buffer_uncompr[i], "<html", 5)==0)
{
file_recovery_new->extension="html.gz";
return 1;
}
}
}
}
#endif
if(d_stream.total_out>0x110 &&
memcmp(&buffer_uncompr[0x101],tar_header_posix,sizeof(tar_header_posix))==0)
{
#ifdef DJGPP
file_recovery_new->extension="tgz";
#else
file_recovery_new->extension="tar.gz";
#endif
return 1;
}
}
#else
#ifndef SINGLE_FORMAT
if(file_recovery->file_stat!=NULL &&
file_recovery->file_stat->file_hint==&file_hint_doc)
{
if(header_ignored_adv(file_recovery, file_recovery_new)==0)
return 0;
}
#endif
reset_file_recovery(file_recovery_new);
file_recovery_new->min_filesize=22;
file_recovery_new->time=le32(gz->mtime);
file_recovery_new->file_rename=&file_rename_gz;
#endif
file_recovery_new->extension=file_hint_gz.extension;
return 1;
}
static void register_header_check_gz(file_stat_t *file_stat)
{
static const unsigned char gz_header_magic[3]= {0x1F, 0x8B, 0x08};
register_header_check(0, gz_header_magic,sizeof(gz_header_magic), &header_check_gz, file_stat);
}
#endif
const char*td_zlib_version(void)
{
#if defined(HAVE_ZLIB_H) && defined(HAVE_LIBZ)
return ZLIB_VERSION;
#else
return "none";
#endif
}