bottlesdevs / Bottles

Run Windows software and games on Linux
https://usebottles.com
GNU General Public License v3.0
6.2k stars 258 forks source link

[Bug]: Desktop shortcuts for bottles with single quotes cause failures #3203

Open rptb1 opened 8 months ago

rptb1 commented 8 months ago

Describe the bug

"Configure in Bottles" right-click on desktop shortcut fails when game has a single quote (apostrophe) in its title.

EDIT: I originally thought this was caused by the game having a single quote, but found out that it is the bottle name that causes the issue.

To Reproduce

  1. Create a shortcut in a bottle with a quote in its name, such as "Baldur's Gate 3".
  2. "Add a Desktop Entry" for the game.
  3. Attempt to launch using the desktop icon.
  4. Error: Syntax error in command flatpak run --command=bottles-cli com.usebottles.bottles run -p 'Baldur'"'"'s Gate 3' -b 'Baldur's Gate 3' -- %u coming from /home/deck/.local/share/applications/Baldur's Gate 3--Baldur's Gate 3--1703080590.362178.desktop
  5. From the desktop icon (in the menu or widget or wherever) right click and choose "Configure in Bottles".
  6. Error: Syntax error in command flatpak run com.usebottles.bottles -b 'Baldur's Gate 3'

Package

Flatpak from Flathub

Distribution

SteamOS Holo

Debugging Information

Official Package: true
Version: '51.9'
DE/WM: plasma-steamos-oneshot
Display:
    X.org: true
    X.org (port): :0
    Wayland: false
Graphics:
    vendors:
        amd:
            vendor: amd
            envs:
                DRI_PRIME: '1'
            icd: /usr/lib/x86_64-linux-gnu/GL/vulkan/icd.d/radeon_icd.x86_64.json:/usr/lib/i386-linux-gnu/GL/vulkan/icd.d/radeon_icd.i686.json
    prime:
        integrated: null
        discrete: null
Kernel:
    Type: Linux
    Version: 6.1.52-valve9-1-neptune-61
Disk:
    Total: 7767031808
    Free: 7766880256
RAM:
    MemTotal: 14.5GiB
    MemAvailable: 10.7GiB
Bottles_envs: null

Troubleshooting Logs

The error is in the command in the desktop shortcut, not in Bottles itself.

Additional context

I've found some other cases where single quotes cause issues in directory names as well. I noticed for example that https://github.com/bottlesdevs/Bottles/blob/f9c19ae7cd2c830ee085aad30c8076207331a7e0/bottles/backend/managers/installer.py#L251 is not safe against single quotes in values. It's probably worth checking everywhere that the shell is invoked.

Single quotes can be quoted to the shell like this:

flatpak run com.usebottles.bottles -b 'Baldur'"'"'s Gate 3'

I suggest applying shlex.quote in many cases:

>>> print(shlex.quote("Baldur's Gate 3"))
'Baldur'"'"'s Gate 3'
rptb1 commented 8 months ago

Workaround: Edit the .desktop file in .local/share/applications to add the proper quoting, e.g.

Exec=flatpak run --command=bottles-cli com.usebottles.bottles run -p 'Baldur'"'"'s Gate 3' -b 'Baldur'"'"'s Gate 3' -- %u

and further down (to fix the configure action)

Exec=flatpak run --command=bottles-cli com.usebottles.bottles run -p 'Baldur'"'"'s Gate 3' -b 'Baldur'"'"'s Gate 3' -- %u
rptb1 commented 8 months ago

It looks like this line https://github.com/bottlesdevs/Bottles/blob/f9c19ae7cd2c830ee085aad30c8076207331a7e0/bottles/backend/utils/manager.py#L245 is missing a shlex.quote on the bottle name. It should probably read

 f.write(f"Exec={cmd_cli} run -p {shlex.quote(program.get('name'))} -b '{shlex.quote(config.get('Name'))}' -- %u\n") 
rptb1 commented 8 months ago

It seems likely that many of these need quoting calls too:

grep -e "'{.*}'" -R .
./bottles/backend/wine/winepath.py:40:        args = f"--unix '{path}'"
./bottles/backend/wine/winepath.py:63:        args = f"--windows '{path}'"
./bottles/backend/wine/winepath.py:69:        args = f"--long '{path}'"
./bottles/backend/wine/winepath.py:75:        args = f"--short '{path}'"
./bottles/backend/wine/winecommand.py:489:                logging.info(f"Running Gamescope command: '{command}'")
./bottles/backend/wine/winecommand.py:545:            command = f"{command} ; sh '{post_script}'"
./bottles/backend/wine/net.py:17:            args = f"start '{name}'"
./bottles/backend/wine/net.py:25:            args = f"stop '{name}'"
./bottles/backend/wine/net.py:34:            args = f"use '{name}'"
./bottles/backend/wine/uninstaller.py:17:            args = f"--list | grep -i '{name}' | cut -f1 -d\\|"
./bottles/backend/wine/reg.py:126:        logging.info(f"Import bundle result: '{res.data}'")
./bottles/backend/managers/steam.py:495:        args = "bottles:run/'{0}'/'{1}'"
./bottles/backend/managers/installer.py:251:            f"bash -c '{script}'",
./bottles/backend/utils/terminal.py:98:            command = ' '.join(self.terminal) % "'sh -c %s'" % f'{command}'
./bottles/backend/utils/terminal.py:100:            command = ' '.join(self.terminal) % "sh -c %s" % f'{command}'
./bottles/backend/utils/terminal.py:102:            command = ' '.join(self.terminal) % "bash -c %s" % f'{command}'
./bottles/backend/utils/gpu.py:50:                f"lspci | grep '{self.__vendors[_vendor]}'",
./bottles/backend/utils/gpu.py:140:                f"lspci | grep -iP '{_query}'",
./bottles/backend/utils/gpu.py:164:            f"lspci | grep -iP '{vendor.value}'",
./bottles/backend/utils/manager.py:245:                f.write(f"Exec={cmd_cli} run -p {shlex.quote(program.get('name'))} -b '{config.get('Name')}' -- %u\n")
./bottles/backend/utils/manager.py:256:                f.write(f"Exec={cmd_legacy} -b '{config.get('Name')}'\n")
./bottles/backend/utils/manager.py:287:                f"{cmd_cli} run -p {shlex.quote(program.get('name'))} -b '{config.get('Path')}'",
./bottles/backend/utils/manager.py:289:                f"{cmd_legacy} -b '{config.get('Name')}'"
./bottles/backend/utils/imagemagick.py:35:        cmd = f"identify '{self.path}'"
./bottles/backend/utils/imagemagick.py:59:        cmd = f"convert '{self.path}'"
./bottles/backend/utils/imagemagick.py:79:        cmd += f" '{dest}'"
./bottles/frontend/cli/cli.py:382:        sys.stdout.write(f"'{_name}' added to '{bottle.Name}'!")
./bottles/frontend/cli/cli.py:643:                f.write(f"export {k}='{v}'\n")
./utils/flatpak-pip-generator.py:376:    with tempfile.TemporaryDirectory(prefix='{}-{}'.format(tempdir_prefix, packag
JakobDev commented 8 months ago

You can just use subprocess.list2cmdline instead of quoting each single argument.