Update webbrowser.py to 3.14.5 (#7868)

This commit is contained in:
Shahar Naveh
2026-05-13 13:53:31 +03:00
committed by GitHub
parent 32e6f8dd81
commit f8e0eeb579
2 changed files with 154 additions and 7 deletions

View File

@@ -1,3 +1,4 @@
import io
import os
import re
import shlex
@@ -5,6 +6,7 @@ import subprocess
import sys
import unittest
import webbrowser
from functools import partial
from test import support
from test.support import import_helper
from test.support import is_apple_mobile
@@ -55,6 +57,14 @@ class CommandTestMixin:
popen_args.pop(popen_args.index(option))
self.assertEqual(popen_args, arguments)
def test_reject_dash_prefixes(self):
browser = self.browser_class(name=CMD_NAME)
with self.assertRaisesRegex(
ValueError,
r"^Invalid URL \(leading dash disallowed\): '--key=val http.*'$"
):
browser.open(f"--key=val {URL}")
class GenericBrowserCommandTest(CommandTestMixin, unittest.TestCase):
@@ -109,6 +119,15 @@ class ChromeCommandTest(CommandTestMixin, unittest.TestCase):
arguments=[URL],
kw=dict(new=999))
def test_reject_action_dash_prefixes(self):
browser = self.browser_class(name=CMD_NAME)
with self.assertRaises(ValueError):
browser.open('%action--incognito')
# new=1: action is "--new-window", so "%action" itself expands to
# a dash-prefixed flag even with no dash in the original URL.
with self.assertRaises(ValueError):
browser.open('%action', new=1)
class EdgeCommandTest(CommandTestMixin, unittest.TestCase):
@@ -301,6 +320,81 @@ class IOSBrowserTest(unittest.TestCase):
self._test('open_new_tab')
class MockPopenPipe:
def __init__(self, cmd, mode):
self.cmd = cmd
self.mode = mode
self.pipe = io.StringIO()
self._closed = False
def write(self, buf):
self.pipe.write(buf)
def close(self):
self._closed = True
return None
@unittest.skipUnless(sys.platform == "darwin", "macOS specific test")
@requires_subprocess()
class MacOSXOSAScriptTest(unittest.TestCase):
def setUp(self):
# Ensure that 'BROWSER' is not set to 'open' or something else.
# See: https://github.com/python/cpython/issues/131254.
env = self.enterContext(os_helper.EnvironmentVarGuard())
env.unset("BROWSER")
support.patch(self, os, "popen", self.mock_popen)
self.browser = webbrowser.MacOSXOSAScript("default")
def mock_popen(self, cmd, mode):
self.popen_pipe = MockPopenPipe(cmd, mode)
return self.popen_pipe
def test_default(self):
browser = webbrowser.get()
assert isinstance(browser, webbrowser.MacOSXOSAScript)
self.assertEqual(browser.name, "default")
def test_default_open(self):
url = "https://python.org"
self.browser.open(url)
self.assertTrue(self.popen_pipe._closed)
self.assertEqual(self.popen_pipe.cmd, "/usr/bin/osascript")
script = self.popen_pipe.pipe.getvalue()
self.assertEqual(script.strip(), f'open location "{url}"')
def test_url_quote(self):
self.browser.open('https://python.org/"quote"')
script = self.popen_pipe.pipe.getvalue()
self.assertEqual(
script.strip(), 'open location "https://python.org/%22quote%22"'
)
def test_default_browser_lookup(self):
url = "file:///tmp/some-file.html"
self.browser.open(url)
script = self.popen_pipe.pipe.getvalue()
# doesn't actually test the browser lookup works,
# just that the branch is taken
self.assertIn("URLForApplicationToOpenURL", script)
self.assertIn(f'open location "{url}"', script)
def test_explicit_browser(self):
browser = webbrowser.MacOSXOSAScript("safari")
browser.open("https://python.org")
script = self.popen_pipe.pipe.getvalue()
self.assertIn('tell application "safari"', script)
self.assertIn('open location "https://python.org"', script)
def test_reject_dash_prefixes(self):
with self.assertRaisesRegex(
ValueError,
r"^Invalid URL \(leading dash disallowed\): '--key=val http.*'$"
):
self.browser.open(f"--key=val {URL}")
class BrowserRegistrationTest(unittest.TestCase):
def setUp(self):

67
Lib/webbrowser.py vendored Executable file → Normal file
View File

