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