Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 35.2k
gh-137586: Replace 'osascript' with 'open' on macOS in webbrowser#146439
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
00fa6c7f697cd577810336066221d54293f080197efdd66498e1eef4e19362698dd1d8bdfc2e6614078fc77f4b8c7259271316dbf612d276386af23e4e89f1b513903aacbceba6d76d65a4fdadde9ec776ddf67ff874d673b390bb21b5499File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,8 @@ | ||
| """Interfaces for launching and remotely controlling web browsers.""" | ||
| # Maintained by Georg Brandl. | ||
| import builtins # because we override open | ||
| import os | ||
| lazy import plistlib | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. | ||
| import shlex | ||
| import shutil | ||
| import sys | ||
| @@ -492,10 +493,15 @@ def register_standard_browsers(): | ||
| _tryorder = [] | ||
| if sys.platform == 'darwin': | ||
| register("MacOSX", None, MacOSXOSAScript('default')) | ||
gpshead marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| register("chrome", None, MacOSXOSAScript('google chrome')) | ||
| register("firefox", None, MacOSXOSAScript('firefox')) | ||
| register("safari", None, MacOSXOSAScript('safari')) | ||
| register("MacOS", None, MacOS('default')) | ||
| register("MacOSX", None, MacOS('default')) # backward compat alias | ||
| register("chrome", None, MacOS('google chrome')) | ||
| register("chromium", None, MacOS('chromium')) | ||
| register("firefox", None, MacOS('firefox')) | ||
| register("safari", None, MacOS('safari')) | ||
| register("opera", None, MacOS('opera')) | ||
| register("microsoft-edge", None, MacOS('microsoft edge')) | ||
| register("brave", None, MacOS('brave browser')) | ||
| # macOS can use below Unix support (but we prefer using the macOS | ||
| # specific stuff) | ||
| @@ -614,8 +620,80 @@ def open(self, url, new=0, autoraise=True): | ||
| # | ||
| if sys.platform == 'darwin': | ||
| def _macos_default_browser_bundle_id(): | ||
gpshead marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| """Return the bundle ID of the default web browser. | ||
| Reads the LaunchServices preferences file that macOS maintains | ||
| when the user sets a default browser. Returns 'com.apple.Safari' | ||
| if the file is absent or no https handler is recorded, because on | ||
| a fresh macOS installation Safari is the default browser and the | ||
| LaunchServices plist is not written until the user explicitly | ||
| changes their default browser. | ||
| """ | ||
| plist = os.path.expanduser( | ||
| '~/Library/Preferences/com.apple.LaunchServices/' | ||
| 'com.apple.launchservices.secure.plist' | ||
| ) | ||
| try: | ||
| with builtins.open(plist, 'rb') as f: | ||
| data = plistlib.load(f) | ||
gpshead marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| for handler in data.get('LSHandlers', []): | ||
| if handler.get('LSHandlerURLScheme') == 'https': | ||
| return (handler.get('LSHandlerRoleAll') | ||
| or handler.get('LSHandlerRoleViewer')) | ||
| except (OSError, KeyError, ValueError): | ||
| pass | ||
| return 'com.apple.Safari' | ||
| class MacOS(BaseBrowser): | ||
| """Launcher class for macOS browsers, using /usr/bin/open. | ||
| For http/https URLs with the default browser, /usr/bin/open is called | ||
| directly; macOS routes these to the registered browser. | ||
| For all other URL schemes (e.g. file://) and for named browsers, | ||
| /usr/bin/open -b <bundle-id> is used so that the URL is always passed | ||
| to a browser application rather than dispatched by the OS file handler. | ||
| This prevents file injection attacks where a file:// URL pointing to an | ||
| executable bundle could otherwise be launched by the OS. | ||
| Named browsers with known bundle IDs use -b; unknown names fall back | ||
| to -a. | ||
| """ | ||
| _BUNDLE_IDS = { | ||
| 'google chrome': 'com.google.Chrome', | ||
| 'firefox': 'org.mozilla.firefox', | ||
| 'safari': 'com.apple.Safari', | ||
| 'chromium': 'org.chromium.Chromium', | ||
| 'opera': 'com.operasoftware.Opera', | ||
hugovk marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| 'microsoft edge': 'com.microsoft.edgemac', | ||
| 'brave browser': 'com.brave.Browser', | ||
| } | ||
| def open(self, url, new=0, autoraise=True): | ||
| sys.audit("webbrowser.open", url) | ||
| self._check_url(url) | ||
| if self.name == 'default': | ||
| proto, sep, _ = url.partition(':') | ||
| if sep and proto.lower() in {'http', 'https'}: | ||
| cmd = ['/usr/bin/open', url] | ||
| else: | ||
| bundle_id = _macos_default_browser_bundle_id() | ||
| cmd = ['/usr/bin/open', '-b', bundle_id, url] | ||
| else: | ||
| bundle_id = self._BUNDLE_IDS.get(self.name.lower()) | ||
| if bundle_id: | ||
| cmd = ['/usr/bin/open', '-b', bundle_id, url] | ||
| else: | ||
| cmd = ['/usr/bin/open', '-a', self.name, url] | ||
| proc = subprocess.run(cmd, stderr=subprocess.DEVNULL) | ||
| return proc.returncode == 0 | ||
| class MacOSXOSAScript(BaseBrowser): | ||
| def __init__(self, name='default'): | ||
| import warnings | ||
| warnings._deprecated("webbrowser.MacOSXOSAScript", remove=(3, 17)) | ||
| super().__init__(name) | ||
| def open(self, url, new=0, autoraise=True): | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| Add :class:`!MacOS` to :mod:`webbrowser` for macOS, which opens URLs via | ||
| ``/usr/bin/open`` instead of piping AppleScript to ``osascript``. | ||
| Deprecate :class:`!MacOSXOSAScript` in favour of :class:`!MacOS`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Fix a PATH-injection vulnerability in :mod:`webbrowser` on macOS where | ||
| ``osascript`` was invoked without an absolute path. The new :class:`!MacOS` | ||
| class uses ``/usr/bin/open`` directly, eliminating the dependency on | ||
| ``osascript`` entirely. |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please list alphabetically, after
typing