diff --git a/src/main/java/fil/sr2/flopbox/FTPResource.java b/src/main/java/fil/sr2/flopbox/FTPResource.java index 97578c7b38807efe2955eabfa269076a4b3941d5..6d5989a3a22338428220a14b3eb6e6a4ce0aa7c9 100644 --- a/src/main/java/fil/sr2/flopbox/FTPResource.java +++ b/src/main/java/fil/sr2/flopbox/FTPResource.java @@ -3,10 +3,14 @@ package fil.sr2.flopbox; import jakarta.ws.rs.*; import jakarta.ws.rs.core.*; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; @@ -15,6 +19,7 @@ import org.apache.commons.net.ftp.FTPFile; @Path("/ftps") public class FTPResource { + // ======== FTP Servers ======== // GET /ftps - liste des serveurs FTP enregistrés @GET @Produces(MediaType.APPLICATION_JSON) @@ -24,18 +29,48 @@ public class FTPResource { return Response.ok(servers).build(); } - // returns the content of the directory as an array it is a directory OR the - // content of the file + // POST /ftps - enregistre un nouveau serveur FTP + @POST + @Consumes(MediaType.APPLICATION_JSON) + public Response addFTPServer(FTPServerConfig config, @Context UriInfo uriInfo) { + System.out.println("addFTPServer()"); + boolean created = FTPServerRepository.getInstance().addServer(config); + if (!created) { + return Response.status(Response.Status.CONFLICT).entity("Alias déjà utilisé").build(); + } + UriBuilder builder = uriInfo.getAbsolutePathBuilder(); + builder.path(config.getAlias()); + return Response.created(builder.build()).build(); + } + + @DELETE + @Path("/{alias}") + public Response removeFTPServer(@PathParam("alias") String alias) { + System.out.println("removeFTPServer()"); + boolean removed = FTPServerRepository.getInstance().removeServer(alias); + return removed ? Response.noContent().build() : Response.status(Response.Status.NOT_FOUND).build(); + } + + @PUT + @Path("/{alias}") + @Consumes(MediaType.APPLICATION_JSON) + public Response updateFTPServer(@PathParam("alias") String alias, FTPServerConfig newConfig) { + System.out.println("updateFTPServer()"); + boolean updated = FTPServerRepository.getInstance().updateServer(alias, newConfig); + return updated ? Response.ok(newConfig).build() : Response.status(Response.Status.NOT_FOUND).build(); + } + // ============================= + + // ========= Resources ========= @GET @Path("/{alias}/{path: .+}") - @Produces(MediaType.APPLICATION_JSON) + @Produces({ MediaType.APPLICATION_OCTET_STREAM, "application/zip" }) public Response getFTPResource( @PathParam("alias") String alias, @PathParam("path") String path, @HeaderParam("X-FTP-User") String user, @HeaderParam("X-FTP-Pass") String pass) { - System.out.println("GetFTPResource()"); FTPServerConfig config = FTPServerRepository.getInstance().getServer(alias); if (config == null) { return Response.status(Response.Status.NOT_FOUND) @@ -50,42 +85,161 @@ public class FTPResource { } ftpClient.enterLocalPassiveMode(); ftpClient.setFileType(FTP.BINARY_FILE_TYPE); - // Normaliser le chemin en retirant le slash final + + // Normaliser le chemin if (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } - // Vérifier si le chemin est un répertoire + + // Vérifier si c'est un dossier + boolean isDirectory = ftpClient.changeWorkingDirectory(path); + if (isDirectory) { + String currentDirectory = ftpClient.printWorkingDirectory(); + System.out.println("Répertoire courant : " + currentDirectory); + + // Créer un ZIP du dossier + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ZipOutputStream zos = new ZipOutputStream(baos); + addDirectoryToZip(ftpClient, currentDirectory, "", zos); + zos.close(); + return Response.ok(baos.toByteArray()) + .type("application/zip") + .header("Content-Disposition", "attachment; filename=\"" + getFileName(path) + ".zip\"") + .build(); + } else { + // Télécharger le fichier + InputStream is = ftpClient.retrieveFileStream(path); + if (is != null) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + is.close(); + ftpClient.completePendingCommand(); + return Response.ok(outputStream.toByteArray()) + .type(determineContentType(path)) + .header("Content-Disposition", "attachment; filename=\"" + getFileName(path) + "\"") + .build(); + } else { + return Response.status(Response.Status.NOT_FOUND) + .entity("Ressource non trouvée").build(); + } + } + } catch (IOException e) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Erreur FTP: " + e.getMessage()).build(); + } finally { + try { + if (ftpClient.isConnected()) { + ftpClient.logout(); + ftpClient.disconnect(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private String determineContentType(String path) { + if (path.endsWith(".txt")) + return MediaType.TEXT_PLAIN; + if (path.endsWith(".html")) + return MediaType.TEXT_HTML; + if (path.endsWith(".json")) + return MediaType.APPLICATION_JSON; + return MediaType.APPLICATION_OCTET_STREAM; + } + + private void addDirectoryToZip(FTPClient ftpClient, String remotePath, String basePath, ZipOutputStream zos) + throws IOException { + FTPFile[] files = ftpClient.listFiles(remotePath); + if (files == null || files.length == 0) { + System.out.println("Aucun fichier trouvé dans le dossier : " + remotePath); + return; + } + + for (FTPFile file : files) { + String filePath = remotePath.isEmpty() ? file.getName() : remotePath + "/" + file.getName(); + String zipEntryPath = basePath.isEmpty() ? file.getName() : basePath + "/" + file.getName(); + + if (file.isDirectory()) { + // Ajouter une entrée de dossier au ZIP + zos.putNextEntry(new ZipEntry(zipEntryPath + "/")); + zos.closeEntry(); + // Explorer le sous-dossier récursivement + addDirectoryToZip(ftpClient, filePath, zipEntryPath, zos); + } else { + // Ajouter un fichier au ZIP + ZipEntry entry = new ZipEntry(zipEntryPath); + zos.putNextEntry(entry); + InputStream is = ftpClient.retrieveFileStream(filePath); + if (is == null) { + System.out.println("Impossible de lire le fichier : " + filePath); + continue; + } + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + zos.write(buffer, 0, bytesRead); + } + is.close(); + ftpClient.completePendingCommand(); // Important pour finaliser la lecture + zos.closeEntry(); + } + } + } + + @GET + @Path("/list/{alias}/{path: .+}") + @Produces(MediaType.APPLICATION_JSON) + public Response listDirectory( + @PathParam("alias") String alias, + @PathParam("path") String path, + @HeaderParam("X-FTP-User") String user, + @HeaderParam("X-FTP-Pass") String pass) { + + FTPServerConfig config = FTPServerRepository.getInstance().getServer(alias); + if (config == null) { + return Response.status(Response.Status.NOT_FOUND) + .entity("Serveur FTP non trouvé").build(); + } + FTPClient ftpClient = new FTPClient(); + try { + ftpClient.connect(config.getHost(), config.getPort()); + if (!ftpClient.login(user, pass)) { + return Response.status(Response.Status.UNAUTHORIZED) + .entity("Authentification FTP échouée").build(); + } + ftpClient.enterLocalPassiveMode(); + ftpClient.setFileType(FTP.BINARY_FILE_TYPE); + + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + FTPFile[] files = ftpClient.listFiles(path); if (files == null || files.length == 0) { return Response.status(Response.Status.NOT_FOUND) - .entity("Ressource non trouvée : " + path).build(); + .entity("Ressource non trouvée: " + path).build(); } - // Construire la structure JSON + FtpNode root = buildFtpTree(ftpClient, path); - ftpClient.logout(); - ftpClient.disconnect(); return Response.ok(root).build(); } catch (IOException e) { return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity("Erreur FTP : " + e.getMessage()).build(); - } - } - - // Fonction récursive pour construire l'arborescence FTP - private FtpNode buildFtpTree(FTPClient ftpClient, String path) throws IOException { - FTPFile[] files = ftpClient.listFiles(path); - FtpNode node = new FtpNode(getFileName(path), true); // Root est un dossier - if (files != null) { - for (FTPFile file : files) { - String fullPath = path + "/" + file.getName(); - if (file.isDirectory()) { - node.children.add(buildFtpTree(ftpClient, fullPath)); // Récursif pour les dossiers - } else { - node.children.add(new FtpNode(file.getName(), false)); // Ajouter fichier + .entity("Erreur FTP: " + e.getMessage()).build(); + } finally { + try { + if (ftpClient.isConnected()) { + ftpClient.logout(); + ftpClient.disconnect(); } + } catch (IOException e) { + e.printStackTrace(); } } - return node; } // Classe représentant un nœud JSON (fichier ou dossier) @@ -103,35 +257,21 @@ public class FTPResource { } } - // POST /ftps - enregistre un nouveau serveur FTP - @POST - @Consumes(MediaType.APPLICATION_JSON) - public Response addFTPServer(FTPServerConfig config, @Context UriInfo uriInfo) { - System.out.println("addFTPServer()"); - boolean created = FTPServerRepository.getInstance().addServer(config); - if (!created) { - return Response.status(Response.Status.CONFLICT).entity("Alias déjà utilisé").build(); + // Fonction récursive pour construire l'arborescence FTP + private FtpNode buildFtpTree(FTPClient ftpClient, String path) throws IOException { + FTPFile[] files = ftpClient.listFiles(path); + FtpNode node = new FtpNode(getFileName(path), true); // Root est un dossier + if (files != null) { + for (FTPFile file : files) { + String fullPath = path + "/" + file.getName(); + if (file.isDirectory()) { + node.children.add(buildFtpTree(ftpClient, fullPath)); // Récursif pour les dossiers + } else { + node.children.add(new FtpNode(file.getName(), false)); // Ajouter fichier + } + } } - UriBuilder builder = uriInfo.getAbsolutePathBuilder(); - builder.path(config.getAlias()); - return Response.created(builder.build()).build(); - } - - @DELETE - @Path("/{alias}") - public Response removeFTPServer(@PathParam("alias") String alias) { - System.out.println("removeFTPServer()"); - boolean removed = FTPServerRepository.getInstance().removeServer(alias); - return removed ? Response.noContent().build() : Response.status(Response.Status.NOT_FOUND).build(); - } - - @PUT - @Path("/{alias}") - @Consumes(MediaType.APPLICATION_JSON) - public Response updateFTPServer(@PathParam("alias") String alias, FTPServerConfig newConfig) { - System.out.println("updateFTPServer()"); - boolean updated = FTPServerRepository.getInstance().updateServer(alias, newConfig); - return updated ? Response.ok(newConfig).build() : Response.status(Response.Status.NOT_FOUND).build(); + return node; } @PUT @@ -142,18 +282,45 @@ public class FTPResource { @PathParam("path") String path, @HeaderParam("X-FTP-User") String user, @HeaderParam("X-FTP-Pass") String pass, + @HeaderParam("X-Zip-Extract") Boolean extractZip, InputStream fileStream) { - System.out.println("uploadFile()"); FTPServerConfig config = FTPServerRepository.getInstance().getServer(alias); + if (config == null) { + return Response.status(Response.Status.NOT_FOUND).entity("Serveur non trouvé").build(); + } FTPClient ftp = new FTPClient(); try { ftp.connect(config.getHost(), config.getPort()); ftp.login(user, pass); - boolean success = ftp.storeFile(path, fileStream); - return success ? Response.ok().build() : Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + ftp.enterLocalPassiveMode(); + ftp.setFileType(FTP.BINARY_FILE_TYPE); + + if (extractZip != null && extractZip) { + if (!ftp.changeWorkingDirectory(path)) { + return Response.status(400).entity("Le chemin n'est pas un dossier").build(); + } + ZipInputStream zis = new ZipInputStream(fileStream); + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + String fullPath = path + "/" + entry.getName(); + if (entry.isDirectory()) { + createDirectories(ftp, fullPath); + } else { + String parentDir = fullPath.substring(0, fullPath.lastIndexOf('/')); + createDirectories(ftp, parentDir); + ftp.storeFile(fullPath, zis); + } + zis.closeEntry(); + } + zis.close(); + return Response.ok().build(); + } else { + boolean success = ftp.storeFile(path, fileStream); + return success ? Response.ok().build() : Response.status(500).entity("Erreur d'upload").build(); + } } catch (IOException e) { - return Response.serverError().entity(e.getMessage()).build(); + return Response.serverError().entity("Erreur FTP: " + e.getMessage()).build(); } finally { try { if (ftp.isConnected()) { @@ -166,6 +333,21 @@ public class FTPResource { } } + private void createDirectories(FTPClient ftp, String path) throws IOException { + String[] dirs = path.split("/"); + StringBuilder sb = new StringBuilder(); + for (String dir : dirs) { + if (dir.isEmpty()) + continue; + sb.append("/").append(dir); + String currentDir = sb.toString(); + if (!ftp.changeWorkingDirectory(currentDir)) { + ftp.makeDirectory(currentDir); + ftp.changeWorkingDirectory(currentDir); + } + } + } + @POST @Path("/{alias}/{path: .+}") @Consumes({ MediaType.APPLICATION_OCTET_STREAM, MediaType.TEXT_PLAIN }) @@ -310,4 +492,5 @@ public class FTPResource { private String getFileName(String path) { return path.substring(path.lastIndexOf('/') + 1); } + // ============================= }