Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat:Remove EXIF information when uploading images, Ignore spaces on both sides of an URL, Ignore case when searching an article #1887

Closed
wants to merge 10 commits into from
39 changes: 26 additions & 13 deletions src/main/java/run/halo/app/handler/file/AliOssFileHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.DeleteObjectsRequest;
import com.aliyun.oss.model.PutObjectResult;
import java.io.File;
import java.io.FileInputStream;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -51,21 +53,21 @@ public AliOssFileHandler(OptionService optionService,
String protocol =
optionService.getByPropertyOfNonNull(AliOssProperties.OSS_PROTOCOL).toString();
String domain =
optionService.getByPropertyOrDefault(AliOssProperties.OSS_DOMAIN, String.class, "");
optionService.getByPropertyOrDefault(AliOssProperties.OSS_DOMAIN, String.class, "").trim();
String source =
optionService.getByPropertyOrDefault(AliOssProperties.OSS_SOURCE, String.class, "");
optionService.getByPropertyOrDefault(AliOssProperties.OSS_SOURCE, String.class, "").trim();
String endPoint =
optionService.getByPropertyOfNonNull(AliOssProperties.OSS_ENDPOINT).toString();
optionService.getByPropertyOfNonNull(AliOssProperties.OSS_ENDPOINT).toString().trim();
String accessKey =
optionService.getByPropertyOfNonNull(AliOssProperties.OSS_ACCESS_KEY).toString();
String accessSecret =
optionService.getByPropertyOfNonNull(AliOssProperties.OSS_ACCESS_SECRET).toString();
String bucketName =
optionService.getByPropertyOfNonNull(AliOssProperties.OSS_BUCKET_NAME).toString();
optionService.getByPropertyOfNonNull(AliOssProperties.OSS_BUCKET_NAME).toString().trim();
String styleRule =
optionService.getByPropertyOrDefault(AliOssProperties.OSS_STYLE_RULE, String.class, "");
optionService.getByPropertyOrDefault(AliOssProperties.OSS_STYLE_RULE, String.class, "").trim();
String thumbnailStyleRule = optionService
.getByPropertyOrDefault(AliOssProperties.OSS_THUMBNAIL_STYLE_RULE, String.class, "");
.getByPropertyOrDefault(AliOssProperties.OSS_THUMBNAIL_STYLE_RULE, String.class, "").trim();

// Init OSS client
OSS ossClient = new OSSClientBuilder().build(endPoint, accessKey, accessSecret);
Expand All @@ -82,6 +84,9 @@ public AliOssFileHandler(OptionService optionService,
.append(URL_SEPARATOR);
}

//Get image without EXIF information
File withoutEXIF = removeEXIF(file);

try {
FilePathDescriptor uploadFilePath = new FilePathDescriptor.Builder()
.setBasePath(basePath.toString())
Expand All @@ -96,12 +101,20 @@ public AliOssFileHandler(OptionService optionService,
log.info(basePath.toString());

// Upload
final PutObjectResult putObjectResult = ossClient.putObject(bucketName,
uploadFilePath.getRelativePath(),
file.getInputStream());

final PutObjectResult putObjectResult;
if (withoutEXIF != null) {
putObjectResult = ossClient.putObject(bucketName,
uploadFilePath.getRelativePath(),
new FileInputStream(withoutEXIF));
withoutEXIF.delete();
} else {
putObjectResult = ossClient.putObject(bucketName,
uploadFilePath.getRelativePath(),
file.getInputStream());
}
if (putObjectResult == null) {
throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到阿里云失败 ");
throw new FileOperationException(
"上传附件 " + file.getOriginalFilename() + " 到阿里云失败 ");
}

// Response result
Expand Down Expand Up @@ -141,13 +154,13 @@ public void delete(@NonNull String key) {

// Get config
String endPoint =
optionService.getByPropertyOfNonNull(AliOssProperties.OSS_ENDPOINT).toString();
optionService.getByPropertyOfNonNull(AliOssProperties.OSS_ENDPOINT).toString().trim();
String accessKey =
optionService.getByPropertyOfNonNull(AliOssProperties.OSS_ACCESS_KEY).toString();
String accessSecret =
optionService.getByPropertyOfNonNull(AliOssProperties.OSS_ACCESS_SECRET).toString();
String bucketName =
optionService.getByPropertyOfNonNull(AliOssProperties.OSS_BUCKET_NAME).toString();
optionService.getByPropertyOfNonNull(AliOssProperties.OSS_BUCKET_NAME).toString().trim();

// Init OSS client
OSS ossClient = new OSSClientBuilder().build(endPoint, accessKey, accessSecret);
Expand Down
30 changes: 21 additions & 9 deletions src/main/java/run/halo/app/handler/file/BaiduBosFileHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.baidubce.services.bos.BosClient;
import com.baidubce.services.bos.BosClientConfiguration;
import com.baidubce.services.bos.model.PutObjectResponse;
import java.io.File;
import java.io.FileInputStream;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -46,19 +48,19 @@ public UploadResult upload(MultipartFile file) {
// Get config
Object protocol = optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_PROTOCOL);
String domain =
optionService.getByPropertyOrDefault(BaiduBosProperties.BOS_DOMAIN, String.class, "");
optionService.getByPropertyOrDefault(BaiduBosProperties.BOS_DOMAIN, String.class, "").trim();
String endPoint =
optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_ENDPOINT).toString();
optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_ENDPOINT).toString().trim();
String accessKey =
optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_ACCESS_KEY).toString();
String secretKey =
optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_SECRET_KEY).toString();
String bucketName =
optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_BUCKET_NAME).toString();
optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_BUCKET_NAME).toString().trim();
String styleRule = optionService
.getByPropertyOrDefault(BaiduBosProperties.BOS_STYLE_RULE, String.class, "");
.getByPropertyOrDefault(BaiduBosProperties.BOS_STYLE_RULE, String.class, "").trim();
String thumbnailStyleRule = optionService
.getByPropertyOrDefault(BaiduBosProperties.BOS_THUMBNAIL_STYLE_RULE, String.class, "");
.getByPropertyOrDefault(BaiduBosProperties.BOS_THUMBNAIL_STYLE_RULE, String.class, "").trim();
String source = StringUtils.join(protocol, bucketName, "." + endPoint);

