diff --git a/build.gradle b/build.gradle index 35ca0d6b..ba4d7386 100644 --- a/build.gradle +++ b/build.gradle @@ -179,6 +179,9 @@ dependencies { runtimeOnly "com.twelvemonkeys.imageio:imageio-webp:$imageioVersion" // runtimeOnly "com.twelvemonkeys.imageio:imageio-xwd:$imageioVersion" + // Image metadata extractor + implementation "com.drewnoakes:metadata-extractor:2.19.0" + implementation "commons-io:commons-io:2.17.0" implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0" //general PDF diff --git a/src/main/java/stirling/software/SPDF/EE/EEAppConfig.java b/src/main/java/stirling/software/SPDF/EE/EEAppConfig.java index cf0bcbd4..8c3c4fd0 100644 --- a/src/main/java/stirling/software/SPDF/EE/EEAppConfig.java +++ b/src/main/java/stirling/software/SPDF/EE/EEAppConfig.java @@ -1,7 +1,5 @@ package stirling.software.SPDF.EE; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -20,6 +18,6 @@ public class EEAppConfig { @Bean(name = "runningEE") public boolean runningEnterpriseEdition() { - return licenseKeyChecker.getEnterpriseEnabledResult(); + return licenseKeyChecker.getEnterpriseEnabledResult(); } -} \ No newline at end of file +} diff --git a/src/main/java/stirling/software/SPDF/config/InitialSetup.java b/src/main/java/stirling/software/SPDF/config/InitialSetup.java index 4be2e5e6..0e0ad2be 100644 --- a/src/main/java/stirling/software/SPDF/config/InitialSetup.java +++ b/src/main/java/stirling/software/SPDF/config/InitialSetup.java @@ -9,6 +9,7 @@ import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import io.micrometer.common.util.StringUtils; + import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.model.ApplicationProperties; @@ -40,7 +41,7 @@ public class InitialSetup { applicationProperties.getAutomaticallyGenerated().setKey(secretKey); } } - + @PostConstruct public void initLegalUrls() throws IOException { // Initialize Terms and Conditions @@ -59,7 +60,4 @@ public class InitialSetup { applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl); } } - } - - diff --git a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java index e6b149ba..04469cec 100644 --- a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java @@ -203,7 +203,8 @@ public class SecurityConfiguration { } // Handle SAML - if (applicationProperties.getSecurity().isSaml2Activ() && applicationProperties.getSystem().getEnableAlphaFunctionality()) { + if (applicationProperties.getSecurity().isSaml2Activ() + && applicationProperties.getSystem().getEnableAlphaFunctionality()) { http.authenticationProvider(samlAuthenticationProvider()); http.saml2Login( saml2 -> diff --git a/src/main/java/stirling/software/SPDF/config/security/UserService.java b/src/main/java/stirling/software/SPDF/config/security/UserService.java index c49002d6..a95ee135 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserService.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserService.java @@ -45,7 +45,6 @@ public class UserService implements UserServiceInterface { @Autowired DatabaseBackupInterface databaseBackupHelper; - // Handle OAUTH2 login and user auto creation. public boolean processOAuth2PostLogin(String username, boolean autoCreateUser) throws IllegalArgumentException, IOException { @@ -360,8 +359,8 @@ public class UserService implements UserServiceInterface { } } - @Override - public long getTotalUsersCount() { - return userRepository.count(); - } + @Override + public long getTotalUsersCount() { + return userRepository.count(); + } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/pipeline/UserServiceInterface.java b/src/main/java/stirling/software/SPDF/controller/api/pipeline/UserServiceInterface.java index 4ef6e6ec..3b69456b 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/pipeline/UserServiceInterface.java +++ b/src/main/java/stirling/software/SPDF/controller/api/pipeline/UserServiceInterface.java @@ -4,6 +4,6 @@ public interface UserServiceInterface { String getApiKeyForUser(String username); String getCurrentUsername(); - + long getTotalUsersCount(); } diff --git a/src/main/java/stirling/software/SPDF/service/PostHogService.java b/src/main/java/stirling/software/SPDF/service/PostHogService.java index 44fe8f60..68a8aa7a 100644 --- a/src/main/java/stirling/software/SPDF/service/PostHogService.java +++ b/src/main/java/stirling/software/SPDF/service/PostHogService.java @@ -29,12 +29,12 @@ public class PostHogService { private final ApplicationProperties applicationProperties; private final UserServiceInterface userService; - @Autowired public PostHogService( PostHog postHog, @Qualifier("UUID") String uuid, - ApplicationProperties applicationProperties, @Autowired(required = false) UserServiceInterface userService) { + ApplicationProperties applicationProperties, + @Autowired(required = false) UserServiceInterface userService) { this.postHog = postHog; this.uniqueId = uuid; this.applicationProperties = applicationProperties; @@ -137,10 +137,9 @@ public class PostHogService { metrics.put("docker_metrics", getDockerMetrics()); } metrics.put("application_properties", captureApplicationProperties()); - - - if(userService != null) { - metrics.put("total_users_created", userService.getTotalUsersCount()); + + if (userService != null) { + metrics.put("total_users_created", userService.getTotalUsersCount()); } } catch (Exception e) { diff --git a/src/main/java/stirling/software/SPDF/utils/ImageProcessingUtils.java b/src/main/java/stirling/software/SPDF/utils/ImageProcessingUtils.java index 655e344c..ba305fcc 100644 --- a/src/main/java/stirling/software/SPDF/utils/ImageProcessingUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/ImageProcessingUtils.java @@ -1,30 +1,46 @@ package stirling.software.SPDF.utils; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferInt; +import java.io.IOException; +import java.io.InputStream; import java.nio.ByteBuffer; +import javax.imageio.ImageIO; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.multipart.MultipartFile; + +import com.drew.imaging.ImageMetadataReader; +import com.drew.imaging.ImageProcessingException; +import com.drew.metadata.Metadata; +import com.drew.metadata.MetadataException; +import com.drew.metadata.exif.ExifSubIFDDirectory; + public class ImageProcessingUtils { + private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class); + static BufferedImage convertColorType(BufferedImage sourceImage, String colorType) { BufferedImage convertedImage; switch (colorType) { case "greyscale": - convertedImage = - new BufferedImage( - sourceImage.getWidth(), - sourceImage.getHeight(), - BufferedImage.TYPE_BYTE_GRAY); + convertedImage = new BufferedImage( + sourceImage.getWidth(), + sourceImage.getHeight(), + BufferedImage.TYPE_BYTE_GRAY); convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null); break; case "blackwhite": - convertedImage = - new BufferedImage( - sourceImage.getWidth(), - sourceImage.getHeight(), - BufferedImage.TYPE_BYTE_BINARY); + convertedImage = new BufferedImage( + sourceImage.getWidth(), + sourceImage.getHeight(), + BufferedImage.TYPE_BYTE_BINARY); convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null); break; default: // full color @@ -59,4 +75,49 @@ public class ImageProcessingUtils { return data; } } + + public static double extractImageOrientation(InputStream is) throws IOException { + try { + Metadata metadata = ImageMetadataReader.readMetadata(is); + ExifSubIFDDirectory directory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class); + if (directory == null) { + return 0; + } + int orientationTag = directory.getInt(ExifSubIFDDirectory.TAG_ORIENTATION); + switch (orientationTag) { + case 1: + return 0; + case 6: + return 90; + case 3: + return 180; + case 8: + return 270; + default: + logger.warn("Unknown orientation tag: {}", orientationTag); + return 0; + } + } catch (ImageProcessingException | MetadataException e) { + return 0; + } + } + + public static BufferedImage applyOrientation(BufferedImage image, double orientation) { + if (orientation == 0) { + return image; + } + AffineTransform transform = AffineTransform.getRotateInstance( + Math.toRadians(orientation), + image.getWidth() / 2.0, + image.getHeight() / 2.0); + AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR); + return op.filter(image, null); + } + + public static BufferedImage loadImageWithExifOrientation(MultipartFile file) + throws IOException { + BufferedImage image = ImageIO.read(file.getInputStream()); + double orientation = extractImageOrientation(file.getInputStream()); + return applyOrientation(image, orientation); + } } diff --git a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index 3416dee6..8ccd2942 100644 --- a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java @@ -194,7 +194,8 @@ public class PdfUtils { pdfDocument.close(); - // Assumes the expectedPageSize is in the format "widthxheight", e.g. "595x842" for A4 + // Assumes the expectedPageSize is in the format "widthxheight", e.g. "595x842" + // for A4 String[] dimensions = expectedPageSize.split("x"); float expectedPageWidth = Float.parseFloat(dimensions[0]); float expectedPageHeight = Float.parseFloat(dimensions[1]); @@ -407,7 +408,7 @@ public class PdfUtils { addImageToDocument(doc, pdImage, fitOption, autoRotate); } } else { - BufferedImage image = ImageIO.read(file.getInputStream()); + BufferedImage image = ImageProcessingUtils.loadImageWithExifOrientation(file); BufferedImage convertedImage = ImageProcessingUtils.convertColorType(image, colorType); // Use JPEGFactory if it's JPEG since JPEG is lossy