diff --git a/README.md b/README.md index 3702df337858c789309bdc6ac6ac0ae14879f4b8..ef4fe88022c8c76e6c8a5184b09f0b2d29b4ef31 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,9 @@ Puis ouvrir le fichier `target/site/apidocs/index.html` ## Fonctionnalités -`python3 serveur_ftp.py` anonymous/anonymous ou user/password +En utilisant la commande suivante `python3 serveur_ftp.py`, vous pouvez donner les identifiants FTP anonymous/anonymous ou user/password + +Il faut aussi penser à ajouter un serveur FTP avec la commande suivant : `curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer valid-token-1" -d '{"alias":"mon-ftp","host":"localhost","port":2121}' http://localhost:8080/ftps` **Note < 10 si le code fourni ne compile pas et ne peut pas être exécuté en suivant les instructions:** @@ -90,14 +92,14 @@ curl -X GET -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: anonymous" - download fichier : ```shell -curl -X GET -H "Authorization: Bearer valid-token-1" http://localhost:8080/ftps/mon-ftp/test +curl -X GET -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: anonymous" -H "X-FTP-Pass: anonymous" http://localhost:8080/ftps/mon-ftp/fichier1 -o fichier1 ``` -download dossier : ```shell -curl -X GET -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: user" -H "X-FTP-Pass: password" http://localhost:8080/ftps/mon-ftp/dir4 -o test.zip +curl -X GET -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: anonymous" -H "X-FTP-Pass: anonymous" http://localhost:8080/ftps/mon-ftp/dossier1 -o dossier1.zip -unzip test.zip +unzip dossier1.zip ``` - upload fichier : diff --git a/src/main/java/fil/sr2/flopbox/FTPResource.java b/src/main/java/fil/sr2/flopbox/FTPResource.java index 87d2fddd0759d8d280e627b635c79283eda67603..9281a6aa253e847f146427ce10dd92bc5ce0d6a3 100644 --- a/src/main/java/fil/sr2/flopbox/FTPResource.java +++ b/src/main/java/fil/sr2/flopbox/FTPResource.java @@ -11,7 +11,6 @@ import java.util.List; public class FTPResource { private FTPService ftpService; - // Endpoints pour gérer la configuration des serveurs FTP public FTPResource(FTPService ftpService) { this.ftpService = ftpService; @@ -29,6 +28,8 @@ public class FTPResource { return Response.ok(servers).build(); } + // --- Endpoints pour gérer la configuration des serveurs FTP --- + @POST @Consumes(MediaType.APPLICATION_JSON) public Response addFTPServer(FTPServerConfig config, @Context UriInfo uriInfo) { @@ -104,10 +105,10 @@ public class FTPResource { } } - // GET pour récupérer la structure d'un répertoire ou d'un fichier + // GET pour télécharger une ressource : fichier ou dossier compressé en ZIP + @GET @Path("/{alias}/{path: .+}") - @Produces(MediaType.APPLICATION_JSON) public Response getFTPResource( @PathParam("alias") String alias, @PathParam("path") String path, @@ -115,8 +116,23 @@ public class FTPResource { @HeaderParam("X-FTP-Pass") String pass) { System.out.println("getFTPResource()"); try { - FTPService.FtpNode root = ftpService.getResourceTree(alias, path, user, pass); - return Response.ok(root).build(); + FTPService.FtpNode node = ftpService.getResourceTree(alias, path, user, pass); + if (node.isDirectory) { + System.out.println("getFTPResource() > directory"); + byte[] zipData = ftpService.downloadDirectoryAsZip(alias, path, user, pass); + return Response.ok(zipData) + .header("Content-Type", "application/zip") + .header("Content-Disposition", "attachment; filename=\"" + getFileName(path) + ".zip\"") + .build(); + } else { + System.out.println("getFTPResource() > NOT directory"); + byte[] fileData = ftpService.downloadFile(alias, path, user, pass); + String contentType = determineContentType(path); + return Response.ok(fileData) + .header("Content-Type", contentType) + .header("Content-Disposition", "attachment; filename=\"" + getFileName(path) + "\"") + .build(); + } } catch (FTPException e) { return Response.status(e.getStatus()).entity(e.getMessage()).build(); } catch (IOException e) { @@ -207,4 +223,21 @@ public class FTPResource { return Response.serverError().entity("Erreur FTP : " + e.getMessage()).build(); } } + + // Méthode utilitaire pour extraire le nom de la ressource à partir du chemin + private String getFileName(String path) { + return path.substring(path.lastIndexOf('/') + 1); + } + + // Méthode pour déterminer le type de contenu en fonction de l'extension + private String determineContentType(String path) { + if (path.endsWith(".txt")) + return "text/plain"; + if (path.endsWith(".html")) + return "text/html"; + if (path.endsWith(".json")) + return "application/json"; + // Par défaut, renvoie binaire + return "application/octet-stream"; + } } diff --git a/src/main/java/fil/sr2/flopbox/FTPService.java b/src/main/java/fil/sr2/flopbox/FTPService.java index be23fa7ba47ac2011775d46a3a1022536685d92a..c380c8180c6c2cd16eab48af2c3b8e25a739c8bf 100644 --- a/src/main/java/fil/sr2/flopbox/FTPService.java +++ b/src/main/java/fil/sr2/flopbox/FTPService.java @@ -5,7 +5,6 @@ import org.apache.commons.net.ftp.FTPFile; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; @@ -22,21 +21,33 @@ public class FTPService { } ftpClient.enterLocalPassiveMode(); ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); - // Normaliser le chemin en retirant le slash final s'il y en a un + // Normaliser le chemin : retirer le slash final s'il y en a un if (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } - FTPFile[] files = ftpClient.listFiles(path); - if (files == null || files.length == 0) { - throw new FTPException("Ressource non trouvée : " + path, 404); + // Vérifier si c'est un dossier + boolean isDir = ftpClient.changeWorkingDirectory(path); + if (isDir) { + // Même si le dossier est vide, c'est valide + return buildFtpTree(ftpClient, path); + } else { + // Sinon, tenter de récupérer un flux de fichier + InputStream is = ftpClient.retrieveFileStream(path); + if (is != null) { + is.close(); + ftpClient.completePendingCommand(); + return new FtpNode(getFileName(path), false); + } else { + throw new FTPException("Ressource non trouvée : " + path, 404); + } } - return buildFtpTree(ftpClient, path); } finally { FTPClientFactory.disconnect(ftpClient); } } // Méthode récursive pour construire l'arborescence + private FtpNode buildFtpTree(FTPClient ftpClient, String path) throws IOException { FTPFile[] files = ftpClient.listFiles(path); FtpNode node = new FtpNode(getFileName(path), true); @@ -53,6 +64,82 @@ public class FTPService { return node; } + public byte[] downloadFile(String alias, String path, String user, String pass) + throws IOException, FTPException { + FTPClient ftp = FTPClientFactory.createClient(alias); + try { + if (!ftp.login(user, pass)) { + throw new FTPException("Authentification FTP échouée", 401); + } + ftp.enterLocalPassiveMode(); + ftp.setFileType(FTPClient.BINARY_FILE_TYPE); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + InputStream is = ftp.retrieveFileStream(path); + if (is == null) { + throw new FTPException("Ressource non trouvée : " + path, 404); + } + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + baos.write(buffer, 0, bytesRead); + } + is.close(); + ftp.completePendingCommand(); + return baos.toByteArray(); + } finally { + FTPClientFactory.disconnect(ftp); + } + } + + public byte[] downloadDirectoryAsZip(String alias, String path, String user, String pass) + throws IOException, FTPException { + FTPClient ftp = FTPClientFactory.createClient(alias); + try { + if (!ftp.login(user, pass)) { + throw new FTPException("Authentification FTP échouée", 401); + } + ftp.enterLocalPassiveMode(); + ftp.setFileType(FTPClient.BINARY_FILE_TYPE); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ZipOutputStream zos = new ZipOutputStream(baos); + addDirectoryToZip(ftp, path, "", zos); + zos.close(); + return baos.toByteArray(); + } finally { + FTPClientFactory.disconnect(ftp); + } + } + + private void addDirectoryToZip(FTPClient ftp, String remotePath, String basePath, ZipOutputStream zos) + throws IOException { + FTPFile[] files = ftp.listFiles(remotePath); + if (files == null || files.length == 0) { + return; + } + for (FTPFile file : files) { + String filePath = remotePath + "/" + file.getName(); + String zipEntryPath = basePath.isEmpty() ? file.getName() : basePath + "/" + file.getName(); + if (file.isDirectory()) { + zos.putNextEntry(new ZipEntry(zipEntryPath + "/")); + zos.closeEntry(); + addDirectoryToZip(ftp, filePath, zipEntryPath, zos); + } else { + zos.putNextEntry(new ZipEntry(zipEntryPath)); + InputStream is = ftp.retrieveFileStream(filePath); + if (is != null) { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + zos.write(buffer, 0, bytesRead); + } + is.close(); + ftp.completePendingCommand(); + } + zos.closeEntry(); + } + } + } + public void uploadFile(String alias, String path, String user, String pass, InputStream fileStream) throws IOException, FTPException { FTPClient ftp = FTPClientFactory.createClient(alias); @@ -95,7 +182,8 @@ public class FTPService { } } - public void deleteResource(String alias, String path, String user, String pass) throws IOException, FTPException { + public void deleteResource(String alias, String path, String user, String pass) + throws IOException, FTPException { FTPClient ftp = FTPClientFactory.createClient(alias); try { if (!ftp.login(user, pass)) { @@ -111,7 +199,6 @@ public class FTPService { } } - // Méthode récursive de suppression private boolean deleteRecursive(FTPClient ftp, String path) throws IOException { if (ftp.deleteFile(path)) { return true; @@ -152,7 +239,6 @@ public class FTPService { } } - // Méthode utilitaire pour extraire le nom du fichier/dossier à partir du chemin private String getFileName(String path) { return path.substring(path.lastIndexOf('/') + 1); }