Added new optional credentials argument to SMTPHandler.__init__, and smtp.login(...
[python.git] / Lib / webbrowser.py
blob209cb17b6ad0a32f7e060ef1182a4ebaef287b0b
1 #! /usr/bin/env python
2 """Interfaces for launching and remotely controlling Web browsers."""
4 import os
5 import shlex
6 import sys
7 import stat
8 import subprocess
9 import time
11 __all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"]
13 class Error(Exception):
14 pass
16 _browsers = {} # Dictionary of available browser controllers
17 _tryorder = [] # Preference order of available browsers
19 def register(name, klass, instance=None, update_tryorder=1):
20 """Register a browser connector and, optionally, connection."""
21 _browsers[name.lower()] = [klass, instance]
22 if update_tryorder > 0:
23 _tryorder.append(name)
24 elif update_tryorder < 0:
25 _tryorder.insert(0, name)
27 def get(using=None):
28 """Return a browser launcher instance appropriate for the environment."""
29 if using is not None:
30 alternatives = [using]
31 else:
32 alternatives = _tryorder
33 for browser in alternatives:
34 if '%s' in browser:
35 # User gave us a command line, split it into name and args
36 browser = shlex.split(browser)
37 if browser[-1] == '&':
38 return BackgroundBrowser(browser[:-1])
39 else:
40 return GenericBrowser(browser)
41 else:
42 # User gave us a browser name or path.
43 try:
44 command = _browsers[browser.lower()]
45 except KeyError:
46 command = _synthesize(browser)
47 if command[1] is not None:
48 return command[1]
49 elif command[0] is not None:
50 return command[0]()
51 raise Error("could not locate runnable browser")
53 # Please note: the following definition hides a builtin function.
54 # It is recommended one does "import webbrowser" and uses webbrowser.open(url)
55 # instead of "from webbrowser import *".
57 def open(url, new=0, autoraise=1):
58 for name in _tryorder:
59 browser = get(name)
60 if browser.open(url, new, autoraise):
61 return True
62 return False
64 def open_new(url):
65 return open(url, 1)
67 def open_new_tab(url):
68 return open(url, 2)
71 def _synthesize(browser, update_tryorder=1):
72 """Attempt to synthesize a controller base on existing controllers.
74 This is useful to create a controller when a user specifies a path to
75 an entry in the BROWSER environment variable -- we can copy a general
76 controller to operate using a specific installation of the desired
77 browser in this way.
79 If we can't create a controller in this way, or if there is no
80 executable for the requested browser, return [None, None].
82 """
83 cmd = browser.split()[0]
84 if not _iscommand(cmd):
85 return [None, None]
86 name = os.path.basename(cmd)
87 try:
88 command = _browsers[name.lower()]
89 except KeyError:
90 return [None, None]
91 # now attempt to clone to fit the new name:
92 controller = command[1]
93 if controller and name.lower() == controller.basename:
94 import copy
95 controller = copy.copy(controller)
96 controller.name = browser
97 controller.basename = os.path.basename(browser)
98 register(browser, None, controller, update_tryorder)
99 return [None, controller]
100 return [None, None]
103 if sys.platform[:3] == "win":
104 def _isexecutable(cmd):
105 cmd = cmd.lower()
106 if os.path.isfile(cmd) and cmd.endswith((".exe", ".bat")):
107 return True
108 for ext in ".exe", ".bat":
109 if os.path.isfile(cmd + ext):
110 return True
111 return False
112 else:
113 def _isexecutable(cmd):
114 if os.path.isfile(cmd):
115 mode = os.stat(cmd)[stat.ST_MODE]
116 if mode & stat.S_IXUSR or mode & stat.S_IXGRP or mode & stat.S_IXOTH:
117 return True
118 return False
120 def _iscommand(cmd):
121 """Return True if cmd is executable or can be found on the executable
122 search path."""
123 if _isexecutable(cmd):
124 return True
125 path = os.environ.get("PATH")
126 if not path:
127 return False
128 for d in path.split(os.pathsep):
129 exe = os.path.join(d, cmd)
130 if _isexecutable(exe):
131 return True
132 return False
135 # General parent classes
137 class BaseBrowser(object):
138 """Parent class for all browsers. Do not use directly."""
140 args = ['%s']
142 def __init__(self, name=""):
143 self.name = name
144 self.basename = name
146 def open(self, url, new=0, autoraise=1):
147 raise NotImplementedError
149 def open_new(self, url):
150 return self.open(url, 1)
152 def open_new_tab(self, url):
153 return self.open(url, 2)
156 class GenericBrowser(BaseBrowser):
157 """Class for all browsers started with a command
158 and without remote functionality."""
160 def __init__(self, name):
161 if isinstance(name, basestring):
162 self.name = name
163 else:
164 # name should be a list with arguments
165 self.name = name[0]
166 self.args = name[1:]
167 self.basename = os.path.basename(self.name)
169 def open(self, url, new=0, autoraise=1):
170 cmdline = [self.name] + [arg.replace("%s", url)
171 for arg in self.args]
172 try:
173 if sys.platform[:3] == 'win':
174 p = subprocess.Popen(cmdline)
175 else:
176 p = subprocess.Popen(cmdline, close_fds=True)
177 return not p.wait()
178 except OSError:
179 return False
182 class BackgroundBrowser(GenericBrowser):
183 """Class for all browsers which are to be started in the
184 background."""
186 def open(self, url, new=0, autoraise=1):
187 cmdline = [self.name] + [arg.replace("%s", url)
188 for arg in self.args]
189 try:
190 if sys.platform[:3] == 'win':
191 p = subprocess.Popen(cmdline)
192 else:
193 setsid = getattr(os, 'setsid', None)
194 if not setsid:
195 setsid = getattr(os, 'setpgrp', None)
196 p = subprocess.Popen(cmdline, close_fds=True, preexec_fn=setsid)
197 return (p.poll() is None)
198 except OSError:
199 return False
202 class UnixBrowser(BaseBrowser):
203 """Parent class for all Unix browsers with remote functionality."""
205 raise_opts = None
206 remote_args = ['%action', '%s']
207 remote_action = None
208 remote_action_newwin = None
209 remote_action_newtab = None
210 background = False
211 redirect_stdout = True
213 def _invoke(self, args, remote, autoraise):
214 raise_opt = []
215 if remote and self.raise_opts:
216 # use autoraise argument only for remote invocation
217 autoraise = int(bool(autoraise))
218 opt = self.raise_opts[autoraise]
219 if opt: raise_opt = [opt]
221 cmdline = [self.name] + raise_opt + args
223 if remote or self.background:
224 inout = file(os.devnull, "r+")
225 else:
226 # for TTY browsers, we need stdin/out
227 inout = None
228 # if possible, put browser in separate process group, so
229 # keyboard interrupts don't affect browser as well as Python
230 setsid = getattr(os, 'setsid', None)
231 if not setsid:
232 setsid = getattr(os, 'setpgrp', None)
234 p = subprocess.Popen(cmdline, close_fds=True, stdin=inout,
235 stdout=(self.redirect_stdout and inout or None),
236 stderr=inout, preexec_fn=setsid)
237 if remote:
238 # wait five secons. If the subprocess is not finished, the
239 # remote invocation has (hopefully) started a new instance.
240 time.sleep(1)
241 rc = p.poll()
242 if rc is None:
243 time.sleep(4)
244 rc = p.poll()
245 if rc is None:
246 return True
247 # if remote call failed, open() will try direct invocation
248 return not rc
249 elif self.background:
250 if p.poll() is None:
251 return True
252 else:
253 return False
254 else:
255 return not p.wait()
257 def open(self, url, new=0, autoraise=1):
258 if new == 0:
259 action = self.remote_action
260 elif new == 1:
261 action = self.remote_action_newwin
262 elif new == 2:
263 if self.remote_action_newtab is None:
264 action = self.remote_action_newwin
265 else:
266 action = self.remote_action_newtab
267 else:
268 raise Error("Bad 'new' parameter to open(); " +
269 "expected 0, 1, or 2, got %s" % new)
271 args = [arg.replace("%s", url).replace("%action", action)
272 for arg in self.remote_args]
273 success = self._invoke(args, True, autoraise)
274 if not success:
275 # remote invocation failed, try straight way
276 args = [arg.replace("%s", url) for arg in self.args]
277 return self._invoke(args, False, False)
278 else:
279 return True
282 class Mozilla(UnixBrowser):
283 """Launcher class for Mozilla/Netscape browsers."""
285 raise_opts = ["-noraise", "-raise"]
287 remote_args = ['-remote', 'openURL(%s%action)']
288 remote_action = ""
289 remote_action_newwin = ",new-window"
290 remote_action_newtab = ",new-tab"
292 background = True
294 Netscape = Mozilla
297 class Galeon(UnixBrowser):
298 """Launcher class for Galeon/Epiphany browsers."""
300 raise_opts = ["-noraise", ""]
301 remote_args = ['%action', '%s']
302 remote_action = "-n"
303 remote_action_newwin = "-w"
305 background = True
308 class Opera(UnixBrowser):
309 "Launcher class for Opera browser."
311 raise_opts = ["", "-raise"]
313 remote_args = ['-remote', 'openURL(%s%action)']
314 remote_action = ""
315 remote_action_newwin = ",new-window"
316 remote_action_newtab = ",new-page"
317 background = True
320 class Elinks(UnixBrowser):
321 "Launcher class for Elinks browsers."
323 remote_args = ['-remote', 'openURL(%s%action)']
324 remote_action = ""
325 remote_action_newwin = ",new-window"
326 remote_action_newtab = ",new-tab"
327 background = False
329 # elinks doesn't like its stdout to be redirected -
330 # it uses redirected stdout as a signal to do -dump
331 redirect_stdout = False
334 class Konqueror(BaseBrowser):
335 """Controller for the KDE File Manager (kfm, or Konqueror).
337 See the output of ``kfmclient --commands``
338 for more information on the Konqueror remote-control interface.
341 def open(self, url, new=0, autoraise=1):
342 # XXX Currently I know no way to prevent KFM from opening a new win.
343 if new == 2:
344 action = "newTab"
345 else:
346 action = "openURL"
348 devnull = file(os.devnull, "r+")
349 # if possible, put browser in separate process group, so
350 # keyboard interrupts don't affect browser as well as Python
351 setsid = getattr(os, 'setsid', None)
352 if not setsid:
353 setsid = getattr(os, 'setpgrp', None)
355 try:
356 p = subprocess.Popen(["kfmclient", action, url],
357 close_fds=True, stdin=devnull,
358 stdout=devnull, stderr=devnull)
359 except OSError:
360 # fall through to next variant
361 pass
362 else:
363 p.wait()
364 # kfmclient's return code unfortunately has no meaning as it seems
365 return True
367 try:
368 p = subprocess.Popen(["konqueror", "--silent", url],
369 close_fds=True, stdin=devnull,
370 stdout=devnull, stderr=devnull,
371 preexec_fn=setsid)
372 except OSError:
373 # fall through to next variant
374 pass
375 else:
376 if p.poll() is None:
377 # Should be running now.
378 return True
380 try:
381 p = subprocess.Popen(["kfm", "-d", url],
382 close_fds=True, stdin=devnull,
383 stdout=devnull, stderr=devnull,
384 preexec_fn=setsid)
385 except OSError:
386 return False
387 else:
388 return (p.poll() is None)
391 class Grail(BaseBrowser):
392 # There should be a way to maintain a connection to Grail, but the
393 # Grail remote control protocol doesn't really allow that at this
394 # point. It probably never will!
395 def _find_grail_rc(self):
396 import glob
397 import pwd
398 import socket
399 import tempfile
400 tempdir = os.path.join(tempfile.gettempdir(),
401 ".grail-unix")
402 user = pwd.getpwuid(os.getuid())[0]
403 filename = os.path.join(tempdir, user + "-*")
404 maybes = glob.glob(filename)
405 if not maybes:
406 return None
407 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
408 for fn in maybes:
409 # need to PING each one until we find one that's live
410 try:
411 s.connect(fn)
412 except socket.error:
413 # no good; attempt to clean it out, but don't fail:
414 try:
415 os.unlink(fn)
416 except IOError:
417 pass
418 else:
419 return s
421 def _remote(self, action):
422 s = self._find_grail_rc()
423 if not s:
424 return 0
425 s.send(action)
426 s.close()
427 return 1
429 def open(self, url, new=0, autoraise=1):
430 if new:
431 ok = self._remote("LOADNEW " + url)
432 else:
433 ok = self._remote("LOAD " + url)
434 return ok
438 # Platform support for Unix
441 # These are the right tests because all these Unix browsers require either
442 # a console terminal or an X display to run.
444 def register_X_browsers():
446 # The default GNOME browser
447 if "GNOME_DESKTOP_SESSION_ID" in os.environ and _iscommand("gnome-open"):
448 register("gnome-open", None, BackgroundBrowser("gnome-open"))
450 # The default KDE browser
451 if "KDE_FULL_SESSION" in os.environ and _iscommand("kfmclient"):
452 register("kfmclient", Konqueror, Konqueror("kfmclient"))
454 # The Mozilla/Netscape browsers
455 for browser in ("mozilla-firefox", "firefox",
456 "mozilla-firebird", "firebird",
457 "seamonkey", "mozilla", "netscape"):
458 if _iscommand(browser):
459 register(browser, None, Mozilla(browser))
461 # Konqueror/kfm, the KDE browser.
462 if _iscommand("kfm"):
463 register("kfm", Konqueror, Konqueror("kfm"))
464 elif _iscommand("konqueror"):
465 register("konqueror", Konqueror, Konqueror("konqueror"))
467 # Gnome's Galeon and Epiphany
468 for browser in ("galeon", "epiphany"):
469 if _iscommand(browser):
470 register(browser, None, Galeon(browser))
472 # Skipstone, another Gtk/Mozilla based browser
473 if _iscommand("skipstone"):
474 register("skipstone", None, BackgroundBrowser("skipstone"))
476 # Opera, quite popular
477 if _iscommand("opera"):
478 register("opera", None, Opera("opera"))
480 # Next, Mosaic -- old but still in use.
481 if _iscommand("mosaic"):
482 register("mosaic", None, BackgroundBrowser("mosaic"))
484 # Grail, the Python browser. Does anybody still use it?
485 if _iscommand("grail"):
486 register("grail", Grail, None)
488 # Prefer X browsers if present
489 if os.environ.get("DISPLAY"):
490 register_X_browsers()
492 # Also try console browsers
493 if os.environ.get("TERM"):
494 # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
495 if _iscommand("links"):
496 register("links", None, GenericBrowser("links"))
497 if _iscommand("elinks"):
498 register("elinks", None, Elinks("elinks"))
499 # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
500 if _iscommand("lynx"):
501 register("lynx", None, GenericBrowser("lynx"))
502 # The w3m browser <http://w3m.sourceforge.net/>
503 if _iscommand("w3m"):
504 register("w3m", None, GenericBrowser("w3m"))
507 # Platform support for Windows
510 if sys.platform[:3] == "win":
511 class WindowsDefault(BaseBrowser):
512 def open(self, url, new=0, autoraise=1):
513 try:
514 os.startfile(url)
515 except WindowsError:
516 # [Error 22] No application is associated with the specified
517 # file for this operation: '<URL>'
518 return False
519 else:
520 return True
522 _tryorder = []
523 _browsers = {}
525 # First try to use the default Windows browser
526 register("windows-default", WindowsDefault)
528 # Detect some common Windows browsers, fallback to IE
529 iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
530 "Internet Explorer\\IEXPLORE.EXE")
531 for browser in ("firefox", "firebird", "seamonkey", "mozilla",
532 "netscape", "opera", iexplore):
533 if _iscommand(browser):
534 register(browser, None, BackgroundBrowser(browser))
537 # Platform support for MacOS
540 try:
541 import ic
542 except ImportError:
543 pass
544 else:
545 class InternetConfig(BaseBrowser):
546 def open(self, url, new=0, autoraise=1):
547 ic.launchurl(url)
548 return True # Any way to get status?
550 register("internet-config", InternetConfig, update_tryorder=-1)
552 if sys.platform == 'darwin':
553 # Adapted from patch submitted to SourceForge by Steven J. Burr
554 class MacOSX(BaseBrowser):
555 """Launcher class for Aqua browsers on Mac OS X
557 Optionally specify a browser name on instantiation. Note that this
558 will not work for Aqua browsers if the user has moved the application
559 package after installation.
561 If no browser is specified, the default browser, as specified in the
562 Internet System Preferences panel, will be used.
564 def __init__(self, name):
565 self.name = name
567 def open(self, url, new=0, autoraise=1):
568 assert "'" not in url
569 # hack for local urls
570 if not ':' in url:
571 url = 'file:'+url
573 # new must be 0 or 1
574 new = int(bool(new))
575 if self.name == "default":
576 # User called open, open_new or get without a browser parameter
577 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
578 else:
579 # User called get and chose a browser
580 if self.name == "OmniWeb":
581 toWindow = ""
582 else:
583 # Include toWindow parameter of OpenURL command for browsers
584 # that support it. 0 == new window; -1 == existing
585 toWindow = "toWindow %d" % (new - 1)
586 cmd = 'OpenURL "%s"' % url.replace('"', '%22')
587 script = '''tell application "%s"
588 activate
589 %s %s
590 end tell''' % (self.name, cmd, toWindow)
591 # Open pipe to AppleScript through osascript command
592 osapipe = os.popen("osascript", "w")
593 if osapipe is None:
594 return False
595 # Write script to osascript's stdin
596 osapipe.write(script)
597 rc = osapipe.close()
598 return not rc
600 # Don't clear _tryorder or _browsers since OS X can use above Unix support
601 # (but we prefer using the OS X specific stuff)
602 register("MacOSX", None, MacOSX('default'), -1)
606 # Platform support for OS/2
609 if sys.platform[:3] == "os2" and _iscommand("netscape"):
610 _tryorder = []
611 _browsers = {}
612 register("os2netscape", None,
613 GenericBrowser(["start", "netscape", "%s"]), -1)
616 # OK, now that we know what the default preference orders for each
617 # platform are, allow user to override them with the BROWSER variable.
618 if "BROWSER" in os.environ:
619 _userchoices = os.environ["BROWSER"].split(os.pathsep)
620 _userchoices.reverse()
622 # Treat choices in same way as if passed into get() but do register
623 # and prepend to _tryorder
624 for cmdline in _userchoices:
625 if cmdline != '':
626 _synthesize(cmdline, -1)
627 cmdline = None # to make del work if _userchoices was empty
628 del cmdline
629 del _userchoices
631 # what to do if _tryorder is now empty?
634 def main():
635 import getopt
636 usage = """Usage: %s [-n | -t] url
637 -n: open new window
638 -t: open new tab""" % sys.argv[0]
639 try:
640 opts, args = getopt.getopt(sys.argv[1:], 'ntd')
641 except getopt.error, msg:
642 print >>sys.stderr, msg
643 print >>sys.stderr, usage
644 sys.exit(1)
645 new_win = 0
646 for o, a in opts:
647 if o == '-n': new_win = 1
648 elif o == '-t': new_win = 2
649 if len(args) <> 1:
650 print >>sys.stderr, usage
651 sys.exit(1)
653 url = args[0]
654 open(url, new_win)
656 print "\a"
658 if __name__ == "__main__":
659 main()