Repository: python-visualization/folium
Issue: Unescaped ` in popup

**Describe the bug**
The character ` is not properly escaped in popup texts. This leads to the non-rendering of every map that contains such a popup. Additionally, this is a potential vulnerability, as it might allow HTML code to be injected into the browser (however, I didnt investigate this further).

**To Reproduce**

```
m = folium.Map(location=[48.7758459, 9.1829321], zoom_start=8)
folium.CircleMarker((48.7758459, 9.1829321), radius=1, popup="ayy`lmao").add_to(m)
folium.LayerControl().add_to(m)
m.save("test.html")
```

Viewing the resulting test.html gives me the following error in the Browser (Firefox):

`Uncaught SyntaxError: missing ) after argument list        test.html:74:154`

The corresponding test.html:74:154:

```
var html_6e374539406378d61899620dc56b22de = $(`<div id="html_6e374539406378d61899620dc56b22de" style="width: 100.0%; height: 100.0%;">ayy`lmao</div>`)[0];
```

As you see, the "ayy`lmao" is not properly escaped. I'd imagine that this could lead to all sorts of injection attacks, however I didn't investigate this further.

**Expected behavior**
` is escaped properly.

**Environment (please complete the following information):**
 - Browser firefox
 - HTML
 - python sys.version_info(major=3, minor=10, micro=6, releaselevel='final', serial=0)
 - folium 0.13.0
 - branca 0.5.0

folium is maintained by volunteers. Can you help making a fix for this issue?

Yes, but only in a week or so. If a fix is needed more quickly, I cannot help.

Best regards, Juri


Changed files:
folium/utilities.py, folium/map.py, tests/test_map.py

diff --git a/folium/map.py b/folium/map.py
index 9f040c1..70ebdfc 100644
--- a/folium/map.py
+++ b/folium/map.py
@@ -2,13 +2,12 @@
 Classes for drawing maps.
 
 """
-
 import warnings
 from collections import OrderedDict
 
 from branca.element import Element, Figure, Html, MacroElement
 
-from folium.utilities import camelize, parse_options, validate_location
+from folium.utilities import camelize, parse_options, validate_location, escape_backticks
 
 from jinja2 import Template
 
@@ -362,6 +361,7 @@ class Popup(Element):
         if isinstance(html, Element):
             self.html.add_child(html)
         elif isinstance(html, str):
+            html = escape_backticks(html)
             self.html.add_child(Html(html, script=script))
 
         self.show = show
diff --git a/folium/utilities.py b/folium/utilities.py
index 875ac01..a8de4d7 100644
--- a/folium/utilities.py
+++ b/folium/utilities.py
@@ -5,6 +5,7 @@ import io
 import json
 import math
 import os
+import re
 import struct
 import tempfile
 import uuid
@@ -474,3 +475,8 @@ def parse_options(**kwargs):
     return {camelize(key): value
             for key, value in kwargs.items()
             if value is not None}
+
+
+def escape_backticks(text):
+    """Escape backticks so text can be used in a JS template."""
+    return re.sub(r"(?<!\\\\)`", r'\\`', text)
diff --git a/tests/test_map.py b/tests/test_map.py
index be01a38..7600678 100644
--- a/tests/test_map.py
+++ b/tests/test_map.py
@@ -89,6 +89,36 @@ def test_popup_show():
     assert normalize(rendered) == normalize(expected)
 
 
+def test_popup_backticks():
+    m = Map()
+    popup = Popup('back`tick`tick').add_to(m)
+    rendered = popup._template.render(this=popup, kwargs={})
+    expected = """
+    var {popup_name} = L.popup({{"maxWidth": "100%"}});
+    var {html_name} = $(`<div id="{html_name}" style="width: 100.0%; height: 100.0%;">back\\\\`tick\\\\`tick</div>`)[0];
+    {popup_name}.setContent({html_name});
+    {map_name}.bindPopup({popup_name});
+    """.format(popup_name=popup.get_name(),
+               html_name=list(popup.html._children.keys())[0],
+               map_name=m.get_name())
+    assert normalize(rendered) == normalize(expected)
+
+
+def test_popup_backticks_already_escaped():
+    m = Map()
+    popup = Popup('back\\\\`tick').add_to(m)
+    rendered = popup._template.render(this=popup, kwargs={})
+    expected = """
+    var {popup_name} = L.popup({{"maxWidth": "100%"}});
+    var {html_name} = $(`<div id="{html_name}" style="width: 100.0%; height: 100.0%;">back\\\\`tick</div>`)[0];
+    {popup_name}.setContent({html_name});
+    {map_name}.bindPopup({popup_name});
+    """.format(popup_name=popup.get_name(),
+               html_name=list(popup.html._children.keys())[0],
+               map_name=m.get_name())
+    assert normalize(rendered) == normalize(expected)
+
+
 def test_icon_valid_marker_colors():
     assert len(Icon.color_options) == 19
     with pytest.warns(None) as record: