@amritk/nish-x86_64-linux 0.29.0 → 0.31.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 +3 -3
- package/bin/nish +0 -0
- package/docs/AI.md +71 -4
- package/package.json +1 -1
- package/runtime/core/host.d.ts +2 -0
- package/runtime/core/host.mjs +9 -0
- package/runtime/nish.d.ts +48 -2
- package/runtime/nish.h +31 -3
- package/runtime/nish.mjs +36 -0
- package/runtime/runtime-net.c +32 -3
- package/runtime/runtime-os.c +33 -1
- package/runtime/runtime-region.c +243 -0
- package/runtime/runtime.c +19 -2
- package/scripts/build.sh +19 -6
- package/std/README.md +10 -7
- package/std/net/{http-client.ts → fetch.ts} +17 -17
- package/std/net/handler.ts +1 -1
- package/std/net/{http1-client.ts → http1-client-codec.ts} +276 -47
- package/std/net/{http1.ts → http1-codec.ts} +252 -49
- package/std/net/http1-server.ts +72 -13
- package/std/net/outbound.ts +2 -2
- package/std/net/postgres-carrier.ts +985 -0
- package/std/net/postgres.ts +680 -53
- package/std/net/prefork.ts +149 -13
- package/std/net/router.ts +1 -1
- package/std/net/upstream.ts +759 -0
- package/std/net/websocket.ts +5 -5
- package/std/region.ts +95 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `nish/net/http1-client` — the client side of HTTP/1.1, sans-IO: a request
|
|
2
|
+
* `nish/net/http1-client-codec` — the client side of HTTP/1.1, sans-IO: a request
|
|
3
3
|
* writer and an incremental response parser, bytes in, events and bytes out,
|
|
4
|
-
* and no socket anywhere. `nish/net/http1` is the server side; the event
|
|
4
|
+
* and no socket anywhere. `nish/net/http1-codec` is the server side; the event
|
|
5
5
|
* constants and the chunk writers are shared and imported from there.
|
|
6
6
|
*
|
|
7
7
|
* **The writer** is `http1RequestHead`, which answers a request line and
|
|
8
8
|
* header section with the framing field the body needs, and
|
|
9
9
|
* `http1WriteRequestHead`, which writes the same head into a caller's buffer
|
|
10
10
|
* with nothing allocated. A chunked body is written with `http1Chunk` and
|
|
11
|
-
* `http1LastChunk` from `nish/net/http1`. The writer refuses anything that
|
|
11
|
+
* `http1LastChunk` from `nish/net/http1-codec`. The writer refuses anything that
|
|
12
12
|
* would let a caller's string end a line early or frame the body itself — the
|
|
13
13
|
* request-splitting and request-smuggling shapes. `http1UpgradeRequestHead`
|
|
14
14
|
* is the one way to ask the server to switch protocols: it writes `Upgrade`
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
* what it asked (`expect`), `feed`s whatever slice the socket gave it, cut
|
|
20
20
|
* anywhere, and calls `next()` until it answers `HTTP1_NEED_MORE`:
|
|
21
21
|
*
|
|
22
|
-
* import { HTTP1_NEED_MORE, HTTP1_HEAD, HTTP1_BODY, HTTP1_END, HTTP1_ERROR } from "nish/net/http1";
|
|
23
|
-
* import { Http1ResponseParser } from "nish/net/http1-client";
|
|
22
|
+
* import { HTTP1_NEED_MORE, HTTP1_HEAD, HTTP1_BODY, HTTP1_END, HTTP1_ERROR } from "nish/net/http1-codec";
|
|
23
|
+
* import { Http1ResponseParser } from "nish/net/http1-client-codec";
|
|
24
24
|
*
|
|
25
25
|
* const p = new Http1ResponseParser(maxStatusLine, maxHeaderBytes, maxHeaders, maxBody, maxTrailerBytes);
|
|
26
26
|
* p.expect(false, false);
|
|
@@ -96,7 +96,7 @@ import {
|
|
|
96
96
|
http1PutText,
|
|
97
97
|
http1SameWord,
|
|
98
98
|
http1Text,
|
|
99
|
-
} from "nish/net/http1"
|
|
99
|
+
} from "nish/net/http1-codec"
|
|
100
100
|
|
|
101
101
|
/** `next()`: an interim 1xx response was read; `status` and the fields are its. */
|
|
102
102
|
export const HTTP1_INFORMATIONAL: i32 = 7
|
|
@@ -538,9 +538,22 @@ export class Http1ResponseParser {
|
|
|
538
538
|
// --- the response: valid from its HEAD or INFORMATIONAL until the next ----
|
|
539
539
|
/** The reason phrase, which RFC 9112 §4 says a client ignores; for a log. */
|
|
540
540
|
reason: string = ""
|
|
541
|
-
/**
|
|
541
|
+
/**
|
|
542
|
+
* The field names, in lowercase, in the order they arrived; `values` beside
|
|
543
|
+
* them, OWS trimmed. These and `reason` are filled only while the parser
|
|
544
|
+
* keeps strings, which it does until `fieldsAsSpans()`; `fields()`,
|
|
545
|
+
* `fieldName`, `fieldValue`, `reasonText` and `header` answer in either mode.
|
|
546
|
+
*/
|
|
542
547
|
names: string[]
|
|
543
548
|
values: string[]
|
|
549
|
+
/**
|
|
550
|
+
* The response's reason phrase and kept fields, copied: `head[reasonStart ..
|
|
551
|
+
* reasonEnd)` is the reason, and field `i` is `head[spans[4i] ..
|
|
552
|
+
* spans[4i + 1])`, its name in lowercase, then `head[spans[4i + 2] ..
|
|
553
|
+
* spans[4i + 3])`, its value. Both arrays are reused from response to response.
|
|
554
|
+
*/
|
|
555
|
+
head: u8[]
|
|
556
|
+
spans: i32[]
|
|
544
557
|
/** The buffer the last `HTTP1_BODY`'s bytes are in, at `bodyOff`, `bodyLen` long. */
|
|
545
558
|
body: u8[]
|
|
546
559
|
/** What was wrong, once `next()` said `HTTP1_ERROR`; for a log. */
|
|
@@ -595,6 +608,13 @@ export class Http1ResponseParser {
|
|
|
595
608
|
wasUpgrade: boolean = false
|
|
596
609
|
/** Whether `finish()` said the server closed the connection. */
|
|
597
610
|
closed: boolean = false
|
|
611
|
+
/** Whether `names`, `values` and `reason` are filled; `fieldsAsSpans()` clears it. */
|
|
612
|
+
keepStrings: boolean = true
|
|
613
|
+
/** Bytes of `head` in use, and the fields recorded in `spans`. */
|
|
614
|
+
headUsed: i32 = 0
|
|
615
|
+
spanCount: i32 = 0
|
|
616
|
+
reasonStart: i32 = 0
|
|
617
|
+
reasonEnd: i32 = 0
|
|
598
618
|
|
|
599
619
|
/**
|
|
600
620
|
* Each `i32` limit is clamped into 0 to 2^30 and `maxBody` into 0 to 2^53,
|
|
@@ -614,6 +634,46 @@ export class Http1ResponseParser {
|
|
|
614
634
|
this.body = this.buf
|
|
615
635
|
this.names = []
|
|
616
636
|
this.values = []
|
|
637
|
+
this.head = []
|
|
638
|
+
this.spans = []
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Stops filling `names`, `values` and `reason`, which then stay empty: a
|
|
643
|
+
* response's fields are read through `fields()`, `fieldName`, `fieldValue`,
|
|
644
|
+
* `reasonText` and `header`, which make a string only when asked. A parser
|
|
645
|
+
* kept for many responses allocates nothing per response this way.
|
|
646
|
+
*/
|
|
647
|
+
fieldsAsSpans(): void {
|
|
648
|
+
this.keepStrings = false
|
|
649
|
+
this.names = []
|
|
650
|
+
this.values = []
|
|
651
|
+
this.reason = ""
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Makes the parser as it was new, for the next connection, keeping its
|
|
656
|
+
* limits, its mode and its buffers: what was fed and not read is dropped,
|
|
657
|
+
* and an error or a close is forgotten.
|
|
658
|
+
*/
|
|
659
|
+
reset(): void {
|
|
660
|
+
this.state = HTTP1_CLIENT_S_STATUS_LINE
|
|
661
|
+
this.start = 0
|
|
662
|
+
this.end = 0
|
|
663
|
+
this.scan = 0
|
|
664
|
+
this.lineStart = 0
|
|
665
|
+
this.lineEnd = 0
|
|
666
|
+
this.closed = false
|
|
667
|
+
this.error = 0
|
|
668
|
+
this.errorText = ""
|
|
669
|
+
this.informational = 0
|
|
670
|
+
this.wasHead = false
|
|
671
|
+
this.wasConnect = false
|
|
672
|
+
this.wasUpgrade = false
|
|
673
|
+
http1ClientStartHead(this)
|
|
674
|
+
this.left = toI64(0)
|
|
675
|
+
this.bodyOff = 0
|
|
676
|
+
this.body = this.buf
|
|
617
677
|
}
|
|
618
678
|
|
|
619
679
|
/**
|
|
@@ -704,16 +764,43 @@ export class Http1ResponseParser {
|
|
|
704
764
|
|
|
705
765
|
/** The value of the first field named `name` (any case), or `null`. */
|
|
706
766
|
header(name: string): string | null {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
if (http1SameWord(name, 0, n, names[i])) {
|
|
711
|
-
return this.values[i]
|
|
767
|
+
for (let i: i32 = 0; i < this.spanCount; i += 1) {
|
|
768
|
+
if (http1ClientHeadSameWord(this.head, this.spans[i * 4], this.spans[i * 4 + 1], name)) {
|
|
769
|
+
return http1Text(this.head, this.spans[i * 4 + 2], this.spans[i * 4 + 3], false)
|
|
712
770
|
}
|
|
713
771
|
}
|
|
714
772
|
return null
|
|
715
773
|
}
|
|
716
774
|
|
|
775
|
+
/** How many fields the response last read has; trailers are not kept. */
|
|
776
|
+
fields(): i32 {
|
|
777
|
+
return this.spanCount
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/** Field `i`'s name, in lowercase, as a fresh string; "" past the last field. */
|
|
781
|
+
fieldName(i: i32): string {
|
|
782
|
+
if (i < 0 || i >= this.spanCount) {
|
|
783
|
+
return ""
|
|
784
|
+
}
|
|
785
|
+
return http1Text(this.head, this.spans[i * 4], this.spans[i * 4 + 1], false)
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/** Field `i`'s value, OWS trimmed, as a fresh string; "" past the last field. */
|
|
789
|
+
fieldValue(i: i32): string {
|
|
790
|
+
if (i < 0 || i >= this.spanCount) {
|
|
791
|
+
return ""
|
|
792
|
+
}
|
|
793
|
+
return http1Text(this.head, this.spans[i * 4 + 2], this.spans[i * 4 + 3], false)
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/** The reason phrase, as a fresh string. */
|
|
797
|
+
reasonText(): string {
|
|
798
|
+
if (this.reasonEnd <= this.reasonStart) {
|
|
799
|
+
return ""
|
|
800
|
+
}
|
|
801
|
+
return http1Text(this.head, this.reasonStart, this.reasonEnd, false)
|
|
802
|
+
}
|
|
803
|
+
|
|
717
804
|
/** How many bytes were fed and not consumed yet. */
|
|
718
805
|
buffered(): i32 {
|
|
719
806
|
return this.end - this.start
|
|
@@ -777,10 +864,17 @@ const http1ClientFail = (p: Http1ResponseParser, code: i32, text: string): i32 =
|
|
|
777
864
|
/** Clears the per-response fields for the status line just taken. */
|
|
778
865
|
const http1ClientStartHead = (p: Http1ResponseParser): void => {
|
|
779
866
|
p.status = 0
|
|
780
|
-
p.reason = ""
|
|
781
867
|
p.minor = 1
|
|
782
|
-
|
|
783
|
-
p.
|
|
868
|
+
// Fresh arrays, not cleared ones: a caller may hold the last response's.
|
|
869
|
+
if (p.keepStrings) {
|
|
870
|
+
p.reason = ""
|
|
871
|
+
p.names = []
|
|
872
|
+
p.values = []
|
|
873
|
+
}
|
|
874
|
+
p.headUsed = 0
|
|
875
|
+
p.spanCount = 0
|
|
876
|
+
p.reasonStart = 0
|
|
877
|
+
p.reasonEnd = 0
|
|
784
878
|
p.contentLength = toI64(-1)
|
|
785
879
|
p.chunked = false
|
|
786
880
|
p.persistent = false
|
|
@@ -872,7 +966,13 @@ const http1ClientStatusLine = (p: Http1ResponseParser): i32 => {
|
|
|
872
966
|
}
|
|
873
967
|
p.minor = buf[a + 7] === 48 ? 0 : 1
|
|
874
968
|
p.status = status
|
|
875
|
-
|
|
969
|
+
if (b - a > 13) {
|
|
970
|
+
p.reasonStart = http1ClientKeep(p, buf, a + 13, b, false)
|
|
971
|
+
p.reasonEnd = p.headUsed
|
|
972
|
+
if (p.keepStrings) {
|
|
973
|
+
p.reason = http1Text(buf, a + 13, b, false)
|
|
974
|
+
}
|
|
975
|
+
}
|
|
876
976
|
p.state = HTTP1_CLIENT_S_FIELDS
|
|
877
977
|
return HTTP1_NEED_MORE
|
|
878
978
|
}
|
|
@@ -925,8 +1025,13 @@ const http1ClientFieldLine = (p: Http1ResponseParser, keep: boolean): i32 => {
|
|
|
925
1025
|
) {
|
|
926
1026
|
valueEnd -= 1
|
|
927
1027
|
}
|
|
928
|
-
p
|
|
929
|
-
p
|
|
1028
|
+
const name: i32 = http1ClientKeep(p, buf, a, nameEnd, true)
|
|
1029
|
+
const value: i32 = http1ClientKeep(p, buf, valueStart, valueEnd, false)
|
|
1030
|
+
http1ClientSpan(p, name, value, value + (valueEnd - valueStart))
|
|
1031
|
+
if (p.keepStrings) {
|
|
1032
|
+
p.names.push(http1Text(buf, a, nameEnd, true))
|
|
1033
|
+
p.values.push(http1Text(buf, valueStart, valueEnd, false))
|
|
1034
|
+
}
|
|
930
1035
|
}
|
|
931
1036
|
return HTTP1_NEED_MORE
|
|
932
1037
|
}
|
|
@@ -940,20 +1045,20 @@ const http1ClientFieldLine = (p: Http1ResponseParser, keep: boolean): i32 => {
|
|
|
940
1045
|
* value past 2^53 is saturated to 2^53 + 1: over any `maxBody`. Saturated
|
|
941
1046
|
* values cannot be told apart, so one seen with any other value is -2.
|
|
942
1047
|
*/
|
|
943
|
-
const http1ClientLengthList = (
|
|
944
|
-
const n: i32 = toI32(
|
|
1048
|
+
const http1ClientLengthList = (head: u8[], from: i32, to: i32, seen: i64): i64 => {
|
|
1049
|
+
const n: i32 = to < toI32(head.length) ? to : toI32(head.length)
|
|
945
1050
|
let found: i64 = seen
|
|
946
|
-
let start: i32 =
|
|
947
|
-
for (let k: i32 =
|
|
948
|
-
if (k === n ||
|
|
949
|
-
const a: i32 =
|
|
950
|
-
const b: i32 =
|
|
1051
|
+
let start: i32 = from
|
|
1052
|
+
for (let k: i32 = from; k <= n; k += 1) {
|
|
1053
|
+
if (k === n || (k >= 0 && k < toI32(head.length) && head[k] === 44)) {
|
|
1054
|
+
const a: i32 = http1ClientHeadTrimStart(head, start, k)
|
|
1055
|
+
const b: i32 = http1ClientHeadTrimEnd(head, a, k)
|
|
951
1056
|
if (a === b) {
|
|
952
1057
|
return toI64(-1)
|
|
953
1058
|
}
|
|
954
1059
|
let v: i64 = toI64(0)
|
|
955
|
-
for (let j: i32 = a; j >= 0 && j < b && j <
|
|
956
|
-
const c: i32 = toI32(
|
|
1060
|
+
for (let j: i32 = a; j >= 0 && j < b && j < toI32(head.length); j += 1) {
|
|
1061
|
+
const c: i32 = toI32(head[j])
|
|
957
1062
|
if (c < 48 || c > 57) {
|
|
958
1063
|
return toI64(-1)
|
|
959
1064
|
}
|
|
@@ -982,20 +1087,19 @@ const http1ClientTally = (p: Http1ResponseParser, name: string, word: string): i
|
|
|
982
1087
|
let elements: i32 = 0
|
|
983
1088
|
let matches: i32 = 0
|
|
984
1089
|
let last: boolean = false
|
|
985
|
-
const
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
let
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
const
|
|
995
|
-
const b: i32 = http1ClientTrimEnd(value, a, k)
|
|
1090
|
+
const head: u8[] = p.head
|
|
1091
|
+
for (let i: i32 = 0; i < p.spanCount; i += 1) {
|
|
1092
|
+
if (http1ClientHeadIs(head, p.spans[i * 4], p.spans[i * 4 + 1], name)) {
|
|
1093
|
+
const from: i32 = p.spans[i * 4 + 2]
|
|
1094
|
+
const n: i32 = p.spans[i * 4 + 3] < toI32(head.length) ? p.spans[i * 4 + 3] : toI32(head.length)
|
|
1095
|
+
let start: i32 = from
|
|
1096
|
+
for (let k: i32 = from; k <= n; k += 1) {
|
|
1097
|
+
if (k === n || (k >= 0 && k < toI32(head.length) && head[k] === 44)) {
|
|
1098
|
+
const a: i32 = http1ClientHeadTrimStart(head, start, k)
|
|
1099
|
+
const b: i32 = http1ClientHeadTrimEnd(head, a, k)
|
|
996
1100
|
if (b > a) {
|
|
997
1101
|
elements += 1
|
|
998
|
-
last =
|
|
1102
|
+
last = http1ClientHeadSameWord(head, a, b, word)
|
|
999
1103
|
if (last) {
|
|
1000
1104
|
matches += 1
|
|
1001
1105
|
}
|
|
@@ -1010,15 +1114,139 @@ const http1ClientTally = (p: Http1ResponseParser, name: string, word: string): i
|
|
|
1010
1114
|
|
|
1011
1115
|
/** Whether a `Connection` field of the response lists `word`. */
|
|
1012
1116
|
const http1ClientConnectionHas = (p: Http1ResponseParser, word: string): boolean => {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1117
|
+
for (let i: i32 = 0; i < p.spanCount; i += 1) {
|
|
1118
|
+
if (
|
|
1119
|
+
http1ClientHeadIs(p.head, p.spans[i * 4], p.spans[i * 4 + 1], "connection") &&
|
|
1120
|
+
http1ClientHeadListHas(p.head, p.spans[i * 4 + 2], p.spans[i * 4 + 3], word)
|
|
1121
|
+
) {
|
|
1016
1122
|
return true
|
|
1017
1123
|
}
|
|
1018
1124
|
}
|
|
1019
1125
|
return false
|
|
1020
1126
|
}
|
|
1021
1127
|
|
|
1128
|
+
/**
|
|
1129
|
+
* Copies `buf[a .. b)` to the end of `p.head`, lowercased when `lower`, and
|
|
1130
|
+
* answers where the copy starts. `head` grows by doubling and is kept, so a
|
|
1131
|
+
* parser reused for many responses stops growing at the largest head.
|
|
1132
|
+
*/
|
|
1133
|
+
const http1ClientKeep = (p: Http1ResponseParser, buf: u8[], a: i32, b: i32, lower: boolean): i32 => {
|
|
1134
|
+
const n: i32 = b - a
|
|
1135
|
+
const at: i32 = p.headUsed
|
|
1136
|
+
if (n > toI32(p.head.length) - at) {
|
|
1137
|
+
let size: i32 = toI32(p.head.length) < 256 ? 256 : toI32(p.head.length)
|
|
1138
|
+
while (size - at < n) {
|
|
1139
|
+
if (size > 1073741823) {
|
|
1140
|
+
panic("Http1ResponseParser: more than 2^30 bytes of head")
|
|
1141
|
+
}
|
|
1142
|
+
size = size * 2
|
|
1143
|
+
}
|
|
1144
|
+
const grown: u8[] = new Array<u8>(size)
|
|
1145
|
+
copyBytes(grown, 0, p.head, 0, at)
|
|
1146
|
+
p.head = grown
|
|
1147
|
+
}
|
|
1148
|
+
copyBytes(p.head, at, buf, a, n)
|
|
1149
|
+
if (lower) {
|
|
1150
|
+
const head: u8[] = p.head
|
|
1151
|
+
for (let k: i32 = at; k >= 0 && k < at + n && k < toI32(head.length); k += 1) {
|
|
1152
|
+
const c: i32 = toI32(head[k])
|
|
1153
|
+
if (c >= 65 && c <= 90) {
|
|
1154
|
+
head[k] = toU8(c + 32)
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
p.headUsed = at + n
|
|
1159
|
+
return at
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
/** Records the field whose name starts at `name` and whose value is `head[value .. valueEnd)`. */
|
|
1163
|
+
const http1ClientSpan = (p: Http1ResponseParser, name: i32, value: i32, valueEnd: i32): void => {
|
|
1164
|
+
const at: i32 = p.spanCount * 4
|
|
1165
|
+
if (at + 4 > toI32(p.spans.length)) {
|
|
1166
|
+
p.spans.push(name)
|
|
1167
|
+
p.spans.push(value)
|
|
1168
|
+
p.spans.push(value)
|
|
1169
|
+
p.spans.push(valueEnd)
|
|
1170
|
+
} else {
|
|
1171
|
+
p.spans[at] = name
|
|
1172
|
+
p.spans[at + 1] = value
|
|
1173
|
+
p.spans[at + 2] = value
|
|
1174
|
+
p.spans[at + 3] = valueEnd
|
|
1175
|
+
}
|
|
1176
|
+
p.spanCount += 1
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/** Whether `head[a .. b)` is `word`, byte for byte. */
|
|
1180
|
+
const http1ClientHeadIs = (head: u8[], a: i32, b: i32, word: string): boolean => {
|
|
1181
|
+
const n: i32 = toI32(word.length)
|
|
1182
|
+
if (b - a !== n || a < 0 || b > toI32(head.length)) {
|
|
1183
|
+
return false
|
|
1184
|
+
}
|
|
1185
|
+
for (let k: i32 = 0; k < n; k += 1) {
|
|
1186
|
+
if (toI32(head[a + k]) !== toI32(word.charCodeAt(k))) {
|
|
1187
|
+
return false
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return true
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
/** Whether `head[a .. b)` is `word` in any case, ASCII letters folded. */
|
|
1194
|
+
const http1ClientHeadSameWord = (head: u8[], a: i32, b: i32, word: string): boolean => {
|
|
1195
|
+
const n: i32 = toI32(word.length)
|
|
1196
|
+
if (b - a !== n || a < 0 || b > toI32(head.length)) {
|
|
1197
|
+
return false
|
|
1198
|
+
}
|
|
1199
|
+
for (let k: i32 = 0; k < n; k += 1) {
|
|
1200
|
+
let x: i32 = toI32(head[a + k])
|
|
1201
|
+
let y: i32 = toI32(word.charCodeAt(k))
|
|
1202
|
+
if (x >= 65 && x <= 90) {
|
|
1203
|
+
x += 32
|
|
1204
|
+
}
|
|
1205
|
+
if (y >= 65 && y <= 90) {
|
|
1206
|
+
y += 32
|
|
1207
|
+
}
|
|
1208
|
+
if (x !== y) {
|
|
1209
|
+
return false
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
return true
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
/** Where `head[a .. b)` starts once its leading OWS is dropped. */
|
|
1216
|
+
const http1ClientHeadTrimStart = (head: u8[], a: i32, b: i32): i32 => {
|
|
1217
|
+
let i: i32 = a
|
|
1218
|
+
while (i >= 0 && i < b && i < toI32(head.length) && http1IsOws(toI32(head[i]))) {
|
|
1219
|
+
i += 1
|
|
1220
|
+
}
|
|
1221
|
+
return i
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
/** Where `head[a .. b)` ends once its trailing OWS is dropped. */
|
|
1225
|
+
const http1ClientHeadTrimEnd = (head: u8[], a: i32, b: i32): i32 => {
|
|
1226
|
+
let i: i32 = b
|
|
1227
|
+
while (i > a && i > 0 && i <= toI32(head.length) && http1IsOws(toI32(head[i - 1]))) {
|
|
1228
|
+
i -= 1
|
|
1229
|
+
}
|
|
1230
|
+
return i
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
/** Whether the comma-separated list in `head[from .. to)` holds `word` in any case, each element trimmed of OWS. */
|
|
1234
|
+
const http1ClientHeadListHas = (head: u8[], from: i32, to: i32, word: string): boolean => {
|
|
1235
|
+
const n: i32 = to < toI32(head.length) ? to : toI32(head.length)
|
|
1236
|
+
let start: i32 = from
|
|
1237
|
+
for (let k: i32 = from; k <= n; k += 1) {
|
|
1238
|
+
if (k === n || (k >= 0 && k < toI32(head.length) && head[k] === 44)) {
|
|
1239
|
+
const a: i32 = http1ClientHeadTrimStart(head, start, k)
|
|
1240
|
+
const b: i32 = http1ClientHeadTrimEnd(head, a, k)
|
|
1241
|
+
if (http1ClientHeadSameWord(head, a, b, word)) {
|
|
1242
|
+
return true
|
|
1243
|
+
}
|
|
1244
|
+
start = k + 1
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
return false
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1022
1250
|
/**
|
|
1023
1251
|
* A header section has ended: answer `HTTP1_INFORMATIONAL` for a 1xx, or
|
|
1024
1252
|
* decide the final response's framing and the connection's fate (RFC 9112
|
|
@@ -1052,12 +1280,13 @@ const http1ClientEndOfHead = (p: Http1ResponseParser): i32 => {
|
|
|
1052
1280
|
}
|
|
1053
1281
|
let codings: boolean = false
|
|
1054
1282
|
let length: i64 = toI64(-1)
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1283
|
+
for (let i: i32 = 0; i < p.spanCount; i += 1) {
|
|
1284
|
+
const a: i32 = p.spans[i * 4]
|
|
1285
|
+
const b: i32 = p.spans[i * 4 + 1]
|
|
1286
|
+
if (http1ClientHeadIs(p.head, a, b, "transfer-encoding")) {
|
|
1058
1287
|
codings = true
|
|
1059
|
-
} else if (
|
|
1060
|
-
length = http1ClientLengthList(p.
|
|
1288
|
+
} else if (http1ClientHeadIs(p.head, a, b, "content-length")) {
|
|
1289
|
+
length = http1ClientLengthList(p.head, p.spans[i * 4 + 2], p.spans[i * 4 + 3], length)
|
|
1061
1290
|
if (length === toI64(-2)) {
|
|
1062
1291
|
return http1ClientFail(
|
|
1063
1292
|
p,
|