From 3b099857e46132459e0ba96a2ead1ba0c8cb9b30 Mon Sep 17 00:00:00 2001 From: SladeGranger <43343180+SladeGranger@users.noreply.github.com> Date: Sun, 24 Apr 2022 08:33:37 +0800 Subject: [PATCH 01/10] Update PostServiceImpl.java Ignore case when searching articles --- src/main/java/run/halo/app/service/impl/PostServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/run/halo/app/service/impl/PostServiceImpl.java b/src/main/java/run/halo/app/service/impl/PostServiceImpl.java index 4ba2a96c90..e4613d9383 100644 --- a/src/main/java/run/halo/app/service/impl/PostServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/PostServiceImpl.java @@ -583,9 +583,9 @@ private Specification buildSpecByQuery(@NonNull PostQuery postQuery) { Subquery postSubquery = query.subquery(Post.class); Root contentRoot = postSubquery.from(Content.class); postSubquery.select(contentRoot.get("id")) - .where(criteriaBuilder.like(contentRoot.get("originalContent"), likeCondition)); + .where(criteriaBuilder.like(criteriaBuilder.lower(root.get("originalContent")), likeCondition.toLowerCase())); - Predicate titleLike = criteriaBuilder.like(root.get("title"), likeCondition); + Predicate titleLike = criteriaBuilder.like(criteriaBuilder.lower(root.get("title")), likeCondition.toLowerCase()); predicates.add( criteriaBuilder.or(titleLike, criteriaBuilder.in(root).value(postSubquery))); From bcaca694d36f67904b1fcddb8317247219eb443c Mon Sep 17 00:00:00 2001 From: SladeGranger <43343180+SladeGranger@users.noreply.github.com> Date: Sun, 24 Apr 2022 08:40:53 +0800 Subject: [PATCH 02/10] Update AliOssFileHandler.java Remove EXIF information when uploading images Ignore spaces on both sides of the possible part of an URL --- .../app/handler/file/AliOssFileHandler.java | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/main/java/run/halo/app/handler/file/AliOssFileHandler.java b/src/main/java/run/halo/app/handler/file/AliOssFileHandler.java index 2cdac5bc82..336a3fd704 100644 --- a/src/main/java/run/halo/app/handler/file/AliOssFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/AliOssFileHandler.java @@ -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; @@ -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); @@ -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()) @@ -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 @@ -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); From afd6c2983cf0934a04ab1e8d3acbcaf2dfcef183 Mon Sep 17 00:00:00 2001 From: SladeGranger <43343180+SladeGranger@users.noreply.github.com> Date: Sun, 24 Apr 2022 08:42:40 +0800 Subject: [PATCH 03/10] Update BaiduBosFileHandler.java Remove EXIF information when uploading images Ignore spaces on both sides of an URL --- .../app/handler/file/BaiduBosFileHandler.java | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/main/java/run/halo/app/handler/file/BaiduBosFileHandler.java b/src/main/java/run/halo/app/handler/file/BaiduBosFileHandler.java index 85719fc8fd..4e805b3f3e 100644 --- a/src/main/java/run/halo/app/handler/file/BaiduBosFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/BaiduBosFileHandler.java @@ -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; @@ -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(); @@ -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) @@ -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() + " 到百度云失败 "); } @@ -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)); From 761a85fd4b468c21cfa6ec96ef502494bffc856a Mon Sep 17 00:00:00 2001 From: SladeGranger <43343180+SladeGranger@users.noreply.github.com> Date: Sun, 24 Apr 2022 08:44:20 +0800 Subject: [PATCH 04/10] Update FileHandler.java Remove EXIF information when uploading images --- .../halo/app/handler/file/FileHandler.java | 84 ++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/src/main/java/run/halo/app/handler/file/FileHandler.java b/src/main/java/run/halo/app/handler/file/FileHandler.java index dc4805b9dc..f8efedc05b 100644 --- a/src/main/java/run/halo/app/handler/file/FileHandler.java +++ b/src/main/java/run/halo/app/handler/file/FileHandler.java @@ -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; @@ -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 directories = + outputSet.getDirectories(); + for (final TiffOutputDirectory directory : directories) { + final List 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. * @@ -117,4 +199,4 @@ default void handleImageMetadata(@NonNull MultipartFile file, */ AttachmentType getAttachmentType(); -} \ No newline at end of file +} From 068517df14fa6935fe3928c96662055cb1023798 Mon Sep 17 00:00:00 2001 From: SladeGranger <43343180+SladeGranger@users.noreply.github.com> Date: Sun, 24 Apr 2022 08:45:23 +0800 Subject: [PATCH 05/10] Update HuaweiObsFileHandler.java Remove EXIF information when uploading images Ignore spaces on both sides of an URL --- .../handler/file/HuaweiObsFileHandler.java | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/main/java/run/halo/app/handler/file/HuaweiObsFileHandler.java b/src/main/java/run/halo/app/handler/file/HuaweiObsFileHandler.java index a4eef3a414..aa2b0b1dec 100644 --- a/src/main/java/run/halo/app/handler/file/HuaweiObsFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/HuaweiObsFileHandler.java @@ -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; @@ -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); @@ -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()) @@ -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()); @@ -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); From 961bad57e33f86568ea94296fd87b1a511eb8953 Mon Sep 17 00:00:00 2001 From: SladeGranger <43343180+SladeGranger@users.noreply.github.com> Date: Sun, 24 Apr 2022 08:56:34 +0800 Subject: [PATCH 06/10] Update LocalFileHandler.java Remove EXIF information when uploading images Fix a problem: Generated thumbnail may have a wrong direction --- .../app/handler/file/LocalFileHandler.java | 83 ++++++++++++++++++- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/src/main/java/run/halo/app/handler/file/LocalFileHandler.java b/src/main/java/run/halo/app/handler/file/LocalFileHandler.java index 64360fa0bf..ff7e0a876b 100644 --- a/src/main/java/run/halo/app/handler/file/LocalFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/LocalFileHandler.java @@ -3,15 +3,31 @@ import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR; 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.Calendar; +import java.util.List; import java.util.Objects; import lombok.extern.slf4j.Slf4j; import net.coobird.thumbnailator.Thumbnails; +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; import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; @@ -102,6 +118,8 @@ public UploadResult upload(@NonNull MultipartFile file) { log.info("Uploading file: [{}] to directory: [{}]", file.getOriginalFilename(), uploadFilePath.getRelativePath()); Path localFileFullPath = Paths.get(uploadFilePath.getFullPath()); + + int orientation = 1; try { // TODO Synchronize here // Create directory @@ -121,7 +139,58 @@ public UploadResult upload(@NonNull MultipartFile file) { .setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType()))); uploadResult.setSize(file.getSize()); + // Remove personal EXIF information + try { + File orgFile = new File(localFileFullPath.toString()); + TiffOutputSet outputSet = null; + final ImageMetadata metadata = Imaging.getMetadata(orgFile); + + 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 directories = + outputSet.getDirectories(); + for (final TiffOutputDirectory directory : directories) { + final List fields = directory.getFields(); + for (final TiffOutputField field : fields) { + if (!StringUtils + .equalsIgnoreCase("Orientation", field.tagInfo.name)) { + outputSet.removeField(field.tagInfo); + } else { + orientation = + ((Short) exif.getFieldValue(field.tagInfo)).intValue(); + } + } + } + BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream( + localFileFullPath.getParent().toString() + FILE_SEPARATOR + + "temp")); + new ExifRewriter().updateExifMetadataLossless(orgFile, os, outputSet); + File withEXIF = new File(localFileFullPath.toString()); + File withoutEXIF = new File( + localFileFullPath.getParent().toString() + FILE_SEPARATOR + "temp"); + withEXIF.delete(); + withoutEXIF.renameTo(withEXIF); + withoutEXIF.delete(); + } + } + } + } catch (IOException | OutOfMemoryError | ImageReadException | ImageWriteException e) { + // ignore IOException and OOM + LoggerFactory.getLogger(getClass()).warn("Failed to remove image personal EXIF", e); + } + // TODO refactor this: if image is svg ext. extension + int finalOrientation = orientation; handleImageMetadata(file, uploadResult, () -> { // Upload a thumbnail FilePathDescriptor thumbnailFilePath = new FilePathDescriptor.Builder() @@ -137,7 +206,7 @@ public UploadResult upload(@NonNull MultipartFile file) { BufferedImage originalImage = ImageUtils.getImageFromFile(is, uploadFilePath.getExtension()); boolean result = generateThumbnail(originalImage, thumbnailPath, - uploadFilePath.getExtension()); + uploadFilePath.getExtension(), finalOrientation); if (result) { // Set thumb path return thumbnailFilePath.getRelativePath(); @@ -209,7 +278,7 @@ private String generatePath() { } private boolean generateThumbnail(BufferedImage originalImage, Path thumbPath, - String extension) { + String extension, int orientation) { Assert.notNull(originalImage, "Image must not be null"); Assert.notNull(thumbPath, "Thumb path must not be null"); @@ -219,7 +288,15 @@ private boolean generateThumbnail(BufferedImage originalImage, Path thumbPath, Files.createFile(thumbPath); // Convert to thumbnail and copy the thumbnail log.debug("Trying to generate thumbnail: [{}]", thumbPath); - Thumbnails.of(originalImage).size(THUMB_WIDTH, THUMB_HEIGHT).keepAspectRatio(true) + int rotationDegree = 0; + if (orientation == 6) { + rotationDegree = 90; + } else if (orientation == 8) { + rotationDegree = -90; + } else if (orientation == 3) { + rotationDegree = 180; + } + Thumbnails.of(originalImage).size(THUMB_WIDTH, THUMB_HEIGHT).rotate(rotationDegree).keepAspectRatio(true) .toFile(thumbPath.toFile()); log.info("Generated thumbnail image, and wrote the thumbnail to [{}]", thumbPath); result = true; From f19ab454f36ac3e4a5be087498e44bcb847104b8 Mon Sep 17 00:00:00 2001 From: SladeGranger <43343180+SladeGranger@users.noreply.github.com> Date: Sun, 24 Apr 2022 08:57:41 +0800 Subject: [PATCH 07/10] Update MinioFileHandler.java Remove EXIF information when uploading images Ignore spaces on both sides of an URL --- .../app/handler/file/MinioFileHandler.java | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/main/java/run/halo/app/handler/file/MinioFileHandler.java b/src/main/java/run/halo/app/handler/file/MinioFileHandler.java index 28d27c0086..35425d0ec8 100644 --- a/src/main/java/run/halo/app/handler/file/MinioFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/MinioFileHandler.java @@ -3,6 +3,8 @@ import io.minio.MinioClient; import io.minio.PutObjectArgs; import io.minio.RemoveObjectArgs; +import java.io.File; +import java.io.FileInputStream; import java.util.Objects; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -44,15 +46,15 @@ public MinioFileHandler(OptionService optionService, public UploadResult upload(@NonNull MultipartFile file) { Assert.notNull(file, "Multipart file must not be null"); // Get config - String endpoint = optionService.getByPropertyOfNonNull(MinioProperties.ENDPOINT).toString(); + String endpoint = optionService.getByPropertyOfNonNull(MinioProperties.ENDPOINT).toString().trim(); String accessKey = optionService.getByPropertyOfNonNull(MinioProperties.ACCESS_KEY).toString(); String accessSecret = optionService.getByPropertyOfNonNull(MinioProperties.ACCESS_SECRET).toString(); String bucketName = - optionService.getByPropertyOfNonNull(MinioProperties.BUCKET_NAME).toString(); + optionService.getByPropertyOfNonNull(MinioProperties.BUCKET_NAME).toString().trim(); String source = - optionService.getByPropertyOrDefault(MinioProperties.SOURCE, String.class, ""); + optionService.getByPropertyOrDefault(MinioProperties.SOURCE, String.class, "").trim(); String region = optionService.getByPropertyOrDefault(MinioProperties.REGION, String.class, "us-east-1"); @@ -64,6 +66,9 @@ public UploadResult upload(@NonNull MultipartFile file) { .region(region) .build(); + //Get image without EXIF information + File withoutEXIF = removeEXIF(file); + try { FilePathDescriptor pathDescriptor = new FilePathDescriptor.Builder() .setBasePath(endpoint + bucketName) @@ -75,12 +80,25 @@ public UploadResult upload(@NonNull MultipartFile file) { .setOriginalName(file.getOriginalFilename()) .build(); - PutObjectArgs putObjectArgs = PutObjectArgs.builder() - .contentType(file.getContentType()) - .bucket(bucketName) - .stream(file.getInputStream(), file.getSize(), -1) - .object(pathDescriptor.getRelativePath()) - .build(); + PutObjectArgs putObjectArgs; + if (withoutEXIF != null) { + putObjectArgs = PutObjectArgs.builder() + .contentType(file.getContentType()) + .bucket(bucketName) + .stream(new FileInputStream(withoutEXIF), file.getSize(), -1) + .object(pathDescriptor.getRelativePath()) + .build(); + withoutEXIF.delete(); + } + else { + putObjectArgs = PutObjectArgs.builder() + .contentType(file.getContentType()) + .bucket(bucketName) + .stream(file.getInputStream(), file.getSize(), -1) + .object(pathDescriptor.getRelativePath()) + .build(); + } + minioClient.ignoreCertCheck(); minioClient.putObject(putObjectArgs); @@ -116,7 +134,7 @@ public void delete(@NonNull String key) { String accessSecret = optionService.getByPropertyOfNonNull(MinioProperties.ACCESS_SECRET).toString(); String bucketName = - optionService.getByPropertyOfNonNull(MinioProperties.BUCKET_NAME).toString(); + optionService.getByPropertyOfNonNull(MinioProperties.BUCKET_NAME).toString().trim(); String region = optionService.getByPropertyOrDefault(MinioProperties.REGION, String.class, "us-east-1"); From abe73206a8b0df7e501cf5d98dc0854e89235139 Mon Sep 17 00:00:00 2001 From: SladeGranger <43343180+SladeGranger@users.noreply.github.com> Date: Sun, 24 Apr 2022 08:59:31 +0800 Subject: [PATCH 08/10] Update QiniuOssFileHandler.java Remove EXIF information when uploading images Ignore spaces on both sides of an URL --- .../app/handler/file/QiniuOssFileHandler.java | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/main/java/run/halo/app/handler/file/QiniuOssFileHandler.java b/src/main/java/run/halo/app/handler/file/QiniuOssFileHandler.java index 6e890b9c5d..721fe5a86e 100644 --- a/src/main/java/run/halo/app/handler/file/QiniuOssFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/QiniuOssFileHandler.java @@ -14,6 +14,8 @@ import com.qiniu.storage.persistent.FileRecorder; import com.qiniu.util.Auth; import com.qiniu.util.StringMap; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; @@ -67,17 +69,17 @@ public UploadResult upload(MultipartFile file) { String secretKey = optionService.getByPropertyOfNonNull(QiniuOssProperties.OSS_SECRET_KEY).toString(); String bucket = - optionService.getByPropertyOfNonNull(QiniuOssProperties.OSS_BUCKET).toString(); + optionService.getByPropertyOfNonNull(QiniuOssProperties.OSS_BUCKET).toString().trim(); String protocol = optionService.getByPropertyOfNonNull(QiniuOssProperties.OSS_PROTOCOL).toString(); String domain = - optionService.getByPropertyOfNonNull(QiniuOssProperties.OSS_DOMAIN).toString(); + optionService.getByPropertyOfNonNull(QiniuOssProperties.OSS_DOMAIN).toString().trim(); String source = - optionService.getByPropertyOrDefault(QiniuOssProperties.OSS_SOURCE, String.class, ""); + optionService.getByPropertyOrDefault(QiniuOssProperties.OSS_SOURCE, String.class, "").trim(); String styleRule = optionService - .getByPropertyOrDefault(QiniuOssProperties.OSS_STYLE_RULE, String.class, ""); + .getByPropertyOrDefault(QiniuOssProperties.OSS_STYLE_RULE, String.class, "").trim(); String thumbnailStyleRule = optionService - .getByPropertyOrDefault(QiniuOssProperties.OSS_THUMBNAIL_STYLE_RULE, String.class, ""); + .getByPropertyOrDefault(QiniuOssProperties.OSS_THUMBNAIL_STYLE_RULE, String.class, "").trim(); // Create configuration Configuration configuration = new Configuration(region); @@ -99,6 +101,9 @@ public UploadResult upload(MultipartFile file) { .append(domain) .append(URL_SEPARATOR); + //Get image without EXIF information + File withoutEXIF = removeEXIF(file); + try { FilePathDescriptor pathDescriptor = new FilePathDescriptor.Builder() .setBasePath(basePath.toString()) @@ -115,9 +120,17 @@ public UploadResult upload(MultipartFile file) { // Get upload manager UploadManager uploadManager = new UploadManager(configuration, fileRecorder); // Put the file - Response response = uploadManager - .put(file.getInputStream(), pathDescriptor.getRelativePath(), uploadToken, null, - null); + Response response; + if (withoutEXIF != null) { + response = uploadManager + .put(new FileInputStream(withoutEXIF), pathDescriptor.getRelativePath(), + uploadToken, null, null); + withoutEXIF.delete(); + } else { + response = uploadManager + .put(file.getInputStream(), pathDescriptor.getRelativePath(), uploadToken, null, + null); + } if (log.isDebugEnabled()) { log.debug("Qiniu oss response: [{}]", response.toString()); @@ -172,7 +185,7 @@ public void delete(String key) { String secretKey = optionService.getByPropertyOfNonNull(QiniuOssProperties.OSS_SECRET_KEY).toString(); String bucket = - optionService.getByPropertyOfNonNull(QiniuOssProperties.OSS_BUCKET).toString(); + optionService.getByPropertyOfNonNull(QiniuOssProperties.OSS_BUCKET).toString().trim(); // Create configuration Configuration configuration = new Configuration(region); From 874bea0290b2f21f907b91ec8aa5bbcbaca55244 Mon Sep 17 00:00:00 2001 From: SladeGranger <43343180+SladeGranger@users.noreply.github.com> Date: Sun, 24 Apr 2022 08:59:59 +0800 Subject: [PATCH 09/10] Update TencentCosFileHandler.java Remove EXIF information when uploading images Ignore spaces on both sides of an URL --- .../handler/file/TencentCosFileHandler.java | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/main/java/run/halo/app/handler/file/TencentCosFileHandler.java b/src/main/java/run/halo/app/handler/file/TencentCosFileHandler.java index 5c7a3f9b47..0f6c1fd904 100644 --- a/src/main/java/run/halo/app/handler/file/TencentCosFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/TencentCosFileHandler.java @@ -9,6 +9,8 @@ import com.qcloud.cos.model.ObjectMetadata; import com.qcloud.cos.model.PutObjectResult; import com.qcloud.cos.region.Region; +import java.io.File; +import java.io.FileInputStream; import java.util.Objects; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -52,7 +54,7 @@ public UploadResult upload(MultipartFile file) { String protocol = optionService.getByPropertyOfNonNull(TencentCosProperties.COS_PROTOCOL).toString(); String domain = - optionService.getByPropertyOrDefault(TencentCosProperties.COS_DOMAIN, String.class, ""); + optionService.getByPropertyOrDefault(TencentCosProperties.COS_DOMAIN, String.class, "").trim(); String region = optionService.getByPropertyOfNonNull(TencentCosProperties.COS_REGION).toString(); String secretId = @@ -60,14 +62,14 @@ public UploadResult upload(MultipartFile file) { String secretKey = optionService.getByPropertyOfNonNull(TencentCosProperties.COS_SECRET_KEY).toString(); String bucketName = - optionService.getByPropertyOfNonNull(TencentCosProperties.COS_BUCKET_NAME).toString(); + optionService.getByPropertyOfNonNull(TencentCosProperties.COS_BUCKET_NAME).toString().trim(); String source = - optionService.getByPropertyOrDefault(TencentCosProperties.COS_SOURCE, String.class, ""); + optionService.getByPropertyOrDefault(TencentCosProperties.COS_SOURCE, String.class, "").trim(); String styleRule = optionService - .getByPropertyOrDefault(TencentCosProperties.COS_STYLE_RULE, String.class, ""); + .getByPropertyOrDefault(TencentCosProperties.COS_STYLE_RULE, String.class, "").trim(); String thumbnailStyleRule = optionService .getByPropertyOrDefault(TencentCosProperties.COS_THUMBNAIL_STYLE_RULE, String.class, - ""); + "").trim(); COSCredentials cred = new BasicCOSCredentials(secretId, secretKey); Region regionConfig = new Region(region); @@ -89,6 +91,9 @@ public UploadResult upload(MultipartFile file) { .append(URL_SEPARATOR); } + //Get image without EXIF information + File withoutEXIF = removeEXIF(file); + try { FilePathDescriptor pathDescriptor = new FilePathDescriptor.Builder() .setBasePath(basePath.toString()) @@ -106,12 +111,21 @@ public UploadResult upload(MultipartFile file) { objectMetadata.setContentLength(file.getSize()); // 设置 Content type, 默认是 application/octet-stream objectMetadata.setContentType(file.getContentType()); - PutObjectResult putObjectResponseFromInputStream = cosClient - .putObject(bucketName, pathDescriptor.getRelativePath(), file.getInputStream(), - objectMetadata); + PutObjectResult putObjectResponseFromInputStream; + if (withoutEXIF != null) { + putObjectResponseFromInputStream = cosClient + .putObject(bucketName, pathDescriptor.getRelativePath(), new FileInputStream(withoutEXIF), + objectMetadata); + withoutEXIF.delete(); + } else { + putObjectResponseFromInputStream = cosClient + .putObject(bucketName, pathDescriptor.getRelativePath(), file.getInputStream(), + objectMetadata); + } if (putObjectResponseFromInputStream == null) { throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到腾讯云失败 "); } + String fullPath = pathDescriptor.getFullPath(); // Response result UploadResult uploadResult = new UploadResult(); @@ -154,7 +168,7 @@ public void delete(String key) { String secretKey = optionService.getByPropertyOfNonNull(TencentCosProperties.COS_SECRET_KEY).toString(); String bucketName = - optionService.getByPropertyOfNonNull(TencentCosProperties.COS_BUCKET_NAME).toString(); + optionService.getByPropertyOfNonNull(TencentCosProperties.COS_BUCKET_NAME).toString().trim(); COSCredentials cred = new BasicCOSCredentials(secretId, secretKey); Region regionConfig = new Region(region); From ebc400f8b4688628a83f1b89b6279fe674aaa923 Mon Sep 17 00:00:00 2001 From: SladeGranger <43343180+SladeGranger@users.noreply.github.com> Date: Sun, 24 Apr 2022 09:00:22 +0800 Subject: [PATCH 10/10] Update UpOssFileHandler.java Remove EXIF information when uploading images Ignore spaces on both sides of an URL --- .../app/handler/file/UpOssFileHandler.java | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/main/java/run/halo/app/handler/file/UpOssFileHandler.java b/src/main/java/run/halo/app/handler/file/UpOssFileHandler.java index 3881b78036..55c568f83b 100644 --- a/src/main/java/run/halo/app/handler/file/UpOssFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/UpOssFileHandler.java @@ -2,6 +2,8 @@ import com.upyun.RestManager; import com.upyun.UpException; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; @@ -43,20 +45,20 @@ public UpOssFileHandler(OptionService optionService) { public UploadResult upload(MultipartFile file) { Assert.notNull(file, "Multipart file must not be null"); - String source = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_SOURCE).toString(); + String source = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_SOURCE).toString().trim(); String password = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_PASSWORD).toString(); - String bucket = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_BUCKET).toString(); + String bucket = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_BUCKET).toString().trim(); String protocol = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_PROTOCOL).toString(); - String domain = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_DOMAIN).toString(); + String domain = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_DOMAIN).toString().trim(); String operator = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_OPERATOR).toString(); // style rule can be null String styleRule = - optionService.getByPropertyOrDefault(UpOssProperties.OSS_STYLE_RULE, String.class, ""); + optionService.getByPropertyOrDefault(UpOssProperties.OSS_STYLE_RULE, String.class, "").trim(); String thumbnailStyleRule = optionService - .getByPropertyOrDefault(UpOssProperties.OSS_THUMBNAIL_STYLE_RULE, String.class, ""); + .getByPropertyOrDefault(UpOssProperties.OSS_THUMBNAIL_STYLE_RULE, String.class, "").trim(); RestManager manager = new RestManager(bucket, operator, password); manager.setTimeout(60 * 10); @@ -64,6 +66,9 @@ public UploadResult upload(MultipartFile file) { Map params = new HashMap<>(); + //Get image without EXIF information + File withoutEXIF = removeEXIF(file); + try { // Get file basename String basename = @@ -78,7 +83,13 @@ public UploadResult upload(MultipartFile file) { // Set md5Content params.put(RestManager.PARAMS.CONTENT_MD5.getValue(), md5OfFile); // Write file - Response result = manager.writeFile(upFilePath, file.getInputStream(), params); + Response result; + if (withoutEXIF != null) { + result = manager.writeFile(upFilePath, new FileInputStream(withoutEXIF), params); + withoutEXIF.delete(); + } else { + result = manager.writeFile(upFilePath, file.getInputStream(), params); + } if (!result.isSuccessful()) { throw new FileOperationException( "上传附件 " + file.getOriginalFilename() + " 到又拍云失败" + upFilePath); @@ -121,7 +132,7 @@ public void delete(String key) { // Get config String password = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_PASSWORD).toString(); - String bucket = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_BUCKET).toString(); + String bucket = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_BUCKET).toString().trim(); String operator = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_OPERATOR).toString();