diff --git a/src/main/java/run/halo/app/controller/admin/api/ThemeController.java b/src/main/java/run/halo/app/controller/admin/api/ThemeController.java index 5955ee053..31688b433 100644 --- a/src/main/java/run/halo/app/controller/admin/api/ThemeController.java +++ b/src/main/java/run/halo/app/controller/admin/api/ThemeController.java @@ -117,13 +117,6 @@ public class ThemeController { themeSettingService.save(settings, themeId); } - @PutMapping("{themeId}") - public ThemeProperty updateTheme(@PathVariable("themeId") String themeId, - @RequestPart(name = "file", required = false) MultipartFile file) { - - return themeService.update(themeId); - } - @DeleteMapping("{themeId}") @ApiOperation("Deletes a theme") public void deleteBy(@PathVariable("themeId") String themeId) { @@ -136,12 +129,25 @@ public class ThemeController { return themeService.upload(file); } + @PutMapping("upload/{themeId}") + public ThemeProperty updateThemeByUpload(@PathVariable("themeId") String themeId, + @RequestPart("file") MultipartFile file) { + return themeService.update(themeId, file); + } + @PostMapping("fetching") @ApiOperation("Fetches a new theme") public ThemeProperty fetchTheme(@RequestParam("uri") String uri) { return themeService.fetch(uri); } + @PutMapping("fetching/{themeId}") + public ThemeProperty updateThemeByFetching(@PathVariable("themeId") String themeId, + @RequestPart(name = "file", required = false) MultipartFile file) { + + return themeService.update(themeId); + } + @PostMapping("reload") @ApiOperation("Reloads themes") public void reload() { diff --git a/src/main/java/run/halo/app/service/ThemeService.java b/src/main/java/run/halo/app/service/ThemeService.java index 6ad65d68d..df100e6e6 100644 --- a/src/main/java/run/halo/app/service/ThemeService.java +++ b/src/main/java/run/halo/app/service/ThemeService.java @@ -24,6 +24,7 @@ public interface ThemeService { /** * Theme property file name. */ + @Deprecated String THEME_PROPERTY_FILE_NAME = "theme.yaml"; /** @@ -236,6 +237,7 @@ public interface ThemeService { * * @param themeTmpPath theme temporary path must not be null * @return theme property + * @throws IOException IOException */ @NonNull ThemeProperty add(@NonNull Path themeTmpPath) throws IOException; @@ -262,4 +264,13 @@ public interface ThemeService { */ @NonNull ThemeProperty update(@NonNull String themeId); + + /** + * Updates theme by theme id. + * + * @param themeId theme id must not be blank + * @param file multipart file must not be null + * @return theme info + */ + ThemeProperty update(@NonNull String themeId, @NonNull MultipartFile file); } diff --git a/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java b/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java index 9c61f8ab5..bdc0d1a60 100644 --- a/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java @@ -105,7 +105,7 @@ public class ThemeServiceImpl implements ThemeService { @Override public ThemeProperty getThemeOfNonNullBy(String themeId) { - return getThemeBy(themeId).orElseThrow(() -> new NotFoundException("Theme with id: " + themeId + " was not found").setErrorData(themeId)); + return getThemeBy(themeId).orElseThrow(() -> new NotFoundException("没有找到 id 为 " + themeId + " 的主题").setErrorData(themeId)); } @Override @@ -487,6 +487,59 @@ public class ThemeServiceImpl implements ThemeService { return getThemeOfNonNullBy(themeId); } + @Override + public ThemeProperty update(String themeId, MultipartFile file) { + Assert.hasText(themeId, "Theme id must not be blank"); + Assert.notNull(themeId, "Theme file must not be blank"); + + if (!StringUtils.endsWithIgnoreCase(file.getOriginalFilename(), ".zip")) { + throw new UnsupportedMediaTypeException("不支持的文件类型: " + file.getContentType()).setErrorData(file.getOriginalFilename()); + } + + ThemeProperty updatingTheme = getThemeOfNonNullBy(themeId); + + ZipInputStream zis = null; + Path tempPath = null; + + try { + // Create temp directory + tempPath = FileUtils.createTempDirectory(); + + String basename = FilenameUtils.getBasename(file.getOriginalFilename()); + Path themeTempPath = tempPath.resolve(basename); + + // Check directory traversal + FileUtils.checkDirectoryTraversal(tempPath, themeTempPath); + + // New zip input stream + zis = new ZipInputStream(file.getInputStream()); + + // Unzip to temp path + FileUtils.unzip(zis, themeTempPath); + + Path preparePath = FileUtils.tryToSkipZipParentFolder(themeTempPath); + + ThemeProperty prepareThemeProperty = getProperty(preparePath); + + if (!prepareThemeProperty.getId().equals(updatingTheme.getId())) { + throw new ServiceException("上传的主题包不是该主题的更新包: " + file.getOriginalFilename()); + } + + // Coping new theme files to old theme folder. + FileUtils.copyFolder(preparePath, Paths.get(updatingTheme.getThemePath())); + + // Gets theme property again. + return getProperty(Paths.get(updatingTheme.getThemePath())); + } catch (IOException e) { + throw new ServiceException("更新主题失败: " + file.getOriginalFilename(), e); + } finally { + // Close zip input stream + FileUtils.closeQuietly(zis); + // Delete folder after testing + FileUtils.deleteFolderQuietly(tempPath); + } + } + private void pullFromGit(@NonNull ThemeProperty themeProperty) throws IOException, GitAPIException, URISyntaxException { Assert.notNull(themeProperty, "Theme property must not be null"); @@ -735,7 +788,7 @@ public class ThemeServiceImpl implements ThemeService { */ @NonNull private ThemeProperty getProperty(@NonNull Path themePath) { - return getPropertyOfNullable(themePath).orElseThrow(() -> new ThemePropertyMissingException("该主题没有说明文件").setErrorData(themePath)); + return getPropertyOfNullable(themePath).orElseThrow(() -> new ThemePropertyMissingException(themePath + " 没有说明文件").setErrorData(themePath)); } /**