From 814309a8a5e5057632f94da1fc29ba2b9e202c75 Mon Sep 17 00:00:00 2001 From: kr-g Date: Thu, 22 Jun 2023 10:59:06 +0200 Subject: [PATCH 01/26] browser incognito mode support for incognito / private browsing --- Lib/webbrowser.py | 283 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 209 insertions(+), 74 deletions(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 8b0628745c57fc..c122895cbc102b 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -11,13 +11,41 @@ __all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"] + class Error(Exception): pass + _lock = threading.RLock() -_browsers = {} # Dictionary of available browser controllers -_tryorder = None # Preference order of available browsers -_os_preferred_browser = None # The preferred browser +_browsers = {} # Dictionary of available browser controllers +_tryorder = None # Preference order of available browsers +_os_preferred_browser = None # The preferred browser + +_browsers_types = [ + "mozilla", + "firefox", + "galeon", + "epiphany", + "skipstone", + "kfmclient", + "konqueror", + "kfm", + "mosaic", + "opera", + "grail", + "links", + "elinks", + "lynx", + "w3m", + "windows-default", + "macosx", + "safari", + "google-chrome", + "chrome", + "chromium", + "chromium-browser", +] + def register(name, klass, instance=None, *, preferred=False): """Register a browser connector.""" @@ -34,6 +62,7 @@ def register(name, klass, instance=None, *, preferred=False): else: _tryorder.append(name) + def get(using=None): """Return a browser launcher instance appropriate for the environment.""" if _tryorder is None: @@ -45,10 +74,10 @@ def get(using=None): else: alternatives = _tryorder for browser in alternatives: - if '%s' in browser: + if "%s" in browser: # User gave us a command line, split it into name and args browser = shlex.split(browser) - if browser[-1] == '&': + if browser[-1] == "&": return BackgroundBrowser(browser[:-1]) else: return GenericBrowser(browser) @@ -64,10 +93,12 @@ def get(using=None): return command[0]() raise Error("could not locate runnable browser") + # Please note: the following definition hides a builtin function. # It is recommended one does "import webbrowser" and uses webbrowser.open(url) # instead of "from webbrowser import *". + def open(url, new=0, autoraise=True): """Display url using the default browser. @@ -75,6 +106,7 @@ def open(url, new=0, autoraise=True): - 0: the same browser window (the default). - 1: a new browser window. - 2: a new browser page ("tab"). + - 3: a incognito/ private browser. If possible, autoraise raises the window (the default) or not. """ if _tryorder is None: @@ -87,6 +119,7 @@ def open(url, new=0, autoraise=True): return True return False + def open_new(url): """Open url in a new window of the default browser. @@ -94,6 +127,7 @@ def open_new(url): """ return open(url, 1) + def open_new_tab(url): """Open url in a new page ("tab") of the default browser. @@ -102,6 +136,14 @@ def open_new_tab(url): return open(url, 2) +def open_incognito(url): + """Open url in incognito mode of the default browser. + + If not possible, then the behavior becomes equivalent to open_new(). + """ + return open(url, 3) + + def _synthesize(browser, *, preferred=False): """Attempt to synthesize a controller based on existing controllers. @@ -126,6 +168,7 @@ def _synthesize(browser, *, preferred=False): controller = command[1] if controller and name.lower() == controller.basename: import copy + controller = copy.copy(controller) controller.name = browser controller.basename = os.path.basename(browser) @@ -136,10 +179,11 @@ def _synthesize(browser, *, preferred=False): # General parent classes + class BaseBrowser(object): """Parent class for all browsers. Do not use directly.""" - args = ['%s'] + args = ["%s"] def __init__(self, name=""): self.name = name @@ -154,10 +198,13 @@ def open_new(self, url): def open_new_tab(self, url): return self.open(url, 2) + def open_incognito(self, url): + return self.open(url, 3) + class GenericBrowser(BaseBrowser): """Class for all browsers started with a command - and without remote functionality.""" + and without remote functionality.""" def __init__(self, name): if isinstance(name, str): @@ -171,10 +218,9 @@ def __init__(self, name): def open(self, url, new=0, autoraise=True): sys.audit("webbrowser.open", url) - cmdline = [self.name] + [arg.replace("%s", url) - for arg in self.args] + cmdline = [self.name] + [arg.replace("%s", url) for arg in self.args] try: - if sys.platform[:3] == 'win': + if sys.platform[:3] == "win": p = subprocess.Popen(cmdline) else: p = subprocess.Popen(cmdline, close_fds=True) @@ -185,19 +231,17 @@ def open(self, url, new=0, autoraise=True): class BackgroundBrowser(GenericBrowser): """Class for all browsers which are to be started in the - background.""" + background.""" def open(self, url, new=0, autoraise=True): - cmdline = [self.name] + [arg.replace("%s", url) - for arg in self.args] + cmdline = [self.name] + [arg.replace("%s", url) for arg in self.args] sys.audit("webbrowser.open", url) try: - if sys.platform[:3] == 'win': + if sys.platform[:3] == "win": p = subprocess.Popen(cmdline) else: - p = subprocess.Popen(cmdline, close_fds=True, - start_new_session=True) - return (p.poll() is None) + p = subprocess.Popen(cmdline, close_fds=True, start_new_session=True) + return p.poll() is None except OSError: return False @@ -214,10 +258,11 @@ class UnixBrowser(BaseBrowser): # used for new=1 (open_new). If newtab is not None, it is used for # new=3 (open_new_tab). After both substitutions are made, any empty # strings in the transformed remote_args list will be removed. - remote_args = ['%action', '%s'] + remote_args = ["%action", "%s", "%incognito"] remote_action = None remote_action_newwin = None remote_action_newtab = None + remote_action_incognito = None def _invoke(self, args, remote, autoraise, url=None): raise_opt = [] @@ -225,7 +270,8 @@ def _invoke(self, args, remote, autoraise, url=None): # use autoraise argument only for remote invocation autoraise = int(autoraise) opt = self.raise_opts[autoraise] - if opt: raise_opt = [opt] + if opt: + raise_opt = [opt] cmdline = [self.name] + raise_opt + args @@ -234,9 +280,14 @@ def _invoke(self, args, remote, autoraise, url=None): else: # for TTY browsers, we need stdin/out inout = None - p = subprocess.Popen(cmdline, close_fds=True, stdin=inout, - stdout=(self.redirect_stdout and inout or None), - stderr=inout, start_new_session=True) + p = subprocess.Popen( + cmdline, + close_fds=True, + stdin=inout, + stdout=(self.redirect_stdout and inout or None), + stderr=inout, + start_new_session=True, + ) if remote: # wait at most five seconds. If the subprocess is not finished, the # remote invocation has (hopefully) started a new instance. @@ -265,12 +316,18 @@ def open(self, url, new=0, autoraise=True): action = self.remote_action_newwin else: action = self.remote_action_newtab + elif new == 3: + action = self.remote_action_incognito else: - raise Error("Bad 'new' parameter to open(); " + - "expected 0, 1, or 2, got %s" % new) - - args = [arg.replace("%s", url).replace("%action", action) - for arg in self.remote_args] + raise Error( + "Bad 'new' parameter to open(); " + + "expected 0, 1, 2 or 3, got %s" % new + ) + + args = [ + arg.replace("%s", url).replace("%action", action) + for arg in self.remote_args + ] args = [arg for arg in args if arg] success = self._invoke(args, True, autoraise, url) if not success: @@ -284,10 +341,11 @@ def open(self, url, new=0, autoraise=True): class Mozilla(UnixBrowser): """Launcher class for Mozilla browsers.""" - remote_args = ['%action', '%s'] + remote_args = ["%action", "%s"] remote_action = "" remote_action_newwin = "-new-window" remote_action_newtab = "-new-tab" + remote_action_incognito = "--private-window" background = True @@ -295,38 +353,42 @@ class Epiphany(UnixBrowser): """Launcher class for Epiphany browser.""" raise_opts = ["-noraise", ""] - remote_args = ['%action', '%s'] + remote_args = ["%action", "%s"] remote_action = "-n" remote_action_newwin = "-w" + remote_action_incognito = "--incognito-mode" background = True class Chrome(UnixBrowser): "Launcher class for Google Chrome browser." - remote_args = ['%action', '%s'] + remote_args = ["%action", "%s"] remote_action = "" remote_action_newwin = "--new-window" remote_action_newtab = "" + remote_action_incognito = "--incognito" background = True + Chromium = Chrome class Opera(UnixBrowser): "Launcher class for Opera browser." - remote_args = ['%action', '%s'] + remote_args = ["%action", "%s"] remote_action = "" remote_action_newwin = "--new-window" remote_action_newtab = "" + remote_action_incognito = "" background = True class Elinks(UnixBrowser): "Launcher class for Elinks browsers." - remote_args = ['-remote', 'openURL(%s%action)'] + remote_args = ["-remote", "openURL(%s%action)"] remote_action = "" remote_action_newwin = ",new-window" remote_action_newtab = ",new-tab" @@ -355,9 +417,13 @@ def open(self, url, new=0, autoraise=True): devnull = subprocess.DEVNULL try: - p = subprocess.Popen(["kfmclient", action, url], - close_fds=True, stdin=devnull, - stdout=devnull, stderr=devnull) + p = subprocess.Popen( + ["kfmclient", action, url], + close_fds=True, + stdin=devnull, + stdout=devnull, + stderr=devnull, + ) except OSError: # fall through to next variant pass @@ -367,10 +433,14 @@ def open(self, url, new=0, autoraise=True): return True try: - p = subprocess.Popen(["konqueror", "--silent", url], - close_fds=True, stdin=devnull, - stdout=devnull, stderr=devnull, - start_new_session=True) + p = subprocess.Popen( + ["konqueror", "--silent", url], + close_fds=True, + stdin=devnull, + stdout=devnull, + stderr=devnull, + start_new_session=True, + ) except OSError: # fall through to next variant pass @@ -380,20 +450,24 @@ def open(self, url, new=0, autoraise=True): return True try: - p = subprocess.Popen(["kfm", "-d", url], - close_fds=True, stdin=devnull, - stdout=devnull, stderr=devnull, - start_new_session=True) + p = subprocess.Popen( + ["kfm", "-d", url], + close_fds=True, + stdin=devnull, + stdout=devnull, + stderr=devnull, + start_new_session=True, + ) except OSError: return False else: - return (p.poll() is None) + return p.poll() is None class Edge(UnixBrowser): "Launcher class for Microsoft Edge browser." - remote_args = ['%action', '%s'] + remote_args = ["%action", "%s"] remote_action = "" remote_action_newwin = "--new-window" remote_action_newtab = "" @@ -407,6 +481,7 @@ class Edge(UnixBrowser): # These are the right tests because all these Unix browsers require either # a console terminal or an X display to run. + def register_X_browsers(): # use xdg-open if around @@ -431,8 +506,7 @@ def register_X_browsers(): register("x-www-browser", None, BackgroundBrowser("x-www-browser")) # The Mozilla browsers - for browser in ("firefox", "iceweasel", "seamonkey", "mozilla-firefox", - "mozilla"): + for browser in ("firefox", "iceweasel", "seamonkey", "mozilla-firefox", "mozilla"): if shutil.which(browser): register(browser, None, Mozilla(browser)) @@ -455,7 +529,6 @@ def register_X_browsers(): if shutil.which("opera"): register("opera", None, Opera("opera")) - if shutil.which("microsoft-edge"): register("microsoft-edge", None, Edge("microsoft-edge")) @@ -464,11 +537,11 @@ def register_standard_browsers(): global _tryorder _tryorder = [] - if sys.platform == 'darwin': - register("MacOSX", None, MacOSXOSAScript('default')) - register("chrome", None, MacOSXOSAScript('chrome')) - register("firefox", None, MacOSXOSAScript('firefox')) - register("safari", None, MacOSXOSAScript('safari')) + if sys.platform == "darwin": + register("MacOSX", None, MacOSXOSAScript("default")) + register("chrome", None, MacOSXOSAScript("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 # specific stuff) @@ -482,13 +555,24 @@ def register_standard_browsers(): # Detect some common Windows browsers, fallback to Microsoft Edge # location in 64-bit Windows - edge64 = os.path.join(os.environ.get("PROGRAMFILES(x86)", "C:\\Program Files (x86)"), - "Microsoft\\Edge\\Application\\msedge.exe") + edge64 = os.path.join( + os.environ.get("PROGRAMFILES(x86)", "C:\\Program Files (x86)"), + "Microsoft\\Edge\\Application\\msedge.exe", + ) # location in 32-bit Windows - edge32 = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"), - "Microsoft\\Edge\\Application\\msedge.exe") - for browser in ("firefox", "seamonkey", "mozilla", "chrome", - "opera", edge64, edge32): + edge32 = os.path.join( + os.environ.get("PROGRAMFILES", "C:\\Program Files"), + "Microsoft\\Edge\\Application\\msedge.exe", + ) + for browser in ( + "firefox", + "seamonkey", + "mozilla", + "chrome", + "opera", + edge64, + edge32, + ): if shutil.which(browser): register(browser, None, BackgroundBrowser(browser)) if shutil.which("MicrosoftEdge.exe"): @@ -500,7 +584,12 @@ def register_standard_browsers(): cmd = "xdg-settings get default-web-browser".split() raw_result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL) result = raw_result.decode().strip() - except (FileNotFoundError, subprocess.CalledProcessError, PermissionError, NotADirectoryError) : + except ( + FileNotFoundError, + subprocess.CalledProcessError, + PermissionError, + NotADirectoryError, + ): pass else: global _os_preferred_browser @@ -534,7 +623,7 @@ 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 cmdline != '': + if cmdline != "": cmd = _synthesize(cmdline, preferred=True) if cmd[1] is None: register(cmdline, None, GenericBrowser(cmdline), preferred=True) @@ -547,6 +636,7 @@ def register_standard_browsers(): # if sys.platform[:3] == "win": + class WindowsDefault(BaseBrowser): def open(self, url, new=0, autoraise=True): sys.audit("webbrowser.open", url) @@ -559,25 +649,32 @@ def open(self, url, new=0, autoraise=True): else: return True + # # Platform support for macOS # -if sys.platform == 'darwin': +if sys.platform == "darwin": + class MacOSXOSAScript(BaseBrowser): - def __init__(self, name='default'): + def __init__(self, name="default"): super().__init__(name) def open(self, url, new=0, autoraise=True): - if self.name == 'default': - script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser + if self.name == "default": + script = 'open location "%s"' % url.replace( + '"', "%22" + ) # opens in default browser else: - script = f''' + script = f""" tell application "%s" activate open location "%s" end - '''%(self.name, url.replace('"', '%22')) + """ % ( + self.name, + url.replace('"', "%22"), + ) osapipe = os.popen("osascript", "w") if osapipe is None: @@ -588,33 +685,71 @@ def open(self, url, new=0, autoraise=True): return not rc +def _get_supported_browsers(): + _brow = [] + for bt in _browsers_types: + try: + _ = get(bt) + _brow.append(bt) + except: + pass + return _brow + + def main(): import getopt - usage = """Usage: %s [-n | -t | -h] url + + usage = ( + """Usage: %s [-l] | [-b browser] [-i | -n | -t | -h] url -n: open new window -t: open new tab - -h, --help: show help""" % sys.argv[0] + -l: list available browsers + -b : uses to open + -h, --help: show help""" + % sys.argv[0] + ) try: - opts, args = getopt.getopt(sys.argv[1:], 'ntdh',['help']) + opts, args = getopt.getopt(sys.argv[1:], "lintb:dh", ["help"]) except getopt.error as msg: print(msg, file=sys.stderr) print(usage, file=sys.stderr) sys.exit(1) new_win = 0 + browser = None for o, a in opts: - if o == '-n': new_win = 1 - elif o == '-t': new_win = 2 - elif o == '-h' or o == '--help': + if o == "-n": + new_win = 1 + elif o == "-t": + new_win = 2 + elif o == "-i": + new_win = 3 + elif o == "-b": + browser = a + elif o == "-l": + for nam in _get_supported_browsers(): + print(nam) + sys.exit() + elif o == "-h" or o == "--help": print(usage, file=sys.stderr) sys.exit() + if len(args) != 1: print(usage, file=sys.stderr) sys.exit(1) url = args[0] - open(url, new_win) + if browser: + try: + br = get(browser) + except: + print(f"browser {browser} not found", file=sys.stderr) + sys.exit(1) + br.open(url, new_win) + else: + open(url, new_win) print("\a") + if __name__ == "__main__": main() From 2a67e334afe677fcab9719f340b7e0ad5fdbb375 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 22 Jun 2023 09:14:46 +0000 Subject: [PATCH 02/26] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Security/2023-06-22-09-14-45.gh-issue-105983.7osoHj.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Security/2023-06-22-09-14-45.gh-issue-105983.7osoHj.rst diff --git a/Misc/NEWS.d/next/Security/2023-06-22-09-14-45.gh-issue-105983.7osoHj.rst b/Misc/NEWS.d/next/Security/2023-06-22-09-14-45.gh-issue-105983.7osoHj.rst new file mode 100644 index 00000000000000..6da001e0b22a9a --- /dev/null +++ b/Misc/NEWS.d/next/Security/2023-06-22-09-14-45.gh-issue-105983.7osoHj.rst @@ -0,0 +1 @@ +support for incognito / private browsing for module webbroser From 46d06d44840fde1b18027d42d0a3a945ad490d4c Mon Sep 17 00:00:00 2001 From: kr-g Date: Thu, 22 Jun 2023 11:26:49 +0200 Subject: [PATCH 03/26] browser incognito mode default value set in base class --- Lib/webbrowser.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index c122895cbc102b..7a64567dd0b7f8 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -262,7 +262,7 @@ class UnixBrowser(BaseBrowser): remote_action = None remote_action_newwin = None remote_action_newtab = None - remote_action_incognito = None + remote_action_incognito = "" def _invoke(self, args, remote, autoraise, url=None): raise_opt = [] @@ -381,7 +381,6 @@ class Opera(UnixBrowser): remote_action = "" remote_action_newwin = "--new-window" remote_action_newtab = "" - remote_action_incognito = "" background = True From 7baebe5bd64bf9a2f26a3212290539e0141baced Mon Sep 17 00:00:00 2001 From: kr-g Date: Thu, 22 Jun 2023 12:35:10 +0200 Subject: [PATCH 04/26] removed unused flag removed %incognito since using %action for the implementation --- Lib/webbrowser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 7a64567dd0b7f8..5337de255fe7a4 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -258,7 +258,7 @@ class UnixBrowser(BaseBrowser): # used for new=1 (open_new). If newtab is not None, it is used for # new=3 (open_new_tab). After both substitutions are made, any empty # strings in the transformed remote_args list will be removed. - remote_args = ["%action", "%s", "%incognito"] + remote_args = ["%action", "%s"] remote_action = None remote_action_newwin = None remote_action_newtab = None From b9079f49b91b45bd439442a9a15fbd02ed6a6a4d Mon Sep 17 00:00:00 2001 From: kr-g Date: Thu, 22 Jun 2023 13:24:11 +0200 Subject: [PATCH 05/26] reverted changes cause by formatting with black --- Lib/webbrowser.py | 212 ++++++++++++++++------------------------------ 1 file changed, 73 insertions(+), 139 deletions(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 5337de255fe7a4..5411e03dca58d2 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -11,15 +11,13 @@ __all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"] - class Error(Exception): pass - _lock = threading.RLock() -_browsers = {} # Dictionary of available browser controllers -_tryorder = None # Preference order of available browsers -_os_preferred_browser = None # The preferred browser +_browsers = {} # Dictionary of available browser controllers +_tryorder = None # Preference order of available browsers +_os_preferred_browser = None # The preferred browser _browsers_types = [ "mozilla", @@ -46,7 +44,6 @@ class Error(Exception): "chromium-browser", ] - def register(name, klass, instance=None, *, preferred=False): """Register a browser connector.""" with _lock: @@ -62,7 +59,6 @@ def register(name, klass, instance=None, *, preferred=False): else: _tryorder.append(name) - def get(using=None): """Return a browser launcher instance appropriate for the environment.""" if _tryorder is None: @@ -74,10 +70,10 @@ def get(using=None): else: alternatives = _tryorder for browser in alternatives: - if "%s" in browser: + if '%s' in browser: # User gave us a command line, split it into name and args browser = shlex.split(browser) - if browser[-1] == "&": + if browser[-1] == '&': return BackgroundBrowser(browser[:-1]) else: return GenericBrowser(browser) @@ -93,12 +89,10 @@ def get(using=None): return command[0]() raise Error("could not locate runnable browser") - # Please note: the following definition hides a builtin function. # It is recommended one does "import webbrowser" and uses webbrowser.open(url) # instead of "from webbrowser import *". - def open(url, new=0, autoraise=True): """Display url using the default browser. @@ -106,7 +100,6 @@ def open(url, new=0, autoraise=True): - 0: the same browser window (the default). - 1: a new browser window. - 2: a new browser page ("tab"). - - 3: a incognito/ private browser. If possible, autoraise raises the window (the default) or not. """ if _tryorder is None: @@ -119,7 +112,6 @@ def open(url, new=0, autoraise=True): return True return False - def open_new(url): """Open url in a new window of the default browser. @@ -127,7 +119,6 @@ def open_new(url): """ return open(url, 1) - def open_new_tab(url): """Open url in a new page ("tab") of the default browser. @@ -135,7 +126,6 @@ def open_new_tab(url): """ return open(url, 2) - def open_incognito(url): """Open url in incognito mode of the default browser. @@ -143,7 +133,6 @@ def open_incognito(url): """ return open(url, 3) - def _synthesize(browser, *, preferred=False): """Attempt to synthesize a controller based on existing controllers. @@ -168,7 +157,6 @@ def _synthesize(browser, *, preferred=False): controller = command[1] if controller and name.lower() == controller.basename: import copy - controller = copy.copy(controller) controller.name = browser controller.basename = os.path.basename(browser) @@ -179,11 +167,10 @@ def _synthesize(browser, *, preferred=False): # General parent classes - class BaseBrowser(object): """Parent class for all browsers. Do not use directly.""" - args = ["%s"] + args = ['%s'] def __init__(self, name=""): self.name = name @@ -204,7 +191,7 @@ def open_incognito(self, url): class GenericBrowser(BaseBrowser): """Class for all browsers started with a command - and without remote functionality.""" + and without remote functionality.""" def __init__(self, name): if isinstance(name, str): @@ -218,9 +205,10 @@ def __init__(self, name): def open(self, url, new=0, autoraise=True): sys.audit("webbrowser.open", url) - cmdline = [self.name] + [arg.replace("%s", url) for arg in self.args] + cmdline = [self.name] + [arg.replace("%s", url) + for arg in self.args] try: - if sys.platform[:3] == "win": + if sys.platform[:3] == 'win': p = subprocess.Popen(cmdline) else: p = subprocess.Popen(cmdline, close_fds=True) @@ -231,17 +219,19 @@ def open(self, url, new=0, autoraise=True): class BackgroundBrowser(GenericBrowser): """Class for all browsers which are to be started in the - background.""" + background.""" def open(self, url, new=0, autoraise=True): - cmdline = [self.name] + [arg.replace("%s", url) for arg in self.args] + cmdline = [self.name] + [arg.replace("%s", url) + for arg in self.args] sys.audit("webbrowser.open", url) try: - if sys.platform[:3] == "win": + if sys.platform[:3] == 'win': p = subprocess.Popen(cmdline) else: - p = subprocess.Popen(cmdline, close_fds=True, start_new_session=True) - return p.poll() is None + p = subprocess.Popen(cmdline, close_fds=True, + start_new_session=True) + return (p.poll() is None) except OSError: return False @@ -258,7 +248,7 @@ class UnixBrowser(BaseBrowser): # used for new=1 (open_new). If newtab is not None, it is used for # new=3 (open_new_tab). After both substitutions are made, any empty # strings in the transformed remote_args list will be removed. - remote_args = ["%action", "%s"] + remote_args = ['%action', '%s'] remote_action = None remote_action_newwin = None remote_action_newtab = None @@ -270,8 +260,7 @@ def _invoke(self, args, remote, autoraise, url=None): # use autoraise argument only for remote invocation autoraise = int(autoraise) opt = self.raise_opts[autoraise] - if opt: - raise_opt = [opt] + if opt: raise_opt = [opt] cmdline = [self.name] + raise_opt + args @@ -280,14 +269,9 @@ def _invoke(self, args, remote, autoraise, url=None): else: # for TTY browsers, we need stdin/out inout = None - p = subprocess.Popen( - cmdline, - close_fds=True, - stdin=inout, - stdout=(self.redirect_stdout and inout or None), - stderr=inout, - start_new_session=True, - ) + p = subprocess.Popen(cmdline, close_fds=True, stdin=inout, + stdout=(self.redirect_stdout and inout or None), + stderr=inout, start_new_session=True) if remote: # wait at most five seconds. If the subprocess is not finished, the # remote invocation has (hopefully) started a new instance. @@ -319,15 +303,11 @@ def open(self, url, new=0, autoraise=True): elif new == 3: action = self.remote_action_incognito else: - raise Error( - "Bad 'new' parameter to open(); " - + "expected 0, 1, 2 or 3, got %s" % new - ) - - args = [ - arg.replace("%s", url).replace("%action", action) - for arg in self.remote_args - ] + raise Error("Bad 'new' parameter to open(); " + + "expected 0, 1, or 2, got %s" % new) + + args = [arg.replace("%s", url).replace("%action", action) + for arg in self.remote_args] args = [arg for arg in args if arg] success = self._invoke(args, True, autoraise, url) if not success: @@ -341,7 +321,7 @@ def open(self, url, new=0, autoraise=True): class Mozilla(UnixBrowser): """Launcher class for Mozilla browsers.""" - remote_args = ["%action", "%s"] + remote_args = ['%action', '%s'] remote_action = "" remote_action_newwin = "-new-window" remote_action_newtab = "-new-tab" @@ -353,7 +333,7 @@ class Epiphany(UnixBrowser): """Launcher class for Epiphany browser.""" raise_opts = ["-noraise", ""] - remote_args = ["%action", "%s"] + remote_args = ['%action', '%s'] remote_action = "-n" remote_action_newwin = "-w" remote_action_incognito = "--incognito-mode" @@ -363,21 +343,20 @@ class Epiphany(UnixBrowser): class Chrome(UnixBrowser): "Launcher class for Google Chrome browser." - remote_args = ["%action", "%s"] + remote_args = ['%action', '%s'] remote_action = "" remote_action_newwin = "--new-window" remote_action_newtab = "" remote_action_incognito = "--incognito" background = True - Chromium = Chrome class Opera(UnixBrowser): "Launcher class for Opera browser." - remote_args = ["%action", "%s"] + remote_args = ['%action', '%s'] remote_action = "" remote_action_newwin = "--new-window" remote_action_newtab = "" @@ -387,7 +366,7 @@ class Opera(UnixBrowser): class Elinks(UnixBrowser): "Launcher class for Elinks browsers." - remote_args = ["-remote", "openURL(%s%action)"] + remote_args = ['-remote', 'openURL(%s%action)'] remote_action = "" remote_action_newwin = ",new-window" remote_action_newtab = ",new-tab" @@ -416,13 +395,9 @@ def open(self, url, new=0, autoraise=True): devnull = subprocess.DEVNULL try: - p = subprocess.Popen( - ["kfmclient", action, url], - close_fds=True, - stdin=devnull, - stdout=devnull, - stderr=devnull, - ) + p = subprocess.Popen(["kfmclient", action, url], + close_fds=True, stdin=devnull, + stdout=devnull, stderr=devnull) except OSError: # fall through to next variant pass @@ -432,14 +407,10 @@ def open(self, url, new=0, autoraise=True): return True try: - p = subprocess.Popen( - ["konqueror", "--silent", url], - close_fds=True, - stdin=devnull, - stdout=devnull, - stderr=devnull, - start_new_session=True, - ) + p = subprocess.Popen(["konqueror", "--silent", url], + close_fds=True, stdin=devnull, + stdout=devnull, stderr=devnull, + start_new_session=True) except OSError: # fall through to next variant pass @@ -449,24 +420,20 @@ def open(self, url, new=0, autoraise=True): return True try: - p = subprocess.Popen( - ["kfm", "-d", url], - close_fds=True, - stdin=devnull, - stdout=devnull, - stderr=devnull, - start_new_session=True, - ) + p = subprocess.Popen(["kfm", "-d", url], + close_fds=True, stdin=devnull, + stdout=devnull, stderr=devnull, + start_new_session=True) except OSError: return False else: - return p.poll() is None + return (p.poll() is None) class Edge(UnixBrowser): "Launcher class for Microsoft Edge browser." - remote_args = ["%action", "%s"] + remote_args = ['%action', '%s'] remote_action = "" remote_action_newwin = "--new-window" remote_action_newtab = "" @@ -480,7 +447,6 @@ class Edge(UnixBrowser): # These are the right tests because all these Unix browsers require either # a console terminal or an X display to run. - def register_X_browsers(): # use xdg-open if around @@ -505,7 +471,8 @@ def register_X_browsers(): register("x-www-browser", None, BackgroundBrowser("x-www-browser")) # The Mozilla browsers - for browser in ("firefox", "iceweasel", "seamonkey", "mozilla-firefox", "mozilla"): + for browser in ("firefox", "iceweasel", "seamonkey", "mozilla-firefox", + "mozilla"): if shutil.which(browser): register(browser, None, Mozilla(browser)) @@ -528,6 +495,7 @@ def register_X_browsers(): if shutil.which("opera"): register("opera", None, Opera("opera")) + if shutil.which("microsoft-edge"): register("microsoft-edge", None, Edge("microsoft-edge")) @@ -536,11 +504,11 @@ def register_standard_browsers(): global _tryorder _tryorder = [] - if sys.platform == "darwin": - register("MacOSX", None, MacOSXOSAScript("default")) - register("chrome", None, MacOSXOSAScript("chrome")) - register("firefox", None, MacOSXOSAScript("firefox")) - register("safari", None, MacOSXOSAScript("safari")) + if sys.platform == 'darwin': + register("MacOSX", None, MacOSXOSAScript('default')) + register("chrome", None, MacOSXOSAScript('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 # specific stuff) @@ -554,24 +522,13 @@ def register_standard_browsers(): # Detect some common Windows browsers, fallback to Microsoft Edge # location in 64-bit Windows - edge64 = os.path.join( - os.environ.get("PROGRAMFILES(x86)", "C:\\Program Files (x86)"), - "Microsoft\\Edge\\Application\\msedge.exe", - ) + edge64 = os.path.join(os.environ.get("PROGRAMFILES(x86)", "C:\\Program Files (x86)"), + "Microsoft\\Edge\\Application\\msedge.exe") # location in 32-bit Windows - edge32 = os.path.join( - os.environ.get("PROGRAMFILES", "C:\\Program Files"), - "Microsoft\\Edge\\Application\\msedge.exe", - ) - for browser in ( - "firefox", - "seamonkey", - "mozilla", - "chrome", - "opera", - edge64, - edge32, - ): + edge32 = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"), + "Microsoft\\Edge\\Application\\msedge.exe") + for browser in ("firefox", "seamonkey", "mozilla", "chrome", + "opera", edge64, edge32): if shutil.which(browser): register(browser, None, BackgroundBrowser(browser)) if shutil.which("MicrosoftEdge.exe"): @@ -583,12 +540,7 @@ def register_standard_browsers(): cmd = "xdg-settings get default-web-browser".split() raw_result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL) result = raw_result.decode().strip() - except ( - FileNotFoundError, - subprocess.CalledProcessError, - PermissionError, - NotADirectoryError, - ): + except (FileNotFoundError, subprocess.CalledProcessError, PermissionError, NotADirectoryError) : pass else: global _os_preferred_browser @@ -622,7 +574,7 @@ 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 cmdline != "": + if cmdline != '': cmd = _synthesize(cmdline, preferred=True) if cmd[1] is None: register(cmdline, None, GenericBrowser(cmdline), preferred=True) @@ -635,7 +587,6 @@ def register_standard_browsers(): # if sys.platform[:3] == "win": - class WindowsDefault(BaseBrowser): def open(self, url, new=0, autoraise=True): sys.audit("webbrowser.open", url) @@ -648,32 +599,25 @@ def open(self, url, new=0, autoraise=True): else: return True - # # Platform support for macOS # -if sys.platform == "darwin": - +if sys.platform == 'darwin': class MacOSXOSAScript(BaseBrowser): - def __init__(self, name="default"): + def __init__(self, name='default'): super().__init__(name) def open(self, url, new=0, autoraise=True): - if self.name == "default": - script = 'open location "%s"' % url.replace( - '"', "%22" - ) # opens in default browser + if self.name == 'default': + script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser else: - script = f""" + script = f''' tell application "%s" activate open location "%s" end - """ % ( - self.name, - url.replace('"', "%22"), - ) + '''%(self.name, url.replace('"', '%22')) osapipe = os.popen("osascript", "w") if osapipe is None: @@ -697,29 +641,21 @@ def _get_supported_browsers(): def main(): import getopt - - usage = ( - """Usage: %s [-l] | [-b browser] [-i | -n | -t | -h] url + usage = """Usage: %s [-l] | [-b browser] [-i | -n | -t | -h] url -n: open new window -t: open new tab - -l: list available browsers -b : uses to open - -h, --help: show help""" - % sys.argv[0] - ) + -h, --help: show help""" % sys.argv[0] try: - opts, args = getopt.getopt(sys.argv[1:], "lintb:dh", ["help"]) + opts, args = getopt.getopt(sys.argv[1:], 'lintb:dh',['help']) except getopt.error as msg: print(msg, file=sys.stderr) print(usage, file=sys.stderr) sys.exit(1) new_win = 0 - browser = None for o, a in opts: - if o == "-n": - new_win = 1 - elif o == "-t": - new_win = 2 + if o == '-n': new_win = 1 + elif o == '-t': new_win = 2 elif o == "-i": new_win = 3 elif o == "-b": @@ -728,10 +664,9 @@ def main(): for nam in _get_supported_browsers(): print(nam) sys.exit() - elif o == "-h" or o == "--help": + elif o == '-h' or o == '--help': print(usage, file=sys.stderr) sys.exit() - if len(args) != 1: print(usage, file=sys.stderr) sys.exit(1) @@ -749,6 +684,5 @@ def main(): print("\a") - if __name__ == "__main__": main() From f04f255fd362e951c15aec0290d4c0f24c80a7af Mon Sep 17 00:00:00 2001 From: karl Date: Thu, 22 Jun 2023 13:28:38 +0200 Subject: [PATCH 06/26] Update Misc/NEWS.d/next/Security/2023-06-22-09-14-45.gh-issue-105983.7osoHj.rst Co-authored-by: Nikita Sobolev --- .../Security/2023-06-22-09-14-45.gh-issue-105983.7osoHj.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Security/2023-06-22-09-14-45.gh-issue-105983.7osoHj.rst b/Misc/NEWS.d/next/Security/2023-06-22-09-14-45.gh-issue-105983.7osoHj.rst index 6da001e0b22a9a..de0a057e861228 100644 --- a/Misc/NEWS.d/next/Security/2023-06-22-09-14-45.gh-issue-105983.7osoHj.rst +++ b/Misc/NEWS.d/next/Security/2023-06-22-09-14-45.gh-issue-105983.7osoHj.rst @@ -1 +1 @@ -support for incognito / private browsing for module webbroser +Add support for incognito / private browsing for :func:`webbrowser.open`. From 7eaa4551f527340c09c4f1b22c9a62a2ff6fc12c Mon Sep 17 00:00:00 2001 From: kr-g Date: Thu, 22 Jun 2023 13:30:12 +0200 Subject: [PATCH 07/26] doc text --- Lib/webbrowser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 5411e03dca58d2..4e801038b4f206 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -100,6 +100,7 @@ def open(url, new=0, autoraise=True): - 0: the same browser window (the default). - 1: a new browser window. - 2: a new browser page ("tab"). + - 3: a incognito/ private browser. If possible, autoraise raises the window (the default) or not. """ if _tryorder is None: From 9e4ee5958793e90d4544933d9591b689e74a636c Mon Sep 17 00:00:00 2001 From: karl Date: Thu, 22 Jun 2023 14:03:06 +0200 Subject: [PATCH 08/26] Update Lib/webbrowser.py Co-authored-by: Nikita Sobolev --- Lib/webbrowser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 4e801038b4f206..aa818ffd441352 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -305,7 +305,7 @@ def open(self, url, new=0, autoraise=True): action = self.remote_action_incognito else: raise Error("Bad 'new' parameter to open(); " + - "expected 0, 1, or 2, got %s" % new) + "expected 0, 1, 2, or 3, got %s" % new) args = [arg.replace("%s", url).replace("%action", action) for arg in self.remote_args] From 333c0e796a783617cf4ffe2ed3cd62a7fa25a31f Mon Sep 17 00:00:00 2001 From: kr-g Date: Thu, 22 Jun 2023 22:52:19 +0200 Subject: [PATCH 09/26] typo --- Lib/webbrowser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index aa818ffd441352..57ccdb245161eb 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -100,7 +100,7 @@ def open(url, new=0, autoraise=True): - 0: the same browser window (the default). - 1: a new browser window. - 2: a new browser page ("tab"). - - 3: a incognito/ private browser. + - 3: an incognito/ private browser. If possible, autoraise raises the window (the default) or not. """ if _tryorder is None: From 308e060f0c67510c0af0f39b3418bab756c7e6ce Mon Sep 17 00:00:00 2001 From: kr-g Date: Fri, 23 Jun 2023 06:10:00 +0200 Subject: [PATCH 10/26] removed outdated browsers --- Lib/webbrowser.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 57ccdb245161eb..f3ccd33d9ad9cf 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -22,15 +22,11 @@ class Error(Exception): _browsers_types = [ "mozilla", "firefox", - "galeon", "epiphany", - "skipstone", "kfmclient", "konqueror", "kfm", - "mosaic", "opera", - "grail", "links", "elinks", "lynx", From fd0e6a39f196838f5c6a626276e7313eb3d6df6b Mon Sep 17 00:00:00 2001 From: kr-g Date: Fri, 23 Jun 2023 06:12:08 +0200 Subject: [PATCH 11/26] removed var assignment --- Lib/webbrowser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index f3ccd33d9ad9cf..63d2bd8f735803 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -629,7 +629,7 @@ def _get_supported_browsers(): _brow = [] for bt in _browsers_types: try: - _ = get(bt) + get(bt) _brow.append(bt) except: pass From e952efe5e8813a68618ecaa273954f7874294637 Mon Sep 17 00:00:00 2001 From: kr-g Date: Fri, 23 Jun 2023 07:59:36 +0200 Subject: [PATCH 12/26] moved from security to library folder --- .../2023-06-22-09-14-45.gh-issue-105983.7osoHj.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/{Security => Library}/2023-06-22-09-14-45.gh-issue-105983.7osoHj.rst (100%) diff --git a/Misc/NEWS.d/next/Security/2023-06-22-09-14-45.gh-issue-105983.7osoHj.rst b/Misc/NEWS.d/next/Library/2023-06-22-09-14-45.gh-issue-105983.7osoHj.rst similarity index 100% rename from Misc/NEWS.d/next/Security/2023-06-22-09-14-45.gh-issue-105983.7osoHj.rst rename to Misc/NEWS.d/next/Library/2023-06-22-09-14-45.gh-issue-105983.7osoHj.rst From ade902e4f58c0cbc3e126859ade2f5d1be3d7c24 Mon Sep 17 00:00:00 2001 From: kr-g Date: Fri, 23 Jun 2023 08:29:42 +0200 Subject: [PATCH 13/26] added tests --- Lib/test/test_webbrowser.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py index 2d695bc883131f..eb53a8540b3960 100644 --- a/Lib/test/test_webbrowser.py +++ b/Lib/test/test_webbrowser.py @@ -94,6 +94,11 @@ def test_open_new_tab(self): options=[], arguments=[URL]) + def test_open_new_tab(self): + self._test('open_incognito', + options=["--incognito"], + arguments=[URL]) + class EdgeCommandTest(CommandTestMixin, unittest.TestCase): @@ -144,6 +149,10 @@ def test_open_new_tab(self): options=[], arguments=['-new-tab', URL]) + def test_open_new_tab(self): + self._test('open_incognito', + options=["--private-window"], + arguments=[URL]) class EpiphanyCommandTest(CommandTestMixin, unittest.TestCase): @@ -169,6 +178,10 @@ def test_open_new_tab(self): options=['-w'], arguments=[URL]) + def test_open_new_tab(self): + self._test('open_incognito', + options=["--incognito-mode"], + arguments=[URL]) class OperaCommandTest(CommandTestMixin, unittest.TestCase): @@ -334,3 +347,4 @@ def test_environment_preferred(self): if __name__=='__main__': unittest.main() + From 7c28c35b6bc2fe93762fc6d3f78131ff6d5011e5 Mon Sep 17 00:00:00 2001 From: kr-g Date: Fri, 23 Jun 2023 08:38:37 +0200 Subject: [PATCH 14/26] renamed to test_open_incognito --- Lib/test/test_webbrowser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py index eb53a8540b3960..30aba79cf18372 100644 --- a/Lib/test/test_webbrowser.py +++ b/Lib/test/test_webbrowser.py @@ -94,7 +94,7 @@ def test_open_new_tab(self): options=[], arguments=[URL]) - def test_open_new_tab(self): + def test_open_incognito(self): self._test('open_incognito', options=["--incognito"], arguments=[URL]) @@ -149,7 +149,7 @@ def test_open_new_tab(self): options=[], arguments=['-new-tab', URL]) - def test_open_new_tab(self): + def test_open_incognito(self): self._test('open_incognito', options=["--private-window"], arguments=[URL]) @@ -178,7 +178,7 @@ def test_open_new_tab(self): options=['-w'], arguments=[URL]) - def test_open_new_tab(self): + def test_open_incognito(self): self._test('open_incognito', options=["--incognito-mode"], arguments=[URL]) From 6597494b43fdc98fd1f6663df41522f8cbb0b9b5 Mon Sep 17 00:00:00 2001 From: karl Date: Fri, 23 Jun 2023 09:42:41 +0200 Subject: [PATCH 15/26] Update Lib/test/test_webbrowser.py Co-authored-by: Hugo van Kemenade --- Lib/test/test_webbrowser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py index 30aba79cf18372..635c5437263ea8 100644 --- a/Lib/test/test_webbrowser.py +++ b/Lib/test/test_webbrowser.py @@ -347,4 +347,3 @@ def test_environment_preferred(self): if __name__=='__main__': unittest.main() - From 89754996b4904c13ea25e72408a49c88d0e4e693 Mon Sep 17 00:00:00 2001 From: karl Date: Fri, 23 Jun 2023 10:30:02 +0200 Subject: [PATCH 16/26] Update Lib/webbrowser.py Co-authored-by: Hugo van Kemenade --- Lib/webbrowser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 63d2bd8f735803..94429b6f85c4ee 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -301,7 +301,7 @@ def open(self, url, new=0, autoraise=True): action = self.remote_action_incognito else: raise Error("Bad 'new' parameter to open(); " + - "expected 0, 1, 2, or 3, got %s" % new) + f"expected 0, 1, 2, or 3, got {new}") args = [arg.replace("%s", url).replace("%action", action) for arg in self.remote_args] From 2b9a2800ce66cf805043be6ef18d9ef58670b04d Mon Sep 17 00:00:00 2001 From: AmirNakhaeii Date: Sat, 24 Jun 2023 17:36:35 -0400 Subject: [PATCH 17/26] gh-105983: added incognito / private mode to webbrowser.py --- Lib/webbrowser.py | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 8b0628745c57fc..249610b6081981 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -102,6 +102,13 @@ def open_new_tab(url): return open(url, 2) +def open_new_incognito_tab(url): + """Open url in a new incognito / private page ("tab") of the default browser. + + If not possible, then the behavior becomes equivalent to open_new(). + """ + return open(url, 3) + def _synthesize(browser, *, preferred=False): """Attempt to synthesize a controller based on existing controllers. @@ -153,7 +160,9 @@ def open_new(self, url): def open_new_tab(self, url): return self.open(url, 2) - + + def open_new_incognito_tab(self, url): + return self.open(url, 3) class GenericBrowser(BaseBrowser): """Class for all browsers started with a command @@ -218,6 +227,7 @@ class UnixBrowser(BaseBrowser): remote_action = None remote_action_newwin = None remote_action_newtab = None + remote_action_new_incognito_tab = None def _invoke(self, args, remote, autoraise, url=None): raise_opt = [] @@ -261,10 +271,11 @@ def open(self, url, new=0, autoraise=True): elif new == 1: action = self.remote_action_newwin elif new == 2: - if self.remote_action_newtab is None: + if action := self.remote_action_newtab is None: + action = self.remote_action_newwin + elif new == 3: + if action := self.remote_action_new_incognito_tab is None: action = self.remote_action_newwin - else: - action = self.remote_action_newtab else: raise Error("Bad 'new' parameter to open(); " + "expected 0, 1, or 2, got %s" % new) @@ -288,6 +299,7 @@ class Mozilla(UnixBrowser): remote_action = "" remote_action_newwin = "-new-window" remote_action_newtab = "-new-tab" + remote_action_new_incognito_tab = "--private-window" background = True @@ -298,6 +310,7 @@ class Epiphany(UnixBrowser): remote_args = ['%action', '%s'] remote_action = "-n" remote_action_newwin = "-w" + remote_action_new_incognito_tab = "-i" background = True @@ -308,6 +321,7 @@ class Chrome(UnixBrowser): remote_action = "" remote_action_newwin = "--new-window" remote_action_newtab = "" + remote_action_new_incognito_tab = "--incognito" background = True Chromium = Chrome @@ -320,6 +334,7 @@ class Opera(UnixBrowser): remote_action = "" remote_action_newwin = "--new-window" remote_action_newtab = "" + remote_action_new_incognito_tab = "--private" background = True @@ -330,6 +345,7 @@ class Elinks(UnixBrowser): remote_action = "" remote_action_newwin = ",new-window" remote_action_newtab = ",new-tab" + remote_action_new_incognito_tab = "" background = False # elinks doesn't like its stdout to be redirected - @@ -397,6 +413,7 @@ class Edge(UnixBrowser): remote_action = "" remote_action_newwin = "--new-window" remote_action_newtab = "" + remote_action_new_incognito_tab = "--private" background = True @@ -590,23 +607,24 @@ def open(self, url, new=0, autoraise=True): def main(): import getopt - usage = """Usage: %s [-n | -t | -h] url + usage = """Usage: %s [-n | -t | -h | -i] url -n: open new window -t: open new tab + -i: open new incognito / private tab -h, --help: show help""" % sys.argv[0] try: - opts, args = getopt.getopt(sys.argv[1:], 'ntdh',['help']) + opts, args = getopt.getopt(sys.argv[1:], 'ntdhi',['help']) except getopt.error as msg: print(msg, file=sys.stderr) print(usage, file=sys.stderr) sys.exit(1) new_win = 0 for o, a in opts: - if o == '-n': new_win = 1 - elif o == '-t': new_win = 2 - elif o == '-h' or o == '--help': - print(usage, file=sys.stderr) - sys.exit() + match o: + case '-n': new_win = 1 + case '-t': new_win = 2 + case '-i': new_win = 3 + case '-h' | '--help': print(usage, file=sys.stderr); sys.exit() if len(args) != 1: print(usage, file=sys.stderr) sys.exit(1) From 545784d94cbd9edf8067fd6d13c3b0e9454a225b Mon Sep 17 00:00:00 2001 From: AmirNakhaeii Date: Sat, 24 Jun 2023 17:59:32 -0400 Subject: [PATCH 18/26] gh-105983: Fixed some logical errors --- Lib/webbrowser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 249610b6081981..b2861a6b21427c 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -271,10 +271,10 @@ def open(self, url, new=0, autoraise=True): elif new == 1: action = self.remote_action_newwin elif new == 2: - if action := self.remote_action_newtab is None: + if (action := self.remote_action_newtab) is None: action = self.remote_action_newwin elif new == 3: - if action := self.remote_action_new_incognito_tab is None: + if (action := self.remote_action_new_incognito_tab) is None: action = self.remote_action_newwin else: raise Error("Bad 'new' parameter to open(); " + From f1a20fbf5c9bc5ea25603fc58f72873d4c861d24 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 24 Jun 2023 23:51:28 +0000 Subject: [PATCH 19/26] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2023-06-24-23-51-27.gh-issue-105983.qTo-Jq.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-06-24-23-51-27.gh-issue-105983.qTo-Jq.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-06-24-23-51-27.gh-issue-105983.qTo-Jq.rst b/Misc/NEWS.d/next/Core and Builtins/2023-06-24-23-51-27.gh-issue-105983.qTo-Jq.rst new file mode 100644 index 00000000000000..bc2af2373adf27 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-06-24-23-51-27.gh-issue-105983.qTo-Jq.rst @@ -0,0 +1,2 @@ +Add incognito / private mode to webbrowser module +open browser in incognito mode when in open() function new parameter is equal 3 From 9e9811b12c7722f9ed6eae96b02e0df2fa8e390a Mon Sep 17 00:00:00 2001 From: AmirHo3ein <136080247+AmirNakhaeii@users.noreply.github.com> Date: Sun, 25 Jun 2023 17:54:16 +0330 Subject: [PATCH 20/26] gh-105983: Fix incognito flag for edge browser --- Lib/webbrowser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index b2861a6b21427c..d72af7b44b50b2 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -413,7 +413,7 @@ class Edge(UnixBrowser): remote_action = "" remote_action_newwin = "--new-window" remote_action_newtab = "" - remote_action_new_incognito_tab = "--private" + remote_action_new_incognito_tab = "--inprivate" background = True From a4657c75864bbc98e34246ccc6365e2c62365d74 Mon Sep 17 00:00:00 2001 From: kr-g Date: Tue, 27 Jun 2023 07:49:43 +0200 Subject: [PATCH 21/26] added incognito mode documentation --- Doc/library/webbrowser.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Doc/library/webbrowser.rst b/Doc/library/webbrowser.rst index b6762f78830a5f..25365026808e4c 100644 --- a/Doc/library/webbrowser.rst +++ b/Doc/library/webbrowser.rst @@ -80,6 +80,12 @@ The following functions are defined: equivalent to :func:`open_new`. +.. function:: open_incognito(url) + + Open *url* in incognito, or private, mode of the default browser, if possible, otherwise + equivalent to :func:`open_new`. + + .. function:: get(using=None) Return a controller object for the browser type *using*. If *using* is @@ -221,6 +227,12 @@ module-level convenience functions: possible, otherwise equivalent to :func:`open_new`. +.. method:: controller.open_incognito(url) + + Open *url* in a incognito, or private, mode of the browser handled by this controller, if + possible, otherwise equivalent to :func:`open_new`. + + .. rubric:: Footnotes .. [1] Executables named here without a full path will be searched in the From 2d7a9c5a3aa4fdc656e62933c0e4dd762a1c7063 Mon Sep 17 00:00:00 2001 From: kr-g Date: Fri, 30 Jun 2023 14:21:52 +0200 Subject: [PATCH 22/26] removed --- .../2023-06-24-23-51-27.gh-issue-105983.qTo-Jq.rst | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-06-24-23-51-27.gh-issue-105983.qTo-Jq.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-06-24-23-51-27.gh-issue-105983.qTo-Jq.rst b/Misc/NEWS.d/next/Core and Builtins/2023-06-24-23-51-27.gh-issue-105983.qTo-Jq.rst deleted file mode 100644 index bff0b0fbd3e8ba..00000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2023-06-24-23-51-27.gh-issue-105983.qTo-Jq.rst +++ /dev/null @@ -1 +0,0 @@ -Add incognito / private mode to webbrowser module From 9bb7124b05bb0db2230e218872e6505711e46108 Mon Sep 17 00:00:00 2001 From: kr-g Date: Fri, 30 Jun 2023 14:31:40 +0200 Subject: [PATCH 23/26] corrected incognito var --- Lib/test/test_webbrowser.py | 5 +++++ Lib/webbrowser.py | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py index 635c5437263ea8..5b0495d4145cc0 100644 --- a/Lib/test/test_webbrowser.py +++ b/Lib/test/test_webbrowser.py @@ -124,6 +124,11 @@ def test_open_new_tab(self): options=[], arguments=[URL]) + def test_open_incognito(self): + self._test('open_incognito', + options=["--inprivate"], + arguments=[URL]) + class MozillaCommandTest(CommandTestMixin, unittest.TestCase): diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index c3e68cc56cc479..aaf533cfb6a6b6 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -357,7 +357,6 @@ class Opera(UnixBrowser): remote_action = "" remote_action_newwin = "--new-window" remote_action_newtab = "" - remote_action_new_incognito_tab = "--private" background = True @@ -435,7 +434,7 @@ class Edge(UnixBrowser): remote_action = "" remote_action_newwin = "--new-window" remote_action_newtab = "" - remote_action_new_incognito_tab = "--inprivate" + remote_action_incognito = "--inprivate" background = True From 2130631292669d103e210d4350677e11abacf2f4 Mon Sep 17 00:00:00 2001 From: kr-g Date: Fri, 30 Jun 2023 15:21:11 +0200 Subject: [PATCH 24/26] added missing help text --- Lib/webbrowser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index aaf533cfb6a6b6..2d88444df4664d 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -642,6 +642,8 @@ def main(): usage = """Usage: %s [-l] | [-b browser] [-i | -n | -t | -h] url -n: open new window -t: open new tab + -i: open with private mode + -l: list available browsers -b : uses to open -h, --help: show help""" % sys.argv[0] try: From f8364db68e95c44201c8ef53c62ff9a13c44108a Mon Sep 17 00:00:00 2001 From: kr-g Date: Fri, 30 Jun 2023 15:24:13 +0200 Subject: [PATCH 25/26] added microsoft edge to the browser list --- Lib/webbrowser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 2d88444df4664d..5b95903e69613d 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -38,6 +38,7 @@ class Error(Exception): "chrome", "chromium", "chromium-browser", + "microsoft-edge", ] def register(name, klass, instance=None, *, preferred=False): From d1de67cc2dd0a4eb038dc0122f73d4f92dfeba7b Mon Sep 17 00:00:00 2001 From: kr-g Date: Fri, 30 Jun 2023 15:51:31 +0200 Subject: [PATCH 26/26] reworked browser list option --- Lib/webbrowser.py | 32 +++----------------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 5b95903e69613d..017c5048d22b0d 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -19,27 +19,6 @@ class Error(Exception): _tryorder = None # Preference order of available browsers _os_preferred_browser = None # The preferred browser -_browsers_types = [ - "mozilla", - "firefox", - "epiphany", - "kfmclient", - "konqueror", - "kfm", - "opera", - "links", - "elinks", - "lynx", - "w3m", - "windows-default", - "macosx", - "safari", - "google-chrome", - "chrome", - "chromium", - "chromium-browser", - "microsoft-edge", -] def register(name, klass, instance=None, *, preferred=False): """Register a browser connector.""" @@ -628,14 +607,9 @@ def open(self, url, new=0, autoraise=True): def _get_supported_browsers(): - _brow = [] - for bt in _browsers_types: - try: - get(bt) - _brow.append(bt) - except: - pass - return _brow + if _tryorder is None: + register_standard_browsers() + return _browsers.keys() def main():