BosClientConfiguration config = new BosClientConfiguration();
Expand All @@ -70,6 +72,9 @@ public UploadResult upload(MultipartFile file) {

domain = protocol + domain;

//Get image without EXIF information
File withoutEXIF = removeEXIF(file);

try {
FilePathDescriptor pathDescriptor = new FilePathDescriptor.Builder()
.setBasePath(domain)
Expand All @@ -82,8 +87,15 @@ public UploadResult upload(MultipartFile file) {
.build();

// Upload
PutObjectResponse putObjectResponseFromInputStream =
client.putObject(bucketName, pathDescriptor.getFullName(), file.getInputStream());
PutObjectResponse putObjectResponseFromInputStream = null;
if (withoutEXIF != null) {
putObjectResponseFromInputStream =
client.putObject(bucketName, pathDescriptor.getFullName(), new FileInputStream(withoutEXIF));
withoutEXIF.delete();
} else {
putObjectResponseFromInputStream =
client.putObject(bucketName, pathDescriptor.getFullName(), file.getInputStream());
}
if (putObjectResponseFromInputStream == null) {
throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到百度云失败 ");
}
Expand Down Expand Up @@ -124,13 +136,13 @@ public void delete(String key) {

// Get config
String endPoint =
optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_ENDPOINT).toString();
optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_ENDPOINT).toString().trim();
String accessKey =
optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_ACCESS_KEY).toString();
String secretKey =
optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_SECRET_KEY).toString();
String bucketName =
optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_BUCKET_NAME).toString();
optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_BUCKET_NAME).toString().trim();

BosClientConfiguration config = new BosClientConfiguration();
config.setCredentials(new DefaultBceCredentials(accessKey, secretKey));
Expand Down
84 changes: 83 additions & 1 deletion src/main/java/run/halo/app/handler/file/FileHandler.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
package run.halo.app.handler.file;

import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR;
import static run.halo.app.model.support.HaloConst.USER_HOME;
import static run.halo.app.utils.HaloUtils.ensureSuffix;

import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.function.Supplier;
import javax.imageio.ImageReader;
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.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
Expand Down Expand Up @@ -102,6 +121,69 @@ default void handleImageMetadata(@NonNull MultipartFile file,
}
}

/**
* Remove EXIF information and return an image file.
*
* @param file multipart file must not be null
*/
default File removeEXIF(@NonNull MultipartFile file) {
//Let .halo be a temp directory to store the image after removing the EXIF information
File withoutEXIF = null;
String tempDir =
ensureSuffix(USER_HOME, FILE_SEPARATOR) + ".halo";
Path tempPath = Paths.get(tempDir);

if (!Files.isDirectory(tempPath)
|| !Files.isReadable(tempPath)
|| !Files.isWritable(tempPath)) {
return null;
}

//Remove EXIF information except for orientation
try {
TiffOutputSet outputSet = null;
final ImageMetadata metadata =
Imaging.getMetadata(file.getInputStream(), file.getOriginalFilename());

if (metadata instanceof JpegImageMetadata) {
final JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
final TiffImageMetadata exif = jpegMetadata.getExif();
if (exif != null) {
try {
outputSet = exif.getOutputSet();
} catch (ImageWriteException e) {
LoggerFactory
.getLogger(getClass()).warn("Failed to fetch image EXIF data", e);
}
// Remove all EXIF information except for orientation
if (outputSet != null) {
final List<TiffOutputDirectory> directories =
outputSet.getDirectories();
for (final TiffOutputDirectory directory : directories) {
final List<TiffOutputField> fields = directory.getFields();
for (final TiffOutputField field : fields) {
if (!StringUtils
.equalsIgnoreCase("Orientation", field.tagInfo.name)) {
outputSet.removeField(field.tagInfo);
}
}
}
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(
tempPath.toString() + FILE_SEPARATOR + "temp"));
new ExifRewriter()
.updateExifMetadataLossless(file.getInputStream(), os, outputSet);
withoutEXIF = new File(
tempPath.toString() + FILE_SEPARATOR + "temp");
}
}
}
} catch (IOException | OutOfMemoryError | ImageReadException | ImageWriteException e) {
// ignore IOException and OOM
LoggerFactory.getLogger(getClass()).warn("Failed to remove image personal EXIF", e);
}
return withoutEXIF;
}

