mirror of https://github.com/hashicorp/consul
228 lines
6.6 KiB
Go
228 lines
6.6 KiB
Go
// Copyright 2016, Google Inc.
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are
|
|
// met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above
|
|
// copyright notice, this list of conditions and the following disclaimer
|
|
// in the documentation and/or other materials provided with the
|
|
// distribution.
|
|
// * Neither the name of Google Inc. nor the names of its
|
|
// contributors may be used to endorse or promote products derived from
|
|
// this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
package gax
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
)
|
|
|
|
// This parser follows the syntax of path templates, from
|
|
// https://github.com/googleapis/googleapis/blob/master/google/api/http.proto.
|
|
// The differences are that there is no custom verb, we allow the initial slash
|
|
// to be absent, and that we are not strict as
|
|
// https://tools.ietf.org/html/rfc6570 about the characters in identifiers and
|
|
// literals.
|
|
|
|
type pathTemplateParser struct {
|
|
r *strings.Reader
|
|
runeCount int // the number of the current rune in the original string
|
|
nextVar int // the number to use for the next unnamed variable
|
|
seenName map[string]bool // names we've seen already
|
|
seenPathWildcard bool // have we seen "**" already?
|
|
}
|
|
|
|
func parsePathTemplate(template string) (pt *PathTemplate, err error) {
|
|
p := &pathTemplateParser{
|
|
r: strings.NewReader(template),
|
|
seenName: map[string]bool{},
|
|
}
|
|
|
|
// Handle panics with strings like errors.
|
|
// See pathTemplateParser.error, below.
|
|
defer func() {
|
|
if x := recover(); x != nil {
|
|
errmsg, ok := x.(errString)
|
|
if !ok {
|
|
panic(x)
|
|
}
|
|
pt = nil
|
|
err = ParseError{p.runeCount, template, string(errmsg)}
|
|
}
|
|
}()
|
|
|
|
segs := p.template()
|
|
// If there is a path wildcard, set its length. We can't do this
|
|
// until we know how many segments we've got all together.
|
|
for i, seg := range segs {
|
|
if _, ok := seg.matcher.(pathWildcardMatcher); ok {
|
|
segs[i].matcher = pathWildcardMatcher(len(segs) - i - 1)
|
|
break
|
|
}
|
|
}
|
|
return &PathTemplate{segments: segs}, nil
|
|
|
|
}
|
|
|
|
// Used to indicate errors "thrown" by this parser. We don't use string because
|
|
// many parts of the standard library panic with strings.
|
|
type errString string
|
|
|
|
// Terminates parsing immediately with an error.
|
|
func (p *pathTemplateParser) error(msg string) {
|
|
panic(errString(msg))
|
|
}
|
|
|
|
// Template = [ "/" ] Segments
|
|
func (p *pathTemplateParser) template() []segment {
|
|
var segs []segment
|
|
if p.consume('/') {
|
|
// Initial '/' needs an initial empty matcher.
|
|
segs = append(segs, segment{matcher: labelMatcher("")})
|
|
}
|
|
return append(segs, p.segments("")...)
|
|
}
|
|
|
|
// Segments = Segment { "/" Segment }
|
|
func (p *pathTemplateParser) segments(name string) []segment {
|
|
var segs []segment
|
|
for {
|
|
subsegs := p.segment(name)
|
|
segs = append(segs, subsegs...)
|
|
if !p.consume('/') {
|
|
break
|
|
}
|
|
}
|
|
return segs
|
|
}
|
|
|
|
// Segment = "*" | "**" | LITERAL | Variable
|
|
func (p *pathTemplateParser) segment(name string) []segment {
|
|
if p.consume('*') {
|
|
if name == "" {
|
|
name = fmt.Sprintf("$%d", p.nextVar)
|
|
p.nextVar++
|
|
}
|
|
if p.consume('*') {
|
|
if p.seenPathWildcard {
|
|
p.error("multiple '**' disallowed")
|
|
}
|
|
p.seenPathWildcard = true
|
|
// We'll change 0 to the right number at the end.
|
|
return []segment{{name: name, matcher: pathWildcardMatcher(0)}}
|
|
}
|
|
return []segment{{name: name, matcher: wildcardMatcher(0)}}
|
|
}
|
|
if p.consume('{') {
|
|
if name != "" {
|
|
p.error("recursive named bindings are not allowed")
|
|
}
|
|
return p.variable()
|
|
}
|
|
return []segment{{name: name, matcher: labelMatcher(p.literal())}}
|
|
}
|
|
|
|
// Variable = "{" FieldPath [ "=" Segments ] "}"
|
|
// "{" is already consumed.
|
|
func (p *pathTemplateParser) variable() []segment {
|
|
// Simplification: treat FieldPath as LITERAL, instead of IDENT { '.' IDENT }
|
|
name := p.literal()
|
|
if p.seenName[name] {
|
|
p.error(name + " appears multiple times")
|
|
}
|
|
p.seenName[name] = true
|
|
var segs []segment
|
|
if p.consume('=') {
|
|
segs = p.segments(name)
|
|
} else {
|
|
// "{var}" is equivalent to "{var=*}"
|
|
segs = []segment{{name: name, matcher: wildcardMatcher(0)}}
|
|
}
|
|
if !p.consume('}') {
|
|
p.error("expected '}'")
|
|
}
|
|
return segs
|
|
}
|
|
|
|
// A literal is any sequence of characters other than a few special ones.
|
|
// The list of stop characters is not quite the same as in the template RFC.
|
|
func (p *pathTemplateParser) literal() string {
|
|
lit := p.consumeUntil("/*}{=")
|
|
if lit == "" {
|
|
p.error("empty literal")
|
|
}
|
|
return lit
|
|
}
|
|
|
|
// Read runes until EOF or one of the runes in stopRunes is encountered.
|
|
// If the latter, unread the stop rune. Return the accumulated runes as a string.
|
|
func (p *pathTemplateParser) consumeUntil(stopRunes string) string {
|
|
var runes []rune
|
|
for {
|
|
r, ok := p.readRune()
|
|
if !ok {
|
|
break
|
|
}
|
|
if strings.IndexRune(stopRunes, r) >= 0 {
|
|
p.unreadRune()
|
|
break
|
|
}
|
|
runes = append(runes, r)
|
|
}
|
|
return string(runes)
|
|
}
|
|
|
|
// If the next rune is r, consume it and return true.
|
|
// Otherwise, leave the input unchanged and return false.
|
|
func (p *pathTemplateParser) consume(r rune) bool {
|
|
rr, ok := p.readRune()
|
|
if !ok {
|
|
return false
|
|
}
|
|
if r == rr {
|
|
return true
|
|
}
|
|
p.unreadRune()
|
|
return false
|
|
}
|
|
|
|
// Read the next rune from the input. Return it.
|
|
// The second return value is false at EOF.
|
|
func (p *pathTemplateParser) readRune() (rune, bool) {
|
|
r, _, err := p.r.ReadRune()
|
|
if err == io.EOF {
|
|
return r, false
|
|
}
|
|
if err != nil {
|
|
p.error(err.Error())
|
|
}
|
|
p.runeCount++
|
|
return r, true
|
|
}
|
|
|
|
// Put the last rune that was read back on the input.
|
|
func (p *pathTemplateParser) unreadRune() {
|
|
if err := p.r.UnreadRune(); err != nil {
|
|
p.error(err.Error())
|
|
}
|
|
p.runeCount--
|
|
}
|