Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
102f729
fix issues with incorrectly escaped values and implement CR feedback
Picazsoo Apr 27, 2026
cceab40
update samples
Picazsoo Apr 27, 2026
e14fdba
Merge branch 'master' into feature/fix-values3
Picazsoo Apr 27, 2026
78ceed8
update samples with worst-case-scenario swagger file
Picazsoo Apr 27, 2026
67ee557
fix: update parameter tables to ensure consistent formatting across A…
Picazsoo Apr 27, 2026
7407e6e
Merge branch 'master' into feature/fix-values3
Picazsoo Apr 28, 2026
b8bc349
add missing unit test
Picazsoo Apr 28, 2026
10b6ab9
migrate the two orphaned tests (gradleWrapperIsGenerated, polymorphic…
Picazsoo Apr 28, 2026
8c51a1c
extract regex to reusable Pattern
Picazsoo Apr 28, 2026
8488e6b
Revert "extract regex to reusable Pattern"
Picazsoo Apr 29, 2026
bedfa3b
Reapply "extract regex to reusable Pattern"
Picazsoo Apr 29, 2026
bbc17b7
Merge branch 'master' into feature/fix-values3
Picazsoo Apr 30, 2026
a1e25d8
Merge branch 'master' into feature/fix-values3
Picazsoo May 15, 2026
35a1bf8
update samples
Picazsoo May 15, 2026
0479f69
Merge branch 'master' into feature/fix-values3
Picazsoo May 18, 2026
d168c14
fix comma after new line and regenerate samples
Picazsoo May 18, 2026
37102e1
Merge branch 'master' into feature/fix-values3
Picazsoo May 24, 2026
85fe8d2
Merge remote-tracking branch 'origin/master' into feature/fix-values3
Picazsoo May 30, 2026
eab153c
fix: prevent code injection via triple-quoted strings...
Picazsoo Jun 5, 2026
66ae0e4
add test spec with injection
Picazsoo Jun 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion bin/configs/kotlin-misk-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
generatorName: kotlin-misk
outputDir: samples/server/petstore/kotlin-misk-config
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
inputSpec: modules/openapi-generator/src/test/resources/3_0/kotlin/petstore-kotlin-misk.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-misk
validateSpec: false
useBeanValidation: true
Expand Down
2 changes: 1 addition & 1 deletion bin/configs/kotlin-misk.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
generatorName: kotlin-misk
outputDir: samples/server/petstore/kotlin-misk
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
inputSpec: modules/openapi-generator/src/test/resources/3_0/kotlin/petstore-kotlin-misk.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-misk
additionalProperties:
hideGenerationTimestamp: "true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class CodegenOperation {
hasErrorResponseObject; // if 4xx, 5xx responses have at least one error object defined
public CodegenProperty returnProperty;
public String path, operationId, returnType, returnFormat, httpMethod, returnBaseType,
returnContainer, summary, unescapedNotes, notes, baseName, defaultResponse;
returnContainer, summary, unescapedSummary, unescapedNotes, notes, baseName, defaultResponse;
public CodegenDiscriminator discriminator;
public List<Map<String, String>> consumes, produces, prioritizedContentTypes;
public List<CodegenServer> servers = new ArrayList<CodegenServer>();
Expand Down Expand Up @@ -429,6 +429,7 @@ public String toString() {
sb.append(", returnBaseType='").append(returnBaseType).append('\'');
sb.append(", returnContainer='").append(returnContainer).append('\'');
sb.append(", summary='").append(summary).append('\'');
sb.append(", unescapedSummary='").append(unescapedSummary).append('\'');
sb.append(", unescapedNotes='").append(unescapedNotes).append('\'');
sb.append(", notes='").append(notes).append('\'');
sb.append(", baseName='").append(baseName).append('\'');
Expand Down Expand Up @@ -502,6 +503,7 @@ public boolean equals(Object o) {
Objects.equals(returnBaseType, that.returnBaseType) &&
Objects.equals(returnContainer, that.returnContainer) &&
Objects.equals(summary, that.summary) &&
Objects.equals(unescapedSummary, that.unescapedSummary) &&
Objects.equals(unescapedNotes, that.unescapedNotes) &&
Objects.equals(notes, that.notes) &&
Objects.equals(baseName, that.baseName) &&
Expand Down Expand Up @@ -547,7 +549,7 @@ public int hashCode() {
returnTypeIsPrimitive, returnSimpleType, subresourceOperation, isMap,
isArray, isMultipart, isVoid, isResponseBinary, isResponseFile, isResponseOptional, hasReference,
isDeprecated, isCallbackRequest, uniqueItems, path, operationId, returnType, httpMethod,
returnBaseType, returnContainer, summary, unescapedNotes, notes, baseName, defaultResponse,
returnBaseType, returnContainer, summary, unescapedSummary, unescapedNotes, notes, baseName, defaultResponse,
discriminator, consumes, produces, prioritizedContentTypes, servers, bodyParam, allParams, bodyParams,
pathParams, queryParams, headerParams, formParams, cookieParams, requiredParams, returnProperty, optionalParams,
authMethods, tags, responses, callbacks, imports, examples, requestBodyExamples, externalDocs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class CodegenParameter implements IJsonSchemaValidationProperties {
isCollectionFormatMulti, isPrimitiveType, isModel, isExplode, isDeepObject, isMatrix, isAllowEmptyValue,
isFormStyle, isSpaceDelimited, isPipeDelimited;
public String baseName, paramName, dataType, datatypeWithEnum, dataFormat, contentType,
collectionFormat, description, unescapedDescription, baseType, defaultValue, enumDefaultValue, enumName, style;
collectionFormat, description, unescapedDescription, baseType, defaultValue, unescapedDefaultValue, enumDefaultValue, enumName, style;

public String nameInLowerCase; // property name in lower case
public String nameInCamelCase; // property name in camel case (e.g. modifiedDate)
Expand Down Expand Up @@ -182,6 +182,7 @@ public CodegenParameter copy() {
output.multipleOf = this.multipleOf;
output.jsonSchema = this.jsonSchema;
output.defaultValue = this.defaultValue;
output.unescapedDefaultValue = this.unescapedDefaultValue;
output.enumDefaultValue = this.enumDefaultValue;
output.example = this.example;
output.examples = this.examples;
Expand Down Expand Up @@ -289,7 +290,7 @@ public int hashCode() {
return Objects.hash(isFormParam, isQueryParam, isPathParam, isHeaderParam, isCookieParam,
isBodyParam, isContainer, isCollectionFormatMulti, isPrimitiveType, isModel, isExplode, baseName,
paramName, dataType, datatypeWithEnum, dataFormat, collectionFormat, description,
unescapedDescription, baseType, containerType, containerTypeMapped, defaultValue,
unescapedDescription, baseType, containerType, containerTypeMapped, defaultValue, unescapedDefaultValue,
enumDefaultValue, enumName, style, isDeepObject, isMatrix, isAllowEmptyValue, example, examples,
isFormStyle, isSpaceDelimited, isPipeDelimited,
jsonSchema, isString, isNumeric, isInteger, isLong, isNumber, isFloat, isDouble, isDecimal,
Expand Down Expand Up @@ -382,6 +383,7 @@ public boolean equals(Object o) {
Objects.equals(containerType, that.containerType) &&
Objects.equals(containerTypeMapped, that.containerTypeMapped) &&
Objects.equals(defaultValue, that.defaultValue) &&
Objects.equals(unescapedDefaultValue, that.unescapedDefaultValue) &&
Objects.equals(enumDefaultValue, that.enumDefaultValue) &&
Objects.equals(enumName, that.enumName) &&
Objects.equals(style, that.style) &&
Expand Down Expand Up @@ -450,6 +452,7 @@ public String toString() {
sb.append(", containerType='").append(containerType).append('\'');
sb.append(", containerTypeMapped='").append(containerTypeMapped).append('\'');
sb.append(", defaultValue='").append(defaultValue).append('\'');
sb.append(", unescapedDefaultValue='").append(unescapedDefaultValue).append('\'');
sb.append(", enumDefaultValue='").append(enumDefaultValue).append('\'');
sb.append(", enumName='").append(enumName).append('\'');
sb.append(", style='").append(style).append('\'');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ public class CodegenProperty implements Cloneable, IJsonSchemaValidationProperti
@Getter @Setter
public String unescapedDescription;

/**
* The default value string without escape characters; use this in string literal contexts
* (e.g. inside "..." or """...""") together with an appropriate escaping lambda.
* Unlike {@code defaultValue}, this is never passed through escapeText() or escapeUnsafeCharacters().
*/
@Getter @Setter
public String unescapedDefaultValue;

/**
* maxLength validation for strings, see http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.2.1
*/
Expand Down Expand Up @@ -992,6 +1000,7 @@ public String toString() {
sb.append(", containerTypeMapped='").append(containerTypeMapped).append('\'');
sb.append(", title='").append(title).append('\'');
sb.append(", unescapedDescription='").append(unescapedDescription).append('\'');
sb.append(", unescapedDefaultValue='").append(unescapedDefaultValue).append('\'');
sb.append(", maxLength=").append(maxLength);
sb.append(", minLength=").append(minLength);
sb.append(", pattern='").append(pattern).append('\'');
Expand Down Expand Up @@ -1179,6 +1188,7 @@ public boolean equals(Object o) {
Objects.equals(containerTypeMapped, that.containerTypeMapped) &&
Objects.equals(title, that.title) &&
Objects.equals(unescapedDescription, that.unescapedDescription) &&
Objects.equals(unescapedDefaultValue, that.unescapedDefaultValue) &&
Objects.equals(maxLength, that.maxLength) &&
Objects.equals(minLength, that.minLength) &&
Objects.equals(pattern, that.pattern) &&
Expand Down Expand Up @@ -1212,7 +1222,7 @@ public int hashCode() {

return Objects.hash(openApiType, baseName, complexType, getter, setter, description,
dataType, datatypeWithEnum, dataFormat, name, min, max, defaultValue,
defaultValueWithParam, baseType, containerType, containerTypeMapped, title, unescapedDescription,
defaultValueWithParam, baseType, containerType, containerTypeMapped, title, unescapedDescription, unescapedDefaultValue,
maxLength, minLength, pattern, example, jsonSchema, minimum, maximum,
exclusiveMinimum, exclusiveMaximum, required, deprecated,
isPrimitiveType, isModel, isContainer, isString, isNumeric,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class CodegenResponse implements IJsonSchemaValidationProperties {
public boolean is4xx;
public boolean is5xx;
public String message;
public String unescapedMessage;
public List<Map<String, Object>> examples;
public String dataType;
public String baseType;
Expand Down Expand Up @@ -109,7 +110,7 @@ public class CodegenResponse implements IJsonSchemaValidationProperties {

@Override
public int hashCode() {
return Objects.hash(headers, code, message, examples, dataType, baseType, containerType, containerTypeMapped, hasHeaders,
return Objects.hash(headers, code, message, unescapedMessage, examples, dataType, baseType, containerType, containerTypeMapped, hasHeaders,
isString, isNumeric, isInteger, isLong, isNumber, isFloat, isDouble, isDecimal, isByteArray, isBoolean, isDate,
isDateTime, isUuid, isEmail, isPassword, isModel, isFreeFormObject, isAnyType, isDefault, simpleType, primitiveType,
isMap, isOptional, isArray, isBinary, isFile, schema, jsonSchema, vendorExtensions, items, additionalProperties,
Expand Down Expand Up @@ -182,6 +183,7 @@ public boolean equals(Object o) {
Objects.equals(headers, that.headers) &&
Objects.equals(code, that.code) &&
Objects.equals(message, that.message) &&
Objects.equals(unescapedMessage, that.unescapedMessage) &&
Objects.equals(examples, that.examples) &&
Objects.equals(dataType, that.dataType) &&
Objects.equals(baseType, that.baseType) &&
Expand Down Expand Up @@ -582,6 +584,7 @@ public String toString() {
sb.append(", is4xx='").append(is4xx).append('\'');
sb.append(", is5xx='").append(is5xx).append('\'');
sb.append(", message='").append(message).append('\'');
sb.append(", unescapedMessage='").append(unescapedMessage).append('\'');
sb.append(", examples=").append(examples);
sb.append(", dataType='").append(dataType).append('\'');
sb.append(", baseType='").append(baseType).append('\'');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,24 @@ protected ImmutableMap.Builder<String, Lambda> addMustacheLambdas() {
.put("trimLineBreaks", new TrimLineBreaksLambda())
.put("trimWhitespace", new TrimWhitespaceLambda())
.put("trimTrailingWithNewLine", new TrimTrailingWhiteSpaceLambda(true))
.put("trimTrailing", new TrimTrailingWhiteSpaceLambda(false));
.put("trimTrailing", new TrimTrailingWhiteSpaceLambda(false))
// Escapes text for use inside a Markdown table cell or inline text.
// Order matters: \\ and & must be first to avoid double-escaping.
// $ is escaped to prevent LaTeX math mode ($...$) in renderers that support it.
.put("escapeMarkdown", (fragment, writer) -> writer.write(fragment.execute()
.replace("\\", "\\\\")
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("$", "\\$")
.replace("|", "\\|")
.replace("\r\n", " ")
.replace("\n", " ")
.replace("\r", " ")))
.put("collapseNewlines", (fragment, writer) -> writer.write(fragment.execute()
.replace("\r\n", " ")
.replace("\n", " ")
.replace("\r", " ")));
}

private void registerMustacheLambdas() {
Expand Down Expand Up @@ -4316,6 +4333,7 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo

// set the default value
property.defaultValue = toDefaultValue(property, p);
property.unescapedDefaultValue = p.getDefault() != null ? String.valueOf(p.getDefault()) : null;
property.defaultValueWithParam = toDefaultValueWithParam(name, p);

LOGGER.debug("debugging from property return: {}", property);
Expand Down Expand Up @@ -4739,6 +4757,7 @@ public CodegenOperation fromOperation(String path,
}

op.summary = escapeText(operation.getSummary());
op.unescapedSummary = operation.getSummary();
op.unescapedNotes = operation.getDescription();
op.notes = escapeText(operation.getDescription());
op.hasConsumes = false;
Expand Down Expand Up @@ -5093,6 +5112,7 @@ public CodegenResponse fromResponse(String responseCode, ApiResponse response) {
}
r.schema = responseSchema;
r.message = escapeText(response.getDescription());
r.unescapedMessage = response.getDescription();

// adding examples to API responses
Map<String, Example> examples = ExamplesUtils.getExamplesFromResponse(openAPI, response);
Expand Down Expand Up @@ -5642,6 +5662,7 @@ public CodegenParameter fromParameter(Parameter parameter, Set<String> imports)

// set default value
codegenParameter.defaultValue = toDefaultParameterValue(codegenProperty, parameterSchema);
codegenParameter.unescapedDefaultValue = parameterSchema.getDefault() != null ? String.valueOf(parameterSchema.getDefault()) : null;

finishUpdatingParameter(codegenParameter, parameter);
return codegenParameter;
Expand Down Expand Up @@ -7578,6 +7599,7 @@ public CodegenParameter fromFormProperty(String name, Schema propertySchema, Set

// set default value
codegenParameter.defaultValue = toDefaultParameterValue(codegenProperty, propertySchema);
codegenParameter.unescapedDefaultValue = propertySchema.getDefault() != null ? String.valueOf(propertySchema.getDefault()) : null;

if (ModelUtils.isFileSchema(ps) && !ModelUtils.isStringSchema(ps)) {
// swagger v2 only, type file
Expand Down Expand Up @@ -8124,6 +8146,7 @@ public CodegenParameter fromRequestBody(RequestBody body, Set<String> imports, S
codegenParameter.baseName = "UNKNOWN_BASE_NAME";
codegenParameter.paramName = "UNKNOWN_PARAM_NAME";
codegenParameter.description = escapeText(body.getDescription());
codegenParameter.unescapedDescription = body.getDescription();
codegenParameter.required = body.getRequired() != null ? body.getRequired() : Boolean.FALSE;
codegenParameter.isBodyParam = Boolean.TRUE;
if (body.getExtensions() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1316,7 +1316,20 @@ protected void updateModelForObject(CodegenModel m, Schema schema) {
@Override
protected ImmutableMap.Builder<String, Mustache.Lambda> addMustacheLambdas() {
return super.addMustacheLambdas()
.put("escapeDollar", new EscapeChar("(?<!\\\\)\\$", "\\\\\\$"));
.put("escapeDollar", new EscapeChar("(?<!\\\\)\\$", "\\\\\\$"))
// Replaces each $ with ${'$'} for use inside """...""" triple-quoted Kotlin strings,
// where backslash escapes are not available.
.put("escapeDollarInMultiline", new EscapeChar("\\$", "\\${'\\$'}"))
// Full escaping for raw values going into "..." double-quoted Kotlin strings.
// Handles \, $, " and also literal newline/tab/carriage-return chars.
// \\ replacement must be first to avoid double-escaping subsequent insertions.
.put("escapeInNormalString", (fragment, writer) -> writer.write(fragment.execute()
.replace("\\", "\\\\")
.replace("$", "\\$")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t")));
}

protected interface DataTypeAssigner {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -115,6 +116,7 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
public static final String COMPANION_OBJECT = "companionObject";

protected static final String VENDOR_EXTENSION_BASE_NAME_LITERAL = "x-base-name-literal";
private static final Pattern RETROFIT_PATH_PARAM_NAME_PATTERN = Pattern.compile("[a-zA-Z][a-zA-Z0-9_-]*");


@Setter protected String dateLibrary = DateLibrary.JAVA8.value;
Expand Down Expand Up @@ -1062,6 +1064,26 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
operation.path = operation.path.substring(1);
}

// For jvm-retrofit2, Retrofit @Path names must match [a-zA-Z][a-zA-Z0-9_-]*.
// When the OpenAPI baseName is invalid (e.g., starts with '$'), replace the
// URL placeholder with the sanitized paramName and mark via vendor extension.
if (JVM_RETROFIT2.equals(getLibrary())) {
for (CodegenParameter param : operation.allParams) {
if (param.isPathParam) {
boolean validRetrofitName = RETROFIT_PATH_PARAM_NAME_PATTERN.matcher(param.baseName).matches();
if (validRetrofitName) {
param.vendorExtensions.put("x-retrofit-path-name", param.baseName);
} else {
param.vendorExtensions.put("x-retrofit-path-name", param.paramName);
operation.path = operation.path.replace(
"{" + param.baseName + "}",
"{" + param.paramName + "}"
);
}
}
}
}

if (JVM_OKHTTP.equals(getLibrary()) || JVM_OKHTTP4.equals(getLibrary())) {
// Ideally we would do content negotiation to choose the best mediatype, but that would be a next step.
// For now we take the first mediatype we can parse and send that.
Expand Down
Loading