Skip to content

Commit 2321358

Browse files
committed
add buffer.transcode to nodejs_compat
1 parent ce15e0b commit 2321358

File tree

9 files changed

+468
-26
lines changed

9 files changed

+468
-26
lines changed

src/node/buffer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
SlowBuffer,
1111
isAscii,
1212
isUtf8,
13+
transcode,
1314
} from 'node-internal:internal_buffer';
1415

1516
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
@@ -30,6 +31,7 @@ export {
3031
SlowBuffer,
3132
isAscii,
3233
isUtf8,
34+
transcode,
3335
};
3436

3537
export default {
@@ -46,4 +48,5 @@ export default {
4648
SlowBuffer,
4749
isAscii,
4850
isUtf8,
51+
transcode,
4952
};

src/node/internal/buffer.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ export function decode(buffer: Uint8Array, state: Uint8Array): string;
3737
export function flush(state: Uint8Array): string;
3838
export function isAscii(value: ArrayBufferView): boolean;
3939
export function isUtf8(value: ArrayBufferView): boolean;
40+
export function transcode(source: ArrayBufferView, fromEncoding: string, toEncoding: string): ArrayBuffer;

src/node/internal/crypto_dh.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ let DiffieHellman = function (this: DiffieHellman, sizeOrKey: number|ArrayLike,
8484
if (typeof sizeOrKey === 'number')
8585
validateInt32(sizeOrKey, 'sizeOrKey');
8686

87-
if (keyEncoding && !Buffer.isEncoding(keyEncoding) && keyEncoding !== 'buffer') {
87+
if (keyEncoding && keyEncoding !== 'buffer' && !Buffer.isEncoding(keyEncoding)) {
8888
genEncoding = generator as any;
8989
generator = keyEncoding;
9090
keyEncoding = "utf-8"; // default encoding

src/node/internal/internal_buffer.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ export function compare(a: Buffer|Uint8Array, b: Buffer|Uint8Array) {
440440

441441
Buffer.compare = compare;
442442

443-
export function isEncoding(encoding: unknown) {
443+
export function isEncoding(encoding: unknown): encoding is string {
444444
return typeof encoding === "string" &&
445445
encoding.length !== 0 &&
446446
normalizeEncoding(encoding) !== undefined;
@@ -2294,6 +2294,22 @@ export function isUtf8(value: ArrayBufferView) {
22942294
return bufferUtil.isUtf8(value);
22952295
}
22962296

2297+
export function transcode(source: ArrayBufferView, fromEncoding: string, toEncoding: string) {
2298+
if (!isArrayBufferView(source)) {
2299+
throw new ERR_INVALID_ARG_TYPE('source', 'ArrayBufferView', typeof source);
2300+
}
2301+
const normalizedFromEncoding = normalizeEncoding(fromEncoding);
2302+
if (!Buffer.isEncoding(normalizedFromEncoding)) {
2303+
throw new ERR_UNKNOWN_ENCODING(fromEncoding);
2304+
}
2305+
const normalizedToEncoding = normalizeEncoding(toEncoding);
2306+
if (!Buffer.isEncoding(normalizedToEncoding)) {
2307+
throw new ERR_UNKNOWN_ENCODING(toEncoding);
2308+
}
2309+
// TODO(soon): Optimization opportunity: Pass int encoding values instead of strings.
2310+
return Buffer.from(bufferUtil.transcode(source, normalizedFromEncoding, normalizedToEncoding));
2311+
}
2312+
22972313
export default {
22982314
Buffer,
22992315
constants,

src/workerd/api/node/buffer.c++

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
#include "buffer-string-search.h"
99
#include <workerd/jsg/buffersource.h>
1010
#include <kj/encoding.h>
11-
#include <algorithm>
11+
#include <kj/array.h>
1212
#include "simdutf.h"
13+
#include "i18n.h"
14+
15+
#include <algorithm>
1316

1417
// These are defined by <sys/byteorder.h> or <netinet/in.h> on some systems.
1518
// To avoid warnings, undefine them before redefining them.
@@ -85,34 +88,24 @@ void SwapBytes(kj::ArrayPtr<kj::byte> bytes) {
8588
}
8689
}
8790

88-
enum class Encoding {
89-
ASCII,
90-
LATIN1,
91-
UTF8,
92-
UTF16LE,
93-
BASE64,
94-
BASE64URL,
95-
HEX,
96-
};
97-
98-
Encoding getEncoding(kj::StringPtr encoding) {
99-
if (encoding == "utf8"_kj) {
91+
inline Encoding getEncoding(kj::StringPtr input) {
92+
if (input == "utf8"_kj) {
10093
return Encoding::UTF8;
101-
} else if (encoding == "ascii") {
94+
} else if (input == "ascii"_kj) {
10295
return Encoding::ASCII;
103-
} else if (encoding == "latin1") {
96+
} else if (input == "latin1"_kj) {
10497
return Encoding::LATIN1;
105-
} else if (encoding == "utf16le") {
98+
} else if (input == "utf16le"_kj) {
10699
return Encoding::UTF16LE;
107-
} else if (encoding == "base64") {
100+
} else if (input == "base64"_kj) {
108101
return Encoding::BASE64;
109-
} else if (encoding == "base64url") {
102+
} else if (input == "base64url"_kj) {
110103
return Encoding::BASE64URL;
111-
} else if (encoding == "hex") {
104+
} else if (input == "hex"_kj) {
112105
return Encoding::HEX;
113106
}
114107

115-
KJ_UNREACHABLE;
108+
JSG_FAIL_REQUIRE(Error, kj::str("Invalid encoding: ", input));
116109
}
117110

118111
kj::Maybe<uint> tryFromHexDigit(char c) {
@@ -137,7 +130,7 @@ kj::Array<byte> decodeHexTruncated(kj::ArrayPtr<kj::byte> text, bool strict = fa
137130
}
138131
text = text.slice(0, text.size() - 1);
139132
}
140-
kj::Vector vec = kj::Vector<kj::byte>(text.size() / 2);
133+
auto vec = kj::Vector<kj::byte>(text.size() / 2);
141134

142135
for (size_t i = 0; i < text.size(); i += 2) {
143136
byte b = 0;
@@ -216,8 +209,9 @@ uint32_t writeInto(
216209
dest.first(amountToCopy).copyFrom(bytes.first(amountToCopy));
217210
return amountToCopy;
218211
}
212+
default:
213+
KJ_UNREACHABLE;
219214
}
220-
KJ_UNREACHABLE;
221215
}
222216

223217
kj::Array<kj::byte> decodeStringImpl(
@@ -272,8 +266,9 @@ kj::Array<kj::byte> decodeStringImpl(
272266
string.writeInto(js, buf, options);
273267
return decodeHexTruncated(buf, strict);
274268
}
269+
default:
270+
KJ_UNREACHABLE;
275271
}
276-
KJ_UNREACHABLE;
277272
}
278273
} // namespace
279274

@@ -561,8 +556,9 @@ jsg::JsString toStringImpl(
561556
case Encoding::HEX: {
562557
return js.str(kj::encodeHex(slice));
563558
}
559+
default:
560+
KJ_UNREACHABLE;
564561
}
565-
KJ_UNREACHABLE;
566562
}
567563

568564
} // namespace
@@ -876,5 +872,16 @@ bool BufferUtil::isUtf8(kj::Array<kj::byte> buffer) {
876872
return simdutf::validate_utf8(buffer.asChars().begin(), buffer.size());
877873
}
878874

875+
kj::Array<kj::byte> BufferUtil::transcode(kj::Array<kj::byte> source, kj::String rawFromEncoding, kj::String rawToEncoding) {
876+
auto fromEncoding = getEncoding(rawFromEncoding);
877+
auto toEncoding = getEncoding(rawToEncoding);
878+
879+
JSG_REQUIRE(i18n::canBeTranscoded(fromEncoding) &&
880+
i18n::canBeTranscoded(toEncoding), Error,
881+
"Unable to transcode buffer due to unsupported encoding");
882+
883+
return i18n::transcode(source, fromEncoding, toEncoding);
884+
}
885+
879886
} // namespace workerd::api::node {
880887

src/workerd/api/node/buffer.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ class BufferUtil final: public jsg::Object {
8181
jsg::JsString flush(jsg::Lock& js, kj::Array<kj::byte> state);
8282
bool isAscii(kj::Array<kj::byte> bytes);
8383
bool isUtf8(kj::Array<kj::byte> bytes);
84+
kj::Array<kj::byte> transcode(kj::Array<kj::byte> source,
85+
kj::String rawFromEncoding,
86+
kj::String rawToEncoding);
8487

8588
JSG_RESOURCE_TYPE(BufferUtil) {
8689
JSG_METHOD(byteLength);
@@ -94,6 +97,7 @@ class BufferUtil final: public jsg::Object {
9497
JSG_METHOD(write);
9598
JSG_METHOD(isAscii);
9699
JSG_METHOD(isUtf8);
100+
JSG_METHOD(transcode);
97101

98102
// For StringDecoder
99103
JSG_METHOD(decode);

0 commit comments

Comments
 (0)