mirror of https://github.com/halo-dev/halo
Provide backup dto fetch api (#1278)
* Fix swagger security reference config error * Add backup dto fetch api * Rearrange fetch apipull/1279/head
parent
49a461f245
commit
b57712e23e
|
@ -146,8 +146,8 @@ public class SwaggerConfiguration {
|
||||||
|
|
||||||
private List<SecurityScheme> adminApiKeys() {
|
private List<SecurityScheme> adminApiKeys() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
new ApiKey("Token from header", ADMIN_TOKEN_HEADER_NAME, In.HEADER.name()),
|
new ApiKey(ADMIN_TOKEN_HEADER_NAME, ADMIN_TOKEN_HEADER_NAME, In.HEADER.name()),
|
||||||
new ApiKey("Token from query", ADMIN_TOKEN_QUERY_NAME, In.QUERY.name())
|
new ApiKey(ADMIN_TOKEN_QUERY_NAME, ADMIN_TOKEN_QUERY_NAME, In.QUERY.name())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ public class SwaggerConfiguration {
|
||||||
final PathMatcher pathMatcher = new AntPathMatcher();
|
final PathMatcher pathMatcher = new AntPathMatcher();
|
||||||
return Collections.singletonList(
|
return Collections.singletonList(
|
||||||
SecurityContext.builder()
|
SecurityContext.builder()
|
||||||
.securityReferences(defaultAuth())
|
.securityReferences(adminApiAuths())
|
||||||
.operationSelector(operationContext -> {
|
.operationSelector(operationContext -> {
|
||||||
var requestMappingPattern = operationContext.requestMappingPattern();
|
var requestMappingPattern = operationContext.requestMappingPattern();
|
||||||
return pathMatcher.match("/api/admin/**/*", requestMappingPattern);
|
return pathMatcher.match("/api/admin/**/*", requestMappingPattern);
|
||||||
|
@ -166,8 +166,8 @@ public class SwaggerConfiguration {
|
||||||
|
|
||||||
private List<SecurityScheme> contentApiKeys() {
|
private List<SecurityScheme> contentApiKeys() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
new ApiKey("Access key from header", API_ACCESS_KEY_HEADER_NAME, In.HEADER.name()),
|
new ApiKey(API_ACCESS_KEY_HEADER_NAME, API_ACCESS_KEY_HEADER_NAME, In.HEADER.name()),
|
||||||
new ApiKey("Access key from query", API_ACCESS_KEY_QUERY_NAME, In.QUERY.name())
|
new ApiKey(API_ACCESS_KEY_QUERY_NAME, API_ACCESS_KEY_QUERY_NAME, In.QUERY.name())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +175,7 @@ public class SwaggerConfiguration {
|
||||||
final PathMatcher pathMatcher = new AntPathMatcher();
|
final PathMatcher pathMatcher = new AntPathMatcher();
|
||||||
return Collections.singletonList(
|
return Collections.singletonList(
|
||||||
SecurityContext.builder()
|
SecurityContext.builder()
|
||||||
.securityReferences(contentApiAuth())
|
.securityReferences(contentApiAuths())
|
||||||
.operationSelector(operationContext -> {
|
.operationSelector(operationContext -> {
|
||||||
var requestMappingPattern = operationContext.requestMappingPattern();
|
var requestMappingPattern = operationContext.requestMappingPattern();
|
||||||
return pathMatcher.match("/api/content/**/*", requestMappingPattern);
|
return pathMatcher.match("/api/content/**/*", requestMappingPattern);
|
||||||
|
@ -184,18 +184,18 @@ public class SwaggerConfiguration {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<SecurityReference> defaultAuth() {
|
private List<SecurityReference> adminApiAuths() {
|
||||||
AuthorizationScope[] authorizationScopes =
|
AuthorizationScope[] authorizationScopes =
|
||||||
{new AuthorizationScope("Admin api", "Access admin api")};
|
{new AuthorizationScope("Admin api", "Access admin api")};
|
||||||
return Arrays.asList(new SecurityReference("Token from header", authorizationScopes),
|
return Arrays.asList(new SecurityReference(ADMIN_TOKEN_HEADER_NAME, authorizationScopes),
|
||||||
new SecurityReference("Token from query", authorizationScopes));
|
new SecurityReference(ADMIN_TOKEN_QUERY_NAME, authorizationScopes));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<SecurityReference> contentApiAuth() {
|
private List<SecurityReference> contentApiAuths() {
|
||||||
AuthorizationScope[] authorizationScopes =
|
AuthorizationScope[] authorizationScopes =
|
||||||
{new AuthorizationScope("content api", "Access content api")};
|
{new AuthorizationScope("content api", "Access content api")};
|
||||||
return Arrays.asList(new SecurityReference("Access key from header", authorizationScopes),
|
return Arrays.asList(new SecurityReference(API_ACCESS_KEY_HEADER_NAME, authorizationScopes),
|
||||||
new SecurityReference("Access key from query", authorizationScopes));
|
new SecurityReference(API_ACCESS_KEY_QUERY_NAME, authorizationScopes));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ApiInfo apiInfo() {
|
private ApiInfo apiInfo() {
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
package run.halo.app.controller.admin.api;
|
package run.halo.app.controller.admin.api;
|
||||||
|
|
||||||
|
import static run.halo.app.service.BackupService.BackupType.JSON_DATA;
|
||||||
|
import static run.halo.app.service.BackupService.BackupType.MARKDOWN;
|
||||||
|
import static run.halo.app.service.BackupService.BackupType.WHOLE_SITE;
|
||||||
|
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -21,6 +26,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import run.halo.app.annotation.DisableOnCondition;
|
import run.halo.app.annotation.DisableOnCondition;
|
||||||
import run.halo.app.config.properties.HaloProperties;
|
import run.halo.app.config.properties.HaloProperties;
|
||||||
|
import run.halo.app.exception.NotFoundException;
|
||||||
import run.halo.app.model.dto.BackupDTO;
|
import run.halo.app.model.dto.BackupDTO;
|
||||||
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
||||||
import run.halo.app.model.params.PostMarkdownParam;
|
import run.halo.app.model.params.PostMarkdownParam;
|
||||||
|
@ -48,6 +54,29 @@ public class BackupController {
|
||||||
this.haloProperties = haloProperties;
|
this.haloProperties = haloProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("work-dir/fetch")
|
||||||
|
public BackupDTO getWorkDirBackup(@RequestParam("filename") String filename) {
|
||||||
|
return backupService.getBackup(Paths.get(haloProperties.getWorkDir(), filename), WHOLE_SITE)
|
||||||
|
.orElseThrow(() ->
|
||||||
|
new NotFoundException("备份文件 " + filename + " 不存在或已删除!").setErrorData(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("data/fetch")
|
||||||
|
public BackupDTO getDataBackup(@RequestParam("filename") String filename) {
|
||||||
|
return backupService
|
||||||
|
.getBackup(Paths.get(haloProperties.getDataExportDir(), filename), JSON_DATA)
|
||||||
|
.orElseThrow(() ->
|
||||||
|
new NotFoundException("备份文件 " + filename + " 不存在或已删除!").setErrorData(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("markdown/fetch")
|
||||||
|
public BackupDTO getMarkdownBackup(@RequestParam("filename") String filename) {
|
||||||
|
return backupService
|
||||||
|
.getBackup(Paths.get(haloProperties.getBackupMarkdownDir(), filename), MARKDOWN)
|
||||||
|
.orElseThrow(() ->
|
||||||
|
new NotFoundException("备份文件 " + filename + " 不存在或已删除!").setErrorData(filename));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("work-dir")
|
@PostMapping("work-dir")
|
||||||
@ApiOperation("Backups work directory")
|
@ApiOperation("Backups work directory")
|
||||||
@DisableOnCondition
|
@DisableOnCondition
|
||||||
|
@ -61,16 +90,16 @@ public class BackupController {
|
||||||
return backupService.listWorkDirBackups();
|
return backupService.listWorkDirBackups();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("work-dir/{fileName:.+}")
|
@GetMapping("work-dir/{filename:.+}")
|
||||||
@ApiOperation("Downloads a work directory backup file")
|
@ApiOperation("Downloads a work directory backup file")
|
||||||
@DisableOnCondition
|
@DisableOnCondition
|
||||||
public ResponseEntity<Resource> downloadBackup(@PathVariable("fileName") String fileName,
|
public ResponseEntity<Resource> downloadBackup(@PathVariable("filename") String filename,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
log.info("Try to download backup file: [{}]", fileName);
|
log.info("Trying to download backup file: [{}]", filename);
|
||||||
|
|
||||||
// Load file as resource
|
// Load file as resource
|
||||||
Resource backupResource =
|
Resource backupResource =
|
||||||
backupService.loadFileAsResource(haloProperties.getBackupDir(), fileName);
|
backupService.loadFileAsResource(haloProperties.getBackupDir(), filename);
|
||||||
|
|
||||||
String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
|
String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
|
||||||
// Try to determine file's content type
|
// Try to determine file's content type
|
||||||
|
|
|
@ -252,7 +252,8 @@ public abstract class AbstractAuthenticationFilter extends OncePerRequestFilter
|
||||||
|
|
||||||
// Get allowed uri
|
// Get allowed uri
|
||||||
String allowedUri = oneTimeTokenService.get(oneTimeToken)
|
String allowedUri = oneTimeTokenService.get(oneTimeToken)
|
||||||
.orElseThrow(() -> new BadRequestException("The one-time token does not exist")
|
.orElseThrow(() -> new BadRequestException(
|
||||||
|
"The one-time token does not exist or has been expired")
|
||||||
.setErrorData(oneTimeToken));
|
.setErrorData(oneTimeToken));
|
||||||
|
|
||||||
// Get request uri
|
// Get request uri
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package run.halo.app.security.service.impl;
|
package run.halo.app.security.service.impl;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
@ -16,10 +17,9 @@ import run.halo.app.utils.HaloUtils;
|
||||||
@Service
|
@Service
|
||||||
public class OneTimeTokenServiceImpl implements OneTimeTokenService {
|
public class OneTimeTokenServiceImpl implements OneTimeTokenService {
|
||||||
|
|
||||||
/**
|
private static final String tokenPrefix = "OTT-";
|
||||||
* One-time token expired day. (unit: day)
|
|
||||||
*/
|
private static final Duration OTT_EXPIRATION_TIME = Duration.ofMinutes(5);
|
||||||
public static final int OTT_EXPIRED_DAY = 1;
|
|
||||||
|
|
||||||
private final AbstractStringCacheStore cacheStore;
|
private final AbstractStringCacheStore cacheStore;
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ public class OneTimeTokenServiceImpl implements OneTimeTokenService {
|
||||||
Assert.hasText(oneTimeToken, "One-time token must not be blank");
|
Assert.hasText(oneTimeToken, "One-time token must not be blank");
|
||||||
|
|
||||||
// Get from cache store
|
// Get from cache store
|
||||||
return cacheStore.get(oneTimeToken);
|
return cacheStore.get(tokenPrefix + oneTimeToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -43,7 +43,10 @@ public class OneTimeTokenServiceImpl implements OneTimeTokenService {
|
||||||
String oneTimeToken = HaloUtils.randomUUIDWithoutDash();
|
String oneTimeToken = HaloUtils.randomUUIDWithoutDash();
|
||||||
|
|
||||||
// Put ott along with request uri
|
// Put ott along with request uri
|
||||||
cacheStore.put(oneTimeToken, uri, OTT_EXPIRED_DAY, TimeUnit.DAYS);
|
cacheStore.put(tokenPrefix + oneTimeToken,
|
||||||
|
uri,
|
||||||
|
OTT_EXPIRATION_TIME.getSeconds(),
|
||||||
|
TimeUnit.SECONDS);
|
||||||
|
|
||||||
// Return ott
|
// Return ott
|
||||||
return oneTimeToken;
|
return oneTimeToken;
|
||||||
|
@ -54,6 +57,6 @@ public class OneTimeTokenServiceImpl implements OneTimeTokenService {
|
||||||
Assert.hasText(oneTimeToken, "One-time token must not be blank");
|
Assert.hasText(oneTimeToken, "One-time token must not be blank");
|
||||||
|
|
||||||
// Delete the token
|
// Delete the token
|
||||||
cacheStore.delete(oneTimeToken);
|
cacheStore.delete(tokenPrefix + oneTimeToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package run.halo.app.service;
|
package run.halo.app.service;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
@ -44,6 +46,16 @@ public interface BackupService {
|
||||||
@NonNull
|
@NonNull
|
||||||
List<BackupDTO> listWorkDirBackups();
|
List<BackupDTO> listWorkDirBackups();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get backup data by backup file name.
|
||||||
|
*
|
||||||
|
* @param backupFileName backup file name must not be blank
|
||||||
|
* @param type backup type must not be null
|
||||||
|
* @return an optional of backup data
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
Optional<BackupDTO> getBackup(@NonNull Path backupFileName, @NonNull BackupType type);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes backup.
|
* Deletes backup.
|
||||||
*
|
*
|
||||||
|
@ -116,4 +128,26 @@ public interface BackupService {
|
||||||
* @param fileName file name
|
* @param fileName file name
|
||||||
*/
|
*/
|
||||||
void deleteMarkdown(@NonNull String fileName);
|
void deleteMarkdown(@NonNull String fileName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backup type.
|
||||||
|
*
|
||||||
|
* @author johnniang
|
||||||
|
*/
|
||||||
|
enum BackupType {
|
||||||
|
WHOLE_SITE("/api/admin/backups/work-dir"),
|
||||||
|
JSON_DATA("/api/admin/backups/data"),
|
||||||
|
MARKDOWN("/api/admin/backups/markdown/export"),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final String baseUri;
|
||||||
|
|
||||||
|
BackupType(String baseUri) {
|
||||||
|
this.baseUri = baseUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBaseUri() {
|
||||||
|
return baseUri;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import static run.halo.app.model.support.HaloConst.HALO_BACKUP_MARKDOWN_PREFIX;
|
||||||
import static run.halo.app.model.support.HaloConst.HALO_BACKUP_PREFIX;
|
import static run.halo.app.model.support.HaloConst.HALO_BACKUP_PREFIX;
|
||||||
import static run.halo.app.model.support.HaloConst.HALO_DATA_EXPORT_PREFIX;
|
import static run.halo.app.model.support.HaloConst.HALO_DATA_EXPORT_PREFIX;
|
||||||
import static run.halo.app.utils.DateTimeUtils.HORIZONTAL_LINE_DATETIME_FORMATTER;
|
import static run.halo.app.utils.DateTimeUtils.HORIZONTAL_LINE_DATETIME_FORMATTER;
|
||||||
|
import static run.halo.app.utils.FileUtils.checkDirectoryTraversal;
|
||||||
|
|
||||||
import cn.hutool.core.date.DateUtil;
|
import cn.hutool.core.date.DateUtil;
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
@ -12,10 +13,7 @@ import cn.hutool.core.util.CharsetUtil;
|
||||||
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.core.util.IdUtil;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -119,16 +117,8 @@ public class BackupServiceImpl implements BackupService {
|
||||||
|
|
||||||
private static final String DATA_EXPORT_BASE_URI = "/api/admin/backups/data";
|
private static final String DATA_EXPORT_BASE_URI = "/api/admin/backups/data";
|
||||||
|
|
||||||
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
|
||||||
|
|
||||||
private static final String UPLOAD_SUB_DIR = "upload/";
|
private static final String UPLOAD_SUB_DIR = "upload/";
|
||||||
|
|
||||||
private static final Type MAP_TYPE = new TypeToken<Map<String, ?>>() {
|
|
||||||
}.getType();
|
|
||||||
|
|
||||||
private static final Type JSON_OBJECT_TYPE = new TypeToken<List<JsonObject>>() {
|
|
||||||
}.getType();
|
|
||||||
|
|
||||||
private final AttachmentService attachmentService;
|
private final AttachmentService attachmentService;
|
||||||
|
|
||||||
private final CategoryService categoryService;
|
private final CategoryService categoryService;
|
||||||
|
@ -214,18 +204,6 @@ public class BackupServiceImpl implements BackupService {
|
||||||
this.eventPublisher = eventPublisher;
|
this.eventPublisher = eventPublisher;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sanitizes the specified file name.
|
|
||||||
*
|
|
||||||
* @param unSanitized the specified file name
|
|
||||||
* @return sanitized file name
|
|
||||||
*/
|
|
||||||
public static String sanitizeFilename(final String unSanitized) {
|
|
||||||
return unSanitized.replaceAll("[^(a-zA-Z0-9\\u4e00-\\u9fa5\\.)]", "")
|
|
||||||
.replaceAll("[\\?\\\\/:|<>\\*\\[\\]\\(\\)\\$%\\{\\}@~\\.]", "")
|
|
||||||
.replaceAll("\\s", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BasePostDetailDTO importMarkdown(MultipartFile file) throws IOException {
|
public BasePostDetailDTO importMarkdown(MultipartFile file) throws IOException {
|
||||||
|
|
||||||
|
@ -233,7 +211,6 @@ public class BackupServiceImpl implements BackupService {
|
||||||
String markdown = IoUtil.read(file.getInputStream(), StandardCharsets.UTF_8);
|
String markdown = IoUtil.read(file.getInputStream(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
// TODO sheet import
|
// TODO sheet import
|
||||||
|
|
||||||
return postService.importMarkdown(markdown, file.getOriginalFilename());
|
return postService.importMarkdown(markdown, file.getOriginalFilename());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,6 +262,16 @@ public class BackupServiceImpl implements BackupService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<BackupDTO> getBackup(@NonNull Path backupFilePath, @NonNull BackupType type) {
|
||||||
|
if (Files.notExists(backupFilePath)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
BackupDTO backupDto = buildBackupDto(type.getBaseUri(), backupFilePath);
|
||||||
|
return Optional.of(backupDto);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteWorkDirBackup(String fileName) {
|
public void deleteWorkDirBackup(String fileName) {
|
||||||
Assert.hasText(fileName, "File name must not be blank");
|
Assert.hasText(fileName, "File name must not be blank");
|
||||||
|
@ -295,7 +282,7 @@ public class BackupServiceImpl implements BackupService {
|
||||||
Path backupPath = backupRootPath.resolve(fileName);
|
Path backupPath = backupRootPath.resolve(fileName);
|
||||||
|
|
||||||
// Check directory traversal
|
// Check directory traversal
|
||||||
run.halo.app.utils.FileUtils.checkDirectoryTraversal(backupRootPath, backupPath);
|
checkDirectoryTraversal(backupRootPath, backupPath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Delete backup file
|
// Delete backup file
|
||||||
|
@ -324,7 +311,7 @@ public class BackupServiceImpl implements BackupService {
|
||||||
Path backupFilePath = Paths.get(basePath, fileName).normalize();
|
Path backupFilePath = Paths.get(basePath, fileName).normalize();
|
||||||
|
|
||||||
// Check directory traversal
|
// Check directory traversal
|
||||||
run.halo.app.utils.FileUtils.checkDirectoryTraversal(backupParentPath, backupFilePath);
|
checkDirectoryTraversal(backupParentPath, backupFilePath);
|
||||||
|
|
||||||
// Build url resource
|
// Build url resource
|
||||||
Resource backupResource = new UrlResource(backupFilePath.toUri());
|
Resource backupResource = new UrlResource(backupFilePath.toUri());
|
||||||
|
@ -418,7 +405,7 @@ public class BackupServiceImpl implements BackupService {
|
||||||
|
|
||||||
Path backupPath = dataExportRootPath.resolve(fileName);
|
Path backupPath = dataExportRootPath.resolve(fileName);
|
||||||
|
|
||||||
run.halo.app.utils.FileUtils.checkDirectoryTraversal(dataExportRootPath, backupPath);
|
checkDirectoryTraversal(dataExportRootPath, backupPath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Delete backup file
|
// Delete backup file
|
||||||
|
@ -436,7 +423,7 @@ public class BackupServiceImpl implements BackupService {
|
||||||
|
|
||||||
ObjectMapper mapper = JsonUtils.createDefaultJsonMapper();
|
ObjectMapper mapper = JsonUtils.createDefaultJsonMapper();
|
||||||
TypeReference<HashMap<String, Object>> typeRef =
|
TypeReference<HashMap<String, Object>> typeRef =
|
||||||
new TypeReference<HashMap<String, Object>>() {
|
new TypeReference<>() {
|
||||||
};
|
};
|
||||||
HashMap<String, Object> data = mapper.readValue(jsonContent, typeRef);
|
HashMap<String, Object> data = mapper.readValue(jsonContent, typeRef);
|
||||||
|
|
||||||
|
@ -539,19 +526,18 @@ public class BackupServiceImpl implements BackupService {
|
||||||
// Write files to the temporary directory
|
// Write files to the temporary directory
|
||||||
String markdownFileTempPathName =
|
String markdownFileTempPathName =
|
||||||
haloProperties.getBackupMarkdownDir() + IdUtil.simpleUUID().hashCode();
|
haloProperties.getBackupMarkdownDir() + IdUtil.simpleUUID().hashCode();
|
||||||
for (int i = 0; i < postMarkdownList.size(); i++) {
|
for (PostMarkdownVO postMarkdownVo : postMarkdownList) {
|
||||||
PostMarkdownVO postMarkdownVO = postMarkdownList.get(i);
|
|
||||||
StringBuilder content = new StringBuilder();
|
StringBuilder content = new StringBuilder();
|
||||||
Boolean needFrontMatter =
|
Boolean needFrontMatter =
|
||||||
Optional.ofNullable(postMarkdownParam.getNeedFrontMatter()).orElse(false);
|
Optional.ofNullable(postMarkdownParam.getNeedFrontMatter()).orElse(false);
|
||||||
if (needFrontMatter) {
|
if (needFrontMatter) {
|
||||||
// Add front-matter
|
// Add front-matter
|
||||||
content.append(postMarkdownVO.getFrontMatter()).append("\n");
|
content.append(postMarkdownVo.getFrontMatter()).append("\n");
|
||||||
}
|
}
|
||||||
content.append(postMarkdownVO.getOriginalContent());
|
content.append(postMarkdownVo.getOriginalContent());
|
||||||
try {
|
try {
|
||||||
String markdownFileName =
|
String markdownFileName =
|
||||||
postMarkdownVO.getTitle() + "-" + postMarkdownVO.getSlug() + ".md";
|
postMarkdownVo.getTitle() + "-" + postMarkdownVo.getSlug() + ".md";
|
||||||
Path markdownFilePath = Paths.get(markdownFileTempPathName, markdownFileName);
|
Path markdownFilePath = Paths.get(markdownFileTempPathName, markdownFileName);
|
||||||
if (!Files.exists(markdownFilePath.getParent())) {
|
if (!Files.exists(markdownFilePath.getParent())) {
|
||||||
Files.createDirectories(markdownFilePath.getParent());
|
Files.createDirectories(markdownFilePath.getParent());
|
||||||
|
@ -565,9 +551,6 @@ public class BackupServiceImpl implements BackupService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ZipOutputStream markdownZipOut = null;
|
|
||||||
// Zip file
|
|
||||||
try {
|
|
||||||
// Create zip path
|
// Create zip path
|
||||||
String markdownZipFileName = HALO_BACKUP_MARKDOWN_PREFIX
|
String markdownZipFileName = HALO_BACKUP_MARKDOWN_PREFIX
|
||||||
+ DateTimeUtils.format(LocalDateTime.now(), HORIZONTAL_LINE_DATETIME_FORMATTER)
|
+ DateTimeUtils.format(LocalDateTime.now(), HORIZONTAL_LINE_DATETIME_FORMATTER)
|
||||||
|
@ -580,8 +563,9 @@ public class BackupServiceImpl implements BackupService {
|
||||||
Files.createDirectories(markdownZipFilePath.getParent());
|
Files.createDirectories(markdownZipFilePath.getParent());
|
||||||
}
|
}
|
||||||
Path markdownZipPath = Files.createFile(markdownZipFilePath);
|
Path markdownZipPath = Files.createFile(markdownZipFilePath);
|
||||||
|
// Zip file
|
||||||
markdownZipOut = new ZipOutputStream(Files.newOutputStream(markdownZipPath));
|
try (ZipOutputStream markdownZipOut = new ZipOutputStream(
|
||||||
|
Files.newOutputStream(markdownZipPath))) {
|
||||||
|
|
||||||
// Zip temporary directory
|
// Zip temporary directory
|
||||||
Path markdownFileTempPath = Paths.get(markdownFileTempPathName);
|
Path markdownFileTempPath = Paths.get(markdownFileTempPathName);
|
||||||
|
@ -602,10 +586,6 @@ public class BackupServiceImpl implements BackupService {
|
||||||
return buildBackupDto(DATA_EXPORT_MARKDOWN_BASE_URI, markdownZipPath);
|
return buildBackupDto(DATA_EXPORT_MARKDOWN_BASE_URI, markdownZipPath);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ServiceException("Failed to export markdowns", e);
|
throw new ServiceException("Failed to export markdowns", e);
|
||||||
} finally {
|
|
||||||
if (markdownZipOut != null) {
|
|
||||||
markdownZipOut.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -632,22 +612,22 @@ public class BackupServiceImpl implements BackupService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteMarkdown(String fileName) {
|
public void deleteMarkdown(String filename) {
|
||||||
Assert.hasText(fileName, "File name must not be blank");
|
Assert.hasText(filename, "File name must not be blank");
|
||||||
|
|
||||||
Path backupRootPath = Paths.get(haloProperties.getBackupMarkdownDir());
|
Path backupRootPath = Paths.get(haloProperties.getBackupMarkdownDir());
|
||||||
|
|
||||||
// Get backup path
|
// Get backup path
|
||||||
Path backupPath = backupRootPath.resolve(fileName);
|
Path backupPath = backupRootPath.resolve(filename);
|
||||||
|
|
||||||
// Check directory traversal
|
// Check directory traversal
|
||||||
run.halo.app.utils.FileUtils.checkDirectoryTraversal(backupRootPath, backupPath);
|
checkDirectoryTraversal(backupRootPath, backupPath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Delete backup file
|
// Delete backup file
|
||||||
Files.delete(backupPath);
|
Files.delete(backupPath);
|
||||||
} catch (NoSuchFileException e) {
|
} catch (NoSuchFileException e) {
|
||||||
throw new NotFoundException("The file " + fileName + " was not found", e);
|
throw new NotFoundException("The file " + filename + " was not found", e);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ServiceException("Failed to delete backup", e);
|
throw new ServiceException("Failed to delete backup", e);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue