View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.imaging.formats.gif;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  
26  import java.awt.image.BufferedImage;
27  import java.io.File;
28  import java.io.IOException;
29  import java.util.List;
30  import java.util.stream.Stream;
31  
32  import org.apache.commons.imaging.ImageInfo;
33  import org.apache.commons.imaging.Imaging;
34  import org.apache.commons.imaging.ImagingException;
35  import org.apache.commons.imaging.bytesource.ByteSource;
36  import org.apache.commons.imaging.common.ImageMetadata;
37  import org.apache.commons.imaging.test.TestResources;
38  import org.junit.jupiter.api.Test;
39  import org.junit.jupiter.params.ParameterizedTest;
40  import org.junit.jupiter.params.provider.MethodSource;
41  
42  public class GifReadTest extends AbstractGifTest {
43  
44      public static Stream<File> animatedImageData() throws Exception {
45          return getAnimatedGifImages().stream();
46      }
47  
48      public static Stream<File> data() throws Exception {
49          return getGifImages().stream();
50      }
51  
52      public static Stream<File> singleImageData() throws Exception {
53          return getGifImagesWithSingleImage().stream();
54      }
55  
56      @ParameterizedTest
57      @MethodSource("data")
58      public void testBufferedImage(final File imageFile) throws Exception {
59          final BufferedImage image = Imaging.getBufferedImage(imageFile);
60          assertNotNull(image);
61          // TODO assert more
62      }
63  
64      @ParameterizedTest
65      @MethodSource("animatedImageData")
66      public void testBufferedImagesForAnimatedImageGif(final File imageFile) throws Exception {
67          final List<BufferedImage> images = Imaging.getAllBufferedImages(imageFile);
68          assertTrue(images.size() > 1);
69      }
70  
71      @ParameterizedTest
72      @MethodSource("singleImageData")
73      public void testBufferedImagesForSingleImageGif(final File imageFile) throws Exception {
74          final List<BufferedImage> images = Imaging.getAllBufferedImages(imageFile);
75          assertEquals(1, images.size());
76      }
77  
78      @Test
79      public void testConvertInvalidDisposalMethodValues() {
80          assertThrows(ImagingException.class, () -> GifImageParser.createDisposalMethodFromIntValue(8));
81      }
82  
83      @Test
84      public void testConvertValidDisposalMethodValues() throws ImagingException {
85          final DisposalMethod unspecified = GifImageParser.createDisposalMethodFromIntValue(0);
86          final DisposalMethod doNotDispose = GifImageParser.createDisposalMethodFromIntValue(1);
87          final DisposalMethod restoreToBackground = GifImageParser.createDisposalMethodFromIntValue(2);
88          final DisposalMethod restoreToPrevious = GifImageParser.createDisposalMethodFromIntValue(3);
89          final DisposalMethod toBeDefined1 = GifImageParser.createDisposalMethodFromIntValue(4);
90          final DisposalMethod toBeDefined2 = GifImageParser.createDisposalMethodFromIntValue(5);
91          final DisposalMethod toBeDefined3 = GifImageParser.createDisposalMethodFromIntValue(6);
92          final DisposalMethod toBeDefined4 = GifImageParser.createDisposalMethodFromIntValue(7);
93          assertEquals(unspecified, DisposalMethod.UNSPECIFIED);
94          assertEquals(doNotDispose, DisposalMethod.DO_NOT_DISPOSE);
95          assertEquals(restoreToBackground, DisposalMethod.RESTORE_TO_BACKGROUND);
96          assertEquals(restoreToPrevious, DisposalMethod.RESTORE_TO_PREVIOUS);
97          assertEquals(toBeDefined1, DisposalMethod.TO_BE_DEFINED_1);
98          assertEquals(toBeDefined2, DisposalMethod.TO_BE_DEFINED_2);
99          assertEquals(toBeDefined3, DisposalMethod.TO_BE_DEFINED_3);
100         assertEquals(toBeDefined4, DisposalMethod.TO_BE_DEFINED_4);
101     }
102 
103     @Test
104     public void testCreateMetadataWithDisposalMethods() {
105         for (final DisposalMethod disposalMethod : DisposalMethod.values()) {
106             final GifImageMetadataItem metadataItem = new GifImageMetadataItem(0, 0, 0, disposalMethod);
107             assertEquals(disposalMethod, metadataItem.getDisposalMethod());
108         }
109     }
110 
111     @ParameterizedTest
112     @MethodSource("data")
113     public void testImageDimensions(final File imageFile) throws Exception {
114         final ImageInfo imageInfo = Imaging.getImageInfo(imageFile);
115         final GifImageMetadata metadata = (GifImageMetadata) Imaging.getMetadata(imageFile);
116         final List<BufferedImage> images = Imaging.getAllBufferedImages(imageFile);
117 
118         int width = 0;
119         int height = 0;
120         for (int i = 0; i < images.size(); i++) {
121             final BufferedImage image = images.get(i);
122             final GifImageMetadataItem metadataItem = metadata.getItems().get(i);
123             final int xOffset = metadataItem.getLeftPosition();
124             final int yOffset = metadataItem.getTopPosition();
125             width = Math.max(width, image.getWidth() + xOffset);
126             height = Math.max(height, image.getHeight() + yOffset);
127         }
128 
129         assertEquals(width, metadata.getWidth());
130         assertEquals(height, metadata.getHeight());
131         assertEquals(width, imageInfo.getWidth());
132         assertEquals(height, imageInfo.getHeight());
133     }
134 
135     @ParameterizedTest
136     @MethodSource("data")
137     public void testImageInfo(final File imageFile) throws Exception {
138         final ImageInfo imageInfo = Imaging.getImageInfo(imageFile);
139         assertNotNull(imageInfo);
140         // TODO assert more
141     }
142 
143     @ParameterizedTest
144     @MethodSource("data")
145     public void testMetadata(final File imageFile) throws IOException {
146         final ImageMetadata metadata = Imaging.getMetadata(imageFile);
147         assertNotNull(metadata);
148         assertInstanceOf(GifImageMetadata.class, metadata);
149         assertTrue(((GifImageMetadata) metadata).getWidth() > 0);
150         assertTrue(((GifImageMetadata) metadata).getHeight() > 0);
151         assertNotNull(metadata.getItems());
152     }
153 
154     /**
155      * The GIF image Lzw compression may contain a table with length inferior to the length of entries in the image data. Which results in an
156      * ArrayOutOfBoundsException. This verifies that instead of throwing an AOOBE, we are handling the case and informing the user why the parser failed to read
157      * it, by throwin an ImageReadException with a more descriptive message.
158      *
159      * <p>
160      * See Google OSS Fuzz issue 33464
161      * </p>
162      *
163      * @throws IOException if it fails to read the test image
164      */
165     @Test
166     public void testUncaughtExceptionOssFuzz33464() throws IOException {
167         final File file = TestResources.resourceToFile("/images/gif/oss-fuzz-33464/clusterfuzz-testcase-minimized-ImagingGifFuzzer-5174009164595200");
168         final GifImageParser parser = new GifImageParser();
169         assertThrows(ImagingException.class, () -> parser.getBufferedImage(ByteSource.file(file), new GifImagingParameters()));
170     }
171 
172     /**
173      * The GIF image data may lead to out of bound array access. This test verifies that we handle that case and raise an appropriate exception.
174      *
175      * <p>
176      * See Google OSS Fuzz issue 33501
177      * </p>
178      *
179      * @throws IOException if it fails to read the test image
180      */
181     @Test
182     public void testUncaughtExceptionOssFuzz33501() throws IOException {
183         final File file = TestResources.resourceToFile("/images/gif/oss-fuzz-33501/clusterfuzz-testcase-minimized-ImagingGifFuzzer-5914278319226880");
184         final GifImageParser parser = new GifImageParser();
185         assertThrows(ImagingException.class, () -> parser.getBufferedImage(ByteSource.file(file), new GifImagingParameters()));
186     }
187 
188     /**
189      * Test that invalid indexes are validated when accessing GIF color table array.
190      *
191      * <p>
192      * See Google OSS Fuzz issue 34185
193      * </p>
194      *
195      * @throws IOException if it fails to read the test image
196      */
197     @Test
198     public void testUncaughtExceptionOssFuzz34185() throws IOException {
199         final File file = TestResources.resourceToFile("/images/gif/IMAGING-318/clusterfuzz-testcase-minimized-ImagingGifFuzzer-5005192379629568");
200         final GifImageParser parser = new GifImageParser();
201         assertThrows(ImagingException.class, () -> parser.getBufferedImage(ByteSource.file(file), new GifImagingParameters()));
202     }
203 }