1// Copyright 2013 Joshua Tacoma. 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 5// Package uritemplates is a level 3 implementation of RFC 6570 (URI 6// Template, http://tools.ietf.org/html/rfc6570). 7// uritemplates does not support composite values (in Go: slices or maps) 8// and so does not qualify as a level 4 implementation. 9package uritemplates 10 11import ( 12 "bytes" 13 "errors" 14 "regexp" 15 "strconv" 16 "strings" 17) 18 19var ( 20 unreserved = regexp.MustCompile("[^A-Za-z0-9\\-._~]") 21 reserved = regexp.MustCompile("[^A-Za-z0-9\\-._~:/?#[\\]@!$&'()*+,;=]") 22 validname = regexp.MustCompile("^([A-Za-z0-9_\\.]|%[0-9A-Fa-f][0-9A-Fa-f])+$") 23 hex = []byte("0123456789ABCDEF") 24) 25 26func pctEncode(src []byte) []byte { 27 dst := make([]byte, len(src)*3) 28 for i, b := range src { 29 buf := dst[i*3 : i*3+3] 30 buf[0] = 0x25 31 buf[1] = hex[b/16] 32 buf[2] = hex[b%16] 33 } 34 return dst 35} 36 37func escape(s string, allowReserved bool) string { 38 if allowReserved { 39 return string(reserved.ReplaceAllFunc([]byte(s), pctEncode)) 40 } 41 return string(unreserved.ReplaceAllFunc([]byte(s), pctEncode)) 42} 43 44// A uriTemplate is a parsed representation of a URI template. 45type uriTemplate struct { 46 raw string 47 parts []templatePart 48} 49 50// parse parses a URI template string into a uriTemplate object. 51func parse(rawTemplate string) (*uriTemplate, error) { 52 split := strings.Split(rawTemplate, "{") 53 parts := make([]templatePart, len(split)*2-1) 54 for i, s := range split { 55 if i == 0 { 56 if strings.Contains(s, "}") { 57 return nil, errors.New("unexpected }") 58 } 59 parts[i].raw = s 60 continue 61 } 62 subsplit := strings.Split(s, "}") 63 if len(subsplit) != 2 { 64 return nil, errors.New("malformed template") 65 } 66 expression := subsplit[0] 67 var err error 68 parts[i*2-1], err = parseExpression(expression) 69 if err != nil { 70 return nil, err 71 } 72 parts[i*2].raw = subsplit[1] 73 } 74 return &uriTemplate{ 75 raw: rawTemplate, 76 parts: parts, 77 }, nil 78} 79 80type templatePart struct { 81 raw string 82 terms []templateTerm 83 first string 84 sep string 85 named bool 86 ifemp string 87 allowReserved bool 88} 89 90type templateTerm struct { 91 name string 92 explode bool 93 truncate int 94} 95 96func parseExpression(expression string) (result templatePart, err error) { 97 switch expression[0] { 98 case '+': 99 result.sep = "," 100 result.allowReserved = true 101 expression = expression[1:] 102 case '.': 103 result.first = "." 104 result.sep = "." 105 expression = expression[1:] 106 case '/': 107 result.first = "/" 108 result.sep = "/" 109 expression = expression[1:] 110 case ';': 111 result.first = ";" 112 result.sep = ";" 113 result.named = true 114 expression = expression[1:] 115 case '?': 116 result.first = "?" 117 result.sep = "&" 118 result.named = true 119 result.ifemp = "=" 120 expression = expression[1:] 121 case '&': 122 result.first = "&" 123 result.sep = "&" 124 result.named = true 125 result.ifemp = "=" 126 expression = expression[1:] 127 case '#': 128 result.first = "#" 129 result.sep = "," 130 result.allowReserved = true 131 expression = expression[1:] 132 default: 133 result.sep = "," 134 } 135 rawterms := strings.Split(expression, ",") 136 result.terms = make([]templateTerm, len(rawterms)) 137 for i, raw := range rawterms { 138 result.terms[i], err = parseTerm(raw) 139 if err != nil { 140 break 141 } 142 } 143 return result, err 144} 145 146func parseTerm(term string) (result templateTerm, err error) { 147 // TODO(djd): Remove "*" suffix parsing once we check that no APIs have 148 // mistakenly used that attribute. 149 if strings.HasSuffix(term, "*") { 150 result.explode = true 151 term = term[:len(term)-1] 152 } 153 split := strings.Split(term, ":") 154 if len(split) == 1 { 155 result.name = term 156 } else if len(split) == 2 { 157 result.name = split[0] 158 var parsed int64 159 parsed, err = strconv.ParseInt(split[1], 10, 0) 160 result.truncate = int(parsed) 161 } else { 162 err = errors.New("multiple colons in same term") 163 } 164 if !validname.MatchString(result.name) { 165 err = errors.New("not a valid name: " + result.name) 166 } 167 if result.explode && result.truncate > 0 { 168 err = errors.New("both explode and prefix modifers on same term") 169 } 170 return result, err 171} 172 173// Expand expands a URI template with a set of values to produce a string. 174func (t *uriTemplate) Expand(values map[string]string) string { 175 var buf bytes.Buffer 176 for _, p := range t.parts { 177 p.expand(&buf, values) 178 } 179 return buf.String() 180} 181 182func (tp *templatePart) expand(buf *bytes.Buffer, values map[string]string) { 183 if len(tp.raw) > 0 { 184 buf.WriteString(tp.raw) 185 return 186 } 187 var first = true 188 for _, term := range tp.terms { 189 value, exists := values[term.name] 190 if !exists { 191 continue 192 } 193 if first { 194 buf.WriteString(tp.first) 195 first = false 196 } else { 197 buf.WriteString(tp.sep) 198 } 199 tp.expandString(buf, term, value) 200 } 201} 202 203func (tp *templatePart) expandName(buf *bytes.Buffer, name string, empty bool) { 204 if tp.named { 205 buf.WriteString(name) 206 if empty { 207 buf.WriteString(tp.ifemp) 208 } else { 209 buf.WriteString("=") 210 } 211 } 212} 213 214func (tp *templatePart) expandString(buf *bytes.Buffer, t templateTerm, s string) { 215 if len(s) > t.truncate && t.truncate > 0 { 216 s = s[:t.truncate] 217 } 218 tp.expandName(buf, t.name, len(s) == 0) 219 buf.WriteString(escape(s, tp.allowReserved)) 220} 221