p1: /v2/scrape, /v1/fetch, auth, CLI, contract tests
Single Fetcher.Fetch on the Guard that holds DenyPrefixes. skipTlsVerification is ignored. No 502. keys add writes the secret only to --out. Release binary has no -test-allow-prefix.
This commit is contained in:
+578
@@ -0,0 +1,578 @@
|
||||
package gojq
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type lexer struct {
|
||||
source string
|
||||
offset int
|
||||
result *Query
|
||||
token string
|
||||
tokenType int
|
||||
inString bool
|
||||
err error
|
||||
}
|
||||
|
||||
func newLexer(src string) *lexer {
|
||||
return &lexer{source: src}
|
||||
}
|
||||
|
||||
const eof = -1
|
||||
|
||||
var keywords = map[string]int{
|
||||
"or": tokOrOp,
|
||||
"and": tokAndOp,
|
||||
"module": tokModule,
|
||||
"import": tokImport,
|
||||
"include": tokInclude,
|
||||
"def": tokDef,
|
||||
"as": tokAs,
|
||||
"label": tokLabel,
|
||||
"break": tokBreak,
|
||||
"null": tokNull,
|
||||
"true": tokTrue,
|
||||
"false": tokFalse,
|
||||
"if": tokIf,
|
||||
"then": tokThen,
|
||||
"elif": tokElif,
|
||||
"else": tokElse,
|
||||
"end": tokEnd,
|
||||
"try": tokTry,
|
||||
"catch": tokCatch,
|
||||
"reduce": tokReduce,
|
||||
"foreach": tokForeach,
|
||||
}
|
||||
|
||||
func (l *lexer) Lex(lval *yySymType) (tokenType int) {
|
||||
defer func() { l.tokenType = tokenType }()
|
||||
if len(l.source) == l.offset {
|
||||
l.token = ""
|
||||
return eof
|
||||
}
|
||||
if l.inString {
|
||||
tok, str := l.scanString(l.offset)
|
||||
lval.token = str
|
||||
return tok
|
||||
}
|
||||
ch, iseof := l.next()
|
||||
if iseof {
|
||||
l.token = ""
|
||||
return eof
|
||||
}
|
||||
switch {
|
||||
case isIdent(ch, false):
|
||||
i := l.offset - 1
|
||||
j, isModule := l.scanIdentOrModule()
|
||||
l.token = l.source[i:j]
|
||||
lval.token = l.token
|
||||
if isModule {
|
||||
return tokModuleIdent
|
||||
}
|
||||
if tok, ok := keywords[l.token]; ok {
|
||||
return tok
|
||||
}
|
||||
return tokIdent
|
||||
case isNumber(ch):
|
||||
i := l.offset - 1
|
||||
j := l.scanNumber(numberStateLead)
|
||||
if j < 0 {
|
||||
l.token = l.source[i:-j]
|
||||
return tokInvalid
|
||||
}
|
||||
l.token = l.source[i:j]
|
||||
lval.token = l.token
|
||||
return tokNumber
|
||||
}
|
||||
switch ch {
|
||||
case '.':
|
||||
ch := l.peek()
|
||||
switch {
|
||||
case ch == '.':
|
||||
l.offset++
|
||||
l.token = ".."
|
||||
return tokRecurse
|
||||
case isIdent(ch, false):
|
||||
l.token = l.source[l.offset-1 : l.scanIdent()]
|
||||
lval.token = l.token[1:]
|
||||
return tokIndex
|
||||
case isNumber(ch):
|
||||
i := l.offset - 1
|
||||
j := l.scanNumber(numberStateFloat)
|
||||
if j < 0 {
|
||||
l.token = l.source[i:-j]
|
||||
return tokInvalid
|
||||
}
|
||||
l.token = l.source[i:j]
|
||||
lval.token = l.token
|
||||
return tokNumber
|
||||
default:
|
||||
return '.'
|
||||
}
|
||||
case '$':
|
||||
if isIdent(l.peek(), false) {
|
||||
i := l.offset - 1
|
||||
j, isModule := l.scanIdentOrModule()
|
||||
l.token = l.source[i:j]
|
||||
lval.token = l.token
|
||||
if isModule {
|
||||
return tokModuleVariable
|
||||
}
|
||||
return tokVariable
|
||||
}
|
||||
case '|':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "|="
|
||||
lval.operator = OpModify
|
||||
return tokUpdateOp
|
||||
}
|
||||
case '?':
|
||||
if l.peek() == '/' {
|
||||
l.offset++
|
||||
if l.peek() == '/' {
|
||||
l.offset++
|
||||
l.token = "?//"
|
||||
return tokDestAltOp
|
||||
}
|
||||
l.offset--
|
||||
}
|
||||
case '+':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "+="
|
||||
lval.operator = OpUpdateAdd
|
||||
return tokUpdateOp
|
||||
}
|
||||
case '-':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "-="
|
||||
lval.operator = OpUpdateSub
|
||||
return tokUpdateOp
|
||||
}
|
||||
case '*':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "*="
|
||||
lval.operator = OpUpdateMul
|
||||
return tokUpdateOp
|
||||
}
|
||||
case '/':
|
||||
switch l.peek() {
|
||||
case '=':
|
||||
l.offset++
|
||||
l.token = "/="
|
||||
lval.operator = OpUpdateDiv
|
||||
return tokUpdateOp
|
||||
case '/':
|
||||
l.offset++
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "//="
|
||||
lval.operator = OpUpdateAlt
|
||||
return tokUpdateOp
|
||||
}
|
||||
l.token = "//"
|
||||
lval.operator = OpAlt
|
||||
return tokAltOp
|
||||
}
|
||||
case '%':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "%="
|
||||
lval.operator = OpUpdateMod
|
||||
return tokUpdateOp
|
||||
}
|
||||
case '=':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "=="
|
||||
lval.operator = OpEq
|
||||
return tokCompareOp
|
||||
}
|
||||
l.token = "="
|
||||
lval.operator = OpAssign
|
||||
return tokUpdateOp
|
||||
case '!':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "!="
|
||||
lval.operator = OpNe
|
||||
return tokCompareOp
|
||||
}
|
||||
case '>':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = ">="
|
||||
lval.operator = OpGe
|
||||
return tokCompareOp
|
||||
}
|
||||
l.token = ">"
|
||||
lval.operator = OpGt
|
||||
return tokCompareOp
|
||||
case '<':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "<="
|
||||
lval.operator = OpLe
|
||||
return tokCompareOp
|
||||
}
|
||||
l.token = "<"
|
||||
lval.operator = OpLt
|
||||
return tokCompareOp
|
||||
case '@':
|
||||
if isIdent(l.peek(), true) {
|
||||
l.token = l.source[l.offset-1 : l.scanIdent()]
|
||||
lval.token = l.token
|
||||
return tokFormat
|
||||
}
|
||||
case '"':
|
||||
tok, str := l.scanString(l.offset - 1)
|
||||
lval.token = str
|
||||
return tok
|
||||
default:
|
||||
if ch >= utf8.RuneSelf {
|
||||
r, size := utf8.DecodeRuneInString(l.source[l.offset-1:])
|
||||
// -1 to adjust for first byte consumed by next()
|
||||
l.offset += size - 1
|
||||
l.token = string(r)
|
||||
}
|
||||
}
|
||||
return int(ch)
|
||||
}
|
||||
|
||||
func (l *lexer) next() (byte, bool) {
|
||||
for {
|
||||
ch := l.source[l.offset]
|
||||
l.offset++
|
||||
if ch == '#' {
|
||||
if l.skipComment() {
|
||||
return 0, true
|
||||
}
|
||||
} else if !isWhite(ch) {
|
||||
return ch, false
|
||||
} else if len(l.source) == l.offset {
|
||||
return 0, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) skipComment() bool {
|
||||
for {
|
||||
switch l.peek() {
|
||||
case 0:
|
||||
return true
|
||||
case '\\':
|
||||
switch l.offset++; l.peek() {
|
||||
case '\\', '\n':
|
||||
l.offset++
|
||||
case '\r':
|
||||
if l.offset++; l.peek() == '\n' {
|
||||
l.offset++
|
||||
}
|
||||
}
|
||||
case '\n', '\r':
|
||||
return false
|
||||
default:
|
||||
l.offset++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) peek() byte {
|
||||
if len(l.source) == l.offset {
|
||||
return 0
|
||||
}
|
||||
return l.source[l.offset]
|
||||
}
|
||||
|
||||
func (l *lexer) scanIdent() int {
|
||||
for isIdent(l.peek(), true) {
|
||||
l.offset++
|
||||
}
|
||||
return l.offset
|
||||
}
|
||||
|
||||
func (l *lexer) scanIdentOrModule() (int, bool) {
|
||||
index := l.scanIdent()
|
||||
var isModule bool
|
||||
if l.peek() == ':' {
|
||||
l.offset++
|
||||
if l.peek() == ':' {
|
||||
l.offset++
|
||||
if isIdent(l.peek(), false) {
|
||||
l.offset++
|
||||
index = l.scanIdent()
|
||||
isModule = true
|
||||
} else {
|
||||
l.offset -= 2
|
||||
}
|
||||
} else {
|
||||
l.offset--
|
||||
}
|
||||
}
|
||||
return index, isModule
|
||||
}
|
||||
|
||||
func (l *lexer) validVarName() bool {
|
||||
if l.peek() != '$' {
|
||||
return false
|
||||
}
|
||||
l.offset++
|
||||
return isIdent(l.peek(), false) && l.scanIdent() == len(l.source)
|
||||
}
|
||||
|
||||
const (
|
||||
numberStateLead = iota
|
||||
numberStateFloat
|
||||
numberStateExpLead
|
||||
numberStateExp
|
||||
)
|
||||
|
||||
func (l *lexer) scanNumber(state int) int {
|
||||
for {
|
||||
switch state {
|
||||
case numberStateLead, numberStateFloat:
|
||||
if ch := l.peek(); isNumber(ch) {
|
||||
l.offset++
|
||||
} else {
|
||||
switch ch {
|
||||
case '.':
|
||||
if state != numberStateLead {
|
||||
l.offset++
|
||||
return -l.offset
|
||||
}
|
||||
l.offset++
|
||||
state = numberStateFloat
|
||||
case 'e', 'E':
|
||||
l.offset++
|
||||
switch l.peek() {
|
||||
case '-', '+':
|
||||
l.offset++
|
||||
}
|
||||
state = numberStateExpLead
|
||||
default:
|
||||
if isIdent(ch, false) {
|
||||
l.offset++
|
||||
return -l.offset
|
||||
}
|
||||
return l.offset
|
||||
}
|
||||
}
|
||||
case numberStateExpLead, numberStateExp:
|
||||
if ch := l.peek(); !isNumber(ch) {
|
||||
if isIdent(ch, false) {
|
||||
l.offset++
|
||||
return -l.offset
|
||||
}
|
||||
if state == numberStateExpLead {
|
||||
return -l.offset
|
||||
}
|
||||
return l.offset
|
||||
}
|
||||
l.offset++
|
||||
state = numberStateExp
|
||||
default:
|
||||
panic(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) validNumber() bool {
|
||||
ch := l.peek()
|
||||
switch ch {
|
||||
case '+', '-':
|
||||
l.offset++
|
||||
ch = l.peek()
|
||||
}
|
||||
state := numberStateLead
|
||||
if ch == '.' {
|
||||
l.offset++
|
||||
ch = l.peek()
|
||||
state = numberStateFloat
|
||||
}
|
||||
return isNumber(ch) && l.scanNumber(state) == len(l.source)
|
||||
}
|
||||
|
||||
func (l *lexer) scanString(start int) (int, string) {
|
||||
var decode bool
|
||||
var controls int
|
||||
unquote := func(src string, quote bool) (string, error) {
|
||||
if !decode {
|
||||
if quote {
|
||||
return src, nil
|
||||
}
|
||||
return src[1 : len(src)-1], nil
|
||||
}
|
||||
var buf []byte
|
||||
if !quote && controls == 0 {
|
||||
buf = []byte(src)
|
||||
} else {
|
||||
buf = quoteAndEscape(src, quote, controls)
|
||||
}
|
||||
if err := json.Unmarshal(buf, &src); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return src, nil
|
||||
}
|
||||
for i := l.offset; i < len(l.source); i++ {
|
||||
ch := l.source[i]
|
||||
switch ch {
|
||||
case '\\':
|
||||
if i++; i >= len(l.source) {
|
||||
break
|
||||
}
|
||||
switch l.source[i] {
|
||||
case 'u':
|
||||
for j := 1; j <= 4; j++ {
|
||||
if i+j >= len(l.source) || !isHex(l.source[i+j]) {
|
||||
l.offset = i + j
|
||||
l.token = l.source[i-1 : l.offset]
|
||||
return tokInvalidEscapeSequence, ""
|
||||
}
|
||||
}
|
||||
i += 4
|
||||
fallthrough
|
||||
case '"', '/', '\\', 'b', 'f', 'n', 'r', 't':
|
||||
decode = true
|
||||
case '(':
|
||||
if !l.inString {
|
||||
l.inString = true
|
||||
return tokStringStart, ""
|
||||
}
|
||||
if i == l.offset+1 {
|
||||
l.offset += 2
|
||||
l.inString = false
|
||||
return tokStringQuery, ""
|
||||
}
|
||||
l.offset = i - 1
|
||||
l.token = l.source[start:l.offset]
|
||||
str, err := unquote(l.token, true)
|
||||
if err != nil {
|
||||
return tokInvalid, ""
|
||||
}
|
||||
return tokString, str
|
||||
default:
|
||||
l.offset = i + 1
|
||||
l.token = l.source[l.offset-2 : l.offset]
|
||||
return tokInvalidEscapeSequence, ""
|
||||
}
|
||||
case '"':
|
||||
if !l.inString {
|
||||
l.offset = i + 1
|
||||
l.token = l.source[start:l.offset]
|
||||
str, err := unquote(l.token, false)
|
||||
if err != nil {
|
||||
return tokInvalid, ""
|
||||
}
|
||||
return tokString, str
|
||||
}
|
||||
if i > l.offset {
|
||||
l.offset = i
|
||||
l.token = l.source[start:l.offset]
|
||||
str, err := unquote(l.token, true)
|
||||
if err != nil {
|
||||
return tokInvalid, ""
|
||||
}
|
||||
return tokString, str
|
||||
}
|
||||
l.inString = false
|
||||
l.offset = i + 1
|
||||
return tokStringEnd, ""
|
||||
default:
|
||||
if !decode {
|
||||
decode = ch > '~'
|
||||
}
|
||||
if ch < ' ' { // ref: unquoteBytes in encoding/json
|
||||
controls++
|
||||
}
|
||||
}
|
||||
}
|
||||
l.offset = len(l.source)
|
||||
l.token = ""
|
||||
return tokUnterminatedString, ""
|
||||
}
|
||||
|
||||
func quoteAndEscape(src string, quote bool, controls int) []byte {
|
||||
size := len(src) + controls*5
|
||||
if quote {
|
||||
size += 2
|
||||
}
|
||||
buf := make([]byte, size)
|
||||
var j int
|
||||
if quote {
|
||||
buf[0] = '"'
|
||||
buf[len(buf)-1] = '"'
|
||||
j++
|
||||
}
|
||||
for i := range len(src) {
|
||||
if ch := src[i]; ch < ' ' {
|
||||
const hex = "0123456789abcdef"
|
||||
copy(buf[j:], `\u00`)
|
||||
buf[j+4] = hex[ch>>4]
|
||||
buf[j+5] = hex[ch&0xF]
|
||||
j += 6
|
||||
} else {
|
||||
buf[j] = ch
|
||||
j++
|
||||
}
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
// ParseError represents a description of a query parsing error.
|
||||
type ParseError struct {
|
||||
Offset int // the error occurred after reading Offset bytes
|
||||
Token string // the Token that caused the error (may be empty)
|
||||
tokenType int
|
||||
}
|
||||
|
||||
func (err *ParseError) Error() string {
|
||||
switch err.tokenType {
|
||||
case eof:
|
||||
return "unexpected EOF"
|
||||
case tokInvalid:
|
||||
return "invalid token " + jsonMarshal(err.Token)
|
||||
case tokInvalidEscapeSequence:
|
||||
return `invalid escape sequence "` + err.Token + `" in string literal`
|
||||
case tokUnterminatedString:
|
||||
return "unterminated string literal"
|
||||
default:
|
||||
return "unexpected token " + jsonMarshal(err.Token)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) Error(string) {
|
||||
offset, token := l.offset, l.token
|
||||
if l.tokenType != eof && l.tokenType < utf8.RuneSelf {
|
||||
token = string(rune(l.tokenType))
|
||||
}
|
||||
l.err = &ParseError{offset, token, l.tokenType}
|
||||
}
|
||||
|
||||
func isWhite(ch byte) bool {
|
||||
switch ch {
|
||||
case '\t', '\n', '\r', ' ':
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isIdent(ch byte, tail bool) bool {
|
||||
return 'a' <= ch && ch <= 'z' ||
|
||||
'A' <= ch && ch <= 'Z' || ch == '_' ||
|
||||
tail && isNumber(ch)
|
||||
}
|
||||
|
||||
func isHex(ch byte) bool {
|
||||
return 'a' <= ch && ch <= 'f' ||
|
||||
'A' <= ch && ch <= 'F' ||
|
||||
isNumber(ch)
|
||||
}
|
||||
|
||||
func isNumber(ch byte) bool {
|
||||
return '0' <= ch && ch <= '9'
|
||||
}
|
||||
Reference in New Issue
Block a user