github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/pvl/helpers.go (about) 1 // Copyright 2016 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package pvl 5 6 import ( 7 "net" 8 "net/url" 9 "regexp" 10 "strings" 11 12 "github.com/PuerkitoBio/goquery" 13 libkb "github.com/keybase/client/go/libkb" 14 keybase1 "github.com/keybase/client/go/protocol/keybase1" 15 ) 16 17 // Substitute register values for %{name} in the string. 18 // Regex-escape variable values 19 func substituteReEscape(template string, state scriptState) (string, libkb.ProofError) { 20 return substituteInner(template, state, true) 21 } 22 23 // Substitute register values for %{name} in the string. 24 // Does not escape register values 25 func substituteExact(template string, state scriptState) (string, libkb.ProofError) { 26 return substituteInner(template, state, false) 27 } 28 29 var nameRE = regexp.MustCompile(`%\{[\w]*\}`) 30 31 func substituteInner(template string, state scriptState, regexEscape bool) (string, libkb.ProofError) { 32 var outerr libkb.ProofError 33 // Regex to find %{name} occurrences. 34 // Match broadly here so that even %{} is sent to the default case and reported as invalid. 35 substituteOne := func(vartag string) string { 36 // Strip off the %, {, and } 37 varname := vartag[2 : len(vartag)-1] 38 value, err := state.Regs.Get(varname) 39 if err != nil { 40 outerr = libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 41 "Invalid substitution: %v", err) 42 return "" 43 } 44 if regexEscape { 45 return regexp.QuoteMeta(value) 46 } 47 return value 48 } 49 res := nameRE.ReplaceAllStringFunc(template, substituteOne) 50 if outerr != nil { 51 return template, outerr 52 } 53 return res, nil 54 } 55 56 func serviceToString(service keybase1.ProofType) (string, libkb.ProofError) { 57 for name, stat := range keybase1.ProofTypeMap { 58 if service == stat { 59 return strings.ToLower(name), nil 60 } 61 } 62 63 return "", libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, "Unsupported service %v", service) 64 } 65 66 // selectionText gets the Text of all elements in a selection, concatenated by a space. 67 // The result can be an empty string. 68 func selectionText(selection *goquery.Selection) string { 69 var results []string 70 selection.Each(func(i int, element *goquery.Selection) { 71 results = append(results, element.Text()) 72 }) 73 return strings.Join(results, " ") 74 } 75 76 // selectionAttr gets the specified attr of all elements in a selection, concatenated by a space. 77 // If getting the attr of any elements fails, that does not cause an error. 78 // The result can be an empty string. 79 func selectionAttr(selection *goquery.Selection, attr string) string { 80 var results []string 81 selection.Each(func(i int, element *goquery.Selection) { 82 res, ok := element.Attr(attr) 83 if ok { 84 results = append(results, res) 85 } 86 }) 87 return strings.Join(results, " ") 88 } 89 90 // selectionData gets the first node's data of all elements in a selection, concatenated by a space. 91 // The result can be an empty string. 92 func selectionData(selection *goquery.Selection) string { 93 var results []string 94 selection.Each(func(i int, element *goquery.Selection) { 95 if len(element.Nodes) > 0 { 96 results = append(results, element.Nodes[0].Data) 97 } 98 }) 99 return strings.Join(results, " ") 100 } 101 102 func stringsContains(xs []string, x string) bool { 103 for _, y := range xs { 104 if x == y { 105 return true 106 } 107 } 108 return false 109 } 110 111 var hasalpha = regexp.MustCompile(`\D`) 112 113 // Check that a url is valid and has only a domain and is not an ip. 114 // No port, path, protocol, user, query, or any other junk is allowed. 115 func validateDomain(s string) bool { 116 // Throw a protocol in front because the parser wants one. 117 proto := "http" 118 u, err := url.Parse(proto + "://" + s) 119 if err != nil { 120 return false 121 } 122 123 // The final group must include a non-numeric character. 124 // To disallow the likes of "8.8.8.8." 125 dotsplit := strings.Split(strings.TrimSuffix(u.Host, "."), ".") 126 if len(dotsplit) > 0 { 127 group := dotsplit[len(dotsplit)-1] 128 if !hasalpha.MatchString(group) { 129 return false 130 } 131 } 132 133 ok := (u.IsAbs()) && 134 (u.Scheme == proto) && 135 (u.User == nil) && 136 (u.Path == "") && 137 (u.RawPath == "") && 138 (u.RawQuery == "") && 139 (u.Fragment == "") && 140 // Disallow colons. So no port, and no ipv6. 141 (!strings.Contains(u.Host, ":")) && 142 // Disallow any valid ip addresses. 143 (net.ParseIP(u.Host) == nil) 144 return ok 145 } 146 147 // validateProtocol takes a protocol and returns the canonicalized form and whether it is valid. 148 func validateProtocol(s string, allowed []string) (string, bool) { 149 canons := map[string]string{ 150 "http": "http", 151 "https": "https", 152 "dns": "dns", 153 "http:": "http", 154 "https:": "https", 155 "dns:": "dns", 156 "http://": "http", 157 "https://": "https", 158 "dns://": "dns", 159 } 160 161 canon, ok := canons[s] 162 if ok { 163 return canon, stringsContains(allowed, canon) 164 } 165 return canon, false 166 } 167 168 func rooterRewriteURL(m metaContext, s string) (string, error) { 169 u1, err := url.Parse(s) 170 if err != nil { 171 return "", err 172 } 173 174 serverURI, err := m.G().GetServerURI() 175 if err != nil { 176 return "", nil 177 } 178 179 u2, err := url.Parse(serverURI) 180 if err != nil { 181 return "", err 182 } 183 184 u3 := url.URL{ 185 Host: u2.Host, 186 Scheme: u2.Scheme, 187 Path: u1.Path, 188 Fragment: u1.Fragment, 189 } 190 191 return u3.String(), nil 192 }