@amritk/nish-x86_64-linux 0.29.0 → 0.30.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.
@@ -123,9 +123,12 @@
123
123
  * `ticket` naming the request. Statements given to `register` are prepared on
124
124
  * each connection ahead of its first request. A connection that fails reports
125
125
  * `PG_LOST` for each request it took, then `PG_FAILED`; the program closes the
126
- * socket and the slot is built again on its next use. `tests/postgres/carrier.ts`
127
- * is a whole carrier: one `pollWait` loop holding the pool's sockets beside an
128
- * HTTP listener.
126
+ * socket and the slot is built again on its next use.
127
+ *
128
+ * **Carrier.** `PgCarrier`, in `nish/net/postgres-carrier`, moves a pool's
129
+ * bytes on an `HttpHandler`'s loop, so a route handler queries without
130
+ * blocking its worker. It is a module of its own because it reads sockets,
131
+ * the clock and the entropy source, and this one reaches none of them.
129
132
  *
130
133
  * **Batching.** `PgBatcher` turns many reads of one row shape by key — a user
131
134
  * by id, an article by slug — into one execution of a statement over
@@ -162,6 +165,7 @@ import { timingSafeEqual } from "nish/crypto/ct"
162
165
  import { hmacSha256, pbkdf2HmacSha256 } from "nish/crypto/hmac"
163
166
  import { Sha256 } from "nish/crypto/sha256"
164
167
  import { Secret, expose, exposeWith, secret, wipe } from "nish:secret"
168
+ import { urlDecode } from "nish/net/url"
165
169
 
166
170
  /** `next()` needs more input before it can say anything. */
167
171
  export const PG_NEED_MORE: i32 = 0
