--- parser3/src/classes/image.C 2024/11/10 20:46:08 1.192 +++ parser3/src/classes/image.C 2026/10/25 16:58:12 1.201 @@ -1,7 +1,7 @@ /** @file Parser: @b image parser class. - Copyright (c) 2001-2024 Art. Lebedev Studio (http://www.artlebedev.com) + Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com) Authors: Konstantin Morshnev , Alexandr Petrosian */ @@ -26,7 +26,7 @@ #include "pa_table.h" #include "pa_charsets.h" -volatile const char * IDENT_IMAGE_C="$Id: image.C,v 1.192 2024/11/10 20:46:08 moko Exp $"; +volatile const char * IDENT_IMAGE_C="$Id: image.C,v 1.201 2026/10/25 16:58:12 moko Exp $"; // defines @@ -246,8 +246,54 @@ public: ///*********************************************** support functions +/// Exif lives in one JPEG segment, whose length is 16 bits: nothing in Exif is longer +#define JPG_EXIF_MAX_SIZE 0xFFFF + +/// limits of the work a measured file can require +class Measure_budget { +public: + enum Kind { + EXIF_VALUES, ///< bytes of the values lying outside of the Exif IFD entries + EXIF_IFDS, ///< jumps from an IFD to the Exif and GPS IFDs + MP4_DEPTH, ///< nesting of MP4 chunks: given back by release() + KIND_COUNT + }; + + Measure_budget(const String& aorigin): origin(aorigin) { + left[EXIF_VALUES]=JPG_EXIF_MAX_SIZE; + left[EXIF_IFDS]=4; // IFD0 has two of them (Exif and GPS), the rest is spare + left[MP4_DEPTH]=8; // real files nest chunks 3 levels deep: moov/trak/mdia + } + + void use(Kind kind, uint amount=1) { + static const char* const messages[KIND_COUNT]={ + "broken Exif - too long values", + "broken Exif - too many IFD references", + "broken MP4 file - chunks nested too deep" + }; + + if(amount>left[kind]) + throw Exception(IMAGE_FORMAT, &origin, messages[kind]); + left[kind]-=amount; + } + + /// gives back what use() took + void release(Kind kind, uint amount=1) { + left[kind]+=amount; + } + +private: + const String& origin; + uint left[KIND_COUNT]; +}; + class Measure_reader { public: + const String& file_name; + Measure_budget budget; // of the file being measured + + Measure_reader(const String& afile_name): file_name(afile_name), budget(afile_name) {} + virtual size_t read(const char* &buf, size_t limit)=0; virtual void seek(uint64_t value)=0; virtual uint64_t tell()=0; @@ -255,12 +301,12 @@ public: }; class Measure_file_reader: public Measure_reader { - const String& file_name; int f; + uint64_t size; public: - Measure_file_reader(int af, const String& afile_name): - file_name(afile_name), f(af) { + Measure_file_reader(int af, const String& afile_name, uint64_t asize): + Measure_reader(afile_name), f(af), size(asize) { } override size_t read(const char* &abuf, size_t limit) { @@ -276,25 +322,22 @@ public: override void seek(uint64_t value) { if(pa_lseek(f, value, SEEK_SET)<0) - throw Exception(IMAGE_FORMAT, &file_name, "seek to %.15g failed: %s (%d)", (double)value, strerror(errno), errno); + throw Exception(IMAGE_FORMAT, &file_name, "seek to %s failed: %s (%d)", pa_uitoa(value), strerror(errno), errno); } override uint64_t tell() { return pa_lseek(f, 0, SEEK_CUR); } - override uint64_t length() { return pa_lseek(f, 0, SEEK_END); } + override uint64_t length() { return size; } }; class Measure_buf_reader: public Measure_reader { - const char* buf; size_t size; - const String& file_name; - size_t offset; public: Measure_buf_reader(const char* abuf, size_t asize, const String& afile_name): - buf(abuf), size(asize), file_name(afile_name), offset(0) { + Measure_reader(afile_name), buf(abuf), size(asize), offset(0) { } override size_t read(const char* &abuf, size_t limit) { @@ -306,7 +349,7 @@ public: override void seek(uint64_t value) { if(value>(uint64_t)size) - throw Exception(IMAGE_FORMAT, &file_name, "seek to %.15g failed: out of buffer (%.15g)", value, size); + throw Exception(IMAGE_FORMAT, &file_name, "seek to %s failed: out of buffer (%s)", pa_uitoa(value), pa_uitoa(size)); offset=(size_t)value; } @@ -411,10 +454,10 @@ static Value* parse_IFD_entry_formatted_ case 10: // signed rational { signed int numerator=(signed int)endian_to_uint(is_big, value); value+=component_size/2; - uint denominator=endian_to_uint(is_big, value); + signed int denominator=(signed int)endian_to_uint(is_big, value); if(!denominator) return 0; - return new VDouble(numerator/denominator); + return new VDouble(((double)numerator)/denominator); } /* case 11: // single float @@ -443,7 +486,8 @@ static Value* parse_IFD_entry_formatted_ catch(...) { /*ignore bad date times*/ } } - return new VString(*new String(cstr, String::L_TAINTED)); + const char* end=(const char*)memchr(cstr, 0, length); + return new VString(pa_strdup(cstr, end ? end-cstr : length)); } if(components_count==1) @@ -453,7 +497,7 @@ static Value* parse_IFD_entry_formatted_ HashStringValue& hash=result->hash(); for(uint i=0; iJPG_EXIF_MAX_SIZE) + throw Exception(IMAGE_FORMAT, &reader.file_name, "broken Exif - too many components"); uint value_size=component_size*components_count; // If its size is over 4bytes, 'DDDDDDDD' contains the offset to data stored address Value* result; @@ -497,13 +543,16 @@ static Value* parse_IFD_entry_value(bool if(value_size<=4) result=parse_IFD_entry_formatted_value(is_big, format, component_size, components_count, entry.value_or_offset_to_it); else { + reader.budget.use(Measure_budget::EXIF_VALUES, value_size); + uint64_t remembered=reader.tell(); { reader.seek(tiff_base+endian_to_uint(is_big, entry.value_or_offset_to_it)); const char* value; if(reader.read(value, value_size)high) << 32) + endian_to_uint(true, ext_size->low); - } + } else if(size==0) // the chunk extends to the end (of file or container) + size=(anext ? anext : reader.length())-next; next+=size; + // the chunk can't end before its header does (size is too small or wraps around): we would never get to the end + if(nextsignature, "ftyp", 4)!=0) throw Exception(IMAGE_FORMAT, &origin_string, "not MP4 file - wrong signature"); first=false; anext=reader.length(); // to avoid reading beyond EOF } else if(strncmp(head->signature, "moov", 4)==0 || strncmp(head->signature, "mdia", 4)==0 || strncmp(head->signature, "trak", 4)==0) { + reader.budget.use(Measure_budget::MP4_DEPTH); if(measure_mp4(origin_string, reader, width, height, next, lastTkhd)) return true; + reader.budget.release(Measure_budget::MP4_DEPTH); } else if(strncmp(head->signature, "tkhd", 4)==0) { if(size>8){ reader.seek(next-8); @@ -980,8 +1038,8 @@ static void measure(const String& file_n // methods -static void file_measure_action(struct stat& /*finfo*/, int f, const String& file_spec, void *context) { - Measure_file_reader reader(f, file_spec); +static void file_measure_action(struct stat& finfo, int f, const String& file_spec, void *context) { + Measure_file_reader reader(f, file_spec, finfo.st_size); measure(file_spec, reader, *static_cast(context)); } @@ -994,31 +1052,22 @@ static void _measure(Request& r, MethodP if(params.count()>1) if(HashStringValue* options=params.as_hash(1, "methods options")) { - int valid_options=0; for(HashStringValue::Iterator i(*options); i; i.next() ){ String::Body key=i.key(); Value* value=i.value(); if(key == "exif") { if(r.process(*value).as_bool()) info.exif=&exif; - valid_options++; - } - if(key == "xmp") { + } else if(key == "xmp") { if(r.process(*value).as_bool()) info.xmp=&xmp; - valid_options++; - } - if(key == "xmp-charset") { + } else if(key == "xmp-charset") { info.xmp_charset=&pa_charsets.get(value->as_string()); - valid_options++; - } - if(key == "video") { + } else if(key == "video") { info.video=r.process(*value).as_bool(); - valid_options++; - } + } else + throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); } - if(valid_options!=options->count()) - throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); } const String* file_name; @@ -1096,7 +1145,7 @@ static gdImage* load(Request& r, const S static void _load(Request& r, MethodParams& params) { - const String& file_name=params.as_string(0, FILE_NAME_MUST_NOT_BE_CODE); + const String& file_name=params.as_file_name(0); gdImage* image=load(r, file_name); GET_SELF(r, VImage).set(&file_name, image->SX(), image->SY(), image); @@ -1347,12 +1396,12 @@ void Font::string_display(gdImage& image static void _font(Request& r, MethodParams& params) { - const String& alphabet=params.as_string(0, "alphabet must not be code"); + const String& alphabet=params.as_string(0, "alphabet must be string"); size_t alphabet_length=alphabet.length(r.charsets.source()); if(!alphabet_length) throw Exception(PARSER_RUNTIME, 0, "alphabet must not be empty"); - gdImage* image=load(r, params.as_string(1, FILE_NAME_MUST_NOT_BE_CODE)); + gdImage* image=load(r, params.as_file_name(1)); int spacebar_width=image->SX(); int monospace_width=0; // proportional @@ -1399,14 +1448,14 @@ static void _font(Request& r, MethodPara static void _text(Request& r, MethodParams& params) { int x=params.as_int(0, "x must be int", r); int y=params.as_int(1, "y must be int", r); - const String& s=params.as_string(2, "text must not be code"); + const String& s=params.as_string(2, "text must be string"); VImage& vimage=GET_SELF(r, VImage); vimage.font().string_display(vimage.image(), x, y, s); } static void _length(Request& r, MethodParams& params) { - const String& s=params.as_string(0, "text must not be code"); + const String& s=params.as_string(0, "text must be string"); VImage& vimage=GET_SELF(r, VImage); r.write(*new VInt(vimage.font().string_width(s)));