diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/AbstractSwaggerUiConfigProperties.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/AbstractSwaggerUiConfigProperties.java index b39a6c0cb..66c0ff7d0 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/AbstractSwaggerUiConfigProperties.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/AbstractSwaggerUiConfigProperties.java @@ -185,6 +185,29 @@ public abstract class AbstractSwaggerUiConfigProperties { */ protected Boolean withCredentials; + /** + * The Document title. + */ + protected String documentTitle; + + /** + * Gets document title. + * + * @return the document title + */ + public String getDocumentTitle() { + return documentTitle; + } + + /** + * Sets document title. + * + * @param documentTitle the document title + */ + public void setDocumentTitle(String documentTitle) { + this.documentTitle = documentTitle; + } + /** * Gets with credentials. * diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/ui/AbstractSwaggerIndexTransformer.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/ui/AbstractSwaggerIndexTransformer.java index 1f48e038e..845d8d207 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/ui/AbstractSwaggerIndexTransformer.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/ui/AbstractSwaggerIndexTransformer.java @@ -59,6 +59,11 @@ public class AbstractSwaggerIndexTransformer { */ private static final String PRESETS = "presets: ["; + /** + * The constant EDITOR_FOLD_MARKER. + */ + private static final String EDITOR_FOLD_MARKER = "//"; + /** * The Swagger ui o auth properties. */ @@ -178,6 +183,9 @@ else if (swaggerUiConfig.getCsrf().isUseSessionStorage()) html = setConfiguredApiDocsUrl(html); } + if (StringUtils.isNotEmpty(swaggerUiConfig.getDocumentTitle())) + html = addDocumentTitle(html); + return html; } @@ -325,4 +333,41 @@ protected String addSyntaxHighlight(String html) { return html.replace(PRESETS, stringBuilder.toString()); } + /** + * Add document title script. + * + * @param html the html + * @return the string + */ + protected String addDocumentTitle(String html) { + if (!html.contains(EDITOR_FOLD_MARKER)) { + return html; + } + StringBuilder stringBuilder = new StringBuilder("document.title = '"); + stringBuilder.append(escapeJavaScriptString(swaggerUiConfig.getDocumentTitle())); + stringBuilder.append("';\n\n "); + stringBuilder.append(EDITOR_FOLD_MARKER); + return html.replace(EDITOR_FOLD_MARKER, stringBuilder.toString()); + } + + /** + * Escape special characters for JavaScript string literal. + * + * @param input the input string + * @return the escaped string + */ + private String escapeJavaScriptString(String input) { + if (input == null) { + return ""; + } + return input + .replace("\\", "\\\\") + .replace("'", "\\'") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t") + .replace("<", "\\u003c") + .replace(">", "\\u003e"); + } + } diff --git a/springdoc-openapi-starter-common/src/test/java/org/springdoc/ui/AbstractSwaggerIndexTransformerTest.java b/springdoc-openapi-starter-common/src/test/java/org/springdoc/ui/AbstractSwaggerIndexTransformerTest.java index c103a063b..b5cb304e2 100644 --- a/springdoc-openapi-starter-common/src/test/java/org/springdoc/ui/AbstractSwaggerIndexTransformerTest.java +++ b/springdoc-openapi-starter-common/src/test/java/org/springdoc/ui/AbstractSwaggerIndexTransformerTest.java @@ -18,6 +18,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; @ExtendWith(MockitoExtension.class) class AbstractSwaggerIndexTransformerTest { @@ -67,4 +68,63 @@ void setApiDocUrlCorrectly() throws IOException { var html = underTest.defaultTransformations(new SwaggerUiConfigParameters(swaggerUiConfig), is); assertThat(html, containsString(apiDocUrl)); } + + @Test + void documentTitle_whenSet_addsDocumentTitleScript() throws IOException { + swaggerUiConfig.setDocumentTitle("My Custom API Documentation"); + InputStream inputStream = new ByteArrayInputStream(swaggerInitJs.getBytes(StandardCharsets.UTF_8)); + var html = underTest.defaultTransformations(new SwaggerUiConfigParameters(swaggerUiConfig), inputStream); + assertThat(html, containsString("document.title = 'My Custom API Documentation';")); + } + + @Test + void documentTitle_whenNotSet_doesNotAddScript() throws IOException { + swaggerUiConfig.setDocumentTitle(null); + InputStream inputStream = new ByteArrayInputStream(swaggerInitJs.getBytes(StandardCharsets.UTF_8)); + var html = underTest.defaultTransformations(new SwaggerUiConfigParameters(swaggerUiConfig), inputStream); + assertThat(html, not(containsString("document.title"))); + } + + @Test + void documentTitle_whenEmpty_doesNotAddScript() throws IOException { + swaggerUiConfig.setDocumentTitle(""); + InputStream inputStream = new ByteArrayInputStream(swaggerInitJs.getBytes(StandardCharsets.UTF_8)); + var html = underTest.defaultTransformations(new SwaggerUiConfigParameters(swaggerUiConfig), inputStream); + assertThat(html, not(containsString("document.title"))); + } + + @Test + void documentTitle_escapesSpecialCharacters() throws IOException { + swaggerUiConfig.setDocumentTitle("Test's API \\ Documentation"); + InputStream inputStream = new ByteArrayInputStream(swaggerInitJs.getBytes(StandardCharsets.UTF_8)); + var html = underTest.defaultTransformations(new SwaggerUiConfigParameters(swaggerUiConfig), inputStream); + assertThat(html, containsString("document.title = 'Test\\'s API \\\\ Documentation';")); + } + + @Test + void documentTitle_escapesNewlines() throws IOException { + swaggerUiConfig.setDocumentTitle("Test\nAPI\rDocs\tTitle"); + InputStream inputStream = new ByteArrayInputStream(swaggerInitJs.getBytes(StandardCharsets.UTF_8)); + var html = underTest.defaultTransformations(new SwaggerUiConfigParameters(swaggerUiConfig), inputStream); + assertThat(html, containsString("document.title = 'Test\\nAPI\\rDocs\\tTitle';")); + } + + @Test + void documentTitle_escapesScriptTags() throws IOException { + swaggerUiConfig.setDocumentTitle(""); + InputStream inputStream = new ByteArrayInputStream(swaggerInitJs.getBytes(StandardCharsets.UTF_8)); + var html = underTest.defaultTransformations(new SwaggerUiConfigParameters(swaggerUiConfig), inputStream); + assertThat(html, not(containsString("