Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/DatasetWidgetsPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
import edu.harvard.iq.dataverse.util.BundleUtil;
import edu.harvard.iq.dataverse.util.FileUtil;
import edu.harvard.iq.dataverse.util.JsfHelper;
import edu.harvard.iq.dataverse.util.SystemConfig;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import jakarta.ejb.EJB;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
Expand All @@ -32,6 +36,9 @@ public class DatasetWidgetsPage implements java.io.Serializable {

@EJB
EjbDataverseEngine commandEngine;

@EJB
SystemConfig systemConfig;

@Inject
DataverseRequestServiceBean dvRequestService;
Expand Down Expand Up @@ -131,6 +138,12 @@ public void flagDatasetThumbnailForRemoval() {
public void handleImageFileUpload(FileUploadEvent event) {
logger.fine("handleImageFileUpload clicked");
UploadedFile uploadedFile = event.getFile();
long maxSize = systemConfig.getThumbnailSizeLimitImage();
if (!FileUtil.isUploadedFileAnImage(uploadedFile, maxSize)) {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Only image files are allowed.", "Only image files under " + maxSize + " bytes are allowed.");
FacesContext.getCurrentInstance().addMessage(null, msg);
return;
}
try {
updateDatasetThumbnailCommand = new UpdateDatasetThumbnailCommand(dvRequestService.getDataverseRequest(), dataset, UpdateDatasetThumbnailCommand.UserIntent.setNonDatasetFileAsThumbnail, null, uploadedFile.getInputStream());
} catch (IOException ex) {
Expand Down
40 changes: 37 additions & 3 deletions src/main/java/edu/harvard/iq/dataverse/ThemeWidgetFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,25 @@
import edu.harvard.iq.dataverse.engine.command.impl.UpdateDataverseThemeCommand;
import edu.harvard.iq.dataverse.settings.JvmSettings;
import edu.harvard.iq.dataverse.util.BundleUtil;
import edu.harvard.iq.dataverse.util.FileUtil;
import edu.harvard.iq.dataverse.util.JsfHelper;
import edu.harvard.iq.dataverse.util.SystemConfig;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;

import jakarta.annotation.PreDestroy;
import jakarta.ejb.EJB;
import jakarta.faces.application.FacesMessage;
Expand Down Expand Up @@ -51,7 +60,8 @@ public class ThemeWidgetFragment implements java.io.Serializable {

public static final String LOGOS_SUBDIR = "logos";
public static final String LOGOS_TEMP_SUBDIR = LOGOS_SUBDIR + File.separator + "temp";

private long maxSize = 0;

private File tempDir;
private File uploadedFile;
private File uploadedFileThumbnail;
Expand All @@ -65,6 +75,7 @@ public class ThemeWidgetFragment implements java.io.Serializable {
EjbDataverseEngine commandEngine;
@EJB
DataverseServiceBean dataverseServiceBean;
@EJB SystemConfig systemConfig;
@Inject
DataverseRequestServiceBean dvRequestService;

Expand Down Expand Up @@ -228,6 +239,11 @@ public void handleImageThumbnailFileUpload(FileUploadEvent event) {
logger.finer("created tempDir");
}
final UploadedFile uFile = event.getFile();
if(!FileUtil.isUploadedFileAnImage(uFile, getMaxSize())) {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Only image files are allowed.", "Only image files under " + getMaxSize() + " bytes are allowed.");
FacesContext.getCurrentInstance().addMessage(null, msg);
return;
}
try {
this.uploadedFileThumbnail = new File(tempDir, uFile.getFileName());
if (!this.uploadedFileThumbnail.exists()) {
Expand Down Expand Up @@ -258,6 +274,12 @@ public void handleImageFooterFileUpload(FileUploadEvent event) {
logger.finer("created tempDir");
}
UploadedFile uFile = event.getFile();
if (!FileUtil.isUploadedFileAnImage(uFile, getMaxSize())) {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Only image files are allowed.", "Only image files under " + getMaxSize() + " bytes are allowed.");
FacesContext.getCurrentInstance().addMessage(null, msg);
return;
}

try {
uploadedFileFooter = new File(tempDir, uFile.getFileName());
if (!uploadedFileFooter.exists()) {
Expand All @@ -283,8 +305,13 @@ public void handleImageFileUpload(FileUploadEvent event) {
logger.finer("created tempDir");
}
UploadedFile uFile = event.getFile();
try {
uploadedFile = new File(tempDir, uFile.getFileName());
if (!FileUtil.isUploadedFileAnImage(uFile, getMaxSize())) {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Only image files are allowed.", "Only image files under " + getMaxSize() + " bytes are allowed.");
FacesContext.getCurrentInstance().addMessage(null, msg);
return;
}
try {
uploadedFile = new File(tempDir, uFile.getFileName());
if (!uploadedFile.exists()) {
uploadedFile.createNewFile();
}
Expand Down Expand Up @@ -430,6 +457,13 @@ public boolean exectThemeCommand(Command<Dataverse> cmd){
return true;
}

// Initialize maxSize from systemConfig
public long getMaxSize() {
if (maxSize == 0) {
maxSize = systemConfig.getUploadLogoSizeLimit();
}
return maxSize;
}
}


Expand Down
68 changes: 63 additions & 5 deletions src/main/java/edu/harvard/iq/dataverse/util/FileUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,11 @@
import static edu.harvard.iq.dataverse.datasetutility.FileSizeChecker.bytesToHumanReadable;
import edu.harvard.iq.dataverse.ingest.IngestReport;
import edu.harvard.iq.dataverse.ingest.IngestServiceBean;
import edu.harvard.iq.dataverse.ingest.IngestServiceShapefileHelper;
import edu.harvard.iq.dataverse.ingest.IngestableDataChecker;
import edu.harvard.iq.dataverse.license.License;
import edu.harvard.iq.dataverse.settings.ConfigCheckService;
import edu.harvard.iq.dataverse.settings.JvmSettings;
import edu.harvard.iq.dataverse.util.file.BagItFileHandler;
import edu.harvard.iq.dataverse.util.file.CreateDataFileResult;
import edu.harvard.iq.dataverse.util.file.BagItFileHandlerFactory;
import edu.harvard.iq.dataverse.util.xml.XmlUtil;
import edu.harvard.iq.dataverse.util.xml.html.HtmlFormatUtil;
Expand All @@ -54,6 +52,7 @@
import static edu.harvard.iq.dataverse.util.xml.html.HtmlFormatUtil.formatTableCellAlignRight;
import static edu.harvard.iq.dataverse.util.xml.html.HtmlFormatUtil.formatTableRow;

import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
Expand All @@ -64,7 +63,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand All @@ -82,7 +80,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -92,6 +90,7 @@
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;

import javax.imageio.ImageIO;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
Expand All @@ -106,9 +105,10 @@
import edu.harvard.iq.dataverse.util.file.FileExceedsStorageQuotaException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.tika.Tika;
import org.primefaces.model.file.UploadedFile;

import ucar.nc2.NetcdfFile;
import ucar.nc2.NetcdfFiles;

Expand Down Expand Up @@ -1900,5 +1900,63 @@ public static String decodeFileName(String originalFileName) {
}
return new String(originalFileName.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
}

/**
* Verifies that an uploaded file is a valid png or jpg image file. Performs both MIME type checking and content validation.
*
* 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
* 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
* will confirm the image is not corrupt. Work could be done to merge the two use cases.
*
* @param uploadedFile
* the file to verify
* @param maxSize
* maximum allowed file size in bytes
*/
public static boolean isUploadedFileAnImage(UploadedFile uploadedFile, long maxSize) {
if (uploadedFile == null) {
return false;
}

// Pre-filter: Check MIME type first (fast rejection)
String contentType = uploadedFile.getContentType();
if (contentType == null || !contentType.startsWith("image/")) {
return false;
}

// Check against allowed MIME types
Set<String> allowedMimeTypes = Set.of(
"image/jpeg",
"image/jpg",
"image/png");

if (!allowedMimeTypes.contains(contentType.toLowerCase())) {
return false;
}

// Validate actual image content (security check)
try (InputStream inputStream = uploadedFile.getInputStream()) {
BufferedImage image = ImageIO.read(inputStream);
if (image == null) {
return false;
}

// Optional: Check file size limit (similar to DataverseFeaturedItemServiceBean)

if (uploadedFile.getSize() > maxSize) {
return false;
}

// Optional: Check image dimensions if needed
int width = image.getWidth();
int height = image.getHeight();
logger.fine("Uploaded image dimensions: " + width + "x" + height);

} catch (IOException e) {
logger.log(Level.WARNING, "Error reading uploaded image file", e);
return false;
}
return true;
}

}
2 changes: 1 addition & 1 deletion src/main/webapp/dataset-widgets.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
<span class="glyphicon glyphicon-question-sign tooltip-icon"
data-toggle="tooltip" data-placement="auto right" data-original-title="#{bundle['dataset.thumbnailsAndWidget.thumbnailImage.uploadNew.title']}"></span>
</label>
<p:fileUpload invalidFileMessage="#{bundle['dataset.thumbnailsAndWidget.thumbnailImage.upload.invalidMsg']}" id="changelogo" allowTypes="/(\.|\/)(jpg|jpeg|tff|png|gif)$/" update="@form"
<p:fileUpload invalidFileMessage="#{bundle['dataset.thumbnailsAndWidget.thumbnailImage.upload.invalidMsg']}" id="changelogo" allowTypes="/(\.|\/)(jpg|jpeg|png)$/" update="@form"
sizeLimit="#{systemConfig.uploadLogoSizeLimit}"
invalidSizeMessage="#{bundle['file.edit.error.file_exceeds_limit']}"
dragDropSupport="true" auto="true" multiple="false"
Expand Down
12 changes: 6 additions & 6 deletions src/main/webapp/themeAndWidgetsFragment.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@
</p>
<p:commandButton update=":themeWidgetsForm:themeWidgetsTabView" value="#{bundle.remove}" action="#{themeWidgetFragment.removeLogo()}"/>
</p:column>
<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"
<p:fileUpload invalidFileMessage="#{bundle['dataverse.theme.logo.image.invalidMsg']}" id="changelogo" allowTypes="/(\.|\/)(jpg|jpeg|png)$/" update=":themeWidgetsForm:themeWidgetsTabView" dragDropSupport="true" auto="true" multiple="false"
listener="#{themeWidgetFragment.handleImageFileUpload}" label="#{bundle['dataverse.theme.logo.image.upload']}"/>
</p:panelGrid>
<p:panelGrid rendered="#{empty themeWidgetFragment.editDv.dataverseTheme.logo}" columns="2" styleClass="noBorders">
<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"
<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"
listener="#{themeWidgetFragment.handleImageFileUpload}" label="#{bundle['dataverse.theme.logo.image.uploadImgFile']}" invalidSizeMessage="#{bundle['file.edit.error.file_exceeds_limit']}"/>
</p:panelGrid>
<ui:fragment rendered="#{not empty themeWidgetFragment.editDv.dataverseTheme.logo}">
Expand Down Expand Up @@ -120,11 +120,11 @@
</p>
<p:commandButton update=":themeWidgetsForm:themeWidgetsTabView" value="#{bundle.remove}" action="#{themeWidgetFragment.removeLogoThumbnail()}"/>
</p:column>
<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"
<p:fileUpload invalidFileMessage="#{bundle['dataverse.theme.logo.image.invalidMsg']}" id="changelogothumbnail" allowTypes="/(\.|\/)(jpg|jpeg|png)$/" update=":themeWidgetsForm:themeWidgetsTabView" dragDropSupport="true" auto="true" multiple="false"
listener="#{themeWidgetFragment.handleImageThumbnailFileUpload}" label="#{bundle['dataverse.theme.logo.image.upload']}"/>
</p:panelGrid>
<p:panelGrid rendered="#{empty themeWidgetFragment.editDv.dataverseTheme.logoThumbnail}" columns="2" styleClass="noBorders">
<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"
<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"
listener="#{themeWidgetFragment.handleImageThumbnailFileUpload}" label="#{bundle['dataverse.theme.logo.image.uploadImgFile']}" invalidSizeMessage="#{bundle['file.edit.error.file_exceeds_limit']}"/>
</p:panelGrid>
</div>
Expand Down Expand Up @@ -221,11 +221,11 @@
</p>
<p:commandButton update=":themeWidgetsForm:themeWidgetsTabView" value="#{bundle.remove}" action="#{themeWidgetFragment.removeLogoFooter()}"/>
</p:column>
<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"
<p:fileUpload invalidFileMessage="#{bundle['dataverse.theme.logo.image.invalidMsg']}" id="changelogoFooter" allowTypes="/(\.|\/)(jpg|jpeg|png)$/" update=":themeWidgetsForm:themeWidgetsTabView" dragDropSupport="true" auto="true" multiple="false"
listener="#{themeWidgetFragment.handleImageFooterFileUpload}" label="#{bundle['dataverse.theme.logo.image.upload']}"/>
</p:panelGrid>
<p:panelGrid rendered="#{empty themeWidgetFragment.editDv.dataverseTheme.logoFooter}" columns="2" styleClass="noBorders">
<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"
<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"
listener="#{themeWidgetFragment.handleImageFooterFileUpload}" label="#{bundle['dataverse.theme.logo.image.uploadImgFile']}" invalidSizeMessage="#{bundle['file.edit.error.file_exceeds_limit']}"/>
</p:panelGrid>
<ui:fragment rendered="#{not empty themeWidgetFragment.editDv.dataverseTheme.logoFooter}">
Expand Down