mirror of https://github.com/dexidp/dex.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
276 lines
5.6 KiB
276 lines
5.6 KiB
// Copyright 2015-2019 Brett Vickers. |
|
// Use of this source code is governed by a BSD-style |
|
// license that can be found in the LICENSE file. |
|
|
|
package etree |
|
|
|
import ( |
|
"bufio" |
|
"io" |
|
"strings" |
|
"unicode/utf8" |
|
) |
|
|
|
// A simple stack |
|
type stack struct { |
|
data []interface{} |
|
} |
|
|
|
func (s *stack) empty() bool { |
|
return len(s.data) == 0 |
|
} |
|
|
|
func (s *stack) push(value interface{}) { |
|
s.data = append(s.data, value) |
|
} |
|
|
|
func (s *stack) pop() interface{} { |
|
value := s.data[len(s.data)-1] |
|
s.data[len(s.data)-1] = nil |
|
s.data = s.data[:len(s.data)-1] |
|
return value |
|
} |
|
|
|
func (s *stack) peek() interface{} { |
|
return s.data[len(s.data)-1] |
|
} |
|
|
|
// A fifo is a simple first-in-first-out queue. |
|
type fifo struct { |
|
data []interface{} |
|
head, tail int |
|
} |
|
|
|
func (f *fifo) add(value interface{}) { |
|
if f.len()+1 >= len(f.data) { |
|
f.grow() |
|
} |
|
f.data[f.tail] = value |
|
if f.tail++; f.tail == len(f.data) { |
|
f.tail = 0 |
|
} |
|
} |
|
|
|
func (f *fifo) remove() interface{} { |
|
value := f.data[f.head] |
|
f.data[f.head] = nil |
|
if f.head++; f.head == len(f.data) { |
|
f.head = 0 |
|
} |
|
return value |
|
} |
|
|
|
func (f *fifo) len() int { |
|
if f.tail >= f.head { |
|
return f.tail - f.head |
|
} |
|
return len(f.data) - f.head + f.tail |
|
} |
|
|
|
func (f *fifo) grow() { |
|
c := len(f.data) * 2 |
|
if c == 0 { |
|
c = 4 |
|
} |
|
buf, count := make([]interface{}, c), f.len() |
|
if f.tail >= f.head { |
|
copy(buf[0:count], f.data[f.head:f.tail]) |
|
} else { |
|
hindex := len(f.data) - f.head |
|
copy(buf[0:hindex], f.data[f.head:]) |
|
copy(buf[hindex:count], f.data[:f.tail]) |
|
} |
|
f.data, f.head, f.tail = buf, 0, count |
|
} |
|
|
|
// countReader implements a proxy reader that counts the number of |
|
// bytes read from its encapsulated reader. |
|
type countReader struct { |
|
r io.Reader |
|
bytes int64 |
|
} |
|
|
|
func newCountReader(r io.Reader) *countReader { |
|
return &countReader{r: r} |
|
} |
|
|
|
func (cr *countReader) Read(p []byte) (n int, err error) { |
|
b, err := cr.r.Read(p) |
|
cr.bytes += int64(b) |
|
return b, err |
|
} |
|
|
|
// countWriter implements a proxy writer that counts the number of |
|
// bytes written by its encapsulated writer. |
|
type countWriter struct { |
|
w io.Writer |
|
bytes int64 |
|
} |
|
|
|
func newCountWriter(w io.Writer) *countWriter { |
|
return &countWriter{w: w} |
|
} |
|
|
|
func (cw *countWriter) Write(p []byte) (n int, err error) { |
|
b, err := cw.w.Write(p) |
|
cw.bytes += int64(b) |
|
return b, err |
|
} |
|
|
|
// isWhitespace returns true if the byte slice contains only |
|
// whitespace characters. |
|
func isWhitespace(s string) bool { |
|
for i := 0; i < len(s); i++ { |
|
if c := s[i]; c != ' ' && c != '\t' && c != '\n' && c != '\r' { |
|
return false |
|
} |
|
} |
|
return true |
|
} |
|
|
|
// spaceMatch returns true if namespace a is the empty string |
|
// or if namespace a equals namespace b. |
|
func spaceMatch(a, b string) bool { |
|
switch { |
|
case a == "": |
|
return true |
|
default: |
|
return a == b |
|
} |
|
} |
|
|
|
// spaceDecompose breaks a namespace:tag identifier at the ':' |
|
// and returns the two parts. |
|
func spaceDecompose(str string) (space, key string) { |
|
colon := strings.IndexByte(str, ':') |
|
if colon == -1 { |
|
return "", str |
|
} |
|
return str[:colon], str[colon+1:] |
|
} |
|
|
|
// Strings used by indentCRLF and indentLF |
|
const ( |
|
indentSpaces = "\r\n " |
|
indentTabs = "\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t" |
|
) |
|
|
|
// indentCRLF returns a CRLF newline followed by n copies of the first |
|
// non-CRLF character in the source string. |
|
func indentCRLF(n int, source string) string { |
|
switch { |
|
case n < 0: |
|
return source[:2] |
|
case n < len(source)-1: |
|
return source[:n+2] |
|
default: |
|
return source + strings.Repeat(source[2:3], n-len(source)+2) |
|
} |
|
} |
|
|
|
// indentLF returns a LF newline followed by n copies of the first non-LF |
|
// character in the source string. |
|
func indentLF(n int, source string) string { |
|
switch { |
|
case n < 0: |
|
return source[1:2] |
|
case n < len(source)-1: |
|
return source[1 : n+2] |
|
default: |
|
return source[1:] + strings.Repeat(source[2:3], n-len(source)+2) |
|
} |
|
} |
|
|
|
// nextIndex returns the index of the next occurrence of sep in s, |
|
// starting from offset. It returns -1 if the sep string is not found. |
|
func nextIndex(s, sep string, offset int) int { |
|
switch i := strings.Index(s[offset:], sep); i { |
|
case -1: |
|
return -1 |
|
default: |
|
return offset + i |
|
} |
|
} |
|
|
|
// isInteger returns true if the string s contains an integer. |
|
func isInteger(s string) bool { |
|
for i := 0; i < len(s); i++ { |
|
if (s[i] < '0' || s[i] > '9') && !(i == 0 && s[i] == '-') { |
|
return false |
|
} |
|
} |
|
return true |
|
} |
|
|
|
type escapeMode byte |
|
|
|
const ( |
|
escapeNormal escapeMode = iota |
|
escapeCanonicalText |
|
escapeCanonicalAttr |
|
) |
|
|
|
// escapeString writes an escaped version of a string to the writer. |
|
func escapeString(w *bufio.Writer, s string, m escapeMode) { |
|
var esc []byte |
|
last := 0 |
|
for i := 0; i < len(s); { |
|
r, width := utf8.DecodeRuneInString(s[i:]) |
|
i += width |
|
switch r { |
|
case '&': |
|
esc = []byte("&") |
|
case '<': |
|
esc = []byte("<") |
|
case '>': |
|
if m == escapeCanonicalAttr { |
|
continue |
|
} |
|
esc = []byte(">") |
|
case '\'': |
|
if m != escapeNormal { |
|
continue |
|
} |
|
esc = []byte("'") |
|
case '"': |
|
if m == escapeCanonicalText { |
|
continue |
|
} |
|
esc = []byte(""") |
|
case '\t': |
|
if m != escapeCanonicalAttr { |
|
continue |
|
} |
|
esc = []byte("	") |
|
case '\n': |
|
if m != escapeCanonicalAttr { |
|
continue |
|
} |
|
esc = []byte("
") |
|
case '\r': |
|
if m == escapeNormal { |
|
continue |
|
} |
|
esc = []byte("
") |
|
default: |
|
if !isInCharacterRange(r) || (r == 0xFFFD && width == 1) { |
|
esc = []byte("\uFFFD") |
|
break |
|
} |
|
continue |
|
} |
|
w.WriteString(s[last : i-width]) |
|
w.Write(esc) |
|
last = i |
|
} |
|
w.WriteString(s[last:]) |
|
} |
|
|
|
func isInCharacterRange(r rune) bool { |
|
return r == 0x09 || |
|
r == 0x0A || |
|
r == 0x0D || |
|
r >= 0x20 && r <= 0xD7FF || |
|
r >= 0xE000 && r <= 0xFFFD || |
|
r >= 0x10000 && r <= 0x10FFFF |
|
}
|
|
|