diff options
-rw-r--r-- | src/env_loader.zig | 13 | ||||
-rw-r--r-- | test/cli/run/env.test.ts | 68 |
2 files changed, 75 insertions, 6 deletions
diff --git a/src/env_loader.zig b/src/env_loader.zig index bf1bf450e..088ec2d0c 100644 --- a/src/env_loader.zig +++ b/src/env_loader.zig @@ -624,6 +624,7 @@ const Parser = struct { fn parseQuoted(this: *Parser, comptime quote: u8) ?string { if (comptime Environment.allow_assert) std.debug.assert(this.src[this.pos] == quote); const start = this.pos; + const max_len = value_buffer.len; var end = start + 1; while (end < this.src.len) : (end += 1) { switch (this.src[end]) { @@ -639,7 +640,7 @@ const Parser = struct { { var ptr: usize = 0; var i = start; - while (i < end) { + while (i < end and ptr < max_len) { switch (this.src[i]) { '\\' => if (comptime quote == '"') { if (comptime Environment.allow_assert) std.debug.assert(i + 1 < end); @@ -647,16 +648,18 @@ const Parser = struct { 'n' => { value_buffer[ptr] = '\n'; ptr += 1; - i += 1; + i += 2; }, 'r' => { value_buffer[ptr] = '\r'; ptr += 1; - i += 1; + i += 2; }, else => { - value_buffer[ptr] = this.src[i]; - value_buffer[ptr + 1] = this.src[i + 1]; + if (ptr + 1 < max_len) { + value_buffer[ptr] = this.src[i]; + value_buffer[ptr + 1] = this.src[i + 1]; + } ptr += 2; i += 2; }, diff --git a/test/cli/run/env.test.ts b/test/cli/run/env.test.ts index 6e4d83d44..159793e27 100644 --- a/test/cli/run/env.test.ts +++ b/test/cli/run/env.test.ts @@ -1,5 +1,22 @@ import { describe, expect, test } from "bun:test"; -import { bunRun, bunTest, tempDirWithFiles } from "harness"; +import { bunRun, bunTest, tempDirWithFiles, bunExe, bunEnv } from "harness"; +import path from "path"; + +function bunRunWithoutTrim(file: string, env?: Record<string, string>) { + const result = Bun.spawnSync([bunExe(), file], { + cwd: path.dirname(file), + env: { + ...bunEnv, + NODE_ENV: undefined, + ...env, + }, + }); + if (!result.success) throw new Error(result.stderr.toString("utf8")); + return { + stdout: result.stdout.toString("utf8"), + stderr: result.stderr.toString("utf8").trim(), + }; +} describe(".env file is loaded", () => { test(".env", () => { @@ -338,3 +355,52 @@ test(".env in a folder doesn't throw an error", () => { const { stdout } = bunRun(`${dir}/index.ts`); expect(stdout).toBe("hey"); }); + +test("#3911", () => { + const dir = tempDirWithFiles("dotenv", { + ".env": 'KEY="a\\nb"', + "index.ts": "console.log(process.env.KEY);", + }); + const { stdout } = bunRun(`${dir}/index.ts`); + expect(stdout).toBe("a\nb"); +}); + +describe("boundary tests", () => { + test("src boundary", () => { + const dir = tempDirWithFiles("dotenv", { + ".env": 'KEY="a\\n"', + "index.ts": "console.log(process.env.KEY);", + }); + const { stdout } = bunRunWithoutTrim(`${dir}/index.ts`); + // should be "a\n" but console.log adds a newline + expect(stdout).toBe("a\n\n"); + + const dir2 = tempDirWithFiles("dotenv", { + ".env": 'KEY="a\\n', + "index.ts": "console.log(process.env.KEY);", + }); + const { stdout: stdout2 } = bunRunWithoutTrim(`${dir2}/index.ts`); + // should be "a\n but console.log adds a newline + expect(stdout2).toBe('"a\n\n'); + }); + + test("buffer boundary", () => { + const expected = "a".repeat(4094); + let content = expected + "a"; + const dir = tempDirWithFiles("dotenv", { + ".env": `KEY="${content}"`, + "index.ts": "console.log(process.env.KEY);", + }); + const { stdout } = bunRun(`${dir}/index.ts`); + + content = expected + "\\n"; + const dir2 = tempDirWithFiles("dotenv", { + ".env": `KEY="${content}"`, + "index.ts": "console.log(process.env.KEY);", + }); + const { stdout: stdout2 } = bunRun(`${dir2}/index.ts`); + // should be truncated + expect(stdout).toBe(expected); + expect(stdout2).toBe(expected); + }); +}); |