Skip to content

Commit c25ee87

Browse files
committed
feat: add improvements from maintainers
1 parent a8db90f commit c25ee87

File tree

5 files changed

+174
-125
lines changed

5 files changed

+174
-125
lines changed

core/src/main/java/com/linecorp/armeria/server/docs/DocService.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,7 @@ private DocService(ExampleSupport exampleSupport,
164164
}
165165

166166
private DocService(SpecificationLoader specificationLoader,
167-
List<BiFunction<ServiceRequestContext, HttpRequest, String>> injectedScriptSuppliers
168-
) {
167+
List<BiFunction<ServiceRequestContext, HttpRequest, String>> injectedScriptSuppliers) {
169168
super(FileService.builder(new DocServiceVfs(specificationLoader))
170169
.serveCompressedFiles(true)
171170
.autoDecompress(true)
@@ -269,7 +268,7 @@ static final class SpecificationLoader {
269268
this.exampleSupport = exampleSupport;
270269
this.filter = filter;
271270
this.descriptiveTypeInfoProvider = composeDescriptiveTypeInfoProvider(descriptiveTypeInfoProvider);
272-
this.docServiceExtraInfo = requireNonNull(docServiceExtraInfo,"docServiceExtraInfo");
271+
this.docServiceExtraInfo = requireNonNull(docServiceExtraInfo, "docServiceExtraInfo");
273272
}
274273

275274
boolean contains(String path) {

core/src/main/java/com/linecorp/armeria/server/docs/DocServiceBuilder.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.util.List;
2525
import java.util.Map;
2626
import java.util.function.BiFunction;
27-
import java.util.regex.Pattern;
2827

2928
import com.google.common.collect.ArrayListMultimap;
3029
import com.google.common.collect.ImmutableList;
@@ -561,14 +560,11 @@ private static String[] guessAndSerializeExampleRequest(Object exampleRequest) {
561560
public DocServiceBuilder webAppTitle(String webAppTitle) {
562561
final String webAppTitleKey = "webAppTitle";
563562
final Integer webAppTitleMaxSize = 50;
564-
final String webAppTitlePattern = "<[^>]*>";
565563
requireNonNull(webAppTitle, webAppTitleKey);
566564
checkArgument(!webAppTitle.trim().isEmpty(), "%s is empty.", webAppTitleKey);
567565
checkArgument(webAppTitle.length() <= webAppTitleMaxSize,
568566
"%s length exceeds %s.", webAppTitleKey, webAppTitleMaxSize);
569-
final String webAppTitlePatternSanitized = Pattern.compile(webAppTitlePattern).matcher(webAppTitle)
570-
.replaceAll("").trim();
571-
docServiceExtraInfo.putIfAbsent(webAppTitleKey, webAppTitlePatternSanitized);
567+
docServiceExtraInfo.putIfAbsent(webAppTitleKey, webAppTitle);
572568
return this;
573569
}
574570

core/src/main/java/com/linecorp/armeria/server/docs/DocServiceInjectedScriptsUtil.java

Lines changed: 94 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
2-
* Copyright 2025 LINE Corporation
2+
* Copyright 2025 LY Corporation
33
*
4-
* LINE Corporation licenses this file to you under the Apache License,
4+
* LY Corporation licenses this file to you under the Apache License,
55
* version 2.0 (the "License"); you may not use this file except in compliance
66
* with the License. You may obtain a copy of the License at:
77
*
@@ -18,6 +18,8 @@
1818
import static com.google.common.base.Preconditions.checkArgument;
1919
import static java.util.Objects.requireNonNull;
2020

21+
import java.net.URI;
22+
import java.net.URISyntaxException;
2123
import java.util.regex.Pattern;
2224

2325
import com.google.common.collect.ImmutableSet;
@@ -32,6 +34,7 @@ public final class DocServiceInjectedScriptsUtil {
3234
private static final String SAFE_DOM_HOOK = "data-js-target";
3335
private static final ImmutableSet<String> ALLOWED_FAVICON_EXTENSIONS =
3436
ImmutableSet.of(".ico", ".png", ".svg");
37+
private static final ImmutableSet<String> ALLOWED_SCHEMES = ImmutableSet.of("http", "https");
3538

3639
/**
3740
* Returns a js script to change the title background color.
@@ -40,11 +43,11 @@ public final class DocServiceInjectedScriptsUtil {
4043
* @return the js script
4144
*/
4245
public static String withTitleBackground(String color) {
43-
final String titleBackgroundKey = "titleBackground";
44-
final String targetAttr = "main-app-bar";
45-
validateHexColor(color, titleBackgroundKey);
46+
final String titleBackgroundKey = "titleBackground";
47+
final String targetAttr = "main-app-bar";
48+
validateHexColor(color, titleBackgroundKey);
4649

47-
return buildStyleScript(color, targetAttr);
50+
return buildStyleScript(color, targetAttr);
4851
}
4952

5053
/**
@@ -64,111 +67,139 @@ public static String withGotoBackground(String color) {
6467
/**
6568
* Returns a js script to change the web favicon.
6669
*
67-
* @param url the url string to set
70+
* @param uri the uri string to set
6871
* @return the js script
6972
*/
70-
public static String withFavicon(String url) {
73+
public static String withFavicon(String uri) {
7174
final String faviconKey = "favicon";
72-
validateFaviconUrl(url, faviconKey);
75+
validateFaviconUri(uri, faviconKey);
7376

74-
return buildFaviconScript(escapeForJavaScriptUrl(url));
77+
return buildFaviconScript(escapeJavaScriptUri(uri));
7578
}
7679

7780
private DocServiceInjectedScriptsUtil() {}
7881

7982
/**
80-
* Validates the favicon url.
83+
* Validates that the given color is a non-null, non-empty, character hex color string.
8184
*
82-
* @param url the url string to validate
85+
* @param color the color string to validate
8386
* @param key the name used in error messages
8487
*/
85-
private static void validateFaviconUrl(String url, String key) {
86-
requireNonNull(url, key);
87-
checkArgument(!url.trim().isEmpty(), "%s is empty.", key);
88-
checkArgument(hasValidFaviconExtension(url), "%s extension not allowed.",key);
88+
private static void validateHexColor(String color, String key) {
89+
requireNonNull(color, key);
90+
checkArgument(!color.trim().isEmpty(), "%s is empty.", key);
91+
checkArgument(color.length() <= MAX_COLOR_LENGTH,
92+
"%s length exceeds %s.", key, MAX_COLOR_LENGTH);
93+
checkArgument(Pattern.matches(HEX_COLOR_PATTERN, color),
94+
"%s not in hex format: %s.", key, color);
95+
}
96+
97+
/**
98+
* Builds a JavaScript snippet that sets the background color of a DOM element.
99+
*
100+
* @param color the background color in hex format
101+
* @param targetAttr the value of the target attribute to match
102+
* @return a JavaScript string that applies the background color to the element
103+
*/
104+
private static String buildStyleScript(String color, String targetAttr) {
105+
return "{\n" +
106+
" const element = document.querySelector('[" + SAFE_DOM_HOOK + "=\"" + targetAttr + "\"]');\n" +
107+
" if (element) {\n" +
108+
" element.style.backgroundColor = '" + color + "';\n" +
109+
" }\n}\n";
110+
}
111+
112+
/**
113+
* Validates the favicon uri.
114+
*
115+
* @param uri the uri string to validate
116+
* @param key the name used in error messages
117+
*/
118+
private static void validateFaviconUri(String uri, String key) {
119+
requireNonNull(uri, key);
120+
checkArgument(!uri.trim().isEmpty(), "%s is empty.", key);
121+
checkArgument(isValidUri(uri), "%s uri invalid.", key);
122+
checkArgument(hasValidFaviconExtension(uri), "%s extension not allowed.",key);
123+
}
124+
125+
/**
126+
* Check if the input is a valid URI.
127+
* @param input the uri string to validate
128+
* @return true if is valid
129+
*/
130+
public static boolean isValidUri(String input) {
131+
try {
132+
final URI uri = new URI(input);
133+
final String scheme = uri.getScheme();
134+
if (scheme == null) {
135+
return true;
136+
}
137+
return ALLOWED_SCHEMES.contains(scheme.toLowerCase());
138+
} catch (URISyntaxException e) {
139+
return false;
140+
}
89141
}
90142

91143
/**
92144
* Validates the favicon extension.
93145
*
94-
* @param url the url string
146+
* @param uri the uri string
95147
* @return the result of validation
96148
*/
97-
private static boolean hasValidFaviconExtension(String url) {
98-
final String lowerUrl = url.toLowerCase();
149+
private static boolean hasValidFaviconExtension(String uri) {
150+
final String lowerUrl = uri.toLowerCase();
99151
return ALLOWED_FAVICON_EXTENSIONS.stream()
100152
.anyMatch(lowerUrl::endsWith);
101153
}
102154

103155
/**
104-
* Escapes special characters in a string to safely embed it in a JavaScript string literal.
156+
* Escapes special characters not filtered by other methods.
105157
*
106-
* @param url the input string to escape
158+
* @param uri the input string to escape
107159
* @return the escaped string
108160
*/
109-
private static String escapeForJavaScriptUrl(String url) {
110-
final StringBuilder escaped = new StringBuilder(url.length());
161+
private static String escapeJavaScriptUri(String uri) {
162+
final StringBuilder escaped = new StringBuilder();
163+
164+
for (int i = 0; i < uri.length(); i++) {
165+
final char c = uri.charAt(i);
111166

112-
for (char c : url.toCharArray()) {
113167
switch (c) {
114168
case '\\':
115169
escaped.append("\\\\");
116170
break;
117171
case '\'':
118172
escaped.append("\\'");
119173
break;
120-
case '"':
121-
escaped.append("\\\"");
174+
case '&':
175+
escaped.append("\\u0026");
122176
break;
123-
case ';':
124-
case '\n':
125-
case '\r':
177+
case '=':
178+
escaped.append("\\u003D");
179+
break;
180+
case '/':
181+
escaped.append("\\/");
126182
break;
127183
default:
128-
escaped.append(c);
184+
if (c > 126) {
185+
escaped.append(String.format("\\u%04X", (int) c));
186+
} else {
187+
escaped.append(c);
188+
}
189+
break;
129190
}
130191
}
131192

132193
return escaped.toString();
133194
}
134195

135-
/**
136-
* Validates that the given color is a non-null, non-empty, character hex color string.
137-
*
138-
* @param color the color string to validate
139-
* @param key the name used in error messages
140-
*/
141-
private static void validateHexColor(String color, String key) {
142-
requireNonNull(color, key);
143-
checkArgument(!color.trim().isEmpty(), "%s is empty.", key);
144-
checkArgument(color.length() <= MAX_COLOR_LENGTH,
145-
"%s length exceeds %s.", key, MAX_COLOR_LENGTH);
146-
checkArgument(Pattern.matches(HEX_COLOR_PATTERN, color),
147-
"%s not in hex format: %s.", key, color);
148-
}
149-
150-
/**
151-
* Builds a JavaScript snippet that sets the background color of a DOM element.
152-
*
153-
* @param color the background color in hex format
154-
* @param targetAttr the value of the target attribute to match
155-
* @return a JavaScript string that applies the background color to the element
156-
*/
157-
private static String buildStyleScript(String color, String targetAttr) {
158-
return "{\n" +
159-
" const element = document.querySelector('[" + SAFE_DOM_HOOK + "=\"" + targetAttr + "\"]');\n" +
160-
" if (element) {\n" +
161-
" element.style.backgroundColor = '" + color + "';\n" +
162-
" }\n}\n";
163-
}
164-
165196
/**
166197
* Builds a JavaScript snippet that sets the new favicon.
167198
*
168-
* @param url the url string to set
199+
* @param uri the uri string to set
169200
* @return a JavaScript string that applies the favicon change
170201
*/
171-
private static String buildFaviconScript(String url) {
202+
private static String buildFaviconScript(String uri) {
172203
return "{\n" +
173204
" let link = document.querySelector('link[rel~=\"icon\"]');\n" +
174205
" if (link) {\n" +
@@ -177,7 +208,7 @@ private static String buildFaviconScript(String url) {
177208
" link = document.createElement('link');\n" +
178209
" link.rel = 'icon';\n" +
179210
" link.type = 'image/x-icon';\n" +
180-
" link.href = '" + url + "';\n" +
211+
" link.href = '" + uri + "';\n" +
181212
" document.head.appendChild(link);\n" +
182213
"}\n";
183214
}

0 commit comments

Comments
 (0)