mirror of
https://github.com/Alfresco/alfresco-transform-core.git
synced 2026-09-16 18:12:54 +00:00
[MNT-25375] Unauthenticated Remote Code Execution vulnerability through Argument Injection (#1145)
* [MNT-25375] WIP * [MNT-25375] Spotless * [MNT-25375] Rewritten test, spotless, updated license year * [MNT-25375] Update imagemagick in aio * [MNT-25375] Update imagemagick to 7.1.2-6 * [MNT-25375] Updating * [MNT-25375] Restored test * [MNT-25375] Added transform.core.imagemagick.commandOptions.enabled and IMAGEMAGICK_COMMAND_OPTIONS_ENABLED to enable deprecated unsecure functionality on demand * [MNT-25375] Spotless * [MNT-25375] Missing header * [MNT-25375] Review fixes * [MNT-25375] Review fixes
This commit is contained in:
@@ -12,9 +12,9 @@ ARG EXIFTOOL_VERSION=12.25
|
||||
ARG EXIFTOOL_FOLDER=Image-ExifTool-${EXIFTOOL_VERSION}
|
||||
ARG EXIFTOOL_URL=https://nexus.alfresco.com/nexus/service/local/repositories/thirdparty/org/exiftool/image-exiftool/${EXIFTOOL_VERSION}/image-exiftool-${EXIFTOOL_VERSION}.tgz
|
||||
|
||||
ARG IMAGEMAGICK_VERSION=7.1.0-16
|
||||
ENV IMAGEMAGICK_RPM_URL=https://nexus.alfresco.com/nexus/service/local/repositories/thirdparty/org/imagemagick/imagemagick-distribution/${IMAGEMAGICK_VERSION}-ci-10/imagemagick-distribution-${IMAGEMAGICK_VERSION}-ci-10
|
||||
ENV IMAGEMAGICK_DEP_RPM_URL=https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
|
||||
ARG IMAGEMAGICK_VERSION=7.1.2-6
|
||||
ENV IMAGEMAGICK_RPM_URL=https://nexus.alfresco.com/nexus/service/local/repositories/thirdparty/org/imagemagick/imagemagick-distribution/${IMAGEMAGICK_VERSION}-ci-1/imagemagick-distribution-${IMAGEMAGICK_VERSION}-ci-1
|
||||
ENV IMAGEMAGICK_DEP_RPM_URL=https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm
|
||||
|
||||
ARG LIBREOFFICE_VERSION=7.2.5
|
||||
ENV LIBREOFFICE_RPM_URL=https://nexus.alfresco.com/nexus/service/local/repositories/thirdparty/org/libreoffice/libreoffice-dist/${LIBREOFFICE_VERSION}/libreoffice-dist-${LIBREOFFICE_VERSION}-linux.gz
|
||||
|
||||
@@ -13,11 +13,13 @@ transform:
|
||||
templateProfileDir: ${LIBREOFFICE_TEMPLATE_PROFILE_DIR:}
|
||||
isEnabled: ${LIBREOFFICE_IS_ENABLED:true}
|
||||
imagemagick:
|
||||
root: ${IMAGEMAGICK_ROOT:/usr/lib64/ImageMagick-7.0.10}
|
||||
dyn: ${IMAGEMAGICK_DYN:/usr/lib64/ImageMagick-7.0.10/lib}
|
||||
root: ${IMAGEMAGICK_ROOT:/usr/lib64/ImageMagick-7.1.2}
|
||||
dyn: ${IMAGEMAGICK_DYN:/usr/lib64/ImageMagick-7.1.2/lib}
|
||||
exe: ${IMAGEMAGICK_EXE:/usr/bin/convert}
|
||||
coders: ${IMAGEMAGICK_CODERS:}
|
||||
config: ${IMAGEMAGICK_CONFIG:}
|
||||
commandOptions:
|
||||
enabled: ${IMAGEMAGICK_COMMAND_OPTIONS_ENABLED:false}
|
||||
tika:
|
||||
pdfBox:
|
||||
notExtractBookmarksTextDefault: ${PDFBOX_NOTEXTRACTBOOKMARKS_DEFAULT:false}
|
||||
@@ -28,4 +30,4 @@ transform:
|
||||
pdfBox:
|
||||
defaultFont: ${MISC_PDFBOX_DEFAULT_FONT:NotoSans-Regular}
|
||||
htmlOptions:
|
||||
collapseHtml: ${MISC_HTML_COLLAPSE:true}
|
||||
collapseHtml: ${MISC_HTML_COLLAPSE:true}
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
FROM alfresco/alfresco-base-java:jre17-rockylinux9@sha256:f98833508b7be8c4b44a25450f9faac44cacfdc075f2295e02836b93fd05bb9c
|
||||
|
||||
# For other ImageMagick versions please look at https://github.com/Alfresco/imagemagick-build tags
|
||||
ARG IMAGEMAGICK_VERSION=7.1.0-16
|
||||
ARG IMAGEMAGICK_VERSION=7.1.2-6
|
||||
|
||||
ENV IMAGEMAGICK_RPM_URL=https://nexus.alfresco.com/nexus/service/local/repositories/thirdparty/org/imagemagick/imagemagick-distribution/${IMAGEMAGICK_VERSION}-ci-10/imagemagick-distribution-${IMAGEMAGICK_VERSION}-ci-10
|
||||
ENV IMAGEMAGICK_DEP_RPM_URL=https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
|
||||
ENV IMAGEMAGICK_RPM_URL=https://nexus.alfresco.com/nexus/service/local/repositories/thirdparty/org/imagemagick/imagemagick-distribution/${IMAGEMAGICK_VERSION}-ci-1/imagemagick-distribution-${IMAGEMAGICK_VERSION}-ci-1
|
||||
ENV IMAGEMAGICK_DEP_RPM_URL=https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm
|
||||
ENV JAVA_OPTS=""
|
||||
|
||||
# Set default user information
|
||||
|
||||
+2
-26
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Transform Core
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* -
|
||||
@@ -48,8 +48,6 @@ public final class ImageMagickOptionsBuilder
|
||||
private static final List<String> GRAVITY_VALUES = ImmutableList.of("North", "NorthEast",
|
||||
"East", "SouthEast", "South", "SouthWest", "West", "NorthWest", "Center");
|
||||
|
||||
private Integer startPage;
|
||||
private Integer endPage;
|
||||
private Boolean alphaRemove;
|
||||
private Boolean autoOrient;
|
||||
private String cropGravity;
|
||||
@@ -69,28 +67,6 @@ public final class ImageMagickOptionsBuilder
|
||||
private ImageMagickOptionsBuilder()
|
||||
{}
|
||||
|
||||
public ImageMagickOptionsBuilder withStartPage(final String startPage)
|
||||
{
|
||||
return withStartPage(stringToInteger(startPage));
|
||||
}
|
||||
|
||||
public ImageMagickOptionsBuilder withStartPage(final Integer startPage)
|
||||
{
|
||||
this.startPage = startPage;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ImageMagickOptionsBuilder withEndPage(final String endPage)
|
||||
{
|
||||
return withEndPage(stringToInteger(endPage));
|
||||
}
|
||||
|
||||
public ImageMagickOptionsBuilder withEndPage(final Integer endPage)
|
||||
{
|
||||
this.endPage = endPage;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ImageMagickOptionsBuilder withAlphaRemove(final String alphaRemove)
|
||||
{
|
||||
return withAlphaRemove(stringToBoolean(alphaRemove));
|
||||
@@ -353,7 +329,7 @@ public final class ImageMagickOptionsBuilder
|
||||
|
||||
return (commandOptions == null || "".equals(
|
||||
commandOptions.trim()) ? "" : commandOptions + ' ') +
|
||||
args.toString();
|
||||
args;
|
||||
}
|
||||
|
||||
public static ImageMagickOptionsBuilder builder()
|
||||
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Transform Core
|
||||
* %%
|
||||
* Copyright (C) 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* -
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
* -
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* -
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
* -
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.transform.imagemagick.transformers;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import org.alfresco.transform.base.executors.AbstractCommandExecutor;
|
||||
import org.alfresco.transform.base.executors.RuntimeExec;
|
||||
|
||||
@Component
|
||||
public class ImageMagickCommandExecutor extends AbstractCommandExecutor
|
||||
{
|
||||
@Value("${transform.core.imagemagick.exe}")
|
||||
private String exe;
|
||||
@Value("${transform.core.imagemagick.dyn}")
|
||||
private String dyn;
|
||||
@Value("${transform.core.imagemagick.root}")
|
||||
private String root;
|
||||
|
||||
// Not currently used, but may be again in the future if we need an ImageMagick extension
|
||||
@Value("${transform.core.imagemagick.coders}")
|
||||
private String coders;
|
||||
@Value("${transform.core.imagemagick.config}")
|
||||
private String config;
|
||||
|
||||
@PostConstruct
|
||||
private void createCommands()
|
||||
{
|
||||
if (StringUtils.isEmpty(exe))
|
||||
{
|
||||
throw new IllegalArgumentException("ImageMagickTransformer IMAGEMAGICK_EXE variable cannot be null or empty");
|
||||
}
|
||||
if (StringUtils.isEmpty(dyn))
|
||||
{
|
||||
throw new IllegalArgumentException("ImageMagickTransformer IMAGEMAGICK_DYN variable cannot be null or empty");
|
||||
}
|
||||
if (StringUtils.isEmpty(root))
|
||||
{
|
||||
throw new IllegalArgumentException("ImageMagickTransformer IMAGEMAGICK_ROOT variable cannot be null or empty");
|
||||
}
|
||||
|
||||
super.transformCommand = createTransformCommand();
|
||||
super.checkCommand = createCheckCommand();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RuntimeExec createTransformCommand()
|
||||
{
|
||||
RuntimeExec runtimeExec = new RuntimeExec();
|
||||
Map<String, String[]> commandsAndArguments = new HashMap<>();
|
||||
commandsAndArguments.put(".*",
|
||||
new String[]{exe, "${source}", "SPLIT:${options}", "-strip", "-quiet", "${target}"});
|
||||
runtimeExec.setCommandsAndArguments(commandsAndArguments);
|
||||
|
||||
Map<String, String> processProperties = new HashMap<>();
|
||||
processProperties.put("MAGICK_HOME", root);
|
||||
processProperties.put("DYLD_FALLBACK_LIBRARY_PATH", dyn);
|
||||
processProperties.put("LD_LIBRARY_PATH", dyn);
|
||||
|
||||
// Optional properties (see also https://imagemagick.org/script/resources.php#environment)
|
||||
if (coders != null && !coders.isBlank())
|
||||
{
|
||||
processProperties.put("MAGICK_CODER_MODULE_PATH", coders);
|
||||
}
|
||||
if (config != null && !config.isBlank())
|
||||
{
|
||||
processProperties.put("MAGICK_CONFIGURE_PATH", config);
|
||||
}
|
||||
runtimeExec.setProcessProperties(processProperties);
|
||||
|
||||
Map<String, String> defaultProperties = new HashMap<>();
|
||||
defaultProperties.put("options", null);
|
||||
runtimeExec.setDefaultProperties(defaultProperties);
|
||||
|
||||
runtimeExec.setErrorCodes(
|
||||
"1,2,255,400,405,410,415,420,425,430,435,440,450,455,460,465,470,475,480,485,490,495,499,700,705,710,715,720,725,730,735,740,750,755,760,765,770,775,780,785,790,795,799");
|
||||
|
||||
return runtimeExec;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RuntimeExec createCheckCommand()
|
||||
{
|
||||
RuntimeExec runtimeExec = new RuntimeExec();
|
||||
Map<String, String[]> commandsAndArguments = new HashMap<>();
|
||||
commandsAndArguments.put(".*", new String[]{exe, "-version"});
|
||||
runtimeExec.setCommandsAndArguments(commandsAndArguments);
|
||||
return runtimeExec;
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Transform Core
|
||||
* %%
|
||||
* Copyright (C) 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* -
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
* -
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* -
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
* -
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.transform.imagemagick.transformers;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ImageMagickCommandOptions
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ImageMagickCommandOptions.class);
|
||||
|
||||
@Value("${transform.core.imagemagick.commandOptions.enabled}")
|
||||
private boolean commandOptionsEnabled;
|
||||
|
||||
@PostConstruct
|
||||
void init()
|
||||
{
|
||||
if (commandOptionsEnabled)
|
||||
{
|
||||
LOG.warn("Parameter commandOptions is enabled. It is deprecated and due to security concerns, it will be removed in the future.");
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.info("Parameter commandOptions is disabled.");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCommandOptionsEnabled()
|
||||
{
|
||||
return commandOptionsEnabled;
|
||||
}
|
||||
}
|
||||
+19
-128
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Transform Core
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* -
|
||||
@@ -26,13 +26,7 @@
|
||||
*/
|
||||
package org.alfresco.transform.imagemagick.transformers;
|
||||
|
||||
import static org.alfresco.transform.base.util.Util.stringToInteger;
|
||||
import static org.alfresco.transform.base.util.Util.stringToLong;
|
||||
import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_BMP;
|
||||
import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_JP2;
|
||||
import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_JPEG;
|
||||
import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_PNG;
|
||||
import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_XWD;
|
||||
import static org.alfresco.transform.common.RequestParamMap.ALLOW_ENLARGEMENT;
|
||||
import static org.alfresco.transform.common.RequestParamMap.ALPHA_REMOVE;
|
||||
import static org.alfresco.transform.common.RequestParamMap.AUTO_ORIENT;
|
||||
@@ -43,71 +37,39 @@ import static org.alfresco.transform.common.RequestParamMap.CROP_PERCENTAGE;
|
||||
import static org.alfresco.transform.common.RequestParamMap.CROP_WIDTH;
|
||||
import static org.alfresco.transform.common.RequestParamMap.CROP_X_OFFSET;
|
||||
import static org.alfresco.transform.common.RequestParamMap.CROP_Y_OFFSET;
|
||||
import static org.alfresco.transform.common.RequestParamMap.END_PAGE;
|
||||
import static org.alfresco.transform.common.RequestParamMap.MAINTAIN_ASPECT_RATIO;
|
||||
import static org.alfresco.transform.common.RequestParamMap.RESIZE_HEIGHT;
|
||||
import static org.alfresco.transform.common.RequestParamMap.RESIZE_PERCENTAGE;
|
||||
import static org.alfresco.transform.common.RequestParamMap.RESIZE_WIDTH;
|
||||
import static org.alfresco.transform.common.RequestParamMap.START_PAGE;
|
||||
import static org.alfresco.transform.common.RequestParamMap.THUMBNAIL;
|
||||
import static org.alfresco.transform.common.RequestParamMap.TIMEOUT;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import org.alfresco.transform.base.TransformManager;
|
||||
import org.alfresco.transform.base.executors.AbstractCommandExecutor;
|
||||
import org.alfresco.transform.base.executors.RuntimeExec;
|
||||
import org.alfresco.transform.base.util.CustomTransformerFileAdaptor;
|
||||
import org.alfresco.transform.exceptions.TransformException;
|
||||
import org.alfresco.transform.imagemagick.ImageMagickOptionsBuilder;
|
||||
import org.alfresco.transform.imagemagick.transformers.page.PageRangeFactory;
|
||||
|
||||
/**
|
||||
* Converts image files into different types of images. Transformer supports multi-page images and allows to specify via parameters `startPage` and `endPage` range of pages that should be converted. In case of a one-page target image type (like `jpeg` or `png`) parameters `startPage` and `endPage` will be set to 0 by default - this means that only first page will be converted.
|
||||
*/
|
||||
@Component
|
||||
public class ImageMagickTransformer extends AbstractCommandExecutor implements CustomTransformerFileAdaptor
|
||||
public class ImageMagickTransformer implements CustomTransformerFileAdaptor
|
||||
{
|
||||
private final List<String> singlePageFormats = List.of(MIMETYPE_IMAGE_BMP, MIMETYPE_IMAGE_JP2, MIMETYPE_IMAGE_JPEG, MIMETYPE_IMAGE_PNG, MIMETYPE_IMAGE_XWD);
|
||||
private final ImageMagickCommandExecutor imageMagickCommandExecutor;
|
||||
private final PageRangeFactory pageRangeFactory;
|
||||
private final ImageMagickCommandOptions imageMagickCommandOptions;
|
||||
|
||||
@Value("${transform.core.imagemagick.exe}")
|
||||
private String exe;
|
||||
@Value("${transform.core.imagemagick.dyn}")
|
||||
private String dyn;
|
||||
@Value("${transform.core.imagemagick.root}")
|
||||
private String root;
|
||||
|
||||
// Not currently used, but may be again in the future if we need an ImageMagick extension
|
||||
@Value("${transform.core.imagemagick.coders}")
|
||||
private String coders;
|
||||
@Value("${transform.core.imagemagick.config}")
|
||||
private String config;
|
||||
|
||||
@PostConstruct
|
||||
private void createCommands()
|
||||
public ImageMagickTransformer(ImageMagickCommandExecutor imageMagickCommandExecutor, PageRangeFactory pageRangeFactory, ImageMagickCommandOptions imageMagickCommandOptions)
|
||||
{
|
||||
if (exe == null || exe.isEmpty())
|
||||
{
|
||||
throw new IllegalArgumentException("ImageMagickTransformer IMAGEMAGICK_EXE variable cannot be null or empty");
|
||||
}
|
||||
if (dyn == null || dyn.isEmpty())
|
||||
{
|
||||
throw new IllegalArgumentException("ImageMagickTransformer IMAGEMAGICK_DYN variable cannot be null or empty");
|
||||
}
|
||||
if (root == null || root.isEmpty())
|
||||
{
|
||||
throw new IllegalArgumentException("ImageMagickTransformer IMAGEMAGICK_ROOT variable cannot be null or empty");
|
||||
}
|
||||
|
||||
super.transformCommand = createTransformCommand();
|
||||
super.checkCommand = createCheckCommand();
|
||||
this.imageMagickCommandExecutor = imageMagickCommandExecutor;
|
||||
this.pageRangeFactory = pageRangeFactory;
|
||||
this.imageMagickCommandOptions = imageMagickCommandOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -116,73 +78,12 @@ public class ImageMagickTransformer extends AbstractCommandExecutor implements C
|
||||
return "imagemagick";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RuntimeExec createTransformCommand()
|
||||
{
|
||||
RuntimeExec runtimeExec = new RuntimeExec();
|
||||
Map<String, String[]> commandsAndArguments = new HashMap<>();
|
||||
commandsAndArguments.put(".*",
|
||||
new String[]{exe, "${source}", "SPLIT:${options}", "-strip", "-quiet", "${target}"});
|
||||
runtimeExec.setCommandsAndArguments(commandsAndArguments);
|
||||
|
||||
Map<String, String> processProperties = new HashMap<>();
|
||||
processProperties.put("MAGICK_HOME", root);
|
||||
processProperties.put("DYLD_FALLBACK_LIBRARY_PATH", dyn);
|
||||
processProperties.put("LD_LIBRARY_PATH", dyn);
|
||||
|
||||
// Optional properties (see also https://imagemagick.org/script/resources.php#environment)
|
||||
if (coders != null && !coders.isBlank())
|
||||
{
|
||||
processProperties.put("MAGICK_CODER_MODULE_PATH", coders);
|
||||
}
|
||||
if (config != null && !config.isBlank())
|
||||
{
|
||||
processProperties.put("MAGICK_CONFIGURE_PATH", config);
|
||||
}
|
||||
runtimeExec.setProcessProperties(processProperties);
|
||||
|
||||
Map<String, String> defaultProperties = new HashMap<>();
|
||||
defaultProperties.put("options", null);
|
||||
runtimeExec.setDefaultProperties(defaultProperties);
|
||||
|
||||
runtimeExec.setErrorCodes(
|
||||
"1,2,255,400,405,410,415,420,425,430,435,440,450,455,460,465,470,475,480,485,490,495,499,700,705,710,715,720,725,730,735,740,750,755,760,765,770,775,780,785,790,795,799");
|
||||
|
||||
return runtimeExec;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RuntimeExec createCheckCommand()
|
||||
{
|
||||
RuntimeExec runtimeExec = new RuntimeExec();
|
||||
Map<String, String[]> commandsAndArguments = new HashMap<>();
|
||||
commandsAndArguments.put(".*", new String[]{exe, "-version"});
|
||||
runtimeExec.setCommandsAndArguments(commandsAndArguments);
|
||||
return runtimeExec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(String sourceMimetype, String targetMimetype, Map<String, String> transformOptions,
|
||||
File sourceFile, File targetFile, TransformManager transformManager) throws TransformException
|
||||
{
|
||||
String startPageString = transformOptions.get(START_PAGE);
|
||||
String endPageString = transformOptions.get(END_PAGE);
|
||||
if (!singlePageFormats.contains(sourceMimetype) && singlePageFormats.contains(targetMimetype))
|
||||
{
|
||||
if (StringUtils.isEmpty(startPageString))
|
||||
{
|
||||
startPageString = "0";
|
||||
}
|
||||
if (StringUtils.isEmpty(endPageString))
|
||||
{
|
||||
endPageString = startPageString;
|
||||
}
|
||||
}
|
||||
|
||||
final String options = ImageMagickOptionsBuilder
|
||||
ImageMagickOptionsBuilder builder = ImageMagickOptionsBuilder
|
||||
.builder()
|
||||
.withStartPage(startPageString)
|
||||
.withEndPage(endPageString)
|
||||
.withAlphaRemove(transformOptions.get(ALPHA_REMOVE))
|
||||
.withAutoOrient(transformOptions.get(AUTO_ORIENT))
|
||||
.withCropGravity(transformOptions.get(CROP_GRAVITY))
|
||||
@@ -196,27 +97,17 @@ public class ImageMagickTransformer extends AbstractCommandExecutor implements C
|
||||
.withResizeHeight(transformOptions.get(RESIZE_HEIGHT))
|
||||
.withResizePercentage(transformOptions.get(RESIZE_PERCENTAGE))
|
||||
.withAllowEnlargement(transformOptions.get(ALLOW_ENLARGEMENT))
|
||||
.withMaintainAspectRatio(transformOptions.get(MAINTAIN_ASPECT_RATIO))
|
||||
.withCommandOptions(transformOptions.get(COMMAND_OPTIONS))
|
||||
.build();
|
||||
.withMaintainAspectRatio(transformOptions.get(MAINTAIN_ASPECT_RATIO));
|
||||
|
||||
String pageRange = calculatePageRange(
|
||||
stringToInteger(startPageString),
|
||||
stringToInteger(endPageString));
|
||||
if (imageMagickCommandOptions.isCommandOptionsEnabled())
|
||||
{
|
||||
builder.withCommandOptions(transformOptions.get(COMMAND_OPTIONS));
|
||||
}
|
||||
String options = builder.build();
|
||||
|
||||
String pageRange = pageRangeFactory.create(sourceMimetype, targetMimetype, transformOptions);
|
||||
Long timeout = stringToLong(transformOptions.get(TIMEOUT));
|
||||
|
||||
run(options, sourceFile, pageRange, targetFile, timeout);
|
||||
}
|
||||
|
||||
private static String calculatePageRange(Integer startPage, Integer endPage)
|
||||
{
|
||||
return startPage == null
|
||||
? endPage == null
|
||||
? ""
|
||||
: "[" + endPage + ']'
|
||||
: endPage == null || startPage.equals(endPage)
|
||||
? "[" + startPage + ']'
|
||||
: "[" + startPage + '-' + endPage + ']';
|
||||
imageMagickCommandExecutor.run(options, sourceFile, pageRange, targetFile, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Transform Core
|
||||
* %%
|
||||
* Copyright (C) 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* -
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
* -
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* -
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
* -
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.transform.imagemagick.transformers.page;
|
||||
|
||||
import static org.alfresco.transform.base.util.Util.stringToInteger;
|
||||
import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_BMP;
|
||||
import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_JP2;
|
||||
import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_JPEG;
|
||||
import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_PNG;
|
||||
import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_XWD;
|
||||
import static org.alfresco.transform.common.RequestParamMap.END_PAGE;
|
||||
import static org.alfresco.transform.common.RequestParamMap.START_PAGE;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class PageRangeFactory
|
||||
{
|
||||
private final List<String> singlePageFormats = List.of(MIMETYPE_IMAGE_BMP, MIMETYPE_IMAGE_JP2, MIMETYPE_IMAGE_JPEG, MIMETYPE_IMAGE_PNG, MIMETYPE_IMAGE_XWD);
|
||||
|
||||
public String create(String sourceMimetype, String targetMimetype, Map<String, String> transformOptions)
|
||||
{
|
||||
String startPageString = transformOptions.get(START_PAGE);
|
||||
String endPageString = transformOptions.get(END_PAGE);
|
||||
if (!singlePageFormats.contains(sourceMimetype) && singlePageFormats.contains(targetMimetype))
|
||||
{
|
||||
if (StringUtils.isEmpty(startPageString))
|
||||
{
|
||||
startPageString = "0";
|
||||
}
|
||||
if (StringUtils.isEmpty(endPageString))
|
||||
{
|
||||
endPageString = startPageString;
|
||||
}
|
||||
}
|
||||
Integer startPage = stringToInteger(startPageString);
|
||||
Integer endPage = stringToInteger(endPageString);
|
||||
return calculatePageRange(startPage, endPage);
|
||||
}
|
||||
|
||||
private String calculatePageRange(Integer startPage, Integer endPage)
|
||||
{
|
||||
return startPage == null
|
||||
? endPage == null
|
||||
? ""
|
||||
: "[" + endPage + ']'
|
||||
: endPage == null || startPage.equals(endPage)
|
||||
? "[" + startPage + ']'
|
||||
: "[" + startPage + '-' + endPage + ']';
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,10 @@ queue:
|
||||
transform:
|
||||
core:
|
||||
imagemagick:
|
||||
root: ${IMAGEMAGICK_ROOT:/usr/lib64/ImageMagick-7.0.10}
|
||||
dyn: ${IMAGEMAGICK_DYN:/usr/lib64/ImageMagick-7.0.10/lib}
|
||||
root: ${IMAGEMAGICK_ROOT:/usr/lib64/ImageMagick-7.1.2}
|
||||
dyn: ${IMAGEMAGICK_DYN:/usr/lib64/ImageMagick-7.1.2/lib}
|
||||
exe: ${IMAGEMAGICK_EXE:/usr/bin/convert}
|
||||
coders: ${IMAGEMAGICK_CODERS:}
|
||||
config: ${IMAGEMAGICK_CONFIG:}
|
||||
commandOptions:
|
||||
enabled: ${IMAGEMAGICK_COMMAND_OPTIONS_ENABLED:false}
|
||||
|
||||
@@ -1114,4 +1114,4 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+6
-7
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Transform Core
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* -
|
||||
@@ -79,7 +79,7 @@ import org.alfresco.transform.base.model.FileRefEntity;
|
||||
import org.alfresco.transform.base.model.FileRefResponse;
|
||||
import org.alfresco.transform.client.model.TransformReply;
|
||||
import org.alfresco.transform.client.model.TransformRequest;
|
||||
import org.alfresco.transform.imagemagick.transformers.ImageMagickTransformer;
|
||||
import org.alfresco.transform.imagemagick.transformers.ImageMagickCommandExecutor;
|
||||
|
||||
/**
|
||||
* Test ImageMagick with mocked external command.
|
||||
@@ -89,7 +89,7 @@ public class ImageMagickTest extends AbstractBaseTest
|
||||
private static String PREFIX_IMAGE = "image/";
|
||||
|
||||
@Autowired
|
||||
private ImageMagickTransformer imageMagickTransformer;
|
||||
private ImageMagickCommandExecutor imageMagickCommandExecutor;
|
||||
|
||||
@Mock
|
||||
protected ExecutionResult mockExecutionResult;
|
||||
@@ -111,7 +111,7 @@ public class ImageMagickTest extends AbstractBaseTest
|
||||
@BeforeEach
|
||||
public void before() throws IOException
|
||||
{
|
||||
setMockExternalCommandsOnTransformer(imageMagickTransformer, mockTransformCommand, mockCheckCommand);
|
||||
setMockExternalCommandsOnTransformer(imageMagickCommandExecutor, mockTransformCommand, mockCheckCommand);
|
||||
mockTransformCommand("jpg", "png", "image/jpeg", true);
|
||||
}
|
||||
|
||||
@@ -312,10 +312,9 @@ public class ImageMagickTest extends AbstractBaseTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deprecatedCommandOptionsTest() throws Exception
|
||||
public void deprecatedCommandOptionsIsSkippedByDefaultTest() throws Exception
|
||||
{
|
||||
// Example of why the commandOptions parameter is a bad idea.
|
||||
expectedOptions = "( horrible command / ); -auto-orient -resize 321x654";
|
||||
expectedOptions = "-auto-orient -resize 321x654";
|
||||
mockMvc
|
||||
.perform(MockMvcRequestBuilders
|
||||
.multipart(ENDPOINT_TRANSFORM)
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Transform Core
|
||||
* %%
|
||||
* Copyright (C) 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* -
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
* -
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* -
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
* -
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.transform.imagemagick;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.alfresco.transform.imagemagick.transformers.ImageMagickCommandExecutor;
|
||||
import org.alfresco.transform.imagemagick.transformers.ImageMagickCommandOptions;
|
||||
import org.alfresco.transform.imagemagick.transformers.ImageMagickTransformer;
|
||||
import org.alfresco.transform.imagemagick.transformers.page.PageRangeFactory;
|
||||
|
||||
public class ImageMagickTransformerTest
|
||||
{
|
||||
private ImageMagickCommandExecutor imageMagickCommandExecutor;
|
||||
private ImageMagickCommandOptions imageMagickCommandOptions;
|
||||
private ImageMagickTransformer imageMagickTransformer;
|
||||
|
||||
@BeforeEach
|
||||
void setUp()
|
||||
{
|
||||
imageMagickCommandExecutor = mock(ImageMagickCommandExecutor.class);
|
||||
PageRangeFactory pageRangeFactory = mock(PageRangeFactory.class);
|
||||
imageMagickCommandOptions = mock(ImageMagickCommandOptions.class);
|
||||
imageMagickTransformer = new ImageMagickTransformer(imageMagickCommandExecutor, pageRangeFactory, imageMagickCommandOptions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotAllowArgumentInjectionThroughCommandOptionsWhenDisabled()
|
||||
{
|
||||
when(imageMagickCommandOptions.isCommandOptionsEnabled()).thenReturn(false);
|
||||
Map<String, String> transformOptions = Map.of("commandOptions", "( horrible command / );");
|
||||
|
||||
imageMagickTransformer.transform(null, null, transformOptions, null, null, null);
|
||||
|
||||
ArgumentCaptor<String> optionsCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(imageMagickCommandExecutor).run(optionsCaptor.capture(), any(), any(), any(), any());
|
||||
assertThat(optionsCaptor.getValue()).doesNotContain("horrible", "horrible command", "( horrible command / );");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAllowArgumentInjectionThroughCommandOptionsWhenEnabled()
|
||||
{
|
||||
when(imageMagickCommandOptions.isCommandOptionsEnabled()).thenReturn(true);
|
||||
Map<String, String> transformOptions = Map.of("commandOptions", "( horrible command / );");
|
||||
|
||||
imageMagickTransformer.transform(null, null, transformOptions, null, null, null);
|
||||
|
||||
ArgumentCaptor<String> optionsCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(imageMagickCommandExecutor).run(optionsCaptor.capture(), any(), any(), any(), any());
|
||||
assertThat(optionsCaptor.getValue()).contains("horrible", "horrible command", "( horrible command / );");
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Transform Core
|
||||
* %%
|
||||
* Copyright (C) 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* -
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
* -
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* -
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
* -
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.transform.imagemagick.transformers;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest(classes = {ImageMagickCommandOptions.class})
|
||||
class ImageMagickCommandOptionsTest
|
||||
{
|
||||
@Value("${transform.core.imagemagick.commandOptions.enabled}")
|
||||
boolean commandOptionsEnabled;
|
||||
|
||||
@Test
|
||||
void shouldBeDisabledByDefault()
|
||||
{
|
||||
assertFalse(commandOptionsEnabled);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user