[
https://issues.apache.org/jira/browse/IMAGING-319?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17491005#comment-17491005
]
Gary Lucas edited comment on IMAGING-319 at 2/11/22, 4:06 PM:
--------------------------------------------------------------
Okay, found it.
In the code below, the method looped through all the available free elements
and found one it calls "bestFit". It is going to store the new data into the
available space. But TIFF files have a rule that the offsets have to be an
even multiple of 2. So there's a check to see if the offset is odd and, if it
is, the code advances the offset forward one. The problem is that it doesn't
recognize that by advancing the offset, it's reduced the amount of available
space (bestFit.length). So, the "excessLength" computation below will be
incorrect. If some subsequent element is an exact match for the incorrect
excessLength value, it will overwrite the unused space and clobber whatever
follows. In this case, the thing that got clobbered was the first byte of
EXIF tag 0x9010.
The probability of this happening is small, but non zero. It is just luck that
Sicheng Yang's data sample triggered the issue.
long offset = bestFit.offset;
if ((offset & 1L) != 0) {
offset += 1;
}
outputItem.setOffset(offset);
unusedElements.remove(bestFit);
if (bestFit.length > outputItemLength) {
// not a perfect fit.
final long excessOffset = bestFit.offset + outputItemLength;
final int excessLength = bestFit.length - outputItemLength;
unusedElements.add(new TiffElement.Stub(excessOffset,
excessLength));
// make sure the new element is in the correct order.
unusedElements.sort(ELEMENT_SIZE_COMPARATOR);
Collections.reverse(unusedElements);
}
I re-wrote the code as follows. It works. Writing a JUnit test for this is
going to be extremely difficult unless we want to include the large test file
in our code distribution (which I think would be a bad idea).
long offset = bestFit.offset;
int length = bestFit.length;
if ((offset & 1L) != 0) {
// offsets have to be at a multiple of 2
offset += 1;
length -=1;
}
outputItem.setOffset(offset);
unusedElements.remove(bestFit);
if (length > outputItemLength) {
// not a perfect fit.
final long excessOffset = offset + outputItemLength;
final int excessLength = length - outputItemLength;
unusedElements.add(new TiffElement.Stub(excessOffset,
excessLength));
// make sure the new element is in the correct order.
unusedElements.sort(ELEMENT_SIZE_COMPARATOR);
Collections.reverse(unusedElements);
}
was (Author: gwlucas):
Okay, found it.
In the code below, the method looped through all the available free elements
and found one it calls "bestFit". It is going to store the new data into the
available space. But TIFF files have a rule that the offsets have to be an
even multiple of 2. So there's a check to see if the offset is odd and, if it
is, the code advances the offset forward one. The problem is that it doesn't
recognize that by advancing the offset, it's reduced the amount of available
space (bestFit.length). So, the "excessLength" computation below will be
incorrect. If some subsequent element is an exact match for the incorrect
excessLength value, it will overwrite the unused space and clobber whatever
follows. In this case, the thing that got clobbered was the first byte of
EXIF tag 0x9010.
The probability of this happening is small, but non zero. It is just luck that
Sicheng Yang's data sample triggered the issue.
{quote} long offset = bestFit.offset;
if ((offset & 1L) != 0) {
offset += 1;
}
outputItem.setOffset(offset);
unusedElements.remove(bestFit);
if (bestFit.length > outputItemLength) {
// not a perfect fit.
final long excessOffset = bestFit.offset + outputItemLength;
final int excessLength = bestFit.length - outputItemLength;
unusedElements.add(new TiffElement.Stub(excessOffset,
excessLength));
// make sure the new element is in the correct order.
unusedElements.sort(ELEMENT_SIZE_COMPARATOR);
Collections.reverse(unusedElements);
}
}
{quote}
I re-wrote the code as follows. It works. Writing a JUnit test for this is
going to be extremely difficult.
{quote} unusedElements.remove(bestFit);
long offset = bestFit.offset;
int length = bestFit.length;
if ((offset & 1L) != 0) {
// offsets have to be at a multiple of 2
offset += 1;
length -=1;
}
outputItem.setOffset(offset);
if (length > outputItemLength) {
// not a perfect fit.
final long excessOffset = offset + outputItemLength;
final int excessLength = length - outputItemLength;
unusedElements.add(new TiffElement.Stub(excessOffset,
excessLength));
// make sure the new element is in the correct order.
unusedElements.sort(ELEMENT_SIZE_COMPARATOR);
Collections.reverse(unusedElements);
}
{quote}
> updateExifMetadataLossless lost the first character of a String
> ---------------------------------------------------------------
>
> Key: IMAGING-319
> URL: https://issues.apache.org/jira/browse/IMAGING-319
> Project: Commons Imaging
> Issue Type: Bug
> Components: Format: JPEG
> Affects Versions: 1.0-alpha2
> Reporter: Sicheng Yang
> Priority: Major
> Attachments: Screen Shot 2021-11-26 at 4.01.06 PM-1.png, Screen Shot
> 2021-11-26 at 4.01.21 PM-1.png, iPhone12-geotag.JPG
>
>
> I try to use TiffOutputSet to generate a new image. However, if a tag that
> contains String, the program may miss the first character of the String.
>
> import java.io.*;
> import org.apache.commons.imaging.ImageReadException;
> import org.apache.commons.imaging.ImageWriteException;
> import org.apache.commons.imaging.Imaging;
> import org.apache.commons.imaging.common.ImageMetadata;
> import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
> import org.apache.commons.imaging.formats.jpeg.exif.ExifRewriter;
> import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
> import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
> public class LibraryTest {
> public static void main(String[] args) throws ImageReadException,
> IOException, ImageWriteException {
> File source = new File("./assets/iPhone12-geotag.JPG");
> File result = new
> File("./assets/results/editted-iPhone12-geotag.JPG");
> final ImageMetadata metadata = Imaging.getMetadata(source);
> final JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
> final TiffImageMetadata exif = jpegMetadata.getExif();
> TiffOutputSet outputSet = exif.getOutputSet();
> BufferedOutputStream bufferedOutputStream = new
> BufferedOutputStream(new FileOutputStream(result));
> new ExifRewriter().updateExifMetadataLossless(source,
> bufferedOutputStream, outputSet);
> }
> }
>
> This is the sample code.
> Tag value in original image
> !image-2021-11-26-16-01-58-645.png!
> Tag value in output image
> !image-2021-11-26-16-04-12-185.png!
--
This message was sent by Atlassian Jira
(v8.20.1#820001)