diff --git a/src/main/java/run/halo/app/controller/admin/api/StaticStorageController.java b/src/main/java/run/halo/app/controller/admin/api/StaticStorageController.java index ce3d8fc2c..c6592ad5c 100644 --- a/src/main/java/run/halo/app/controller/admin/api/StaticStorageController.java +++ b/src/main/java/run/halo/app/controller/admin/api/StaticStorageController.java @@ -3,6 +3,7 @@ package run.halo.app.controller.admin.api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import run.halo.app.model.params.StaticContentParam; import run.halo.app.model.support.StaticFile; import run.halo.app.service.StaticStorageService; @@ -49,4 +50,17 @@ public class StaticStorageController { @RequestPart("file") MultipartFile file) { staticStorageService.upload(basePath, file); } + + @PostMapping("rename") + @ApiOperation("Renames static file") + public void rename(String basePath, + String newName) { + staticStorageService.rename(basePath, newName); + } + + @PutMapping("files") + @ApiOperation("Save static file") + public void save(@RequestBody StaticContentParam param) { + staticStorageService.save(param.getPath(), param.getContent()); + } } diff --git a/src/main/java/run/halo/app/handler/migrate/support/wordpress/Item.java b/src/main/java/run/halo/app/handler/migrate/support/wordpress/Item.java index 7a7c1212a..957fa042f 100644 --- a/src/main/java/run/halo/app/handler/migrate/support/wordpress/Item.java +++ b/src/main/java/run/halo/app/handler/migrate/support/wordpress/Item.java @@ -3,7 +3,6 @@ package run.halo.app.handler.migrate.support.wordpress; import com.alibaba.fastjson.annotation.JSONField; import lombok.Data; import run.halo.app.handler.migrate.utils.PropertyMappingTo; -import run.halo.app.model.entity.BasePost; import java.util.List; diff --git a/src/main/java/run/halo/app/model/params/StaticContentParam.java b/src/main/java/run/halo/app/model/params/StaticContentParam.java new file mode 100644 index 000000000..3368adb59 --- /dev/null +++ b/src/main/java/run/halo/app/model/params/StaticContentParam.java @@ -0,0 +1,15 @@ +package run.halo.app.model.params; + +import lombok.Data; + +/** + * Static content param. + * + * @author Holldean + * @date 2020-05-04 + */ +@Data +public class StaticContentParam { + private String path; + private String content; +} diff --git a/src/main/java/run/halo/app/service/StaticStorageService.java b/src/main/java/run/halo/app/service/StaticStorageService.java index 305abfe10..3f10666e3 100644 --- a/src/main/java/run/halo/app/service/StaticStorageService.java +++ b/src/main/java/run/halo/app/service/StaticStorageService.java @@ -50,4 +50,20 @@ public interface StaticStorageService { * @param file file must not be null. */ void upload(String basePath, @NonNull MultipartFile file); + + /** + * Rename static file or folder. + * + * @param basePath base path must not be null + * @param newName new name must not be null + */ + void rename(@NonNull String basePath, @NonNull String newName); + + /** + * Save static file. + * + * @param path path must not be null + * @param content saved content + */ + void save(@NonNull String path, String content); } diff --git a/src/main/java/run/halo/app/service/impl/StaticStorageServiceImpl.java b/src/main/java/run/halo/app/service/impl/StaticStorageServiceImpl.java index 2c3b45cb2..ce9f18175 100644 --- a/src/main/java/run/halo/app/service/impl/StaticStorageServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/StaticStorageServiceImpl.java @@ -21,9 +21,8 @@ import run.halo.app.utils.FileUtils; import javax.activation.MimetypesFileTypeMap; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; import java.util.LinkedList; import java.util.List; import java.util.stream.Stream; @@ -97,6 +96,10 @@ public class StaticStorageServiceImpl implements StaticStorageService, Applicati Assert.notNull(relativePath, "Relative path must not be null"); Path path = Paths.get(staticDir.toString(), relativePath); + + // check if the path is valid (not outside staticDir) + FileUtils.checkDirectoryTraversal(staticDir.toString(), path.toString()); + log.debug(path.toString()); try { @@ -127,6 +130,9 @@ public class StaticStorageServiceImpl implements StaticStorageService, Applicati path = Paths.get(staticDir.toString(), basePath, folderName); } + // check if the path is valid (not outside staticDir) + FileUtils.checkDirectoryTraversal(staticDir.toString(), path.toString()); + if (path.toFile().exists()) { throw new FileOperationException("目录 " + path.toString() + " 已存在").setErrorData(path); } @@ -154,6 +160,9 @@ public class StaticStorageServiceImpl implements StaticStorageService, Applicati uploadPath = Paths.get(staticDir.toString(), basePath, file.getOriginalFilename()); } + // check if the path is valid (not outside staticDir) + FileUtils.checkDirectoryTraversal(staticDir.toString(), uploadPath.toString()); + if (uploadPath.toFile().exists()) { throw new FileOperationException("文件 " + file.getOriginalFilename() + " 已存在").setErrorData(uploadPath); } @@ -167,6 +176,53 @@ public class StaticStorageServiceImpl implements StaticStorageService, Applicati } } + @Override + public void rename(String basePath, String newName) { + Assert.notNull(basePath, "Base path must not be null"); + Assert.notNull(newName, "New name must not be null"); + + Path pathToRename; + + if (StringUtils.startsWith(newName, API_FOLDER_NAME)) { + throw new FileOperationException("重命名名称 " + newName + " 不合法"); + } + + pathToRename = Paths.get(staticDir.toString(), basePath); + + // check if the path is valid (not outside staticDir) + FileUtils.checkDirectoryTraversal(staticDir.toString(), pathToRename.toString()); + + try { + FileUtils.rename(pathToRename, newName); + onChange(); + } catch (FileAlreadyExistsException e) { + throw new FileOperationException("该路径下名称 " + newName + " 已存在"); + } catch (IOException e) { + throw new FileOperationException("重命名 " + pathToRename.toString() + " 失败"); + } + } + + @Override + public void save(String path, String content) { + Assert.notNull(path, "Path must not be null"); + + Path savePath = Paths.get(staticDir.toString(), path); + + // check if the path is valid (not outside staticDir) + FileUtils.checkDirectoryTraversal(staticDir.toString(), savePath.toString()); + + // check if file exist + if (!Files.isRegularFile(savePath)) { + throw new FileOperationException("路径 " + path + " 不合法"); + } + + try { + Files.write(savePath, content.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new ServiceException("保存内容失败 " + path, e); + } + } + private void onChange() { eventPublisher.publishEvent(new StaticStorageChangedEvent(this, staticDir)); } diff --git a/src/main/java/run/halo/app/utils/FileUtils.java b/src/main/java/run/halo/app/utils/FileUtils.java index 026c5b012..2ad8a7cd1 100644 --- a/src/main/java/run/halo/app/utils/FileUtils.java +++ b/src/main/java/run/halo/app/utils/FileUtils.java @@ -77,6 +77,24 @@ public class FileUtils { log.info("Deleted [{}] successfully", deletingPath); } + /** + * Renames file or folder. + * + * @param pathToRename file path to rename must not be null + * @param newName new name must not be null + */ + public static void rename(@NonNull Path pathToRename, @NonNull String newName) throws IOException { + Assert.notNull(pathToRename, "File path to rename must not be null"); + Assert.notNull(newName, "New name must not be null"); + + Path newPath = pathToRename.resolveSibling(newName); + log.info("Rename [{}] to [{}]", pathToRename, newPath); + + Files.move(pathToRename, newPath); + + log.info("Rename [{}] successfully", pathToRename); + } + /** * Unzips content to the target path. * diff --git a/src/test/java/run/halo/app/utils/FileUtilsTest.java b/src/test/java/run/halo/app/utils/FileUtilsTest.java index 420894007..cb7100805 100644 --- a/src/test/java/run/halo/app/utils/FileUtilsTest.java +++ b/src/test/java/run/halo/app/utils/FileUtilsTest.java @@ -8,6 +8,7 @@ import run.halo.app.model.support.HaloConst; import java.io.IOException; import java.io.RandomAccessFile; +import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -108,4 +109,90 @@ public class FileUtilsTest { log.debug("Buffer String: [{}]", bufString); } } + + @Test + public void testRenameFile() throws IOException { + // Create a temp folder + Path tempDirectory = Files.createTempDirectory("halo-test"); + + Path testPath = tempDirectory.resolve("test/test"); + Path filePath = tempDirectory.resolve("test/test/test.file"); + + // Create a temp file and folder + Files.createDirectories(testPath); + Files.createFile(filePath); + + // Write content to the temp file + String content = "Test Content!\n"; + Files.write(filePath, content.getBytes()); + + // Rename temp file + FileUtils.rename(filePath, "newName"); + Path newPath = filePath.resolveSibling("newName"); + + Assert.assertFalse(Files.exists(filePath)); + Assert.assertTrue(Files.isRegularFile(newPath)); + Assert.assertEquals(new String(Files.readAllBytes(newPath)), content); + + FileUtils.deleteFolder(tempDirectory); + } + + @Test + public void testRenameFolder() throws IOException { + // Create a temp folder + Path tempDirectory = Files.createTempDirectory("halo-test"); + + Path testPath = tempDirectory.resolve("test/test"); + Path filePath = tempDirectory.resolve("test/test.file"); + + // Create a temp file and folder + Files.createDirectories(testPath); + Files.createFile(filePath); + + // Rename temp folder + FileUtils.rename(tempDirectory.resolve("test"), "newName"); + Path newPath = tempDirectory.resolve("newName"); + + Assert.assertTrue(Files.isDirectory(newPath)); + Assert.assertTrue(Files.isRegularFile(newPath.resolve("test.file"))); + + FileUtils.deleteFolder(tempDirectory); + } + + @Test + public void testRenameRepeat() throws IOException { + // Create a temp folder + Path tempDirectory = Files.createTempDirectory("halo-test"); + + Path testPathOne = tempDirectory.resolve("test/testOne"); + Path testPathTwo = tempDirectory.resolve("test/testTwo"); + Path filePathOne = tempDirectory.resolve("test/testOne.file"); + Path filePathTwo = tempDirectory.resolve("test/testTwo.file"); + + // Create temp files and folders + Files.createDirectories(testPathOne); + Files.createDirectories(testPathTwo); + Files.createFile(filePathOne); + Files.createFile(filePathTwo); + + try { + FileUtils.rename(testPathOne, "testTwo"); + } catch (Exception e) { + Assert.assertTrue(e instanceof FileAlreadyExistsException); + } + + try { + FileUtils.rename(filePathOne, "testTwo.file"); + } catch (Exception e) { + Assert.assertTrue(e instanceof FileAlreadyExistsException); + } + + try { + FileUtils.rename(filePathOne, "testOne"); + } catch (Exception e) { + Assert.assertTrue(e instanceof FileAlreadyExistsException); + } + + FileUtils.deleteFolder(tempDirectory); + } } \ No newline at end of file