1
1
/*
2
- * Copyright 2025 LINE Corporation
2
+ * Copyright 2025 LY Corporation
3
3
*
4
- * LINE Corporation licenses this file to you under the Apache License,
4
+ * LY Corporation licenses this file to you under the Apache License,
5
5
* version 2.0 (the "License"); you may not use this file except in compliance
6
6
* with the License. You may obtain a copy of the License at:
7
7
*
18
18
import static com .google .common .base .Preconditions .checkArgument ;
19
19
import static java .util .Objects .requireNonNull ;
20
20
21
+ import java .net .URI ;
22
+ import java .net .URISyntaxException ;
21
23
import java .util .regex .Pattern ;
22
24
23
25
import com .google .common .collect .ImmutableSet ;
@@ -32,6 +34,7 @@ public final class DocServiceInjectedScriptsUtil {
32
34
private static final String SAFE_DOM_HOOK = "data-js-target" ;
33
35
private static final ImmutableSet <String > ALLOWED_FAVICON_EXTENSIONS =
34
36
ImmutableSet .of (".ico" , ".png" , ".svg" );
37
+ private static final ImmutableSet <String > ALLOWED_SCHEMES = ImmutableSet .of ("http" , "https" );
35
38
36
39
/**
37
40
* Returns a js script to change the title background color.
@@ -40,11 +43,11 @@ public final class DocServiceInjectedScriptsUtil {
40
43
* @return the js script
41
44
*/
42
45
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 );
46
49
47
- return buildStyleScript (color , targetAttr );
50
+ return buildStyleScript (color , targetAttr );
48
51
}
49
52
50
53
/**
@@ -64,111 +67,139 @@ public static String withGotoBackground(String color) {
64
67
/**
65
68
* Returns a js script to change the web favicon.
66
69
*
67
- * @param url the url string to set
70
+ * @param uri the uri string to set
68
71
* @return the js script
69
72
*/
70
- public static String withFavicon (String url ) {
73
+ public static String withFavicon (String uri ) {
71
74
final String faviconKey = "favicon" ;
72
- validateFaviconUrl ( url , faviconKey );
75
+ validateFaviconUri ( uri , faviconKey );
73
76
74
- return buildFaviconScript (escapeForJavaScriptUrl ( url ));
77
+ return buildFaviconScript (escapeJavaScriptUri ( uri ));
75
78
}
76
79
77
80
private DocServiceInjectedScriptsUtil () {}
78
81
79
82
/**
80
- * Validates the favicon url .
83
+ * Validates that the given color is a non-null, non-empty, character hex color string .
81
84
*
82
- * @param url the url string to validate
85
+ * @param color the color string to validate
83
86
* @param key the name used in error messages
84
87
*/
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
+ }
89
141
}
90
142
91
143
/**
92
144
* Validates the favicon extension.
93
145
*
94
- * @param url the url string
146
+ * @param uri the uri string
95
147
* @return the result of validation
96
148
*/
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 ();
99
151
return ALLOWED_FAVICON_EXTENSIONS .stream ()
100
152
.anyMatch (lowerUrl ::endsWith );
101
153
}
102
154
103
155
/**
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 .
105
157
*
106
- * @param url the input string to escape
158
+ * @param uri the input string to escape
107
159
* @return the escaped string
108
160
*/
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 );
111
166
112
- for (char c : url .toCharArray ()) {
113
167
switch (c ) {
114
168
case '\\' :
115
169
escaped .append ("\\ \\ " );
116
170
break ;
117
171
case '\'' :
118
172
escaped .append ("\\ '" );
119
173
break ;
120
- case '" ' :
121
- escaped .append ("\\ \" " );
174
+ case '& ' :
175
+ escaped .append ("\\ u0026 " );
122
176
break ;
123
- case ';' :
124
- case '\n' :
125
- case '\r' :
177
+ case '=' :
178
+ escaped .append ("\\ u003D" );
179
+ break ;
180
+ case '/' :
181
+ escaped .append ("\\ /" );
126
182
break ;
127
183
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 ;
129
190
}
130
191
}
131
192
132
193
return escaped .toString ();
133
194
}
134
195
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
-
165
196
/**
166
197
* Builds a JavaScript snippet that sets the new favicon.
167
198
*
168
- * @param url the url string to set
199
+ * @param uri the uri string to set
169
200
* @return a JavaScript string that applies the favicon change
170
201
*/
171
- private static String buildFaviconScript (String url ) {
202
+ private static String buildFaviconScript (String uri ) {
172
203
return "{\n " +
173
204
" let link = document.querySelector('link[rel~=\" icon\" ]');\n " +
174
205
" if (link) {\n " +
@@ -177,7 +208,7 @@ private static String buildFaviconScript(String url) {
177
208
" link = document.createElement('link');\n " +
178
209
" link.rel = 'icon';\n " +
179
210
" link.type = 'image/x-icon';\n " +
180
- " link.href = '" + url + "';\n " +
211
+ " link.href = '" + uri + "';\n " +
181
212
" document.head.appendChild(link);\n " +
182
213
"}\n " ;
183
214
}
0 commit comments