mirror of https://github.com/halo-dev/halo
Remove exif in image file (#2168)
* Issue-1790[feature] add pre handler for attachment files * Issue-2121 remove exif from JPEG * Issue-2121 add an 'enable' option in preHandler * Issue-2121 add an 'enable' option in preHandler * Issue-2121 remove useless code * Issue-2121 ImageFileMultipartFile * Issue-2121 change method name * Issue-1720 remove useless annotation and add author on file * Issue-1720 1. Modify for code review. 2. Keep 'Orientation' Exif tag for images. * Issue-1720 for review * Issue-1720 for review * Issue-1720 remove all Exifpull/2185/head
parent
eab2ee2903
commit
f5d35dd26d
|
@ -75,6 +75,7 @@ ext {
|
||||||
jsoupVersion = '1.14.3'
|
jsoupVersion = '1.14.3'
|
||||||
embeddedRedisVersion = '0.6'
|
embeddedRedisVersion = '0.6'
|
||||||
diffUtilsVersion = '4.11'
|
diffUtilsVersion = '4.11'
|
||||||
|
commonsImagingVersion = "1.0-alpha3"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -125,6 +126,7 @@ dependencies {
|
||||||
implementation "org.flywaydb:flyway-core:$flywayVersion"
|
implementation "org.flywaydb:flyway-core:$flywayVersion"
|
||||||
implementation "com.google.zxing:core:$zxingVersion"
|
implementation "com.google.zxing:core:$zxingVersion"
|
||||||
implementation "io.github.java-diff-utils:java-diff-utils:$diffUtilsVersion"
|
implementation "io.github.java-diff-utils:java-diff-utils:$diffUtilsVersion"
|
||||||
|
implementation "org.apache.commons:commons-imaging:$commonsImagingVersion"
|
||||||
|
|
||||||
implementation "org.iq80.leveldb:leveldb:$levelDbVersion"
|
implementation "org.iq80.leveldb:leveldb:$levelDbVersion"
|
||||||
runtimeOnly "com.h2database:h2:$h2Version"
|
runtimeOnly "com.h2database:h2:$h2Version"
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package run.halo.app.handler.file;
|
package run.halo.app.handler.file;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
@ -10,8 +12,11 @@ import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import run.halo.app.exception.BadRequestException;
|
||||||
import run.halo.app.exception.FileOperationException;
|
import run.halo.app.exception.FileOperationException;
|
||||||
import run.halo.app.exception.RepeatTypeException;
|
import run.halo.app.exception.RepeatTypeException;
|
||||||
|
import run.halo.app.handler.prehandler.ByteMultipartFile;
|
||||||
|
import run.halo.app.handler.prehandler.FilePreHandlers;
|
||||||
import run.halo.app.model.entity.Attachment;
|
import run.halo.app.model.entity.Attachment;
|
||||||
import run.halo.app.model.enums.AttachmentType;
|
import run.halo.app.model.enums.AttachmentType;
|
||||||
import run.halo.app.model.support.UploadResult;
|
import run.halo.app.model.support.UploadResult;
|
||||||
|
@ -26,6 +31,9 @@ import run.halo.app.model.support.UploadResult;
|
||||||
@Component
|
@Component
|
||||||
public class FileHandlers {
|
public class FileHandlers {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private FilePreHandlers filePreHandlers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File handler container.
|
* File handler container.
|
||||||
*/
|
*/
|
||||||
|
@ -50,6 +58,13 @@ public class FileHandlers {
|
||||||
@NonNull
|
@NonNull
|
||||||
public UploadResult upload(@NonNull MultipartFile file,
|
public UploadResult upload(@NonNull MultipartFile file,
|
||||||
@NonNull AttachmentType attachmentType) {
|
@NonNull AttachmentType attachmentType) {
|
||||||
|
try {
|
||||||
|
byte[] bytes = filePreHandlers.process(file.getBytes());
|
||||||
|
file = new ByteMultipartFile(bytes, file.getOriginalFilename(), file.getName(),
|
||||||
|
file.getContentType());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new BadRequestException("Get file bytes for preprocess failed", e);
|
||||||
|
}
|
||||||
return getSupportedType(attachmentType).upload(file);
|
return getSupportedType(attachmentType).upload(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,4 +109,5 @@ public class FileHandlers {
|
||||||
}
|
}
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
package run.halo.app.handler.prehandler;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.util.FileCopyUtils;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author eziosudo
|
||||||
|
* @date 2022-06-19
|
||||||
|
*/
|
||||||
|
public class ByteMultipartFile implements MultipartFile {
|
||||||
|
|
||||||
|
private final byte[] imgContent;
|
||||||
|
private final String originalFilename;
|
||||||
|
private final String name;
|
||||||
|
private final String contentType;
|
||||||
|
|
||||||
|
public ByteMultipartFile(byte[] imgContent, String originalFilename, String name,
|
||||||
|
String contentType) {
|
||||||
|
this.imgContent = imgContent;
|
||||||
|
this.originalFilename = originalFilename;
|
||||||
|
this.name = name;
|
||||||
|
this.contentType = contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getOriginalFilename() {
|
||||||
|
return this.originalFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getContentType() {
|
||||||
|
return this.contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return imgContent == null || imgContent.length == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getSize() {
|
||||||
|
return imgContent.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBytes() throws IOException {
|
||||||
|
return imgContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream() throws IOException {
|
||||||
|
return new ByteArrayInputStream(imgContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transferTo(@NotNull File dest) throws IOException, IllegalStateException {
|
||||||
|
FileCopyUtils.copy(imgContent, dest);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package run.halo.app.handler.prehandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author eziosudo
|
||||||
|
* @date 2022-06-16
|
||||||
|
*/
|
||||||
|
public interface FilePreHandler {
|
||||||
|
|
||||||
|
byte[] preProcess(byte[] bytes);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package run.halo.app.handler.prehandler;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author eziosudo
|
||||||
|
* @date 2022-06-16
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class FilePreHandlers {
|
||||||
|
|
||||||
|
private final List<FilePreHandler> preHandlers;
|
||||||
|
|
||||||
|
FilePreHandlers(ApplicationContext applicationContext) {
|
||||||
|
preHandlers =
|
||||||
|
applicationContext.getBeanProvider(FilePreHandler.class)
|
||||||
|
.orderedStream()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 遍历预处理方法,对输入文件进行预处理
|
||||||
|
*
|
||||||
|
* @param bytes 输入文件字节流
|
||||||
|
* @return 输出预处理后的字节流
|
||||||
|
*/
|
||||||
|
public byte[] process(byte[] bytes) {
|
||||||
|
for (FilePreHandler filePreHandler : preHandlers) {
|
||||||
|
bytes = filePreHandler.preProcess(bytes);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package run.halo.app.handler.prehandler;
|
||||||
|
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
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.TiffOutputDirectory;
|
||||||
|
import org.apache.commons.imaging.formats.tiff.write.TiffOutputField;
|
||||||
|
import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import run.halo.app.model.properties.AttachmentProperties;
|
||||||
|
import run.halo.app.service.OptionService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author eziosudo
|
||||||
|
* @date 2022-06-16
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@Order(value = 1)
|
||||||
|
public class PictureExifRemovalPreHandler implements FilePreHandler {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OptionService optionService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] preProcess(byte[] bytes) {
|
||||||
|
if (!isRemoveExifEnable()) {
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ImageMetadata metadata = Imaging.getMetadata(bytes);
|
||||||
|
final JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
|
||||||
|
if (null == jpegMetadata) {
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
final TiffImageMetadata exif = jpegMetadata.getExif();
|
||||||
|
if (null == exif) {
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
final TiffOutputSet outputSet = exif.getOutputSet();
|
||||||
|
if (null == outputSet) {
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
OutputStream os = new BufferedOutputStream(bos)) {
|
||||||
|
for (TiffOutputDirectory directory : outputSet.getDirectories()) {
|
||||||
|
for (TiffOutputField field : directory.getFields()) {
|
||||||
|
if (!StringUtils.equalsAnyIgnoreCase("Orientation", field.tagInfo.name)) {
|
||||||
|
outputSet.removeField(field.tagInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new ExifRewriter().updateExifMetadataLossless(bytes, os, outputSet);
|
||||||
|
bytes = bos.toByteArray();
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.info("Cannot parse to image format.");
|
||||||
|
} catch (ImageWriteException | ImageReadException | IOException e) {
|
||||||
|
log.info("Cannot get metadata from bytes.", e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.info("Cannot check or remove Exif from bytes.", e);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRemoveExifEnable() {
|
||||||
|
return optionService.getByPropertyOrDefault(
|
||||||
|
AttachmentProperties.REMOVE_IMAGE_EXIF_ENABLE,
|
||||||
|
Boolean.class, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -15,6 +15,11 @@ public enum AttachmentProperties implements PropertyEnum {
|
||||||
*/
|
*/
|
||||||
UPLOAD_IMAGE_PREVIEW_ENABLE("attachment_upload_image_preview_enable", Boolean.class, "true"),
|
UPLOAD_IMAGE_PREVIEW_ENABLE("attachment_upload_image_preview_enable", Boolean.class, "true"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove Exif in image when upload
|
||||||
|
*/
|
||||||
|
REMOVE_IMAGE_EXIF_ENABLE("attachment_EXIF_remove_enable", Boolean.class, "false"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload max parallel uploads
|
* Upload max parallel uploads
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue