diff options
Diffstat (limited to 'vendor/github.com/go-logfmt/logfmt')
-rw-r--r-- | vendor/github.com/go-logfmt/logfmt/.gitignore | 4 | ||||
-rw-r--r-- | vendor/github.com/go-logfmt/logfmt/.travis.yml | 15 | ||||
-rw-r--r-- | vendor/github.com/go-logfmt/logfmt/LICENSE | 22 | ||||
-rw-r--r-- | vendor/github.com/go-logfmt/logfmt/README.md | 33 | ||||
-rw-r--r-- | vendor/github.com/go-logfmt/logfmt/decode.go | 237 | ||||
-rw-r--r-- | vendor/github.com/go-logfmt/logfmt/doc.go | 6 | ||||
-rw-r--r-- | vendor/github.com/go-logfmt/logfmt/encode.go | 321 | ||||
-rw-r--r-- | vendor/github.com/go-logfmt/logfmt/fuzz.go | 126 | ||||
-rw-r--r-- | vendor/github.com/go-logfmt/logfmt/jsonstring.go | 277 |
9 files changed, 1041 insertions, 0 deletions
diff --git a/vendor/github.com/go-logfmt/logfmt/.gitignore b/vendor/github.com/go-logfmt/logfmt/.gitignore new file mode 100644 index 0000000..320e53e --- /dev/null +++ b/vendor/github.com/go-logfmt/logfmt/.gitignore @@ -0,0 +1,4 @@ +_testdata/ +_testdata2/ +logfmt-fuzz.zip +logfmt.test.exe diff --git a/vendor/github.com/go-logfmt/logfmt/.travis.yml b/vendor/github.com/go-logfmt/logfmt/.travis.yml new file mode 100644 index 0000000..b599f65 --- /dev/null +++ b/vendor/github.com/go-logfmt/logfmt/.travis.yml @@ -0,0 +1,15 @@ +language: go +sudo: false +go: + - 1.3 + - 1.4 + - 1.5 + - 1.6 + - tip + +before_install: + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover + +script: + - goveralls -service=travis-ci diff --git a/vendor/github.com/go-logfmt/logfmt/LICENSE b/vendor/github.com/go-logfmt/logfmt/LICENSE new file mode 100644 index 0000000..c026508 --- /dev/null +++ b/vendor/github.com/go-logfmt/logfmt/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 go-logfmt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/go-logfmt/logfmt/README.md b/vendor/github.com/go-logfmt/logfmt/README.md new file mode 100644 index 0000000..3a8f10b --- /dev/null +++ b/vendor/github.com/go-logfmt/logfmt/README.md @@ -0,0 +1,33 @@ +[![GoDoc](https://godoc.org/github.com/go-logfmt/logfmt?status.svg)](https://godoc.org/github.com/go-logfmt/logfmt) +[![Go Report Card](https://goreportcard.com/badge/go-logfmt/logfmt)](https://goreportcard.com/report/go-logfmt/logfmt) +[![TravisCI](https://travis-ci.org/go-logfmt/logfmt.svg?branch=master)](https://travis-ci.org/go-logfmt/logfmt) +[![Coverage Status](https://coveralls.io/repos/github/go-logfmt/logfmt/badge.svg?branch=master)](https://coveralls.io/github/go-logfmt/logfmt?branch=master) + +# logfmt + +Package logfmt implements utilities to marshal and unmarshal data in the [logfmt +format](https://brandur.org/logfmt). It provides an API similar to +[encoding/json](http://golang.org/pkg/encoding/json/) and +[encoding/xml](http://golang.org/pkg/encoding/xml/). + +The logfmt format was first documented by Brandur Leach in [this +article](https://brandur.org/logfmt). The format has not been formally +standardized. The most authoritative public specification to date has been the +documentation of a Go Language [package](http://godoc.org/github.com/kr/logfmt) +written by Blake Mizerany and Keith Rarick. + +## Goals + +This project attempts to conform as closely as possible to the prior art, while +also removing ambiguity where necessary to provide well behaved encoder and +decoder implementations. + +## Non-goals + +This project does not attempt to formally standardize the logfmt format. In the +event that logfmt is standardized this project would take conforming to the +standard as a goal. + +## Versioning + +Package logfmt publishes releases via [semver](http://semver.org/) compatible Git tags prefixed with a single 'v'. diff --git a/vendor/github.com/go-logfmt/logfmt/decode.go b/vendor/github.com/go-logfmt/logfmt/decode.go new file mode 100644 index 0000000..04e0eff --- /dev/null +++ b/vendor/github.com/go-logfmt/logfmt/decode.go @@ -0,0 +1,237 @@ +package logfmt + +import ( + "bufio" + "bytes" + "fmt" + "io" + "unicode/utf8" +) + +// A Decoder reads and decodes logfmt records from an input stream. +type Decoder struct { + pos int + key []byte + value []byte + lineNum int + s *bufio.Scanner + err error +} + +// NewDecoder returns a new decoder that reads from r. +// +// The decoder introduces its own buffering and may read data from r beyond +// the logfmt records requested. +func NewDecoder(r io.Reader) *Decoder { + dec := &Decoder{ + s: bufio.NewScanner(r), + } + return dec +} + +// ScanRecord advances the Decoder to the next record, which can then be +// parsed with the ScanKeyval method. It returns false when decoding stops, +// either by reaching the end of the input or an error. After ScanRecord +// returns false, the Err method will return any error that occurred during +// decoding, except that if it was io.EOF, Err will return nil. +func (dec *Decoder) ScanRecord() bool { + if dec.err != nil { + return false + } + if !dec.s.Scan() { + dec.err = dec.s.Err() + return false + } + dec.lineNum++ + dec.pos = 0 + return true +} + +// ScanKeyval advances the Decoder to the next key/value pair of the current +// record, which can then be retrieved with the Key and Value methods. It +// returns false when decoding stops, either by reaching the end of the +// current record or an error. +func (dec *Decoder) ScanKeyval() bool { + dec.key, dec.value = nil, nil + if dec.err != nil { + return false + } + + line := dec.s.Bytes() + + // garbage + for p, c := range line[dec.pos:] { + if c > ' ' { + dec.pos += p + goto key + } + } + dec.pos = len(line) + return false + +key: + const invalidKeyError = "invalid key" + + start, multibyte := dec.pos, false + for p, c := range line[dec.pos:] { + switch { + case c == '=': + dec.pos += p + if dec.pos > start { + dec.key = line[start:dec.pos] + if multibyte && bytes.IndexRune(dec.key, utf8.RuneError) != -1 { + dec.syntaxError(invalidKeyError) + return false + } + } + if dec.key == nil { + dec.unexpectedByte(c) + return false + } + goto equal + case c == '"': + dec.pos += p + dec.unexpectedByte(c) + return false + case c <= ' ': + dec.pos += p + if dec.pos > start { + dec.key = line[start:dec.pos] + if multibyte && bytes.IndexRune(dec.key, utf8.RuneError) != -1 { + dec.syntaxError(invalidKeyError) + return false + } + } + return true + case c >= utf8.RuneSelf: + multibyte = true + } + } + dec.pos = len(line) + if dec.pos > start { + dec.key = line[start:dec.pos] + if multibyte && bytes.IndexRune(dec.key, utf8.RuneError) != -1 { + dec.syntaxError(invalidKeyError) + return false + } + } + return true + +equal: + dec.pos++ + if dec.pos >= len(line) { + return true + } + switch c := line[dec.pos]; { + case c <= ' ': + return true + case c == '"': + goto qvalue + } + + // value + start = dec.pos + for p, c := range line[dec.pos:] { + switch { + case c == '=' || c == '"': + dec.pos += p + dec.unexpectedByte(c) + return false + case c <= ' ': + dec.pos += p + if dec.pos > start { + dec.value = line[start:dec.pos] + } + return true + } + } + dec.pos = len(line) + if dec.pos > start { + dec.value = line[start:dec.pos] + } + return true + +qvalue: + const ( + untermQuote = "unterminated quoted value" + invalidQuote = "invalid quoted value" + ) + + hasEsc, esc := false, false + start = dec.pos + for p, c := range line[dec.pos+1:] { + switch { + case esc: + esc = false + case c == '\\': + hasEsc, esc = true, true + case c == '"': + dec.pos += p + 2 + if hasEsc { + v, ok := unquoteBytes(line[start:dec.pos]) + if !ok { + dec.syntaxError(invalidQuote) + return false + } + dec.value = v + } else { + start++ + end := dec.pos - 1 + if end > start { + dec.value = line[start:end] + } + } + return true + } + } + dec.pos = len(line) + dec.syntaxError(untermQuote) + return false +} + +// Key returns the most recent key found by a call to ScanKeyval. The returned +// slice may point to internal buffers and is only valid until the next call +// to ScanRecord. It does no allocation. +func (dec *Decoder) Key() []byte { + return dec.key +} + +// Value returns the most recent value found by a call to ScanKeyval. The +// returned slice may point to internal buffers and is only valid until the +// next call to ScanRecord. It does no allocation when the value has no +// escape sequences. +func (dec *Decoder) Value() []byte { + return dec.value +} + +// Err returns the first non-EOF error that was encountered by the Scanner. +func (dec *Decoder) Err() error { + return dec.err +} + +func (dec *Decoder) syntaxError(msg string) { + dec.err = &SyntaxError{ + Msg: msg, + Line: dec.lineNum, + Pos: dec.pos + 1, + } +} + +func (dec *Decoder) unexpectedByte(c byte) { + dec.err = &SyntaxError{ + Msg: fmt.Sprintf("unexpected %q", c), + Line: dec.lineNum, + Pos: dec.pos + 1, + } +} + +// A SyntaxError represents a syntax error in the logfmt input stream. +type SyntaxError struct { + Msg string + Line int + Pos int +} + +func (e *SyntaxError) Error() string { + return fmt.Sprintf("logfmt syntax error at pos %d on line %d: %s", e.Pos, e.Line, e.Msg) +} diff --git a/vendor/github.com/go-logfmt/logfmt/doc.go b/vendor/github.com/go-logfmt/logfmt/doc.go new file mode 100644 index 0000000..378e9ad --- /dev/null +++ b/vendor/github.com/go-logfmt/logfmt/doc.go @@ -0,0 +1,6 @@ +// Package logfmt implements utilities to marshal and unmarshal data in the +// logfmt format. The logfmt format records key/value pairs in a way that +// balances readability for humans and simplicity of computer parsing. It is +// most commonly used as a more human friendly alternative to JSON for +// structured logging. +package logfmt diff --git a/vendor/github.com/go-logfmt/logfmt/encode.go b/vendor/github.com/go-logfmt/logfmt/encode.go new file mode 100644 index 0000000..55f1603 --- /dev/null +++ b/vendor/github.com/go-logfmt/logfmt/encode.go @@ -0,0 +1,321 @@ +package logfmt + +import ( + "bytes" + "encoding" + "errors" + "fmt" + "io" + "reflect" + "strings" + "unicode/utf8" +) + +// MarshalKeyvals returns the logfmt encoding of keyvals, a variadic sequence +// of alternating keys and values. +func MarshalKeyvals(keyvals ...interface{}) ([]byte, error) { + buf := &bytes.Buffer{} + if err := NewEncoder(buf).EncodeKeyvals(keyvals...); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// An Encoder writes logfmt data to an output stream. +type Encoder struct { + w io.Writer + scratch bytes.Buffer + needSep bool +} + +// NewEncoder returns a new encoder that writes to w. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{ + w: w, + } +} + +var ( + space = []byte(" ") + equals = []byte("=") + newline = []byte("\n") + null = []byte("null") +) + +// EncodeKeyval writes the logfmt encoding of key and value to the stream. A +// single space is written before the second and subsequent keys in a record. +// Nothing is written if a non-nil error is returned. +func (enc *Encoder) EncodeKeyval(key, value interface{}) error { + enc.scratch.Reset() + if enc.needSep { + if _, err := enc.scratch.Write(space); err != nil { + return err + } + } + if err := writeKey(&enc.scratch, key); err != nil { + return err + } + if _, err := enc.scratch.Write(equals); err != nil { + return err + } + if err := writeValue(&enc.scratch, value); err != nil { + return err + } + _, err := enc.w.Write(enc.scratch.Bytes()) + enc.needSep = true + return err +} + +// EncodeKeyvals writes the logfmt encoding of keyvals to the stream. Keyvals +// is a variadic sequence of alternating keys and values. Keys of unsupported +// type are skipped along with their corresponding value. Values of +// unsupported type or that cause a MarshalerError are replaced by their error +// but do not cause EncodeKeyvals to return an error. If a non-nil error is +// returned some key/value pairs may not have be written. +func (enc *Encoder) EncodeKeyvals(keyvals ...interface{}) error { + if len(keyvals) == 0 { + return nil + } + if len(keyvals)%2 == 1 { + keyvals = append(keyvals, nil) + } + for i := 0; i < len(keyvals); i += 2 { + k, v := keyvals[i], keyvals[i+1] + err := enc.EncodeKeyval(k, v) + if err == ErrUnsupportedKeyType { + continue + } + if _, ok := err.(*MarshalerError); ok || err == ErrUnsupportedValueType { + v = err + err = enc.EncodeKeyval(k, v) + } + if err != nil { + return err + } + } + return nil +} + +// MarshalerError represents an error encountered while marshaling a value. +type MarshalerError struct { + Type reflect.Type + Err error +} + +func (e *MarshalerError) Error() string { + return "error marshaling value of type " + e.Type.String() + ": " + e.Err.Error() +} + +// ErrNilKey is returned by Marshal functions and Encoder methods if a key is +// a nil interface or pointer value. +var ErrNilKey = errors.New("nil key") + +// ErrInvalidKey is returned by Marshal functions and Encoder methods if a key +// contains an invalid character. +var ErrInvalidKey = errors.New("invalid key") + +// ErrUnsupportedKeyType is returned by Encoder methods if a key has an +// unsupported type. +var ErrUnsupportedKeyType = errors.New("unsupported key type") + +// ErrUnsupportedValueType is returned by Encoder methods if a value has an +// unsupported type. +var ErrUnsupportedValueType = errors.New("unsupported value type") + +func writeKey(w io.Writer, key interface{}) error { + if key == nil { + return ErrNilKey + } + + switch k := key.(type) { + case string: + return writeStringKey(w, k) + case []byte: + if k == nil { + return ErrNilKey + } + return writeBytesKey(w, k) + case encoding.TextMarshaler: + kb, err := safeMarshal(k) + if err != nil { + return err + } + if kb == nil { + return ErrNilKey + } + return writeBytesKey(w, kb) + case fmt.Stringer: + ks, ok := safeString(k) + if !ok { + return ErrNilKey + } + return writeStringKey(w, ks) + default: + rkey := reflect.ValueOf(key) + switch rkey.Kind() { + case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice, reflect.Struct: + return ErrUnsupportedKeyType + case reflect.Ptr: + if rkey.IsNil() { + return ErrNilKey + } + return writeKey(w, rkey.Elem().Interface()) + } + return writeStringKey(w, fmt.Sprint(k)) + } +} + +func invalidKeyRune(r rune) bool { + return r <= ' ' || r == '=' || r == '"' || r == utf8.RuneError +} + +func invalidKeyString(key string) bool { + return len(key) == 0 || strings.IndexFunc(key, invalidKeyRune) != -1 +} + +func invalidKey(key []byte) bool { + return len(key) == 0 || bytes.IndexFunc(key, invalidKeyRune) != -1 +} + +func writeStringKey(w io.Writer, key string) error { + if invalidKeyString(key) { + return ErrInvalidKey + } + _, err := io.WriteString(w, key) + return err +} + +func writeBytesKey(w io.Writer, key []byte) error { + if invalidKey(key) { + return ErrInvalidKey + } + _, err := w.Write(key) + return err +} + +func writeValue(w io.Writer, value interface{}) error { + switch v := value.(type) { + case nil: + return writeBytesValue(w, null) + case string: + return writeStringValue(w, v, true) + case []byte: + return writeBytesValue(w, v) + case encoding.TextMarshaler: + vb, err := safeMarshal(v) + if err != nil { + return err + } + if vb == nil { + vb = null + } + return writeBytesValue(w, vb) + case error: + se, ok := safeError(v) + return writeStringValue(w, se, ok) + case fmt.Stringer: + ss, ok := safeString(v) + return writeStringValue(w, ss, ok) + default: + rvalue := reflect.ValueOf(value) + switch rvalue.Kind() { + case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice, reflect.Struct: + return ErrUnsupportedValueType + case reflect.Ptr: + if rvalue.IsNil() { + return writeBytesValue(w, null) + } + return writeValue(w, rvalue.Elem().Interface()) + } + return writeStringValue(w, fmt.Sprint(v), true) + } +} + +func needsQuotedValueRune(r rune) bool { + return r <= ' ' || r == '=' || r == '"' || r == utf8.RuneError +} + +func writeStringValue(w io.Writer, value string, ok bool) error { + var err error + if ok && value == "null" { + _, err = io.WriteString(w, `"null"`) + } else if strings.IndexFunc(value, needsQuotedValueRune) != -1 { + _, err = writeQuotedString(w, value) + } else { + _, err = io.WriteString(w, value) + } + return err +} + +func writeBytesValue(w io.Writer, value []byte) error { + var err error + if bytes.IndexFunc(value, needsQuotedValueRune) != -1 { + _, err = writeQuotedBytes(w, value) + } else { + _, err = w.Write(value) + } + return err +} + +// EndRecord writes a newline character to the stream and resets the encoder +// to the beginning of a new record. +func (enc *Encoder) EndRecord() error { + _, err := enc.w.Write(newline) + if err == nil { + enc.needSep = false + } + return err +} + +// Reset resets the encoder to the beginning of a new record. +func (enc *Encoder) Reset() { + enc.needSep = false +} + +func safeError(err error) (s string, ok bool) { + defer func() { + if panicVal := recover(); panicVal != nil { + if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() { + s, ok = "null", false + } else { + panic(panicVal) + } + } + }() + s, ok = err.Error(), true + return +} + +func safeString(str fmt.Stringer) (s string, ok bool) { + defer func() { + if panicVal := recover(); panicVal != nil { + if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() { + s, ok = "null", false + } else { + panic(panicVal) + } + } + }() + s, ok = str.String(), true + return +} + +func safeMarshal(tm encoding.TextMarshaler) (b []byte, err error) { + defer func() { + if panicVal := recover(); panicVal != nil { + if v := reflect.ValueOf(tm); v.Kind() == reflect.Ptr && v.IsNil() { + b, err = nil, nil + } else { + panic(panicVal) + } + } + }() + b, err = tm.MarshalText() + if err != nil { + return nil, &MarshalerError{ + Type: reflect.TypeOf(tm), + Err: err, + } + } + return +} diff --git a/vendor/github.com/go-logfmt/logfmt/fuzz.go b/vendor/github.com/go-logfmt/logfmt/fuzz.go new file mode 100644 index 0000000..6553b35 --- /dev/null +++ b/vendor/github.com/go-logfmt/logfmt/fuzz.go @@ -0,0 +1,126 @@ +// +build gofuzz + +package logfmt + +import ( + "bufio" + "bytes" + "fmt" + "io" + "reflect" + + kr "github.com/kr/logfmt" +) + +// Fuzz checks reserialized data matches +func Fuzz(data []byte) int { + parsed, err := parse(data) + if err != nil { + return 0 + } + var w1 bytes.Buffer + if err = write(parsed, &w1); err != nil { + panic(err) + } + parsed, err = parse(w1.Bytes()) + if err != nil { + panic(err) + } + var w2 bytes.Buffer + if err = write(parsed, &w2); err != nil { + panic(err) + } + if !bytes.Equal(w1.Bytes(), w2.Bytes()) { + panic(fmt.Sprintf("reserialized data does not match:\n%q\n%q\n", w1.Bytes(), w2.Bytes())) + } + return 1 +} + +// FuzzVsKR checks go-logfmt/logfmt against kr/logfmt +func FuzzVsKR(data []byte) int { + parsed, err := parse(data) + parsedKR, errKR := parseKR(data) + + // github.com/go-logfmt/logfmt is a stricter parser. It returns errors for + // more inputs than github.com/kr/logfmt. Ignore any inputs that have a + // stict error. + if err != nil { + return 0 + } + + // Fail if the more forgiving parser finds an error not found by the + // stricter parser. + if errKR != nil { + panic(fmt.Sprintf("unmatched error: %v", errKR)) + } + + if !reflect.DeepEqual(parsed, parsedKR) { + panic(fmt.Sprintf("parsers disagree:\n%+v\n%+v\n", parsed, parsedKR)) + } + return 1 +} + +type kv struct { + k, v []byte +} + +func parse(data []byte) ([][]kv, error) { + var got [][]kv + dec := NewDecoder(bytes.NewReader(data)) + for dec.ScanRecord() { + var kvs []kv + for dec.ScanKeyval() { + kvs = append(kvs, kv{dec.Key(), dec.Value()}) + } + got = append(got, kvs) + } + return got, dec.Err() +} + +func parseKR(data []byte) ([][]kv, error) { + var ( + s = bufio.NewScanner(bytes.NewReader(data)) + err error + h saveHandler + got [][]kv + ) + for err == nil && s.Scan() { + h.kvs = nil + err = kr.Unmarshal(s.Bytes(), &h) + got = append(got, h.kvs) + } + if err == nil { + err = s.Err() + } + return got, err +} + +type saveHandler struct { + kvs []kv +} + +func (h *saveHandler) HandleLogfmt(key, val []byte) error { + if len(key) == 0 { + key = nil + } + if len(val) == 0 { + val = nil + } + h.kvs = append(h.kvs, kv{key, val}) + return nil +} + +func write(recs [][]kv, w io.Writer) error { + enc := NewEncoder(w) + for _, rec := range recs { + for _, f := range rec { + if err := enc.EncodeKeyval(f.k, f.v); err != nil { + return err + } + } + if err := enc.EndRecord(); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/go-logfmt/logfmt/jsonstring.go b/vendor/github.com/go-logfmt/logfmt/jsonstring.go new file mode 100644 index 0000000..030ac85 --- /dev/null +++ b/vendor/github.com/go-logfmt/logfmt/jsonstring.go @@ -0,0 +1,277 @@ +package logfmt + +import ( + "bytes" + "io" + "strconv" + "sync" + "unicode" + "unicode/utf16" + "unicode/utf8" +) + +// Taken from Go's encoding/json and modified for use here. + +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +var hex = "0123456789abcdef" + +var bufferPool = sync.Pool{ + New: func() interface{} { + return &bytes.Buffer{} + }, +} + +func getBuffer() *bytes.Buffer { + return bufferPool.Get().(*bytes.Buffer) +} + +func poolBuffer(buf *bytes.Buffer) { + buf.Reset() + bufferPool.Put(buf) +} + +// NOTE: keep in sync with writeQuotedBytes below. +func writeQuotedString(w io.Writer, s string) (int, error) { + buf := getBuffer() + buf.WriteByte('"') + start := 0 + for i := 0; i < len(s); { + if b := s[i]; b < utf8.RuneSelf { + if 0x20 <= b && b != '\\' && b != '"' { + i++ + continue + } + if start < i { + buf.WriteString(s[start:i]) + } + switch b { + case '\\', '"': + buf.WriteByte('\\') + buf.WriteByte(b) + case '\n': + buf.WriteByte('\\') + buf.WriteByte('n') + case '\r': + buf.WriteByte('\\') + buf.WriteByte('r') + case '\t': + buf.WriteByte('\\') + buf.WriteByte('t') + default: + // This encodes bytes < 0x20 except for \n, \r, and \t. + buf.WriteString(`\u00`) + buf.WriteByte(hex[b>>4]) + buf.WriteByte(hex[b&0xF]) + } + i++ + start = i + continue + } + c, size := utf8.DecodeRuneInString(s[i:]) + if c == utf8.RuneError { + if start < i { + buf.WriteString(s[start:i]) + } + buf.WriteString(`\ufffd`) + i += size + start = i + continue + } + i += size + } + if start < len(s) { + buf.WriteString(s[start:]) + } + buf.WriteByte('"') + n, err := w.Write(buf.Bytes()) + poolBuffer(buf) + return n, err +} + +// NOTE: keep in sync with writeQuoteString above. +func writeQuotedBytes(w io.Writer, s []byte) (int, error) { + buf := getBuffer() + buf.WriteByte('"') + start := 0 + for i := 0; i < len(s); { + if b := s[i]; b < utf8.RuneSelf { + if 0x20 <= b && b != '\\' && b != '"' { + i++ + continue + } + if start < i { + buf.Write(s[start:i]) + } + switch b { + case '\\', '"': + buf.WriteByte('\\') + buf.WriteByte(b) + case '\n': + buf.WriteByte('\\') + buf.WriteByte('n') + case '\r': + buf.WriteByte('\\') + buf.WriteByte('r') + case '\t': + buf.WriteByte('\\') + buf.WriteByte('t') + default: + // This encodes bytes < 0x20 except for \n, \r, and \t. + buf.WriteString(`\u00`) + buf.WriteByte(hex[b>>4]) + buf.WriteByte(hex[b&0xF]) + } + i++ + start = i + continue + } + c, size := utf8.DecodeRune(s[i:]) + if c == utf8.RuneError { + if start < i { + buf.Write(s[start:i]) + } + buf.WriteString(`\ufffd`) + i += size + start = i + continue + } + i += size + } + if start < len(s) { + buf.Write(s[start:]) + } + buf.WriteByte('"') + n, err := w.Write(buf.Bytes()) + poolBuffer(buf) + return n, err +} + +// getu4 decodes \uXXXX from the beginning of s, returning the hex value, +// or it returns -1. +func getu4(s []byte) rune { + if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { + return -1 + } + r, err := strconv.ParseUint(string(s[2:6]), 16, 64) + if err != nil { + return -1 + } + return rune(r) +} + +func unquoteBytes(s []byte) (t []byte, ok bool) { + if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { + return + } + s = s[1 : len(s)-1] + + // Check for unusual characters. If there are none, + // then no unquoting is needed, so return a slice of the + // original bytes. + r := 0 + for r < len(s) { + c := s[r] + if c == '\\' || c == '"' || c < ' ' { + break + } + if c < utf8.RuneSelf { + r++ + continue + } + rr, size := utf8.DecodeRune(s[r:]) + if rr == utf8.RuneError { + break + } + r += size + } + if r == len(s) { + return s, true + } + + b := make([]byte, len(s)+2*utf8.UTFMax) + w := copy(b, s[0:r]) + for r < len(s) { + // Out of room? Can only happen if s is full of + // malformed UTF-8 and we're replacing each + // byte with RuneError. + if w >= len(b)-2*utf8.UTFMax { + nb := make([]byte, (len(b)+utf8.UTFMax)*2) + copy(nb, b[0:w]) + b = nb + } + switch c := s[r]; { + case c == '\\': + r++ + if r >= len(s) { + return + } + switch s[r] { + default: + return + case '"', '\\', '/', '\'': + b[w] = s[r] + r++ + w++ + case 'b': + b[w] = '\b' + r++ + w++ + case 'f': + b[w] = '\f' + r++ + w++ + case 'n': + b[w] = '\n' + r++ + w++ + case 'r': + b[w] = '\r' + r++ + w++ + case 't': + b[w] = '\t' + r++ + w++ + case 'u': + r-- + rr := getu4(s[r:]) + if rr < 0 { + return + } + r += 6 + if utf16.IsSurrogate(rr) { + rr1 := getu4(s[r:]) + if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar { + // A valid pair; consume. + r += 6 + w += utf8.EncodeRune(b[w:], dec) + break + } + // Invalid surrogate; fall back to replacement rune. + rr = unicode.ReplacementChar + } + w += utf8.EncodeRune(b[w:], rr) + } + + // Quote, control characters are invalid. + case c == '"', c < ' ': + return + + // ASCII + case c < utf8.RuneSelf: + b[w] = c + r++ + w++ + + // Coerce to well-formed UTF-8. + default: + rr, size := utf8.DecodeRune(s[r:]) + r += size + w += utf8.EncodeRune(b[w:], rr) + } + } + return b[0:w], true +} |