// Package webbrowser provides a simple API for opening web pages on your // default browser. package webbrowser import ( "errors" "fmt" "net/url" "os" "os/exec" "runtime" "strings" ) var ( ErrCantOpenBrowser = errors.New("webbrowser: can't open browser") ErrNoCandidates = errors.New("webbrowser: no browser candidate found for your OS") ) // Candidates contains a list of registered `Browser`s that will be tried with Open. var Candidates []Browser type Browser interface { // Command returns a ready to be used Cmd that will open an URL. Command(string) (*exec.Cmd, error) // Open tries to open a URL in your default browser. NOTE: This may cause // your program to hang until the browser process is closed in some OSes, // see https://github.com/toqueteos/webbrowser/issues/4. Open(string) error } // Open tries to open a URL in your default browser ensuring you have a display // set up and not running this from SSH. NOTE: This may cause your program to // hang until the browser process is closed in some OSes, see // https://github.com/toqueteos/webbrowser/issues/4. func Open(s string) (err error) { if len(Candidates) == 0 { return ErrNoCandidates } // Try to determine if there's a display available (only linux) and we // aren't on a terminal (all but windows). switch runtime.GOOS { case "linux": // No display, no need to open a browser. Lynx users **MAY** have // something to say about this. if os.Getenv("DISPLAY") == "" { return fmt.Errorf("webbrowser: tried to open %q, no screen found", s) } fallthrough case "darwin": // Check SSH env vars. if os.Getenv("SSH_CLIENT") != "" || os.Getenv("SSH_TTY") != "" { return fmt.Errorf("webbrowser: tried to open %q, but you are running a shell session", s) } } // Try all candidates for _, candidate := range Candidates { err := candidate.Open(s) if err == nil { return nil } } return ErrCantOpenBrowser } func init() { // Register the default Browser for current OS, if it exists. if os, ok := osCommand[runtime.GOOS]; ok { Candidates = append(Candidates, browserCommand{os.cmd, os.args}) } } var ( osCommand = map[string]*browserCommand{ "android": &browserCommand{"xdg-open", nil}, "darwin": &browserCommand{"open", nil}, "freebsd": &browserCommand{"xdg-open", nil}, "linux": &browserCommand{"xdg-open", nil}, "netbsd": &browserCommand{"xdg-open", nil}, "openbsd": &browserCommand{"xdg-open", nil}, // It may be open instead "windows": &browserCommand{"cmd", []string{"/c", "start"}}, } winSchemes = [3]string{"https", "http", "file"} ) type browserCommand struct { cmd string args []string } func (b browserCommand) Command(s string) (*exec.Cmd, error) { u, err := url.Parse(s) if err != nil { return nil, err } validUrl := ensureValidURL(u) b.args = append(b.args, validUrl) return exec.Command(b.cmd, b.args...), nil } func (b browserCommand) Open(s string) error { cmd, err := b.Command(s) if err != nil { return err } return cmd.Run() } func ensureScheme(u *url.URL) { for _, s := range winSchemes { if u.Scheme == s { return } } u.Scheme = "http" } func ensureValidURL(u *url.URL) string { // Enforce a scheme (windows requires scheme to be set to work properly). ensureScheme(u) s := u.String() // Escape characters not allowed by cmd/bash switch runtime.GOOS { case "windows": s = strings.Replace(s, "&", `^&`, -1) } return s }