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
168173class 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
683734def 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