Skip to content

Commit f326e78

Browse files
Michael Straußkevinrushforth
authored andcommitted
8277572: ImageStorage should correctly handle MIME types for images encoded in data URIs
Reviewed-by: kcr, arapte
1 parent 6f28d91 commit f326e78

File tree

16 files changed

+258
-96
lines changed

16 files changed

+258
-96
lines changed

modules/javafx.graphics/src/main/java/com/sun/javafx/iio/ImageFormatDescription.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -56,13 +56,13 @@ public interface ImageFormatDescription {
5656
*/
5757
List<Signature> getSignatures();
5858

59-
// /**
60-
// * Get the MIME type(s) corresponding to this format, for example,
61-
// * "image/jpeg," "image/png," etc.
62-
// *
63-
// * @return the MIME type(s) of this format.
64-
// */
65-
// String[] getMIMETypes();
59+
/**
60+
* Get the MIME subtype(s) of the "image" type corresponding to this format,
61+
* for example, "jpeg" "png," etc.
62+
*
63+
* @return the MIME type(s) of this format.
64+
*/
65+
List<String> getMIMESubtypes();
6666

6767
/**
6868
* Represents a sequences of bytes which can appear at the beginning of

modules/javafx.graphics/src/main/java/com/sun/javafx/iio/ImageStorage.java

Lines changed: 100 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,12 @@
3333
import com.sun.javafx.iio.ios.IosImageLoaderFactory;
3434
import com.sun.javafx.iio.jpeg.JPEGImageLoaderFactory;
3535
import com.sun.javafx.iio.png.PNGImageLoaderFactory;
36+
import com.sun.javafx.logging.PlatformLogger;
3637
import com.sun.javafx.util.DataURI;
38+
import com.sun.javafx.util.Logging;
3739

3840
import java.io.ByteArrayInputStream;
41+
import java.io.EOFException;
3942
import java.io.IOException;
4043
import java.io.InputStream;
4144
import java.io.SequenceInputStream;
@@ -110,20 +113,32 @@ public static enum ImageType {
110113
*/
111114
RGBA_PRE
112115
};
113-
/**
114-
* A mapping of lower case file extensions to loader factories.
115-
*/
116+
// /**
117+
// * A mapping of lower case file extensions to loader factories.
118+
// */
116119
// private static HashMap<String, ImageLoaderFactory> loaderFactoriesByExtension;
117120
/**
118121
* A mapping of format signature byte sequences to loader factories.
119122
*/
120-
private static final HashMap<Signature, ImageLoaderFactory> loaderFactoriesBySignature;
121-
private static final ImageLoaderFactory[] loaderFactories;
123+
private final HashMap<Signature, ImageLoaderFactory> loaderFactoriesBySignature;
124+
/**
125+
* A mapping of lower case MIME subtypes to loader factories.
126+
*/
127+
private final HashMap<String, ImageLoaderFactory> loaderFactoriesByMimeSubtype;
128+
private final ImageLoaderFactory[] loaderFactories;
129+
private int maxSignatureLength;
130+
122131
private static final boolean isIOS = PlatformUtil.isIOS();
123132

124-
private static int maxSignatureLength;
133+
private static class InstanceHolder {
134+
static final ImageStorage INSTANCE = new ImageStorage();
135+
}
136+
137+
public static ImageStorage getInstance() {
138+
return InstanceHolder.INSTANCE;
139+
}
125140