@@ -3131,8 +3135,9 @@ export class PgBatcher {
3131
3135
  refused: i32 = 0
3132
3136
  /** The number of `row`, `end` and `fail` calls on a spent handle, each ignored, stopping at 2^31 - 1. */
3133
3137
  stray: i32 = 0
3138
+ /** The most requests one execution takes, from `PgBatchConfig.maxRequests`. */
3139
+ readonly maxRequests: i32
3134
3140
  private readonly keyType: i32
3135
- private readonly maxRequests: i32
3136
3141
  private readonly maxBytes: i32
3137
3142
  private readonly binary: boolean
3138
3143
  private storeEnd: i32 = 0
@@ -3210,6 +3215,24 @@ export class PgBatcher {
3210
3215
  return this.count
3211
3216
  }
3212
3217
 
3218
+ /**
3219
+ * Drops the oldest waiting request and answers its tag, or -1 when none
3220
+ * waits: for a program that cannot reach the server to fail requests
3221
+ * rather than hold them.
3222
+ */
3223
+ shed(): i32 {
3224
+ if (this.count === 0) {
3225
+ return -1
3226
+ }
3227
+ const tag: i32 = this.qTags[this.head]
3228
+ this.head = (this.head + 1) % toI32(this.qTags.length)
3229
+ this.count -= 1
3230
+ if (this.count === 0) {
3231
+ this.storeEnd = 0
3232
+ }
3233
+ return tag
3234
+ }
3235
+
3213
3236
  /** Queues a request for the int8 `key`; false when `queueSize` are waiting. Panics on a text batcher. */
3214
3237
  addInt(key: i64, tag: i32): boolean {
3215
3238
  if (this.keyType !== PG_BATCH_INT) {
@@ -3958,3 +3981,273 @@ export class PgBatchTurns {
3958
3981
  }
3959
3982
  }
3960
3983
  }
3984
+
3985
+ /** `PgTarget.failure`: none; connect as `unix` says. */
3986
+ export const PG_TARGET_OK: i32 = 0
3987
+ /** `PgTarget.failure`: not a `postgres://` or `postgresql://` URL, or a query parameter with no `=`. */
3988
+ export const PG_TARGET_BAD_URL: i32 = 1
3989
+ /** `PgTarget.failure`: a `%` not followed by two hex digits, `%00`, or escapes that decode to bytes that are not UTF-8. */
3990
+ export const PG_TARGET_BAD_ESCAPE: i32 = 2
3991
+ /** `PgTarget.failure`: no host, more than one (a `,`), an unclosed `[` or text after its `]`, a raw `@` past the user information, or a NUL byte. */
3992
+ export const PG_TARGET_BAD_HOST: i32 = 3
3993
+ /** `PgTarget.failure`: a port that is not 1 to 65535 in decimal digits. */
3994
+ export const PG_TARGET_BAD_PORT: i32 = 4
3995
+ /** `PgTarget.failure`: a socket path longer than Linux's `sun_path` holds. */
3996
+ export const PG_TARGET_TOO_LONG: i32 = 5
3997
+
3998
+ /** The port a target names when nothing else does. */
3999
+ const PG_DEFAULT_PORT: i32 = 5432
4000
+ /** The longest socket path, NUL excluded: Linux's 108-byte `sun_path`, less its NUL; an `@` name has no NUL, so its `@` takes that byte. */
4001
+ const PG_SOCKET_PATH_MAX: i32 = 107
4002
+
4003
+ /**
4004
+ * Where a connection goes, from libpq's `host` and `port` or a
4005
+ * `postgres://` URL: `pgTarget` and `pgTargetUrl` make one, reading no
4006
+ * socket and no file. When `failure` is `PG_TARGET_OK`, a `unix` target is
4007
+ * `unixConnect(path)`, and any other is `host` and `port`, for
4008
+ * `netAddress` and `tcpConnect`.
4009
+ *
4010
+ * A host starting with `/` is a socket directory, and one starting with `@`
4011
+ * a Linux abstract name, as in libpq: the socket is
4012
+ * `<host>/.s.PGSQL.<port>`. The host is always the directory, never the
4013
+ * socket file itself. Any other host is TCP.
4014
+ */
4015
+ export class PgTarget {
4016
+ /** `PG_TARGET_OK`, or the `PG_TARGET_*` reason the input was refused. */
4017
+ failure: i32 = 0
4018
+ /** Whether the target is a Unix socket, at `path`. */
4019
+ unix: boolean = false
4020
+ /** The host as given (a URL's decoded, without an IPv6 address's brackets); for a socket, its directory or `@` name. */
4021
+ host: string = ""
4022
+ /** The port: the TCP port, or the number in the socket's name. */
4023
+ port: i32 = 5432
4024
+ /** The socket's path, `<host>/.s.PGSQL.<port>`, when `unix`; otherwise "". */
4025
+ path: string = ""
4026
+ }
4027
+
4028
+ /** A target refused for `failure`. */
4029
+ const pgTargetFailed = (failure: i32): PgTarget => {
4030
+ const t = new PgTarget()
4031
+ t.failure = failure
4032
+ return t
4033
+ }
4034
+
4035
+ /**
4036
+ * The target `host` and `port` name, as libpq's `host` and `port` do: a host
4037
+ * starting with `/` or `@` is the socket `<host>/.s.PGSQL.<port>`, any other
4038
+ * a TCP host. The host is not percent-decoded. A port outside 1 to 65535, an
4039
+ * empty host, a `,` (more than one host) or a NUL byte is refused, and so is
4040
+ * a socket path over 107 bytes (an `@` name over 107 after its `@`), never
4041
+ * cut short.
4042
+ */
4043
+ export const pgTarget = (host: string, port: i32): PgTarget => {
4044
+ if (port < 1 || port > 65535) {
4045
+ return pgTargetFailed(PG_TARGET_BAD_PORT)
4046
+ }
4047
+ const n: i32 = toI32(host.length)
4048
+ if (n === 0) {
4049
+ return pgTargetFailed(PG_TARGET_BAD_HOST)
4050
+ }
4051
+ for (let i: i32 = 0; i < n; i++) {
4052
+ const c: i32 = toI32(host.charCodeAt(i))
4053
+ if (c === 0 || c === 44) {
4054
+ return pgTargetFailed(PG_TARGET_BAD_HOST)
4055
+ }
4056
+ }
4057
+ const t = new PgTarget()
4058
+ t.host = host
4059
+ t.port = port
4060
+ const first: i32 = toI32(host.charCodeAt(0))
4061
+ if (first === 47 || first === 64) {
4062
+ const path: string = `${host}/.s.PGSQL.${port}`
4063
+ // An `@` name's NUL-free bytes after the `@` fill what a path's NUL would.
4064
+ const limit: i32 = first === 64 ? PG_SOCKET_PATH_MAX + 1 : PG_SOCKET_PATH_MAX
4065
+ if (toI32(path.length) > limit) {
4066
+ return pgTargetFailed(PG_TARGET_TOO_LONG)
4067
+ }
4068
+ t.unix = true
4069
+ t.path = path
4070
+ }
4071
+ return t
4072
+ }
4073
+
4074
+ /** `text[start, end)` percent-decoded, or null for a `%` without two hex digits after it, `%00`, or bytes that are not UTF-8. */
4075
+ const pgDecoded = (text: string, start: i32, end: i32): string | null =>
4076
+ urlDecode(text.substring(start, end), 0)
4077
+
4078
+ /** The port `text` spells, 1 to 5 decimal digits naming 1 to 65535, or -1. */
4079
+ const pgPortValue = (text: string): i32 => {
4080
+ const n: i32 = toI32(text.length)
4081
+ if (n === 0 || n > 5) {
4082
+ return -1
4083
+ }
4084
+ let v: i32 = 0
4085
+ for (let i: i32 = 0; i < n; i++) {
4086
+ const c: i32 = toI32(text.charCodeAt(i))
4087
+ if (c < 48 || c > 57) {
4088
+ return -1
4089
+ }
4090
+ v = v * 10 + (c - 48)
4091
+ }
4092
+ if (v < 1 || v > 65535) {
4093
+ return -1
4094
+ }
4095
+ return v
4096
+ }
4097
+
4098
+ /**
4099
+ * The target a `postgres://` or `postgresql://` URL names, as libpq reads
4100
+ * one: `postgres://[user[:password]@][host][:port][/dbname][?key=value&...]`.
4101
+ * The host, the port and every query key and value are percent-decoded, so a
4102
+ * socket directory is written `%2Ftmp` or passed as `?host=/tmp`; a `host` or
4103
+ * `port` in the query overrides the authority's, and other keys are ignored.
4104
+ * The user, password and database are not read here, but a bad escape in them
4105
+ * still refuses the URL. A `%` without two hex digits after it, `%00`, or a
4106
+ * part that decodes to bytes that are not UTF-8 is `PG_TARGET_BAD_ESCAPE`.
4107
+ * The user information ends at the first `@`, as in libpq; a raw `@` after a
4108
+ * `?` or after that first `@` in the authority, or a raw NUL byte anywhere,
4109
+ * is `PG_TARGET_BAD_HOST`, as is a URL with no host anywhere, where libpq
4110
+ * would use its default socket directory. An empty port or query value is
4111
+ * refused, where libpq would take it as unset.
4112
+ * The decoded host is then resolved as `pgTarget` resolves one.
4113
+ */
4114
+ export const pgTargetUrl = (url: string): PgTarget => {
4115
+ let at: i32 = 0
4116
+ if (url.startsWith("postgresql://")) {
4117
+ at = 13
4118
+ } else if (url.startsWith("postgres://")) {
4119
+ at = 11
4120
+ } else {
4121
+ return pgTargetFailed(PG_TARGET_BAD_URL)
4122
+ }
4123
+ const n: i32 = toI32(url.length)
4124
+ // The authority runs to the first `/` or `?`; the path to the `?`.
4125
+ let authorityEnd: i32 = at
4126
+ while (authorityEnd < n) {
4127
+ const c: i32 = toI32(url.charCodeAt(authorityEnd))
4128
+ if (c === 47 || c === 63) {
4129
+ break
4130
+ }
4131
+ authorityEnd += 1
4132
+ }
4133
+ let queryStart: i32 = authorityEnd
4134
+ while (queryStart < n && toI32(url.charCodeAt(queryStart)) !== 63) {
4135
+ queryStart += 1
4136
+ }
4137
+ // A raw NUL byte anywhere is refused: no part may carry one past here.
4138
+ for (let i: i32 = at; i < n; i++) {
4139
+ if (toI32(url.charCodeAt(i)) === 0) {
4140
+ return pgTargetFailed(PG_TARGET_BAD_HOST)
4141
+ }
4142
+ }
4143
+ // libpq ends the user information at the first `@` before a `/`, a `?`
4144
+ // included. One after a `?`, or a second in the host and port, reads
4145
+ // differently by other parsers, so either is refused. The loops below also
4146
+ // test `>= 0` and `< n`, always true as `authorityEnd <= n`, so the prover
4147
+ // drops each access's bounds check.
4148
+ let firstAt: i32 = at
4149
+ while (firstAt >= 0 && firstAt < n) {
4150
+ const c: i32 = toI32(url.charCodeAt(firstAt))
4151
+ if (c === 64 || c === 47) {
4152
+ break
4153
+ }
4154
+ firstAt += 1
4155
+ }
4156
+ let hostStart: i32 = at
4157
+ if (firstAt < n && toI32(url.charCodeAt(firstAt)) === 64) {
4158
+ if (firstAt > authorityEnd) {
4159
+ return pgTargetFailed(PG_TARGET_BAD_HOST)
4160
+ }
4161
+ hostStart = firstAt + 1
4162
+ }
4163
+ for (let i: i32 = hostStart; i >= 0 && i < authorityEnd && i < n; i++) {
4164
+ if (toI32(url.charCodeAt(i)) === 64) {
4165
+ return pgTargetFailed(PG_TARGET_BAD_HOST)
4166
+ }
4167
+ }
4168
+ if (hostStart > at && pgDecoded(url, at, hostStart - 1) === null) {
4169
+ return pgTargetFailed(PG_TARGET_BAD_ESCAPE)
4170
+ }
4171
+ if (authorityEnd < queryStart && pgDecoded(url, authorityEnd, queryStart) === null) {
4172
+ return pgTargetFailed(PG_TARGET_BAD_ESCAPE)
4173
+ }
4174
+ // The host ends at a `:` before the port, after an IPv6 address's `]`.
4175
+ let hostEnd: i32 = authorityEnd
4176
+ let portStart: i32 = -1
4177
+ let bracketed: boolean = false
4178
+ if (hostStart < authorityEnd && toI32(url.charCodeAt(hostStart)) === 91) {
4179
+ let close: i32 = hostStart + 1
4180
+ while (close >= 0 && close < authorityEnd && close < n && toI32(url.charCodeAt(close)) !== 93) {
4181
+ close += 1
4182
+ }
4183
+ if (close === authorityEnd) {
4184
+ return pgTargetFailed(PG_TARGET_BAD_HOST)
4185
+ }
4186
+ bracketed = true
4187
+ hostEnd = close + 1
4188
+ if (hostEnd < authorityEnd) {
4189
+ if (toI32(url.charCodeAt(hostEnd)) !== 58) {
4190
+ return pgTargetFailed(PG_TARGET_BAD_HOST)
4191
+ }
4192
+ portStart = hostEnd + 1
4193
+ }
4194
+ } else {
4195
+ for (let i: i32 = hostStart; i >= 0 && i < authorityEnd && i < n; i++) {
4196
+ if (toI32(url.charCodeAt(i)) === 58) {
4197
+ hostEnd = i
4198
+ portStart = i + 1
4199
+ break
4200
+ }
4201
+ }
4202
+ }
4203
+ const decodedHost: string | null = bracketed
4204
+ ? pgDecoded(url, hostStart + 1, hostEnd - 1)
4205
+ : pgDecoded(url, hostStart, hostEnd)
4206
+ if (decodedHost === null) {
4207
+ return pgTargetFailed(PG_TARGET_BAD_ESCAPE)
4208
+ }
4209
+ let host: string = decodedHost
4210
+ let port: i32 = PG_DEFAULT_PORT
4211
+ if (portStart >= 0) {
4212
+ const decodedPort: string | null = pgDecoded(url, portStart, authorityEnd)
4213
+ if (decodedPort === null) {
4214
+ return pgTargetFailed(PG_TARGET_BAD_ESCAPE)
4215
+ }
4216
+ port = pgPortValue(decodedPort)
4217
+ if (port < 0) {
4218
+ return pgTargetFailed(PG_TARGET_BAD_PORT)
4219
+ }
4220
+ }
4221
+ // The query: `key=value` pairs split at `&`.
4222
+ let i: i32 = queryStart + 1
4223
+ while (i >= 0 && i < n) {
4224
+ let end: i32 = i
4225
+ let eq: i32 = -1
4226
+ while (end < n && toI32(url.charCodeAt(end)) !== 38) {
4227
+ if (eq < 0 && toI32(url.charCodeAt(end)) === 61) {
4228
+ eq = end
4229
+ }
4230
+ end += 1
4231
+ }
4232
+ if (end > i) {
4233
+ if (eq < 0) {
4234
+ return pgTargetFailed(PG_TARGET_BAD_URL)
4235
+ }
4236
+ const key: string | null = pgDecoded(url, i, eq)
4237
+ const value: string | null = pgDecoded(url, eq + 1, end)
4238
+ if (key === null || value === null) {
4239
+ return pgTargetFailed(PG_TARGET_BAD_ESCAPE)
4240
+ }
4241
+ if (key === "host") {
4242
+ host = value
4243
+ } else if (key === "port") {
4244
+ port = pgPortValue(value)
4245
+ if (port < 0) {
4246
+ return pgTargetFailed(PG_TARGET_BAD_PORT)
4247
+ }
4248
+ }
4249
+ }
4250
+ i = end + 1
4251
+ }
4252
+ return pgTarget(host, port)
4253
+ }