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