126-
static {
141+
public ImageStorage() {
127142
if (isIOS) {
128143
//On iOS we have single factory/ native loader
129144
//for all image formats
@@ -141,14 +156,15 @@ public static enum ImageType {
141156
}
142157

143158
// loaderFactoriesByExtension = new HashMap(numExtensions);
144-
loaderFactoriesBySignature = new HashMap<Signature, ImageLoaderFactory>(loaderFactories.length);
159+
loaderFactoriesBySignature = new HashMap<>(loaderFactories.length);
160+
loaderFactoriesByMimeSubtype = new HashMap<>(loaderFactories.length);
145161

146162
for (int i = 0; i < loaderFactories.length; i++) {
147163
addImageLoaderFactory(loaderFactories[i]);
148164
}
149165
}
150166

151-
public static ImageFormatDescription[] getSupportedDescriptions() {
167+
public ImageFormatDescription[] getSupportedDescriptions() {
152168
ImageFormatDescription[] formats = new ImageFormatDescription[loaderFactories.length];
153169
for (int i = 0; i < loaderFactories.length; i++) {
154170
formats[i] = loaderFactories[i].getFormatDescription();
@@ -162,7 +178,7 @@ public static ImageFormatDescription[] getSupportedDescriptions() {
162178
* @param type the type of image
163179
* @return the number of bands of a raw image of this type
164180
*/
165-
public static int getNumBands(ImageType type) {
181+
public int getNumBands(ImageType type) {
166182
int numBands = -1;
167183
switch (type) {
168184
case GRAY:
@@ -191,12 +207,12 @@ public static int getNumBands(ImageType type) {
191207

192208
/**
193209
* Registers an image loader factory. The factory replaces any other factory
194-
* previously registered for the file extensions (converted to lower case)
195-
* and signature indicated by the format description.
210+
* previously registered for the file extensions (converted to lower case),
211+
* MIME subtype, and signature indicated by the format description.
196212
*
197213
* @param factory the factory to register.
198214
*/
199-
public static void addImageLoaderFactory(ImageLoaderFactory factory) {
215+
public void addImageLoaderFactory(ImageLoaderFactory factory) {
200216
ImageFormatDescription desc = factory.getFormatDescription();
201217
// String[] extensions = desc.getExtensions();
202218
// for (int j = 0; j < extensions.length; j++) {
@@ -207,6 +223,10 @@ public static void addImageLoaderFactory(ImageLoaderFactory factory) {
207223
loaderFactoriesBySignature.put(signature, factory);
208224
}
209225

226+
for (String subtype : desc.getMIMESubtypes()) {
227+
loaderFactoriesByMimeSubtype.put(subtype.toLowerCase(), factory);
228+
}
229+
210230
// invalidate max signature length
211231
synchronized (ImageStorage.class) {
212232
maxSignatureLength = -1;
@@ -254,7 +274,7 @@ public static void addImageLoaderFactory(ImageLoaderFactory factory) {
254274
* @return the sequence of all images in the specified source or
255275
* <code>null</code> on error.
256276
*/
257-
public static ImageFrame[] loadAll(InputStream input, ImageLoadListener listener,
277+
public ImageFrame[] loadAll(InputStream input, ImageLoadListener listener,
258278
double width, double height, boolean preserveAspectRatio,
259279
float pixelScale, boolean smooth) throws ImageStorageException {
260280
ImageLoader loader = null;
@@ -289,7 +309,7 @@ public static ImageFrame[] loadAll(InputStream input, ImageLoadListener listener
289309
* Load all images present in the specified input. For more details refer to
290310
* {@link #loadAll(InputStream, ImageLoadListener, double, double, boolean, float, boolean)}.
291311
*/
292-
public static ImageFrame[] loadAll(String input, ImageLoadListener listener,
312+
public ImageFrame[] loadAll(String input, ImageLoadListener listener,
293313
double width, double height, boolean preserveAspectRatio,
294314
float devPixelScale, boolean smooth) throws ImageStorageException {
295315

@@ -304,38 +324,69 @@ public static ImageFrame[] loadAll(String input, ImageLoadListener listener,
304324
try {
305325
float imgPixelScale = 1.0f;
306326
try {
307-
if (devPixelScale >= 1.5f) {
308-
// Use Mac Retina conventions for >= 1.5f
309-
try {
310-
String name2x = ImageTools.getScaledImageName(input);
311-
theStream = ImageTools.createInputStream(name2x);
312-
imgPixelScale = 2.0f;
313-
} catch (IOException ignored) {
327+
DataURI dataUri = DataURI.tryParse(input);
328+
if (dataUri != null) {
329+
if (!"image".equalsIgnoreCase(dataUri.getMimeType())) {
330+
throw new IllegalArgumentException("Unexpected MIME type: " + dataUri.getMimeType());
314331
}
315-
}
316332

317-
if (theStream == null) {
318-
try {
319-
theStream = ImageTools.createInputStream(input);
320-
} catch (IOException ex) {
321-
DataURI dataUri = DataURI.tryParse(input);
322-
if (dataUri != null) {
323-
String mimeType = dataUri.getMimeType();
324-
if (mimeType != null && !"image".equalsIgnoreCase(dataUri.getMimeType())) {
325-
throw new IllegalArgumentException("Unexpected MIME type: " + dataUri.getMimeType());
333+
// Find a factory that can load images with the specified MIME type.
334+
var factory = loaderFactoriesByMimeSubtype.get(dataUri.getMimeSubtype().toLowerCase());
335+
if (factory == null) {
336+
throw new IllegalArgumentException(
337+
"Unsupported MIME subtype: image/" + dataUri.getMimeSubtype());
338+
}
339+
340+
// We also inspect the image file signature to confirm that it matches the MIME type.
341+
theStream = new ByteArrayInputStream(dataUri.getData());
342+
ImageLoader loaderBySignature = getLoaderBySignature(theStream, listener);
343+
344+
if (loaderBySignature != null) {
345+
// If the MIME type doesn't agree with the file signature, log a warning and
346+
// continue with the image loader that matches the file signature.
347+
boolean imageTypeMismatch = !factory.getFormatDescription().getFormatName().equals(
348+
loaderBySignature.getFormatDescription().getFormatName());
349+
350+
if (imageTypeMismatch) {
351+
var logger = Logging.getJavaFXLogger();
352+
if (logger.isLoggable(PlatformLogger.Level.WARNING)) {
353+
logger.warning(String.format(
354+
"Image format '%s' does not match MIME type '%s/%s' in URI '%s'",
355+
loaderBySignature.getFormatDescription().getFormatName(),
356+
dataUri.getMimeType(), dataUri.getMimeSubtype(), dataUri));
326357
}
358+
}
327359

328-
theStream = new ByteArrayInputStream(dataUri.getData());
329-
} else {
330-
throw ex;
360+
loader = loaderBySignature;
361+
} else {
362+
// We're here because the image format doesn't have a detectable signature.
363+
// In this case, we need to close the input stream (because we already consumed
364+
// parts of it to detect a potential file signature) and create a new input
365+
// stream for the image loader that matches the MIME type.
366+
theStream.close();
367+
theStream = new ByteArrayInputStream(dataUri.getData());
368+
loader = factory.createImageLoader(theStream);
369+
}
370+
} else {
371+
if (devPixelScale >= 1.5f) {
372+
// Use Mac Retina conventions for >= 1.5f
373+
try {
374+
String name2x = ImageTools.getScaledImageName(input);
375+
theStream = ImageTools.createInputStream(name2x);
376+
imgPixelScale = 2.0f;
377+
} catch (IOException ignored) {
331378
}
332379
}
333-
}
334380

335-
if (isIOS) {
336-
loader = IosImageLoaderFactory.getInstance().createImageLoader(theStream);
337-
} else {
338-
loader = getLoaderBySignature(theStream, listener);
381+
if (theStream == null) {
382+
theStream = ImageTools.createInputStream(input);
383+
}
384+
385+
if (isIOS) {
386+
loader = IosImageLoaderFactory.getInstance().createImageLoader(theStream);
387+
} else {
388+
loader = getLoaderBySignature(theStream, listener);
389+
}
339390
}
340391
} catch (Exception e) {
341392
throw new ImageStorageException(e.getMessage(), e);
@@ -361,7 +412,7 @@ public static ImageFrame[] loadAll(String input, ImageLoadListener listener,
361412
return images;
362413
}
363414

364-
private static synchronized int getMaxSignatureLength() {
415+
private synchronized int getMaxSignatureLength() {
365416
if (maxSignatureLength < 0) {
366417
maxSignatureLength = 0;
367418
for (final Signature signature:
@@ -376,7 +427,7 @@ private static synchronized int getMaxSignatureLength() {
376427
return maxSignatureLength;
377428
}
378429

379-
private static ImageFrame[] loadAll(ImageLoader loader,
430+
private ImageFrame[] loadAll(ImageLoader loader,
380431
double width, double height, boolean preserveAspectRatio,
381432
float pixelScale, boolean smooth) throws ImageStorageException {
382433
ImageFrame[] images = null;
@@ -433,9 +484,14 @@ private static ImageFrame[] loadAll(ImageLoader loader,
433484
// return loader;
434485
// }
435486

436-
private static ImageLoader getLoaderBySignature(InputStream stream, ImageLoadListener listener) throws IOException {
487+
private ImageLoader getLoaderBySignature(InputStream stream, ImageLoadListener listener) throws IOException {
437488
byte[] header = new byte[getMaxSignatureLength()];
438-
ImageTools.readFully(stream, header);
489+
490+
try {
491+
ImageTools.readFully(stream, header);
492+
} catch (EOFException ignored) {
493+
return null;
494+
}
439495

440496
for (final Entry<Signature, ImageLoaderFactory> factoryRegistration:
441497
loaderFactoriesBySignature.entrySet()) {
@@ -454,7 +510,4 @@ private static ImageLoader getLoaderBySignature(InputStream stream, ImageLoadLis
454510
// not found
455511
return null;
456512
}
457-
458-
private ImageStorage() {
459-
}
460513
}

modules/javafx.graphics/src/main/java/com/sun/javafx/iio/bmp/BMPImageLoaderFactory.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -33,12 +33,13 @@
3333
final class BMPDescriptor extends ImageDescriptor {
3434

3535
static final String formatName = "BMP";
36-
static final String extensions[] = { "bmp" };
37-
static final Signature signatures[] = {new Signature((byte)0x42, (byte)0x4D)};
36+
static final String[] extensions = { "bmp" };
37+
static final Signature[] signatures = {new Signature((byte)0x42, (byte)0x4D)};
38+
static final String[] mimeSubtypes = { "bmp" };
3839
static final ImageDescriptor theInstance = new BMPDescriptor();
3940

4041
private BMPDescriptor() {
41-
super(formatName, extensions, signatures);
42+
super(formatName, extensions, signatures, mimeSubtypes);
4243
}
4344
}
4445

modules/javafx.graphics/src/main/java/com/sun/javafx/iio/common/ImageDescriptor.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -34,15 +34,16 @@ public class ImageDescriptor implements ImageFormatDescription {
3434
private final String formatName;
3535
private final List<String> extensions;
3636
private final List<Signature> signatures;
37-
// private String[] MIMETypes;
37+
private final List<String> mimeSubtypes;
3838

39-
public ImageDescriptor(String formatName, String[] extensions, Signature[] signatures) {
39+
public ImageDescriptor(String formatName, String[] extensions, Signature[] signatures, String[] mimeSubtypes) {
4040
this.formatName = formatName;
4141
this.extensions = Collections.unmodifiableList(
4242
Arrays.asList(extensions));
4343
this.signatures = Collections.unmodifiableList(
4444
Arrays.asList(signatures));
45-
// this.MIMETypes = MIMETypes;
45+
this.mimeSubtypes = Collections.unmodifiableList(
46+
Arrays.asList(mimeSubtypes));
4647
}
4748

4849
public String getFormatName() {
@@ -57,7 +58,7 @@ public List<Signature> getSignatures() {
5758
return signatures;
5859
}
5960

60-
// public String[] getMIMETypes() {
61-
// return MIMETypes;
62-
// }
61+
public List<String> getMIMESubtypes() {
62+
return mimeSubtypes;
63+
}
6364
}

modules/javafx.graphics/src/main/java/com/sun/javafx/iio/common/ImageTools.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -697,7 +697,7 @@ public static int[] computeDimensions(int sourceWidth, int sourceHeight,
697697
public static ImageFrame scaleImageFrame(ImageFrame src,
698698
int destWidth, int destHeight, boolean isSmooth)
699699
{
700-
int numBands = ImageStorage.getNumBands(src.getImageType());
700+
int numBands = ImageStorage.getInstance().getNumBands(src.getImageType());
701701
ByteBuffer dst = scaleImage((ByteBuffer) src.getImageData(),
702702
src.getWidth(), src.getHeight(), numBands,
703703
destWidth, destHeight, isSmooth);

modules/javafx.graphics/src/main/java/com/sun/javafx/iio/gif/GIFDescriptor.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -37,12 +37,12 @@ public class GIFDescriptor extends ImageDescriptor {
3737
new Signature(new byte[] { 'G', 'I', 'F', '8', '9', 'a' })
3838
};
3939

40-
// private static final String[] MIMETypes = { "image/gif" };
40+
private static final String[] mimeSubtypes = { "gif" };
4141

4242
private static ImageDescriptor theInstance = null;
4343

4444
private GIFDescriptor() {
45-
super(formatName, extensions, signatures);
45+
super(formatName, extensions, signatures, mimeSubtypes);
4646
}
4747

4848
public static synchronized ImageDescriptor getInstance() {

0 commit comments

Comments
 (0)