aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/env_loader.zig13
-rw-r--r--test/cli/run/env.test.ts68
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);
+ });
+});