Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ext/node/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ deno_core::extension!(deno_node,
"internal/http2/util.ts",
"internal/idna.ts",
"internal/net.ts",
"internal/normalize_encoding.mjs",
"internal/normalize_encoding.ts",
"internal/options.ts",
"internal/primordials.mjs",
"internal/process/per_thread.mjs",
Expand Down
74 changes: 1 addition & 73 deletions ext/node/polyfills/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const {
Error,
PromisePrototypeThen,
ArrayPrototypePop,
StringPrototypeToLowerCase,
NumberIsInteger,
ObjectGetOwnPropertyNames,
ReflectGetOwnPropertyDescriptor,
Expand All @@ -29,6 +28,7 @@ export type TextEncodings =
| "ucs2"
| "ucs-2"
| "base64"
| "base64url"
| "latin1"
| "hex";

Expand Down Expand Up @@ -94,78 +94,6 @@ export function spliceOne(list: string[], index: number) {
ArrayPrototypePop(list);
}

// Taken from: https://github.com/nodejs/node/blob/ba684805b6c0eded76e5cd89ee00328ac7a59365/lib/internal/util.js#L125
// Return undefined if there is no match.
// Move the "slow cases" to a separate function to make sure this function gets
// inlined properly. That prioritizes the common case.
export function normalizeEncoding(
enc: string | null,
): TextEncodings | undefined {
if (enc == null || enc === "utf8" || enc === "utf-8") return "utf8";
return slowCases(enc);
}

// https://github.com/nodejs/node/blob/ba684805b6c0eded76e5cd89ee00328ac7a59365/lib/internal/util.js#L130
function slowCases(enc: string): TextEncodings | undefined {
switch (enc.length) {
case 4:
if (enc === "UTF8") return "utf8";
if (enc === "ucs2" || enc === "UCS2") return "utf16le";
enc = StringPrototypeToLowerCase(enc);
if (enc === "utf8") return "utf8";
if (enc === "ucs2") return "utf16le";
break;
case 3:
if (
enc === "hex" || enc === "HEX" ||
StringPrototypeToLowerCase(enc) === "hex"
) {
return "hex";
}
break;
case 5:
if (enc === "ascii") return "ascii";
if (enc === "ucs-2") return "utf16le";
if (enc === "UTF-8") return "utf8";
if (enc === "ASCII") return "ascii";
if (enc === "UCS-2") return "utf16le";
enc = StringPrototypeToLowerCase(enc);
if (enc === "utf-8") return "utf8";
if (enc === "ascii") return "ascii";
if (enc === "ucs-2") return "utf16le";
break;
case 6:
if (enc === "base64") return "base64";
if (enc === "latin1" || enc === "binary") return "latin1";
if (enc === "BASE64") return "base64";
if (enc === "LATIN1" || enc === "BINARY") return "latin1";
enc = StringPrototypeToLowerCase(enc);
if (enc === "base64") return "base64";
if (enc === "latin1" || enc === "binary") return "latin1";
break;
case 7:
if (
enc === "utf16le" ||
enc === "UTF16LE" ||
StringPrototypeToLowerCase(enc) === "utf16le"
) {
return "utf16le";
}
break;
case 8:
if (
enc === "utf-16le" ||
enc === "UTF-16LE" ||
StringPrototypeToLowerCase(enc) === "utf-16le"
) {
return "utf16le";
}
break;
default:
if (enc === "") return "utf8";
}
}

export function validateIntegerRange(
value: number,
name: string,
Expand Down
4 changes: 1 addition & 3 deletions ext/node/polyfills/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,7 @@ import type {
TransformOptions,
WritableOptions,
} from "ext:deno_node/_stream.d.ts";
import {
normalizeEncoding,
} from "ext:deno_node/internal/normalize_encoding.mjs";
import { normalizeEncoding } from "ext:deno_node/internal/util.mjs";
import { isArrayBufferView } from "ext:deno_node/internal/util/types.ts";
import { validateString } from "ext:deno_node/internal/validators.mjs";
import { crypto as webcrypto } from "ext:deno_crypto/00_crypto.js";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

import { primordials } from "ext:core/mod.js";
const { StringPrototypeToLowerCase } = primordials;
import type { TextEncodings } from "ext:deno_node/_utils.ts";

export function normalizeEncoding(enc) {
// https://github.com/nodejs/node/blob/a73b575304722a3682fbec3a5fb13b39c5791342/lib/internal/util.js#L252
export function normalizeEncoding(
enc: string | null,
): TextEncodings | undefined {
if (enc == null || enc === "utf8" || enc === "utf-8") return "utf8";
return slowCases(enc);
}

export function slowCases(enc) {
function slowCases(enc: string): TextEncodings | undefined {
switch (enc.length) {
case 4:
if (enc === "UTF8") return "utf8";
Expand Down
8 changes: 2 additions & 6 deletions ext/node/polyfills/internal/util.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
// Copyright 2018-2025 the Deno authors. MIT license.

import { validateFunction } from "ext:deno_node/internal/validators.mjs";
import {
normalizeEncoding,
slowCases,
} from "ext:deno_node/internal/normalize_encoding.mjs";
export { normalizeEncoding, slowCases };
import { normalizeEncoding } from "ext:deno_node/internal/normalize_encoding.ts";
export { normalizeEncoding };
import {
ObjectCreate,
StringPrototypeToUpperCase,
Expand Down Expand Up @@ -186,5 +183,4 @@ export default {
normalizeEncoding,
once,
promisify,
slowCases,
};
2 changes: 1 addition & 1 deletion ext/node/polyfills/internal/validators.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const {
import { codes } from "ext:deno_node/internal/error_codes.ts";
import { hideStackFrames } from "ext:deno_node/internal/hide_stack_frames.ts";
import { isArrayBufferView } from "ext:deno_node/internal/util/types.ts";
import { normalizeEncoding } from "ext:deno_node/internal/normalize_encoding.mjs";
import { normalizeEncoding } from "ext:deno_node/internal/util.mjs";

/**
* @param {number} value
Expand Down
14 changes: 10 additions & 4 deletions ext/node/polyfills/string_decoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
// (https://github.com/nodejs/node/blob/ba06c5c509956dc413f91b755c1c93798bb700d4/src/string_decoder.cc)

import { Buffer, constants } from "node:buffer";
import { normalizeEncoding as castEncoding } from "ext:deno_node/_utils.ts";
import { normalizeEncoding as castEncoding } from "ext:deno_node/internal/util.mjs";
import {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_THIS,
Expand Down Expand Up @@ -132,7 +132,8 @@ function decode(this: StringDecoder, buf: Buffer) {
let rest = "";

if (
enc === Encoding.Utf8 || enc === Encoding.Utf16 || enc === Encoding.Base64
enc === Encoding.Utf8 || enc === Encoding.Utf16 ||
enc === Encoding.Base64 || enc === Encoding.Base64Url
) {
// check if we need to finish an incomplete char from the last chunk
// written. If we do, we copy the bytes into our `lastChar` buffer
Expand Down Expand Up @@ -242,7 +243,7 @@ function decode(this: StringDecoder, buf: Buffer) {
this[kBufferedBytes] = 2;
this[kMissingBytes] = 2;
}
} else if (enc === Encoding.Base64) {
} else if (enc === Encoding.Base64 || enc === Encoding.Base64Url) {
this[kBufferedBytes] = (buf.length - bufIdx) % 3;
if (this[kBufferedBytes] > 0) {
this[kMissingBytes] = 3 - this[kBufferedBytes];
Expand Down Expand Up @@ -302,6 +303,7 @@ function flush(this: StringDecoder) {
enum Encoding {
Utf8,
Base64,
Base64Url,
Utf16,
Ascii,
Latin1,
Expand Down Expand Up @@ -345,7 +347,11 @@ export function StringDecoder(this: Partial<StringDecoder>, encoding?: string) {
break;
case "base64":
enc = Encoding.Base64;
bufLen = 3;
bufLen = 4;
break;
case "base64url":
enc = Encoding.Base64Url;
bufLen = 4;
break;
case "utf16le":
enc = Encoding.Utf16;
Expand Down
2 changes: 2 additions & 0 deletions tests/node_compat/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,8 @@
"parallel/test-stream3-pause-then-read.js" = {}
"parallel/test-stream3-pipeline-async-iterator.js" = {}
"parallel/test-streams-highwatermark.js" = {}
"parallel/test-string-decoder-end.js" = {}
"parallel/test-string-decoder-fuzz.js" = {}
"parallel/test-string-decoder.js" = { flaky = true }
"parallel/test-sys.js" = {}
"parallel/test-timers-api-refs.js" = {}
Expand Down
35 changes: 35 additions & 0 deletions tests/unit_node/string_decoder_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,41 @@ Deno.test({
},
});

Deno.test({
name: "String decoder is encoding base64url correctly",
fn() {
let decoder;

decoder = new StringDecoder("base64url");
assertEquals(decoder.write(Buffer.from("E1", "hex")), "");
assertEquals(decoder.end(), "4Q");

decoder = new StringDecoder("base64url");
assertEquals(decoder.write(Buffer.from("E18B", "hex")), "");
assertEquals(decoder.end(), "4Ys");

decoder = new StringDecoder("base64url");
assertEquals(decoder.write(Buffer.from("\ufffd")), "77-9");
assertEquals(decoder.end(), "");

decoder = new StringDecoder("base64url");
assertEquals(
decoder.write(Buffer.from("\ufffd\ufffd\ufffd")),
"77-977-977-9",
);
assertEquals(decoder.end(), "");

decoder = new StringDecoder("base64url");
assertEquals(decoder.write(Buffer.from("EFBFBDE2", "hex")), "77-9");
assertEquals(decoder.end(), "4g");

decoder = new StringDecoder("base64url");
assertEquals(decoder.write(Buffer.from("F1", "hex")), "");
assertEquals(decoder.write(Buffer.from("41F2", "hex")), "8UHy");
assertEquals(decoder.end(), "");
},
});

Deno.test({
name: "String decoder is encoding hex correctly",
fn() {
Expand Down
2 changes: 1 addition & 1 deletion tools/core_import_map.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@
"ext:deno_node/internal/http.ts": "../ext/node/polyfills/internal/http.ts",
"ext:deno_node/internal/http2/util.ts": "../ext/node/polyfills/internal/http2/util.ts",
"ext:deno_node/internal/net.ts": "../ext/node/polyfills/internal/net.ts",
"ext:deno_node/internal/normalize_encoding.mjs": "../ext/node/polyfills/internal/normalize_encoding.mjs",
"ext:deno_node/internal/normalize_encoding.ts": "../ext/node/polyfills/internal/normalize_encoding.ts",
"ext:deno_node/internal/options.ts": "../ext/node/polyfills/internal/options.ts",
"ext:deno_node/internal/primordials.mjs": "../ext/node/polyfills/internal/primordials.mjs",
"ext:deno_node/internal/process/per_thread.mjs": "../ext/node/polyfills/internal/process/per_thread.mjs",
Expand Down
Loading