Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* The MIT License
*
* Copyright (c) 2009-2023 PrimeTek Informatics
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.primefaces.util;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.validator.ValidatorException;
import javax.servlet.http.HttpServletRequest;
import org.primefaces.shaded.commons.io.FilenameUtils;
import org.primefaces.component.fileupload.FileUpload;
import org.primefaces.component.fileupload.FileUploadChunkDecoder;
import org.primefaces.component.fileupload.FileUploadDecoder;
import org.primefaces.context.PrimeApplicationContext;
import org.primefaces.model.file.UploadedFile;
import org.primefaces.shaded.owasp.SafeFile;
import org.primefaces.shaded.owasp.ValidationException;
import org.primefaces.virusscan.VirusException;
/**
* Utilities for FileUpload components.
*/
public class FileUploadUtils {
private static final Logger LOGGER = Logger.getLogger(FileUploadUtils.class.getName());
private static final Pattern INVALID_FILENAME_PATTERN = Pattern.compile("([\\/:*?\"<>|])");
private FileUploadUtils() {
}
public static String getValidFilename(String filename) {
if (LangUtils.isBlank(filename)) {
return null;
}
if (isSystemWindows()) {
if (!filename.contains("\\\\")) {
int length = FilenameUtils.getPrefixLength(filename);
String prefix = LangUtils.substring(filename, 0, length);
String[] parts = prefix.split(Pattern.quote(File.separator));
for (String part : parts) {
if (INVALID_FILENAME_PATTERN.matcher(part).find()) {
throw new FacesException("Invalid filename: " + filename);
}
}
}
else {
throw new FacesException("Invalid filename: " + filename);
}
}
String name = FilenameUtils.getName(filename);
String extension = FilenameUtils.getExtension(filename);
if (name.isEmpty() && extension.isEmpty()) {
throw new FacesException("Filename can not be the empty string");
}
return name;
}
public static String getValidFilePath(String filePath) throws ValidationException {
if (LangUtils.isBlank(filePath)) {
throw new FacesException("Path can not be the empty string or null");
}
try {
SafeFile file = new SafeFile(filePath);
File parentFile = file.getParentFile();
if (!file.exists()) {
throw new ValidationException("Invalid directory", "Invalid directory, \"" + file + "\" does not exist.");
}
if (!parentFile.exists()) {
throw new ValidationException("Invalid directory", "Invalid directory, specified parent does not exist.");
}
if (!parentFile.isDirectory()) {
throw new ValidationException("Invalid directory", "Invalid directory, specified parent is not a directory.");
}
if (!file.getCanonicalFile().toPath().startsWith(parentFile.getCanonicalFile().toPath())) {
throw new ValidationException("Invalid directory", "Invalid directory, \"" + file + "\" does not reside inside specified parent.");
}
if (!file.getCanonicalPath().equals(filePath)) {
throw new ValidationException("Invalid directory", "Invalid directory name does not match the canonical path");
}
}
catch (IOException ex) {
throw new ValidationException("Invalid directory", "Failure to validate directory path");
}
return filePath;
}
public static boolean isSystemWindows() {
return File.separatorChar == '\\';
}
/**
* Check if an uploaded file meets all specifications regarding its filename and content type. It evaluates {@link FileUpload#getAllowTypes}
* as well as {@link FileUpload#getAccept} and uses the installed {@link java.nio.file.spi.FileTypeDetector} implementation.
* For most reliable content type checking it's recommended to plug in Apache Tika as an implementation.
*
* @param context the {@link PrimeApplicationContext}
* @param fileUpload the {@link FileUpload} component
* @param uploadedFile the details of the uploaded file
* @return true, if all validations regarding filename and content type passed, false else
*/
public static boolean isValidType(PrimeApplicationContext context, FileUpload fileUpload, UploadedFile uploadedFile) {
String fileName = uploadedFile.getFileName();
try (InputStream input = uploadedFile.getInputStream()) {
boolean validType = isValidFileName(fileUpload, uploadedFile)
&& isValidFileContent(context, fileUpload, fileName, input);
if (validType) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(String.format("The uploaded file %s meets the filename and content type specifications", fileName));
}
}
return validType;
}
catch (IOException ex) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, String.format("The type of the uploaded file %s could not be validated", fileName), ex);
}
return false;
}
}
private static boolean isValidFileName(FileUpload fileUpload, UploadedFile uploadedFile) {
String javascriptRegex = fileUpload.getAllowTypes();
if (LangUtils.isBlank(javascriptRegex)) {
return true;
}
String javaRegex = convertJavaScriptRegex(javascriptRegex);
if (LangUtils.isBlank(javaRegex)) {
return true;
}
String fileName = EscapeUtils.forJavaScriptAttribute(uploadedFile.getFileName());
String contentType = EscapeUtils.forJavaScriptAttribute(uploadedFile.getContentType());
final Pattern allowTypesPattern = Pattern.compile(javaRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
final Matcher fileNameMatcher = allowTypesPattern.matcher(fileName);
final Matcher contentTypeMatcher = allowTypesPattern.matcher(contentType);
boolean isValid = fileNameMatcher.find() || contentTypeMatcher.find();
if (!isValid) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.warning(String.format("The uploaded filename %s does not match the specified regex %s", fileName, javaRegex));
}
return false;
}
return true;
}
/**
* Converts a JavaScript regular expression like '/(\.|\/)(gif|jpe?g|png)$/i'
* to the Java usable format '(\\.|\\/)(gif|jpe?g|png)$'
* @param jsRegex the client side JavaScript regex
* @return the Java converted version of the regex
*/
protected static String convertJavaScriptRegex(String jsRegex) {
int start = 0;
int end = jsRegex.length() - 1;
if (jsRegex.charAt(0) == '/') {
start = 1;
}
char endChar = jsRegex.charAt(end);
if (endChar != '/' && endChar == 'i' || endChar == 'g') {
end = end - 1;
}
return LangUtils.substring(jsRegex, start, end);
}
private static boolean isValidFileContent(PrimeApplicationContext context, FileUpload fileUpload, String fileName, InputStream stream) throws IOException {
if (!fileUpload.isValidateContentType()) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Content type checking is disabled");
}
return true;
}
if (LangUtils.isBlank(fileUpload.getAccept())) {
//Short circuit
return true;
}
String tempFilePrefix = UUID.randomUUID().toString();
Path tempFile = Files.createTempFile(tempFilePrefix, null);
try {
try (InputStream in = new PushbackInputStream(new BufferedInputStream(stream))) {
try (OutputStream out = new FileOutputStream(tempFile.toFile())) {
IOUtils.copyLarge(in, out);
}
}
String contentType = context.getFileTypeDetector().probeContentType(tempFile);
if (contentType == null) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.warning(String.format("Could not determine content type of uploaded file %s, consider plugging in an adequate " +
"FileTypeDetector implementation", fileName));
}
return false;
}
//Comma-separated values: file_extension|audio/*|video/*|image/*|media_type (see https://www.w3schools.com/tags/att_input_accept.asp)
String[] accepts = fileUpload.getAccept().split(",");
boolean accepted = false;
for (String accept : accepts) {
accept = accept.trim().toLowerCase();
if (accept.startsWith(".") && fileName.toLowerCase().endsWith(accept)) {
accepted = true;
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(String.format("The file extension %s of the uploaded file %s is accepted", accept, fileName));
}
break;
}
//Now we have a media type that may contain wildcards
if (FilenameUtils.wildcardMatch(contentType.toLowerCase(), accept)) {
accepted = true;
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(String.format("The content type %s of the uploaded file %s is accepted by %s", contentType, fileName, accept));
}
break;
}
}
if (!accepted) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(String.format("The uploaded file %s with content type %s does not match the accept specification %s", fileName, contentType,
fileUpload.getAccept()));
}
return false;
}
}
finally {
try {
Files.delete(tempFile);
}
catch (Exception ex) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, String.format("Could not delete temporary file %s, will try to delete on JVM exit",
tempFile.toAbsolutePath()), ex);
try {
tempFile.toFile().deleteOnExit();
}
catch (Exception ex1) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, String.format("Could not register temporary file %s for deletion on JVM exit",
tempFile.toAbsolutePath()), ex1);
}
}
}
}
}
return true;
}
public static void performVirusScan(FacesContext facesContext, UploadedFile file) throws VirusException {
PrimeApplicationContext.getCurrentInstance(facesContext).getVirusScannerService().performVirusScan(file);
}
public static void tryValidateFile(FacesContext context, FileUpload fileUpload, UploadedFile uploadedFile) throws ValidatorException {
Long sizeLimit = fileUpload.getSizeLimit();
PrimeApplicationContext appContext = PrimeApplicationContext.getCurrentInstance(context);
if (sizeLimit != null && uploadedFile.getSize() > sizeLimit) {
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, fileUpload.getInvalidSizeMessage(), ""));
}
if (!isValidType(appContext, fileUpload, uploadedFile)) {
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, fileUpload.getInvalidFileMessage(), ""));
}
if (fileUpload.isVirusScan()) {
performVirusScan(context, uploadedFile);
}
}
public static void tryValidateFiles(FacesContext context, FileUpload fileUpload, List files) {
long totalPartSize = 0;
Long sizeLimit = fileUpload.getSizeLimit();
for (UploadedFile file : files) {
totalPartSize += file.getSize();
tryValidateFile(context, fileUpload, file);
}
if (sizeLimit != null && totalPartSize > sizeLimit) {
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, fileUpload.getInvalidFileMessage(), ""));
}
}
/**
* OWASP prevent directory path traversal of "../../image.png".
*
* @see https://owasp.org/www-community/attacks/Path_Traversal
* @param relativePath the relative path to check for path traversal
* @return the relative path
* @throws FacesException if any error is detected
*/
public static String checkPathTraversal(String relativePath) {
// Unix systems can start with / but Windows cannot
String os = System.getProperty("os.name").toLowerCase();
if (!os.contains("win")) {
relativePath = relativePath.startsWith("/") ? relativePath.substring(1) : relativePath;
}
File file = new File(relativePath);
if (file.isAbsolute()) {
throw new FacesException("Path traversal attempt - absolute path not allowed.");
}
try {
String pathUsingCanonical = file.getCanonicalPath();
String pathUsingAbsolute = file.getAbsolutePath();
// Require the absolute path and canonicalized path match.
// This is done to avoid directory traversal
// attacks, e.g. "1/../2/"
if (!pathUsingCanonical.equals(pathUsingAbsolute)) {
throw new FacesException("Path traversal attempt for path " + relativePath);
}
}
catch (IOException ex) {
throw new FacesException("Path traversal - unexpected exception.", ex);
}
return relativePath;
}
public static List listChunks(Path path) {
try (Stream walk = Files.walk(path)) {
return walk
.filter(p -> p.toFile().isFile() && p.getFileName().toString().matches("\\d+"))
.sorted(Comparator.comparing(p -> Long.parseLong(p.getFileName().toString())))
.collect(Collectors.toList());
}
catch (IOException | SecurityException e) {
throw new FacesException(e);
}
}
public static List listChunks(T request) {
Path chunkDir = getChunkDir(request);
if (!chunkDir.toFile().exists()) {
return Collections.emptyList();
}
return listChunks(chunkDir);
}
public static FileUploadChunkDecoder getFileUploadChunkDecoder(T request) {
PrimeApplicationContext pfContext = PrimeApplicationContext.getCurrentInstance(request.getServletContext());
FileUploadDecoder decoder = pfContext.getFileUploadDecoder();
if (!(decoder instanceof FileUploadChunkDecoder)) {
throw new FacesException("Chunk decoder not supported");
}
return (FileUploadChunkDecoder) decoder;
}
public static Path getChunkDir(T request) {
FileUploadChunkDecoder chunkDecoder = getFileUploadChunkDecoder(request);
String fileKey = chunkDecoder.generateFileInfoKey(request);
return Paths.get(chunkDecoder.getUploadDirectory(request), fileKey);
}
}