@@ -2,10 +2,9 @@ package io.github.optimumcode.json.schema.internal.formats
2
2
3
3
import io.github.optimumcode.json.schema.FormatValidationResult
4
4
import io.github.optimumcode.json.schema.FormatValidator
5
-
6
- private const val SCHEMA_DELIMITER = ' :'
7
- private const val QUERY_DELIMITER = ' ?'
8
- private const val FRAGMENT_DELIMITER = ' #'
5
+ import io.github.optimumcode.json.schema.internal.formats.UriSpec.FRAGMENT_DELIMITER
6
+ import io.github.optimumcode.json.schema.internal.formats.UriSpec.QUERY_DELIMITER
7
+ import io.github.optimumcode.json.schema.internal.formats.UriSpec.SCHEMA_DELIMITER
9
8
10
9
internal object UriFormatValidator : AbstractStringFormatValidator() {
11
10
@Suppress(" detekt:ReturnCount" )
@@ -20,16 +19,15 @@ internal object UriFormatValidator : AbstractStringFormatValidator() {
20
19
}
21
20
22
21
val schema = value.substring(0 , schemaEndIndex)
23
- if (! isValidSchema(schema)) {
22
+ if (! UriSpec . isValidSchema(schema)) {
24
23
return FormatValidator .Invalid ()
25
24
}
26
25
27
- val queryDelimiterIndex = value.indexOf(QUERY_DELIMITER )
28
26
val fragmentDelimiterIndex = value.indexOf(FRAGMENT_DELIMITER )
29
- if ( queryDelimiterIndex > 0 && fragmentDelimiterIndex > 0 && queryDelimiterIndex > fragmentDelimiterIndex) {
30
- // fragment is before query
31
- return FormatValidator . Invalid ()
32
- }
27
+ val queryDelimiterIndex =
28
+ value.indexOf( QUERY_DELIMITER )
29
+ . takeUnless { fragmentDelimiterIndex in 0 .. < it }
30
+ ? : - 1
33
31
val hierPart =
34
32
when {
35
33
queryDelimiterIndex > 0 ->
@@ -39,7 +37,7 @@ internal object UriFormatValidator : AbstractStringFormatValidator() {
39
37
else ->
40
38
value.substring(schemaEndIndex + 1 )
41
39
}
42
- if (! isValidHierPart(hierPart)) {
40
+ if (! UriSpec . isValidHierPart(hierPart)) {
43
41
return FormatValidator .Invalid ()
44
42
}
45
43
@@ -50,271 +48,18 @@ internal object UriFormatValidator : AbstractStringFormatValidator() {
50
48
} else {
51
49
value.substring(queryDelimiterIndex + 1 )
52
50
}
53
- if (! isValidQuery(query)) {
51
+ if (! UriSpec . isValidQuery(query)) {
54
52
return FormatValidator .Invalid ()
55
53
}
56
54
}
57
55
58
56
if (fragmentDelimiterIndex > 0 && fragmentDelimiterIndex < value.lastIndex) {
59
57
val fragment = value.substring(fragmentDelimiterIndex + 1 )
60
- if (! isValidFragment(fragment)) {
58
+ if (! UriSpec . isValidFragment(fragment)) {
61
59
return FormatValidator .Invalid ()
62
60
}
63
61
}
64
62
65
63
return FormatValidator .Valid ()
66
64
}
67
-
68
- private fun isValidFragment (fragment : String ): Boolean = isValidFragmentOrQuery(fragment)
69
-
70
- private fun isValidQuery (query : String ): Boolean = isValidFragmentOrQuery(query)
71
-
72
- private fun isValidHierPart (hierPart : String ): Boolean {
73
- if (hierPart.isEmpty()) {
74
- return true
75
- }
76
- return when {
77
- hierPart.startsWith(" //" ) ->
78
- isValidAuthorityWithPath(hierPart.substring(2 ))
79
- hierPart.startsWith(" /" ) ->
80
- isValidAbsolutePath(hierPart.substring(1 ))
81
- else ->
82
- isValidRootlessPath(hierPart)
83
- }
84
- }
85
-
86
- private fun isValidRootlessPath (rootlessPath : String ): Boolean {
87
- if (rootlessPath.isEmpty()) {
88
- return false
89
- }
90
-
91
- return isValidSegments(rootlessPath)
92
- }
93
-
94
- private fun isValidAbsolutePath (absolutePath : String ): Boolean {
95
- if (absolutePath.isEmpty()) {
96
- return true
97
- }
98
-
99
- return isValidSegments(absolutePath)
100
- }
101
-
102
- private fun isValidSegments (segments : String ): Boolean {
103
- var lastSep = - 1
104
- for ((index, value) in segments.withIndex()) {
105
- if (value == ' /' ) {
106
- if (! hasOnlyPChars(segments.substring(lastSep + 1 , index))) {
107
- return false
108
- }
109
- lastSep = index
110
- }
111
- }
112
-
113
- return hasOnlyPChars(segments.substring(lastSep + 1 ))
114
- }
115
-
116
- @Suppress(" detekt:ReturnCount" )
117
- private fun isValidAuthorityWithPath (authorityWithPath : String ): Boolean {
118
- if (authorityWithPath.isEmpty()) {
119
- return false
120
- }
121
- val userInfoSeparatorIndex = authorityWithPath.indexOf(' @' )
122
- if (userInfoSeparatorIndex >= 0 ) {
123
- if (! isValidUserInfo(authorityWithPath.substring(0 , userInfoSeparatorIndex))) {
124
- return false
125
- }
126
- }
127
- val ipV6EndIndex = authorityWithPath.lastIndexOf(' ]' )
128
- val portSeparatorIndex =
129
- authorityWithPath.indexOf(
130
- ' :' ,
131
- startIndex =
132
- when {
133
- ipV6EndIndex > 0 -> ipV6EndIndex
134
- userInfoSeparatorIndex > 0 -> userInfoSeparatorIndex
135
- else -> 0
136
- },
137
- )
138
-
139
- val segmentSeparatorIndex = authorityWithPath.indexOf(' /' )
140
- val hostEndIndex =
141
- when {
142
- portSeparatorIndex > 0 -> portSeparatorIndex
143
- segmentSeparatorIndex > 0 -> segmentSeparatorIndex
144
- else -> authorityWithPath.length
145
- }
146
- val hostStartIndex =
147
- if (userInfoSeparatorIndex >= 0 ) {
148
- userInfoSeparatorIndex + 1
149
- } else {
150
- 0
151
- }
152
- val host = authorityWithPath.substring(hostStartIndex, hostEndIndex)
153
- if (! isValidHost(host)) {
154
- return false
155
- }
156
- if (portSeparatorIndex > 0 && portSeparatorIndex < authorityWithPath.lastIndex) {
157
- val portEndIndex =
158
- if (segmentSeparatorIndex > 0 ) {
159
- segmentSeparatorIndex
160
- } else {
161
- authorityWithPath.length
162
- }
163
- // empty port part
164
- return isValidPort(authorityWithPath.substring(portSeparatorIndex + 1 , portEndIndex))
165
- }
166
- return segmentSeparatorIndex < 0 || isValidSegments(authorityWithPath.substring(segmentSeparatorIndex))
167
- }
168
-
169
- private fun isValidPort (port : String ): Boolean {
170
- if (port.isEmpty()) {
171
- return true
172
- }
173
-
174
- for (ch in port) {
175
- if (! isDigit(ch)) {
176
- return false
177
- }
178
- }
179
-
180
- return true
181
- }
182
-
183
- private fun isValidHost (host : String ): Boolean {
184
- if (host.isEmpty()) {
185
- return false
186
- }
187
- if (IpV4FormatValidator .validate(host).isValid()) {
188
- return true
189
- }
190
- if (host.startsWith(' [' ) && host.endsWith(' ]' )) {
191
- val substr = host.substring(1 , host.lastIndex)
192
- return IpV6FormatValidator .validate(substr).isValid() || isValidIPvFuture(substr)
193
- }
194
- return isRegName(host)
195
- }
196
-
197
- @Suppress(" detekt:ReturnCount" )
198
- private fun isValidIPvFuture (ipVFuture : String ): Boolean {
199
- if (ipVFuture.isEmpty()) {
200
- return false
201
- }
202
- if (ipVFuture[0 ] != ' v' ) {
203
- return false
204
- }
205
- val dotIndex = ipVFuture.indexOf(' .' )
206
- if (dotIndex < 0 ) {
207
- return false
208
- }
209
- val firstPart = ipVFuture.substring(1 , dotIndex)
210
- val secondPart = ipVFuture.substring(dotIndex + 1 )
211
- if (firstPart.isEmpty() || secondPart.isEmpty()) {
212
- return false
213
- }
214
- for (ch in firstPart) {
215
- if (isHexDigit(ch)) {
216
- continue
217
- }
218
- return false
219
- }
220
- for (ch in secondPart) {
221
- if (isUnreserved(ch) || isSubDelimiter(ch) || ch == ' :' ) {
222
- continue
223
- }
224
- return false
225
- }
226
- return true
227
- }
228
-
229
- private fun isRegName (host : String ): Boolean =
230
- hasValidCharsOrPctEncoded(host) {
231
- isSubDelimiter(it) || isUnreserved(it)
232
- }
233
-
234
- private fun isValidUserInfo (userInfo : String ): Boolean =
235
- hasValidCharsOrPctEncoded(userInfo) {
236
- it == ' :' || isSubDelimiter(it) || isUnreserved(it)
237
- }
238
-
239
- private fun isValidSchema (schema : String ): Boolean {
240
- if (schema.isEmpty()) {
241
- return false
242
- }
243
-
244
- if (! isAlpha(schema[0 ])) {
245
- return false
246
- }
247
-
248
- for (i in 1 .. schema.lastIndex) {
249
- val char = schema[i]
250
- @Suppress(" detekt:ComplexCondition" )
251
- if (isAlpha(char) || isDigit(char) || char == ' +' || char == ' -' || char == ' .' ) {
252
- continue
253
- }
254
- return false
255
- }
256
-
257
- return true
258
- }
259
-
260
- private fun isValidFragmentOrQuery (part : String ): Boolean {
261
- if (part.isEmpty()) {
262
- return true
263
- }
264
-
265
- return hasValidCharsOrPctEncoded(part) {
266
- it == ' /' || it == ' ?' || isPChar(it)
267
- }
268
- }
269
-
270
- private fun hasOnlyPChars (part : String ): Boolean = hasValidCharsOrPctEncoded(part, ::isPChar)
271
-
272
- private inline fun hasValidCharsOrPctEncoded (
273
- part : String ,
274
- isValidChar : (Char ) -> Boolean ,
275
- ): Boolean {
276
- var i = 0
277
- var valid = true
278
- while (i < part.length) {
279
- val char = part[i]
280
- if (char != ' %' && ! isValidChar(char)) {
281
- valid = false
282
- break
283
- }
284
- if (char == ' %' ) {
285
- if (! isPctEncoded(i, part)) {
286
- valid = false
287
- break
288
- }
289
- i + = 2
290
- }
291
- i + = 1
292
- }
293
-
294
- return valid
295
- }
296
-
297
- private fun isPctEncoded (
298
- index : Int ,
299
- str : String ,
300
- ): Boolean {
301
- if (index + 2 >= str.length) {
302
- return false
303
- }
304
- return str[index] == ' %' && isHexDigit(str[index + 1 ]) && isHexDigit(str[index + 2 ])
305
- }
306
-
307
- private fun isAlpha (c : Char ): Boolean = c in ' a' .. ' z' || c in ' A' .. ' Z'
308
-
309
- private fun isDigit (c : Char ): Boolean = c in ' 0' .. ' 9'
310
-
311
- private fun isPChar (c : Char ): Boolean = isUnreserved(c) || isSubDelimiter(c) || c == ' :' || c == ' @'
312
-
313
- private fun isUnreserved (c : Char ): Boolean = isAlpha(c) || isDigit(c) || c == ' _' || c == ' -' || c == ' .' || c == ' ~'
314
-
315
- private fun isSubDelimiter (c : Char ): Boolean =
316
- c == ' !' || c == ' $' || c == ' &' || c == ' \' ' || c == ' (' || c == ' )' ||
317
- c == ' *' || c == ' +' || c == ' ,' || c == ' ;' || c == ' ='
318
-
319
- private fun isHexDigit (c : Char ): Boolean = c in ' 0' .. ' 9' || c in ' a' .. ' f' || c in ' A' .. ' F'
320
65
}
0 commit comments