package ee.esutoniagodesu.service; import com.google.common.base.Joiner; import ee.esutoniagodesu.domain.ac.table.EAuthority; import ee.esutoniagodesu.security.permission.Permission; import ee.esutoniagodesu.util.JCString; import org.apache.log4j.Logger; import org.springframework.context.EnvironmentAware; import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import org.springframework.web.multipart.MultipartFile; import javax.inject.Inject; import java.io.*; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.AbstractMap; import java.util.Map; import java.util.Properties; import java.util.UUID; /** * Does not move files across different mount points. * Must have write rights to destination folder. */ @Service public class SHAFileService implements EnvironmentAware { private static final Logger log = Logger.getLogger(SHAFileService.class); private static String filesPath; private static String deletedPath; private static String tempUploadFolder; private static final String _PROP_SUFFIX = ".properties"; @Override public void setEnvironment(Environment env) { String path = env.getProperty("app.files.path"); filesPath = path + "main/"; tempUploadFolder = path + "temp-uploads/"; deletedPath = path + "deleted/"; Assert.notNull(filesPath); } public Map.Entry<Properties, File> getWithProperties(String sha256sum) throws IOException { Properties properties = getProperties(sha256sum); return new AbstractMap.SimpleImmutableEntry<>(properties, get(sha256sum)); } public Properties getProperties(String sha256sum) throws IOException { String hexpath = hexpath(sha256sum); String path = filesPath + hexpath + sha256sum + _PROP_SUFFIX; Properties prop = new Properties(); File file = new File(path); if (!file.exists()) { throw new FileNotFoundException(path); } prop.load(new FileInputStream(path)); return prop; } public File get(String sha256sum) { String hexpath = hexpath(sha256sum); return new File(filesPath + hexpath + sha256sum); } public String put(MultipartFile multipartFile, EAuthority... authorities) throws IOException { String originalName = multipartFile.getOriginalFilename(); File tmp = new File(tempUploadFolder + UUID.randomUUID().toString()); multipartFile.transferTo(tmp); Properties properties = metadata(tmp); properties.put("orig-name", originalName); properties.put("orig-extension", JCString.getExtension(originalName)); return put(tmp, properties, authorities); } public String put(File file, EAuthority... authorities) throws IOException { Properties properties = metadata(file); return put(file, properties, authorities); } private String put(File file, Properties properties, EAuthority... authorities) throws IOException { if (authorities != null && authorities.length > 0) properties.put("roles-allowed", Joiner.on(",").join(authorities)); return put(file, properties); } private String put(File tmp, Properties properties) throws IOException { File propertiesFile = writeProperties(properties); String sha256sum = properties.getProperty("sha256sum"); String subdir = hexpath(sha256sum); new File(filesPath + subdir).mkdirs(); File movedProperties = new File(filesPath + subdir + propertiesFile.getName()); Files.move(propertiesFile.toPath(), movedProperties.toPath(), StandardCopyOption.ATOMIC_MOVE); File movedFile = new File(filesPath + subdir + sha256sum); Files.move(tmp.toPath(), movedFile.toPath(), StandardCopyOption.ATOMIC_MOVE); return sha256sum; } public void delete(String sha256sum) throws IOException { String subdir = hexpath(sha256sum); new File(deletedPath + subdir).mkdirs(); File properties = new File(filesPath + subdir + sha256sum + _PROP_SUFFIX); File movedProperties = new File(deletedPath + subdir + sha256sum + _PROP_SUFFIX); Files.move(properties.toPath(), movedProperties.toPath(), StandardCopyOption.ATOMIC_MOVE); File file = new File(filesPath + subdir + sha256sum); File movedFile = new File(deletedPath + subdir + sha256sum); Files.move(file.toPath(), movedFile.toPath(), StandardCopyOption.ATOMIC_MOVE); } private static File writeProperties(Properties properties) throws IOException { String filename = properties.getProperty("sha256sum") + _PROP_SUFFIX; File file = new File(filename); FileOutputStream fileOut = new FileOutputStream(file); properties.store(fileOut, null); fileOut.close(); return file; } public static String mimetype(File file) throws IOException { Process p = Runtime.getRuntime().exec("file --mime-type " + file.getPath()); BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream())); return stdInput.readLine().split(":")[1].trim(); } public static Properties metadata(File file) throws IOException { Properties properties = new Properties(); properties.setProperty("temp-name", file.getName()); properties.setProperty("mime-type", mimetype(file)); properties.setProperty("sha256sum", sha256sum(file)); properties.setProperty("length", String.valueOf(file.length())); JCString.getExtension(file.getName()); return properties; } public static String hexpath(String hexstring) { if (hexstring.length() != 64) throw new IllegalArgumentException("invalid sha256 length"); StringBuilder s = new StringBuilder(); char[] chars = hexstring.toCharArray(); int i = 1; int mod = 2; for (char c : chars) { s.append(c); if ((i + 2) % mod == 0) { s.append(File.separator); mod *= mod; } i++; } return s.append(File.separator).toString(); } public static String sha256sum(File file) throws IOException { String path = file.getAbsolutePath(); log.debug("sha256sum: path=" + path); Process p = Runtime.getRuntime().exec("sha256sum " + path); BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream())); String output = stdInput.readLine(); log.debug("sha256sum: output=" + output); return output.substring(0, 64); } }