Skip to content

Commit b3c3195

Browse files
committed
Improve quoting of values in set_key
The value of `quote_mode` must now be one of `auto`, `never` or `always`, to ensure that users aren't accidentally relying on any other value for their scripts to work. Surrounding quotes are no longer stripped. This makes it possible for the user to control exactly what goes in the .env file. Note that when doing `dotenv set foo 'bar'` in Bash, the shell will have already removed the quotes. Single quotes are used instead of double quotes. This avoids accidentally having values interpreted by the parser or Bash (e.g. if you set a password with `dotenv set password 'af$rb0'`. Previously, the `auto` mode of quoting had the same effect as `always`. This commit restores the functionality of `auto` by not quoting alphanumeric values (which don't need quotes). Plenty of other kinds of values also don't need quotes but it's hard to know which ones without actually parsing them, so we just omit quotes for alphanumeric values, at least for now.
1 parent 3034238 commit b3c3195

File tree

4 files changed

+40
-23
lines changed

4 files changed

+40
-23
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this
66
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## Unreleased
9+
10+
### Changed
11+
12+
- Raise `ValueError` if `quote_mode` isn't one of `always`, `auto` or `never` in
13+
`set_key` (#330 by [@bbc2]).
14+
- When writing a value to a .env file with `set_key` or `dotenv set <key> <value>` (#330
15+
by [@bbc2]):
16+
- Use single quotes instead of double quotes.
17+
- Don't strip surrounding quotes.
18+
- In `auto` mode, don't add quotes if the value is only made of alphanumeric characters
19+
(as determined by `string.isalnum`).
20+
821
## [0.17.1] - 2021-04-29
922

1023
### Fixed

src/dotenv/main.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,16 @@ def set_key(dotenv_path, key_to_set, value_to_set, quote_mode="always", export=F
151151
If the .env path given doesn't exist, fails instead of risking creating
152152
an orphan .env somewhere in the filesystem
153153
"""
154-
value_to_set = value_to_set.strip("'").strip('"')
154+
if quote_mode not in ("always", "auto", "never"):
155+
raise ValueError("Unknown quote_mode: {}".format(quote_mode))
155156

156-
if " " in value_to_set:
157-
quote_mode = "always"
157+
quote = (
158+
quote_mode == "always"
159+
or (quote_mode == "auto" and not value_to_set.isalnum())
160+
)
158161

159-
if quote_mode == "always":
160-
value_out = '"{}"'.format(value_to_set.replace('"', '\\"'))
162+
if quote:
163+
value_out = "'{}'".format(value_to_set.replace("'", "\\'"))
161164
else:
162165
value_out = value_to_set
163166
if export:

tests/test_cli.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,11 @@ def test_unset_non_existent_value(cli, dotenv_file):
7373
@pytest.mark.parametrize(
7474
"quote_mode,variable,value,expected",
7575
(
76-
("always", "HELLO", "WORLD", 'HELLO="WORLD"\n'),
77-
("never", "HELLO", "WORLD", 'HELLO=WORLD\n'),
78-
("auto", "HELLO", "WORLD", 'HELLO=WORLD\n'),
79-
("auto", "HELLO", "HELLO WORLD", 'HELLO="HELLO WORLD"\n'),
76+
("always", "a", "x", "a='x'\n"),
77+
("never", "a", "x", 'a=x\n'),
78+
("auto", "a", "x", "a=x\n"),
79+
("auto", "a", "x y", "a='x y'\n"),
80+
("auto", "a", "$", "a='$'\n"),
8081
)
8182
)
8283
def test_set_quote_options(cli, dotenv_file, quote_mode, variable, value, expected):
@@ -92,8 +93,8 @@ def test_set_quote_options(cli, dotenv_file, quote_mode, variable, value, expect
9293
@pytest.mark.parametrize(
9394
"dotenv_file,export_mode,variable,value,expected",
9495
(
95-
(".nx_file", "true", "HELLO", "WORLD", "export HELLO=\"WORLD\"\n"),
96-
(".nx_file", "false", "HELLO", "WORLD", "HELLO=\"WORLD\"\n"),
96+
(".nx_file", "true", "a", "x", "export a='x'\n"),
97+
(".nx_file", "false", "a", "x", "a='x'\n"),
9798
)
9899
)
99100
def test_set_export(cli, dotenv_file, export_mode, variable, value, expected):

tests/test_main.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,18 @@ def test_set_key_no_file(tmp_path):
2828
@pytest.mark.parametrize(
2929
"before,key,value,expected,after",
3030
[
31-
("", "a", "", (True, "a", ""), 'a=""\n'),
32-
("", "a", "b", (True, "a", "b"), 'a="b"\n'),
33-
("", "a", "'b'", (True, "a", "b"), 'a="b"\n'),
34-
("", "a", "\"b\"", (True, "a", "b"), 'a="b"\n'),
35-
("", "a", "b'c", (True, "a", "b'c"), 'a="b\'c"\n'),
36-
("", "a", "b\"c", (True, "a", "b\"c"), 'a="b\\\"c"\n'),
37-
("a=b", "a", "c", (True, "a", "c"), 'a="c"\n'),
38-
("a=b\n", "a", "c", (True, "a", "c"), 'a="c"\n'),
39-
("a=b\n\n", "a", "c", (True, "a", "c"), 'a="c"\n\n'),
40-
("a=b\nc=d", "a", "e", (True, "a", "e"), 'a="e"\nc=d'),
41-
("a=b\nc=d\ne=f", "c", "g", (True, "c", "g"), 'a=b\nc="g"\ne=f'),
42-
("a=b\n", "c", "d", (True, "c", "d"), 'a=b\nc="d"\n'),
31+
("", "a", "", (True, "a", ""), "a=''\n"),
32+
("", "a", "b", (True, "a", "b"), "a='b'\n"),
33+
("", "a", "'b'", (True, "a", "'b'"), "a='\\'b\\''\n"),
34+
("", "a", "\"b\"", (True, "a", '"b"'), "a='\"b\"'\n"),
35+
("", "a", "b'c", (True, "a", "b'c"), "a='b\\'c'\n"),
36+
("", "a", "b\"c", (True, "a", "b\"c"), "a='b\"c'\n"),
37+
("a=b", "a", "c", (True, "a", "c"), "a='c'\n"),
38+
("a=b\n", "a", "c", (True, "a", "c"), "a='c'\n"),
39+
("a=b\n\n", "a", "c", (True, "a", "c"), "a='c'\n\n"),
40+
("a=b\nc=d", "a", "e", (True, "a", "e"), "a='e'\nc=d"),
41+
("a=b\nc=d\ne=f", "c", "g", (True, "c", "g"), "a=b\nc='g'\ne=f"),
42+
("a=b\n", "c", "d", (True, "c", "d"), "a=b\nc='d'\n"),
4343
],
4444
)
4545
def test_set_key(dotenv_file, before, key, value, expected, after):

0 commit comments

Comments
 (0)