Skip to content

Commit f8e0eeb

Browse files
authored
Update webbrowser.py to 3.14.5 (RustPython#7868)
1 parent 32e6f8d commit f8e0eeb

2 files changed

Lines changed: 154 additions & 7 deletions

File tree

Lib/test/test_webbrowser.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import io
12
import os
23
import re
34
import shlex
45
import subprocess
56
import sys
67
import unittest
78
import webbrowser
9+
from functools import partial
810
from test import support
911
from test.support import import_helper
1012
from test.support import is_apple_mobile
@@ -55,6 +57,14 @@ def _test(self, meth, *, args=[URL], kw={}, options, arguments):
5557
popen_args.pop(popen_args.index(option))
5658
self.assertEqual(popen_args, arguments)
5759

60+
def test_reject_dash_prefixes(self):
61+
browser = self.browser_class(name=CMD_NAME)
62+
with self.assertRaisesRegex(
63+
ValueError,
64+
r"^Invalid URL \(leading dash disallowed\): '--key=val http.*'$"
65+
):
66+
browser.open(f"--key=val {URL}")
67+
5868

5969
class GenericBrowserCommandTest(CommandTestMixin, unittest.TestCase):
6070

@@ -109,6 +119,15 @@ def test_open_bad_new_parameter(self):
109119
arguments=[URL],
110120
kw=dict(new=999))
111121

122+
def test_reject_action_dash_prefixes(self):
123+
browser = self.browser_class(name=CMD_NAME)
124+
with self.assertRaises(ValueError):
125+
browser.open('%action--incognito')
126+
# new=1: action is "--new-window", so "%action" itself expands to
127+
# a dash-prefixed flag even with no dash in the original URL.
128+
with self.assertRaises(ValueError):
129+
browser.open('%action', new=1)
130+
112131

113132
class EdgeCommandTest(CommandTestMixin, unittest.TestCase):
114133

@@ -301,6 +320,81 @@ def test_open_new_tab(self):
301320
self._test('open_new_tab')
302321

303322

323+
class MockPopenPipe:
324+
def __init__(self, cmd, mode):
325+
self.cmd = cmd
326+
self.mode = mode
327+
self.pipe = io.StringIO()
328+
self._closed = False
329+
330+
def write(self, buf):
331+
self.pipe.write(buf)
332+
333+
def close(self):
334+
self._closed = True
335+
return None
336+
337+
338+
@unittest.skipUnless(sys.platform == "darwin", "macOS specific test")
339+
@requires_subprocess()
340+
class MacOSXOSAScriptTest(unittest.TestCase):
341+
def setUp(self):
342+
# Ensure that 'BROWSER' is not set to 'open' or something else.
343+
# See: https://github.com/python/cpython/issues/131254.
344+
env = self.enterContext(os_helper.EnvironmentVarGuard())
345+
env.unset("BROWSER")
346+
347+
support.patch(self, os, "popen", self.mock_popen)
348+
self.browser = webbrowser.MacOSXOSAScript("default")
349+
350+
def mock_popen(self, cmd, mode):
351+
self.popen_pipe = MockPopenPipe(cmd, mode)
352+
return self.popen_pipe
353+
354+
def test_default(self):
355+
browser = webbrowser.get()
356+
assert isinstance(browser, webbrowser.MacOSXOSAScript)
357+
self.assertEqual(browser.name, "default")
358+
359+
def test_default_open(self):
360+
url = "https://python.org"
361+
self.browser.open(url)
362+
self.assertTrue(self.popen_pipe._closed)
363+
self.assertEqual(self.popen_pipe.cmd, "/usr/bin/osascript")
364+
script = self.popen_pipe.pipe.getvalue()
365+
self.assertEqual(script.strip(), f'open location "{url}"')
366+
367+
def test_url_quote(self):
368+
self.browser.open('https://python.org/"quote"')
369+
script = self.popen_pipe.pipe.getvalue()
370+
self.assertEqual(
371+
script.strip(), 'open location "https://python.org/%22quote%22"'
372+
)
373+
374+
def test_default_browser_lookup(self):
375+
url = "file:///tmp/some-file.html"
376+
self.browser.open(url)
377+
script = self.popen_pipe.pipe.getvalue()
378+
# doesn't actually test the browser lookup works,
379+
# just that the branch is taken
380+
self.assertIn("URLForApplicationToOpenURL", script)
381+
self.assertIn(f'open location "{url}"', script)
382+
383+
def test_explicit_browser(self):
384+
browser = webbrowser.MacOSXOSAScript("safari")
385+
browser.open("https://python.org")
386+
script = self.popen_pipe.pipe.getvalue()
387+
self.assertIn('tell application "safari"', script)
388+
self.assertIn('open location "https://python.org"', script)
389+
390+
def test_reject_dash_prefixes(self):
391+
with self.assertRaisesRegex(
392+
ValueError,
393+
r"^Invalid URL \(leading dash disallowed\): '--key=val http.*'$"
394+
):
395+
self.browser.open(f"--key=val {URL}")
396+
397+
304398
class BrowserRegistrationTest(unittest.TestCase):
305399

