Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support Rename and Edit Staitc Files (#573) #819

Merged
merged 10 commits into from
May 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
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;

Expand Down Expand Up @@ -49,4 +50,17 @@ public void upload(String basePath,
@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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
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;

Expand Down
15 changes: 15 additions & 0 deletions src/main/java/run/halo/app/model/params/StaticContentParam.java
Original file line number Diff line number Diff line change
@@ -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;
}
16 changes: 16 additions & 0 deletions src/main/java/run/halo/app/service/StaticStorageService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@

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;
Expand Down Expand Up @@ -97,6 +96,10 @@ public void delete(String relativePath) {
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 {
Expand Down Expand Up @@ -127,6 +130,9 @@ public void createFolder(String basePath, String folderName) {
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);
}
Expand Down Expand Up @@ -154,6 +160,9 @@ public void upload(String basePath, MultipartFile file) {
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);
}
Expand All @@ -167,6 +176,53 @@ public void upload(String basePath, MultipartFile file) {
}
}

@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);
ruibaby marked this conversation as resolved.
Show resolved Hide resolved

// 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));
}
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/run/halo/app/utils/FileUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,24 @@ public static void deleteFolder(@NonNull Path deletingPath) throws IOException {
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.
*
Expand Down
87 changes: 87 additions & 0 deletions src/test/java/run/halo/app/utils/FileUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

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;
Expand Down Expand Up @@ -108,4 +109,90 @@ public void dbFileReadTest() throws IOException {
log.debug("Buffer String: [{}]", bufString);
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

也许应该再增加一个关于 directory traversal 的测试。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Impl的测试单元要怎么写?上次我提交了一个测试,结果导致隔壁的测试挂了,估计是注解写的不对。可以给个ImplTest的样例代码吗?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

早上给你回复。

@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);
}
}