Skip to content

Commit

Permalink
Keep newline/tab escapes in quoted strings (fix certificate serializi…
Browse files Browse the repository at this point in the history
…ng) (#296)

Limit backslash escape removal for quoted newlines

Keep escape backslash for newline/tab characters. Parsing still
differs from shell syntax but keeps string structure intact.

Example certificate data
    VAR="---BEGIN---\r\n---END---"
now becomes
    "---BEGIN---\r\n---END---"
instead of
    "---BEGIN---rn---END---"

Co-authored-by: Serghei Iakovlev <egrep@protonmail.ch>
  • Loading branch information
johnbergvall and sergeyklay committed Sep 14, 2021
1 parent 3bb8e6e commit 78b8a03
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 7 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0

`v0.8.0`_ - 00-Unreleased-2021
------------------------------
Fixed
+++++
- Keep newline/tb escaped in quoted strings


`v0.7.0`_ - 11-September-2021
Expand Down
49 changes: 44 additions & 5 deletions docs/tips.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,54 @@ You can use something like this to handle similar cases.
Multiline value
===============

You can set a multiline variable value:
To get multiline value pass ``multiline=True`` to ```str()```.

.. note::

You shouldn't escape newline/tab characters yourself if you want to preserve
the formatting.

The following example demonstrates the above:

**.env file**:

.. code-block:: shell
# .env file contents
UNQUOTED_CERT=---BEGIN---\r\n---END---
QUOTED_CERT="---BEGIN---\r\n---END---"
ESCAPED_CERT=---BEGIN---\\n---END---
**settings.py file**:

.. code-block:: python
# MULTILINE_TEXT=Hello\\nWorld
>>> print env.str('MULTILINE_TEXT', multiline=True)
Hello
World
# settings.py file contents
import environ
env = environ.Env()
print(env.str('UNQUOTED_CERT', multiline=True))
# ---BEGIN---
# ---END---
print(env.str('UNQUOTED_CERT', multiline=False))
# ---BEGIN---\r\n---END---
print(env.str('QUOTED_CERT', multiline=True))
# ---BEGIN---
# ---END---
print(env.str('QUOTED_CERT', multiline=False))
# ---BEGIN---\r\n---END---
print(env.str('ESCAPED_CERT', multiline=True))
# ---BEGIN---\
# ---END---
print(env.str('ESCAPED_CERT', multiline=False))
# ---BEGIN---\\n---END---
Proxy value
===========
Expand Down
12 changes: 10 additions & 2 deletions environ/environ.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def str(self, var, default=NOTSET, multiline=False):
"""
value = self.get_value(var, cast=str, default=default)
if multiline:
return value.replace('\\n', '\n')
return re.sub(r'(\\r)?\\n', r'\n', value)
return value

def unicode(self, var, default=NOTSET):
Expand Down Expand Up @@ -769,6 +769,13 @@ def read_env(cls, env_file=None, **overrides):

logger.debug('Read environment variables from: {}'.format(env_file))

def _keep_escaped_format_characters(match):
"""Keep escaped newline/tabs in quoted strings"""
escaped_char = match.group(1)
if escaped_char in 'rnt':
return '\\' + escaped_char
return escaped_char

for line in content.splitlines():
m1 = re.match(r'\A(?:export )?([A-Za-z_0-9]+)=(.*)\Z', line)
if m1:
Expand All @@ -778,7 +785,8 @@ def read_env(cls, env_file=None, **overrides):
val = m2.group(1)
m3 = re.match(r'\A"(.*)"\Z', val)
if m3:
val = re.sub(r'\\(.)', r'\1', m3.group(1))
val = re.sub(r'\\(.)', _keep_escaped_format_characters,
m3.group(1))
cls.ENVIRON.setdefault(key, str(val))

# set defaults
Expand Down
2 changes: 2 additions & 0 deletions tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class FakeEnv:
def generate_data(cls):
return dict(STR_VAR='bar',
MULTILINE_STR_VAR='foo\\nbar',
MULTILINE_QUOTED_STR_VAR='---BEGIN---\\r\\n---END---',
MULTILINE_ESCAPED_STR_VAR='---BEGIN---\\\\n---END---',
INT_VAR='42',
FLOAT_VAR='33.3',
FLOAT_COMMA_VAR='33,3',
Expand Down
4 changes: 4 additions & 0 deletions tests/test_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ def test_contains(self):
('STR_VAR', 'bar', False),
('MULTILINE_STR_VAR', 'foo\\nbar', False),
('MULTILINE_STR_VAR', 'foo\nbar', True),
('MULTILINE_QUOTED_STR_VAR', '---BEGIN---\\r\\n---END---', False),
('MULTILINE_QUOTED_STR_VAR', '---BEGIN---\n---END---', True),
('MULTILINE_ESCAPED_STR_VAR', '---BEGIN---\\\\n---END---', False),
('MULTILINE_ESCAPED_STR_VAR', '---BEGIN---\\\n---END---', True),
],
)
def test_str(self, var, val, multiline):
Expand Down
2 changes: 2 additions & 0 deletions tests/test_env.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ INT_VAR=42
STR_LIST_WITH_SPACES= foo, bar
STR_VAR=bar
MULTILINE_STR_VAR=foo\nbar
MULTILINE_QUOTED_STR_VAR="---BEGIN---\r\n---END---"
MULTILINE_ESCAPED_STR_VAR=---BEGIN---\\n---END---
INT_LIST=42,33
CYRILLIC_VAR=фуубар
INT_TUPLE=(42,33)
Expand Down

0 comments on commit 78b8a03

Please sign in to comment.