package fr.titionfire.ffsaf.utils; import fr.titionfire.ffsaf.data.model.CompetitionGuestModel; import fr.titionfire.ffsaf.data.model.MembreModel; import fr.titionfire.ffsaf.domain.service.VirusScannerService; import fr.titionfire.ffsaf.rest.exception.DBadRequestException; import fr.titionfire.ffsaf.rest.exception.DInternalError; import fr.titionfire.ffsaf.rest.exception.DetailException; import io.quarkiverse.antivirus.runtime.AntivirusScanResult; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.unchecked.Unchecked; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jodd.net.MimeTypes; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.multipart.FileUpload; import java.io.*; import java.net.URISyntaxException; import java.net.URLConnection; import java.nio.file.Files; import java.util.Calendar; import java.util.Date; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; public class Utils { private static final Logger LOGGER = Logger.getLogger(Utils.class); public static String HTML_HEADER = """ %s
ffsaf

Fédération France Soft Armored Fighting

Bonjour,

"""; public static String HTML_FOOTER = """

Cordialement,
L’équipe de la FFSAF

"""; public static int getSaison() { return getSaison(new Date()); } public static int getSaison(Date date) { Calendar calendar = Calendar.getInstance(); calendar.setTime(date); if (calendar.get(Calendar.MONTH) >= Calendar.SEPTEMBER) { return calendar.get(Calendar.YEAR); } else { return calendar.get(Calendar.YEAR) - 1; } } public static Calendar toCalendar(Date date) { Calendar cal = Calendar.getInstance(); cal.setTime(date); return cal; } public static Categorie getCategoryFormBirthDate(Date birth_date, Date currentDate) { int currentSaison = getSaison(currentDate); int birthYear = toCalendar(birth_date).get(Calendar.YEAR); int diff = currentSaison - birthYear; if (diff < 6) { return Categorie.SUPER_MINI; } else if (diff < 8) { return Categorie.MINI_POUSSIN; } else if (diff < 10) { return Categorie.POUSSIN; } else if (diff < 12) { return Categorie.BENJAMIN; } else if (diff < 14) { return Categorie.MINIME; } else if (diff < 16) { return Categorie.CADET; } else if (diff < 18) { return Categorie.JUNIOR; } else if (diff < 25) { return Categorie.SENIOR1; } else if (diff < 35) { return Categorie.SENIOR2; } else if (diff < 45) { return Categorie.VETERAN1; } else { return Categorie.VETERAN2; } } public static Uni moveMedia(long idSrc, long idDest, String media, String dirSrc, String dirDst) { return Uni.createFrom().nullItem().map(__ -> { File dirFile = new File(media, dirSrc); if (!dirFile.exists()) return "Not found"; File dirDestFile = new File(media, dirDst); if (!dirDestFile.exists()) if (!dirDestFile.mkdirs()) return "Fail to create directory " + dirDestFile; FilenameFilter filter = (directory, filename) -> filename.startsWith(idSrc + "."); File[] files = dirFile.listFiles(filter); if (files == null || files.length == 0) return "Not found"; FilenameFilter filter2 = (directory, filename) -> filename.startsWith(idDest + "."); File[] files2 = dirDestFile.listFiles(filter2); if (files2 != null) { for (File file : files2) { //noinspection ResultOfMethodCallIgnored file.delete(); } } for (File file : files) { //noinspection ResultOfMethodCallIgnored file.renameTo(new File(dirDestFile, file.getName().replaceFirst(String.valueOf(idSrc), String.valueOf(idDest)))); } return "Ok"; }); } public static Uni uploadFile(VirusScannerService ss, FileUpload file, long id, String media, String dir) { if (file == null || file.size() == 0) return Uni.createFrom().item("Ok"); LOGGER.infof("Received file upload request for: %s (size: %d bytes)", file.fileName(), file.size()); return Uni.createFrom().item(() -> { try { // Read the entire file into memory for virus scanning // ByteArrayInputStream fully supports mark/reset operations required by ClamAV // This ensures we can scan the file content before any filesystem storage InputStream fileStream = java.nio.file.Files.newInputStream(file.uploadedFile()); byte[] fileBytes = fileStream.readAllBytes(); fileStream.close(); // Close the file stream immediately return new ByteArrayInputStream(fileBytes); } catch (IOException e) { throw new RuntimeException("Failed to read uploaded file: " + e.getMessage(), e); } }).runSubscriptionOn(io.smallrye.mutiny.infrastructure.Infrastructure.getDefaultWorkerPool()).onItem() .transformToUni(inputStream -> { // Perform virus scanning reactively using the input stream return ss.scanFileReactive(file.fileName(), inputStream).onItem().invoke(() -> { try { inputStream.close(); } catch (IOException e) { LOGGER.warn("Warning: Failed to close input stream: " + e.getMessage()); } }); }) .onItem().transformToUni(scanResults -> Uni.createFrom().item(Unchecked.supplier(() -> { // Check if any scanner found a threat for (AntivirusScanResult result : scanResults) { if (result.getStatus() != Response.Status.OK.getStatusCode()) { LOGGER.warnf("THREAT DETECTED in %s: %s", file.fileName(), result.getMessage()); throw new DetailException(Response.Status.fromStatusCode(result.getStatus()), "THREAT_DETECTED on File " + file.fileName() + " is infected: " + result.getMessage()); } } // File is clean - now we can safely process it LOGGER.infof("File is clean: %s (size=%d) (contentType=%s)", file.fileName(), file.size(), file.contentType()); String[] detectedExtensions = MimeTypes.findExtensionsByMimeTypes(file.contentType(), false); if (detectedExtensions.length == 0) throw new DBadRequestException( "Fail to detect file extension for MIME type " + file.contentType()); File dirFile = new File(media, dir); if (!dirFile.exists()) if (!dirFile.mkdirs()) throw new DInternalError("Fail to create directory " + dir); FilenameFilter filter = (directory, filename) -> filename.startsWith(id + "."); File[] files = dirFile.listFiles(filter); if (files != null) { for (File f : files) { //noinspection ResultOfMethodCallIgnored f.delete(); } } File f = file.filePath().toFile(); //noinspection ResultOfMethodCallIgnored f.renameTo(new File(dirFile, id + "." + detectedExtensions[0])); return "ok"; }))); } public static Uni getMediaFile(long id, String media, String dirname, Uni uniBase) throws URISyntaxException { return getMediaFile(id, media, dirname, null, uniBase, true); } public static Uni getMediaFileNoDefault(long id, String media, String dirname, Uni uniBase) throws URISyntaxException { return getMediaFile(id, media, dirname, null, uniBase, false); } public static Uni getMediaFile(long id, String media, String dirname, String out_filename, Uni uniBase, boolean default_) throws URISyntaxException { Future> future = CompletableFuture.supplyAsync(() -> { FilenameFilter filter = (directory, filename) -> filename.startsWith(id + "."); File[] files = new File(media, dirname).listFiles(filter); if (files != null && files.length > 0) { File file = files[0]; try { byte[] data = Files.readAllBytes(file.toPath()); return new Pair<>(file, data); } catch (IOException ignored) { } } return null; }); Future future2 = CompletableFuture.supplyAsync(() -> { try (InputStream st = Utils.class.getClassLoader().getResourceAsStream("asset/blank-profile-picture.png")) { if (st == null) return null; return st.readAllBytes(); } catch (IOException ignored) { } return null; }); return uniBase.chain(__ -> Uni.createFrom().future(future) .chain(filePair -> { if (filePair == null) { if (default_) { return Uni.createFrom().future(future2).map(data -> { if (data == null) return Response.noContent().build(); String mimeType = "image/apng"; Response.ResponseBuilder resp = Response.ok(data); resp.type(MediaType.APPLICATION_OCTET_STREAM); resp.header(HttpHeaders.CONTENT_LENGTH, data.length); resp.header(HttpHeaders.CONTENT_TYPE, mimeType); resp.header(HttpHeaders.CONTENT_DISPOSITION, "inline; " + ((out_filename == null) ? "" : "filename=\"" + out_filename + "\"")); return resp.build(); }); } else { Response.ResponseBuilder resp = Response.status(404); resp.header(HttpHeaders.CACHE_CONTROL, "max-age=600"); return Uni.createFrom().item(resp.build()); } } else { return Uni.createFrom().item(() -> { String mimeType = URLConnection.guessContentTypeFromName(filePair.getKey().getName()); String filename = out_filename; if (filename != null && (filename.length() < 5 || filename.lastIndexOf( '.') <= filename.length() - 6)) { filename += filePair.getKey().getName() .substring(filePair.getKey().getName().lastIndexOf('.')); } Response.ResponseBuilder resp = Response.ok(filePair.getValue()); resp.type(MediaType.APPLICATION_OCTET_STREAM); resp.header(HttpHeaders.CONTENT_LENGTH, filePair.getValue().length); resp.header(HttpHeaders.CONTENT_TYPE, mimeType); resp.header(HttpHeaders.CONTENT_DISPOSITION, "inline; " + ((filename == null) ? "" : "filename=\"" + filename + "\"")); return resp.build(); }); } })); } public static Uni deleteMedia(long id, String media, String dir) { return Uni.createFrom().nullItem().map(__ -> { File dirFile = new File(media, dir); if (!dirFile.exists()) return "OK"; FilenameFilter filter = (directory, filename) -> filename.startsWith(id + "."); File[] files = dirFile.listFiles(filter); if (files != null) { for (File file : files) { //noinspection ResultOfMethodCallIgnored file.delete(); } } return "Ok"; }); } public static int getDaysBeforeCompetition(Date date) { Calendar calendar = Calendar.getInstance(); calendar.setTime(date); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); Calendar now = Calendar.getInstance(); now.set(Calendar.HOUR_OF_DAY, 0); now.set(Calendar.MINUTE, 0); now.set(Calendar.SECOND, 0); return (int) ((calendar.getTimeInMillis() - now.getTimeInMillis()) / (1000 * 60 * 60 * 24)); } public static String formatPrenom(String input) { if (input == null || input.isEmpty()) { return input; } StringBuilder result = new StringBuilder(); String[] mots = input.split(" "); for (String mot : mots) { if (!mot.isEmpty()) { String[] parties = mot.split("[-']"); StringBuilder motFormate = new StringBuilder(); for (int i = 0; i < parties.length; i++) { if (!parties[i].isEmpty()) { String premiereLettre = parties[i].substring(0, 1).toUpperCase(); String reste = parties[i].substring(1).toLowerCase(); motFormate.append(premiereLettre).append(reste); } if (i < parties.length - 1) { motFormate.append(mot.charAt(mot.indexOf(parties[i]) + parties[i].length())); } } result.append(motFormate).append(" "); } } return result.toString().trim(); } public static String getFullName(Object... models) { for (Object model : models) { if (model == null) continue; if (model instanceof MembreModel membreModel) return membreModel.getFname() + " " + membreModel.getLname(); if (model instanceof CompetitionGuestModel guestModel) return guestModel.getFname() + " " + guestModel.getLname(); } return ""; } }