Handle vanity redirect to godoc.org
Gddo assumes that the project URL for a vanity import is the page
containing the go-import meta tag for the vanity path.
This change sets the project URL to the resolved package project URL in
the case where the page uses an http-equiv meta tag to redirect the user
back to godoc.org.
Here's an example. The vanity import path "rsc.io/benchstat" resolves to
"gitbub.com/rsc/benchstat". The page at "http://rsc.io/benchstat" has an
http-equiv meta tag the refreshes to "https://godoc.org/rsc.io/benchstat".
Prior to this change, the project name in the gray banner at the top of
the package documentation page linked to "http://rsc.io/benchstat". The
page on rsc.io redirects the user back to the package documentation
page. Clicking the link is a noop.
After this change, the project name in the gray banner links to
https://github.com/rsc/benchstat".
This is a partial fix to issue #270. This change does not handle the
case where the vanity server responds with an HTTP redirect when
?go-get=1 query is not specified.
diff --git a/gosrc/gosrc.go b/gosrc/gosrc.go
index 82db4a0..9ae9fca 100644
--- a/gosrc/gosrc.go
+++ b/gosrc/gosrc.go
@@ -186,7 +186,7 @@
return ""
}
-func fetchMeta(client *http.Client, importPath string) (string, *importMeta, *sourceMeta, error) {
+func fetchMeta(client *http.Client, importPath string) (scheme string, im *importMeta, sm *sourceMeta, redir bool, err error) {
uri := importPath
if !strings.Contains(uri, "/") {
// Add slash for root of domain.
@@ -195,7 +195,7 @@
uri = uri + "?go-get=1"
c := httpClient{client: client}
- scheme := "https"
+ scheme = "https"
resp, err := c.get(scheme + "://" + uri)
if err != nil || resp.StatusCode != 200 {
if err == nil {
@@ -204,18 +204,17 @@
scheme = "http"
resp, err = c.get(scheme + "://" + uri)
if err != nil {
- return scheme, nil, nil, err
+ return scheme, nil, nil, false, err
}
}
defer resp.Body.Close()
- im, sm, err := parseMeta(scheme, importPath, resp.Body)
- return scheme, im, sm, err
+ im, sm, redir, err = parseMeta(scheme, importPath, resp.Body)
+ return scheme, im, sm, redir, err
}
-func parseMeta(scheme, importPath string, r io.Reader) (*importMeta, *sourceMeta, error) {
- var im *importMeta
- var sm *sourceMeta
+var refreshToGodocPat = regexp.MustCompile(`(?i)^\d+; url=https?://godoc\.org/`)
+func parseMeta(scheme, importPath string, r io.Reader) (im *importMeta, sm *sourceMeta, redir bool, err error) {
errorMessage := "go-import meta tag not found"
d := xml.NewDecoder(r)
@@ -238,6 +237,11 @@
if !strings.EqualFold(t.Name.Local, "meta") {
continue metaScan
}
+ if strings.EqualFold(attrValue(t.Attr, "http-equiv"), "refresh") {
+ // Check for http-equiv refresh back to godoc.org.
+ redir = refreshToGodocPat.MatchString(attrValue(t.Attr, "content"))
+ continue metaScan
+ }
nameAttr := attrValue(t.Attr, "name")
if nameAttr != "go-import" && nameAttr != "go-source" {
continue metaScan
@@ -287,12 +291,12 @@
}
}
if im == nil {
- return nil, nil, NotFoundError{Message: fmt.Sprintf("%s at %s://%s", errorMessage, scheme, importPath)}
+ return nil, nil, redir, NotFoundError{Message: fmt.Sprintf("%s at %s://%s", errorMessage, scheme, importPath)}
}
if sm != nil && sm.projectRoot != im.projectRoot {
sm = nil
}
- return im, sm, nil
+ return im, sm, redir, nil
}
// getVCSDirFn is called by getDynamic to fetch source using VCS commands. The
@@ -304,14 +308,14 @@
// getDynamic gets a directory from a service that is not statically known.
func getDynamic(client *http.Client, importPath, etag string) (*Directory, error) {
- metaProto, im, sm, err := fetchMeta(client, importPath)
+ metaProto, im, sm, redir, err := fetchMeta(client, importPath)
if err != nil {
return nil, err
}
if im.projectRoot != importPath {
var imRoot *importMeta
- metaProto, imRoot, _, err = fetchMeta(client, im.projectRoot)
+ metaProto, imRoot, _, redir, err = fetchMeta(client, im.projectRoot)
if err != nil {
return nil, err
}
@@ -350,7 +354,9 @@
dir.ProjectRoot = im.projectRoot
dir.ResolvedPath = resolvedPath
dir.ProjectName = path.Base(im.projectRoot)
- dir.ProjectURL = metaProto + "://" + im.projectRoot
+ if !redir {
+ dir.ProjectURL = metaProto + "://" + im.projectRoot
+ }
if sm == nil {
return dir, nil
diff --git a/gosrc/gosrc_test.go b/gosrc/gosrc_test.go
index 429cec4..84e81f3 100644
--- a/gosrc/gosrc_test.go
+++ b/gosrc/gosrc_test.go
@@ -64,6 +64,13 @@
"https://bob.com/pkg/source": `<head>` +
`<meta name="go-import" content="bob.com/pkg git https://vcs.net/bob/pkg.git">` +
`<meta name="go-source" content="bob.com/pkg http://bob.com/pkg http://bob.com/pkg{/dir}/ http://bob.com/pkg{/dir}/?f={file}#Line{line}">`,
+ // Meta refresh to godoc.org
+ "http://rsc.io/benchstat": `<head>` +
+ `<!DOCTYPE html><html><head>` +
+ `<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>` +
+ `<meta name="go-import" content="rsc.io/benchstat git https://github.com/rsc/benchstat">` +
+ `<meta http-equiv="refresh" content="0; url=https://godoc.org/rsc.io/benchstat">` +
+ `</head>`,
}
var getDynamicTests = []struct {
@@ -158,6 +165,17 @@
VCS: "git",
Files: []*File{{Name: "main.go", BrowseURL: "http://bob.com/pkg/source/?f=main.go"}},
}},
+ {"rsc.io/benchstat", &Directory{
+ BrowseURL: "https://github.com/rsc/benchstat",
+ ImportPath: "rsc.io/benchstat",
+ LineFmt: "%s#L%d",
+ ProjectName: "benchstat",
+ ProjectRoot: "rsc.io/benchstat",
+ ProjectURL: "https://github.com/rsc/benchstat",
+ ResolvedPath: "github.com/rsc/benchstat",
+ VCS: "git",
+ Files: []*File{{Name: "main.go", BrowseURL: "https://github.com/rsc/benchstat/blob/master/main.go"}},
+ }},
}
type testTransport map[string]string