/**
* Deletes file.
*
Expand All @@ -117,4 +199,4 @@ default void handleImageMetadata(@NonNull MultipartFile file,
*/
AttachmentType getAttachmentType();

}
}
34 changes: 24 additions & 10 deletions src/main/java/run/halo/app/handler/file/HuaweiObsFileHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import com.obs.services.ObsClient;
import com.obs.services.model.PutObjectResult;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -49,21 +51,21 @@ public HuaweiObsFileHandler(OptionService optionService,
String protocol =
optionService.getByPropertyOfNonNull(HuaweiObsProperties.OSS_PROTOCOL).toString();
String domain =
optionService.getByPropertyOrDefault(HuaweiObsProperties.OSS_DOMAIN, String.class, "");
optionService.getByPropertyOrDefault(HuaweiObsProperties.OSS_DOMAIN, String.class, "").trim();
String source =
optionService.getByPropertyOrDefault(HuaweiObsProperties.OSS_SOURCE, String.class, "");
optionService.getByPropertyOrDefault(HuaweiObsProperties.OSS_SOURCE, String.class, "").trim();
String endPoint =
optionService.getByPropertyOfNonNull(HuaweiObsProperties.OSS_ENDPOINT).toString();
optionService.getByPropertyOfNonNull(HuaweiObsProperties.OSS_ENDPOINT).toString().trim();
String accessKey =
optionService.getByPropertyOfNonNull(HuaweiObsProperties.OSS_ACCESS_KEY).toString();
String accessSecret =
optionService.getByPropertyOfNonNull(HuaweiObsProperties.OSS_ACCESS_SECRET).toString();
String bucketName =
optionService.getByPropertyOfNonNull(HuaweiObsProperties.OSS_BUCKET_NAME).toString();
optionService.getByPropertyOfNonNull(HuaweiObsProperties.OSS_BUCKET_NAME).toString().trim();
String styleRule = optionService
.getByPropertyOrDefault(HuaweiObsProperties.OSS_STYLE_RULE, String.class, "");
.getByPropertyOrDefault(HuaweiObsProperties.OSS_STYLE_RULE, String.class, "").trim();
String thumbnailStyleRule = optionService
.getByPropertyOrDefault(HuaweiObsProperties.OSS_THUMBNAIL_STYLE_RULE, String.class, "");
.getByPropertyOrDefault(HuaweiObsProperties.OSS_THUMBNAIL_STYLE_RULE, String.class, "").trim();

// Init OSS client
final ObsClient obsClient = new ObsClient(accessKey, accessSecret, endPoint);
Expand All @@ -80,6 +82,9 @@ public HuaweiObsFileHandler(OptionService optionService,
.append(URL_SEPARATOR);
}

//Get image without EXIF information
File withoutEXIF = removeEXIF(file);

try {
FilePathDescriptor pathDescriptor = new FilePathDescriptor.Builder()
.setBasePath(basePath.toString())
Expand All @@ -94,13 +99,22 @@ public HuaweiObsFileHandler(OptionService optionService,
log.info(basePath.toString());

// Upload
PutObjectResult putObjectResult =
obsClient.putObject(bucketName, pathDescriptor.getRelativePath(),
PutObjectResult putObjectResult;
if (withoutEXIF != null) {
putObjectResult = obsClient.putObject(bucketName,
pathDescriptor.getRelativePath(),
new FileInputStream(withoutEXIF));
withoutEXIF.delete();
} else {
putObjectResult = obsClient.putObject(bucketName,
pathDescriptor.getRelativePath(),
file.getInputStream());
}
if (putObjectResult == null) {
throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到华为云失败 ");
}


// Response result
UploadResult uploadResult = new UploadResult();
uploadResult.setFilename(pathDescriptor.getName());
Expand Down Expand Up @@ -142,13 +156,13 @@ public void delete(@NonNull String key) {

// Get config
String endPoint =
optionService.getByPropertyOfNonNull(HuaweiObsProperties.OSS_ENDPOINT).toString();
optionService.getByPropertyOfNonNull(HuaweiObsProperties.OSS_ENDPOINT).toString().trim();
String accessKey =
optionService.getByPropertyOfNonNull(HuaweiObsProperties.OSS_ACCESS_KEY).toString();
String accessSecret =
optionService.getByPropertyOfNonNull(HuaweiObsProperties.OSS_ACCESS_SECRET).toString();
String bucketName =
optionService.getByPropertyOfNonNull(HuaweiObsProperties.OSS_BUCKET_NAME).toString();
optionService.getByPropertyOfNonNull(HuaweiObsProperties.OSS_BUCKET_NAME).toString().trim();

// Init OSS client
final ObsClient obsClient = new ObsClient(accessKey, accessSecret, endPoint);
Expand Down
Loading