diff --git a/src/Makefile.am b/src/Makefile.am index c059fdee..a58b0081 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -50,6 +50,7 @@ testdisk_ncurses_H = addpart.h addpartn.h adv.h askloc.h chgarch.h chgarchn.h ch testdisk_SOURCES = $(base_C) $(base_H) $(fs_C) $(fs_H) $(testdisk_ncurses_C) $(testdisk_ncurses_H) dir.c dir.h dir_common.h exfat_dir.c exfat_dir.h ext2_dir.c ext2_dir.h ext2_inc.h fat_dir.c fat_dir.h ntfs_dir.c ntfs_dir.h ntfs_inc.h partgptw.c rfs_dir.c rfs_dir.h $(ICON_TESTDISK) next.c next.h file_C = filegen.c \ + image_filter.c \ file_list.c \ file_1cd.c \ file_3dm.c \ @@ -402,7 +403,7 @@ file_C = filegen.c \ file_zpr.c \ utfsize.c -file_H = ext2.h hfsp_struct.h filegen.h file_doc.h file_jpg.h file_gz.h file_riff.h file_sp3.h file_tar.h file_tiff.h luks_struct.h ntfs_struct.h ole.h pe.h suspend.h utfsize.h xfs_struct.h +file_H = ext2.h hfsp_struct.h filegen.h image_filter.h file_doc.h file_jpg.h file_gz.h file_riff.h file_sp3.h file_tar.h file_tiff.h luks_struct.h ntfs_struct.h ole.h pe.h suspend.h utfsize.h xfs_struct.h photorec_C = photorec.c phcfg.c addpart.c chgarch.c chgtype.c dir.c exfatp.c ext2grp.c ext2_dir.c ext2p.c fat_dir.c fatp.c file_found.c geometry.c ntfs_dir.c ntfsp.c pdisksel.c poptions.c sessionp.c dfxml.c partgptro.c json_log.c diff --git a/src/file_jpg.c b/src/file_jpg.c index 0c321015..0c2611a6 100644 --- a/src/file_jpg.c +++ b/src/file_jpg.c @@ -60,6 +60,7 @@ #endif #include "file_tiff.h" #include "setdate.h" +#include "image_filter.h" #if defined(__FRAMAC__) #include "__fc_builtin.h" #endif @@ -90,6 +91,7 @@ const file_hint_t file_hint_jpg= { .max_filesize=50*1024*1024, .recover=1, .enable_by_default=1, + .is_image=1, .register_header_check=®ister_header_check_jpg }; @@ -863,6 +865,7 @@ static time_t jpg_get_date(const unsigned char *buffer, const unsigned int buffe return 0; } +static int jpg_maches_image_filtering(const unsigned char *buffer, const unsigned int buffer_size, file_recovery_t *file_recovery); /*@ @ requires PHOTOREC_MAX_BLOCKSIZE >= buffer_size >= 10; @@ -1052,6 +1055,8 @@ static int header_check_jpg(const unsigned char *buffer, const unsigned int buff file_recovery_new->time=jpg_time; file_recovery_new->extension=file_hint_jpg.extension; file_recovery_new->file_check=&file_check_jpg; + file_recovery_new->file_check_presave=&jpg_maches_image_filtering; + file_recovery_new->image_filter=file_recovery->image_filter; if(buffer_size >= 4) file_recovery_new->data_check=&data_check_jpg; /*@ assert valid_read_string(file_recovery_new->extension); */ @@ -1926,6 +1931,58 @@ struct sof_header #endif } __attribute__ ((gcc_struct, __packed__)); +static int jpg_maches_image_filtering(const unsigned char *buffer, const unsigned int buffer_size, file_recovery_t *file_recovery) +{ + if(!file_recovery->image_filter) + return 1; + + if(buffer_size < 20) + return 1; + + if(!(buffer[0] == 0xff && buffer[1] == 0xd8 && buffer[2] == 0xff)) + return 1; + + const unsigned char *check_buffer = buffer; + unsigned int check_size = buffer_size; + + uint64_t estimated_file_size = 0; + if(check_size > 2) { + for(unsigned int i = check_size - 2; i > 2; i--) + { + if(check_buffer[i] == 0xff && check_buffer[i+1] == 0xd9) + { + estimated_file_size = i + 2; + break; + } + } + } + + // Apply file size filter if we found end marker + if(estimated_file_size > 0 && file_recovery->image_filter && should_skip_image_by_filesize(file_recovery->image_filter, estimated_file_size)) + return 0; + + // Check dimensions in the buffer we're examining + if(check_size > 10) + { + for(unsigned int i = 0; i < check_size - 10; i++) + { + if(check_buffer[i] == 0xff && check_buffer[i+1] == 0xc0) + { + if(i + 10 < check_size) + { + const struct sof_header *sof = (const struct sof_header *)&check_buffer[i]; + const unsigned int width = be16(sof->width); + const unsigned int height = be16(sof->height); + if(file_recovery->image_filter && should_skip_image_by_dimensions(file_recovery->image_filter, width, height)) + return 0; + } + break; + } + } + } + return 1; +} + /*@ @ requires \valid_read(buffer + (0..buffer_size-1)); @ terminates \true; @@ -2299,6 +2356,10 @@ static int jpg_check_app1(file_recovery_t *file_recovery, const unsigned int ext return 1; if(thumb_offset+thumb_size > nbytes) return 1; + + if (file_recovery->image_filter && !jpg_maches_image_filtering((const unsigned char *)(buffer + thumb_offset), thumb_size, file_recovery)) + return 1; + /*@ assert thumb_offset + thumb_size <= nbytes; */ /*@ assert 0 < thumb_size; */ /*@ assert thumb_offset < nbytes; */ @@ -2457,6 +2518,14 @@ static void file_check_jpg(file_recovery_t *file_recovery) #endif if(file_recovery->offset_error!=0) return ; +// we could simply disable jpg thumb extraction when image filtering is active +// but for now they're passed through jpg_maches_image_filtering like normal photos +// const unsigned int extract_thumb = file_recovery->image_filter ? 0 : 1; +// #ifdef DEBUG_JPEG +// if(file_recovery->image_filter) +// log_info("skipping thumbnail extraction because image filtering is enabled\n"); +// #endif +// thumb_offset=jpg_check_structure(file_recovery, extract_thumb); thumb_offset=jpg_check_structure(file_recovery, 1); #ifdef DEBUG_JPEG log_info("jpg_check_structure error at %llu\n", (long long unsigned)file_recovery->offset_error); @@ -2489,11 +2558,22 @@ static void file_check_jpg(file_recovery_t *file_recovery) #endif if(file_recovery->offset_error!=0) return ; + #if defined(HAVE_LIBJPEG) && defined(HAVE_JPEGLIB_H) jpg_check_picture(file_recovery); #else file_recovery->file_size=file_recovery->calculated_file_size; #endif + if(file_recovery->image_filter && file_recovery->handle) { + fseek(file_recovery->handle, 0, SEEK_SET); + char buffer[512]; + if(fread(buffer, 1, sizeof(buffer), file_recovery->handle) > 0) { + unsigned int width = 0, height = 0; + jpg_get_size((unsigned char*)buffer, sizeof(buffer), &height, &width); + file_recovery->image_data.width = width; + file_recovery->image_data.height = height; + } + } #if 0 /* FIXME REMOVE ME */ if(file_recovery->offset_error!=0) @@ -2630,6 +2710,10 @@ static data_check_t data_check_jpg(const unsigned char *buffer, const unsigned i /* Skip the SOI */ if(file_recovery->calculated_file_size<2) file_recovery->calculated_file_size=2; + + if (file_recovery->image_filter && !jpg_maches_image_filtering(buffer, buffer_size, file_recovery)) + return DC_STOP; + /*@ assert file_recovery->calculated_file_size >= 2; */ /*@ assert file_recovery->data_check == &data_check_jpg; */ /* Search SOS */ diff --git a/src/file_png.c b/src/file_png.c index 10a349f5..7a027881 100644 --- a/src/file_png.c +++ b/src/file_png.c @@ -38,6 +38,7 @@ #include "types.h" #include "common.h" #include "filegen.h" +#include "image_filter.h" extern const file_hint_t file_hint_doc; @@ -50,6 +51,7 @@ const file_hint_t file_hint_png= { .max_filesize=PHOTOREC_MAX_FILE_SIZE, .recover=1, .enable_by_default=1, + .is_image=1, .register_header_check=®ister_header_check_png }; @@ -167,6 +169,10 @@ static void file_check_png(file_recovery_t *fr) fr->file_size=0; return ; } + if(fr->image_filter) { + fr->image_data.width = be32(ihdr->width); + fr->image_data.height = be32(ihdr->height); + } } } } @@ -313,6 +319,49 @@ static int header_check_mng(const unsigned char *buffer, const unsigned int buff return 1; } +static int png_maches_image_filtering(const unsigned char *buffer, const unsigned int buffer_size, file_recovery_t *file_recovery) +{ + if(!file_recovery->image_filter) + return 1; + + if(buffer_size < 24) + return 1; + + if(!(buffer[0] == 0x89 && buffer[1] == 'P' && buffer[2] == 'N' && buffer[3] == 'G')) + return 1; + + const unsigned char *check_buffer = buffer; + unsigned int check_size = buffer_size; + + // Estimate file size by finding PNG IEND chunk + uint64_t estimated_file_size = 0; + if(check_size > 12) { + for(unsigned int i = check_size - 12; i > 8; i--) + { + if(check_buffer[i] == 'I' && check_buffer[i+1] == 'E' && check_buffer[i+2] == 'N' && check_buffer[i+3] == 'D') + { + estimated_file_size = i + 8; // IEND chunk + 4-byte CRC + break; + } + } + } + + // Apply file size filter if we found IEND + if(estimated_file_size > 0 && file_recovery->image_filter && should_skip_image_by_filesize(file_recovery->image_filter, estimated_file_size)) + return 0; + + if(check_size >= 16 + sizeof(struct png_ihdr)) + { + const struct png_ihdr *ihdr = (const struct png_ihdr *)&check_buffer[16]; + const unsigned int width = be32(ihdr->width); + const unsigned int height = be32(ihdr->height); + if(should_skip_image_by_dimensions(file_recovery->image_filter, width, height)) + return 0; + } + + return 1; +} + /*@ @ requires buffer_size >= 16 + sizeof(struct png_ihdr); @ requires separation: \separated(&file_hint_png, buffer+(..), file_recovery, file_recovery_new); @@ -350,6 +399,8 @@ static int header_check_png(const unsigned char *buffer, const unsigned int buff file_recovery_new->calculated_file_size=8; file_recovery_new->data_check=&data_check_png; file_recovery_new->file_check=&file_check_png; + file_recovery_new->file_check_presave=&png_maches_image_filtering; + file_recovery_new->image_filter=file_recovery->image_filter; /*@ assert valid_file_recovery(file_recovery_new); */ return 1; } diff --git a/src/filegen.c b/src/filegen.c index 86b1ffec..37275811 100644 --- a/src/filegen.c +++ b/src/filegen.c @@ -37,6 +37,7 @@ #endif #include #include +#include #include #ifdef HAVE_TIME_H #include @@ -491,6 +492,8 @@ void file_check_size_max(file_recovery_t *file_recovery) void reset_file_recovery(file_recovery_t *file_recovery) { + // Save image_filter - it's session config, not per-file state + const image_size_filter_t *saved_filter = file_recovery->image_filter; file_recovery->filename[0]='\0'; file_recovery->time=0; file_recovery->file_stat=NULL; @@ -506,6 +509,7 @@ void reset_file_recovery(file_recovery_t *file_recovery) file_recovery->data_check=NULL; file_recovery->file_check=NULL; file_recovery->file_rename=NULL; + file_recovery->file_check_presave=NULL; file_recovery->offset_error=0; file_recovery->offset_ok=0; file_recovery->checkpoint_status=0; @@ -513,6 +517,10 @@ void reset_file_recovery(file_recovery_t *file_recovery) file_recovery->flags=0; file_recovery->extra=0; file_recovery->data_check_tmp=0; + file_recovery->image_data.width=0; + file_recovery->image_data.height=0; + free_memory_buffer(file_recovery); + file_recovery->image_filter=saved_filter; } file_stat_t * init_file_stats(file_enable_t *files_enable) @@ -1201,3 +1209,111 @@ time_t get_time_from_YYYYMMDD_HHMMSS(const char *date_asc) tm_time.tm_isdst = -1; /* unknown daylight saving time */ return mktime(&tm_time); } + +static uint64_t calculate_max_buffer_size(file_recovery_t *file_recovery) +{ + uint64_t max_size = 0; + + if(file_recovery->file_stat && file_recovery->file_stat->file_hint && + file_recovery->file_stat->file_hint->max_filesize > 0) { + max_size = file_recovery->file_stat->file_hint->max_filesize; + } + + if(file_recovery->image_filter && file_recovery->image_filter->max_file_size > 0) { + if(max_size == 0 || file_recovery->image_filter->max_file_size < max_size) { + max_size = file_recovery->image_filter->max_file_size; + } + } + + // Limit memory buffer to reasonable size (100MB) to avoid allocation failures + const uint64_t MAX_MEMORY_BUFFER = 100 * 1024 * 1024; // 100MB + if(max_size > MAX_MEMORY_BUFFER) { + max_size = MAX_MEMORY_BUFFER; + } + + return max_size; +} + +int init_memory_buffer(file_recovery_t *file_recovery) +{ + if(!file_recovery->use_memory_buffering) { + return 0; + } + + file_recovery->buffer_max_size = calculate_max_buffer_size(file_recovery); + if(file_recovery->buffer_max_size == 0) { + return 0; + } + + file_recovery->memory_buffer = (unsigned char*)calloc(file_recovery->buffer_max_size, 1); + if(file_recovery->memory_buffer == NULL) { + file_recovery->use_memory_buffering = 0; + return -1; + } + + file_recovery->buffer_size = 0; + return 0; +} + +int append_to_memory_buffer(file_recovery_t *file_recovery, const unsigned char *data, size_t size) +{ + if(!file_recovery->use_memory_buffering || !file_recovery->memory_buffer) { + return -1; + } + + if(file_recovery->buffer_size + size > file_recovery->buffer_max_size) { + free_memory_buffer(file_recovery); + return -2; + } + + memcpy(file_recovery->memory_buffer + file_recovery->buffer_size, data, size); + file_recovery->buffer_size += size; + return 0; +} + +int flush_memory_buffer_to_file(file_recovery_t *file_recovery) +{ + if(!file_recovery->memory_buffer || file_recovery->buffer_size == 0) { + return 0; + } + + if(file_recovery->handle == NULL) { +#if defined(__CYGWIN__) || defined(__MINGW32__) + file_recovery->handle = fopen(file_recovery->filename, "w+b"); +#else + file_recovery->handle = fopen(file_recovery->filename, "w+b"); +#endif + if(file_recovery->handle == NULL) { + return -1; + } + } + + if(fwrite(file_recovery->memory_buffer, file_recovery->buffer_size, 1, file_recovery->handle) < 1) { + return -1; + } + + if(fflush(file_recovery->handle) != 0) { + return -1; + } + + // Don't close the file handle - let PhotoRec handle that later + // PhotoRec needs handle to be non-NULL to call file_finish_aux() which registers files as recovered + + file_recovery->file_size = file_recovery->buffer_size; + free_memory_buffer(file_recovery); + return 0; +} + +void free_memory_buffer(file_recovery_t *file_recovery) +{ + if(file_recovery) { + // Only free if use_memory_buffering was actually set (not garbage) + if(file_recovery->use_memory_buffering == 1 && file_recovery->memory_buffer) { + free(file_recovery->memory_buffer); + } + file_recovery->memory_buffer = NULL; + file_recovery->buffer_size = 0; + file_recovery->buffer_max_size = 0; + file_recovery->use_memory_buffering = 0; + } +} diff --git a/src/filegen.h b/src/filegen.h index c713eafb..c79f2025 100644 --- a/src/filegen.h +++ b/src/filegen.h @@ -29,6 +29,7 @@ extern "C" { #endif #include "list.h" +#include "image_filter.h" #if defined(DJGPP) #define PHOTOREC_MAX_FILE_SIZE (((uint64_t)1<<31)-1) @@ -54,6 +55,7 @@ struct file_hint_struct const char *description; const uint64_t max_filesize; const int recover; + const unsigned int is_image; const unsigned int enable_by_default; void (*register_header_check)(file_stat_t *file_stat); }; @@ -80,11 +82,20 @@ struct file_stat_struct const file_hint_t *file_hint; }; +struct image_data_struct +{ + uint32_t width; + uint32_t height; +}; + +typedef struct image_data_struct image_data_t; + struct file_recovery_struct { char filename[2048]; alloc_list_t location; file_stat_t *file_stat; + image_data_t image_data; FILE *handle; time_t time; uint64_t file_size; @@ -98,11 +109,17 @@ struct file_recovery_struct /* data_check modifies file_recovery->calculated_file_size, it can also update data_check, file_check, offset_error, offset_ok, time, data_check_tmp */ void (*file_check)(file_recovery_t *file_recovery); void (*file_rename)(file_recovery_t *file_recovery); + int (*file_check_presave)(const unsigned char *buffer, const unsigned int buffer_size, file_recovery_t *file_recovery); + const image_size_filter_t *image_filter; uint64_t checkpoint_offset; int checkpoint_status; /* 0=suspend at offset_checkpoint if offset_checkpoint>0, 1=resume at offset_checkpoint */ unsigned int blocksize; unsigned int flags; unsigned int data_check_tmp; + unsigned char *memory_buffer; + uint64_t buffer_size; + uint64_t buffer_max_size; + int use_memory_buffering; }; typedef struct @@ -362,6 +379,11 @@ void file_check_size_max(file_recovery_t *file_recovery); // ensures valid_file_recovery(file_recovery); void reset_file_recovery(file_recovery_t *file_recovery); +int init_memory_buffer(file_recovery_t *file_recovery); +int append_to_memory_buffer(file_recovery_t *file_recovery, const unsigned char *data, size_t size); +int flush_memory_buffer_to_file(file_recovery_t *file_recovery); +void free_memory_buffer(file_recovery_t *file_recovery); + /*@ @ requires offset <= PHOTOREC_MAX_SIG_OFFSET; @ requires 0 < length <= PHOTOREC_MAX_SIG_SIZE; diff --git a/src/image_filter.c b/src/image_filter.c new file mode 100644 index 00000000..fac78ed0 --- /dev/null +++ b/src/image_filter.c @@ -0,0 +1,539 @@ +/* + + File: image_filter.c + + Copyright (C) 2024 Christophe GRENIER + + 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 +#endif + +#ifdef HAVE_STRING_H +#include +#endif +#include +#include +#include +#include +#include "types.h" +#include "common.h" +#include "filegen.h" +#include "photorec.h" +#include "image_filter.h" +#include "log.h" + + +void print_image_filter(const image_size_filter_t *filter) +{ + if(!filter) { + printf("Image filter: NULL (no filtering)\n"); + return; + } + + printf("=== Image Filter Settings ===\n"); + + /* File size filters */ + if(filter->min_file_size > 0 || filter->max_file_size > 0) { + printf("File size: "); + if(filter->min_file_size > 0) { + if(filter->min_file_size >= 1024*1024*1024) { + printf("min=%.1fGB", (double)filter->min_file_size / (1024*1024*1024)); + } else if(filter->min_file_size >= 1024*1024) { + printf("min=%.1fMB", (double)filter->min_file_size / (1024*1024)); + } else if(filter->min_file_size >= 1024) { + printf("min=%.1fKB", (double)filter->min_file_size / 1024); + } else { + printf("min=%llu bytes", (unsigned long long)filter->min_file_size); + } + } else { + printf("min=none"); + } + + printf(", "); + + if(filter->max_file_size > 0) { + if(filter->max_file_size >= 1024*1024*1024) { + printf("max=%.1fGB", (double)filter->max_file_size / (1024*1024*1024)); + } else if(filter->max_file_size >= 1024*1024) { + printf("max=%.1fMB", (double)filter->max_file_size / (1024*1024)); + } else if(filter->max_file_size >= 1024) { + printf("max=%.1fKB", (double)filter->max_file_size / 1024); + } else { + printf("max=%llu bytes", (unsigned long long)filter->max_file_size); + } + } else { + printf("max=none"); + } + printf("\n"); + } else { + printf("File size: no limits\n"); + } + + /* Width filters */ + if(filter->min_width > 0 || filter->max_width > 0) { + printf("Width: "); + if(filter->min_width > 0) { + printf("min=%u", filter->min_width); + } else { + printf("min=none"); + } + printf(", "); + if(filter->max_width > 0) { + printf("max=%u", filter->max_width); + } else { + printf("max=none"); + } + printf(" pixels\n"); + } else { + printf("Width: no limits\n"); + } + + /* Height filters */ + if(filter->min_height > 0 || filter->max_height > 0) { + printf("Height: "); + if(filter->min_height > 0) { + printf("min=%u", filter->min_height); + } else { + printf("min=none"); + } + printf(", "); + if(filter->max_height > 0) { + printf("max=%u", filter->max_height); + } else { + printf("max=none"); + } + printf(" pixels\n"); + } else { + printf("Height: no limits\n"); + } + + /* Pixel count filters */ + if(filter->min_pixels > 0 || filter->max_pixels > 0) { + printf("Total pixels: "); + if(filter->min_pixels > 0) { + if(filter->min_pixels >= 1000000) { + printf("min=%.1fM", (double)filter->min_pixels / 1000000); + } else if(filter->min_pixels >= 1000) { + printf("min=%.1fK", (double)filter->min_pixels / 1000); + } else { + printf("min=%llu", (unsigned long long)filter->min_pixels); + } + } else { + printf("min=none"); + } + printf(", "); + if(filter->max_pixels > 0) { + if(filter->max_pixels >= 1000000) { + printf("max=%.1fM", (double)filter->max_pixels / 1000000); + } else if(filter->max_pixels >= 1000) { + printf("max=%.1fK", (double)filter->max_pixels / 1000); + } else { + printf("max=%llu", (unsigned long long)filter->max_pixels); + } + } else { + printf("max=none"); + } + printf(" pixels\n"); + } else { + printf("Total pixels: no limits\n"); + } + + printf("=============================\n"); +} + +int should_skip_image_by_dimensions(const image_size_filter_t *filter, uint32_t width, uint32_t height) +{ + if(!filter) + return 0; + + if(filter->min_pixels > 0 || filter->max_pixels > 0) + { + uint64_t pixels = (uint64_t)width * height; + if(filter->min_pixels > 0 && pixels < filter->min_pixels) + return 1; + if(filter->max_pixels > 0 && pixels > filter->max_pixels) + return 1; + + return 0; + } + + if(filter->min_width > 0 && width < filter->min_width) + return 1; + if(filter->max_width > 0 && width > filter->max_width) + return 1; + if(filter->min_height > 0 && height < filter->min_height) + return 1; + if(filter->max_height > 0 && height > filter->max_height) + return 1; + + return 0; +} + +int should_skip_image_by_filesize(const image_size_filter_t *filter, uint64_t file_size) +{ + if(!filter) + return 0; + + if(filter->min_file_size > 0 && file_size < filter->min_file_size) + return 1; + if(filter->max_file_size > 0 && file_size > filter->max_file_size) + return 1; + + return 0; +} + +int has_any_image_size_filter(const image_size_filter_t *filter) +{ + return (filter->min_file_size | filter->max_file_size | + filter->min_width | filter->max_width | + filter->min_height | filter->max_height | + filter->min_pixels | filter->max_pixels) > 0; +} + +/* Parse file size with unit suffixes. Valid formats: + * - "1000" : exact size in bytes (1000) + * - "10k" : size in kilobytes (10240 bytes) + * - "1.5m" : size in megabytes with decimal (1572864 bytes) + * - "2g" : size in gigabytes (2147483648 bytes) + * - Units: k/K (kilobytes), m/M (megabytes), g/G (gigabytes) + * - Decimal values supported (e.g., "1.5k", "0.5m") + */ +uint64_t parse_size_with_units(char **cmd) +{ + char *ptr = *cmd; + double val = 0.0; + + /* Parse number with decimal support */ + char *endptr; + val = strtod(ptr, &endptr); + + if(endptr == ptr) + return 0; + + ptr = endptr; + + /* Parse unit suffix and convert to bytes */ + uint64_t multiplier = 1; + if(*ptr == 'k' || *ptr == 'K') + { + multiplier = 1024; + ptr++; + } + else if(*ptr == 'm' || *ptr == 'M') + { + multiplier = 1024 * 1024; + ptr++; + } + else if(*ptr == 'g' || *ptr == 'G') + { + multiplier = 1024 * 1024 * 1024; + ptr++; + } + + *cmd = ptr; + return (uint64_t)(val * multiplier); +} + +/* Parse pixel value in numeric or WIDTHxHEIGHT format. Valid formats: + * - "1000" : exact pixel count (1000) + * - "800x600" : resolution format (calculates 800*600 = 480000 pixels) + * - "1920x1080": HD resolution (calculates 1920*1080 = 2073600 pixels) + * - Width and height must be positive integers + */ +uint64_t parse_pixels_value(char **cmd) +{ + char *ptr = *cmd; + uint64_t val = 0; + + /* Check if it's WIDTHxHEIGHT format */ + if(strchr(ptr, 'x') != NULL) + { + uint32_t width = 0, height = 0; + + /* Parse width */ + while(*ptr && isdigit(*ptr)) + { + width = width * 10 + (*ptr - '0'); + ptr++; + } + + /* Skip 'x' */ + if(*ptr == 'x') + ptr++; + + /* Parse height */ + while(*ptr && isdigit(*ptr)) + { + height = height * 10 + (*ptr - '0'); + ptr++; + } + + val = (uint64_t)width * height; + } + else + { + /* Direct number format */ + while(*ptr && isdigit(*ptr)) + { + val = val * 10 + (*ptr - '0'); + ptr++; + } + } + + *cmd = ptr; + return val; +} + +/* Parse pixel range specification. Valid formats: + * - "1000" : exact pixel count (1000) + * - "800x600" : exact resolution (480000 pixels) + * - "1000-" : minimum 1000 pixels, no maximum + * - "-5000" : maximum 5000 pixels, no minimum + * - "1000-5000" : range from 1000 to 5000 pixels + * - "800x600-1920x1080" : resolution range (480000 to 2073600 pixels) + * - "800x600-" : minimum 800x600 resolution, no maximum + * - "-1920x1080" : maximum 1920x1080 resolution, no minimum + */ +void parse_pixels_range(char **cmd, uint64_t *min_pixels, uint64_t *max_pixels) +{ + char *ptr = *cmd; + + *min_pixels = 0; + *max_pixels = 0; + + /* Check for leading dash (only max) */ + if(*ptr == '-') + { + ptr++; + *max_pixels = parse_pixels_value(&ptr); + } + else + { + /* Parse min value */ + *min_pixels = parse_pixels_value(&ptr); + + /* Check for range separator */ + if(*ptr == '-') + { + ptr++; + /* Check if there's a max value after dash */ + if(*ptr && *ptr != ',' && *ptr != '\0') + { + *max_pixels = parse_pixels_value(&ptr); + } + } + } + + *cmd = ptr; +} + +int validate_image_filter(const image_size_filter_t *filter) +{ + /* Check for conflicting parameters: pixels vs width/height */ + int has_pixels = (filter->min_pixels > 0 || filter->max_pixels > 0); + int has_dimensions = (filter->min_width > 0 || filter->max_width > 0 || + filter->min_height > 0 || filter->max_height > 0); + + if(has_pixels && has_dimensions) + { + log_error("Cannot combine 'pixels' parameter with 'width' or 'height' parameters.\n"); + log_error("Use either:\n"); + log_error(" - pixels:WIDTHxHEIGHT-WIDTHxHEIGHT (direct pixel control)\n"); + log_error(" - width:MIN-MAX,height:MIN-MAX (dimension control)\n"); + return 0; + } + + return 1; +} + +void change_imagesize_cli(char **cmd, image_size_filter_t *filter) +{ + char *ptr = *cmd; + memset(filter, 0, sizeof(*filter)); + + while(*ptr) + { + if(strncmp(ptr, "size,", 5) == 0) + { + ptr += 5; + if(*ptr == '-') + { + ptr++; + filter->max_file_size = parse_size_with_units(&ptr); + } + else + { + filter->min_file_size = parse_size_with_units(&ptr); + if(*ptr == '-') + { + ptr++; + if(*ptr && *ptr != ',' && *ptr != '\0') + filter->max_file_size = parse_size_with_units(&ptr); + } + } + } + else if(strncmp(ptr, "width,", 6) == 0) + { + ptr += 6; + if(*ptr == '-') + { + ptr++; + filter->max_width = (uint32_t)get_int_from_command(&ptr); + } + else + { + filter->min_width = (uint32_t)get_int_from_command(&ptr); + if(*ptr == '-') + { + ptr++; + if(*ptr && *ptr != ',' && *ptr != '\0') + filter->max_width = (uint32_t)get_int_from_command(&ptr); + } + } + } + else if(strncmp(ptr, "height,", 7) == 0) + { + ptr += 7; + if(*ptr == '-') + { + ptr++; + filter->max_height = (uint32_t)get_int_from_command(&ptr); + } + else + { + filter->min_height = (uint32_t)get_int_from_command(&ptr); + if(*ptr == '-') + { + ptr++; + if(*ptr && *ptr != ',' && *ptr != '\0') + filter->max_height = (uint32_t)get_int_from_command(&ptr); + } + } + } + else if(strncmp(ptr, "pixels,", 7) == 0) + { + ptr += 7; + parse_pixels_range(&ptr, &filter->min_pixels, &filter->max_pixels); + } + else + { + /* Unknown parameter - stop parsing and leave it for other parsers */ + break; + } + + /* Skip to next parameter */ + if(*ptr == ',') + ptr++; + } + + *cmd = ptr; + + /* Validate configuration */ + validate_image_filter(filter); +} + +/* Format file size for display in user interface. + * Converts bytes to human-readable format: + * - Values < 1024: shows exact bytes (e.g., "55", "1023") + * - Values >= 1024 < 1MB: shows with 'k' suffix (e.g., "1k", "500k") + * - Values >= 1MB: shows with 'm' suffix (e.g., "1m", "10m") + * - Size 0: returns empty string + */ +void format_file_size_string(uint64_t size, char *buffer, size_t buffer_size) +{ + if (size == 0) { + buffer[0] = '\0'; + return; + } + + if (size >= 1024*1024*1024) { + double gb = (double)size / (1024*1024*1024); + snprintf(buffer, buffer_size, "%.2fg", gb); + } else if (size >= 1024*1024) { + double mb = (double)size / (1024*1024); + snprintf(buffer, buffer_size, "%.2fm", mb); + } else if (size >= 1024) { + double kb = (double)size / 1024.0; + snprintf(buffer, buffer_size, "%.2fk", kb); + } else { + snprintf(buffer, buffer_size, "%lu", (unsigned long)size); + } +} + +/* Convert image_size_filter_t to CLI format string for session saving */ +void image_size_2_cli(const image_size_filter_t *filter, char *buffer, size_t buffer_size) +{ + int written = 0; + + if (!has_any_image_size_filter(filter)) { + buffer[0] = '\0'; + return; + } + + written += snprintf(buffer + written, buffer_size - written, "imagesize,"); + + /* File size filters */ + if (filter->min_file_size > 0 && filter->max_file_size > 0) { + written += snprintf(buffer + written, buffer_size - written, "size,%luk-%luk,", + (unsigned long)(filter->min_file_size / 1024), + (unsigned long)(filter->max_file_size / 1024)); + } else if (filter->min_file_size > 0) { + written += snprintf(buffer + written, buffer_size - written, "size,%luk-,", + (unsigned long)(filter->min_file_size / 1024)); + } else if (filter->max_file_size > 0) { + written += snprintf(buffer + written, buffer_size - written, "size,-%luk,", + (unsigned long)(filter->max_file_size / 1024)); + } + + /* Width filters */ + if (filter->min_width > 0 && filter->max_width > 0) { + written += snprintf(buffer + written, buffer_size - written, "width,%u-%u,", + filter->min_width, filter->max_width); + } else if (filter->min_width > 0) { + written += snprintf(buffer + written, buffer_size - written, "width,%u-,", + filter->min_width); + } else if (filter->max_width > 0) { + written += snprintf(buffer + written, buffer_size - written, "width,-%u,", + filter->max_width); + } + + /* Height filters */ + if (filter->min_height > 0 && filter->max_height > 0) { + written += snprintf(buffer + written, buffer_size - written, "height,%u-%u,", + filter->min_height, filter->max_height); + } else if (filter->min_height > 0) { + written += snprintf(buffer + written, buffer_size - written, "height,%u-,", + filter->min_height); + } else if (filter->max_height > 0) { + written += snprintf(buffer + written, buffer_size - written, "height,-%u,", + filter->max_height); + } + + /* Pixels filters */ + if (filter->min_pixels > 0 && filter->max_pixels > 0) { + written += snprintf(buffer + written, buffer_size - written, "pixels,%llu-%llu,", + (unsigned long long)filter->min_pixels, (unsigned long long)filter->max_pixels); + } else if (filter->min_pixels > 0) { + written += snprintf(buffer + written, buffer_size - written, "pixels,%llu-,", + (unsigned long long)filter->min_pixels); + } else if (filter->max_pixels > 0) { + written += snprintf(buffer + written, buffer_size - written, "pixels,-%llu,", + (unsigned long long)filter->max_pixels); + } +} diff --git a/src/image_filter.h b/src/image_filter.h new file mode 100644 index 00000000..9e135b2d --- /dev/null +++ b/src/image_filter.h @@ -0,0 +1,70 @@ +/* + + File: image_filter.h + + Copyright (C) 2024 Christophe GRENIER + + 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. + + */ + +#ifndef _IMAGE_FILTER_H +#define _IMAGE_FILTER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "types.h" +#include + +struct image_size_filter_struct +{ + uint64_t min_file_size; /* 0 = no limit */ + uint64_t max_file_size; /* 0 = no limit */ + uint32_t min_width; /* 0 = no limit */ + uint32_t max_width; /* 0 = no limit */ + uint32_t min_height; /* 0 = no limit */ + uint32_t max_height; /* 0 = no limit */ + uint64_t min_pixels; /* 0 = no limit (width × height) */ + uint64_t max_pixels; /* 0 = no limit (width × height) */ +}; + +typedef struct image_size_filter_struct image_size_filter_t; + +int has_any_image_size_filter(const image_size_filter_t *filter); +int should_skip_image_by_dimensions(const image_size_filter_t *filter, uint32_t width, uint32_t height); +int should_skip_image_by_filesize(const image_size_filter_t *filter, uint64_t file_size); + +void change_imagesize_cli(char **cmd, image_size_filter_t *filter); + +int validate_image_filter(const image_size_filter_t *filter); + +uint64_t parse_size_with_units(char **cmd); + +uint64_t parse_pixels_value(char **cmd); + +void print_image_filter(const image_size_filter_t *filter); + +void parse_pixels_range(char **cmd, uint64_t *min_pixels, uint64_t *max_pixels); + +void format_file_size_string(uint64_t size, char *buffer, size_t buffer_size); + +void image_size_2_cli(const image_size_filter_t *filter, char *buffer, size_t buffer_size); + +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file diff --git a/src/phbf.c b/src/phbf.c index f6250078..20598eca 100644 --- a/src/phbf.c +++ b/src/phbf.c @@ -67,6 +67,7 @@ #include "log_part.h" #include "file_tar.h" #include "phcfg.h" +#include "image_filter.h" #include "pblocksize.h" #include "pnext.h" #include "phbf.h" @@ -148,7 +149,7 @@ pstatus_t photorec_bf(struct ph_param *params, const struct ph_options *options, int need_to_check_file; int go_backward=1; file_recovery_t file_recovery; -// memset(&file_recovery, 0, sizeof(file_recovery_t)); + memset(&file_recovery, 0, sizeof(file_recovery_t)); reset_file_recovery(&file_recovery); file_recovery.blocksize=blocksize; current_search_space=td_list_entry(search_walker, alloc_data_t, list); @@ -170,7 +171,7 @@ pstatus_t photorec_bf(struct ph_param *params, const struct ph_options *options, { const struct td_list_head *tmpl; file_recovery_t file_recovery_new; -// memset(&file_recovery_new, 0, sizeof(file_recovery_t)); + memset(&file_recovery_new, 0, sizeof(file_recovery_t)); file_recovery_new.blocksize=blocksize; file_recovery_new.location.start=offset; file_recovery_new.file_stat=NULL; @@ -218,21 +219,31 @@ pstatus_t photorec_bf(struct ph_param *params, const struct ph_options *options, if(file_recovery.file_stat!=NULL && file_recovery.handle==NULL) { /* Create new file */ set_filename(&file_recovery, params); - if(file_recovery.file_stat->file_hint->recover==1) + if(file_recovery.file_stat->file_hint->recover==1 && !file_recovery.use_memory_buffering) { if(!(file_recovery.handle=fopen(file_recovery.filename,"w+b"))) - { + { log_critical("Cannot create file %s: %s\n", file_recovery.filename, strerror(errno)); ind_stop=PSTATUS_EACCES; } } } - if(need_to_check_file==0 && file_recovery.handle!=NULL && file_recovery.file_stat!=NULL) + if(need_to_check_file==0 && file_recovery.file_stat!=NULL) { - if(fwrite(buffer,blocksize,1,file_recovery.handle)<1) - { - log_critical("Cannot write to file %s: %s\n", file_recovery.filename, strerror(errno)); - ind_stop=PSTATUS_ENOSPC; + if(file_recovery.use_memory_buffering) { + int write_result = append_to_memory_buffer(&file_recovery, buffer, blocksize); + if(write_result == -2) { + need_to_check_file = 1; + } else if(write_result < 0) { + log_critical("Memory buffer error\n"); + ind_stop=PSTATUS_ENOSPC; + } + } else if(file_recovery.handle!=NULL) { + if(fwrite(buffer,blocksize,1,file_recovery.handle)<1) + { + log_critical("Cannot write to file %s: %s\n", file_recovery.filename, strerror(errno)); + ind_stop=PSTATUS_ENOSPC; + } } { data_check_t res=DC_CONTINUE; @@ -240,7 +251,9 @@ pstatus_t photorec_bf(struct ph_param *params, const struct ph_options *options, file_block_append(&file_recovery, list_search_space, ¤t_search_space, &offset, blocksize, 1); if(file_recovery.data_check!=NULL) res=file_recovery.data_check(buffer_olddata, 2*blocksize, &file_recovery); - file_recovery.file_size+=blocksize; + if(!file_recovery.use_memory_buffering) { + file_recovery.file_size+=blocksize; + } if(res==DC_STOP || res==DC_ERROR) { /* EOF found */ need_to_check_file=1; @@ -573,7 +586,7 @@ static bf_status_t photorec_bf_frag(struct ph_param *params, file_recovery_t *fi uint64_t extrablock_offset; int blocs_to_skip; file_recovery_t file_recovery_backup; -// memset(&file_recovery_backup, 0, sizeof(file_recovery_t)); + memset(&file_recovery_backup, 0, sizeof(file_recovery_t)); file_recovery->checkpoint_status=0; file_recovery->checkpoint_offset = file_offset; file_recovery->file_size=file_offset; diff --git a/src/phbs.c b/src/phbs.c index 9364041b..4617dece 100644 --- a/src/phbs.c +++ b/src/phbs.c @@ -87,6 +87,7 @@ pstatus_t photorec_find_blocksize(struct ph_param *params, const struct ph_optio /*@ assert read_size >= 65536; */ alloc_data_t *current_search_space; file_recovery_t file_recovery; + memset(&file_recovery, 0, sizeof(file_recovery_t)); #ifndef DISABLED_FOR_FRAMAC params->file_nbr=0; reset_file_recovery(&file_recovery); @@ -109,6 +110,7 @@ pstatus_t photorec_find_blocksize(struct ph_param *params, const struct ph_optio const uint64_t old_offset=offset; { file_recovery_t file_recovery_new; + memset(&file_recovery_new, 0, sizeof(file_recovery_t)); file_recovery_new.blocksize=blocksize; file_recovery_new.location.start=offset; #if !defined(SINGLE_FORMAT) || defined(SINGLE_FORMAT_tar) diff --git a/src/phcli.c b/src/phcli.c index 1f7b0a99..dc7a0ae1 100644 --- a/src/phcli.c +++ b/src/phcli.c @@ -38,6 +38,7 @@ extern int need_to_stop; #include "filegen.h" #include "log.h" #include "photorec.h" +#include "image_filter.h" #include "ext2grp.h" #include "geometry.h" #include "poptions.h" @@ -208,6 +209,10 @@ int menu_photorec_cli(list_part_t *list_part, struct ph_param *params, struct ph if(file_select_cli(options->list_file_format, ¶ms->cmd_run) < 0) return -1; } + else if(check_command(¶ms->cmd_run,"imagesize,",10)==0) + { + change_imagesize_cli(¶ms->cmd_run, &options->image_filter); + } else if(check_command(¶ms->cmd_run,"blocksize,",10)==0) { user_blocksize=get_int_from_command(¶ms->cmd_run); diff --git a/src/phmain.c b/src/phmain.c index 056ae398..51d0e90e 100644 --- a/src/phmain.c +++ b/src/phmain.c @@ -90,6 +90,7 @@ #include "pdiskseln.h" #include "dfxml.h" #include "json_log.h" +#include "image_filter.h" int need_to_stop=0; extern file_enable_t array_file_enable[]; @@ -183,7 +184,8 @@ int main( int argc, char **argv ) .expert=0, .lowmem=0, .verbose=0, - .list_file_format=array_file_enable + .list_file_format=array_file_enable, + .image_filter={0} }; struct ph_param params; if(argc <= 0) @@ -462,6 +464,7 @@ int main( int argc, char **argv ) { use_sudo=2; } + if(use_sudo==0) use_sudo=do_curses_photorec(¶ms, &options, list_disk); #else diff --git a/src/photorec.c b/src/photorec.c index 6e64e643..970e2108 100644 --- a/src/photorec.c +++ b/src/photorec.c @@ -66,6 +66,7 @@ #include "log.h" #include "setdate.h" #include "dfxml.h" +#include "image_filter.h" /* #define DEBUG_FILE_FINISH */ /* #define DEBUG_UPDATE_SEARCH_SPACE */ @@ -681,6 +682,23 @@ static void file_finish_aux(file_recovery_t *file_recovery, struct ph_param *par #endif file_recovery->file_size=0; } + if(params->image_filter && has_any_image_size_filter(params->image_filter) && + file_recovery->file_stat->file_hint->is_image ) { +#ifdef DEBUG_FILE_FINISH + log_trace("Image filter check: file=%s, size=%llu, width=%u, height=%u\n", + file_recovery->filename, + (unsigned long long)file_recovery->file_size, + file_recovery->image_data.width, + file_recovery->image_data.height); +#endif + + if(should_skip_image_by_filesize(params->image_filter, file_recovery->file_size)) { + file_recovery->file_size=0; + } + else if(should_skip_image_by_dimensions(params->image_filter, file_recovery->image_data.width, file_recovery->image_data.height)) { + file_recovery->file_size=0; + } + } if(file_recovery->file_size==0) { if(paranoid==2) @@ -735,8 +753,26 @@ static void file_finish_aux(file_recovery_t *file_recovery, struct ph_param *par int file_finish_bf(file_recovery_t *file_recovery, struct ph_param *params, alloc_data_t *list_search_space) { + if(file_recovery->file_stat==NULL) return 0; + + // Handle memory buffering for images with filtering + if(file_recovery->use_memory_buffering) { + if(file_recovery->image_filter && file_recovery->file_check_presave) { + if(!file_recovery->file_check_presave(file_recovery->memory_buffer, file_recovery->buffer_size, file_recovery)) { + // File rejected by filters - don't create it at all + reset_file_recovery(file_recovery); + return 0; + } + } + // File passed filters - flush to disk + if(flush_memory_buffer_to_file(file_recovery) < 0) { + reset_file_recovery(file_recovery); + return -1; + } + } + if(file_recovery->handle) file_finish_aux(file_recovery, params, 2); if(file_recovery->file_size==0) @@ -752,6 +788,7 @@ int file_finish_bf(file_recovery_t *file_recovery, struct ph_param *params, reset_file_recovery(file_recovery); return 0; } + file_block_truncate(file_recovery, list_search_space, params->blocksize); file_block_log(file_recovery, params->disk->sector_size); #ifdef ENABLE_DFXML @@ -781,8 +818,27 @@ void file_recovery_aborted(file_recovery_t *file_recovery, struct ph_param *para pfstatus_t file_finish2(file_recovery_t *file_recovery, struct ph_param *params, const int paranoid, alloc_data_t *list_search_space) { int file_truncated; + + if(file_recovery->file_stat==NULL) return PFSTATUS_BAD; + + // Handle memory buffering for images with filtering + if(file_recovery->use_memory_buffering) { + if(file_recovery->image_filter && file_recovery->file_check_presave) { + if(!file_recovery->file_check_presave(file_recovery->memory_buffer, file_recovery->buffer_size, file_recovery)) { + // File rejected by filters - don't create it at all + reset_file_recovery(file_recovery); + return PFSTATUS_BAD; + } + } + // File passed filters - flush to disk + if(flush_memory_buffer_to_file(file_recovery) < 0) { + reset_file_recovery(file_recovery); + return PFSTATUS_BAD; + } + } + if(file_recovery->handle) file_finish_aux(file_recovery, params, (paranoid==0?0:1)); if(file_recovery->file_size==0) @@ -791,11 +847,13 @@ pfstatus_t file_finish2(file_recovery_t *file_recovery, struct ph_param *params, reset_file_recovery(file_recovery); return PFSTATUS_BAD; } + file_truncated=file_block_truncate(file_recovery, list_search_space, params->blocksize); file_block_log(file_recovery, params->disk->sector_size); #ifdef ENABLE_DFXML xml_log_file_recovered(file_recovery); #endif + file_block_free(&file_recovery->location); reset_file_recovery(file_recovery); return (file_truncated>0?PFSTATUS_OK_TRUNCATED:PFSTATUS_OK); @@ -965,6 +1023,7 @@ void params_reset(struct ph_param *params, const struct ph_options *options) { /*@ assert valid_ph_param(params); */ params->file_stats=init_file_stats(options->list_file_format); + params->image_filter=&options->image_filter; /*@ assert valid_ph_param(params); */ params_reset_aux(params); } diff --git a/src/photorec.h b/src/photorec.h index 59f9dfd8..c231a15e 100644 --- a/src/photorec.h +++ b/src/photorec.h @@ -43,6 +43,7 @@ struct ph_options unsigned int lowmem; int verbose; file_enable_t *list_file_format; + image_size_filter_t image_filter; }; struct ph_param @@ -62,6 +63,7 @@ struct ph_param unsigned int file_nbr; file_stat_t *file_stats; uint64_t offset; + const image_size_filter_t *image_filter; }; /*@ diff --git a/src/photorec_check_header.h b/src/photorec_check_header.h index fa7e4aeb..66556a7c 100644 --- a/src/photorec_check_header.h +++ b/src/photorec_check_header.h @@ -135,8 +135,25 @@ static pstatus_t photorec_header_found(const file_recovery_t *file_recovery_new, } #endif set_filename(file_recovery, params); + + if(file_recovery->image_filter && (file_recovery->file_stat->file_hint->is_image)) { + file_recovery->use_memory_buffering = 1; + if(init_memory_buffer(file_recovery) < 0) { + file_recovery->use_memory_buffering = 0; + } + } + + if(file_recovery->image_filter && file_recovery->file_check_presave) { + const unsigned int blocksize=params->blocksize; + const unsigned int read_size=(blocksize>65536?blocksize:65536); + if(!file_recovery->file_check_presave(buffer, read_size, file_recovery)) { + return PSTATUS_OK; + } + } + if(file_recovery->file_stat->file_hint->recover==1) { + if(!file_recovery->use_memory_buffering) { #if defined(__CYGWIN__) || defined(__MINGW32__) file_recovery->handle=fopen_with_retry(file_recovery->filename,"w+b"); #else @@ -150,6 +167,7 @@ static pstatus_t photorec_header_found(const file_recovery_t *file_recovery_new, params->offset=offset; return PSTATUS_EACCES; } + } } return PSTATUS_OK; } @@ -174,6 +192,7 @@ inline static pstatus_t photorec_check_header(file_recovery_t *file_recovery, st const unsigned int blocksize=params->blocksize; const unsigned int read_size=(blocksize>65536?blocksize:65536); file_recovery_t file_recovery_new; + memset(&file_recovery_new, 0, sizeof(file_recovery_new)); /*@ assert valid_file_recovery(file_recovery); */ file_recovery_new.blocksize=blocksize; file_recovery_new.location.start=offset; diff --git a/src/phrecn.c b/src/phrecn.c index d20ea805..a0129cc6 100644 --- a/src/phrecn.c +++ b/src/phrecn.c @@ -33,6 +33,7 @@ #ifdef HAVE_STDLIB_H #include #endif +#include #ifdef HAVE_UNISTD_H #include /* unlink, ftruncate */ #endif @@ -67,6 +68,7 @@ #include "lang.h" #include "filegen.h" #include "photorec.h" +#include "image_filter.h" #include "sessionp.h" #include "phrecn.h" #include "log.h" @@ -81,6 +83,10 @@ #include "phbf.h" #include "phnc.h" #include "phbs.h" + +/* Global variables to store original format user entered for pixels */ +static char pixels_min_format[32] = ""; +static char pixels_max_format[32] = ""; #include "file_found.h" #include "dfxml.h" #include "poptions.h" @@ -502,9 +508,14 @@ int photorec(struct ph_param *params, const struct ph_options *options, alloc_da } #ifdef HAVE_NCURSES +/* Forward declarations */ +static void interface_ask_file_size(image_size_filter_t *filter); +static void interface_ask_image_size(image_size_filter_t *filter); +static void interface_edit_image_filter_field(image_size_filter_t *filter, int field_num); + void interface_options_photorec_ncurses(struct ph_options *options) { - unsigned int menu = 5; + unsigned int menu = 6; struct MenuItem menuOptions[]= { { 'P', NULL, "Check JPG files" }, @@ -512,6 +523,7 @@ void interface_options_photorec_ncurses(struct ph_options *options) { 'S',NULL,"Try to skip indirect block"}, { 'E',NULL,"Provide additional controls"}, { 'L',NULL,"Low memory"}, + { 'I',NULL,"Image size filters"}, { 'Q',"Quit","Return to main menu"}, { 0, NULL, NULL } }; @@ -535,8 +547,9 @@ void interface_options_photorec_ncurses(struct ph_options *options) menuOptions[2].name=options->mode_ext2?"ext2/ext3 mode: Yes":"ext2/ext3 mode : No"; menuOptions[3].name=options->expert?"Expert mode : Yes":"Expert mode : No"; menuOptions[4].name=options->lowmem?"Low memory: Yes":"Low memory: No"; + menuOptions[5].name=has_any_image_size_filter(&options->image_filter)?"Image size filters : Enabled":"Image size filters : Disabled"; aff_copy(stdscr); - car=wmenuSelect_ext(stdscr, 23, INTER_OPTION_Y, INTER_OPTION_X, menuOptions, 0, "PKELQ", MENU_VERT|MENU_VERT_ARROW2VALID, &menu,&real_key); + car=wmenuSelect_ext(stdscr, 23, INTER_OPTION_Y, INTER_OPTION_X, menuOptions, 0, "PKELIQ", MENU_VERT|MENU_VERT_ARROW2VALID, &menu,&real_key); switch(car) { case 'p': @@ -562,6 +575,10 @@ void interface_options_photorec_ncurses(struct ph_options *options) case 'L': options->lowmem=!options->lowmem; break; + case 'i': + case 'I': + interface_imagesize_photorec_ncurses(options); + break; case key_ESC: case 'q': case 'Q': @@ -720,4 +737,857 @@ void interface_file_select_ncurses(file_enable_t *files_enable) offset=current_element_num-INTER_FSELECT+1; } } -#endif + +void interface_imagesize_photorec_ncurses(struct ph_options *options) +{ + int field_selected = 0; /* 0=none, 1=file_size_min, 2=file_size_max, 3=width_min, 4=width_max, 5=height_min, 6=height_max, 7=pixels_min, 8=pixels_max */ + + while (1) + { + int key; + char file_min_str[16] = ""; + char file_max_str[16] = ""; + char width_min_str[16] = ""; + char width_max_str[16] = ""; + char height_min_str[16] = ""; + char height_max_str[16] = ""; + char pixels_min_str[32] = ""; + char pixels_max_str[32] = ""; + + /* Check which fields should be disabled based on specific values set */ + int pixels_min_active = (options->image_filter.min_pixels > 0); + int pixels_max_active = (options->image_filter.max_pixels > 0); + int dimensions_min_active = (options->image_filter.min_width > 0 || options->image_filter.min_height > 0); + int dimensions_max_active = (options->image_filter.max_width > 0 || options->image_filter.max_height > 0); + + /* Format current values as strings */ + format_file_size_string(options->image_filter.min_file_size, file_min_str, sizeof(file_min_str)); + format_file_size_string(options->image_filter.max_file_size, file_max_str, sizeof(file_max_str)); + + /* Width strings */ + if (options->image_filter.min_width > 0) { + snprintf(width_min_str, sizeof(width_min_str), "%u", options->image_filter.min_width); + } + if (options->image_filter.max_width > 0) { + snprintf(width_max_str, sizeof(width_max_str), "%u", options->image_filter.max_width); + } + + /* Height strings */ + if (options->image_filter.min_height > 0) { + snprintf(height_min_str, sizeof(height_min_str), "%u", options->image_filter.min_height); + } + if (options->image_filter.max_height > 0) { + snprintf(height_max_str, sizeof(height_max_str), "%u", options->image_filter.max_height); + } + + /* Pixels strings - show exactly what user entered */ + if (options->image_filter.min_pixels > 0) { + if (strlen(pixels_min_format) > 0) { + snprintf(pixels_min_str, sizeof(pixels_min_str), "%s", pixels_min_format); + } else { + /* Fallback - show as plain number */ + snprintf(pixels_min_str, sizeof(pixels_min_str), "%lu", (unsigned long)options->image_filter.min_pixels); + } + } + if (options->image_filter.max_pixels > 0) { + if (strlen(pixels_max_format) > 0) { + snprintf(pixels_max_str, sizeof(pixels_max_str), "%s", pixels_max_format); + } else { + /* Fallback - show as plain number */ + snprintf(pixels_max_str, sizeof(pixels_max_str), "%lu", (unsigned long)options->image_filter.max_pixels); + } + } + + /* Display interface */ + aff_copy(stdscr); + wmove(stdscr, 4, 0); + wprintw(stdscr, "Image size filters : %s\n", has_any_image_size_filter(&options->image_filter) ? "Enabled" : "Disabled"); + wmove(stdscr, 5, 0); + wprintw(stdscr, "Note: These filters apply only to JPG and PNG files\n"); + + + /* Empty line */ + wmove(stdscr, 7, 0); + wprintw(stdscr, "\n"); + + /* Header row */ + wmove(stdscr, 8, 0); + wprintw(stdscr, " min max"); + + /* File size row */ + wmove(stdscr, 9, 0); + wprintw(stdscr, "File size: "); + if (field_selected == 1) wattron(stdscr, A_REVERSE); + wprintw(stdscr, "[%-10s]", strlen(file_min_str) > 0 ? file_min_str : " disabled"); + if (field_selected == 1) wattroff(stdscr, A_REVERSE); + wprintw(stdscr, " - "); + if (field_selected == 2) wattron(stdscr, A_REVERSE); + wprintw(stdscr, "[%-10s]", strlen(file_max_str) > 0 ? file_max_str : " disabled"); + if (field_selected == 2) wattroff(stdscr, A_REVERSE); + + /* Width row */ + wmove(stdscr, 10, 0); + wprintw(stdscr, "Width (pixels): "); + if (pixels_min_active) wattron(stdscr, A_DIM); + if (field_selected == 3) wattron(stdscr, A_REVERSE); + wprintw(stdscr, "[%-10s]", strlen(width_min_str) > 0 ? width_min_str : " disabled"); + if (field_selected == 3) wattroff(stdscr, A_REVERSE); + if (pixels_min_active) wattroff(stdscr, A_DIM); + wprintw(stdscr, " - "); + if (pixels_max_active) wattron(stdscr, A_DIM); + if (field_selected == 4) wattron(stdscr, A_REVERSE); + wprintw(stdscr, "[%-10s]", strlen(width_max_str) > 0 ? width_max_str : " disabled"); + if (field_selected == 4) wattroff(stdscr, A_REVERSE); + if (pixels_max_active) wattroff(stdscr, A_DIM); + + /* Height row */ + wmove(stdscr, 11, 0); + wprintw(stdscr, "Height (pixels): "); + if (pixels_min_active) wattron(stdscr, A_DIM); + if (field_selected == 5) wattron(stdscr, A_REVERSE); + wprintw(stdscr, "[%-10s]", strlen(height_min_str) > 0 ? height_min_str : " disabled"); + if (field_selected == 5) wattroff(stdscr, A_REVERSE); + if (pixels_min_active) wattroff(stdscr, A_DIM); + wprintw(stdscr, " - "); + if (pixels_max_active) wattron(stdscr, A_DIM); + if (field_selected == 6) wattron(stdscr, A_REVERSE); + wprintw(stdscr, "[%-10s]", strlen(height_max_str) > 0 ? height_max_str : " disabled"); + if (field_selected == 6) wattroff(stdscr, A_REVERSE); + if (pixels_max_active) wattroff(stdscr, A_DIM); + + /* Resolution row */ + wmove(stdscr, 12, 0); + wprintw(stdscr, "Resolution (WIDTHxHEIGHT): "); + if (dimensions_min_active) wattron(stdscr, A_DIM); + if (field_selected == 7) wattron(stdscr, A_REVERSE); + wprintw(stdscr, "[%-10s]", strlen(pixels_min_str) > 0 ? pixels_min_str : " disabled"); + if (field_selected == 7) wattroff(stdscr, A_REVERSE); + if (dimensions_min_active) wattroff(stdscr, A_DIM); + wprintw(stdscr, " - "); + if (dimensions_max_active) wattron(stdscr, A_DIM); + if (field_selected == 8) wattron(stdscr, A_REVERSE); + wprintw(stdscr, "[%-10s]", strlen(pixels_max_str) > 0 ? pixels_max_str : " disabled"); + if (field_selected == 8) wattroff(stdscr, A_REVERSE); + if (dimensions_max_active) wattroff(stdscr, A_DIM); + + wmove(stdscr, 15, 0); + wprintw(stdscr, "Use Arrow keys to select field, Enter to edit, 'c' to clear, 'q' to quit"); + + wrefresh(stdscr); + + /* Helper variables to check field availability */ + + /* Handle input */ + key = wgetch(stdscr); + switch(key) + { + case KEY_UP: + if (field_selected > 2) field_selected -= 2; + else if (field_selected > 0) field_selected = 0; + break; + case KEY_DOWN: + if (field_selected == 0) field_selected = 1; + else if (field_selected <= 6) field_selected += 2; + break; + case KEY_LEFT: + if (field_selected % 2 == 0 && field_selected > 0) field_selected -= 1; + else if (field_selected == 0) field_selected = 1; + break; + case KEY_RIGHT: + if (field_selected % 2 == 1 && field_selected < 8) field_selected += 1; + else if (field_selected == 0) field_selected = 1; + break; + case '\n': + case '\r': + case KEY_ENTER: + if (field_selected >= 1 && field_selected <= 8) { + /* Check if field is disabled before allowing edit */ + int can_edit = 1; + if ((field_selected == 3 || field_selected == 5) && pixels_min_active) can_edit = 0; + if ((field_selected == 4 || field_selected == 6) && pixels_max_active) can_edit = 0; + if (field_selected == 7 && dimensions_min_active) can_edit = 0; + if (field_selected == 8 && dimensions_max_active) can_edit = 0; + + if (can_edit) { + interface_edit_image_filter_field(&options->image_filter, field_selected); + } + } + break; + case 'c': + case 'C': + /* Clear all filters */ + memset(&options->image_filter, 0, sizeof(options->image_filter)); + field_selected = 0; + break; + case key_ESC: + case 'q': + case 'Q': + return; + } + } +} + +static void interface_edit_image_filter_field(image_size_filter_t *filter, int field_num) +{ + char input[32]; + char prompt[64]; + char current_value[32] = ""; + + /* Prepare current value and prompt */ + switch(field_num) { + case 1: /* file size min */ + format_file_size_string(filter->min_file_size, current_value, sizeof(current_value)); + snprintf(prompt, sizeof(prompt), "Min file size (e.g. 5000, 100k, 2m): "); + break; + case 2: /* file size max */ + format_file_size_string(filter->max_file_size, current_value, sizeof(current_value)); + snprintf(prompt, sizeof(prompt), "Max file size (e.g. 5000, 2m, 1000k): "); + break; + case 3: /* width min */ + if (filter->min_width > 0) { + snprintf(current_value, sizeof(current_value), "%u", filter->min_width); + } + snprintf(prompt, sizeof(prompt), "Min width (pixels): "); + break; + case 4: /* width max */ + if (filter->max_width > 0) { + snprintf(current_value, sizeof(current_value), "%u", filter->max_width); + } + snprintf(prompt, sizeof(prompt), "Max width (pixels): "); + break; + case 5: /* height min */ + if (filter->min_height > 0) { + snprintf(current_value, sizeof(current_value), "%u", filter->min_height); + } + snprintf(prompt, sizeof(prompt), "Min height (pixels): "); + break; + case 6: /* height max */ + if (filter->max_height > 0) { + snprintf(current_value, sizeof(current_value), "%u", filter->max_height); + } + snprintf(prompt, sizeof(prompt), "Max height (pixels): "); + break; + case 7: /* pixels min */ + if (filter->min_pixels > 0) { + if (strlen(pixels_min_format) > 0) { + snprintf(current_value, sizeof(current_value), "%s", pixels_min_format); + } else { + snprintf(current_value, sizeof(current_value), "%lu", (unsigned long)filter->min_pixels); + } + } + snprintf(prompt, sizeof(prompt), "Min pixels (total or WIDTHxHEIGHT): "); + break; + case 8: /* pixels max */ + if (filter->max_pixels > 0) { + if (strlen(pixels_max_format) > 0) { + snprintf(current_value, sizeof(current_value), "%s", pixels_max_format); + } else { + snprintf(current_value, sizeof(current_value), "%lu", (unsigned long)filter->max_pixels); + } + } + snprintf(prompt, sizeof(prompt), "Max pixels (total or WIDTHxHEIGHT): "); + break; + default: + return; + } + + /* Show input dialog */ + aff_copy(stdscr); + wmove(stdscr, LINES/2-1, 0); + wprintw(stdscr, "%s", prompt); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Current: %s", strlen(current_value) > 0 ? current_value : "none"); + wmove(stdscr, LINES/2+1, 0); + wprintw(stdscr, "New value (empty to clear, Esc to cancel): "); + wrefresh(stdscr); + + /* Get input with Escape support */ + int ch; + int input_pos = 0; + int prompt_len = 44; /* Length of "New value (empty to clear, Esc to cancel): " */ + input[0] = '\0'; + + /* Position cursor right after the prompt */ + wmove(stdscr, LINES/2+1, prompt_len); + wrefresh(stdscr); + + echo(); + while (1) { + ch = wgetch(stdscr); + + if (ch == key_ESC) { + noecho(); + return; /* Cancel - don't change anything */ + } + else if (ch == '\n' || ch == '\r') { + break; /* Enter pressed - accept input (even if empty) */ + } + else if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) { + if (input_pos > 0) { + input_pos--; + input[input_pos] = '\0'; + /* Move cursor back and clear character */ + wmove(stdscr, LINES/2+1, prompt_len + input_pos); + waddch(stdscr, ' '); + wmove(stdscr, LINES/2+1, prompt_len + input_pos); + wrefresh(stdscr); + } + } + else if (ch >= 32 && ch <= 126 && input_pos < sizeof(input)-2) { /* Printable characters */ + input[input_pos] = ch; + input_pos++; + input[input_pos] = '\0'; + /* Position cursor and add character */ + wmove(stdscr, LINES/2+1, prompt_len + input_pos - 1); + waddch(stdscr, ch); + wrefresh(stdscr); + } + } + noecho(); + + /* Parse and apply input */ + if (strlen(input) > 0) { + char temp_cmd[128]; + char *cmd_ptr; + + switch(field_num) { + case 1: /* file size min */ + { + char *ptr = input; + uint64_t val = parse_size_with_units(&ptr); + + /* Check if min > max */ + if (filter->max_file_size > 0 && val > filter->max_file_size) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Min file size (%lu) cannot be greater than max file size (%lu)", + (unsigned long)val, (unsigned long)filter->max_file_size); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + + filter->min_file_size = val; + } + break; + case 2: /* file size max */ + { + char *ptr = input; + uint64_t val = parse_size_with_units(&ptr); + + /* Check if max < min */ + if (filter->min_file_size > 0 && val < filter->min_file_size) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Max file size (%lu) cannot be less than min file size (%lu)", + (unsigned long)val, (unsigned long)filter->min_file_size); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + + filter->max_file_size = val; + } + break; + case 3: /* width min */ + { + char *ptr = input; + uint64_t val = (uint64_t)get_int_from_command(&ptr); + if (val > UINT32_MAX) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Width must be <= %u", UINT32_MAX); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + filter->min_width = (uint32_t)val; + } + /* Clear pixels min when setting width min */ + filter->min_pixels = 0; + /* Clear saved format */ + pixels_min_format[0] = '\0'; + break; + case 4: /* width max */ + { + char *ptr = input; + uint64_t val = (uint64_t)get_int_from_command(&ptr); + if (val > UINT32_MAX) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Width must be <= %u", UINT32_MAX); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + filter->max_width = (uint32_t)val; + } + /* Clear pixels max when setting width max */ + filter->max_pixels = 0; + /* Clear saved format */ + pixels_max_format[0] = '\0'; + break; + case 5: /* height min */ + { + char *ptr = input; + uint64_t val = (uint64_t)get_int_from_command(&ptr); + if (val > UINT32_MAX) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Height must be <= %u", UINT32_MAX); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + filter->min_height = (uint32_t)val; + } + /* Clear pixels min when setting height min */ + filter->min_pixels = 0; + /* Clear saved format */ + pixels_min_format[0] = '\0'; + break; + case 6: /* height max */ + { + char *ptr = input; + uint64_t val = (uint64_t)get_int_from_command(&ptr); + if (val > UINT32_MAX) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Height must be <= %u", UINT32_MAX); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + filter->max_height = (uint32_t)val; + } + /* Clear pixels max when setting height max */ + filter->max_pixels = 0; + /* Clear saved format */ + pixels_max_format[0] = '\0'; + break; + case 7: /* pixels min */ + /* Check string length first to prevent buffer overflows */ + if (strlen(input) > 31) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Input too long"); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + + /* Check if it's WIDTHxHEIGHT format */ + if(strchr(input, 'x') != NULL) { + uint64_t width = 0, height = 0; + char *ptr = input; + + /* Parse width with overflow check */ + while(*ptr && isdigit(*ptr)) { + if (width > UINT64_MAX / 10) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Width value too large"); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + width = width * 10; + if (width > UINT64_MAX - (*ptr - '0')) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Width value too large"); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + width += (*ptr - '0'); + ptr++; + } + + if(*ptr == 'x') { + ptr++; + /* Parse height with overflow check */ + while(*ptr && isdigit(*ptr)) { + if (height > UINT64_MAX / 10) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Height value too large"); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + height = height * 10; + if (height > UINT64_MAX - (*ptr - '0')) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Height value too large"); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + height += (*ptr - '0'); + ptr++; + } + } + /* Check for overflow and reasonable limits */ + if (width > UINT32_MAX || height > UINT32_MAX) { + /* Show error message */ + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Width and height must be <= %u", UINT32_MAX); + wrefresh(stdscr); + wgetch(stdscr); + return; /* Don't update the filter */ + } + + if (width > 0 && height > 0 && height <= UINT64_MAX / width) { + uint64_t calculated_pixels = (uint64_t)width * height; + + /* Check if min > max */ + if (filter->max_pixels > 0 && calculated_pixels > filter->max_pixels) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Min pixels (%lu) cannot be greater than max pixels (%lu)", + (unsigned long)calculated_pixels, (unsigned long)filter->max_pixels); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + + filter->min_pixels = calculated_pixels; + /* Save the original format user entered */ + snprintf(pixels_min_format, sizeof(pixels_min_format), "%s", input); + } else { + /* Show overflow error */ + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Width x Height calculation overflow"); + wrefresh(stdscr); + wgetch(stdscr); + return; /* Don't update the filter */ + } + } else { + /* Parse as plain number using strtoull for safety */ + char *endptr; + errno = 0; + uint64_t val = strtoull(input, &endptr, 10); + + /* Check for overflow or parsing errors */ + if (errno == ERANGE) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Value too large (max: %lu)", UINT64_MAX); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + + /* Check if whole string was parsed */ + if (*endptr != '\0') { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Invalid number format"); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + + /* Check if min > max */ + if (filter->max_pixels > 0 && val > filter->max_pixels) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Min pixels (%lu) cannot be greater than max pixels (%lu)", + (unsigned long)val, (unsigned long)filter->max_pixels); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + + filter->min_pixels = val; + /* Save the original format user entered */ + snprintf(pixels_min_format, sizeof(pixels_min_format), "%s", input); + } + /* Clear width/height min when setting pixels min */ + filter->min_width = 0; + filter->min_height = 0; + break; + case 8: /* pixels max */ + /* Check string length first to prevent buffer overflows */ + if (strlen(input) > 31) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Input too long"); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + + /* Check if it's WIDTHxHEIGHT format */ + if(strchr(input, 'x') != NULL) { + uint64_t width = 0, height = 0; + char *ptr = input; + + /* Parse width with overflow check */ + while(*ptr && isdigit(*ptr)) { + if (width > UINT64_MAX / 10) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Width value too large"); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + width = width * 10; + if (width > UINT64_MAX - (*ptr - '0')) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Width value too large"); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + width += (*ptr - '0'); + ptr++; + } + + if(*ptr == 'x') { + ptr++; + /* Parse height with overflow check */ + while(*ptr && isdigit(*ptr)) { + if (height > UINT64_MAX / 10) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Height value too large"); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + height = height * 10; + if (height > UINT64_MAX - (*ptr - '0')) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Height value too large"); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + height += (*ptr - '0'); + ptr++; + } + } + /* Check for overflow and reasonable limits */ + if (width > UINT32_MAX || height > UINT32_MAX) { + /* Show error message */ + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Width and height must be <= %u", UINT32_MAX); + wrefresh(stdscr); + wgetch(stdscr); + return; /* Don't update the filter */ + } + + if (width > 0 && height > 0 && height <= UINT64_MAX / width) { + uint64_t calculated_pixels = (uint64_t)width * height; + + /* Check if max < min */ + if (filter->min_pixels > 0 && calculated_pixels < filter->min_pixels) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Max pixels (%lu) cannot be less than min pixels (%lu)", + (unsigned long)calculated_pixels, (unsigned long)filter->min_pixels); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + + filter->max_pixels = calculated_pixels; + /* Save the original format user entered */ + snprintf(pixels_max_format, sizeof(pixels_max_format), "%s", input); + } else { + /* Show overflow error */ + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Width x Height calculation overflow"); + wrefresh(stdscr); + wgetch(stdscr); + return; /* Don't update the filter */ + } + } else { + /* Parse as plain number with overflow protection */ + uint64_t val = 0; + char *ptr = input; + + /* Skip whitespace */ + while (*ptr == ' ' || *ptr == '\t') ptr++; + + /* Parse digits with overflow check */ + while (*ptr && isdigit(*ptr)) { + /* Check for overflow before multiplying */ + if (val > UINT64_MAX / 10) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Value too large (max: %lu)", UINT64_MAX); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + val = val * 10; + + /* Check for overflow before adding */ + if (val > UINT64_MAX - (*ptr - '0')) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Value too large (max: %lu)", UINT64_MAX); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + val += (*ptr - '0'); + ptr++; + } + + /* Check if max < min */ + if (filter->min_pixels > 0 && val < filter->min_pixels) { + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "Error: Max pixels (%lu) cannot be less than min pixels (%lu)", + (unsigned long)val, (unsigned long)filter->min_pixels); + wrefresh(stdscr); + wgetch(stdscr); + return; + } + + filter->max_pixels = val; + /* Save the original format user entered */ + snprintf(pixels_max_format, sizeof(pixels_max_format), "%s", input); + } + /* Clear width/height max when setting pixels max */ + filter->max_width = 0; + filter->max_height = 0; + break; + } + } else { + /* Empty input - clear the field */ + switch(field_num) { + case 1: filter->min_file_size = 0; break; + case 2: filter->max_file_size = 0; break; + case 3: filter->min_width = 0; break; + case 4: filter->max_width = 0; break; + case 5: filter->min_height = 0; break; + case 6: filter->max_height = 0; break; + case 7: filter->min_pixels = 0; break; + case 8: filter->max_pixels = 0; break; + } + } + + /* Validate min <= max constraints */ + if (filter->min_file_size > 0 && filter->max_file_size > 0 && filter->min_file_size > filter->max_file_size) { + filter->max_file_size = filter->min_file_size; + } + if (filter->min_width > 0 && filter->max_width > 0 && filter->min_width > filter->max_width) { + filter->max_width = filter->min_width; + } + if (filter->min_height > 0 && filter->max_height > 0 && filter->min_height > filter->max_height) { + filter->max_height = filter->min_height; + } + if (filter->min_pixels > 0 && filter->max_pixels > 0 && filter->min_pixels > filter->max_pixels) { + filter->max_pixels = filter->min_pixels; + } +} + +static void interface_ask_file_size(image_size_filter_t *filter) +{ + char input[64]; + char prompt_text[128]; + + /* Show current values */ + if (filter->min_file_size > 0 && filter->max_file_size > 0) { + snprintf(prompt_text, sizeof(prompt_text), "File size range (current: %luk-%luk): ", + (unsigned long)(filter->min_file_size/1024), + (unsigned long)(filter->max_file_size/1024)); + } else if (filter->min_file_size > 0) { + snprintf(prompt_text, sizeof(prompt_text), "File size range (current: %luk-): ", + (unsigned long)(filter->min_file_size/1024)); + } else if (filter->max_file_size > 0) { + snprintf(prompt_text, sizeof(prompt_text), "File size range (current: -%luk): ", + (unsigned long)(filter->max_file_size/1024)); + } else { + snprintf(prompt_text, sizeof(prompt_text), "File size range (e.g. 100k-2m or 100k- or -2m): "); + } + + /* Ask for input */ + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "%s", prompt_text); + wrefresh(stdscr); + + /* Get user input */ + echo(); + wgetnstr(stdscr, input, sizeof(input)-1); + noecho(); + + /* Parse input like "100k-2m" or "100k-" or "-2m" */ + if (strlen(input) > 0) { + char temp_cmd[128]; + char *cmd_ptr; + snprintf(temp_cmd, sizeof(temp_cmd), "size,%s", input); + cmd_ptr = temp_cmd; + + /* Clear current file size settings */ + filter->min_file_size = 0; + filter->max_file_size = 0; + + /* Parse using existing function (skip "imagesize," prefix) */ + parse_imagesize_command(&cmd_ptr, filter); + } +} + +static void interface_ask_image_size(image_size_filter_t *filter) +{ + char input[64]; + char prompt_text[128]; + + /* Show current values */ + if (filter->min_pixels > 0 && filter->max_pixels > 0) { + snprintf(prompt_text, sizeof(prompt_text), "Image size range (current: %lu-%lu pixels): ", + (unsigned long)filter->min_pixels, (unsigned long)filter->max_pixels); + } else if (filter->min_width > 0 || filter->min_height > 0) { + snprintf(prompt_text, sizeof(prompt_text), "Image size range (current: %ux%u-): ", + filter->min_width, filter->min_height); + } else { + snprintf(prompt_text, sizeof(prompt_text), "Image size range (e.g. 800x600-1920x1080 or 1000000-): "); + } + + /* Ask for input */ + aff_copy(stdscr); + wmove(stdscr, LINES/2, 0); + wprintw(stdscr, "%s", prompt_text); + wrefresh(stdscr); + + /* Get user input */ + echo(); + wgetnstr(stdscr, input, sizeof(input)-1); + noecho(); + + /* Parse input */ + if (strlen(input) > 0) { + char temp_cmd[128]; + char *cmd_ptr; + snprintf(temp_cmd, sizeof(temp_cmd), "pixels,%s", input); + cmd_ptr = temp_cmd; + + /* Clear current image size settings */ + filter->min_pixels = 0; + filter->max_pixels = 0; + filter->min_width = 0; + filter->max_width = 0; + filter->min_height = 0; + filter->max_height = 0; + + /* Parse using existing function (skip "imagesize," prefix) */ + parse_imagesize_command(&cmd_ptr, filter); + } +} + + +#endif \ No newline at end of file diff --git a/src/phrecn.h b/src/phrecn.h index 61df09e6..f4974f3c 100644 --- a/src/phrecn.h +++ b/src/phrecn.h @@ -38,6 +38,7 @@ int photorec(struct ph_param *params, const struct ph_options *options, alloc_da #ifdef HAVE_NCURSES void interface_file_select_ncurses(file_enable_t *files_enable); void interface_options_photorec_ncurses(struct ph_options *options); +void interface_imagesize_photorec_ncurses(struct ph_options *options); #endif #ifdef __cplusplus diff --git a/src/poptions.c b/src/poptions.c index da28aec1..c9288778 100644 --- a/src/poptions.c +++ b/src/poptions.c @@ -33,6 +33,7 @@ #include "photorec.h" #include "log.h" #include "poptions.h" +#include "image_filter.h" void interface_options_photorec_cli(struct ph_options *options, char **current_cmd) { diff --git a/src/psearchn.c b/src/psearchn.c index 3aa74dd9..813d4ded 100644 --- a/src/psearchn.c +++ b/src/psearchn.c @@ -74,6 +74,7 @@ #include "psearchn.h" #include "photorec_check_header.h" #include "json_log.h" +#include "image_filter.h" #define READ_SIZE 1024*512 extern int need_to_stop; @@ -105,6 +106,7 @@ pstatus_t photorec_aux(struct ph_param *params, const struct ph_options *options memset(&file_recovery, 0, sizeof(file_recovery)); reset_file_recovery(&file_recovery); file_recovery.blocksize=blocksize; + file_recovery.image_filter = (options && has_any_image_size_filter(&options->image_filter)) ? &options->image_filter : NULL; /*@ assert valid_file_recovery(&file_recovery); */ #ifndef DISABLED_FOR_FRAMAC buffer_start=(unsigned char *)MALLOC(buffer_size); @@ -183,10 +185,21 @@ pstatus_t photorec_aux(struct ph_param *params, const struct ph_options *options } else { - if(file_recovery.handle!=NULL) + // Handle memory buffering for images + if(file_recovery.use_memory_buffering) + { + if(append_to_memory_buffer(&file_recovery, buffer, blocksize) < 0) + { +#ifndef DISABLED_FOR_FRAMAC + log_critical("Cannot append to memory buffer for %s: buffer full\n", file_recovery.filename); +#endif + data_check_status=DC_STOP; + } + } + else if(file_recovery.handle!=NULL) { if(fwrite(buffer,blocksize,1,file_recovery.handle)<1) - { + { #ifndef DISABLED_FOR_FRAMAC log_critical("Cannot write to file %s after %llu bytes: %s\n", file_recovery.filename, (long long unsigned)file_recovery.file_size, strerror(errno)); #endif diff --git a/src/sessionp.c b/src/sessionp.c index 12841eca..1941f8d2 100644 --- a/src/sessionp.c +++ b/src/sessionp.c @@ -50,6 +50,7 @@ #include "intrf.h" #include "filegen.h" #include "photorec.h" +#include "image_filter.h" #include "sessionp.h" #include "log.h" @@ -281,6 +282,13 @@ int session_save(const alloc_data_t *list_free_space, const struct ph_param *par } } } + /* Save image filter settings */ + if(has_any_image_size_filter(&options->image_filter)) + { + char filter_buffer[512]; + image_size_2_cli(&options->image_filter, filter_buffer, sizeof(filter_buffer)); + fprintf(f_session, "%s", filter_buffer); + } /* Save options */ fprintf(f_session, "options,"); if(options->paranoid==0)