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'
|
||||
embeddedRedisVersion = '0.6'
|
||||
diffUtilsVersion = '4.11'
|
||||
commonsImagingVersion = "1.0-alpha3"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -125,6 +126,7 @@ dependencies {
|
|||
implementation "org.flywaydb:flyway-core:$flywayVersion"
|
||||
implementation "com.google.zxing:core:$zxingVersion"
|
||||
implementation "io.github.java-diff-utils:java-diff-utils:$diffUtilsVersion"
|
||||
implementation "org.apache.commons:commons-imaging:$commonsImagingVersion"
|
||||
|
||||
implementation "org.iq80.leveldb:leveldb:$levelDbVersion"
|
||||
runtimeOnly "com.h2database:h2:$h2Version"
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package run.halo.app.handler.file;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
@ -10,8 +12,11 @@ import org.springframework.stereotype.Component;
|
|||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import run.halo.app.exception.BadRequestException;
|
||||
import run.halo.app.exception.FileOperationException;
|
||||
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.enums.AttachmentType;
|
||||
import run.halo.app.model.support.UploadResult;
|
||||
|
@ -26,6 +31,9 @@ import run.halo.app.model.support.UploadResult;
|
|||
@Component
|
||||
public class FileHandlers {
|
||||
|
||||
@Autowired
|
||||
private FilePreHandlers filePreHandlers;
|
||||
|
||||
/**
|
||||
* File handler container.
|
||||
*/
|
||||
|
@ -49,7 +57,14 @@ public class FileHandlers {
|
|||
*/
|
||||
@NonNull
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -94,4 +109,5 @@ public class FileHandlers {
|
|||
}
|
||||
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"),
|
||||
|
||||
/**
|
||||
* Remove Exif in image when upload
|
||||
*/
|
||||
REMOVE_IMAGE_EXIF_ENABLE("attachment_EXIF_remove_enable", Boolean.class, "false"),
|
||||
|
||||
/**
|
||||
* Upload max parallel uploads
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue