Repository: facelessuser/pymdown-extensions

Issue:
pymdownx.keys may break base64 encoded images
### Description

With a base64 encoded image, given the fact that a base64 encoded string can contain something like `++a++`, the pymdownx.keys would convert it to `A` which will corrupt the content.

pymdownx.keys should perhaps not process strings within a link since the link URL should always be left as-is.

### Minimal Reproduction

1. Enable pymdownx.keys in mkdocs.yml.
2. Create a markdown file with content like this: `![](data:image/png;base64,aaaaa++a++aaaa)`
3. Build and browse to the site, and the resulting image is `<img alt="" src="data:image/png;base64,aaaaaAaaaa">`

### Version(s) & System Info

- Operating System: Windows 10
- Python Version: 3.12
- Package Version: latest

Discussion:
facelessuser: @gir-bot lgtm

Patch:
diff --git a/docs/src/markdown/about/changelog.md b/docs/src/markdown/about/changelog.md
index 54636015..fe8ab95c 100644
--- a/docs/src/markdown/about/changelog.md
+++ b/docs/src/markdown/about/changelog.md
@@ -1,5 +1,9 @@
 # Changelog
 
+## 10.5
+
+- **FIX**: Keys: Ensure that Keys does not parse base64 encoded URLs.
+
 ## 10.4
 
 -   **NEW**: Snippets: Allow PathLike objects for `base_path` to better support interactions with MkDocs.
diff --git a/pymdownx/keys.py b/pymdownx/keys.py
index 9377fcce..219819f1 100644
--- a/pymdownx/keys.py
+++ b/pymdownx/keys.py
@@ -91,7 +91,7 @@ from . import util
 from . import keymap_db as keymap
 import re
 
-RE_KBD = r'''(?x)
+RE_EARLY_KBD = r'''(?x)
 (?:
     # Escape
     (?<!\\)(?P<escapes>(?:\\{2})+)(?=\+)|
@@ -105,6 +105,8 @@ RE_KBD = r'''(?x)
 )
 '''
 
+RE_KBD = r'\+{2}([\w\-]+(?:\+[\w\-]+)*?)\+{2}'
+
 ESCAPE_RE = re.compile(r'''(?<!\\)(?:\\\\)*\\(.)''')
 UNESCAPED_PLUS = re.compile(r'''(?<!\\)(?:\\\\)*(\+)''')
 ESCAPED_BSLASH = '%s%s%s' % (md_util.STX, ord('\\'), md_util.ETX)
@@ -114,7 +116,7 @@ DOUBLE_BSLASH = '\\\\'
 class KeysPattern(InlineProcessor):
     """Return kbd tag."""
 
-    def __init__(self, pattern, config, md):
+    def __init__(self, pattern, config, md, early=False):
         """Initialize."""
 
         self.ksep = config['separator']
@@ -123,6 +125,7 @@ class KeysPattern(InlineProcessor):
         self.map = self.merge(keymap.keymap, config['key_map'])
         self.aliases = keymap.aliases
         self.camel = config['camel_case']
+        self.early = early
         super().__init__(pattern, md)
 
     def merge(self, x, y):
@@ -166,9 +169,21 @@ class KeysPattern(InlineProcessor):
     def handleMatch(self, m, data):
         """Handle kbd pattern matches."""
 
-        if m.group(1):
-            return m.group('escapes').replace(DOUBLE_BSLASH, ESCAPED_BSLASH), m.start(0), m.end(0)
-        content = [self.process_key(key) for key in UNESCAPED_PLUS.split(m.group(2)) if key != '+']
+        if self.early:
+            if m.group(1):
+                return m.group('escapes').replace(DOUBLE_BSLASH, ESCAPED_BSLASH), m.start(0), m.end(0)
+            quoted = 0
+            content = []
+            for key in UNESCAPED_PLUS.split(m.group(2)):
+                if key != '+':
+                    if key.startswith(('"', "'")):
+                        quoted += 1
+                    content.append(self.process_key(key))
+            # Defer unquoted cases until later to avoid parsing URLs
+            if not quoted:
+                return None, None, None
+        else:
+            content = [self.process_key(key) for key in m.group(1).split('+')]
 
         if None in content:
             return None, None, None
@@ -215,7 +230,8 @@ class KeysExtension(Extension):
         """Add support for keys."""
 
         util.escape_chars(md, ['+'])
-        md.inlinePatterns.register(KeysPattern(RE_KBD, self.getConfigs(), md), "keys", 185)
+        md.inlinePatterns.register(KeysPattern(RE_EARLY_KBD, self.getConfigs(), md, early=True), "keys-custom", 185)
+        md.inlinePatterns.register(KeysPattern(RE_KBD, self.getConfigs(), md), "keys", 70)
 
 
 def makeExtension(*args, **kwargs):


Test patch:
diff --git a/tests/test_extensions/test_keys.py b/tests/test_extensions/test_keys.py
new file mode 100644
index 00000000..3ee700d7
--- /dev/null
+++ b/tests/test_extensions/test_keys.py
@@ -0,0 +1,18 @@
+"""Test cases for Keys."""
+from .. import util
+
+
+class TestKeys(util.MdCase):
+    """Tests for Keys."""
+
+    extension = [
+        'pymdownx.keys'
+    ]
+
+    def test_avoid_base64(self):
+        """Test complex case where `**text*text***` may be detected on accident."""
+
+        self.check_markdown(
+            "![](data:image/png;base64,aaaaa++a++aaaa) ++ctrl+a++ ++ctrl+'custom'++",
+            '<p><img alt="" src="data:image/png;base64,aaaaa++a++aaaa" /> <span class="keys"><kbd class="key-control">Ctrl</kbd><span>+</span><kbd class="key-a">A</kbd></span> <span class="keys"><kbd class="key-control">Ctrl</kbd><span>+</span><kbd>custom</kbd></span></p>'  # noqa: E501
+        )