Skip to content

Commit 56c6f10

Browse files
committed
Parse and examine URL, assume scheme if missing
1 parent 055ff21 commit 56c6f10

2 files changed

Lines changed: 34 additions & 17 deletions

File tree

pkg/browser/browser.go

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ package browser
44
import (
55
"fmt"
66
"io"
7+
"net/url"
78
"os"
89
"os/exec"
9-
"strings"
1010

1111
cliBrowser "github.com/cli/browser"
1212
"github.com/cli/go-gh/v2/pkg/config"
@@ -47,13 +47,20 @@ func (b *Browser) Browse(url string) error {
4747
}
4848

4949
func (b *Browser) browse(url string, env []string) error {
50-
if err := isPossibleProtocol(url); err != nil {
50+
// Ensure the URL is supported including the scheme,
51+
// overwrite `url` for use within the function.
52+
urlParsed, err := isPossibleProtocol(url)
53+
if err != nil {
5154
return err
5255
}
5356

57+
url = urlParsed.String()
58+
59+
// Use default `gh` browsing module for opening URL if not customized.
5460
if b.launcher == "" {
5561
return cliBrowser.OpenURL(url)
5662
}
63+
5764
launcherArgs, err := shlex.Split(b.launcher)
5865
if err != nil {
5966
return err
@@ -85,21 +92,29 @@ func resolveLauncher() string {
8592
return os.Getenv("BROWSER")
8693
}
8794

88-
func isSupportedProtocol(u string) bool {
89-
return strings.HasPrefix(u, "http:") ||
90-
strings.HasPrefix(u, "https:") ||
91-
strings.HasPrefix(u, "vscode:") ||
92-
strings.HasPrefix(u, "vscode-insiders:")
95+
func isSupportedScheme(scheme string) bool {
96+
switch scheme {
97+
case "http", "https", "vscode", "vscode-insiders":
98+
return true
99+
default:
100+
return false
101+
}
93102
}
94103

95-
func isPossibleProtocol(u string) error {
96-
if isSupportedProtocol(u) {
97-
return nil
104+
func isPossibleProtocol(u string) (*url.URL, error) {
105+
// Parse URL for known supported schemes before handling unknown cases.
106+
urlParsed, err := url.Parse(u)
107+
if err != nil {
108+
return nil, fmt.Errorf("opening unparsable URL is unsupported: %s", u)
109+
}
110+
111+
if isSupportedScheme(urlParsed.Scheme) {
112+
return urlParsed, nil
98113
}
99114

100-
// Disallow URLs using alternative `file:` protocol
101-
if strings.HasPrefix(u, "file:") {
102-
return fmt.Errorf("opening files or directories is unsupported: %s", u)
115+
// Disallow any unrecognized URL schemes if explicitly present.
116+
if urlParsed.Scheme != "" {
117+
return nil, fmt.Errorf("opening unsupport URL scheme: %s", u)
103118
}
104119

105120
// Disallow URLs that match existing files or directories on the filesystem
@@ -109,14 +124,16 @@ func isPossibleProtocol(u string) error {
109124
// Symlinks should not be resolved in order to avoid broken links or other
110125
// vulnerabilities trying to resolve them.
111126
if fileInfo, _ := os.Lstat(u); fileInfo != nil {
112-
return fmt.Errorf("opening files or directories is unsupported: %s", u)
127+
return nil, fmt.Errorf("opening files or directories is unsupported: %s", u)
113128
}
114129

115130
// Disallow URLs that match executables found in the user path.
116131
exec, _ := safeexec.LookPath(u)
117132
if exec != "" {
118-
return fmt.Errorf("opening executables is unsupported: %s", u)
133+
return nil, fmt.Errorf("opening executables is unsupported: %s", u)
119134
}
120135

121-
return nil
136+
// Otherwise, assume HTTP URL using `https` to ensure secure browsing.
137+
urlParsed.Scheme = "https"
138+
return urlParsed, nil
122139
}

pkg/browser/browser_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func TestBrowse(t *testing.T) {
6363
name: "Implicit `https` URL works",
6464
url: "github.com",
6565
launcher: fmt.Sprintf("%q -test.run=TestHelperProcess -- implicit https", os.Args[0]),
66-
expected: "[implicit https github.com]",
66+
expected: "[implicit https https://github.com]",
6767
},
6868
{
6969
name: "Explicit absolute `file://` URL errors",

0 commit comments

Comments
 (0)