Repository: xonsh/xonsh

Issue:
"Little Bobby Colors": $PROMPT evaluates {colors} *after* substitution from external input
## xonfig

<details>

```
+------------------+----------------------+
| xonsh            | 0.9.27               |
| Git SHA          | 71fe9014             |
| Commit Date      | Jan 29 08:58:58 2021 |
| Python           | 3.9.5                |
| PLY              | 3.11                 |
| have readline    | True                 |
| prompt toolkit   | 3.0.19               |
| shell type       | prompt_toolkit       |
| pygments         | 2.9.0                |
| on posix         | True                 |
| on linux         | True                 |
| distro           | ubuntu               |
| on darwin        | False                |
| on windows       | False                |
| on cygwin        | False                |
| on msys2         | False                |
| is superuser     | False                |
| default encoding | utf-8                |
| xonsh encoding   | utf-8                |
| encoding errors  | surrogateescape      |
| on jupyter       | False                |
| jupyter kernel   | None                 |
| xontrib 1        | apt_tabcomplete      |
| xontrib 2        | direnv               |
| xontrib 3        | kitty                |
| xontrib 4        | prompt_ret_code      |
+------------------+----------------------+
```

</details>

## Expected Behavior
When a prompt includes shell-external names (like working directory, running job name, etc), any "meta" command directives should be escaped, lest unusual names trigger surprising (and perhaps even harmful) behavior.

## Current Behavior
```
$ xonsh --no-rc  # just to demo with the default prompt

... default xonsh message ...

egnor@ostrich ~ $ mkdir '{'  # slightly odd directory name
egnor@ostrich ~ $ cd '{'  # here we go!
{BOLD_GREEN}egnor@ostrich{BOLD_BLUE} ~/{{BOLD_INTENSE_YELLOW}{RESET} {BOLD_BLUE}
${RESET}
```

Suddenly the prompt is barfing, because the curly braces are no longer balanced, because the `{` directory name was substituted into the prompt. This is also fun:

```
egnor@ostrich ~ $ mkdir '{BACKGROUND_RED} ALERT'
egnor@ostrich ~ $ cd '{BACKGROUND_RED} ALERT'
egnor@ostrich ~/ ALERT $
```

...and "ALERT" gets a bright red background color in the prompt. As far as I know, nothing in curly braces will do anything particularly terrible (nothing takes any arguments) so I don't _think_ this is a security issue but it sure doesn't feel right.

## Steps to Reproduce
1. Have a prompt that shows the current directory (e.g. the default prompt)
2. Create a directory with `{` / `}` characters in it, perhaps even color tags like `{RED}`
3. Enter that directory

## VERY VERY HUMBLE editorializing 🙇
<sup>(Please take this commentary with a HUGE grain of salt :salt: because I am a super newcomer to xonsh and not a contributor (yet?), though I'd be happy to help improve this... I _love_ xonsh's overall structure and direction!)</sup>

<sup>This problem could be fixed by somehow "escaping" the output from `{cwd}` and the like, OR by doing color substitution before/while doing other expansions (rather than expanding `{cwd}` in one pass, then expanding colors like `{RED}` in a separate pass)...</sup>

<sup>BUT I don't love the little mini language used in `$PROMPT` and friends (`$RIGHT_PROMPT`, `$TITLE`, `$BOTTOM_TOOLBAR`, etc). It's conventional to have such a little mini language in shells but I think xonsh can do better _and_ be simpler. Fundamentally this is yet another little string interpolation mini template language, with the usual problems of escaping "text" vs "markup".</sup>

<sup>But since this is all Python we don't really need "markup" and its attendant escaping problems. $PROMPT could just be a function that returns the prompt to show. That function can then call whatever handy dandy utility functions it wants to (to get the cwd formatted various ways, hostname, git status, etc) and assemble it using ordinary Python string manipulation (f-strings or `string.Template` or just the `+` operator), no fuss, no muss, no weird little special curly-brackets-with-colons things to learn. Colors and similar text formatting could be supported with a `ColoredStr` class which could be constructed and concatenated (with other `ColoredStr` and/or regular `str`) and sliced much like `str`. Then everything would be clean and easy and Pythonic without curly braces flopping about.</sup>

<sup>(End humble editorializing!)</sup>

## For community
⬇️  **Please click the 👍 reaction instead of leaving a `+1` or 👍  comment**


Discussion:

Resolving patch:
diff --git a/xonsh/prompt/cwd.py b/xonsh/prompt/cwd.py
--- a/xonsh/prompt/cwd.py
+++ b/xonsh/prompt/cwd.py
@@ -27,7 +27,8 @@ def _replace_home(x):
 
 
 def _replace_home_cwd():
-    return _replace_home(XSH.env["PWD"])
+    pwd = XSH.env["PWD"].replace("{", "{{").replace("}", "}}")
+    return _replace_home(pwd)
 
 
 def _collapsed_pwd():


Test patch:
diff --git a/tests/prompt/test_cwd.py b/tests/prompt/test_cwd.py
new file mode 100644
--- /dev/null
+++ b/tests/prompt/test_cwd.py
@@ -0,0 +1,16 @@
+from xonsh.prompt.cwd import _replace_home_cwd
+from xonsh.built_ins import XSH
+
+
+def test_cwd_escapes_curly_brackets_with_more_curly_brackets():
+    XSH.env["PWD"] = "{foo}"
+    assert _replace_home_cwd() == "{{foo}}"
+
+    XSH.env["PWD"] = "{{foo}}"
+    assert _replace_home_cwd() == "{{{{foo}}}}"
+
+    XSH.env["PWD"] = "{"
+    assert _replace_home_cwd() == "{{"
+
+    XSH.env["PWD"] = "}}"
+    assert _replace_home_cwd() == "}}}}"