@amritk/nish-x86_64-linux 0.20.0 → 0.22.1
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 +52 -10
- package/bin/nish +0 -0
- package/docs/AI.md +2777 -0
- package/package.json +2 -1
- package/runtime/browser.d.ts +6 -0
- package/runtime/browser.mjs +77 -0
- package/runtime/core/arrays.d.ts +36 -0
- package/runtime/core/arrays.mjs +153 -0
- package/runtime/core/checks.mjs +20 -0
- package/runtime/core/host.d.ts +80 -0
- package/runtime/core/host.mjs +325 -0
- package/runtime/core/numbers.d.ts +66 -0
- package/runtime/core/numbers.mjs +343 -0
- package/runtime/core/result.d.ts +45 -0
- package/runtime/core/result.mjs +140 -0
- package/runtime/core/strings.d.ts +14 -0
- package/runtime/core/strings.mjs +169 -0
- package/runtime/core.d.ts +9 -0
- package/runtime/core.mjs +55 -0
- package/runtime/host.d.ts +6 -0
- package/runtime/nish.d.ts +46 -2
- package/runtime/nish.h +75 -2
- package/runtime/nish.mjs +23 -3
- package/runtime/node.mjs +386 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +1 -1
- package/runtime/runtime-host.c +1 -1
- package/runtime/runtime-net.c +12 -1
- package/runtime/runtime-os.c +1 -1
- package/runtime/runtime-parallel.c +1 -1
- package/runtime/runtime-simd.c +231 -7
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +0 -11
- package/runtime/runtime.c +190 -6
- package/runtime/shim.mjs +6 -985
- package/runtime/sqlite/LICENSE.md +85 -0
- package/runtime/sqlite/README.md +45 -0
- package/runtime/sqlite/nish-sqlite.c +359 -0
- package/runtime/sqlite/sqlite3.c +271131 -0
- package/runtime/sqlite/sqlite3.h +14447 -0
- package/scripts/build.sh +311 -59
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +32 -12
- package/std/base64.ts +116 -0
- package/std/bytes.ts +256 -0
- package/std/cli.ts +344 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +1557 -0
- package/std/crypto/hmac.ts +138 -2
- package/std/crypto/x509.ts +11 -122
- package/std/json-tokenizer.ts +2998 -57
- package/std/json-value.ts +22 -3
- package/std/json.ts +87 -8
- package/std/math.ts +608 -0
- package/std/net/http1-server.ts +282 -40
- package/std/net/http1.ts +40 -21
- package/std/net/http3-server.ts +50 -12
- package/std/net/postgres.ts +2294 -0
- package/std/net/prefork.ts +23 -3
- package/std/net/quic-listener.ts +20 -4
- package/std/random.ts +66 -4
- package/std/regex.ts +4274 -0
- package/std/sqlite.ts +391 -0
- package/std/term-io.ts +978 -0
- package/std/term-pixels.ts +874 -0
- package/std/term-probe.ts +459 -0
- package/std/term-rate.ts +993 -0
- package/std/term.ts +2453 -0
- package/std/text.ts +90 -7
- package/std/yaml-lines.ts +141 -0
- package/std/yaml.ts +4675 -0
package/std/cli.ts
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `std/cli` — a command-line tool's help menu: whether a stream gets colour,
|
|
3
|
+
* the escapes that paint it, and the rows a `--help` is made of.
|
|
4
|
+
*
|
|
5
|
+
* import { HelpMenu, agentDriven, styleFor } from "nish/cli";
|
|
6
|
+
*
|
|
7
|
+
* const menu = new HelpMenu(styleFor(1, agentDriven()), "greet", 30, 40);
|
|
8
|
+
* menu.header("1.0.0", "say hello");
|
|
9
|
+
* menu.usage("<name> [flags]", "greet someone");
|
|
10
|
+
* menu.section("Flags");
|
|
11
|
+
* menu.row("--shout", "", "in capitals");
|
|
12
|
+
* menu.row("--times", "<n>", "say it n times");
|
|
13
|
+
* console.log(menu.text());
|
|
14
|
+
*
|
|
15
|
+
* **The terminal's own colours.** A `Style` paints with the eight basic SGR
|
|
16
|
+
* colours, bold and faint, never a fixed RGB or 256-palette tone. Every theme
|
|
17
|
+
* redefines those eight, so the menu comes out in whatever palette the reader
|
|
18
|
+
* chose, and a terminal with no theme set paints them in its built-in
|
|
19
|
+
* defaults. A terminal with no colours at all (`tput colors` says fewer than
|
|
20
|
+
* eight) gets the failsafe: the same structure in bold and underline, which a
|
|
21
|
+
* monochrome terminal still shows. Colour that is forced is the theme's.
|
|
22
|
+
*
|
|
23
|
+
* **Colour is for a person reading a terminal**, and nothing else may see it:
|
|
24
|
+
* a script that captures `--help`, a test that compares it, a pipe into `less`
|
|
25
|
+
* without `-R` all want the plain bytes. So `styleFor` decides per stream, by
|
|
26
|
+
* asking whether that stream is a terminal, and the conventions people already
|
|
27
|
+
* set win over the guess: `NO_COLOR` (https://no-color.org) turns it off, and
|
|
28
|
+
* `CLICOLOR_FORCE` or `FORCE_COLOR` turns it on for a pager that understands
|
|
29
|
+
* escapes. The plain text is the same bytes either way, and a row's padding is
|
|
30
|
+
* counted on the plain text, so the columns line up with or without escapes.
|
|
31
|
+
*
|
|
32
|
+
* **An agent is a reader of its own kind**: it may drive a pseudo-terminal, so
|
|
33
|
+
* the terminal check says yes, but every escape is tokens it pays for and
|
|
34
|
+
* reads past. `agentDriven` says when one is at the keyboard, and `styleFor`
|
|
35
|
+
* is then plain whatever forcing variable its shell inherited.
|
|
36
|
+
*
|
|
37
|
+
* **What it reaches.** `Style` and `HelpMenu` reach no capability. `styleFor`
|
|
38
|
+
* reads the environment and, on the terminal check, forks a shell
|
|
39
|
+
* (`env` and `process.spawn`),
|
|
40
|
+
* and `agentDriven` reads the environment; a program that builds its `Style`
|
|
41
|
+
* by hand reaches neither.
|
|
42
|
+
*
|
|
43
|
+
* **The compiler's own help is built with it**, through a relative import from
|
|
44
|
+
* `src/cli-style.ts`, so this file is compiled by the last release as part of
|
|
45
|
+
* the compiler: it may use only what that release compiles (the rolling
|
|
46
|
+
* freeze, `.claude/selfhost.md`), which is why it asks the shell whether a
|
|
47
|
+
* descriptor is a terminal rather than calling `nish:term`'s `isTerminal`.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/** The Control Sequence Introducer every SGR escape opens with. */
|
|
51
|
+
const CSI: string = "\u001b["
|
|
52
|
+
|
|
53
|
+
/** `paletteOf`'s answer: marked up in the theme's colours. */
|
|
54
|
+
const PALETTE_THEME: i32 = 0
|
|
55
|
+
|
|
56
|
+
/** `paletteOf`'s answer: marked up in attributes only, the failsafe. */
|
|
57
|
+
const PALETTE_MONO: i32 = 2
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* How one output stream is painted. `on` says whether to mark the text up at
|
|
61
|
+
* all, and `colour` whether the terminal has colours to mark it up with;
|
|
62
|
+
* without them each mark falls back to bold, underline or nothing.
|
|
63
|
+
*/
|
|
64
|
+
export class Style {
|
|
65
|
+
on: boolean
|
|
66
|
+
colour: boolean
|
|
67
|
+
|
|
68
|
+
constructor(on: boolean, colour: boolean) {
|
|
69
|
+
this.on = on
|
|
70
|
+
this.colour = colour
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* `text` wrapped in the escape for `themed` (an SGR parameter list in the
|
|
75
|
+
* theme's basic colours) or `mono` (attributes only), and a reset after it;
|
|
76
|
+
* `text` unchanged when marking up is off or the chosen list is empty, so a
|
|
77
|
+
* caller never has to ask.
|
|
78
|
+
*/
|
|
79
|
+
paint(text: string, themed: string, mono: string): string {
|
|
80
|
+
const code = this.colour ? themed : mono
|
|
81
|
+
if (!this.on || text.length === 0 || code.length === 0) {
|
|
82
|
+
return text
|
|
83
|
+
}
|
|
84
|
+
return `${CSI}${code}m${text}${CSI}0m`
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** A section heading: bold, in the theme's magenta. */
|
|
88
|
+
heading(text: string): string {
|
|
89
|
+
return this.paint(text, "1;35", "1")
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The tool's own name where it opens a line. */
|
|
93
|
+
name(text: string): string {
|
|
94
|
+
return this.paint(text, "1;35", "1")
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** A flag, or a command a person would type. */
|
|
98
|
+
flag(text: string): string {
|
|
99
|
+
return this.paint(text, "36", "1")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** A placeholder the person fills in: `<file.ts>`, `speed|size`. */
|
|
103
|
+
meta(text: string): string {
|
|
104
|
+
return this.paint(text, "33", "4")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Text that explains rather than names: descriptions, notes. Faint rather
|
|
109
|
+
* than bright black, which Solarized makes the background; plain without
|
|
110
|
+
* colour, where the bold flags already stand apart from it.
|
|
111
|
+
*/
|
|
112
|
+
dim(text: string): string {
|
|
113
|
+
return this.paint(text, "2", "")
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Whether the environment variable `name` is set to something other than empty. */
|
|
118
|
+
const envIsSet = (name: string): boolean => {
|
|
119
|
+
const value = getenv(name)
|
|
120
|
+
return value !== null && value.length > 0
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Whether `value` spells `word`, which is lower case, in any ASCII case. */
|
|
124
|
+
const spells = (value: string, word: string): boolean => {
|
|
125
|
+
if (value.length !== word.length) {
|
|
126
|
+
return false
|
|
127
|
+
}
|
|
128
|
+
// Both bounds, though the lengths match: the checker proves an index in range
|
|
129
|
+
// only for a string its loop condition names.
|
|
130
|
+
for (let i: i32 = 0; i < toI32(value.length) && i < toI32(word.length); i++) {
|
|
131
|
+
let code: i32 = toI32(value.charCodeAt(i))
|
|
132
|
+
if (code >= 65 && code <= 90) {
|
|
133
|
+
code = code + 32
|
|
134
|
+
}
|
|
135
|
+
if (code !== toI32(word.charCodeAt(i))) {
|
|
136
|
+
return false
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return true
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Whether `value` is a no: `0`, `false`, `no` or `off`, in any case. */
|
|
143
|
+
const isNo = (value: string): boolean =>
|
|
144
|
+
value === "0" || spells(value, "false") || spells(value, "no") || spells(value, "off")
|
|
145
|
+
|
|
146
|
+
/** Whether a variable's `value` is set to a no (`isNo`). */
|
|
147
|
+
const saysNo = (value: string | null): boolean => value !== null && isNo(value)
|
|
148
|
+
|
|
149
|
+
/** Whether a variable's `value` is set and says yes: not empty, and not a no. */
|
|
150
|
+
const saysYes = (value: string | null): boolean => value !== null && value.length > 0 && !isNo(value)
|
|
151
|
+
|
|
152
|
+
/** Whether the variable `name` is set to a no (`saysNo`). */
|
|
153
|
+
const envSaysNo = (name: string): boolean => saysNo(getenv(name))
|
|
154
|
+
|
|
155
|
+
/** Whether the variable `name` says yes (`saysYes`). */
|
|
156
|
+
const envSaysYes = (name: string): boolean => saysYes(getenv(name))
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* What terminal descriptor `fd` is: `PALETTE_THEME`, `PALETTE_MONO`, or
|
|
160
|
+
* anything else for no terminal, which is plain text. The shell answers: a
|
|
161
|
+
* child inherits the descriptors and the environment, `[ -t 1 ]` is POSIX,
|
|
162
|
+
* and `tput colors` reads the count `TERM`'s terminfo entry declares (`-1` for
|
|
163
|
+
* none). `tput` is run by absolute path, never looked up on `PATH`, so a
|
|
164
|
+
* `PATH` with `.` in it cannot run a `tput` planted in the working directory
|
|
165
|
+
* (`docs/security/cli.md`). When there is no `tput` there, or it does not know
|
|
166
|
+
* `TERM`, the answer is the theme: almost every terminal has colours, and an
|
|
167
|
+
* escape a monochrome one does not know it drops. It costs one fork of the
|
|
168
|
+
* shell and, on a terminal, one of `tput`. Where nothing can be spawned (a
|
|
169
|
+
* WebAssembly build) the answer is `-1`, which is plain text, the safe side.
|
|
170
|
+
*/
|
|
171
|
+
const paletteOf = (fd: i32): i32 => {
|
|
172
|
+
const tty = `[ -t ${fd} ] || exit 1; `
|
|
173
|
+
const find = "t=/usr/bin/tput; [ -x $t ] || t=/bin/tput; [ -x $t ] || exit 0; "
|
|
174
|
+
const count = "c=$($t colors 2>/dev/null) || exit 0; case $c in ''|*[!0-9-]*) exit 0;; esac; "
|
|
175
|
+
return toI32(spawnSync(["/bin/sh", "-c", `${tty}${find}${count}[ "$c" -ge 8 ] || exit ${PALETTE_MONO}`]))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The style for a `paletteOf` answer: the theme, the failsafe, or plain. */
|
|
179
|
+
const styleOfPalette = (palette: i32): Style => {
|
|
180
|
+
if (palette === PALETTE_THEME) {
|
|
181
|
+
return new Style(true, true)
|
|
182
|
+
}
|
|
183
|
+
return new Style(palette === PALETTE_MONO, false)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Whether an agent, rather than a person, is running the program.
|
|
188
|
+
* `NISH_AGENT` answers for any agent: a no (`envSaysNo`) for no, any other
|
|
189
|
+
* value for yes. Without it, the variables the agents that set one put in
|
|
190
|
+
* their commands' environment (Claude Code's `CLAUDECODE`, Gemini CLI's
|
|
191
|
+
* `GEMINI_CLI`) say yes.
|
|
192
|
+
*/
|
|
193
|
+
export const agentDriven = (): boolean => {
|
|
194
|
+
if (envIsSet("NISH_AGENT")) {
|
|
195
|
+
return !envSaysNo("NISH_AGENT")
|
|
196
|
+
}
|
|
197
|
+
return envSaysYes("CLAUDECODE") || envSaysYes("GEMINI_CLI")
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The style for descriptor `fd` (1 or 2), for a reader `agent` says is an
|
|
202
|
+
* agent or not (`agentDriven`, asked once by the caller). `NO_COLOR` wins,
|
|
203
|
+
* then an agent, then `FORCE_COLOR` saying no (`isNo`; Node reads `0` and
|
|
204
|
+
* `false` so), then a forcing variable, then `CLICOLOR` saying no or
|
|
205
|
+
* `TERM=dumb`, and only then the terminal check, where the colours `TERM`
|
|
206
|
+
* declares choose between the theme and the failsafe (`paletteOf`). Forced
|
|
207
|
+
* colour is the theme's, asked of nothing: the reader is a pager or an editor
|
|
208
|
+
* that renders the escapes itself (Emacs's shell sets `TERM=dumb` and paints
|
|
209
|
+
* ANSI colour), so `TERM` does not describe it, and forcing forks nothing.
|
|
210
|
+
*/
|
|
211
|
+
export const styleFor = (fd: i32, agent: boolean): Style => {
|
|
212
|
+
const level = getenv("FORCE_COLOR")
|
|
213
|
+
if (envIsSet("NO_COLOR") || agent || saysNo(level)) {
|
|
214
|
+
return new Style(false, false)
|
|
215
|
+
}
|
|
216
|
+
if (saysYes(level) || envSaysYes("CLICOLOR_FORCE")) {
|
|
217
|
+
return new Style(true, true)
|
|
218
|
+
}
|
|
219
|
+
const term = getenv("TERM")
|
|
220
|
+
if (envSaysNo("CLICOLOR") || (term !== null && term === "dumb")) {
|
|
221
|
+
return new Style(false, false)
|
|
222
|
+
}
|
|
223
|
+
return styleOfPalette(paletteOf(fd))
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** A run of spaces to cut padding from, as wide as a terminal's usual 80 columns. */
|
|
227
|
+
const SPACE_RUN: string = " "
|
|
228
|
+
|
|
229
|
+
/** `count` spaces, or none when `count` is not positive: a cut of `SPACE_RUN`, built only past its width. */
|
|
230
|
+
const spaces = (count: i32): string => {
|
|
231
|
+
if (count <= 0) {
|
|
232
|
+
return ""
|
|
233
|
+
}
|
|
234
|
+
if (count <= toI32(SPACE_RUN.length)) {
|
|
235
|
+
return SPACE_RUN.substring(0, count)
|
|
236
|
+
}
|
|
237
|
+
const out: string[] = []
|
|
238
|
+
for (let i: i32 = 0; i < count; i++) {
|
|
239
|
+
out.push(" ")
|
|
240
|
+
}
|
|
241
|
+
return out.join("")
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* The spaces that take plain text `width` columns wide to `column`, or two
|
|
246
|
+
* when that would leave fewer, so a long flag still keeps apart from its text.
|
|
247
|
+
*/
|
|
248
|
+
const padTo = (width: i32, column: i32): string => spaces(width + 2 <= column ? column - width : 2)
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* A help menu, built a line at a time and answered whole by `text`. Rows are
|
|
252
|
+
* indented two columns and their descriptions start at `column`; usage lines'
|
|
253
|
+
* descriptions start at `usageColumn`, past the longest command shape. Every
|
|
254
|
+
* width is counted on the plain text, one column a byte, so the columns line
|
|
255
|
+
* up whether or not `style` paints, as long as the text is ASCII.
|
|
256
|
+
*/
|
|
257
|
+
export class HelpMenu {
|
|
258
|
+
style: Style
|
|
259
|
+
program: string
|
|
260
|
+
column: i32
|
|
261
|
+
usageColumn: i32
|
|
262
|
+
lines: string[]
|
|
263
|
+
usages: i32
|
|
264
|
+
|
|
265
|
+
constructor(style: Style, program: string, column: i32, usageColumn: i32) {
|
|
266
|
+
this.style = style
|
|
267
|
+
this.program = program
|
|
268
|
+
this.column = column
|
|
269
|
+
this.usageColumn = usageColumn
|
|
270
|
+
this.lines = []
|
|
271
|
+
this.usages = 0
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** The opening line, the program's name, `version` and a one-line `summary`, and a blank after it. */
|
|
275
|
+
header(version: string, summary: string): void {
|
|
276
|
+
this.title(version, summary)
|
|
277
|
+
this.lines.push("")
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* The opening line alone, without `header`'s blank after it, for a menu that
|
|
282
|
+
* puts something under its title (`line`) before the blank.
|
|
283
|
+
*/
|
|
284
|
+
title(version: string, summary: string): void {
|
|
285
|
+
const style = this.style
|
|
286
|
+
this.lines.push(`${style.name(this.program)} ${style.dim(version)} ${style.dim(summary)}`)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* A line exactly as given, painted (or not) by the caller with the menu's
|
|
291
|
+
* `style`: a rule, a banner, a blank. Its width is the caller's to keep.
|
|
292
|
+
*/
|
|
293
|
+
line(text: string): void {
|
|
294
|
+
this.lines.push(text)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* A usage line: the command `shape` after the program's name, and what it
|
|
299
|
+
* is for (`description` may be empty). The first opens with `usage:` and
|
|
300
|
+
* the rest are indented under it.
|
|
301
|
+
*/
|
|
302
|
+
usage(shape: string, description: string): void {
|
|
303
|
+
const style = this.style
|
|
304
|
+
const lead = this.usages === 0 ? `${style.heading("usage:")} ` : spaces(7)
|
|
305
|
+
const width: i32 = 8 + toI32(this.program.length) + toI32(shape.length)
|
|
306
|
+
const text = description.length > 0 ? `${padTo(width, this.usageColumn)}${style.dim(description)}` : ""
|
|
307
|
+
this.lines.push(`${lead}${style.name(this.program)} ${style.flag(shape)}${text}`)
|
|
308
|
+
this.usages = this.usages + 1
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** A section: a blank line, then `title` as a heading. */
|
|
312
|
+
section(title: string): void {
|
|
313
|
+
this.lines.push("")
|
|
314
|
+
this.lines.push(this.style.heading(title))
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* One row: the `flag` (or a command), its placeholder `meta` (may be
|
|
319
|
+
* empty), and what it does, the description starting at `column`.
|
|
320
|
+
*/
|
|
321
|
+
row(flag: string, meta: string, description: string): void {
|
|
322
|
+
const style = this.style
|
|
323
|
+
const width: i32 = 2 + toI32(flag.length) + (meta.length > 0 ? 1 + toI32(meta.length) : 0)
|
|
324
|
+
const placeholder = meta.length > 0 ? ` ${style.meta(meta)}` : ""
|
|
325
|
+
this.lines.push(
|
|
326
|
+
` ${style.flag(flag)}${placeholder}${padTo(width, this.column)}${style.dim(description)}`
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** An indented line of prose, faint. */
|
|
331
|
+
note(text: string): void {
|
|
332
|
+
this.lines.push(` ${this.style.dim(text)}`)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** An indented line the reader fills in or opens, a path or a value, as a placeholder. */
|
|
336
|
+
value(text: string): void {
|
|
337
|
+
this.lines.push(` ${this.style.meta(text)}`)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** The menu so far, its lines joined by newlines, with no newline after the last. */
|
|
341
|
+
text(): string {
|
|
342
|
+
return this.lines.join("\n")
|
|
343
|
+
}
|
|
344
|
+
}
|
package/std/collections.ts
CHANGED
|
@@ -53,11 +53,12 @@ const INDEX_CAP: i32 = 16777215
|
|
|
53
53
|
const INITIAL_SLOTS: i32 = 8
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* The key's hash, never 0:
|
|
57
|
-
*
|
|
58
|
-
* integer
|
|
59
|
-
*
|
|
60
|
-
*
|
|
56
|
+
* The key's hash, never 0: SipHash-1-3 of a string's bytes under the process's
|
|
57
|
+
* random key, folded to 32 bits (docs/security/json.md, JSON-13), murmur3's
|
|
58
|
+
* `fmix32` for an integer of 32 bits or fewer, `fmix64` folded to 32 bits for a
|
|
59
|
+
* 64-bit integer, a float (normalised first, so that -0 and +0, and every NaN,
|
|
60
|
+
* hash alike) and a class instance's address (§5.2). SipHash's reference is
|
|
61
|
+
* CC0; MurmurHash3 and its constants are public domain.
|
|
61
62
|
*
|
|
62
63
|
* The body is never emitted. Every call is lowered in place, per key type, by
|
|
63
64
|
* `src/emit-map.ts`; the constant is what the checker and the whole-program
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
crypt_blowfish (https://www.openwall.com/crypt/), version 1.3, crypt_blowfish.c
|
|
2
|
+
|
|
3
|
+
Written by Solar Designer <solar at openwall.com> in 1998-2014.
|
|
4
|
+
No copyright is claimed, and the software is hereby placed in the public
|
|
5
|
+
domain. In case this attempt to disclaim copyright and place the software
|
|
6
|
+
in the public domain is deemed null and void, then the software is
|
|
7
|
+
Copyright (c) 1998-2014 Solar Designer and it is hereby released to the
|
|
8
|
+
general public under the following terms:
|
|
9
|
+
|
|
10
|
+
Redistribution and use in source and binary forms, with or without
|
|
11
|
+
modification, are permitted.
|
|
12
|
+
|
|
13
|
+
There's ABSOLUTELY NO WARRANTY, express or implied.
|
|
14
|
+
|
|
15
|
+
It is my intent that you should be able to use this on your system,
|
|
16
|
+
as part of a software package, or anywhere else to improve security,
|
|
17
|
+
ensure compatibility, or for any other purpose. I would appreciate
|
|
18
|
+
it if you give credit where it is due and keep your modifications in
|
|
19
|
+
the public domain as well, but I don't require that in order to let
|
|
20
|
+
you place this code and any modifications you make under a license
|
|
21
|
+
of your choice.
|
|
22
|
+
|
|
23
|
+
This implementation is fully compatible with OpenBSD's bcrypt.c for prefix
|
|
24
|
+
"$2b$", originally by Niels Provos <provos at citi.umich.edu>, and it uses
|
|
25
|
+
some of his ideas. The password hashing algorithm was designed by David
|
|
26
|
+
Mazieres <dm at lcs.mit.edu>.
|
package/std/crypto/base64url.ts
CHANGED
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
* text already says how many bytes the last group holds (RFC 4648 §3.2 lets a
|
|
7
7
|
* specification that knows its lengths drop the padding, and RFC 7515 §2 does).
|
|
8
8
|
*
|
|
9
|
+
* The module also holds RFC 4648 §4 standard base64 — `+` and `/`, padded with
|
|
10
|
+
* `=` — as `base64StdPadEncode` and `base64StdPadDecode`, for PEM
|
|
11
|
+
* (`nish/crypto/x509`) and SCRAM (`nish/net/postgres`). It is as strict and as
|
|
12
|
+
* constant-time as the URL alphabet, by the same masks, and it refuses a `=`
|
|
13
|
+
* anywhere but the last one or two characters of a text whose length is a
|
|
14
|
+
* multiple of four. They are not `nish/base64`'s `base64Encode`, which encodes
|
|
15
|
+
* only, indexes its alphabet and is for pixels; the `StdPad` names keep the two
|
|
16
|
+
* apart in the program's one namespace.
|
|
17
|
+
*
|
|
9
18
|
* **Decoding is strict, so that each byte string has exactly one spelling.**
|
|
10
19
|
* `base64urlDecode` answers `null` for a `=` anywhere, for any character outside
|
|
11
20
|
* the alphabet (whitespace included), for a length of 1 mod 4 — six bits, not a
|
|
@@ -169,3 +178,119 @@ export const base64urlDecode = (text: string): u8[] | null => {
|
|
|
169
178
|
}
|
|
170
179
|
return out
|
|
171
180
|
}
|
|
181
|
+
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// Standard base64 (RFC 4648 §4): `+` and `/`, padded with `=`
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The standard-alphabet character for the sextet `v`: the same steps as
|
|
188
|
+
* `base64urlCharOf` until 62, which lands on `+` and 63 on `/`.
|
|
189
|
+
*/
|
|
190
|
+
const base64StdPadCharOf = (v: i32): i32 =>
|
|
191
|
+
toI32(
|
|
192
|
+
65 +
|
|
193
|
+
toU32(v) +
|
|
194
|
+
toU32(base64urlRangeMask(v, 26, 63) & 6) -
|
|
195
|
+
toU32(base64urlRangeMask(v, 52, 63) & 75) -
|
|
196
|
+
toU32(base64urlRangeMask(v, 62, 63) & 15) +
|
|
197
|
+
toU32(base64urlRangeMask(v, 63, 63) & 3)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
/** The sextet the byte `c` stands for in standard base64, or `-1` when it is not in the alphabet. */
|
|
201
|
+
const base64StdPadSextetOf = (c: i32): i32 => {
|
|
202
|
+
const upper: i32 = base64urlRangeMask(c, 65, 90)
|
|
203
|
+
const lower: i32 = base64urlRangeMask(c, 97, 122)
|
|
204
|
+
const digit: i32 = base64urlRangeMask(c, 48, 57)
|
|
205
|
+
const plus: i32 = base64urlRangeMask(c, 43, 43)
|
|
206
|
+
const slash: i32 = base64urlRangeMask(c, 47, 47)
|
|
207
|
+
const value: i32 =
|
|
208
|
+
(upper & toI32(toU32(c) - 65)) |
|
|
209
|
+
(lower & toI32(toU32(c) - 71)) |
|
|
210
|
+
(digit & toI32(toU32(c) + 4)) |
|
|
211
|
+
(plus & 62) |
|
|
212
|
+
(slash & 63)
|
|
213
|
+
return value | ~(upper | lower | digit | plus | slash)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* `data` in padded standard base64, as one line: PEM's body, and SCRAM's
|
|
218
|
+
* nonces, salts and proofs (RFC 5802 §5.1). One `=` closes a final group of
|
|
219
|
+
* two bytes, two a group of one. An array longer than 2^31 - 1 bytes panics.
|
|
220
|
+
*/
|
|
221
|
+
export const base64StdPadEncode = (data: u8[]): string => {
|
|
222
|
+
if (data.length > 2147483647) {
|
|
223
|
+
panic("base64StdPadEncode: more than 2^31 - 1 bytes")
|
|
224
|
+
}
|
|
225
|
+
const parts: string[] = []
|
|
226
|
+
let acc: i32 = 0
|
|
227
|
+
let bits: u32 = 0
|
|
228
|
+
for (let k: i32 = 0; k < toI32(data.length); k++) {
|
|
229
|
+
acc = ((acc << 8) | toI32(data[k])) & 0xfff
|
|
230
|
+
bits += 8
|
|
231
|
+
while (bits >= 6) {
|
|
232
|
+
bits -= 6
|
|
233
|
+
parts.push(String.fromCharCode(base64StdPadCharOf((acc >> toI32(bits)) & 63)))
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (bits > 0) {
|
|
237
|
+
parts.push(String.fromCharCode(base64StdPadCharOf((acc << toI32(6 - bits)) & 63)))
|
|
238
|
+
}
|
|
239
|
+
const tail: i32 = toI32(data.length) % 3
|
|
240
|
+
if (tail > 0) {
|
|
241
|
+
parts.push(tail === 1 ? "==" : "=")
|
|
242
|
+
}
|
|
243
|
+
return parts.join("")
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* The bytes of padded standard base64 `text`, or `null` unless it is the one
|
|
248
|
+
* canonical spelling of them: a whole number of four-character groups, `=`
|
|
249
|
+
* only as the last one or two characters, every other character in the
|
|
250
|
+
* alphabet, and the bits the padding leaves over all zero (RFC 4648 §3.5).
|
|
251
|
+
* The empty text is the spelling of no bytes (RFC 4648 §10). Where the padding
|
|
252
|
+
* is depends on the length alone; the characters themselves are read by masks
|
|
253
|
+
* and judged once, after the last.
|
|
254
|
+
*/
|
|
255
|
+
export const base64StdPadDecode = (text: string): u8[] | null => {
|
|
256
|
+
if (text.length > 2147483647) {
|
|
257
|
+
return null
|
|
258
|
+
}
|
|
259
|
+
const n: i32 = toI32(text.length)
|
|
260
|
+
if ((n & 3) !== 0) {
|
|
261
|
+
return null
|
|
262
|
+
}
|
|
263
|
+
let pad: i32 = 0
|
|
264
|
+
if (n > 0 && toI32(text.charCodeAt(n - 1)) === 61) {
|
|
265
|
+
pad = toI32(text.charCodeAt(n - 2)) === 61 ? 2 : 1
|
|
266
|
+
}
|
|
267
|
+
const chars: i32 = n - pad
|
|
268
|
+
const out: u8[] = new Array<u8>((n >> 2) * 3 - pad)
|
|
269
|
+
const outLen: i32 = toI32(out.length)
|
|
270
|
+
let bad: i32 = 0
|
|
271
|
+
let acc: i32 = 0
|
|
272
|
+
let bits: i32 = 0
|
|
273
|
+
let j: i32 = 0
|
|
274
|
+
// The padding is past `chars` and is not data; which positions those are is
|
|
275
|
+
// a fact about the length. A `=` before them is outside the alphabet.
|
|
276
|
+
for (let k: i32 = 0; k < chars && k < toI32(text.length); k++) {
|
|
277
|
+
const v: i32 = base64StdPadSextetOf(toI32(text.charCodeAt(k)))
|
|
278
|
+
bad = bad | (v >> 31)
|
|
279
|
+
acc = ((acc << 6) | (v & 63)) & 0xfff
|
|
280
|
+
bits += 6
|
|
281
|
+
if (bits >= 8) {
|
|
282
|
+
bits -= 8
|
|
283
|
+
// Always true — `out` was sized from the same length — and written so
|
|
284
|
+
// the store keeps no bounds check.
|
|
285
|
+
if (j >= 0 && j < outLen) {
|
|
286
|
+
out[j] = toU8((acc >> bits) & 255)
|
|
287
|
+
}
|
|
288
|
+
j++
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
bad = bad | (acc & ((1 << bits) - 1))
|
|
292
|
+
if (bad !== 0) {
|
|
293
|
+
return null
|
|
294
|
+
}
|
|
295
|
+
return out
|
|
296
|
+
}
|