mirror of https://github.com/halo-dev/halo
Secure backup file downloading
parent
8463a3f31e
commit
2153998a32
|
@ -4,7 +4,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import freemarker.template.TemplateException;
|
import freemarker.template.TemplateException;
|
||||||
import freemarker.template.TemplateExceptionHandler;
|
import freemarker.template.TemplateExceptionHandler;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.boot.jackson.JsonComponentModule;
|
import org.springframework.boot.jackson.JsonComponentModule;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
@ -32,7 +31,9 @@ import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR;
|
||||||
import static run.halo.app.model.support.HaloConst.HALO_ADMIN_RELATIVE_PATH;
|
import static run.halo.app.model.support.HaloConst.HALO_ADMIN_RELATIVE_PATH;
|
||||||
|
import static run.halo.app.utils.HaloUtils.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mvc configuration.
|
* Mvc configuration.
|
||||||
|
@ -81,18 +82,20 @@ public class WebMvcAutoConfiguration implements WebMvcConfigurer {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
String workDir = FILE_PROTOCOL + StringUtils.appendIfMissing(haloProperties.getWorkDir(), "/");
|
String workDir = FILE_PROTOCOL + ensureSuffix(haloProperties.getWorkDir(), FILE_SEPARATOR);
|
||||||
String backupDir = FILE_PROTOCOL + StringUtils.appendIfMissing(haloProperties.getBackupDir(), "/");
|
String backupDir = FILE_PROTOCOL + ensureSuffix(haloProperties.getBackupDir(), FILE_SEPARATOR);
|
||||||
registry.addResourceHandler("/**")
|
registry.addResourceHandler("/**")
|
||||||
.addResourceLocations(workDir + "templates/themes/")
|
.addResourceLocations(workDir + "templates/themes/")
|
||||||
.addResourceLocations(workDir + "templates/admin/")
|
.addResourceLocations(workDir + "templates/admin/")
|
||||||
.addResourceLocations("classpath:/admin/")
|
.addResourceLocations("classpath:/admin/")
|
||||||
.addResourceLocations(workDir + "static/");
|
.addResourceLocations(workDir + "static/");
|
||||||
registry.addResourceHandler(haloProperties.getUploadUrlPrefix() + "/**")
|
|
||||||
|
String uploadUrlPattern = ensureBoth(haloProperties.getUploadUrlPrefix(), URL_SEPARATOR) + "**";
|
||||||
|
String adminPathPattern = ensureSuffix(haloProperties.getAdminPath(), URL_SEPARATOR) + "**";
|
||||||
|
|
||||||
|
registry.addResourceHandler(uploadUrlPattern)
|
||||||
.addResourceLocations(workDir + "upload/");
|
.addResourceLocations(workDir + "upload/");
|
||||||
registry.addResourceHandler(haloProperties.getBackupUrlPrefix() + "/**")
|
registry.addResourceHandler(adminPathPattern)
|
||||||
.addResourceLocations(workDir + "backup/", backupDir);
|
|
||||||
registry.addResourceHandler(haloProperties.getAdminPath() + "/**")
|
|
||||||
.addResourceLocations(workDir + HALO_ADMIN_RELATIVE_PATH)
|
.addResourceLocations(workDir + HALO_ADMIN_RELATIVE_PATH)
|
||||||
.addResourceLocations("classpath:/admin/");
|
.addResourceLocations("classpath:/admin/");
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,15 @@ package run.halo.app.config.properties;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import run.halo.app.model.support.HaloConst;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import static run.halo.app.model.support.HaloConst.*;
|
||||||
|
import static run.halo.app.utils.HaloUtils.ensureSuffix;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Halo configuration properties.
|
* Halo configuration properties.
|
||||||
|
@ -38,27 +39,22 @@ public class HaloProperties {
|
||||||
/**
|
/**
|
||||||
* Admin path.
|
* Admin path.
|
||||||
*/
|
*/
|
||||||
private String adminPath = "/admin";
|
private String adminPath = "admin";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Halo backup directory.(Not recommended to modify this config);
|
* Halo backup directory.(Not recommended to modify this config);
|
||||||
*/
|
*/
|
||||||
private String backupDir = HaloConst.TEMP_DIR + "/halo-backup/";
|
private String backupDir = ensureSuffix(TEMP_DIR, FILE_SEPARATOR) + "halo-backup" + FILE_SEPARATOR;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Work directory.
|
* Work directory.
|
||||||
*/
|
*/
|
||||||
private String workDir = HaloConst.USER_HOME + "/.halo/";
|
private String workDir = ensureSuffix(USER_HOME, FILE_SEPARATOR) + ".halo" + FILE_SEPARATOR;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload prefix.
|
* Upload prefix.
|
||||||
*/
|
*/
|
||||||
private String uploadUrlPrefix = "/upload";
|
private String uploadUrlPrefix = "upload";
|
||||||
|
|
||||||
/**
|
|
||||||
* backup prefix.
|
|
||||||
*/
|
|
||||||
private String backupUrlPrefix = "/backup";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download Timeout.
|
* Download Timeout.
|
||||||
|
|
|
@ -6,6 +6,10 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import run.halo.app.exception.FileOperationException;
|
import run.halo.app.exception.FileOperationException;
|
||||||
|
@ -13,6 +17,7 @@ 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.service.BackupService;
|
import run.halo.app.service.BackupService;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
@ -49,6 +54,28 @@ public class BackupController {
|
||||||
return backupService.listHaloBackups();
|
return backupService.listHaloBackups();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("halo/{fileName:.+}")
|
||||||
|
@ApiOperation("Download backup file")
|
||||||
|
public ResponseEntity<Resource> downloadBackup(@PathVariable("fileName") String fileName, HttpServletRequest request) {
|
||||||
|
log.info("Try to download backup file: [{}]", fileName);
|
||||||
|
|
||||||
|
// Load file as resource
|
||||||
|
Resource backupResource = backupService.loadFileAsResource(fileName);
|
||||||
|
|
||||||
|
String contentType = "application/octet-stream";
|
||||||
|
// Try to determine file's content type
|
||||||
|
try {
|
||||||
|
contentType = request.getServletContext().getMimeType(backupResource.getFile().getAbsolutePath());
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("Could not determine file type", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.parseMediaType(contentType))
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + backupResource.getFilename() + "\"")
|
||||||
|
.body(backupResource);
|
||||||
|
}
|
||||||
|
|
||||||
@DeleteMapping("halo")
|
@DeleteMapping("halo")
|
||||||
@ApiOperation("Delete a backup")
|
@ApiOperation("Delete a backup")
|
||||||
public void deleteBackup(@RequestParam("filename") String filename) {
|
public void deleteBackup(@RequestParam("filename") String filename) {
|
||||||
|
|
|
@ -7,7 +7,11 @@ import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
import org.aspectj.lang.annotation.Around;
|
import org.aspectj.lang.annotation.Around;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
import org.aspectj.lang.annotation.Pointcut;
|
import org.aspectj.lang.annotation.Pointcut;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
@ -75,16 +79,36 @@ public class ControllerLogAop {
|
||||||
|
|
||||||
private void printResponseLog(HttpServletRequest request, String className, String methodName, Object returnObj, long usage) throws JsonProcessingException {
|
private void printResponseLog(HttpServletRequest request, String className, String methodName, Object returnObj, long usage) throws JsonProcessingException {
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
String returningData = null;
|
String returnData = "";
|
||||||
|
|
||||||
if (returnObj != null) {
|
if (returnObj != null) {
|
||||||
if (returnObj.getClass().isAssignableFrom(byte[].class)) {
|
if (returnObj instanceof ResponseEntity) {
|
||||||
returningData = "Binary data";
|
ResponseEntity responseEntity = (ResponseEntity) returnObj;
|
||||||
|
if (responseEntity.getBody() instanceof Resource) {
|
||||||
|
returnData = "[ BINARY DATA ]";
|
||||||
} else {
|
} else {
|
||||||
returningData = JsonUtils.objectToJson(returnObj);
|
returnData = toString(responseEntity.getBody());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
returnData = toString(returnObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
log.debug("{}.{} Response: [{}], usage: [{}]ms", className, methodName, returnData, usage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.debug("{}.{} Response: [{}], usage: [{}]ms", className, methodName, returningData, usage);
|
|
||||||
|
@NonNull
|
||||||
|
private String toString(@NonNull Object obj) throws JsonProcessingException {
|
||||||
|
Assert.notNull(obj, "Return object must not be null");
|
||||||
|
|
||||||
|
String toString = "";
|
||||||
|
if (obj.getClass().isAssignableFrom(byte[].class) && obj instanceof Resource) {
|
||||||
|
toString = "[ BINARY DATA ]";
|
||||||
|
} else {
|
||||||
|
toString = JsonUtils.objectToJson(obj);
|
||||||
}
|
}
|
||||||
|
return toString;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package run.halo.app.service;
|
package run.halo.app.service;
|
||||||
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
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;
|
||||||
import run.halo.app.model.dto.BackupDTO;
|
import run.halo.app.model.dto.BackupDTO;
|
||||||
|
@ -61,7 +62,16 @@ public interface BackupService {
|
||||||
/**
|
/**
|
||||||
* Deletes backup.
|
* Deletes backup.
|
||||||
*
|
*
|
||||||
* @param filename filename must not be blank
|
* @param fileName filename must not be blank
|
||||||
*/
|
*/
|
||||||
void deleteHaloBackup(@NonNull String filename);
|
void deleteHaloBackup(@NonNull String fileName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads file as resource.
|
||||||
|
*
|
||||||
|
* @param fileName backup file name must not be blank.
|
||||||
|
* @return resource of the given file
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
Resource loadFileAsResource(@NonNull String fileName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import org.apache.commons.io.FileUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.UrlResource;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
@ -24,9 +26,11 @@ import run.halo.app.service.BackupService;
|
||||||
import run.halo.app.service.OptionService;
|
import run.halo.app.service.OptionService;
|
||||||
import run.halo.app.service.PostService;
|
import run.halo.app.service.PostService;
|
||||||
import run.halo.app.service.PostTagService;
|
import run.halo.app.service.PostTagService;
|
||||||
|
import run.halo.app.utils.HaloUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
|
@ -125,10 +129,8 @@ public class BackupServiceImpl implements BackupService {
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(post.getPassword())) {
|
if (StringUtils.isNotBlank(post.getPassword())) {
|
||||||
passwords.add(one);
|
passwords.add(one);
|
||||||
continue;
|
|
||||||
} else if (post.getDeleted()) {
|
} else if (post.getDeleted()) {
|
||||||
drafts.add(one);
|
drafts.add(one);
|
||||||
continue;
|
|
||||||
} else {
|
} else {
|
||||||
posts.add(one);
|
posts.add(one);
|
||||||
}
|
}
|
||||||
|
@ -159,10 +161,10 @@ public class BackupServiceImpl implements BackupService {
|
||||||
// Zip work directory to temporary file
|
// Zip work directory to temporary file
|
||||||
try {
|
try {
|
||||||
// Create zip path for halo zip
|
// Create zip path for halo zip
|
||||||
String haloZipFileName = new StringBuilder().append(HaloConst.HALO_BACKUP_PREFIX)
|
String haloZipFileName = HaloConst.HALO_BACKUP_PREFIX +
|
||||||
.append(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")))
|
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) +
|
||||||
.append(IdUtil.simpleUUID())
|
IdUtil.simpleUUID() +
|
||||||
.append(".zip").toString();
|
".zip";
|
||||||
// Create halo zip file
|
// Create halo zip file
|
||||||
Path haloZipPath = Files.createFile(Paths.get(haloProperties.getBackupDir(), haloZipFileName));
|
Path haloZipPath = Files.createFile(Paths.get(haloProperties.getBackupDir(), haloZipFileName));
|
||||||
|
|
||||||
|
@ -198,22 +200,42 @@ public class BackupServiceImpl implements BackupService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteHaloBackup(String filename) {
|
public void deleteHaloBackup(String fileName) {
|
||||||
Assert.hasText(filename, "File name must not be blank");
|
Assert.hasText(fileName, "File name must not be blank");
|
||||||
|
|
||||||
// Get backup path
|
// Get backup path
|
||||||
Path backupPath = Paths.get(haloProperties.getBackupDir(), filename);
|
Path backupPath = Paths.get(haloProperties.getBackupDir(), fileName);
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Resource loadFileAsResource(String fileName) {
|
||||||
|
Assert.hasText(fileName, "Backup file name must not be blank");
|
||||||
|
|
||||||
|
// Get backup file path
|
||||||
|
Path backupFilePath = Paths.get(haloProperties.getBackupDir(), fileName).normalize();
|
||||||
|
try {
|
||||||
|
// Build url resource
|
||||||
|
Resource backupResource = new UrlResource(backupFilePath.toUri());
|
||||||
|
if (!backupResource.exists()) {
|
||||||
|
// If the backup resouce is not exist
|
||||||
|
throw new NotFoundException("The file " + fileName + " was not found");
|
||||||
|
}
|
||||||
|
// Return the backup resource
|
||||||
|
return backupResource;
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new NotFoundException("The file " + fileName + " was not found", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds backup dto.
|
* Builds backup dto.
|
||||||
*
|
*
|
||||||
|
@ -226,7 +248,7 @@ public class BackupServiceImpl implements BackupService {
|
||||||
String backupFileName = backupPath.getFileName().toString();
|
String backupFileName = backupPath.getFileName().toString();
|
||||||
BackupDTO backup = new BackupDTO();
|
BackupDTO backup = new BackupDTO();
|
||||||
backup.setDownloadUrl(buildDownloadUrl(backupFileName));
|
backup.setDownloadUrl(buildDownloadUrl(backupFileName));
|
||||||
backup.setDownloadLink(backup.getDownloadLink());
|
backup.setDownloadLink(backup.getDownloadUrl());
|
||||||
backup.setFilename(backupFileName);
|
backup.setFilename(backupFileName);
|
||||||
try {
|
try {
|
||||||
backup.setUpdateTime(Files.getLastModifiedTime(backupPath).toMillis());
|
backup.setUpdateTime(Files.getLastModifiedTime(backupPath).toMillis());
|
||||||
|
@ -247,9 +269,6 @@ public class BackupServiceImpl implements BackupService {
|
||||||
private String buildDownloadUrl(@NonNull String filename) {
|
private String buildDownloadUrl(@NonNull String filename) {
|
||||||
Assert.hasText(filename, "File name must not be blank");
|
Assert.hasText(filename, "File name must not be blank");
|
||||||
|
|
||||||
return StringUtils.joinWith("/",
|
return HaloUtils.compositeHttpUrl(optionService.getBlogBaseUrl(), "api/admin/backups/halo", filename);
|
||||||
optionService.getBlogBaseUrl(),
|
|
||||||
StringUtils.removeEnd(StringUtils.removeStart(haloProperties.getBackupUrlPrefix(), "/"), "/"),
|
|
||||||
filename);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,75 @@ public class HaloUtils {
|
||||||
|
|
||||||
private static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)";
|
private static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)";
|
||||||
|
|
||||||
|
public static final String URL_SEPARATOR = "/";
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static String ensureBoth(@NonNull String string, @NonNull String bothfix) {
|
||||||
|
return ensureBoth(string, bothfix, bothfix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static String ensureBoth(@NonNull String string, @NonNull String prefix, @NonNull String suffix) {
|
||||||
|
return ensureSuffix(ensurePrefix(string, prefix), suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures the string contain prefix.
|
||||||
|
*
|
||||||
|
* @param string string must not be blank
|
||||||
|
* @param prefix prefix must not be blank
|
||||||
|
* @return string contain prefix specified
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static String ensurePrefix(@NonNull String string, @NonNull String prefix) {
|
||||||
|
Assert.hasText(string, "String must not be blank");
|
||||||
|
Assert.hasText(prefix, "Prefix must not be blank");
|
||||||
|
|
||||||
|
return prefix + StringUtils.removeStart(string, prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures the string contain suffix.
|
||||||
|
*
|
||||||
|
* @param string string must not be blank
|
||||||
|
* @param suffix suffix must not be blank
|
||||||
|
* @return string contain suffix specified
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static String ensureSuffix(@NonNull String string, @NonNull String suffix) {
|
||||||
|
Assert.hasText(string, "String must not be blank");
|
||||||
|
Assert.hasText(suffix, "Suffix must not be blank");
|
||||||
|
|
||||||
|
return StringUtils.removeEnd(string, suffix) + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composites partial url to full http url.
|
||||||
|
*
|
||||||
|
* @param partUrls partial urls must not be empty
|
||||||
|
* @return full url
|
||||||
|
*/
|
||||||
|
public static String compositeHttpUrl(@NonNull String... partUrls) {
|
||||||
|
Assert.notEmpty(partUrls, "Partial url must not be blank");
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (int i = 0; i < partUrls.length; i++) {
|
||||||
|
String partUrl = partUrls[i];
|
||||||
|
if (StringUtils.isBlank(partUrl)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
partUrl = StringUtils.removeStart(partUrl, URL_SEPARATOR);
|
||||||
|
partUrl = StringUtils.removeEnd(partUrl, URL_SEPARATOR);
|
||||||
|
if (i != 0) {
|
||||||
|
builder.append(URL_SEPARATOR);
|
||||||
|
}
|
||||||
|
builder.append(partUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Desensitizes the plain text.
|
* Desensitizes the plain text.
|
||||||
*
|
*
|
||||||
|
|
|
@ -2,11 +2,13 @@ package run.halo.app.utils;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.RandomUtils;
|
import org.apache.commons.lang3.RandomUtils;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -123,4 +125,22 @@ public class HaloUtilsTest {
|
||||||
String plainText = " ";
|
String plainText = " ";
|
||||||
HaloUtils.desensitize(plainText, 1, 1);
|
HaloUtils.desensitize(plainText, 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compositeHttpUrl() {
|
||||||
|
String url = HaloUtils.compositeHttpUrl("https://halo.run", "path1", "path2");
|
||||||
|
assertEquals("https://halo.run/path1/path2", url);
|
||||||
|
|
||||||
|
url = HaloUtils.compositeHttpUrl("https://halo.run/", "path1", "path2");
|
||||||
|
assertEquals("https://halo.run/path1/path2", url);
|
||||||
|
|
||||||
|
url = HaloUtils.compositeHttpUrl("https://halo.run/", "/path1", "path2");
|
||||||
|
assertEquals("https://halo.run/path1/path2", url);
|
||||||
|
|
||||||
|
url = HaloUtils.compositeHttpUrl("https://halo.run/", "/path1/", "path2");
|
||||||
|
assertEquals("https://halo.run/path1/path2", url);
|
||||||
|
|
||||||
|
url = HaloUtils.compositeHttpUrl("https://halo.run/", "/path1/", "/path2/");
|
||||||
|
assertEquals("https://halo.run/path1/path2", url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue