[core] Templating: limit compilation to supported extensions and constrain target paths (#6598)

This commit is contained in:
Jim Schubert 2020-09-02 15:52:35 -04:00 committed by GitHub
parent 91ea6a17d9
commit a6d30cac9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 225 additions and 64 deletions

View File

@ -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
*
@ -65,9 +75,10 @@ public interface TemplatingEngineAdapter {
* @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);

View File

@ -913,6 +913,11 @@ public class DefaultGenerator implements Generator {
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));

View File

@ -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;
@ -12,14 +13,8 @@ import org.slf4j.LoggerFactory;
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;
/**
@ -59,6 +54,10 @@ public class TemplateManager implements TemplatingExecutor, TemplateProcessor {
throw new TemplateNotFoundException(name);
}
if (name == null || name.contains("..")) {
throw new IllegalArgumentException("Template location must be constrained to template directory.");
}
return template;
}
@ -104,7 +103,12 @@ public class TemplateManager implements TemplatingExecutor, TemplateProcessor {
* @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) {
@ -118,15 +122,12 @@ public class TemplateManager implements TemplatingExecutor, TemplateProcessor {
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());
@ -134,6 +135,18 @@ public class TemplateManager implements TemplatingExecutor, TemplateProcessor {
}
}
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
*
@ -145,18 +158,32 @@ public class TemplateManager implements TemplatingExecutor, TemplateProcessor {
*/
@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);
}
/**
@ -189,23 +216,23 @@ public class TemplateManager implements TemplatingExecutor, TemplateProcessor {
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);
}
@ -216,7 +243,7 @@ public class TemplateManager implements TemplatingExecutor, TemplateProcessor {
// 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;
}

View File

@ -22,6 +22,11 @@ import java.util.Locale;
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());
@ -37,7 +42,7 @@ public class TemplatingEngineLoader {
// 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);
}
}
}

View File

@ -129,35 +129,35 @@ public class PhpLumenServerCodegen extends AbstractPhpCodegen {
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"));

View File

@ -236,7 +236,8 @@ public class PythonBluePlanetServerCodegen extends AbstractPythonConnexionServer
@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

View File

@ -35,13 +35,17 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
public class HandlebarsEngineAdapter extends AbstractTemplatingEngineAdapter {
static Logger LOGGER = LoggerFactory.getLogger(HandlebarsEngineAdapter.class);
static final Logger LOGGER = LoggerFactory.getLogger(HandlebarsEngineAdapter.class);
private final String[] extensions = new String[]{"handlebars", "hbs"};
// We use this as a simple lookup for valid file name extensions. This adapter will inspect .mustache (built-in) and infer the relevant handlebars filename
private final String[] canCompileFromExtensions = new String[]{".handlebars",".hbs",".mustache"};
/**
* Provides an identifier used to load the adapter. This could be a name, uuid, or any other string.
*
@ -71,7 +75,7 @@ public class HandlebarsEngineAdapter extends AbstractTemplatingEngineAdapter {
Handlebars handlebars = new Handlebars(loader);
handlebars.registerHelperMissing((obj, options) -> {
LOGGER.warn(String.format(Locale.ROOT, "Unregistered helper name '%s', processing template:\n%s", options.helperName, options.fn.text()));
LOGGER.warn(String.format(Locale.ROOT, "Unregistered helper name '%s', processing template:%n%s", options.helperName, options.fn.text()));
return "";
});
handlebars.registerHelper("json", Jackson2Helper.INSTANCE);
@ -82,6 +86,7 @@ public class HandlebarsEngineAdapter extends AbstractTemplatingEngineAdapter {
return tmpl.apply(context);
}
@SuppressWarnings({"java:S108"})
public TemplateSource findTemplate(TemplatingExecutor generator, String templateFile) {
String[] possibilities = getModifiedFileLocation(templateFile);
for (String file : possibilities) {
@ -104,5 +109,17 @@ public class HandlebarsEngineAdapter extends AbstractTemplatingEngineAdapter {
public String[] getFileExtensions() {
return extensions;
}
/**
* 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.
*/
@Override
public boolean handlesFile(String filename) {
// disallow any extension-only files like ".hbs" or ".mustache", and only consider a file compilable if it's handlebars or mustache (from which we later infer the handlebars filename)
return Arrays.stream(canCompileFromExtensions).anyMatch(suffix -> !suffix.equalsIgnoreCase(filename) && filename.endsWith(suffix));
}
}

View File

@ -39,7 +39,7 @@ public class MustacheEngineAdapter implements TemplatingEngineAdapter {
return "mustache";
}
public String[] extensions = new String[]{"mustache"};
private final String[] extensions = new String[]{"mustache"};
Mustache.Compiler compiler = Mustache.compiler();
/**
@ -61,6 +61,7 @@ public class MustacheEngineAdapter implements TemplatingEngineAdapter {
return tmpl.execute(bundle);
}
@SuppressWarnings({"java:S108"}) // catch-all is expected, and is later thrown
public Reader findTemplate(TemplatingExecutor generator, String name) {
for (String extension : extensions) {
try {
@ -69,12 +70,6 @@ public class MustacheEngineAdapter implements TemplatingEngineAdapter {
}
}
// support files without targeted extension (e.g. .gitignore, README.md), etc.
try {
return new StringReader(generator.getFullTemplateContents(name));
} catch (Exception ignored) {
}
throw new TemplateNotFoundException(name);
}

View File

@ -113,7 +113,7 @@ impl Request {
let mut path = self.path;
for (k, v) in self.path_params {
// replace {id} with the value of the id path param
{{=<% %>=}}path = path.replace(&format!("{{{}}}", k), &v);<%={{ }}=%>
path = path.replace(&format!("{{{}}}", k), &v);
}
for (k, v) in self.header_params {

View File

@ -47,6 +47,15 @@ public class TemplateManagerTest {
assertEquals(manager.getFullTemplateContents("simple.mustache"), "{{name}} and {{age}}");
}
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Template location must be constrained to template directory\\.")
public void loadTemplateContentsThrowsForEscapingTemplates(){
TemplateManagerOptions opts = new TemplateManagerOptions(false,false);
TemplateManager manager = new TemplateManager(opts, mustacheEngineAdapter, new TemplatePathLocator[]{ locator });
manager.getFullTemplateContents("../simple.mustache");
fail("Expected an exception that did not occur");
}
@Test
public void readTemplate(){
TemplateManagerOptions opts = new TemplateManagerOptions(false,false);
@ -71,6 +80,15 @@ public class TemplateManagerTest {
assertTrue(manager.getTemplateReader("templating/templates/simple.mustache") instanceof InputStreamReader);
}
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Template location must be constrained to template directory\\.")
public void getTemplateReaderThrowsForEscapingTemplates(){
TemplateManagerOptions opts = new TemplateManagerOptions(false,false);
TemplateManager manager = new TemplateManager(opts, mustacheEngineAdapter, new TemplatePathLocator[]{ locator });
manager.getTemplateReader("../templating/templates/simple.mustache");
fail("Expected an exception that did not occur");
}
@Test
public void writeViaMustacheAdapter() throws IOException {
TemplateManagerOptions opts = new TemplateManagerOptions(false,false);
@ -91,6 +109,28 @@ public class TemplateManagerTest {
}
}
@Test
public void writeUsingMustacheAdapterSkipsNonMustache() throws IOException {
TemplateManagerOptions opts = new TemplateManagerOptions(false,false);
TemplateManager manager = new TemplateManager(opts, mustacheEngineAdapter, new TemplatePathLocator[]{ locator });
Map<String, Object> data = new HashMap<>();
data.put("this", "this");
data.put("that", "1234");
Path target = Files.createTempDirectory("test-templatemanager");
try {
File output = new File(target.toFile(), ".gitignore");
File written = manager.write(data, ".gitignore", output);
assertEquals(Files.readAllLines(written.toPath()).get(0), "# Should not escape {{this}} or that: {{{that}}}");
output = new File(target.toFile(), "README.md");
written = manager.write(data, "README.md", output);
assertEquals(Files.readAllLines(written.toPath()).get(0), "This should not escape `{{this}}` or `{{{that}}}` or `{{name}} counts{{#each numbers}} {{.}}{{/each}}`");
} finally {
target.toFile().delete();
}
}
@Test
public void skipOverwriteViaOption() throws IOException {
TemplateManagerOptions opts = new TemplateManagerOptions(false,true);
@ -186,4 +226,26 @@ public class TemplateManagerTest {
target.toFile().delete();
}
}
@Test
public void writeUsingHandlebarsAdapterSkipsNonHandlebars() throws IOException {
TemplateManagerOptions opts = new TemplateManagerOptions(false,false);
TemplateManager manager = new TemplateManager(opts, handlebarsEngineAdapter, new TemplatePathLocator[]{ locator });
Map<String, Object> data = new HashMap<>();
data.put("this", "this");
data.put("that", "1234");
Path target = Files.createTempDirectory("test-templatemanager");
try {
File output = new File(target.toFile(), ".gitignore");
File written = manager.write(data, ".gitignore", output);
assertEquals(Files.readAllLines(written.toPath()).get(0), "# Should not escape {{this}} or that: {{{that}}}");
output = new File(target.toFile(), "README.md");
written = manager.write(data, "README.md", output);
assertEquals(Files.readAllLines(written.toPath()).get(0), "This should not escape `{{this}}` or `{{{that}}}` or `{{name}} counts{{#each numbers}} {{.}}{{/each}}`");
} finally {
target.toFile().delete();
}
}
}

View File

@ -0,0 +1,36 @@
package org.openapitools.codegen.templating;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
public class HandlebarsEngineAdapterTest {
@Test(dataProvider = "handlesFileExpectations")
public void checkHandleFiles(String input, boolean shouldHandle, String message) {
HandlebarsEngineAdapter adapter = new HandlebarsEngineAdapter();
boolean handles = adapter.handlesFile(input);
assertEquals(handles, shouldHandle, message);
}
@DataProvider(name = "handlesFileExpectations")
public Object[][] handlesFileExpectations() {
// input, shouldHandle, message
return new Object[][]{
{"api.handlebars", true, "Expected to support handlebars extension"},
{"api.hbs", true, "Expected to support hbs extension"},
{"model.handlebars", true, "Expected to support handlebars extension"},
{"model.hbs", true, "Expected to support hbs extension"},
{"libraries/some/api.handlebars", true, "Expected to support handlebars extension for libraries"},
{"libraries/some/api.hbs", true, "Expected to support hbs extension for libraries"},
{"api.mustache", true, "Expected to support inferring handlebars extension from a mustache input"},
{"model.mustache", true, "Expected to support inferring handlebars extension from a mustache input"},
{"libraries/some/api.mustache", true, "Expected to support inferring handlebars extension from a mustache input for libraries"},
{"libraries/some/model.mustache", true, "Expected to support inferring handlebars extension from a mustache input for libraries"},
{".hbs", false, "Should not consider .hbs a valid file to process"},
{".handlebars", false, "Should not consider .handlebars a valid file to process"},
{".gitignore", false, "Should not attempt to handle .gitignore"},
{"README.md", false, "Should not attempt to handle non-handlebars extensions (other than mustache)"}
};
}
}

View File

@ -0,0 +1 @@
# Should not escape {{this}} or that: {{{that}}}

View File

@ -0,0 +1 @@
This should not escape `{{this}}` or `{{{that}}}` or `{{name}} counts{{#each numbers}} {{.}}{{/each}}`

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="">
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@ -68,12 +68,12 @@
@if (Route::has('login'))
<div class="top-right links">
@auth
<a href="">Home</a>
<a href="{{ url('/home') }}">Home</a>
@else
<a href="">Login</a>
<a href="{{ route('login') }}">Login</a>
@if (Route::has('register'))
<a href="">Register</a>
<a href="{{ route('register') }}">Register</a>
@endif
@endauth
</div>