package org.ovirt.engine.core.bll.validator;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.bll.ValidationResult;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dal.dbbroker.DbFacade;
public class IconValidator {
private static final int MAX_DATAURL_SIZE = 32 * 1024;
private final DimensionsType dimensionsType;
private String dataUrl;
private String mimeType;
private String base64Data;
private byte[] rawImageData;
private FileType imageType;
private BufferedImage image;
private ValidationResult validationResult = ValidationResult.VALID;
private IconValidator(DimensionsType dimensionsType, String dataUrl) {
this.dimensionsType = dimensionsType;
this.dataUrl = dataUrl;
validateDataUrlFormat(dataUrl);
if (!validationResult.isValid()) {
return;
}
validateBase64();
validateImageType();
if (!validationResult.isValid()) {
return;
}
validateParsability();
if (!validationResult.isValid()) {
return;
}
validateMimeType();
if (!validationResult.isValid()) {
return;
}
validateDimensions();
if (!validationResult.isValid()) {
return;
}
validateDataSize();
}
public static ValidationResult validate(DimensionsType iconType, String dataUrl) {
return new IconValidator(iconType, dataUrl).getValidationResult();
}
public static ValidationResult validateIconId(Guid iconId, String nameForErrorMessage) {
if (DbFacade.getInstance().getVmIconDao().exists(iconId)) {
return ValidationResult.VALID;
}
return new ValidationResult(EngineMessage.ICON_OF_PROVIDED_ID_DOES_NOT_EXIST,
"$iconName " + nameForErrorMessage);
}
public ValidationResult getValidationResult() {
return validationResult;
}
private void validateDataUrlFormat(String dataUrl) {
final String dataUrlRegex = "^data:(\\w+/\\w+);base64,([\\w+/]+={0,2})$";
final Matcher matcher = Pattern.compile(dataUrlRegex).matcher(dataUrl);
final boolean matches = matcher.find();
if (!matches) {
validationResult = new ValidationResult(EngineMessage.VM_ICON_DATAURL_MALFORMED);
return;
}
mimeType = matcher.group(1);
base64Data = matcher.group(2);
}
private void validateBase64() {
try {
rawImageData = DatatypeConverter.parseBase64Binary(base64Data);
} catch (ArrayIndexOutOfBoundsException | IllegalArgumentException e) {
validationResult = new ValidationResult(EngineMessage.VM_ICON_BASE64_PART_MALFORMED);
}
}
private void validateImageType() {
try {
final InputStream inputStream = new ByteArrayInputStream(rawImageData);
final ImageInputStream imageInputStream = ImageIO.createImageInputStream(inputStream);
final Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
if (!imageReaders.hasNext()) {
validationResult = new ValidationResult(EngineMessage.PROVIDED_VM_ICON_OF_UNKNOWN_TYPE);
return;
}
final String formatName = imageReaders.next().getFormatName();
imageType = FileType.getByFormatName(formatName);
if (imageType == null) {
validationResult = new ValidationResult(EngineMessage.PROVIDED_VM_ICONS_OF_UNSUPPORTED_TYPE,
"$fileType " + formatName,
"$supportedFileTypes " + FileType.getSupportedTypes());
}
} catch (IOException e) {
validationResult = new ValidationResult(EngineMessage.PROVIDED_VM_ICON_CANT_BE_READ);
}
}
private void validateParsability() {
try {
image = ImageIO.read(new ByteArrayInputStream(rawImageData));
} catch (RuntimeException | IOException e) {
validationResult = new ValidationResult(EngineMessage.PROVIDED_VM_ICON_CANT_BE_READ);
}
}
private void validateMimeType() {
if (!imageType.getMimeType().equals(mimeType)) {
validationResult = new ValidationResult(EngineMessage.VM_ICON_MIME_TYPE_DOESNT_MATCH_IMAGE_DATA,
"$mimeType " + mimeType,
"$imageType " + imageType.getMimeType());
}
}
private void validateDimensions() {
boolean dimensionsValid = image.getWidth() >= dimensionsType.getMinWidth()
&& image.getWidth() <= dimensionsType.getMaxWidth()
&& image.getHeight() >= dimensionsType.getMinHeight()
&& image.getHeight() <= dimensionsType.getMaxHeight();
if (!dimensionsValid) {
validationResult = new ValidationResult(EngineMessage.PROVIDED_VM_ICON_HAS_INVALID_DIMENSIONS,
"$allowedDimensions " + "from " + dimensionsType.getMinWidth() + "x" + dimensionsType.getMinHeight()
+ " to " + dimensionsType.getMaxWidth() + "x" + dimensionsType.getMaxHeight(),
"$currentDimensions " + image.getWidth() + "x" + image.getHeight());
}
}
private void validateDataSize() {
if (dataUrl.length() > MAX_DATAURL_SIZE) {
validationResult = new ValidationResult(EngineMessage.DATA_SIZE_OF_PROVIDED_VM_ICON_TOO_LARGE,
"$maxSize " + getSizeEstimateByDataUrlLength(MAX_DATAURL_SIZE),
"$currentSize " + getSizeEstimateByDataUrlLength(dataUrl.length()));
}
}
/**
* @return size estimate in form of 'x kB'
*/
private static String getSizeEstimateByDataUrlLength(int dataUrlLength) {
return "" + (int) ((3.0/4) * dataUrlLength / 1000) + " kB";
}
public enum FileType {
JPG(Arrays.asList("jpg", "jpeg"), "image/jpeg", "JPEG"),
PNG(Arrays.asList("png"), "image/png", "png"),
GIF(Arrays.asList("gif"), "image/gif", "gif");
/**
* lower case
*/
private final List<String> extensions;
private final String mimeType;
/**
* String used to identify image format by {@link javax.imageio.ImageWriter}s
* and {@link javax.imageio.ImageReader}a
*/
private final String formatName;
FileType(List<String> extensions, String mimeType, String formatName) {
this.extensions = extensions;
this.mimeType = mimeType;
this.formatName = formatName;
}
public List<String> getExtensions() {
return extensions;
}
public String getMimeType() {
return mimeType;
}
public String getFormatName() {
return formatName;
}
public static FileType getByFormatName(String formatName) {
for (FileType type : FileType.values()) {
if (type.getFormatName().equals(formatName)) {
return type;
}
}
return null;
}
public static String getSupportedTypes() {
List<String> supportedTypeNames = new ArrayList<>();
for (FileType type : FileType.values()) {
supportedTypeNames.add(type.toString().toLowerCase());
}
return StringUtils.join(supportedTypeNames, ", ");
}
}
public static enum DimensionsType {
SMALL_PREDEFINED_ICON(43, 43, 43, 43),
LARGE_PREDEFINED_ICON(150, 120, 150, 120),
SMALL_CUSTOM_ICON(1, 1, 43, 43),
LARGE_CUSTOM_ICON(1, 1, 150, 120);
private final int minWidth;
private final int minHeight;
private final int maxWidth;
private final int maxHeight;
DimensionsType(int minWidth, int minHeight, int maxWidth, int maxHeight) {
this.minWidth = minWidth;
this.minHeight = minHeight;
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
}
public int getMinWidth() {
return minWidth;
}
public int getMinHeight() {
return minHeight;
}
public int getMaxWidth() {
return maxWidth;
}
public int getMaxHeight() {
return maxHeight;
}
public boolean isInMaxBounds(RenderedImage image) {
return image.getWidth() <= maxWidth
&& image.getHeight() <= maxHeight;
}
}
}