aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/google.golang.org/api/googleapi/internal/uritemplates/uritemplates.go
blob: 63bf0538301a19f4b77a02a515e8cfc5f710677f (plain) (tree)























































































































































































































































                                                                                       
// 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)
}