@amritk/nish-x86_64-linux 0.27.2 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +13 -8
- package/bin/nish +0 -0
- package/docs/AI.md +19 -2
- package/package.json +1 -1
- package/runtime/core.d.ts +47 -0
- package/runtime/core.mjs +67 -0
- package/runtime/nish.d.ts +49 -0
- package/runtime/nish.h +50 -5
- package/runtime/nish.mjs +6 -0
- package/runtime/runtime-checksum.c +270 -0
- package/runtime/runtime-parallel.c +21 -0
- package/runtime/runtime-sandbox.c +470 -0
- package/runtime/runtime-sha256.c +239 -0
- package/scripts/build.sh +44 -23
- package/std/README.md +15 -10
- package/std/compress/checksum.ts +88 -75
- package/std/compress/deflate.ts +224 -0
- package/std/compress/gzip.ts +52 -0
- package/std/crypto/bcrypt.ts +816 -280
- package/std/json-equal.ts +335 -0
- package/std/json-format.ts +1070 -0
- package/std/json-number.ts +428 -0
- package/std/json-pointer.ts +245 -0
- package/std/json-value.ts +510 -17
- package/std/net/http-compress.ts +259 -16
- package/std/net/http1-client.ts +104 -19
- package/std/net/http1-server.ts +61 -12
- package/std/net/postgres.ts +405 -0
- package/std/net/websocket.ts +295 -14
- package/std/regex.ts +613 -52
- package/std/term-io.ts +35 -8
- package/std/yaml-json.ts +841 -0
|
@@ -0,0 +1,1070 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/json-format` — the string formats of JSON Schema draft 2020-12
|
|
3
|
+
* (§7.3 of the validation vocabulary), each a predicate, and one dispatcher
|
|
4
|
+
* that answers valid, invalid or unknown for a format named in a schema.
|
|
5
|
+
*
|
|
6
|
+
* import { jsonFormatId, jsonFormatCheck, isEmail, JSON_FORMAT_VALID } from "nish/json-format"
|
|
7
|
+
*
|
|
8
|
+
* isEmail("joe.bloggs@example.com") // true
|
|
9
|
+
* const id = jsonFormatId("date-time") // resolve the name once, when the schema is read
|
|
10
|
+
* jsonFormatCheck(id, "1963-06-19T08:30:06Z") // JSON_FORMAT_VALID, for each instance
|
|
11
|
+
* jsonFormatValidate("idn-email", "x@y") // JSON_FORMAT_UNKNOWN: not checked here
|
|
12
|
+
*
|
|
13
|
+
* **What each format is.** Each predicate is the grammar of the RFC the
|
|
14
|
+
* specification names, applied to the whole string:
|
|
15
|
+
*
|
|
16
|
+
* - `date-time`, `date` and `time` are RFC 3339 §5.6: `T` and `Z` in either
|
|
17
|
+
* case, a fraction of any length, an offset of at most 23:59, the month's
|
|
18
|
+
* length in the year's calendar, and a second of 60 only where the time is
|
|
19
|
+
* 23:59 in UTC once the offset is taken off.
|
|
20
|
+
* - `duration` is RFC 3339 Appendix A as written: units in order, none
|
|
21
|
+
* skipped (`P1Y1D` is invalid, since a year may be followed only by a
|
|
22
|
+
* month), a `T` before any hour, minute or second and at least one after
|
|
23
|
+
* it, and weeks alone.
|
|
24
|
+
* - `email` is RFC 5321's `Mailbox`: a dot-string or a quoted string of at
|
|
25
|
+
* most 64 octets, then a domain of at most 255 that is a `hostname` or an
|
|
26
|
+
* address literal (`[127.0.0.1]`, `[IPv6:::1]`, or a tag and its content).
|
|
27
|
+
* - `hostname` is RFC 1123: labels of 1 to 63 letters, digits and hyphens
|
|
28
|
+
* with no hyphen at either end, 253 octets in all, and no trailing dot. An
|
|
29
|
+
* A-label (`xn--`) is checked as letters and digits; its Punycode is not
|
|
30
|
+
* decoded.
|
|
31
|
+
* - `ipv4` is RFC 2673's dotted quad: four decimal parts of 0 to 255, with no
|
|
32
|
+
* leading zero. `ipv6` is RFC 4291 §2.2: at most one `::`, groups of 1 to 4
|
|
33
|
+
* hex digits, an IPv4 tail counted as two, and no zone or prefix length.
|
|
34
|
+
* - `uuid` is RFC 4122's 8-4-4-4-12 hex digits, in either case, of any
|
|
35
|
+
* version or variant.
|
|
36
|
+
* - `uri` and `uri-reference` are RFC 3986 §3 and §4.1; `iri` and
|
|
37
|
+
* `iri-reference` are RFC 3987 §2.2, the same grammar with any `ucschar`
|
|
38
|
+
* where an unreserved character goes and `iprivate` in the query.
|
|
39
|
+
* - `uri-template` is RFC 6570 §2, any level.
|
|
40
|
+
* - `regex` is a pattern `nish/regex` compiles (see "What is not checked").
|
|
41
|
+
*
|
|
42
|
+
* **Why the URI grammar is checked here and not by `nish/net/url`.**
|
|
43
|
+
* `urlParse` reads an absolute URL a client is about to fetch, and refuses
|
|
44
|
+
* what two readers could take for different hosts: a percent-encoded host
|
|
45
|
+
* byte, a numeric host that is not a dotted-decimal IPv4 address, an
|
|
46
|
+
* IPvFuture literal, a port above 65535, any byte past ASCII, and a reference
|
|
47
|
+
* with no scheme (`std/net/url.ts`). Each of those is a valid RFC 3986 URI,
|
|
48
|
+
* IRI or relative reference, which this module must answer valid. It also
|
|
49
|
+
* allocates the parts it answers. So this module walks the ABNF itself, over
|
|
50
|
+
* the string, allocating nothing.
|
|
51
|
+
*
|
|
52
|
+
* **What is not checked.** `jsonFormatId` answers -1, and the dispatcher
|
|
53
|
+
* `JSON_FORMAT_UNKNOWN`, for `idn-email`, `idn-hostname`, `json-pointer`,
|
|
54
|
+
* `relative-json-pointer` and any other name: an unknown format is an
|
|
55
|
+
* annotation, which a validator accepts (§7.2.3). `regex` is answered by
|
|
56
|
+
* `regexCompile`, which is stricter than ECMA-262: it refuses lookaround and
|
|
57
|
+
* backreferences, which it does not implement, and reads the pattern with the
|
|
58
|
+
* `u` flag's rules, so a lone `{` or `]` is an error. Such a pattern is valid
|
|
59
|
+
* ECMA-262 and is answered invalid here, a false negative.
|
|
60
|
+
*
|
|
61
|
+
* **What a call costs.** Every check but `regex` is linear in the length of
|
|
62
|
+
* its string: each unit is read a small fixed number of times (at most four, in
|
|
63
|
+
* a URI's authority, which is found and then split at `@` and `:`). It
|
|
64
|
+
* allocates nothing and cannot panic. `jsonFormatId` compares the
|
|
65
|
+
* name with up to 15 strings; resolve it once per schema, not per instance.
|
|
66
|
+
* `isRegex` compiles the pattern, which allocates the program `nish/regex`
|
|
67
|
+
* builds. That is not linear in the pattern's length, since a counted repeat
|
|
68
|
+
* copies its body, but `nish/regex`'s limits bound it: `(a{1000}){100}` is
|
|
69
|
+
* refused at 100,000 instructions (`REGEX_MAX_INSTRUCTIONS`) or a 4 MiB slot
|
|
70
|
+
* table, whichever comes first.
|
|
71
|
+
*
|
|
72
|
+
* Offsets are code units: UTF-8 bytes natively and UTF-16 units under Node.
|
|
73
|
+
* The checks answer the same on both, since every non-ASCII character is
|
|
74
|
+
* decoded where a grammar allows one and refused everywhere else.
|
|
75
|
+
*/
|
|
76
|
+
import { regexCompile } from "nish/regex"
|
|
77
|
+
|
|
78
|
+
/** The dispatcher's answer for a string the format accepts. */
|
|
79
|
+
export const JSON_FORMAT_VALID: i32 = 1
|
|
80
|
+
/** The dispatcher's answer for a string the format refuses. */
|
|
81
|
+
export const JSON_FORMAT_INVALID: i32 = 0
|
|
82
|
+
/** The dispatcher's answer for a format this module does not check. */
|
|
83
|
+
export const JSON_FORMAT_UNKNOWN: i32 = -1
|
|
84
|
+
|
|
85
|
+
/** The id of `date-time`, for `jsonFormatCheck`. */
|
|
86
|
+
export const JSON_FORMAT_DATE_TIME: i32 = 0
|
|
87
|
+
/** The id of `date`. */
|
|
88
|
+
export const JSON_FORMAT_DATE: i32 = 1
|
|
89
|
+
/** The id of `time`. */
|
|
90
|
+
export const JSON_FORMAT_TIME: i32 = 2
|
|
91
|
+
/** The id of `duration`. */
|
|
92
|
+
export const JSON_FORMAT_DURATION: i32 = 3
|
|
93
|
+
/** The id of `email`. */
|
|
94
|
+
export const JSON_FORMAT_EMAIL: i32 = 4
|
|
95
|
+
/** The id of `hostname`. */
|
|
96
|
+
export const JSON_FORMAT_HOSTNAME: i32 = 5
|
|
97
|
+
/** The id of `ipv4`. */
|
|
98
|
+
export const JSON_FORMAT_IPV4: i32 = 6
|
|
99
|
+
/** The id of `ipv6`. */
|
|
100
|
+
export const JSON_FORMAT_IPV6: i32 = 7
|
|
101
|
+
/** The id of `uuid`. */
|
|
102
|
+
export const JSON_FORMAT_UUID: i32 = 8
|
|
103
|
+
/** The id of `uri`. */
|
|
104
|
+
export const JSON_FORMAT_URI: i32 = 9
|
|
105
|
+
/** The id of `uri-reference`. */
|
|
106
|
+
export const JSON_FORMAT_URI_REFERENCE: i32 = 10
|
|
107
|
+
/** The id of `iri`. */
|
|
108
|
+
export const JSON_FORMAT_IRI: i32 = 11
|
|
109
|
+
/** The id of `iri-reference`. */
|
|
110
|
+
export const JSON_FORMAT_IRI_REFERENCE: i32 = 12
|
|
111
|
+
/** The id of `uri-template`. */
|
|
112
|
+
export const JSON_FORMAT_URI_TEMPLATE: i32 = 13
|
|
113
|
+
/** The id of `regex`. */
|
|
114
|
+
export const JSON_FORMAT_REGEX: i32 = 14
|
|
115
|
+
|
|
116
|
+
// The URI grammar's character classes, as `jfUriUnit` reads them. Each names
|
|
117
|
+
// the ASCII set of one RFC 3986 rule; the IRI forms add `ucschar`, and the
|
|
118
|
+
// query alone `iprivate`.
|
|
119
|
+
const JF_REG_NAME: i32 = 0 // unreserved / sub-delims
|
|
120
|
+
const JF_USERINFO: i32 = 1 // unreserved / sub-delims / ":"
|
|
121
|
+
const JF_SEGMENT: i32 = 2 // pchar: unreserved / sub-delims / ":" / "@"
|
|
122
|
+
const JF_PATH: i32 = 3 // pchar / "/"
|
|
123
|
+
const JF_FRAGMENT: i32 = 4 // pchar / "/" / "?"
|
|
124
|
+
const JF_QUERY: i32 = 5 // the fragment's set, and `iprivate` in an IRI
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The code unit of `s` at `i`, or -1 past its end, so that a grammar can look
|
|
128
|
+
* one unit ahead without testing the length first.
|
|
129
|
+
*/
|
|
130
|
+
const jfUnitAt = (s: string, i: i32): i32 => (i < toI32(s.length) ? toI32(s.charCodeAt(i)) : -1)
|
|
131
|
+
|
|
132
|
+
const jfIsDigit = (c: i32): boolean => c >= 48 && c <= 57
|
|
133
|
+
|
|
134
|
+
const jfIsAlpha = (c: i32): boolean => (c >= 65 && c <= 90) || (c >= 97 && c <= 122)
|
|
135
|
+
|
|
136
|
+
const jfIsHex = (c: i32): boolean => jfIsDigit(c) || (c >= 65 && c <= 70) || (c >= 97 && c <= 102)
|
|
137
|
+
|
|
138
|
+
/** `c` as a lower-case ASCII letter, so that `T` and `t` compare alike. */
|
|
139
|
+
const jfLower = (c: i32): i32 => (c >= 65 && c <= 90 ? c + 32 : c)
|
|
140
|
+
|
|
141
|
+
/** The value of the `n` decimal digits of `s` at `at`, or -1 if any is not one. */
|
|
142
|
+
const jfDigits = (s: string, at: i32, n: i32): i32 => {
|
|
143
|
+
let value: i32 = 0
|
|
144
|
+
let i: i32 = 0
|
|
145
|
+
while (i < n) {
|
|
146
|
+
const c = jfUnitAt(s, at + i)
|
|
147
|
+
if (!jfIsDigit(c)) {
|
|
148
|
+
return -1
|
|
149
|
+
}
|
|
150
|
+
value = value * 10 + (c - 48)
|
|
151
|
+
i += 1
|
|
152
|
+
}
|
|
153
|
+
return value
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The number of days in `month` (1 to 12) of the Gregorian `year`. */
|
|
157
|
+
const jfDaysInMonth = (year: i32, month: i32): i32 => {
|
|
158
|
+
if (month === 2) {
|
|
159
|
+
const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
|
|
160
|
+
return leap ? 29 : 28
|
|
161
|
+
}
|
|
162
|
+
return month === 4 || month === 6 || month === 9 || month === 11 ? 30 : 31
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Whether `s` holds RFC 3339's `full-date`, `YYYY-MM-DD`, at `at`. */
|
|
166
|
+
const jfFullDateAt = (s: string, at: i32): boolean => {
|
|
167
|
+
const year = jfDigits(s, at, 4)
|
|
168
|
+
const month = jfDigits(s, at + 5, 2)
|
|
169
|
+
const day = jfDigits(s, at + 8, 2)
|
|
170
|
+
return (
|
|
171
|
+
year >= 0 &&
|
|
172
|
+
jfUnitAt(s, at + 4) === 45 &&
|
|
173
|
+
jfUnitAt(s, at + 7) === 45 &&
|
|
174
|
+
month >= 1 &&
|
|
175
|
+
month <= 12 &&
|
|
176
|
+
day >= 1 &&
|
|
177
|
+
day <= jfDaysInMonth(year, month)
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Whether `s` from `at` to its end is RFC 3339's `full-time`:
|
|
183
|
+
* `HH:MM:SS[.frac]` and `Z` or `±HH:MM`.
|
|
184
|
+
*
|
|
185
|
+
* A leap second is the last second of a UTC day, so `23:59:60Z` and
|
|
186
|
+
* `15:59:60-08:00` are valid and `22:59:60Z` is not: the offset is taken off
|
|
187
|
+
* the local minute of the day and what is left must be 23:59.
|
|
188
|
+
*/
|
|
189
|
+
const jfFullTimeFrom = (s: string, at: i32): boolean => {
|
|
190
|
+
const hour = jfDigits(s, at, 2)
|
|
191
|
+
const minute = jfDigits(s, at + 3, 2)
|
|
192
|
+
const second = jfDigits(s, at + 6, 2)
|
|
193
|
+
if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 60) {
|
|
194
|
+
return false
|
|
195
|
+
}
|
|
196
|
+
if (jfUnitAt(s, at + 2) !== 58 || jfUnitAt(s, at + 5) !== 58) {
|
|
197
|
+
return false
|
|
198
|
+
}
|
|
199
|
+
let i = at + 8
|
|
200
|
+
if (jfUnitAt(s, i) === 46) {
|
|
201
|
+
i += 1
|
|
202
|
+
const start = i
|
|
203
|
+
while (jfIsDigit(jfUnitAt(s, i))) {
|
|
204
|
+
i += 1
|
|
205
|
+
}
|
|
206
|
+
if (i === start) {
|
|
207
|
+
return false
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const sign = jfUnitAt(s, i)
|
|
211
|
+
let offset: i32 = 0
|
|
212
|
+
if (jfLower(sign) === 122) {
|
|
213
|
+
i += 1
|
|
214
|
+
} else if (sign === 43 || sign === 45) {
|
|
215
|
+
const offsetHour = jfDigits(s, i + 1, 2)
|
|
216
|
+
const offsetMinute = jfDigits(s, i + 4, 2)
|
|
217
|
+
if (
|
|
218
|
+
offsetHour < 0 ||
|
|
219
|
+
offsetHour > 23 ||
|
|
220
|
+
offsetMinute < 0 ||
|
|
221
|
+
offsetMinute > 59 ||
|
|
222
|
+
jfUnitAt(s, i + 3) !== 58
|
|
223
|
+
) {
|
|
224
|
+
return false
|
|
225
|
+
}
|
|
226
|
+
offset = offsetHour * 60 + offsetMinute
|
|
227
|
+
if (sign === 45) {
|
|
228
|
+
offset = -offset
|
|
229
|
+
}
|
|
230
|
+
i += 6
|
|
231
|
+
} else {
|
|
232
|
+
return false
|
|
233
|
+
}
|
|
234
|
+
if (i !== toI32(s.length)) {
|
|
235
|
+
return false
|
|
236
|
+
}
|
|
237
|
+
// The UTC minute of the day is in -1439 to 2878 before the wrap.
|
|
238
|
+
return second !== 60 || (hour * 60 + minute - offset + 1440) % 1440 === 1439
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Whether `s` is an RFC 3339 `date-time`, `1963-06-19T08:30:06.283185Z`. */
|
|
242
|
+
export const isDateTime = (s: string): boolean =>
|
|
243
|
+
jfFullDateAt(s, 0) && jfLower(jfUnitAt(s, 10)) === 116 && jfFullTimeFrom(s, 11)
|
|
244
|
+
|
|
245
|
+
/** Whether `s` is an RFC 3339 `full-date`, `1963-06-19`. */
|
|
246
|
+
export const isDate = (s: string): boolean => toI32(s.length) === 10 && jfFullDateAt(s, 0)
|
|
247
|
+
|
|
248
|
+
/** Whether `s` is an RFC 3339 `full-time`, `08:30:06Z` or `08:30:06+01:00`. */
|
|
249
|
+
export const isTime = (s: string): boolean => jfFullTimeFrom(s, 0)
|
|
250
|
+
|
|
251
|
+
/** The place of unit `u` in `units`, or -1 if it is not one of them. */
|
|
252
|
+
const jfUnitIndex = (u: i32, units: string): i32 => {
|
|
253
|
+
let k: i32 = 0
|
|
254
|
+
while (k < toI32(units.length)) {
|
|
255
|
+
if (toI32(units.charCodeAt(k)) === u) {
|
|
256
|
+
return k
|
|
257
|
+
}
|
|
258
|
+
k += 1
|
|
259
|
+
}
|
|
260
|
+
return -1
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Whether `s` is an RFC 3339 Appendix A `duration`, `P3Y6M4DT12H30M5S`.
|
|
265
|
+
*
|
|
266
|
+
* The ABNF lets each unit be followed only by the next one (`dur-year =
|
|
267
|
+
* 1*DIGIT "Y" [dur-month]`), so the units of each half must be consecutive
|
|
268
|
+
* in `YMD` and `HMS` order: `P1Y1D` and `PT1H1S` are invalid as written,
|
|
269
|
+
* though ISO 8601 allows them.
|
|
270
|
+
*/
|
|
271
|
+
export const isDuration = (s: string): boolean => {
|
|
272
|
+
const length: i32 = toI32(s.length)
|
|
273
|
+
if (jfUnitAt(s, 0) !== 80) {
|
|
274
|
+
return false
|
|
275
|
+
}
|
|
276
|
+
let i: i32 = 1
|
|
277
|
+
let inTime = false
|
|
278
|
+
let last: i32 = -1
|
|
279
|
+
let count: i32 = 0
|
|
280
|
+
let timeCount: i32 = 0
|
|
281
|
+
while (i < length) {
|
|
282
|
+
if (jfUnitAt(s, i) === 84) {
|
|
283
|
+
if (inTime) {
|
|
284
|
+
return false
|
|
285
|
+
}
|
|
286
|
+
inTime = true
|
|
287
|
+
last = -1
|
|
288
|
+
i += 1
|
|
289
|
+
continue
|
|
290
|
+
}
|
|
291
|
+
const start = i
|
|
292
|
+
while (jfIsDigit(jfUnitAt(s, i))) {
|
|
293
|
+
i += 1
|
|
294
|
+
}
|
|
295
|
+
const unit = jfUnitAt(s, i)
|
|
296
|
+
if (i === start || unit < 0) {
|
|
297
|
+
return false
|
|
298
|
+
}
|
|
299
|
+
i += 1
|
|
300
|
+
if (unit === 87) {
|
|
301
|
+
// `dur-week` is the whole duration.
|
|
302
|
+
return !inTime && count === 0 && i === length
|
|
303
|
+
}
|
|
304
|
+
const k = jfUnitIndex(unit, inTime ? "HMS" : "YMD")
|
|
305
|
+
if (k < 0 || (last >= 0 && k !== last + 1)) {
|
|
306
|
+
return false
|
|
307
|
+
}
|
|
308
|
+
last = k
|
|
309
|
+
count += 1
|
|
310
|
+
if (inTime) {
|
|
311
|
+
timeCount += 1
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return count > 0 && (!inTime || timeCount > 0)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Whether `s[from, to)` is a dotted-quad IPv4 address. */
|
|
318
|
+
const jfIpv4Range = (s: string, from: i32, to: i32): boolean => {
|
|
319
|
+
let i = from
|
|
320
|
+
let part: i32 = 0
|
|
321
|
+
while (part < 4) {
|
|
322
|
+
if (part > 0) {
|
|
323
|
+
if (i >= to || jfUnitAt(s, i) !== 46) {
|
|
324
|
+
return false
|
|
325
|
+
}
|
|
326
|
+
i += 1
|
|
327
|
+
}
|
|
328
|
+
const start = i
|
|
329
|
+
let value: i32 = 0
|
|
330
|
+
while (i < to && i - start < 3 && jfIsDigit(jfUnitAt(s, i))) {
|
|
331
|
+
value = value * 10 + (jfUnitAt(s, i) - 48)
|
|
332
|
+
i += 1
|
|
333
|
+
}
|
|
334
|
+
const n = i - start
|
|
335
|
+
if (n === 0 || value > 255 || (n > 1 && jfUnitAt(s, start) === 48)) {
|
|
336
|
+
return false
|
|
337
|
+
}
|
|
338
|
+
part += 1
|
|
339
|
+
}
|
|
340
|
+
return i === to
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Whether `s[from, to)` is an RFC 4291 IPv6 address, with no zone or prefix. */
|
|
344
|
+
const jfIpv6Range = (s: string, from: i32, to: i32): boolean => {
|
|
345
|
+
let i = from
|
|
346
|
+
let groups: i32 = 0
|
|
347
|
+
let elided = false
|
|
348
|
+
if (i + 1 < to && jfUnitAt(s, i) === 58 && jfUnitAt(s, i + 1) === 58) {
|
|
349
|
+
elided = true
|
|
350
|
+
i += 2
|
|
351
|
+
}
|
|
352
|
+
while (i < to) {
|
|
353
|
+
const start = i
|
|
354
|
+
while (i < to && jfIsHex(jfUnitAt(s, i))) {
|
|
355
|
+
i += 1
|
|
356
|
+
}
|
|
357
|
+
if (i < to && jfUnitAt(s, i) === 46) {
|
|
358
|
+
// An IPv4 tail ends the address and fills two groups.
|
|
359
|
+
if (!jfIpv4Range(s, start, to)) {
|
|
360
|
+
return false
|
|
361
|
+
}
|
|
362
|
+
groups += 2
|
|
363
|
+
break
|
|
364
|
+
}
|
|
365
|
+
if (i === start || i - start > 4) {
|
|
366
|
+
return false
|
|
367
|
+
}
|
|
368
|
+
groups += 1
|
|
369
|
+
if (i === to) {
|
|
370
|
+
break
|
|
371
|
+
}
|
|
372
|
+
if (jfUnitAt(s, i) !== 58) {
|
|
373
|
+
return false
|
|
374
|
+
}
|
|
375
|
+
i += 1
|
|
376
|
+
if (i < to && jfUnitAt(s, i) === 58) {
|
|
377
|
+
if (elided) {
|
|
378
|
+
return false
|
|
379
|
+
}
|
|
380
|
+
elided = true
|
|
381
|
+
i += 1
|
|
382
|
+
} else if (i === to) {
|
|
383
|
+
return false
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return elided ? groups <= 7 : groups === 8
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** Whether `s` is a dotted-quad IPv4 address, `192.168.0.1`. */
|
|
390
|
+
export const isIpv4 = (s: string): boolean => jfIpv4Range(s, 0, toI32(s.length))
|
|
391
|
+
|
|
392
|
+
/** Whether `s` is an IPv6 address, `::ffff:192.168.0.1`. */
|
|
393
|
+
export const isIpv6 = (s: string): boolean => jfIpv6Range(s, 0, toI32(s.length))
|
|
394
|
+
|
|
395
|
+
const jfIsLdh = (c: i32): boolean => jfIsAlpha(c) || jfIsDigit(c) || c === 45
|
|
396
|
+
|
|
397
|
+
/** Whether `s[from, to)` is an RFC 1123 host name. */
|
|
398
|
+
const jfHostnameRange = (s: string, from: i32, to: i32): boolean => {
|
|
399
|
+
if (to <= from || to - from > 253) {
|
|
400
|
+
return false
|
|
401
|
+
}
|
|
402
|
+
let start = from
|
|
403
|
+
let i = from
|
|
404
|
+
while (i <= to) {
|
|
405
|
+
if (i === to || jfUnitAt(s, i) === 46) {
|
|
406
|
+
const n = i - start
|
|
407
|
+
if (n === 0 || n > 63 || jfUnitAt(s, start) === 45 || jfUnitAt(s, i - 1) === 45) {
|
|
408
|
+
return false
|
|
409
|
+
}
|
|
410
|
+
start = i + 1
|
|
411
|
+
} else if (!jfIsLdh(jfUnitAt(s, i))) {
|
|
412
|
+
return false
|
|
413
|
+
}
|
|
414
|
+
i += 1
|
|
415
|
+
}
|
|
416
|
+
return true
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** Whether `s` is a host name, `www.example.com`. */
|
|
420
|
+
export const isHostname = (s: string): boolean => jfHostnameRange(s, 0, toI32(s.length))
|
|
421
|
+
|
|
422
|
+
/** Whether `c` is RFC 5322's `atext`: a letter, a digit or one of ``!#$%&'*+-/=?^_`{|}~``. */
|
|
423
|
+
const jfIsAtext = (c: i32): boolean =>
|
|
424
|
+
jfIsAlpha(c) ||
|
|
425
|
+
jfIsDigit(c) ||
|
|
426
|
+
c === 33 ||
|
|
427
|
+
(c >= 35 && c <= 39) ||
|
|
428
|
+
c === 42 ||
|
|
429
|
+
c === 43 ||
|
|
430
|
+
c === 45 ||
|
|
431
|
+
c === 47 ||
|
|
432
|
+
c === 61 ||
|
|
433
|
+
c === 63 ||
|
|
434
|
+
(c >= 94 && c <= 96) ||
|
|
435
|
+
(c >= 123 && c <= 126)
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* The end of the RFC 5321 `Local-part` at the start of `s`, a dot-string or a
|
|
439
|
+
* quoted string, or -1 when it is neither.
|
|
440
|
+
*/
|
|
441
|
+
const jfLocalPartEnd = (s: string): i32 => {
|
|
442
|
+
let i: i32 = 0
|
|
443
|
+
if (jfUnitAt(s, 0) === 34) {
|
|
444
|
+
i = 1
|
|
445
|
+
while (true) {
|
|
446
|
+
const c = jfUnitAt(s, i)
|
|
447
|
+
if (c === 34) {
|
|
448
|
+
return i + 1
|
|
449
|
+
}
|
|
450
|
+
if (c === 92) {
|
|
451
|
+
const quoted = jfUnitAt(s, i + 1)
|
|
452
|
+
if (quoted < 32 || quoted > 126) {
|
|
453
|
+
return -1
|
|
454
|
+
}
|
|
455
|
+
i += 2
|
|
456
|
+
} else if (c === 32 || c === 33 || (c >= 35 && c <= 91) || (c >= 93 && c <= 126)) {
|
|
457
|
+
i += 1
|
|
458
|
+
} else {
|
|
459
|
+
return -1
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
while (true) {
|
|
464
|
+
const start = i
|
|
465
|
+
while (jfIsAtext(jfUnitAt(s, i))) {
|
|
466
|
+
i += 1
|
|
467
|
+
}
|
|
468
|
+
if (i === start) {
|
|
469
|
+
return -1
|
|
470
|
+
}
|
|
471
|
+
if (jfUnitAt(s, i) !== 46) {
|
|
472
|
+
return i
|
|
473
|
+
}
|
|
474
|
+
i += 1
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Whether `s[from, to)` is RFC 5321's general address literal content,
|
|
480
|
+
* `Standardized-tag ":" 1*dcontent`, for a tag other than `IPv6`.
|
|
481
|
+
*/
|
|
482
|
+
const jfGeneralLiteral = (s: string, from: i32, to: i32): boolean => {
|
|
483
|
+
let i = from
|
|
484
|
+
while (i < to && jfIsLdh(jfUnitAt(s, i))) {
|
|
485
|
+
i += 1
|
|
486
|
+
}
|
|
487
|
+
if (i === from || jfUnitAt(s, i - 1) === 45 || i >= to || jfUnitAt(s, i) !== 58) {
|
|
488
|
+
return false
|
|
489
|
+
}
|
|
490
|
+
i += 1
|
|
491
|
+
if (i === to) {
|
|
492
|
+
return false
|
|
493
|
+
}
|
|
494
|
+
while (i < to) {
|
|
495
|
+
const c = jfUnitAt(s, i)
|
|
496
|
+
if (c < 33 || (c > 90 && c < 94) || c > 126) {
|
|
497
|
+
return false
|
|
498
|
+
}
|
|
499
|
+
i += 1
|
|
500
|
+
}
|
|
501
|
+
return true
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/** Whether `s` is an RFC 5321 mailbox, `joe.bloggs@example.com`. */
|
|
505
|
+
export const isEmail = (s: string): boolean => {
|
|
506
|
+
const length: i32 = toI32(s.length)
|
|
507
|
+
const at = jfLocalPartEnd(s)
|
|
508
|
+
if (at < 0 || at > 64 || jfUnitAt(s, at) !== 64) {
|
|
509
|
+
return false
|
|
510
|
+
}
|
|
511
|
+
const from = at + 1
|
|
512
|
+
if (length - from > 255) {
|
|
513
|
+
return false
|
|
514
|
+
}
|
|
515
|
+
if (jfUnitAt(s, from) !== 91) {
|
|
516
|
+
return jfHostnameRange(s, from, length)
|
|
517
|
+
}
|
|
518
|
+
if (jfUnitAt(s, length - 1) !== 93) {
|
|
519
|
+
return false
|
|
520
|
+
}
|
|
521
|
+
const inner = from + 1
|
|
522
|
+
const end = length - 1
|
|
523
|
+
if (
|
|
524
|
+
end - inner > 5 &&
|
|
525
|
+
jfLower(jfUnitAt(s, inner)) === 105 &&
|
|
526
|
+
jfLower(jfUnitAt(s, inner + 1)) === 112 &&
|
|
527
|
+
jfLower(jfUnitAt(s, inner + 2)) === 118 &&
|
|
528
|
+
jfUnitAt(s, inner + 3) === 54 &&
|
|
529
|
+
jfUnitAt(s, inner + 4) === 58
|
|
530
|
+
) {
|
|
531
|
+
return jfIpv6Range(s, inner + 5, end)
|
|
532
|
+
}
|
|
533
|
+
return jfIpv4Range(s, inner, end) || jfGeneralLiteral(s, inner, end)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/** Whether `s` is a UUID, `2eb8aa08-aa98-11ea-b4aa-73b441d16380`. */
|
|
537
|
+
export const isUuid = (s: string): boolean => {
|
|
538
|
+
if (toI32(s.length) !== 36) {
|
|
539
|
+
return false
|
|
540
|
+
}
|
|
541
|
+
let i: i32 = 0
|
|
542
|
+
while (i < 36) {
|
|
543
|
+
const c = jfUnitAt(s, i)
|
|
544
|
+
if (i === 8 || i === 13 || i === 18 || i === 23 ? c !== 45 : !jfIsHex(c)) {
|
|
545
|
+
return false
|
|
546
|
+
}
|
|
547
|
+
i += 1
|
|
548
|
+
}
|
|
549
|
+
return true
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/** Whether host-side code units are UTF-8 bytes, as natively, or UTF-16 units, as under Node. */
|
|
553
|
+
const jfUnitsAreBytes = (): boolean => toI32("é".length) === 2
|
|
554
|
+
|
|
555
|
+
/** Whether `cp` is RFC 3987's `iprivate`. */
|
|
556
|
+
const jfIsPrivate = (cp: i32): boolean =>
|
|
557
|
+
(cp >= 0xe000 && cp <= 0xf8ff) || (cp >= 0xf0000 && (cp & 0xffff) <= 0xfffd)
|
|
558
|
+
|
|
559
|
+
/** Whether `cp` is RFC 3987's `ucschar`. */
|
|
560
|
+
const jfIsUcschar = (cp: i32): boolean => {
|
|
561
|
+
if (cp < 0x10000) {
|
|
562
|
+
return (cp >= 0xa0 && cp <= 0xd7ff) || (cp >= 0xf900 && cp <= 0xfdcf) || (cp >= 0xfdf0 && cp <= 0xffef)
|
|
563
|
+
}
|
|
564
|
+
// Planes 1 to 13 bar their last two code points; plane 14 starts at E1000.
|
|
565
|
+
return cp < 0xf0000 && (cp & 0xffff) <= 0xfffd && (cp < 0xe0000 || cp >= 0xe1000)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* The code units of the non-ASCII character of `s` at `i`, when it is a
|
|
570
|
+
* `ucschar`, or an `iprivate` where `privateOk`; else 0, for a character
|
|
571
|
+
* outside those ranges or units that do not encode one.
|
|
572
|
+
*/
|
|
573
|
+
const jfUcsWidth = (s: string, i: i32, privateOk: boolean): i32 => {
|
|
574
|
+
const lead = jfUnitAt(s, i)
|
|
575
|
+
let cp: i32 = 0
|
|
576
|
+
let width: i32 = 0
|
|
577
|
+
if (jfUnitsAreBytes()) {
|
|
578
|
+
// UTF-8: no overlong form, no surrogate, nothing past U+10FFFF.
|
|
579
|
+
const b1 = jfUnitAt(s, i + 1)
|
|
580
|
+
let low: i32 = 0x80
|
|
581
|
+
let high: i32 = 0xbf
|
|
582
|
+
if (lead >= 0xc2 && lead <= 0xdf) {
|
|
583
|
+
width = 2
|
|
584
|
+
cp = lead & 0x1f
|
|
585
|
+
} else if (lead >= 0xe0 && lead <= 0xef) {
|
|
586
|
+
width = 3
|
|
587
|
+
cp = lead & 0x0f
|
|
588
|
+
low = lead === 0xe0 ? 0xa0 : 0x80
|
|
589
|
+
high = lead === 0xed ? 0x9f : 0xbf
|
|
590
|
+
} else if (lead >= 0xf0 && lead <= 0xf4) {
|
|
591
|
+
width = 4
|
|
592
|
+
cp = lead & 0x07
|
|
593
|
+
low = lead === 0xf0 ? 0x90 : 0x80
|
|
594
|
+
high = lead === 0xf4 ? 0x8f : 0xbf
|
|
595
|
+
} else {
|
|
596
|
+
return 0
|
|
597
|
+
}
|
|
598
|
+
if (b1 < low || b1 > high) {
|
|
599
|
+
return 0
|
|
600
|
+
}
|
|
601
|
+
cp = (cp << 6) | (b1 & 0x3f)
|
|
602
|
+
let k: i32 = 2
|
|
603
|
+
while (k < width) {
|
|
604
|
+
const b = jfUnitAt(s, i + k)
|
|
605
|
+
if (b < 0x80 || b > 0xbf) {
|
|
606
|
+
return 0
|
|
607
|
+
}
|
|
608
|
+
cp = (cp << 6) | (b & 0x3f)
|
|
609
|
+
k += 1
|
|
610
|
+
}
|
|
611
|
+
} else if (lead >= 0xd800 && lead <= 0xdbff) {
|
|
612
|
+
const trail = jfUnitAt(s, i + 1)
|
|
613
|
+
if (trail < 0xdc00 || trail > 0xdfff) {
|
|
614
|
+
return 0
|
|
615
|
+
}
|
|
616
|
+
width = 2
|
|
617
|
+
cp = ((lead - 0xd800) << 10) + (trail - 0xdc00) + 0x10000
|
|
618
|
+
} else if (lead >= 0xdc00 && lead <= 0xdfff) {
|
|
619
|
+
return 0
|
|
620
|
+
} else {
|
|
621
|
+
width = 1
|
|
622
|
+
cp = lead
|
|
623
|
+
}
|
|
624
|
+
return jfIsUcschar(cp) || (privateOk && jfIsPrivate(cp)) ? width : 0
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const jfIsUnreserved = (c: i32): boolean =>
|
|
628
|
+
jfIsAlpha(c) || jfIsDigit(c) || c === 45 || c === 46 || c === 95 || c === 126
|
|
629
|
+
|
|
630
|
+
/** Whether `c` is RFC 3986's `sub-delims`: ``!$&'()*+,;=``. */
|
|
631
|
+
const jfIsSubDelim = (c: i32): boolean => c === 33 || c === 36 || (c >= 38 && c <= 44) || c === 59 || c === 61
|
|
632
|
+
|
|
633
|
+
/** Whether the ASCII `c` belongs to the URI character class `set` (a `JF_` constant). */
|
|
634
|
+
const jfInSet = (c: i32, set: i32): boolean => {
|
|
635
|
+
if (jfIsUnreserved(c) || jfIsSubDelim(c)) {
|
|
636
|
+
return true
|
|
637
|
+
}
|
|
638
|
+
if (set === JF_REG_NAME) {
|
|
639
|
+
return false
|
|
640
|
+
}
|
|
641
|
+
if (c === 58) {
|
|
642
|
+
return true
|
|
643
|
+
}
|
|
644
|
+
if (set === JF_USERINFO) {
|
|
645
|
+
return false
|
|
646
|
+
}
|
|
647
|
+
if (c === 64) {
|
|
648
|
+
return true
|
|
649
|
+
}
|
|
650
|
+
if (set === JF_SEGMENT) {
|
|
651
|
+
return false
|
|
652
|
+
}
|
|
653
|
+
return c === 47 || (set !== JF_PATH && c === 63)
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* The code units of one character of the class `set` at `i` of `s`: 1 for an
|
|
658
|
+
* ASCII member, 3 for `%` and two hex digits, the character's width for one
|
|
659
|
+
* past ASCII where `iri` allows it, and 0 where none fits.
|
|
660
|
+
*/
|
|
661
|
+
const jfUriUnit = (s: string, i: i32, set: i32, iri: boolean): i32 => {
|
|
662
|
+
const c = jfUnitAt(s, i)
|
|
663
|
+
if (c < 0) {
|
|
664
|
+
return 0
|
|
665
|
+
}
|
|
666
|
+
if (c === 37) {
|
|
667
|
+
return jfIsHex(jfUnitAt(s, i + 1)) && jfIsHex(jfUnitAt(s, i + 2)) ? 3 : 0
|
|
668
|
+
}
|
|
669
|
+
if (c < 0x80) {
|
|
670
|
+
return jfInSet(c, set) ? 1 : 0
|
|
671
|
+
}
|
|
672
|
+
return iri ? jfUcsWidth(s, i, set === JF_QUERY) : 0
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/** Whether every character of `s[from, to)` belongs to the URI class `set`. */
|
|
676
|
+
const jfUriRun = (s: string, from: i32, to: i32, set: i32, iri: boolean): boolean => {
|
|
677
|
+
let i = from
|
|
678
|
+
while (i < to) {
|
|
679
|
+
const w = jfUriUnit(s, i, set, iri)
|
|
680
|
+
if (w === 0) {
|
|
681
|
+
return false
|
|
682
|
+
}
|
|
683
|
+
i += w
|
|
684
|
+
}
|
|
685
|
+
return i === to
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/** The first index in `[from, to)` whose unit is `a` or `b`, or `to`. */
|
|
689
|
+
const jfFind = (s: string, from: i32, to: i32, a: i32, b: i32): i32 => {
|
|
690
|
+
let i = from
|
|
691
|
+
while (i < to) {
|
|
692
|
+
const u = jfUnitAt(s, i)
|
|
693
|
+
if (u === a || u === b) {
|
|
694
|
+
return i
|
|
695
|
+
}
|
|
696
|
+
i += 1
|
|
697
|
+
}
|
|
698
|
+
return to
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Whether `s[from, to)` is RFC 3986's `IP-literal` content: an IPv6 address
|
|
703
|
+
* or `IPvFuture`, `"v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )`.
|
|
704
|
+
*/
|
|
705
|
+
const jfIpLiteral = (s: string, from: i32, to: i32): boolean => {
|
|
706
|
+
if (jfLower(jfUnitAt(s, from)) !== 118) {
|
|
707
|
+
return jfIpv6Range(s, from, to)
|
|
708
|
+
}
|
|
709
|
+
let i = from + 1
|
|
710
|
+
while (i < to && jfIsHex(jfUnitAt(s, i))) {
|
|
711
|
+
i += 1
|
|
712
|
+
}
|
|
713
|
+
if (i === from + 1 || i >= to || jfUnitAt(s, i) !== 46 || i + 1 === to) {
|
|
714
|
+
return false
|
|
715
|
+
}
|
|
716
|
+
return jfUriRun(s, i + 1, to, JF_USERINFO, false)
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/** Whether `s[from, to)` is an RFC 3986 `authority`: `[ userinfo "@" ] host [ ":" port ]`. */
|
|
720
|
+
const jfAuthority = (s: string, from: i32, to: i32, iri: boolean): boolean => {
|
|
721
|
+
// `userinfo` cannot hold an `@`, so the first one ends it.
|
|
722
|
+
const at = jfFind(s, from, to, 64, 64)
|
|
723
|
+
let host = from
|
|
724
|
+
if (at < to) {
|
|
725
|
+
if (!jfUriRun(s, from, at, JF_USERINFO, iri)) {
|
|
726
|
+
return false
|
|
727
|
+
}
|
|
728
|
+
host = at + 1
|
|
729
|
+
}
|
|
730
|
+
let port: i32 = 0
|
|
731
|
+
if (jfUnitAt(s, host) === 91) {
|
|
732
|
+
const close = jfFind(s, host, to, 93, 93)
|
|
733
|
+
if (close === to || !jfIpLiteral(s, host + 1, close)) {
|
|
734
|
+
return false
|
|
735
|
+
}
|
|
736
|
+
port = close + 1
|
|
737
|
+
} else {
|
|
738
|
+
// A `reg-name` cannot hold a `:`, so the first one starts the port; an
|
|
739
|
+
// IPv4 address is a `reg-name` too, so it needs no check of its own.
|
|
740
|
+
port = jfFind(s, host, to, 58, 58)
|
|
741
|
+
if (!jfUriRun(s, host, port, JF_REG_NAME, iri)) {
|
|
742
|
+
return false
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
if (port === to) {
|
|
746
|
+
return true
|
|
747
|
+
}
|
|
748
|
+
if (jfUnitAt(s, port) !== 58) {
|
|
749
|
+
return false
|
|
750
|
+
}
|
|
751
|
+
let i = port + 1
|
|
752
|
+
while (i < to) {
|
|
753
|
+
if (!jfIsDigit(jfUnitAt(s, i))) {
|
|
754
|
+
return false
|
|
755
|
+
}
|
|
756
|
+
i += 1
|
|
757
|
+
}
|
|
758
|
+
return true
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Whether `s` is a URI, or with `reference` a URI reference, and with `iri`
|
|
763
|
+
* the IRI forms of either: RFC 3986 §3 and §4.1, and RFC 3987 §2.2.
|
|
764
|
+
*/
|
|
765
|
+
const jfUriCheck = (s: string, iri: boolean, reference: boolean): boolean => {
|
|
766
|
+
const length: i32 = toI32(s.length)
|
|
767
|
+
let i: i32 = 0
|
|
768
|
+
if (jfIsAlpha(jfUnitAt(s, 0))) {
|
|
769
|
+
let j: i32 = 1
|
|
770
|
+
while (true) {
|
|
771
|
+
const c = jfUnitAt(s, j)
|
|
772
|
+
if (!(jfIsAlpha(c) || jfIsDigit(c) || c === 43 || c === 45 || c === 46)) {
|
|
773
|
+
break
|
|
774
|
+
}
|
|
775
|
+
j += 1
|
|
776
|
+
}
|
|
777
|
+
if (jfUnitAt(s, j) === 58) {
|
|
778
|
+
i = j + 1
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
if (i === 0 && !reference) {
|
|
782
|
+
return false
|
|
783
|
+
}
|
|
784
|
+
const pathEnd = jfFind(s, i, length, 63, 35)
|
|
785
|
+
if (jfUnitAt(s, i) === 47 && jfUnitAt(s, i + 1) === 47) {
|
|
786
|
+
const authorityEnd = jfFind(s, i + 2, pathEnd, 47, 47)
|
|
787
|
+
if (!jfAuthority(s, i + 2, authorityEnd, iri)) {
|
|
788
|
+
return false
|
|
789
|
+
}
|
|
790
|
+
i = authorityEnd
|
|
791
|
+
} else if (i === 0) {
|
|
792
|
+
// `path-noscheme`: a relative path's first segment cannot hold a `:`,
|
|
793
|
+
// or it would read as a scheme.
|
|
794
|
+
const firstSegmentEnd = jfFind(s, i, pathEnd, 47, 47)
|
|
795
|
+
if (jfFind(s, i, firstSegmentEnd, 58, 58) < firstSegmentEnd) {
|
|
796
|
+
return false
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
if (!jfUriRun(s, i, pathEnd, JF_PATH, iri)) {
|
|
800
|
+
return false
|
|
801
|
+
}
|
|
802
|
+
i = pathEnd
|
|
803
|
+
if (jfUnitAt(s, i) === 63) {
|
|
804
|
+
const queryEnd = jfFind(s, i + 1, length, 35, 35)
|
|
805
|
+
if (!jfUriRun(s, i + 1, queryEnd, JF_QUERY, iri)) {
|
|
806
|
+
return false
|
|
807
|
+
}
|
|
808
|
+
i = queryEnd
|
|
809
|
+
}
|
|
810
|
+
if (jfUnitAt(s, i) === 35) {
|
|
811
|
+
return jfUriRun(s, i + 1, length, JF_FRAGMENT, iri)
|
|
812
|
+
}
|
|
813
|
+
return true
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/** Whether `s` is an absolute URI, `http://example.com/a?b#c`. */
|
|
817
|
+
export const isUri = (s: string): boolean => jfUriCheck(s, false, false)
|
|
818
|
+
|
|
819
|
+
/** Whether `s` is a URI or a relative reference, `../a?b#c`. */
|
|
820
|
+
export const isUriReference = (s: string): boolean => jfUriCheck(s, false, true)
|
|
821
|
+
|
|
822
|
+
/** Whether `s` is an absolute IRI, `http://ƒøø.ßår/?∂éœ=πîx`. */
|
|
823
|
+
export const isIri = (s: string): boolean => jfUriCheck(s, true, false)
|
|
824
|
+
|
|
825
|
+
/** Whether `s` is an IRI or a relative IRI reference. */
|
|
826
|
+
export const isIriReference = (s: string): boolean => jfUriCheck(s, true, true)
|
|
827
|
+
|
|
828
|
+
/** The code units of RFC 6570's `varchar` at `i` of `s`, `ALPHA / DIGIT / "_" / pct-encoded`, or 0. */
|
|
829
|
+
const jfVarchar = (s: string, i: i32): i32 => {
|
|
830
|
+
const c = jfUnitAt(s, i)
|
|
831
|
+
if (jfIsAlpha(c) || jfIsDigit(c) || c === 95) {
|
|
832
|
+
return 1
|
|
833
|
+
}
|
|
834
|
+
return c === 37 && jfIsHex(jfUnitAt(s, i + 1)) && jfIsHex(jfUnitAt(s, i + 2)) ? 3 : 0
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* The end of the RFC 6570 `expression` whose `{` is at `open`, or -1 when the
|
|
839
|
+
* text there is not one: an operator, then `varspec`s separated by commas,
|
|
840
|
+
* each a `varname` and a `:` prefix of 1 to 9999 or a `*`.
|
|
841
|
+
*/
|
|
842
|
+
const jfExpressionEnd = (s: string, open: i32): i32 => {
|
|
843
|
+
let i = open + 1
|
|
844
|
+
const op = jfUnitAt(s, i)
|
|
845
|
+
if (jfUnitIndex(op, "+#./;?&=,!@|") >= 0) {
|
|
846
|
+
i += 1
|
|
847
|
+
}
|
|
848
|
+
while (true) {
|
|
849
|
+
let w = jfVarchar(s, i)
|
|
850
|
+
if (w === 0) {
|
|
851
|
+
return -1
|
|
852
|
+
}
|
|
853
|
+
i += w
|
|
854
|
+
while (true) {
|
|
855
|
+
if (jfUnitAt(s, i) === 46) {
|
|
856
|
+
w = jfVarchar(s, i + 1)
|
|
857
|
+
if (w === 0) {
|
|
858
|
+
return -1
|
|
859
|
+
}
|
|
860
|
+
i += 1 + w
|
|
861
|
+
} else {
|
|
862
|
+
w = jfVarchar(s, i)
|
|
863
|
+
if (w === 0) {
|
|
864
|
+
break
|
|
865
|
+
}
|
|
866
|
+
i += w
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
const modifier = jfUnitAt(s, i)
|
|
870
|
+
if (modifier === 58) {
|
|
871
|
+
const first = jfUnitAt(s, i + 1)
|
|
872
|
+
if (first < 49 || first > 57) {
|
|
873
|
+
return -1
|
|
874
|
+
}
|
|
875
|
+
i += 2
|
|
876
|
+
let more: i32 = 0
|
|
877
|
+
while (more < 3 && jfIsDigit(jfUnitAt(s, i))) {
|
|
878
|
+
i += 1
|
|
879
|
+
more += 1
|
|
880
|
+
}
|
|
881
|
+
} else if (modifier === 42) {
|
|
882
|
+
i += 1
|
|
883
|
+
}
|
|
884
|
+
const next = jfUnitAt(s, i)
|
|
885
|
+
if (next === 125) {
|
|
886
|
+
return i + 1
|
|
887
|
+
}
|
|
888
|
+
if (next !== 44) {
|
|
889
|
+
return -1
|
|
890
|
+
}
|
|
891
|
+
i += 1
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* Whether the ASCII `c` is RFC 6570's `literals`: anything but a control,
|
|
897
|
+
* space and ``"'%<>\^`{|}``.
|
|
898
|
+
*/
|
|
899
|
+
const jfIsTemplateLiteral = (c: i32): boolean =>
|
|
900
|
+
c === 33 ||
|
|
901
|
+
c === 35 ||
|
|
902
|
+
c === 36 ||
|
|
903
|
+
c === 38 ||
|
|
904
|
+
(c >= 40 && c <= 59) ||
|
|
905
|
+
c === 61 ||
|
|
906
|
+
(c >= 63 && c <= 91) ||
|
|
907
|
+
c === 93 ||
|
|
908
|
+
c === 95 ||
|
|
909
|
+
(c >= 97 && c <= 122) ||
|
|
910
|
+
c === 126
|
|
911
|
+
|
|
912
|
+
/** Whether `s` is an RFC 6570 URI template, `http://example.com/{term:1}/{term}`. */
|
|
913
|
+
export const isUriTemplate = (s: string): boolean => {
|
|
914
|
+
const length: i32 = toI32(s.length)
|
|
915
|
+
let i: i32 = 0
|
|
916
|
+
while (i < length) {
|
|
917
|
+
const c = jfUnitAt(s, i)
|
|
918
|
+
if (c === 123) {
|
|
919
|
+
i = jfExpressionEnd(s, i)
|
|
920
|
+
if (i < 0) {
|
|
921
|
+
return false
|
|
922
|
+
}
|
|
923
|
+
} else if (c === 37) {
|
|
924
|
+
if (!jfIsHex(jfUnitAt(s, i + 1)) || !jfIsHex(jfUnitAt(s, i + 2))) {
|
|
925
|
+
return false
|
|
926
|
+
}
|
|
927
|
+
i += 3
|
|
928
|
+
} else if (c < 0x80) {
|
|
929
|
+
if (!jfIsTemplateLiteral(c)) {
|
|
930
|
+
return false
|
|
931
|
+
}
|
|
932
|
+
i += 1
|
|
933
|
+
} else {
|
|
934
|
+
const w = jfUcsWidth(s, i, true)
|
|
935
|
+
if (w === 0) {
|
|
936
|
+
return false
|
|
937
|
+
}
|
|
938
|
+
i += w
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
return true
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* Whether `s` is a regular expression `nish/regex` compiles with no flags.
|
|
946
|
+
* Lookaround, backreferences and what the `u` flag refuses are answered
|
|
947
|
+
* false, though ECMA-262 accepts them (see the module header).
|
|
948
|
+
*/
|
|
949
|
+
export const isRegex = (s: string): boolean => regexCompile(s, "").isOk()
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* The id of the format `name` for `jsonFormatCheck`, one of the
|
|
953
|
+
* `JSON_FORMAT_*` ids, or -1 for a format this module does not check.
|
|
954
|
+
*/
|
|
955
|
+
export const jsonFormatId = (name: string): i32 => {
|
|
956
|
+
if (name === "date-time") {
|
|
957
|
+
return JSON_FORMAT_DATE_TIME
|
|
958
|
+
}
|
|
959
|
+
if (name === "date") {
|
|
960
|
+
return JSON_FORMAT_DATE
|
|
961
|
+
}
|
|
962
|
+
if (name === "time") {
|
|
963
|
+
return JSON_FORMAT_TIME
|
|
964
|
+
}
|
|
965
|
+
if (name === "duration") {
|
|
966
|
+
return JSON_FORMAT_DURATION
|
|
967
|
+
}
|
|
968
|
+
if (name === "email") {
|
|
969
|
+
return JSON_FORMAT_EMAIL
|
|
970
|
+
}
|
|
971
|
+
if (name === "hostname") {
|
|
972
|
+
return JSON_FORMAT_HOSTNAME
|
|
973
|
+
}
|
|
974
|
+
if (name === "ipv4") {
|
|
975
|
+
return JSON_FORMAT_IPV4
|
|
976
|
+
}
|
|
977
|
+
if (name === "ipv6") {
|
|
978
|
+
return JSON_FORMAT_IPV6
|
|
979
|
+
}
|
|
980
|
+
if (name === "uuid") {
|
|
981
|
+
return JSON_FORMAT_UUID
|
|
982
|
+
}
|
|
983
|
+
if (name === "uri") {
|
|
984
|
+
return JSON_FORMAT_URI
|
|
985
|
+
}
|
|
986
|
+
if (name === "uri-reference") {
|
|
987
|
+
return JSON_FORMAT_URI_REFERENCE
|
|
988
|
+
}
|
|
989
|
+
if (name === "iri") {
|
|
990
|
+
return JSON_FORMAT_IRI
|
|
991
|
+
}
|
|
992
|
+
if (name === "iri-reference") {
|
|
993
|
+
return JSON_FORMAT_IRI_REFERENCE
|
|
994
|
+
}
|
|
995
|
+
if (name === "uri-template") {
|
|
996
|
+
return JSON_FORMAT_URI_TEMPLATE
|
|
997
|
+
}
|
|
998
|
+
if (name === "regex") {
|
|
999
|
+
return JSON_FORMAT_REGEX
|
|
1000
|
+
}
|
|
1001
|
+
return -1
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/** Whether `value` passes the format `id`, which is one of the `JSON_FORMAT_*` ids. */
|
|
1005
|
+
const jfPasses = (id: i32, value: string): boolean => {
|
|
1006
|
+
if (id === JSON_FORMAT_DATE_TIME) {
|
|
1007
|
+
return isDateTime(value)
|
|
1008
|
+
}
|
|
1009
|
+
if (id === JSON_FORMAT_DATE) {
|
|
1010
|
+
return isDate(value)
|
|
1011
|
+
}
|
|
1012
|
+
if (id === JSON_FORMAT_TIME) {
|
|
1013
|
+
return isTime(value)
|
|
1014
|
+
}
|
|
1015
|
+
if (id === JSON_FORMAT_DURATION) {
|
|
1016
|
+
return isDuration(value)
|
|
1017
|
+
}
|
|
1018
|
+
if (id === JSON_FORMAT_EMAIL) {
|
|
1019
|
+
return isEmail(value)
|
|
1020
|
+
}
|
|
1021
|
+
if (id === JSON_FORMAT_HOSTNAME) {
|
|
1022
|
+
return isHostname(value)
|
|
1023
|
+
}
|
|
1024
|
+
if (id === JSON_FORMAT_IPV4) {
|
|
1025
|
+
return isIpv4(value)
|
|
1026
|
+
}
|
|
1027
|
+
if (id === JSON_FORMAT_IPV6) {
|
|
1028
|
+
return isIpv6(value)
|
|
1029
|
+
}
|
|
1030
|
+
if (id === JSON_FORMAT_UUID) {
|
|
1031
|
+
return isUuid(value)
|
|
1032
|
+
}
|
|
1033
|
+
if (id === JSON_FORMAT_URI) {
|
|
1034
|
+
return isUri(value)
|
|
1035
|
+
}
|
|
1036
|
+
if (id === JSON_FORMAT_URI_REFERENCE) {
|
|
1037
|
+
return isUriReference(value)
|
|
1038
|
+
}
|
|
1039
|
+
if (id === JSON_FORMAT_IRI) {
|
|
1040
|
+
return isIri(value)
|
|
1041
|
+
}
|
|
1042
|
+
if (id === JSON_FORMAT_IRI_REFERENCE) {
|
|
1043
|
+
return isIriReference(value)
|
|
1044
|
+
}
|
|
1045
|
+
if (id === JSON_FORMAT_URI_TEMPLATE) {
|
|
1046
|
+
return isUriTemplate(value)
|
|
1047
|
+
}
|
|
1048
|
+
return isRegex(value)
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
/**
|
|
1052
|
+
* `value` checked against the format `id` from `jsonFormatId`:
|
|
1053
|
+
* `JSON_FORMAT_VALID` or `JSON_FORMAT_INVALID`, or `JSON_FORMAT_UNKNOWN`
|
|
1054
|
+
* for an id that names no format, -1 included.
|
|
1055
|
+
*/
|
|
1056
|
+
export const jsonFormatCheck = (id: i32, value: string): i32 => {
|
|
1057
|
+
if (id < JSON_FORMAT_DATE_TIME || id > JSON_FORMAT_REGEX) {
|
|
1058
|
+
return JSON_FORMAT_UNKNOWN
|
|
1059
|
+
}
|
|
1060
|
+
return jfPasses(id, value) ? JSON_FORMAT_VALID : JSON_FORMAT_INVALID
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* `value` checked against the format named `name`: `JSON_FORMAT_VALID`,
|
|
1065
|
+
* `JSON_FORMAT_INVALID`, or `JSON_FORMAT_UNKNOWN` for a format this module
|
|
1066
|
+
* does not check. A validator that checks many instances against one schema
|
|
1067
|
+
* resolves the name once with `jsonFormatId` and calls `jsonFormatCheck`.
|
|
1068
|
+
*/
|
|
1069
|
+
export const jsonFormatValidate = (name: string, value: string): i32 =>
|
|
1070
|
+
jsonFormatCheck(jsonFormatId(name), value)
|