From 85c861408af92264663f5069618b85cae1669642 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Sun, 17 Jan 2021 18:22:28 +0000 Subject: [PATCH] fpexif: Fix crash when file with incorrectly specified segment length is written. Fix XMP metadata being lost when they coexist together with EXIF. git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7965 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- components/fpexif/fpemetadata.pas | 59 +++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/components/fpexif/fpemetadata.pas b/components/fpexif/fpemetadata.pas index 4a63d567e..4bc56d400 100644 --- a/components/fpexif/fpemetadata.pas +++ b/components/fpexif/fpemetadata.pas @@ -397,11 +397,14 @@ type end; const SOI_MARKER: array[0..1] of byte = ($FF, $D8); + XMP_SIGNATURE = 'http://ns.adobe.com/xap/1.0/'; var header: TSegmentHeader; + headerSize, newHeaderSize: Word; n, count: Int64; savedPos: Int64; jfif: TJpegJFIFSegment; + s: String; begin // Write the header segment and all metadata segments stored in TImgInfo // to the beginning of the stream @@ -415,19 +418,61 @@ begin while AInputStream.Position < AInputStream.Size do begin savedPos := AInputStream.Position; // just for debugging n := AInputStream.Read(header{%H-}, SizeOf(header)); - if n <> Sizeof(header) then + if n <> SizeOf(header) then + // End of file reached, cannot read complete header Error(rsIncompleteJpegSegmentHeader); + + // Some images do not specify the segment length correctly and fill the + // space to the next segment with zero bytes. + if LongInt(header) = 0 then begin + n := 0; + while AInputStream.ReadByte = 0 do + inc(n); + AInputStream.Position := AInputStream.Position - 1; + n := AInputStream.Read(header, SizeOf(Header)); + if n <> SizeOf(Header) then + Error(rsIncompleteJpegSegmentHeader); + end; + if header.Key <> $FF then Error(rsJpegSegmentMarkerExpected); - header.Size := BEToN(header.Size); + headerSize := BEToN(header.Size); // Save stream position before segment size value. savedPos := AInputStream.Position - 2; case header.Marker of M_SOI: - header.Size := 0; - M_JFIF, M_EXIF, M_IPTC, M_COM: // these segments were already written by WriteJpeg + headerSize := 0; + M_JFIF, M_IPTC, M_COM: // These segments were already written by WriteJpeg ; + M_EXIF: + // there may be also the XMP segment which has the same key $E1. + begin + SetLength(s, Length(XMP_SIGNATURE)); + AInputStream.Read(s[1], Length(XMP_SIGNATURE)); + if s = XMP_SIGNATURE then + begin + // XMP segment found --> write it back as it is (needs to be fixed to write current meta data) + newHeaderSize := (Trunc(headerSize / 256) + 1) * 256 - SizeOf(header); + SetLength(s, newHeaderSize - Length(XMP_SIGNATURE)); + FillChar(s[1], Length(s), 0); + AInputStream.Read(s[1], headerSize); + header.Size := NToBE(newHeaderSize); + AOutputStream.Write(header, SizeOf(Header)); + AOutputStream.Write(XMP_SIGNATURE, Length(XMP_SIGNATURE)); + AOutputStream.Write(s[1], Length(s)); + { + AInputStream.Position := savedPos - 4; + n := AOutputStream.CopyFrom(AInputStream, Int64(header.Size) + 2); + if n <> Int64(header.Size) + 2 then + Error(rsJpegReadWriteErrorInSegment); + } + end + else + begin + // EXIF segment found. But this already has been written by WriteJpeg -> nothing to do + end; + end; M_SOS: begin // this is the last segment before compressed data which don't have a marker @@ -442,11 +487,11 @@ begin end; else AInputStream.Position := AInputStream.Position - 4; // go back to where the segment begins - n := AOutputStream.CopyFrom(AInputStream, Int64(header.Size) + 2); - if n <> Int64(header.Size) + 2 then + n := AOutputStream.CopyFrom(AInputStream, headerSize + 2); + if n <> Int64(headerSize) + 2 then Error(rsJpegReadWriteErrorInSegment); end; - AInputStream.Position := savedPos + header.Size; + AInputStream.Position := savedPos + headerSize; end; end;