package org.owasp.webgoat.plugins; import org.owasp.webgoat.lessons.AbstractLesson; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.List; import java.util.Map; import static java.nio.file.StandardOpenOption.APPEND; import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; import static org.owasp.webgoat.plugins.PluginFileUtils.fileEndsWith; import static org.owasp.webgoat.plugins.PluginFileUtils.hasParentDirectoryWithName; public class Plugin { private static final String NAME_LESSON_SOLUTION_DIRECTORY = "lessonSolutions"; private static final String NAME_LESSON_PLANS_DIRECTORY = "lessonPlans"; private static final String NAME_LESSON_I18N_DIRECTORY = "i18n"; private final Logger logger = LoggerFactory.getLogger(Plugin.class); private final Path pluginDirectory; private Class lesson; private Map solutionLanguageFiles = new HashMap<>(); private Map lessonPlansLanguageFiles = new HashMap<>(); private File lessonSourceFile; public static class PluginLoadingFailure extends RuntimeException { public PluginLoadingFailure(String message) { super(message); } public PluginLoadingFailure(String message, Exception e) { super(message, e); } } public Plugin(Path pluginDirectory) { this.pluginDirectory = pluginDirectory; } public void loadClasses(Map classes) { for (Map.Entry clazz : classes.entrySet()) { loadClass(clazz.getKey(), clazz.getValue()); } if (lesson == null) { throw new PluginLoadingFailure(String .format("Lesson class not found, following classes were detected in the plugin: %s", StringUtils.collectionToCommaDelimitedString(classes.keySet()))); } } private void loadClass(String name, byte[] classFile) { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); PluginClassLoader pluginClassLoader = new PluginClassLoader(contextClassLoader, classFile); try { String realClassName = name.replaceFirst("/", "").replaceAll("/", ".").replaceAll(".class", ""); Class clazz = pluginClassLoader.loadClass(realClassName); if (AbstractLesson.class.isAssignableFrom(clazz)) { this.lesson = clazz; } } catch (ClassNotFoundException e) { logger.error("Unable to load class {}", name); } } public void loadFiles(List files, boolean reload) { for (Path file : files) { if (fileEndsWith(file, ".html") && hasParentDirectoryWithName(file, NAME_LESSON_SOLUTION_DIRECTORY)) { solutionLanguageFiles.put(file.getParent().getFileName().toString(), file.toFile()); } if (fileEndsWith(file, ".html") && hasParentDirectoryWithName(file, NAME_LESSON_PLANS_DIRECTORY)) { lessonPlansLanguageFiles.put(file.getParent().getFileName().toString(), file.toFile()); } if (fileEndsWith(file, ".java")) { lessonSourceFile = file.toFile(); } if (fileEndsWith(file, ".properties") && hasParentDirectoryWithName(file, NAME_LESSON_I18N_DIRECTORY)) { copyProperties(reload, file); } } } private void copyProperties(boolean reload, Path file) { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); Files.copy(file, bos); Path propertiesPath = createPropertiesDirectory(); ResourceBundleClassLoader.setPropertiesPath(propertiesPath); if ( reload ) { Files.write(propertiesPath.resolve(file.getFileName()), bos.toByteArray(), CREATE, APPEND); } else { Files.write(propertiesPath.resolve(file.getFileName()), bos.toByteArray(), CREATE, TRUNCATE_EXISTING); } } catch (IOException io) { throw new PluginLoadingFailure("Property file detected, but unable to copy the properties", io); } } private Path createPropertiesDirectory() throws IOException { if (Files.exists(pluginDirectory.resolve(NAME_LESSON_I18N_DIRECTORY))) { return pluginDirectory.resolve(NAME_LESSON_I18N_DIRECTORY); } else { return Files.createDirectory(pluginDirectory.resolve(NAME_LESSON_I18N_DIRECTORY)); } } public void rewritePaths(Path pluginTarget) { try { PluginFileUtils.replaceInFiles(this.lesson.getSimpleName() + "_files", pluginTarget.getFileName().toString() + "/plugin/" + this.lesson.getSimpleName() + "/lessonSolutions/en/" + this.lesson.getSimpleName() + "_files", solutionLanguageFiles.values()); PluginFileUtils.replaceInFiles(this.lesson.getSimpleName() + "_files", pluginTarget.getFileName().toString() + "/plugin/" + this.lesson.getSimpleName() + "/lessonPlans/en/" + this.lesson.getSimpleName() + "_files", lessonPlansLanguageFiles.values()); } catch (IOException e) { throw new PluginLoadingFailure("Unable to rewrite the paths in the solutions", e); } } public Class getLesson() { return lesson; } public Map getLessonSolutions() { return this.solutionLanguageFiles; } public File getLessonSource() { return lessonSourceFile; } public Map getLessonPlans() { return this.lessonPlansLanguageFiles; } }