| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 | // 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 uritemplatesimport (	"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)}
 |