Skip to content

Commit

Permalink
[core] Templating: limit compilation to supported extensions and cons…
Browse files Browse the repository at this point in the history
…train target paths (#6598)
  • Loading branch information
jimschubert authored Sep 2, 2020
1 parent 91ea6a1 commit a6d30ca
Show file tree
Hide file tree
Showing 61 changed files with 225 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ public interface TemplatingEngineAdapter {
*/
String[] getFileExtensions();

/**
* Determine if the adapter handles compilation of the file
* @param filename The template filename
*
* @return True if the file should be compiled by this adapter, else false.
*/
default boolean handlesFile(String filename) {
return filename != null && filename.length() > 0 && Arrays.stream(getFileExtensions()).anyMatch(i -> filename.endsWith("." + i));
}

/**
* Compiles a template into a string
*
Expand All @@ -65,9 +75,10 @@ String compileTemplate(TemplatingExecutor executor, Map<String, Object> bundle,
* @param templateFile The original target filename
* @return True if the template is available in the template search path, false if it can not be found
*/
@SuppressWarnings({"java:S2093"}) // ignore java:S2093 because we have double-assignment to the closeable
default boolean templateExists(TemplatingExecutor generator, String templateFile) {
return Arrays.stream(getFileExtensions()).anyMatch(ext -> {
int idx = templateFile.lastIndexOf(".");
int idx = templateFile.lastIndexOf('.');
String baseName;
if (idx > 0 && idx < templateFile.length() - 1) {
baseName = templateFile.substring(0, idx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,11 @@ protected File processTemplateToFile(Map<String, Object> templateData, String te
File target = new File(adjustedOutputFilename);
if (ignoreProcessor.allowsFile(target)) {
if (shouldGenerate) {
Path outDir = java.nio.file.Paths.get(this.config.getOutputDir()).toAbsolutePath();
Path absoluteTarget = target.toPath().toAbsolutePath();
if (!absoluteTarget.startsWith(outDir)) {
throw new RuntimeException(String.format(Locale.ROOT, "Target files must be generated within the output directory; absoluteTarget=%s outDir=%s", absoluteTarget, outDir));
}
return this.templateProcessor.write(templateData,templateName, target);
} else {
this.templateProcessor.skip(target.toPath(), String.format(Locale.ROOT, "Skipped by %s options supplied by user.", skippedByOption));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.openapitools.codegen;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.api.TemplatePathLocator;
import org.openapitools.codegen.api.TemplateProcessor;
Expand All @@ -12,14 +13,8 @@

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
import java.nio.file.*;
import java.util.*;
import java.util.regex.Pattern;

/**
Expand Down Expand Up @@ -59,6 +54,10 @@ private String getFullTemplateFile(String name) {
throw new TemplateNotFoundException(name);
}

if (name == null || name.contains("..")) {
throw new IllegalArgumentException("Template location must be constrained to template directory.");
}

return template;
}

Expand Down Expand Up @@ -104,7 +103,12 @@ public static String getCPResourcePath(final String name) {
* @param name The location of the template
* @return The raw template contents
*/
@SuppressWarnings({"java:S112"})
// ignored rule java:S112 as RuntimeException is used to match previous exception type
public String readTemplate(String name) {
if (name == null || name.contains("..")) {
throw new IllegalArgumentException("Template location must be constrained to template directory.");
}
try {
Reader reader = getTemplateReader(name);
if (reader == null) {
Expand All @@ -118,22 +122,31 @@ public String readTemplate(String name) {
throw new RuntimeException("can't load template " + name);
}

@SuppressWarnings("squid:S2095")
// ignored rule as used in the CLI and it's required to return a reader
@SuppressWarnings({"squid:S2095", "java:S112"})
// ignored rule squid:S2095 as used in the CLI and it's required to return a reader
// ignored rule java:S112 as RuntimeException is used to match previous exception type
public Reader getTemplateReader(String name) {
InputStream is = null;
try {
is = this.getClass().getClassLoader().getResourceAsStream(getCPResourcePath(name));
if (is == null) {
is = new FileInputStream(new File(name)); // May throw but never return a null value
}
InputStream is = getInputStream(name);
return new InputStreamReader(is, StandardCharsets.UTF_8);
} catch (FileNotFoundException e) {
LOGGER.error(e.getMessage());
throw new RuntimeException("can't load template " + name);
}
}

private InputStream getInputStream(String name) throws FileNotFoundException {
InputStream is;
is = this.getClass().getClassLoader().getResourceAsStream(getCPResourcePath(name));
if (is == null) {
if (name == null || name.contains("..")) {
throw new IllegalArgumentException("Template location must be constrained to template directory.");
}
is = new FileInputStream(new File(name)); // May throw but never return a null value
}
return is;
}

/**
* Writes data to a compiled template
*
Expand All @@ -145,18 +158,32 @@ public Reader getTemplateReader(String name) {
*/
@Override
public File write(Map<String, Object> data, String template, File target) throws IOException {
String templateContent = this.engineAdapter.compileTemplate(this, data, template);
return writeToFile(target.getPath(), templateContent);
if (this.engineAdapter.handlesFile(template)) {
// Only pass files with valid endings through template engine
String templateContent = this.engineAdapter.compileTemplate(this, data, template);
return writeToFile(target.getPath(), templateContent);
} else {
// Do a straight copy of the file if not listed as supported by the template engine.
InputStream is;
try {
// look up the file using the same template resolution logic the adapters would use.
String fullTemplatePath = getFullTemplateFile(template);
is = getInputStream(fullTemplatePath);
} catch (TemplateNotFoundException ex) {
is = new FileInputStream(Paths.get(template).toFile());
}
return writeToFile(target.getAbsolutePath(), IOUtils.toByteArray(is));
}
}

@Override
public void ignore(Path path, String context) {
LOGGER.info("Ignored {} ({})", path.toString(), context);
LOGGER.info("Ignored {} ({})", path, context);
}

@Override
public void skip(Path path, String context) {
LOGGER.info("Skipped {} ({})", path.toString(), context);
LOGGER.info("Skipped {} ({})", path, context);
}

/**
Expand Down Expand Up @@ -189,23 +216,23 @@ public File writeToFile(String filename, byte[] contents) throws IOException {
try {
tempFile = writeToFileRaw(tempFilename, contents);
if (!filesEqual(tempFile, outputFile)) {
LOGGER.info("writing file " + filename);
LOGGER.info("writing file {}", filename);
Files.move(tempFile.toPath(), outputFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
tempFile = null;
} else {
LOGGER.info("skipping unchanged file " + filename);
LOGGER.info("skipping unchanged file {}", filename);
}
} finally {
if (tempFile != null && tempFile.exists()) {
try {
tempFile.delete();
Files.delete(tempFile.toPath());
} catch (Exception ex) {
LOGGER.error("Error removing temporary file " + tempFile, ex);
LOGGER.error("Error removing temporary file {}", tempFile, ex);
}
}
}
} else {
LOGGER.info("writing file " + filename);
LOGGER.info("writing file {}", filename);
outputFile = writeToFileRaw(filename, contents);
}

Expand All @@ -216,7 +243,7 @@ private File writeToFileRaw(String filename, byte[] contents) throws IOException
// Use Paths.get here to normalize path (for Windows file separator, space escaping on Linux/Mac, etc)
File output = Paths.get(filename).toFile();
if (this.options.isSkipOverwrite() && output.exists()) {
LOGGER.info("skip overwrite of file " + filename);
LOGGER.info("skip overwrite of file {}", filename);
return output;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
import java.util.ServiceLoader;

public class TemplatingEngineLoader {
private TemplatingEngineLoader() {
throw new IllegalStateException("Utility class");
}

@SuppressWarnings({"java:S112"}) // ignore java:S112 as generic RuntimeException is acceptable here
public static TemplatingEngineAdapter byIdentifier(String id) {
ServiceLoader<TemplatingEngineAdapter> loader = ServiceLoader.load(TemplatingEngineAdapter.class, TemplatingEngineLoader.class.getClassLoader());

Expand All @@ -37,7 +42,7 @@ public static TemplatingEngineAdapter byIdentifier(String id) {
// Attempt to load skipping SPI
return (TemplatingEngineAdapter) Class.forName(id).getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException(String.format(Locale.ROOT, "Couldn't load template engine adapter %s. Available options: \n%s", id, sb.toString()), e);
throw new RuntimeException(String.format(Locale.ROOT, "Couldn't load template engine adapter %s. Available options: %n%s", id, sb.toString()), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,35 +129,35 @@ public void processOpts() {
supportingFiles.add(new SupportingFile("storage_logs_gitignore", srcBasePath + File.separator + "storage" + File.separator + "framework" + File.separator + "views", ".gitignore"));
supportingFiles.add(new SupportingFile("storage_framework_cache_gitignore", srcBasePath + File.separator + "storage" + File.separator + "framework" + File.separator + "cache", ".gitignore"));
supportingFiles.add(new SupportingFile("storage_logs_gitignore", srcBasePath + File.separator + "storage" + File.separator + "framework" + File.separator + "cache" + File.separator + "data", ".gitignore"));
supportingFiles.add(new SupportingFile("artisan", srcBasePath, "artisan"));
supportingFiles.add(new SupportingFile("artisan.mustache", srcBasePath, "artisan"));
supportingFiles.add(new SupportingFile("composer.mustache", srcBasePath, "composer.json"));
supportingFiles.add(new SupportingFile("readme.md", srcBasePath, "readme.md"));
supportingFiles.add(new SupportingFile("User.php", srcBasePath + File.separator + "app", "User.php"));
supportingFiles.add(new SupportingFile("Kernel.php", srcBasePath + File.separator + "app" + File.separator + "Console", "Kernel.php"));
supportingFiles.add(new SupportingFile("User.php.mustache", srcBasePath + File.separator + "app", "User.php"));
supportingFiles.add(new SupportingFile("Kernel.php.mustache", srcBasePath + File.separator + "app" + File.separator + "Console", "Kernel.php"));
supportingFiles.add(new SupportingFile(".gitkeep", srcBasePath + File.separator + "app" + File.separator + "Console" + File.separator + "Commands", ".gitkeep"));
supportingFiles.add(new SupportingFile("Event.php", srcBasePath + File.separator + "app" + File.separator + "Events", "Event.php"));
supportingFiles.add(new SupportingFile("ExampleEvent.php", srcBasePath + File.separator + "app" + File.separator + "Events", "ExampleEvent.php"));
supportingFiles.add(new SupportingFile("Handler.php", srcBasePath + File.separator + "app" + File.separator + "Exceptions", "Handler.php"));
supportingFiles.add(new SupportingFile("Controller.php", srcBasePath + File.separator + "app" + File.separator + "Http" + File.separator + "Controllers" + File.separator, "Controller.php"));
supportingFiles.add(new SupportingFile("ExampleController.php", srcBasePath + File.separator + "app" + File.separator + "Http" + File.separator + "Controllers" + File.separator, "ExampleController.php"));
supportingFiles.add(new SupportingFile("Authenticate.php", srcBasePath + File.separator + "app" + File.separator + "Http" + File.separator + "Middleware" + File.separator, "Authenticate.php"));
supportingFiles.add(new SupportingFile("ExampleMiddleware.php", srcBasePath + File.separator + "app" + File.separator + "Http" + File.separator + "Middleware" + File.separator, "ExampleMiddleware.php"));
supportingFiles.add(new SupportingFile("ExampleJob.php", srcBasePath + File.separator + "app" + File.separator + "Jobs", "ExampleJob.php"));
supportingFiles.add(new SupportingFile("Job.php", srcBasePath + File.separator + "app" + File.separator + "Jobs", "Job.php"));
supportingFiles.add(new SupportingFile("ExampleListener.php", srcBasePath + File.separator + "app" + File.separator + "Listeners", "ExampleListener.php"));
supportingFiles.add(new SupportingFile("AppServiceProvider.php", srcBasePath + File.separator + "app" + File.separator + "Providers", "AppServiceProvider.php"));
supportingFiles.add(new SupportingFile("AuthServiceProvider.php", srcBasePath + File.separator + "app" + File.separator + "Providers", "AuthServiceProvider.php"));
supportingFiles.add(new SupportingFile("EventServiceProvider.php", srcBasePath + File.separator + "app" + File.separator + "Providers", "EventServiceProvider.php"));
supportingFiles.add(new SupportingFile("app.php", srcBasePath + File.separator + "bootstrap", "app.php"));
supportingFiles.add(new SupportingFile("ModelFactory.php", srcBasePath + File.separator + "database" + File.separator + "factories", "ModelFactory.php"));
supportingFiles.add(new SupportingFile("Event.php.mustache", srcBasePath + File.separator + "app" + File.separator + "Events", "Event.php"));
supportingFiles.add(new SupportingFile("ExampleEvent.php.mustache", srcBasePath + File.separator + "app" + File.separator + "Events", "ExampleEvent.php"));
supportingFiles.add(new SupportingFile("Handler.php.mustache", srcBasePath + File.separator + "app" + File.separator + "Exceptions", "Handler.php"));
supportingFiles.add(new SupportingFile("Controller.php.mustache", srcBasePath + File.separator + "app" + File.separator + "Http" + File.separator + "Controllers" + File.separator, "Controller.php"));
supportingFiles.add(new SupportingFile("ExampleController.php.mustache", srcBasePath + File.separator + "app" + File.separator + "Http" + File.separator + "Controllers" + File.separator, "ExampleController.php"));
supportingFiles.add(new SupportingFile("Authenticate.php.mustache", srcBasePath + File.separator + "app" + File.separator + "Http" + File.separator + "Middleware" + File.separator, "Authenticate.php"));
supportingFiles.add(new SupportingFile("ExampleMiddleware.php.mustache", srcBasePath + File.separator + "app" + File.separator + "Http" + File.separator + "Middleware" + File.separator, "ExampleMiddleware.php"));
supportingFiles.add(new SupportingFile("ExampleJob.php.mustache", srcBasePath + File.separator + "app" + File.separator + "Jobs", "ExampleJob.php"));
supportingFiles.add(new SupportingFile("Job.php.mustache", srcBasePath + File.separator + "app" + File.separator + "Jobs", "Job.php"));
supportingFiles.add(new SupportingFile("ExampleListener.php.mustache", srcBasePath + File.separator + "app" + File.separator + "Listeners", "ExampleListener.php"));
supportingFiles.add(new SupportingFile("AppServiceProvider.php.mustache", srcBasePath + File.separator + "app" + File.separator + "Providers", "AppServiceProvider.php"));
supportingFiles.add(new SupportingFile("AuthServiceProvider.php.mustache", srcBasePath + File.separator + "app" + File.separator + "Providers", "AuthServiceProvider.php"));
supportingFiles.add(new SupportingFile("EventServiceProvider.php.mustache", srcBasePath + File.separator + "app" + File.separator + "Providers", "EventServiceProvider.php"));
supportingFiles.add(new SupportingFile("app.php.mustache", srcBasePath + File.separator + "bootstrap", "app.php"));
supportingFiles.add(new SupportingFile("ModelFactory.php.mustache", srcBasePath + File.separator + "database" + File.separator + "factories", "ModelFactory.php"));
supportingFiles.add(new SupportingFile(".gitkeep", srcBasePath + File.separator + "database" + File.separator + "migrations", ".gitkeep"));
supportingFiles.add(new SupportingFile("DatabaseSeeder.php", srcBasePath + File.separator + "database" + File.separator + "seeds", "DatabaseSeeder.php"));
supportingFiles.add(new SupportingFile("DatabaseSeeder.php.mustache", srcBasePath + File.separator + "database" + File.separator + "seeds", "DatabaseSeeder.php"));
supportingFiles.add(new SupportingFile(".htaccess", srcBasePath + File.separator + "public", ".htaccess"));
supportingFiles.add(new SupportingFile("index.php", srcBasePath + File.separator + "public", "index.php"));
supportingFiles.add(new SupportingFile("index.php.mustache", srcBasePath + File.separator + "public", "index.php"));
supportingFiles.add(new SupportingFile(".gitkeep", srcBasePath + File.separator + "resources" + File.separator + "views", ".gitkeep"));
supportingFiles.add(new SupportingFile("routes.mustache", srcBasePath + File.separator + "routes", "web.php"));
supportingFiles.add(new SupportingFile("ExampleTest.php", srcBasePath + File.separator + "tests", "ExampleTest.php"));
supportingFiles.add(new SupportingFile("TestCase.php", srcBasePath + File.separator + "tests", "TestCase.php"));
supportingFiles.add(new SupportingFile("ExampleTest.php.mustache", srcBasePath + File.separator + "tests", "ExampleTest.php"));
supportingFiles.add(new SupportingFile("TestCase.php.mustache", srcBasePath + File.separator + "tests", "TestCase.php"));
supportingFiles.add(new SupportingFile("editorconfig", srcBasePath, ".editorconfig"));
supportingFiles.add(new SupportingFile("styleci", srcBasePath, ".styleci.yml"));
supportingFiles.add(new SupportingFile("phpunit.xml", srcBasePath, "phpunit.xml"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ protected void addSupportingFiles() {

@Override
public String modelDocFileFolder() {
return (outputFolder + File.separator + modelDocPath).replace('.', File.separatorChar);
// character replaces should _only_ occur on paths we define. Don't replace on outputFolder (which is supplied by the user and should always be considered correct)
return outputFolder + File.separator + modelDocPath.replace('.', File.separatorChar);
}

@Override
Expand Down
Loading

0 comments on commit a6d30ca

Please sign in to comment.