From 413a6ab1503df0eb415aba982d8198ec32568b4e Mon Sep 17 00:00:00 2001 From: Jens Pelzetter Date: Sun, 9 Aug 2020 12:33:58 +0200 Subject: [PATCH] Update a theme by uploading a ZIP file --- .../java/org/libreccm/api/themes/Themes.java | 145 +++++++++++++++++- 1 file changed, 138 insertions(+), 7 deletions(-) diff --git a/ccm-core/src/main/java/org/libreccm/api/themes/Themes.java b/ccm-core/src/main/java/org/libreccm/api/themes/Themes.java index 9bac3c015..17891cf84 100644 --- a/ccm-core/src/main/java/org/libreccm/api/themes/Themes.java +++ b/ccm-core/src/main/java/org/libreccm/api/themes/Themes.java @@ -20,6 +20,8 @@ package org.libreccm.api.themes; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.hibernate.engine.jdbc.internal.BinaryStreamImpl; +import org.libreccm.core.UnexpectedErrorException; import org.libreccm.security.AuthorizationRequired; import org.libreccm.security.RequiresPrivilege; import org.libreccm.theming.ThemeFileInfo; @@ -28,17 +30,27 @@ import org.libreccm.theming.ThemeProvider; import org.libreccm.theming.ThemeVersion; import org.libreccm.theming.ThemingPrivileges; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; +import java.rmi.UnexpectedException; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.stream.Collectors; import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import javax.enterprise.context.RequestScoped; @@ -496,13 +508,86 @@ public class Themes implements Serializable { ) ) ); - - // Read every file from ZIP - // Try to find file in theme - // Create or update file in theme - // remove files not in ZIP from theme - - throw new UnsupportedOperationException(); + + final Set pathsInTheme = createThemeFilesSet( + provider, themeName + ); + + try ( + ByteArrayInputStream bais = new ByteArrayInputStream(updatedTheme); + ZipInputStream zis = new ZipInputStream(bais)) { + ZipEntry entry = zis.getNextEntry(); + while (entry != null) { + final String path = entry.getName(); + entry = zis.getNextEntry(); + if (pathsInTheme.contains(path)) { + final ByteArrayOutputStream zipBaos + = new ByteArrayOutputStream(); + byte[] zipBuffer = new byte[1024]; + int zipRead = zis.read(zipBuffer, 0, 1024); + while (zipRead != -1) { + zipBaos.writeBytes(zipBuffer); + zipRead = zis.read(zipBuffer, 0, 1024); + } + + final byte[] bytesFromZip = zipBaos.toByteArray(); + + final InputStream inputStream = provider + .getThemeFileAsStream( + themeName, ThemeVersion.DRAFT, path + ) + .orElseThrow(() -> new UnexpectedErrorException()); + final ByteArrayOutputStream themeBaos + = new ByteArrayOutputStream(); + byte[] themeBuffer = new byte[1024]; + int themeRead = inputStream.read(themeBuffer); + while (themeRead != -1) { + themeBaos.writeBytes(zipBuffer); + themeRead = inputStream.read(themeBuffer); + } + + final byte[] bytesFromTheme = themeBaos.toByteArray(); + + final byte[] fromZipChecksum = MessageDigest + .getInstance("SHA-256") + .digest(bytesFromZip); + final byte[] fromThemeChecksum = MessageDigest + .getInstance("SHA-256") + .digest(bytesFromTheme); + + if (!MessageDigest.isEqual( + fromZipChecksum, fromThemeChecksum + )) { + try(OutputStream outputStream = provider + .getOutputStreamForThemeFile(themeName, path)) { + outputStream.write(bytesFromZip); + } + } + } else { + copyFileFromZipToTheme( + zis, + provider, + themeName, + path + ); + } + } + + for (final String path : pathsInTheme) { + provider.deleteThemeFile(themeName, path); + } + } catch (IOException ex) { + throw new UnexpectedErrorException( + String.format("Failed to update theme %s", themeName), + ex + ); + } catch (NoSuchAlgorithmException ex) { + throw new UnexpectedErrorException( + "Message digest SHA-256 is not available.", ex + ); + } + + return Response.ok().build(); } private String getProviderName(final ThemeProvider provider) { @@ -592,4 +677,50 @@ public class Themes implements Serializable { ); } + private SortedSet createThemeFilesSet( + final ThemeProvider provider, final String themeName + ) { + return createThemeFilesSet(provider, themeName, "/"); + } + + private SortedSet createThemeFilesSet( + final ThemeProvider provider, + final String themeName, + final String currentPath + ) { + final SortedSet files = new TreeSet<>(); + final List filesInfo = provider + .listThemeFiles(themeName, ThemeVersion.DRAFT, currentPath); + for (final ThemeFileInfo fileInfo : filesInfo) { + final String filePath = String.format( + "%s/%s", currentPath, fileInfo.getName() + ); + files.add(filePath); + if (fileInfo.isDirectory()) { + final Set subFiles = createThemeFilesSet( + provider, themeName, filePath); + files.addAll(subFiles); + } + } + + return files; + } + + private void copyFileFromZipToTheme( + final ZipInputStream zis, + final ThemeProvider themeProvider, + final String themeName, + final String path + ) throws IOException { + try (OutputStream outputStream = themeProvider + .getOutputStreamForThemeFile(themeName, path)) { + final byte[] buffer = new byte[1024]; + int read = zis.read(buffer, 0, 1024); + while (read != -1) { + outputStream.write(buffer); + read = zis.read(buffer, 0, 1024); + } + } + } + }