1// Copyright 2012 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package vcs
6
7import (
8	"encoding/xml"
9	"fmt"
10	"io"
11	"strings"
12)
13
14// charsetReader returns a reader that converts from the given charset to UTF-8.
15// Currently it only supports UTF-8 and ASCII. Otherwise, it returns a meaningful
16// error which is printed by go get, so the user can find why the package
17// wasn't downloaded if the encoding is not supported. Note that, in
18// order to reduce potential errors, ASCII is treated as UTF-8 (i.e. characters
19// greater than 0x7f are not rejected).
20func charsetReader(charset string, input io.Reader) (io.Reader, error) {
21	switch strings.ToLower(charset) {
22	case "utf-8", "ascii":
23		return input, nil
24	default:
25		return nil, fmt.Errorf("can't decode XML document using charset %q", charset)
26	}
27}
28
29// parseMetaGoImports returns meta imports from the HTML in r.
30// Parsing ends at the end of the <head> section or the beginning of the <body>.
31func parseMetaGoImports(r io.Reader, mod ModuleMode) ([]metaImport, error) {
32	d := xml.NewDecoder(r)
33	d.CharsetReader = charsetReader
34	d.Strict = false
35	var imports []metaImport
36	for {
37		t, err := d.RawToken()
38		if err != nil {
39			if err != io.EOF && len(imports) == 0 {
40				return nil, err
41			}
42			break
43		}
44		if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") {
45			break
46		}
47		if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") {
48			break
49		}
50		e, ok := t.(xml.StartElement)
51		if !ok || !strings.EqualFold(e.Name.Local, "meta") {
52			continue
53		}
54		if attrValue(e.Attr, "name") != "go-import" {
55			continue
56		}
57		if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 3 {
58			imports = append(imports, metaImport{
59				Prefix:   f[0],
60				VCS:      f[1],
61				RepoRoot: f[2],
62			})
63		}
64	}
65
66	// Extract mod entries if we are paying attention to them.
67	var list []metaImport
68	var have map[string]bool
69	if mod == PreferMod {
70		have = make(map[string]bool)
71		for _, m := range imports {
72			if m.VCS == "mod" {
73				have[m.Prefix] = true
74				list = append(list, m)
75			}
76		}
77	}
78
79	// Append non-mod entries, ignoring those superseded by a mod entry.
80	for _, m := range imports {
81		if m.VCS != "mod" && !have[m.Prefix] {
82			list = append(list, m)
83		}
84	}
85	return list, nil
86}
87
88// attrValue returns the attribute value for the case-insensitive key
89// `name', or the empty string if nothing is found.
90func attrValue(attrs []xml.Attr, name string) string {
91	for _, a := range attrs {
92		if strings.EqualFold(a.Name.Local, name) {
93			return a.Value
94		}
95	}
96	return ""
97}
98