@@ -1,4 +1,3 @@
#! /usr/bin/env python3
"""Interfaces for launching and remotely controlling web browsers."""
# Maintained by Georg Brandl.
@@ -164,6 +163,12 @@ class BaseBrowser:
def open_new_tab(self, url):
return self.open(url, 2)
@staticmethod
def _check_url(url):
"""Ensures that the URL is safe to pass to subprocesses as a parameter"""
if url and url.lstrip().startswith("-"):
raise ValueError(f"Invalid URL (leading dash disallowed): {url!r}")
class GenericBrowser(BaseBrowser):
"""Class for all browsers started with a command
@@ -181,6 +186,7 @@ class GenericBrowser(BaseBrowser):
def open(self, url, new=0, autoraise=True):
sys.audit("webbrowser.open", url)
self._check_url(url)
cmdline = [self.name] + [arg.replace("%s", url)
for arg in self.args]
try:
@@ -201,6 +207,7 @@ class BackgroundBrowser(GenericBrowser):
cmdline = [self.name] + [arg.replace("%s", url)
for arg in self.args]
sys.audit("webbrowser.open", url)
self._check_url(url)
try:
if sys.platform[:3] == 'win':
p = subprocess.Popen(cmdline)
@@ -280,7 +287,9 @@ class UnixBrowser(BaseBrowser):
raise Error("Bad 'new' parameter to open(); "
f"expected 0, 1, or 2, got {new}")
args = [arg.replace("%s", url).replace("%action", action)
self._check_url(url.replace("%action", action))
args = [arg.replace("%action", action).replace("%s", url)
for arg in self.remote_args]
args = [arg for arg in args if arg]
success = self._invoke(args, True, autoraise, url)
@@ -358,6 +367,7 @@ class Konqueror(BaseBrowser):
def open(self, url, new=0, autoraise=True):
sys.audit("webbrowser.open", url)
self._check_url(url)
# XXX Currently I know no way to prevent KFM from opening a new win.
if new == 2:
action = "newTab"
@@ -483,10 +493,10 @@ def register_standard_browsers():
if sys.platform == 'darwin':
register("MacOSX", None, MacOSXOSAScript('default'))
register("chrome", None, MacOSXOSAScript('chrome'))
register("chrome", None, MacOSXOSAScript('google chrome'))
register("firefox", None, MacOSXOSAScript('firefox'))
register("safari", None, MacOSXOSAScript('safari'))
# OS X can use below Unix support (but we prefer using the OS X
# macOS can use below Unix support (but we prefer using the macOS
# specific stuff)
if sys.platform == "ios":
@@ -560,6 +570,19 @@ def register_standard_browsers():
# Treat choices in same way as if passed into get() but do register
# and prepend to _tryorder
for cmdline in userchoices:
if all(x not in cmdline for x in " \t"):
# Assume this is the name of a registered command, use
# that unless it is a GenericBrowser.
try:
command = _browsers[cmdline.lower()]
except KeyError:
pass
else:
if not isinstance(command[1], GenericBrowser):
_tryorder.insert(0, cmdline.lower())
continue
if cmdline != '':
cmd = _synthesize(cmdline, preferred=True)
if cmd[1] is None:
@@ -576,6 +599,7 @@ if sys.platform[:3] == "win":
class WindowsDefault(BaseBrowser):
def open(self, url, new=0, autoraise=True):
sys.audit("webbrowser.open", url)
self._check_url(url)
try:
os.startfile(url)
except OSError:
@@ -596,9 +620,35 @@ if sys.platform == 'darwin':
def open(self, url, new=0, autoraise=True):
sys.audit("webbrowser.open", url)
self._check_url(url)
url = url.replace('"', '%22')
if self.name == 'default':
script = f'open location "{url}"' # opens in default browser
proto, _sep, _rest = url.partition(":")
if _sep and proto.lower() in {"http", "https"}:
# default web URL, don't need to lookup browser
script = f'open location "{url}"'
else:
# if not a web URL, need to lookup default browser to ensure a browser is launched
# this should always work, but is overkill to lookup http handler
# before launching http
script = f"""
use framework "AppKit"
use AppleScript version "2.4"
use scripting additions
property NSWorkspace : a reference to current application's NSWorkspace
property NSURL : a reference to current application's NSURL
set http_url to NSURL's URLWithString:"https://python.org"
set browser_url to (NSWorkspace's sharedWorkspace)'s ¬
URLForApplicationToOpenURL:http_url
set app_path to browser_url's relativePath as text -- NSURL to absolute path '/Applications/Safari.app'
tell application app_path
activate
open location "{url}"
end tell
"""
else:
script = f'''
tell application "{self.name}"
@@ -607,7 +657,7 @@ if sys.platform == 'darwin':
end
'''
osapipe = os.popen("osascript", "w")
osapipe = os.popen("/usr/bin/osascript", "w")
if osapipe is None:
return False
@@ -627,6 +677,7 @@ if sys.platform == "ios":
class IOSBrowser(BaseBrowser):
def open(self, url, new=0, autoraise=True):
sys.audit("webbrowser.open", url)
self._check_url(url)
# If ctypes isn't available, we can't open a browser
if objc is None:
return False
@@ -682,7 +733,9 @@ if sys.platform == "ios":
def parse_args(arg_list: list[str] | None):
import argparse
parser = argparse.ArgumentParser(description="Open URL in a web browser.")
parser = argparse.ArgumentParser(
description="Open URL in a web browser.", color=True,
)
parser.add_argument("url", help="URL to open")
group = parser.add_mutually_exclusive_group()