306400
def setUp(self):

Lib/webbrowser.py

100755100644
Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#! /usr/bin/env python3
21
"""Interfaces for launching and remotely controlling web browsers."""
32
# Maintained by Georg Brandl.
43

@@ -164,6 +163,12 @@ def open_new(self, url):
164163
def open_new_tab(self, url):
165164
return self.open(url, 2)
166165

166+
@staticmethod
167+
def _check_url(url):
168+
"""Ensures that the URL is safe to pass to subprocesses as a parameter"""
169+
if url and url.lstrip().startswith("-"):
170+
raise ValueError(f"Invalid URL (leading dash disallowed): {url!r}")
171+
167172

168173
class GenericBrowser(BaseBrowser):
169174
"""Class for all browsers started with a command
@@ -181,6 +186,7 @@ def __init__(self, name):
181186

182187
def open(self, url, new=0, autoraise=True):
183188
sys.audit("webbrowser.open", url)
189+
self._check_url(url)
184190
cmdline = [self.name] + [arg.replace("%s", url)
185191
for arg in self.args]
186192
try:
@@ -201,6 +207,7 @@ def open(self, url, new=0, autoraise=True):
201207
cmdline = [self.name] + [arg.replace("%s", url)
202208
for arg in self.args]
203209
sys.audit("webbrowser.open", url)
210+
self._check_url(url)
204211
try:
205212
if sys.platform[:3] == 'win':
206213
p = subprocess.Popen(cmdline)
@@ -280,7 +287,9 @@ def open(self, url, new=0, autoraise=True):
280287
raise Error("Bad 'new' parameter to open(); "
281288
f"expected 0, 1, or 2, got {new}")
282289

283-
args = [arg.replace("%s", url).replace("%action", action)
290+
self._check_url(url.replace("%action", action))
291+
292+
args = [arg.replace("%action", action).replace("%s", url)
284293
for arg in self.remote_args]
285294
args = [arg for arg in args if arg]
286295
success = self._invoke(args, True, autoraise, url)
@@ -358,6 +367,7 @@ class Konqueror(BaseBrowser):
358367

359368
def open(self, url, new=0, autoraise=True):
360369
sys.audit("webbrowser.open", url)
370+
self._check_url(url)
361371
# XXX Currently I know no way to prevent KFM from opening a new win.
362372
if new == 2:
363373
action = "newTab"
@@ -483,10 +493,10 @@ def register_standard_browsers():
483493

484494
if sys.platform == 'darwin':
485495
register("MacOSX", None, MacOSXOSAScript('default'))
486-
register("chrome", None, MacOSXOSAScript('chrome'))
496+
register("chrome", None, MacOSXOSAScript('google chrome'))
487497
register("firefox", None, MacOSXOSAScript('firefox'))
488498
register("safari", None, MacOSXOSAScript('safari'))
489-
# OS X can use below Unix support (but we prefer using the OS X
499+
# macOS can use below Unix support (but we prefer using the macOS
490500
# specific stuff)
491501

492502
if sys.platform == "ios":
@@ -560,6 +570,19 @@ def register_standard_browsers():
560570
# Treat choices in same way as if passed into get() but do register
561571
# and prepend to _tryorder
562572
for cmdline in userchoices:
573+
if all(x not in cmdline for x in " \t"):
574+
# Assume this is the name of a registered command, use
575+
# that unless it is a GenericBrowser.
576+
try:
577+
command = _browsers[cmdline.lower()]
578+
except KeyError:
579+
pass
580+
581+
else:
582+
if not isinstance(command[1], GenericBrowser):
583+
_tryorder.insert(0, cmdline.lower())
584+
continue
585+
563586
if cmdline != '':
564587
cmd = _synthesize(cmdline, preferred=True)
565588
if cmd[1] is None:
@@ -576,6 +599,7 @@ def register_standard_browsers():
576599
class WindowsDefault(BaseBrowser):
577600
def open(self, url, new=0, autoraise=True):
578601
sys.audit("webbrowser.open", url)
602+
self._check_url(url)
579603
try:
580604
os.startfile(url)
581605
except OSError:
@@ -596,9 +620,35 @@ def __init__(self, name='default'):
596620

597621
def open(self, url, new=0, autoraise=True):
598622
sys.audit("webbrowser.open", url)
623+
self._check_url(url)
599624
url = url.replace('"', '%22')
600625
if self.name == 'default':
601-
script = f'open location "{url}"' # opens in default browser
626+
proto, _sep, _rest = url.partition(":")
627+
if _sep and proto.lower() in {"http", "https"}:
628+
# default web URL, don't need to lookup browser
629+
script = f'open location "{url}"'
630+
else:
631+
# if not a web URL, need to lookup default browser to ensure a browser is launched
632+
# this should always work, but is overkill to lookup http handler
633+
# before launching http
634+
script = f"""
635+
use framework "AppKit"
636+
use AppleScript version "2.4"
637+
use scripting additions
638+
639+
property NSWorkspace : a reference to current application's NSWorkspace
640+
property NSURL : a reference to current application's NSURL
641+
642+
set http_url to NSURL's URLWithString:"https://python.org"
643+
set browser_url to (NSWorkspace's sharedWorkspace)'s ¬
644+
URLForApplicationToOpenURL:http_url
645+
set app_path to browser_url's relativePath as text -- NSURL to absolute path '/Applications/Safari.app'
646+
647+
tell application app_path
648+
activate
649+
open location "{url}"
650+
end tell
651+
"""
602652
else:
603653
script = f'''
604654
tell application "{self.name}"
@@ -607,7 +657,7 @@ def open(self, url, new=0, autoraise=True):
607657
end
608658
'''
609659

610-
osapipe = os.popen("osascript", "w")
660+
osapipe = os.popen("/usr/bin/osascript", "w")
611661
if osapipe is None:
612662
return False
613663

@@ -627,6 +677,7 @@ def open(self, url, new=0, autoraise=True):
627677
class IOSBrowser(BaseBrowser):
628678
def open(self, url, new=0, autoraise=True):
629679
sys.audit("webbrowser.open", url)
680+
self._check_url(url)
630681
# If ctypes isn't available, we can't open a browser
631682
if objc is None:
632683
return False
@@ -682,7 +733,9 @@ def open(self, url, new=0, autoraise=True):
682733

683734
def parse_args(arg_list: list[str] | None):
684735
import argparse
685-
parser = argparse.ArgumentParser(description="Open URL in a web browser.")
736+
parser = argparse.ArgumentParser(
737+
description="Open URL in a web browser.", color=True,
738+
)
686739
parser.add_argument("url", help="URL to open")
687740

688741
group = parser.add_mutually_exclusive_group()

0 commit comments

Comments
 (0)