123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- // Copyright 2013 Joshua Tacoma. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- // Package uritemplates is a level 3 implementation of RFC 6570 (URI
- // Template, http://tools.ietf.org/html/rfc6570).
- // uritemplates does not support composite values (in Go: slices or maps)
- // and so does not qualify as a level 4 implementation.
- package uritemplates
- import (
- "bytes"
- "errors"
- "regexp"
- "strconv"
- "strings"
- )
- var (
- unreserved = regexp.MustCompile("[^A-Za-z0-9\\-._~]")
- reserved = regexp.MustCompile("[^A-Za-z0-9\\-._~:/?#[\\]@!$&'()*+,;=]")
- validname = regexp.MustCompile("^([A-Za-z0-9_\\.]|%[0-9A-Fa-f][0-9A-Fa-f])+$")
- hex = []byte("0123456789ABCDEF")
- )
- func pctEncode(src []byte) []byte {
- dst := make([]byte, len(src)*3)
- for i, b := range src {
- buf := dst[i*3 : i*3+3]
- buf[0] = 0x25
- buf[1] = hex[b/16]
- buf[2] = hex[b%16]
- }
- return dst
- }
- // pairWriter is a convenience struct which allows escaped and unescaped
- // versions of the template to be written in parallel.
- type pairWriter struct {
- escaped, unescaped bytes.Buffer
- }
- // Write writes the provided string directly without any escaping.
- func (w *pairWriter) Write(s string) {
- w.escaped.WriteString(s)
- w.unescaped.WriteString(s)
- }
- // Escape writes the provided string, escaping the string for the
- // escaped output.
- func (w *pairWriter) Escape(s string, allowReserved bool) {
- w.unescaped.WriteString(s)
- if allowReserved {
- w.escaped.Write(reserved.ReplaceAllFunc([]byte(s), pctEncode))
- } else {
- w.escaped.Write(unreserved.ReplaceAllFunc([]byte(s), pctEncode))
- }
- }
- // Escaped returns the escaped string.
- func (w *pairWriter) Escaped() string {
- return w.escaped.String()
- }
- // Unescaped returns the unescaped string.
- func (w *pairWriter) Unescaped() string {
- return w.unescaped.String()
- }
- // A uriTemplate is a parsed representation of a URI template.
- type uriTemplate struct {
- raw string
- parts []templatePart
- }
- // parse parses a URI template string into a uriTemplate object.
- func parse(rawTemplate string) (*uriTemplate, error) {
- split := strings.Split(rawTemplate, "{")
- parts := make([]templatePart, len(split)*2-1)
- for i, s := range split {
- if i == 0 {
- if strings.Contains(s, "}") {
- return nil, errors.New("unexpected }")
- }
- parts[i].raw = s
- continue
- }
- subsplit := strings.Split(s, "}")
- if len(subsplit) != 2 {
- return nil, errors.New("malformed template")
- }
- expression := subsplit[0]
- var err error
- parts[i*2-1], err = parseExpression(expression)
- if err != nil {
- return nil, err
- }
- parts[i*2].raw = subsplit[1]
- }
- return &uriTemplate{
- raw: rawTemplate,
- parts: parts,
- }, nil
- }
- type templatePart struct {
- raw string
- terms []templateTerm
- first string
- sep string
- named bool
- ifemp string
- allowReserved bool
- }
- type templateTerm struct {
- name string
- explode bool
- truncate int
- }
- func parseExpression(expression string) (result templatePart, err error) {
- switch expression[0] {
- case '+':
- result.sep = ","
- result.allowReserved = true
- expression = expression[1:]
- case '.':
- result.first = "."
- result.sep = "."
- expression = expression[1:]
- case '/':
- result.first = "/"
- result.sep = "/"
- expression = expression[1:]
- case ';':
- result.first = ";"
- result.sep = ";"
- result.named = true
- expression = expression[1:]
- case '?':
- result.first = "?"
- result.sep = "&"
- result.named = true
- result.ifemp = "="
- expression = expression[1:]
- case '&':
- result.first = "&"
- result.sep = "&"
- result.named = true
- result.ifemp = "="
- expression = expression[1:]
- case '#':
- result.first = "#"
- result.sep = ","
- result.allowReserved = true
- expression = expression[1:]
- default:
- result.sep = ","
- }
- rawterms := strings.Split(expression, ",")
- result.terms = make([]templateTerm, len(rawterms))
- for i, raw := range rawterms {
- result.terms[i], err = parseTerm(raw)
- if err != nil {
- break
- }
- }
- return result, err
- }
- func parseTerm(term string) (result templateTerm, err error) {
- // TODO(djd): Remove "*" suffix parsing once we check that no APIs have
- // mistakenly used that attribute.
- if strings.HasSuffix(term, "*") {
- result.explode = true
- term = term[:len(term)-1]
- }
- split := strings.Split(term, ":")
- if len(split) == 1 {
- result.name = term
- } else if len(split) == 2 {
- result.name = split[0]
- var parsed int64
- parsed, err = strconv.ParseInt(split[1], 10, 0)
- result.truncate = int(parsed)
- } else {
- err = errors.New("multiple colons in same term")
- }
- if !validname.MatchString(result.name) {
- err = errors.New("not a valid name: " + result.name)
- }
- if result.explode && result.truncate > 0 {
- err = errors.New("both explode and prefix modifers on same term")
- }
- return result, err
- }
- // Expand expands a URI template with a set of values to produce the
- // resultant URI. Two forms of the result are returned: one with all the
- // elements escaped, and one with the elements unescaped.
- func (t *uriTemplate) Expand(values map[string]string) (escaped, unescaped string) {
- var w pairWriter
- for _, p := range t.parts {
- p.expand(&w, values)
- }
- return w.Escaped(), w.Unescaped()
- }
- func (tp *templatePart) expand(w *pairWriter, values map[string]string) {
- if len(tp.raw) > 0 {
- w.Write(tp.raw)
- return
- }
- var first = true
- for _, term := range tp.terms {
- value, exists := values[term.name]
- if !exists {
- continue
- }
- if first {
- w.Write(tp.first)
- first = false
- } else {
- w.Write(tp.sep)
- }
- tp.expandString(w, term, value)
- }
- }
- func (tp *templatePart) expandName(w *pairWriter, name string, empty bool) {
- if tp.named {
- w.Write(name)
- if empty {
- w.Write(tp.ifemp)
- } else {
- w.Write("=")
- }
- }
- }
- func (tp *templatePart) expandString(w *pairWriter, t templateTerm, s string) {
- if len(s) > t.truncate && t.truncate > 0 {
- s = s[:t.truncate]
- }
- tp.expandName(w, t.name, len(s) == 0)
- w.Escape(s, tp.allowReserved)
- }
|