@poe-platform/safe-fs 0.1.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/LICENSE +21 -0
- package/README.md +290 -0
- package/dist/safe-fs/bridge/confinement.d.ts +3 -0
- package/dist/safe-fs/bridge/confinement.js +171 -0
- package/dist/safe-fs/bridge/filesystem.d.ts +88 -0
- package/dist/safe-fs/bridge/filesystem.js +411 -0
- package/dist/safe-fs/bridge/index.d.ts +6 -0
- package/dist/safe-fs/bridge/index.js +18 -0
- package/dist/safe-fs/bridge/stats.d.ts +4 -0
- package/dist/safe-fs/bridge/stats.js +37 -0
- package/dist/safe-fs/bridge/types.d.ts +75 -0
- package/dist/safe-fs/bridge/types.js +1 -0
- package/dist/safe-fs/bridge/values.d.ts +6 -0
- package/dist/safe-fs/bridge/values.js +65 -0
- package/dist/safe-fs/config/memory.d.ts +3 -0
- package/dist/safe-fs/config/memory.js +9 -0
- package/dist/safe-fs/config/real.d.ts +5 -0
- package/dist/safe-fs/config/real.js +16 -0
- package/dist/safe-fs/config.d.ts +15 -0
- package/dist/safe-fs/config.js +46 -0
- package/dist/safe-fs/config.node.d.ts +2 -0
- package/dist/safe-fs/config.node.js +22 -0
- package/dist/safe-fs/contracts/abort.d.ts +5 -0
- package/dist/safe-fs/contracts/abort.js +34 -0
- package/dist/safe-fs/contracts/cleanup.d.ts +1 -0
- package/dist/safe-fs/contracts/cleanup.js +9 -0
- package/dist/safe-fs/contracts/errors.d.ts +50 -0
- package/dist/safe-fs/contracts/errors.js +77 -0
- package/dist/safe-fs/contracts/filesystem.d.ts +96 -0
- package/dist/safe-fs/contracts/filesystem.js +1 -0
- package/dist/safe-fs/contracts/index.d.ts +4 -0
- package/dist/safe-fs/contracts/index.js +4 -0
- package/dist/safe-fs/contracts/io.d.ts +8 -0
- package/dist/safe-fs/contracts/io.js +92 -0
- package/dist/safe-fs/contracts/path.d.ts +8 -0
- package/dist/safe-fs/contracts/path.js +8 -0
- package/dist/safe-fs/contracts/portable-path.d.ts +13 -0
- package/dist/safe-fs/contracts/portable-path.js +57 -0
- package/dist/safe-fs/contracts/virtual-path.d.ts +7 -0
- package/dist/safe-fs/contracts/virtual-path.js +66 -0
- package/dist/safe-fs/core.d.ts +12 -0
- package/dist/safe-fs/core.js +12 -0
- package/dist/safe-fs/fs/memory/index.d.ts +58 -0
- package/dist/safe-fs/fs/memory/index.js +532 -0
- package/dist/safe-fs/fs/mount/comparison.d.ts +19 -0
- package/dist/safe-fs/fs/mount/comparison.js +118 -0
- package/dist/safe-fs/fs/mount/identity.d.ts +2 -0
- package/dist/safe-fs/fs/mount/identity.js +12 -0
- package/dist/safe-fs/fs/mount/index.d.ts +44 -0
- package/dist/safe-fs/fs/mount/index.js +546 -0
- package/dist/safe-fs/fs/overlay/index.d.ts +73 -0
- package/dist/safe-fs/fs/overlay/index.js +927 -0
- package/dist/safe-fs/fs/readonly/index.d.ts +31 -0
- package/dist/safe-fs/fs/readonly/index.js +128 -0
- package/dist/safe-fs/fs/real/allocation.d.ts +1 -0
- package/dist/safe-fs/fs/real/allocation.js +8 -0
- package/dist/safe-fs/fs/real/index.d.ts +76 -0
- package/dist/safe-fs/fs/real/index.js +543 -0
- package/dist/safe-fs/fs/s3/authority.d.ts +7 -0
- package/dist/safe-fs/fs/s3/authority.js +31 -0
- package/dist/safe-fs/fs/s3/filesystem.d.ts +93 -0
- package/dist/safe-fs/fs/s3/filesystem.js +1024 -0
- package/dist/safe-fs/fs/s3/http/index.d.ts +2 -0
- package/dist/safe-fs/fs/s3/http/index.js +1 -0
- package/dist/safe-fs/fs/s3/http/request.d.ts +16 -0
- package/dist/safe-fs/fs/s3/http/request.js +196 -0
- package/dist/safe-fs/fs/s3/http/signature.d.ts +20 -0
- package/dist/safe-fs/fs/s3/http/signature.js +74 -0
- package/dist/safe-fs/fs/s3/http/transport.d.ts +3 -0
- package/dist/safe-fs/fs/s3/http/transport.js +455 -0
- package/dist/safe-fs/fs/s3/http/types.d.ts +30 -0
- package/dist/safe-fs/fs/s3/http/types.js +1 -0
- package/dist/safe-fs/fs/s3/http/xml.d.ts +11 -0
- package/dist/safe-fs/fs/s3/http/xml.js +188 -0
- package/dist/safe-fs/fs/s3/index.d.ts +6 -0
- package/dist/safe-fs/fs/s3/index.js +3 -0
- package/dist/safe-fs/fs/s3/mock.d.ts +48 -0
- package/dist/safe-fs/fs/s3/mock.js +233 -0
- package/dist/safe-fs/fs/s3/registry.d.ts +10 -0
- package/dist/safe-fs/fs/s3/registry.js +71 -0
- package/dist/safe-fs/fs/s3/transport.d.ts +103 -0
- package/dist/safe-fs/fs/s3/transport.js +28 -0
- package/dist/safe-fs/fs/webdav/index.d.ts +2 -0
- package/dist/safe-fs/fs/webdav/index.js +1 -0
- package/dist/safe-fs/fs/webdav/resource-id.d.ts +18 -0
- package/dist/safe-fs/fs/webdav/resource-id.js +109 -0
- package/dist/safe-fs/fs/webdav/webdav.d.ts +101 -0
- package/dist/safe-fs/fs/webdav/webdav.js +1226 -0
- package/dist/safe-fs/fs/webdav/xml.d.ts +15 -0
- package/dist/safe-fs/fs/webdav/xml.js +250 -0
- package/dist/safe-fs/index.d.ts +14 -0
- package/dist/safe-fs/index.js +12 -0
- package/dist/safe-fs/node/filesystem.d.ts +11 -0
- package/dist/safe-fs/node/filesystem.js +28 -0
- package/dist/safe-fs/node/index.d.ts +2 -0
- package/dist/safe-fs/node/index.js +1 -0
- package/dist/safe-fs/node/stats.d.ts +1 -0
- package/dist/safe-fs/node/stats.js +1 -0
- package/dist/safe-fs/node/values.d.ts +1 -0
- package/dist/safe-fs/node/values.js +1 -0
- package/dist/safe-fs/node-host.d.ts +1 -0
- package/dist/safe-fs/node-host.js +1 -0
- package/dist/safe-fs/node-unavailable.d.ts +1 -0
- package/dist/safe-fs/node-unavailable.js +1 -0
- package/dist/safe-fs/platform/browser.d.ts +12 -0
- package/dist/safe-fs/platform/browser.js +39 -0
- package/dist/safe-fs/platform/node.d.ts +13 -0
- package/dist/safe-fs/platform/node.js +26 -0
- package/package.json +108 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createS3HttpTransport } from "./transport.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IncomingMessage, RequestOptions } from "node:http";
|
|
2
|
+
import type { S3HttpRequestFactory } from "./types.js";
|
|
3
|
+
export interface RequestScope {
|
|
4
|
+
readonly signal: AbortSignal;
|
|
5
|
+
readonly finish: () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function scopeFor(signal: AbortSignal | undefined, timeout: number): RequestScope;
|
|
8
|
+
export declare function abortable<Value>(pending: Promise<Value>, signal: AbortSignal): Promise<Value>;
|
|
9
|
+
export interface WireResponse {
|
|
10
|
+
readonly message: IncomingMessage;
|
|
11
|
+
readonly body: AsyncIterable<Uint8Array>;
|
|
12
|
+
readonly close: () => void;
|
|
13
|
+
}
|
|
14
|
+
export declare function sendRequest(options: RequestOptions, body: Uint8Array, scope: RequestScope, factory?: S3HttpRequestFactory): Promise<WireResponse>;
|
|
15
|
+
export declare function limitedBody(response: WireResponse, maximum: number, expected?: number): AsyncIterable<Uint8Array>;
|
|
16
|
+
export declare function collect(response: WireResponse, maximum: number, expected?: number): Promise<Uint8Array>;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { once } from "node:events";
|
|
2
|
+
import { request as httpRequest } from "node:http";
|
|
3
|
+
import { request as httpsRequest } from "node:https";
|
|
4
|
+
import { S3ServiceError } from "../transport.js";
|
|
5
|
+
export function scopeFor(signal, timeout) {
|
|
6
|
+
const controller = new AbortController();
|
|
7
|
+
const abort = () => controller.abort(signal?.reason);
|
|
8
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
9
|
+
if (signal?.aborted)
|
|
10
|
+
abort();
|
|
11
|
+
const timer = setTimeout(() => controller.abort(new S3ServiceError("RequestTimeout", 408)), timeout);
|
|
12
|
+
return {
|
|
13
|
+
signal: controller.signal,
|
|
14
|
+
finish: () => { clearTimeout(timer); signal?.removeEventListener("abort", abort); },
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export async function abortable(pending, signal) {
|
|
18
|
+
let abort;
|
|
19
|
+
try {
|
|
20
|
+
return await new Promise((resolve, reject) => {
|
|
21
|
+
abort = () => reject(signal.reason);
|
|
22
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
23
|
+
pending.then(resolve, reject);
|
|
24
|
+
if (signal.aborted)
|
|
25
|
+
abort();
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
if (abort)
|
|
30
|
+
signal.removeEventListener("abort", abort);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export async function sendRequest(options, body, scope, factory) {
|
|
34
|
+
const signal = scope.signal;
|
|
35
|
+
signal.throwIfAborted();
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
let request;
|
|
38
|
+
let response;
|
|
39
|
+
let headersReceived = false;
|
|
40
|
+
let finished = false;
|
|
41
|
+
const cleanup = () => {
|
|
42
|
+
signal.removeEventListener("abort", abort);
|
|
43
|
+
scope.finish();
|
|
44
|
+
};
|
|
45
|
+
const close = () => {
|
|
46
|
+
if (finished)
|
|
47
|
+
return;
|
|
48
|
+
finished = true;
|
|
49
|
+
cleanup();
|
|
50
|
+
response?.destroy();
|
|
51
|
+
request?.destroy();
|
|
52
|
+
};
|
|
53
|
+
const fail = (error) => {
|
|
54
|
+
reject(signal.aborted ? signal.reason : error);
|
|
55
|
+
close();
|
|
56
|
+
};
|
|
57
|
+
const abort = () => fail(signal.reason);
|
|
58
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
59
|
+
try {
|
|
60
|
+
const create = factory ?? (options.protocol === "https:" ? httpsRequest : httpRequest);
|
|
61
|
+
request = create(options, message => {
|
|
62
|
+
response = message;
|
|
63
|
+
message.on("error", () => { });
|
|
64
|
+
if (finished || signal.aborted) {
|
|
65
|
+
close();
|
|
66
|
+
message.destroy();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
headersReceived = true;
|
|
70
|
+
message.once("close", cleanup);
|
|
71
|
+
let claimed = false;
|
|
72
|
+
const source = {
|
|
73
|
+
[Symbol.asyncIterator]() {
|
|
74
|
+
if (claimed)
|
|
75
|
+
throw new S3ServiceError("InvalidResponse", 502, "response body is single-use");
|
|
76
|
+
claimed = true;
|
|
77
|
+
const iterator = message[Symbol.asyncIterator]();
|
|
78
|
+
let ended = false;
|
|
79
|
+
return {
|
|
80
|
+
async next() {
|
|
81
|
+
try {
|
|
82
|
+
signal.throwIfAborted();
|
|
83
|
+
if (ended)
|
|
84
|
+
return { done: true, value: undefined };
|
|
85
|
+
const result = await abortable(iterator.next(), signal);
|
|
86
|
+
signal.throwIfAborted();
|
|
87
|
+
if (result.done) {
|
|
88
|
+
ended = true;
|
|
89
|
+
if (!message.complete)
|
|
90
|
+
throw new S3ServiceError("InvalidResponse", 502, "incomplete HTTP response");
|
|
91
|
+
close();
|
|
92
|
+
return { done: true, value: undefined };
|
|
93
|
+
}
|
|
94
|
+
if (!(result.value instanceof Uint8Array))
|
|
95
|
+
throw new S3ServiceError("InvalidResponse", 502, "nonbinary HTTP body");
|
|
96
|
+
return { done: false, value: new Uint8Array(result.value) };
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
ended = true;
|
|
100
|
+
close();
|
|
101
|
+
throw signal.aborted ? signal.reason : error;
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
async return() {
|
|
105
|
+
ended = true;
|
|
106
|
+
close();
|
|
107
|
+
return { done: true, value: undefined };
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
resolve({ message, body: source, close });
|
|
113
|
+
});
|
|
114
|
+
request.on("error", fail);
|
|
115
|
+
request.once("close", () => {
|
|
116
|
+
if (!headersReceived)
|
|
117
|
+
fail(new S3ServiceError("InvalidResponse", 502, "connection closed before response headers"));
|
|
118
|
+
});
|
|
119
|
+
if (signal.aborted || finished) {
|
|
120
|
+
abort();
|
|
121
|
+
request.destroy();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
void (async () => {
|
|
125
|
+
for (let offset = 0; offset < body.length; offset += 64 * 1024) {
|
|
126
|
+
signal.throwIfAborted();
|
|
127
|
+
if (!request.write(body.subarray(offset, offset + 64 * 1024)))
|
|
128
|
+
await once(request, "drain", { signal });
|
|
129
|
+
}
|
|
130
|
+
signal.throwIfAborted();
|
|
131
|
+
request.end();
|
|
132
|
+
})().catch(fail);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
fail(error);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
export function limitedBody(response, maximum, expected) {
|
|
140
|
+
if (expected !== undefined && expected > maximum) {
|
|
141
|
+
response.close();
|
|
142
|
+
throw new S3ServiceError("EntityTooLarge", 413, "response exceeds byte limit");
|
|
143
|
+
}
|
|
144
|
+
let claimed = false;
|
|
145
|
+
return {
|
|
146
|
+
[Symbol.asyncIterator]() {
|
|
147
|
+
if (claimed)
|
|
148
|
+
throw new S3ServiceError("InvalidResponse", 502, "response body is single-use");
|
|
149
|
+
claimed = true;
|
|
150
|
+
const iterator = response.body[Symbol.asyncIterator]();
|
|
151
|
+
let count = 0;
|
|
152
|
+
return {
|
|
153
|
+
async next() {
|
|
154
|
+
try {
|
|
155
|
+
const result = await iterator.next();
|
|
156
|
+
if (result.done) {
|
|
157
|
+
if (expected !== undefined && count !== expected)
|
|
158
|
+
throw new S3ServiceError("InvalidResponse", 502, "response content length mismatch");
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
count += result.value.length;
|
|
162
|
+
if (count > maximum)
|
|
163
|
+
throw new S3ServiceError("EntityTooLarge", 413, "response exceeds byte limit");
|
|
164
|
+
if (expected !== undefined && count > expected)
|
|
165
|
+
throw new S3ServiceError("InvalidResponse", 502, "response content length mismatch");
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
response.close();
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
async return() {
|
|
175
|
+
response.close();
|
|
176
|
+
await iterator.return?.();
|
|
177
|
+
return { done: true, value: undefined };
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
export async function collect(response, maximum, expected) {
|
|
184
|
+
const chunks = [];
|
|
185
|
+
let count = 0;
|
|
186
|
+
try {
|
|
187
|
+
for await (const chunk of limitedBody(response, maximum, expected)) {
|
|
188
|
+
chunks.push(chunk);
|
|
189
|
+
count += chunk.length;
|
|
190
|
+
}
|
|
191
|
+
return Buffer.concat(chunks, count);
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
response.close();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { S3HttpCredentials } from "./types.js";
|
|
2
|
+
export declare function invalid(message: string): never;
|
|
3
|
+
export declare function uriEncode(value: string, preserveSlash?: boolean): string;
|
|
4
|
+
export declare function canonicalQuery(entries: readonly (readonly [string, string])[]): string;
|
|
5
|
+
export declare function sha256(bytes: Uint8Array | string): string;
|
|
6
|
+
export declare function headerValue(value: string): string;
|
|
7
|
+
export declare function signRequest(input: {
|
|
8
|
+
readonly method: string;
|
|
9
|
+
readonly path: string;
|
|
10
|
+
readonly query: string;
|
|
11
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
12
|
+
readonly body: Uint8Array;
|
|
13
|
+
readonly region: string;
|
|
14
|
+
readonly credentials: S3HttpCredentials;
|
|
15
|
+
readonly date: Date;
|
|
16
|
+
}): {
|
|
17
|
+
readonly headers: Record<string, string>;
|
|
18
|
+
readonly canonicalRequest: string;
|
|
19
|
+
readonly signature: string;
|
|
20
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createHash, createHmac } from "node:crypto";
|
|
2
|
+
import { S3ServiceError } from "../transport.js";
|
|
3
|
+
export function invalid(message) {
|
|
4
|
+
throw new S3ServiceError("InvalidArgument", 400, message);
|
|
5
|
+
}
|
|
6
|
+
export function uriEncode(value, preserveSlash = false) {
|
|
7
|
+
if (typeof value !== "string")
|
|
8
|
+
invalid("expected a UTF-8 string");
|
|
9
|
+
try {
|
|
10
|
+
const encoded = encodeURIComponent(value).replace(/[!'()*]/g, character => `%${character.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
11
|
+
return preserveSlash ? encoded.replace(/%2F/g, "/") : encoded;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return invalid("invalid Unicode string");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function canonicalQuery(entries) {
|
|
18
|
+
return entries.map(([name, value]) => [uriEncode(name), uriEncode(value)])
|
|
19
|
+
.sort(([leftName, leftValue], [rightName, rightValue]) => leftName < rightName ? -1 : leftName > rightName ? 1 : leftValue < rightValue ? -1 : leftValue > rightValue ? 1 : 0)
|
|
20
|
+
.map(([name, value]) => `${name}=${value}`).join("&");
|
|
21
|
+
}
|
|
22
|
+
export function sha256(bytes) {
|
|
23
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
24
|
+
}
|
|
25
|
+
export function headerValue(value) {
|
|
26
|
+
if (typeof value !== "string" || /[^\x20-\x7e\t]/.test(value))
|
|
27
|
+
invalid("header values must be printable ASCII without line breaks");
|
|
28
|
+
return value.trim().replace(/[ \t]+/g, " ");
|
|
29
|
+
}
|
|
30
|
+
export function signRequest(input) {
|
|
31
|
+
const credentials = input.credentials;
|
|
32
|
+
if (!credentials || typeof credentials.accessKeyId !== "string" || !/^[A-Za-z0-9_-]+$/.test(credentials.accessKeyId)
|
|
33
|
+
|| typeof credentials.secretAccessKey !== "string" || credentials.secretAccessKey.length === 0
|
|
34
|
+
|| credentials.secretAccessKey.length > 4096)
|
|
35
|
+
invalid("explicit signing credentials are required");
|
|
36
|
+
if (!/^[a-z0-9-]{1,64}$/.test(input.region))
|
|
37
|
+
invalid("invalid signing region");
|
|
38
|
+
if (!(input.date instanceof Date) || !Number.isFinite(input.date.getTime())
|
|
39
|
+
|| input.date.getUTCFullYear() < 1970 || input.date.getUTCFullYear() > 9999)
|
|
40
|
+
invalid("invalid signing clock");
|
|
41
|
+
const timestamp = input.date.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
42
|
+
const date = timestamp.slice(0, 8);
|
|
43
|
+
const headers = Object.create(null);
|
|
44
|
+
for (const [name, value] of Object.entries(input.headers)) {
|
|
45
|
+
const lower = name.toLowerCase();
|
|
46
|
+
if (!/^[a-z0-9-]+$/.test(lower) || lower in headers)
|
|
47
|
+
invalid("invalid or duplicate signing header");
|
|
48
|
+
headers[lower] = headerValue(value);
|
|
49
|
+
}
|
|
50
|
+
if (!headers.host)
|
|
51
|
+
invalid("host is required for signing");
|
|
52
|
+
const payloadHash = sha256(input.body);
|
|
53
|
+
headers["x-amz-date"] = timestamp;
|
|
54
|
+
headers["x-amz-content-sha256"] = payloadHash;
|
|
55
|
+
if (credentials.sessionToken !== undefined) {
|
|
56
|
+
headers["x-amz-security-token"] = headerValue(credentials.sessionToken);
|
|
57
|
+
if (!headers["x-amz-security-token"])
|
|
58
|
+
invalid("session token must not be empty");
|
|
59
|
+
}
|
|
60
|
+
const names = Object.keys(headers).sort();
|
|
61
|
+
const signedHeaders = names.join(";");
|
|
62
|
+
const canonicalRequest = [input.method, input.path, input.query,
|
|
63
|
+
names.map(name => `${name}:${headers[name]}\n`).join(""), signedHeaders, payloadHash].join("\n");
|
|
64
|
+
const scope = `${date}/${input.region}/s3/aws4_request`;
|
|
65
|
+
const stringToSign = ["AWS4-HMAC-SHA256", timestamp, scope, sha256(canonicalRequest)].join("\n");
|
|
66
|
+
let key = createHmac("sha256", `AWS4${credentials.secretAccessKey}`).update(date).digest();
|
|
67
|
+
for (const part of [input.region, "s3", "aws4_request"])
|
|
68
|
+
key = createHmac("sha256", key).update(part).digest();
|
|
69
|
+
const signature = createHmac("sha256", key).update(stringToSign).digest("hex");
|
|
70
|
+
headers.authorization = `AWS4-HMAC-SHA256 Credential=${credentials.accessKeyId}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
71
|
+
if (Object.entries(headers).reduce((size, [name, value]) => size + name.length + value.length + 4, 0) > 16 * 1024)
|
|
72
|
+
invalid("request headers exceed 16 KiB");
|
|
73
|
+
return { headers, canonicalRequest, signature };
|
|
74
|
+
}
|