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