Skip to content

Commit 7dc624c

Browse files
authored
Merge pull request #12136 from GlobalDataverseCommunityConsortium/Add_logo/thumb_type_checks
Add logo/thumb type checks
2 parents 180aa55 + 543372a commit 7dc624c

5 files changed

Lines changed: 120 additions & 15 deletions

File tree

src/main/java/edu/harvard/iq/dataverse/DatasetWidgetsPage.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@
99
import edu.harvard.iq.dataverse.util.BundleUtil;
1010
import edu.harvard.iq.dataverse.util.FileUtil;
1111
import edu.harvard.iq.dataverse.util.JsfHelper;
12+
import edu.harvard.iq.dataverse.util.SystemConfig;
13+
1214
import java.io.File;
1315
import java.io.IOException;
1416
import java.util.List;
1517
import java.util.logging.Level;
1618
import java.util.logging.Logger;
1719
import jakarta.ejb.EJB;
20+
import jakarta.faces.application.FacesMessage;
21+
import jakarta.faces.context.FacesContext;
1822
import jakarta.faces.view.ViewScoped;
1923
import jakarta.inject.Inject;
2024
import jakarta.inject.Named;
@@ -32,6 +36,9 @@ public class DatasetWidgetsPage implements java.io.Serializable {
3236

3337
@EJB
3438
EjbDataverseEngine commandEngine;
39+
40+
@EJB
41+
SystemConfig systemConfig;
3542

3643
@Inject
3744
DataverseRequestServiceBean dvRequestService;
@@ -131,6 +138,12 @@ public void flagDatasetThumbnailForRemoval() {
131138
public void handleImageFileUpload(FileUploadEvent event) {
132139
logger.fine("handleImageFileUpload clicked");
133140
UploadedFile uploadedFile = event.getFile();
141+
long maxSize = systemConfig.getThumbnailSizeLimitImage();
142+
if (!FileUtil.isUploadedFileAnImage(uploadedFile, maxSize)) {
143+
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Only image files are allowed.", "Only image files under " + maxSize + " bytes are allowed.");
144+
FacesContext.getCurrentInstance().addMessage(null, msg);
145+
return;
146+
}
134147
try {
135148
updateDatasetThumbnailCommand = new UpdateDatasetThumbnailCommand(dvRequestService.getDataverseRequest(), dataset, UpdateDatasetThumbnailCommand.UserIntent.setNonDatasetFileAsThumbnail, null, uploadedFile.getInputStream());
136149
} catch (IOException ex) {

src/main/java/edu/harvard/iq/dataverse/ThemeWidgetFragment.java

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,25 @@
99
import edu.harvard.iq.dataverse.engine.command.impl.UpdateDataverseThemeCommand;
1010
import edu.harvard.iq.dataverse.settings.JvmSettings;
1111
import edu.harvard.iq.dataverse.util.BundleUtil;
12+
import edu.harvard.iq.dataverse.util.FileUtil;
1213
import edu.harvard.iq.dataverse.util.JsfHelper;
14+
import edu.harvard.iq.dataverse.util.SystemConfig;
15+
16+
import java.awt.image.BufferedImage;
1317
import java.io.File;
1418
import java.io.IOException;
19+
import java.io.InputStream;
1520
import java.net.MalformedURLException;
1621
import java.net.URL;
1722
import java.nio.file.Files;
1823
import java.nio.file.Path;
1924
import java.nio.file.StandardCopyOption;
25+
import java.util.Set;
2026
import java.util.logging.Level;
2127
import java.util.logging.Logger;
28+
29+
import javax.imageio.ImageIO;
30+
2231
import jakarta.annotation.PreDestroy;
2332
import jakarta.ejb.EJB;
2433
import jakarta.faces.application.FacesMessage;
@@ -51,7 +60,8 @@ public class ThemeWidgetFragment implements java.io.Serializable {
5160

5261
public static final String LOGOS_SUBDIR = "logos";
5362
public static final String LOGOS_TEMP_SUBDIR = LOGOS_SUBDIR + File.separator + "temp";
54-
63+
private long maxSize = 0;
64+
5565
private File tempDir;
5666
private File uploadedFile;
5767
private File uploadedFileThumbnail;
@@ -65,6 +75,7 @@ public class ThemeWidgetFragment implements java.io.Serializable {
6575
EjbDataverseEngine commandEngine;
6676
@EJB
6777
DataverseServiceBean dataverseServiceBean;
78+
@EJB SystemConfig systemConfig;
6879
@Inject
6980
DataverseRequestServiceBean dvRequestService;
7081

@@ -228,6 +239,11 @@ public void handleImageThumbnailFileUpload(FileUploadEvent event) {
228239
logger.finer("created tempDir");
229240
}
230241
final UploadedFile uFile = event.getFile();
242+
if(!FileUtil.isUploadedFileAnImage(uFile, getMaxSize())) {
243+
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Only image files are allowed.", "Only image files under " + getMaxSize() + " bytes are allowed.");
244+
FacesContext.getCurrentInstance().addMessage(null, msg);
245+
return;
246+
}
231247
try {
232248
this.uploadedFileThumbnail = new File(tempDir, uFile.getFileName());
233249
if (!this.uploadedFileThumbnail.exists()) {
@@ -258,6 +274,12 @@ public void handleImageFooterFileUpload(FileUploadEvent event) {
258274
logger.finer("created tempDir");
259275
}
260276
UploadedFile uFile = event.getFile();
277+
if (!FileUtil.isUploadedFileAnImage(uFile, getMaxSize())) {
278+
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Only image files are allowed.", "Only image files under " + getMaxSize() + " bytes are allowed.");
279+
FacesContext.getCurrentInstance().addMessage(null, msg);
280+
return;
281+
}
282+
261283
try {
262284
uploadedFileFooter = new File(tempDir, uFile.getFileName());
263285
if (!uploadedFileFooter.exists()) {
@@ -283,8 +305,13 @@ public void handleImageFileUpload(FileUploadEvent event) {
283305
logger.finer("created tempDir");
284306
}
285307
UploadedFile uFile = event.getFile();
286-
try {
287-
uploadedFile = new File(tempDir, uFile.getFileName());
308+
if (!FileUtil.isUploadedFileAnImage(uFile, getMaxSize())) {
309+
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Only image files are allowed.", "Only image files under " + getMaxSize() + " bytes are allowed.");
310+
FacesContext.getCurrentInstance().addMessage(null, msg);
311+
return;
312+
}
313+
try {
314+
uploadedFile = new File(tempDir, uFile.getFileName());
288315
if (!uploadedFile.exists()) {
289316
uploadedFile.createNewFile();
290317
}
@@ -430,6 +457,13 @@ public boolean exectThemeCommand(Command<Dataverse> cmd){
430457
return true;
431458
}
432459

460+
// Initialize maxSize from systemConfig
461+
public long getMaxSize() {
462+
if (maxSize == 0) {
463+
maxSize = systemConfig.getUploadLogoSizeLimit();
464+
}
465+
return maxSize;
466+
}
433467
}
434468

435469

src/main/java/edu/harvard/iq/dataverse/util/FileUtil.java

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,11 @@
3434
import static edu.harvard.iq.dataverse.datasetutility.FileSizeChecker.bytesToHumanReadable;
3535
import edu.harvard.iq.dataverse.ingest.IngestReport;
3636
import edu.harvard.iq.dataverse.ingest.IngestServiceBean;
37-
import edu.harvard.iq.dataverse.ingest.IngestServiceShapefileHelper;
3837
import edu.harvard.iq.dataverse.ingest.IngestableDataChecker;
3938
import edu.harvard.iq.dataverse.license.License;
4039
import edu.harvard.iq.dataverse.settings.ConfigCheckService;
4140
import edu.harvard.iq.dataverse.settings.JvmSettings;
4241
import edu.harvard.iq.dataverse.util.file.BagItFileHandler;
43-
import edu.harvard.iq.dataverse.util.file.CreateDataFileResult;
4442
import edu.harvard.iq.dataverse.util.file.BagItFileHandlerFactory;
4543
import edu.harvard.iq.dataverse.util.xml.XmlUtil;
4644
import edu.harvard.iq.dataverse.util.xml.html.HtmlFormatUtil;
@@ -54,6 +52,7 @@
5452
import static edu.harvard.iq.dataverse.util.xml.html.HtmlFormatUtil.formatTableCellAlignRight;
5553
import static edu.harvard.iq.dataverse.util.xml.html.HtmlFormatUtil.formatTableRow;
5654

55+
import java.awt.image.BufferedImage;
5756
import java.io.BufferedInputStream;
5857
import java.io.File;
5958
import java.io.FileInputStream;
@@ -64,7 +63,6 @@
6463
import java.io.InputStream;
6564
import java.io.OutputStream;
6665
import java.nio.ByteBuffer;
67-
import java.nio.charset.Charset;
6866
import java.nio.file.Files;
6967
import java.nio.file.Path;
7068
import java.nio.file.Paths;
@@ -82,7 +80,7 @@
8280
import java.util.HashMap;
8381
import java.util.List;
8482
import java.util.Optional;
85-
import java.util.ResourceBundle;
83+
import java.util.Set;
8684
import java.util.UUID;
8785
import java.util.logging.Level;
8886
import java.util.logging.Logger;
@@ -92,6 +90,7 @@
9290
import jakarta.json.JsonArray;
9391
import jakarta.json.JsonObject;
9492

93+
import javax.imageio.ImageIO;
9594
import javax.xml.stream.XMLInputFactory;
9695
import javax.xml.stream.XMLStreamConstants;
9796
import javax.xml.stream.XMLStreamException;
@@ -106,9 +105,10 @@
106105
import edu.harvard.iq.dataverse.util.file.FileExceedsStorageQuotaException;
107106
import java.nio.charset.StandardCharsets;
108107
import java.util.Arrays;
109-
import org.apache.commons.io.IOUtils;
110108
import org.apache.commons.lang3.StringUtils;
111109
import org.apache.tika.Tika;
110+
import org.primefaces.model.file.UploadedFile;
111+
112112
import ucar.nc2.NetcdfFile;
113113
import ucar.nc2.NetcdfFiles;
114114

@@ -1900,5 +1900,63 @@ public static String decodeFileName(String originalFileName) {
19001900
}
19011901
return new String(originalFileName.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
19021902
}
1903+
1904+
/**
1905+
* Verifies that an uploaded file is a valid png or jpg image file. Performs both MIME type checking and content validation.
1906+
*
1907+
* Note: This is similar to the isFileOfImageType which is used for collection feature items. This method works with PrimeFaces UploadedFile vs File, limits to jpg and png (as the UI states), uses
1908+
* ImageIO to read the content, and checks size (as the caller of isFileOfImageType does). It avoids using Tika in the core (as we once tried to do) and is potentially slower but more thorough as it
1909+
* will confirm the image is not corrupt. Work could be done to merge the two use cases.
1910+
*
1911+
* @param uploadedFile
1912+
* the file to verify
1913+
* @param maxSize
1914+
* maximum allowed file size in bytes
1915+
*/
1916+
public static boolean isUploadedFileAnImage(UploadedFile uploadedFile, long maxSize) {
1917+
if (uploadedFile == null) {
1918+
return false;
1919+
}
1920+
1921+
// Pre-filter: Check MIME type first (fast rejection)
1922+
String contentType = uploadedFile.getContentType();
1923+
if (contentType == null || !contentType.startsWith("image/")) {
1924+
return false;
1925+
}
1926+
1927+
// Check against allowed MIME types
1928+
Set<String> allowedMimeTypes = Set.of(
1929+
"image/jpeg",
1930+
"image/jpg",
1931+
"image/png");
1932+
1933+
if (!allowedMimeTypes.contains(contentType.toLowerCase())) {
1934+
return false;
1935+
}
1936+
1937+
// Validate actual image content (security check)
1938+
try (InputStream inputStream = uploadedFile.getInputStream()) {
1939+
BufferedImage image = ImageIO.read(inputStream);
1940+
if (image == null) {
1941+
return false;
1942+
}
1943+
1944+
// Optional: Check file size limit (similar to DataverseFeaturedItemServiceBean)
1945+
1946+
if (uploadedFile.getSize() > maxSize) {
1947+
return false;
1948+
}
1949+
1950+
// Optional: Check image dimensions if needed
1951+
int width = image.getWidth();
1952+
int height = image.getHeight();
1953+
logger.fine("Uploaded image dimensions: " + width + "x" + height);
1954+
1955+
} catch (IOException e) {
1956+
logger.log(Level.WARNING, "Error reading uploaded image file", e);
1957+
return false;
1958+
}
1959+
return true;
1960+
}
19031961

19041962
}

src/main/webapp/dataset-widgets.xhtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
<span class="glyphicon glyphicon-question-sign tooltip-icon"
7474
data-toggle="tooltip" data-placement="auto right" data-original-title="#{bundle['dataset.thumbnailsAndWidget.thumbnailImage.uploadNew.title']}"></span>
7575
</label>
76-
<p:fileUpload invalidFileMessage="#{bundle['dataset.thumbnailsAndWidget.thumbnailImage.upload.invalidMsg']}" id="changelogo" allowTypes="/(\.|\/)(jpg|jpeg|tff|png|gif)$/" update="@form"
76+
<p:fileUpload invalidFileMessage="#{bundle['dataset.thumbnailsAndWidget.thumbnailImage.upload.invalidMsg']}" id="changelogo" allowTypes="/(\.|\/)(jpg|jpeg|png)$/" update="@form"
7777
sizeLimit="#{systemConfig.uploadLogoSizeLimit}"
7878
invalidSizeMessage="#{bundle['file.edit.error.file_exceeds_limit']}"
7979
dragDropSupport="true" auto="true" multiple="false"

src/main/webapp/themeAndWidgetsFragment.xhtml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@
5050
</p>
5151
<p:commandButton update=":themeWidgetsForm:themeWidgetsTabView" value="#{bundle.remove}" action="#{themeWidgetFragment.removeLogo()}"/>
5252
</p:column>
53-
<p:fileUpload invalidFileMessage="#{bundle['dataverse.theme.logo.image.invalidMsg']}" id="changelogo" allowTypes="/(\.|\/)(jpg|jpeg|tff|png|gif)$/" update=":themeWidgetsForm:themeWidgetsTabView" dragDropSupport="true" auto="true" multiple="false"
53+
<p:fileUpload invalidFileMessage="#{bundle['dataverse.theme.logo.image.invalidMsg']}" id="changelogo" allowTypes="/(\.|\/)(jpg|jpeg|png)$/" update=":themeWidgetsForm:themeWidgetsTabView" dragDropSupport="true" auto="true" multiple="false"
5454
listener="#{themeWidgetFragment.handleImageFileUpload}" label="#{bundle['dataverse.theme.logo.image.upload']}"/>
5555
</p:panelGrid>
5656
<p:panelGrid rendered="#{empty themeWidgetFragment.editDv.dataverseTheme.logo}" columns="2" styleClass="noBorders">
57-
<p:fileUpload id="uploadlogo" invalidFileMessage="#{bundle['dataverse.theme.logo.image.invalidMsg']}" sizeLimit="#{systemConfig.uploadLogoSizeLimit}" allowTypes="/(\.|\/)(jpg|jpeg|tff|png|gif)$/" update=":themeWidgetsForm:themeWidgetsTabView" dragDropSupport="true" auto="true" multiple="false"
57+
<p:fileUpload id="uploadlogo" invalidFileMessage="#{bundle['dataverse.theme.logo.image.invalidMsg']}" sizeLimit="#{systemConfig.uploadLogoSizeLimit}" allowTypes="/(\.|\/)(jpg|jpeg|png)$/" update=":themeWidgetsForm:themeWidgetsTabView" dragDropSupport="true" auto="true" multiple="false"
5858
listener="#{themeWidgetFragment.handleImageFileUpload}" label="#{bundle['dataverse.theme.logo.image.uploadImgFile']}" invalidSizeMessage="#{bundle['file.edit.error.file_exceeds_limit']}"/>
5959
</p:panelGrid>
6060
<ui:fragment rendered="#{not empty themeWidgetFragment.editDv.dataverseTheme.logo}">
@@ -120,11 +120,11 @@
120120
</p>
121121
<p:commandButton update=":themeWidgetsForm:themeWidgetsTabView" value="#{bundle.remove}" action="#{themeWidgetFragment.removeLogoThumbnail()}"/>
122122
</p:column>
123-
<p:fileUpload invalidFileMessage="#{bundle['dataverse.theme.logo.image.invalidMsg']}" id="changelogothumbnail" allowTypes="/(\.|\/)(jpg|jpeg|tff|png|gif)$/" update=":themeWidgetsForm:themeWidgetsTabView" dragDropSupport="true" auto="true" multiple="false"
123+
<p:fileUpload invalidFileMessage="#{bundle['dataverse.theme.logo.image.invalidMsg']}" id="changelogothumbnail" allowTypes="/(\.|\/)(jpg|jpeg|png)$/" update=":themeWidgetsForm:themeWidgetsTabView" dragDropSupport="true" auto="true" multiple="false"
124124
listener="#{themeWidgetFragment.handleImageThumbnailFileUpload}" label="#{bundle['dataverse.theme.logo.image.upload']}"/>
125125
</p:panelGrid>
126126
<p:panelGrid rendered="#{empty themeWidgetFragment.editDv.dataverseTheme.logoThumbnail}" columns="2" styleClass="noBorders">
127-
<p:fileUpload id="uploadlogoThumbnail" invalidFileMessage="#{bundle['dataverse.theme.logo.image.invalidMsg']}" sizeLimit="#{systemConfig.uploadLogoSizeLimit}" allowTypes="/(\.|\/)(jpg|jpeg|tff|png|gif)$/" update=":themeWidgetsForm:themeWidgetsTabView" dragDropSupport="true" auto="true" multiple="false"
127+
<p:fileUpload id="uploadlogoThumbnail" invalidFileMessage="#{bundle['dataverse.theme.logo.image.invalidMsg']}" sizeLimit="#{systemConfig.uploadLogoSizeLimit}" allowTypes="/(\.|\/)(jpg|jpeg|png)$/" update=":themeWidgetsForm:themeWidgetsTabView" dragDropSupport="true" auto="true" multiple="false"
128128
listener="#{themeWidgetFragment.handleImageThumbnailFileUpload}" label="#{bundle['dataverse.theme.logo.image.uploadImgFile']}" invalidSizeMessage="#{bundle['file.edit.error.file_exceeds_limit']}"/>
129129
</p:panelGrid>
130130
</div>
@@ -221,11 +221,11 @@
221221
</p>
222222
<p:commandButton update=":themeWidgetsForm:themeWidgetsTabView" value="#{bundle.remove}" action="#{themeWidgetFragment.removeLogoFooter()}"/>
223223
</p:column>
224-
<p:fileUpload invalidFileMessage="#{bundle['dataverse.theme.logo.image.invalidMsg']}" id="changelogoFooter" allowTypes="/(\.|\/)(jpg|jpeg|tff|png|gif)$/" update=":themeWidgetsForm:themeWidgetsTabView" dragDropSupport="true" auto="true" multiple="false"
224+
<p:fileUpload invalidFileMessage="#{bundle['dataverse.theme.logo.image.invalidMsg']}" id="changelogoFooter" allowTypes="/(\.|\/)(jpg|jpeg|png)$/" update=":themeWidgetsForm:themeWidgetsTabView" dragDropSupport="true" auto="true" multiple="false"
225225
listener="#{themeWidgetFragment.handleImageFooterFileUpload}" label="#{bundle['dataverse.theme.logo.image.upload']}"/>
226226
</p:panelGrid>
227227
<p:panelGrid rendered="#{empty themeWidgetFragment.editDv.dataverseTheme.logoFooter}" columns="2" styleClass="noBorders">
228-
<p:fileUpload id="uploadlogoFooter" invalidFileMessage="#{bundle['dataverse.theme.logo.image.invalidMsg']}" sizeLimit="#{systemConfig.uploadLogoSizeLimit}" allowTypes="/(\.|\/)(jpg|jpeg|tff|png|gif)$/" update=":themeWidgetsForm:themeWidgetsTabView" dragDropSupport="true" auto="true" multiple="false"
228+
<p:fileUpload id="uploadlogoFooter" invalidFileMessage="#{bundle['dataverse.theme.logo.image.invalidMsg']}" sizeLimit="#{systemConfig.uploadLogoSizeLimit}" allowTypes="/(\.|\/)(jpg|jpeg|png)$/" update=":themeWidgetsForm:themeWidgetsTabView" dragDropSupport="true" auto="true" multiple="false"
229229
listener="#{themeWidgetFragment.handleImageFooterFileUpload}" label="#{bundle['dataverse.theme.logo.image.uploadImgFile']}" invalidSizeMessage="#{bundle['file.edit.error.file_exceeds_limit']}"/>
230230
</p:panelGrid>
231231
<ui:fragment rendered="#{not empty themeWidgetFragment.editDv.dataverseTheme.logoFooter}">

0 commit comments

Comments
 (0)