From 876b8cffc462ad33e46f56264b6e4ca511c1cff8 Mon Sep 17 00:00:00 2001 From: Fuxin Hao Date: Thu, 21 Dec 2017 10:21:41 +0800 Subject: [PATCH] Correct dependencies --- Godeps/Godeps.json | 20 +- .../DHowett/go-plist/.gitlab-ci.yml | 39 ++ .../github.com/DHowett/go-plist/.travis.yml | 8 + vendor/github.com/DHowett/go-plist/README.md | 6 +- vendor/github.com/DHowett/go-plist/bplist.go | 575 ------------------ .../DHowett/go-plist/bplist_generator.go | 290 +++++++++ .../DHowett/go-plist/bplist_parser.go | 338 ++++++++++ vendor/github.com/DHowett/go-plist/decode.go | 5 +- vendor/github.com/DHowett/go-plist/encode.go | 2 +- vendor/github.com/DHowett/go-plist/marshal.go | 106 ++-- vendor/github.com/DHowett/go-plist/plist.go | 104 +--- .../DHowett/go-plist/plist_types.go | 139 +++++ vendor/github.com/DHowett/go-plist/text.go | 569 ----------------- .../DHowett/go-plist/text_generator.go | 226 +++++++ .../DHowett/go-plist/text_parser.go | 515 ++++++++++++++++ .../DHowett/go-plist/text_tables.go | 23 +- .../github.com/DHowett/go-plist/unmarshal.go | 200 +++--- .../DHowett/go-plist/xml_generator.go | 145 +++++ .../go-plist/{xml.go => xml_parser.go} | 152 +---- .../github.com/DHowett/go-plist/zerocopy.go | 20 + .../DHowett/go-plist/zerocopy_appengine.go | 7 + .../codeskyblue/go-accesslog/.gitignore | 1 + .../codeskyblue/go-accesslog/README.markdown | 49 ++ .../codeskyblue/go-accesslog/accesslog.go | 172 ++++++ vendor/github.com/gorilla/context/context.go | 45 +- vendor/golang.org/x/net/AUTHORS | 3 + vendor/golang.org/x/net/CONTRIBUTORS | 3 + 27 files changed, 2254 insertions(+), 1508 deletions(-) create mode 100644 vendor/github.com/DHowett/go-plist/.gitlab-ci.yml create mode 100644 vendor/github.com/DHowett/go-plist/.travis.yml create mode 100644 vendor/github.com/DHowett/go-plist/bplist_generator.go create mode 100644 vendor/github.com/DHowett/go-plist/bplist_parser.go create mode 100644 vendor/github.com/DHowett/go-plist/plist_types.go delete mode 100644 vendor/github.com/DHowett/go-plist/text.go create mode 100644 vendor/github.com/DHowett/go-plist/text_generator.go create mode 100644 vendor/github.com/DHowett/go-plist/text_parser.go create mode 100644 vendor/github.com/DHowett/go-plist/xml_generator.go rename vendor/github.com/DHowett/go-plist/{xml.go => xml_parser.go} (53%) create mode 100644 vendor/github.com/DHowett/go-plist/zerocopy.go create mode 100644 vendor/github.com/DHowett/go-plist/zerocopy_appengine.go create mode 100644 vendor/github.com/codeskyblue/go-accesslog/.gitignore create mode 100644 vendor/github.com/codeskyblue/go-accesslog/README.markdown create mode 100644 vendor/github.com/codeskyblue/go-accesslog/accesslog.go create mode 100644 vendor/golang.org/x/net/AUTHORS create mode 100644 vendor/golang.org/x/net/CONTRIBUTORS diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 93f86b5..d0e6ac0 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,11 +1,11 @@ { "ImportPath": "github.com/codeskyblue/gohttpserver", - "GoVersion": "go1.7", - "GodepVersion": "v74", + "GoVersion": "go1.9", + "GodepVersion": "v79", "Deps": [ { "ImportPath": "github.com/DHowett/go-plist", - "Rev": "fd61394c0abc85ba629ff8471fa35d4309cd6609" + "Rev": "233df3c4f07b0c562da0e8a6fb850681ac49bb90" }, { "ImportPath": "github.com/alecthomas/kingpin", @@ -28,6 +28,10 @@ "ImportPath": "github.com/codeskyblue/dockerignore", "Rev": "de82dee623d9207f906d327172149cba50427a88" }, + { + "ImportPath": "github.com/codeskyblue/go-accesslog", + "Rev": "6188d3bd9371c3caba018648c6acd9cdf805787e" + }, { "ImportPath": "github.com/codeskyblue/openid-go", "Rev": "0d30842b2fb4704849cd07f6fba862a7a9f4b403" @@ -42,12 +46,12 @@ }, { "ImportPath": "github.com/gorilla/context", - "Comment": "v1.1-8-g0b6087c", - "Rev": "0b6087c0035ecc2ecddef2451329bca7f36192ab" + "Comment": "v1.1-7-g08b5f42", + "Rev": "08b5f424b9271eedf6f9f0ce86cb9396ed337a42" }, { "ImportPath": "github.com/gorilla/handlers", - "Comment": "v1.1-14-g3a5767c", + "Comment": "v1.2", "Rev": "3a5767ca75ece5f7f1440b1d16975247f8d8b221" }, { @@ -65,10 +69,6 @@ "Comment": "v1.1", "Rev": "ca9ada44574153444b00d3fd9c8559e4cc95f896" }, - { - "ImportPath": "github.com/mash/go-accesslog", - "Rev": "9ba8e13f36087d6cb83d9a9f17f9e8da137d5ee9" - }, { "ImportPath": "github.com/pkg/errors", "Comment": "v0.8.0-1-g839d9e9", diff --git a/vendor/github.com/DHowett/go-plist/.gitlab-ci.yml b/vendor/github.com/DHowett/go-plist/.gitlab-ci.yml new file mode 100644 index 0000000..11d6dbf --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/.gitlab-ci.yml @@ -0,0 +1,39 @@ +image: golang:alpine +stages: + - test + +variables: + GO_PACKAGE: "howett.net/plist" + +before_script: + - "mkdir -p $(dirname $GOPATH/src/$GO_PACKAGE)" + - "ln -s $(pwd) $GOPATH/src/$GO_PACKAGE" + - "cd $GOPATH/src/$GO_PACKAGE" + +.template:go-test: &template-go-test + stage: test + script: + - go test + +go-test-cover:latest: + stage: test + script: + - go test -v -cover + coverage: '/^coverage: \d+\.\d+/' + +go-test-appengine:latest: + stage: test + script: + - go test -tags appengine + +go-test:1.6: + <<: *template-go-test + image: golang:1.6-alpine + +go-test:1.4: + <<: *template-go-test + image: golang:1.4-alpine + +go-test:1.2: + <<: *template-go-test + image: golang:1.2 diff --git a/vendor/github.com/DHowett/go-plist/.travis.yml b/vendor/github.com/DHowett/go-plist/.travis.yml new file mode 100644 index 0000000..b1f27e3 --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/.travis.yml @@ -0,0 +1,8 @@ +language: go +go_import_path: "howett.net/plist" +go: + - 1.2 + - master +script: + - go test -v + - go test -tags appengine diff --git a/vendor/github.com/DHowett/go-plist/README.md b/vendor/github.com/DHowett/go-plist/README.md index 5cf76d7..ffc456f 100644 --- a/vendor/github.com/DHowett/go-plist/README.md +++ b/vendor/github.com/DHowett/go-plist/README.md @@ -1,6 +1,8 @@ -# plist - A pure Go property list transcoder +# plist - A pure Go property list transcoder [![coverage report](https://gitlab.howett.net/DHowett/plist/badges/master/coverage.svg)](https://gitlab.howett.net/DHowett/plist/commits/master) ## INSTALL - $ go get howett.net/plist +``` +$ go get howett.net/plist +``` ## FEATURES * Supports encoding/decoding property lists (Apple XML, Apple Binary, OpenStep and GNUStep) from/to arbitrary Go types diff --git a/vendor/github.com/DHowett/go-plist/bplist.go b/vendor/github.com/DHowett/go-plist/bplist.go index 45f07b4..962793a 100644 --- a/vendor/github.com/DHowett/go-plist/bplist.go +++ b/vendor/github.com/DHowett/go-plist/bplist.go @@ -1,18 +1,5 @@ package plist -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "hash/crc32" - "io" - "math" - "runtime" - "time" - "unicode/utf16" -) - type bplistTrailer struct { Unused [5]uint8 SortVersion uint8 @@ -37,565 +24,3 @@ const ( bpTagArray = 0xA0 bpTagDictionary = 0xD0 ) - -type bplistGenerator struct { - writer *countedWriter - uniqmap map[interface{}]uint64 - objmap map[*plistValue]uint64 - objtable []*plistValue - nobjects uint64 - trailer bplistTrailer -} - -func (p *bplistGenerator) flattenPlistValue(pval *plistValue) { - switch pval.kind { - case String, Integer, Real: - if _, ok := p.uniqmap[pval.value]; ok { - return - } - p.uniqmap[pval.value] = p.nobjects - case Date: - k := pval.value.(time.Time).UnixNano() - if _, ok := p.uniqmap[k]; ok { - return - } - p.uniqmap[k] = p.nobjects - case Data: - // Data are uniqued by their checksums. - // The wonderful difference between uint64 (which we use for numbers) - // and uint32 makes this possible. - // Todo: Look at calculating this only once and storing it somewhere; - // crc32 is fairly quick, however. - uniqkey := crc32.ChecksumIEEE(pval.value.([]byte)) - if _, ok := p.uniqmap[uniqkey]; ok { - return - } - p.uniqmap[uniqkey] = p.nobjects - } - - p.objtable = append(p.objtable, pval) - p.objmap[pval] = p.nobjects - p.nobjects++ - - switch pval.kind { - case Dictionary: - dict := pval.value.(*dictionary) - dict.populateArrays() - for _, k := range dict.keys { - p.flattenPlistValue(&plistValue{String, k}) - } - for _, v := range dict.values { - p.flattenPlistValue(v) - } - case Array: - subvalues := pval.value.([]*plistValue) - for _, v := range subvalues { - p.flattenPlistValue(v) - } - } -} - -func (p *bplistGenerator) indexForPlistValue(pval *plistValue) (uint64, bool) { - var v uint64 - var ok bool - switch pval.kind { - case String, Integer, Real: - v, ok = p.uniqmap[pval.value] - case Date: - v, ok = p.uniqmap[pval.value.(time.Time).UnixNano()] - case Data: - v, ok = p.uniqmap[crc32.ChecksumIEEE(pval.value.([]byte))] - default: - v, ok = p.objmap[pval] - } - return v, ok -} - -func (p *bplistGenerator) generateDocument(rootpval *plistValue) { - p.objtable = make([]*plistValue, 0, 15) - p.uniqmap = make(map[interface{}]uint64) - p.objmap = make(map[*plistValue]uint64) - p.flattenPlistValue(rootpval) - - p.trailer.NumObjects = uint64(len(p.objtable)) - p.trailer.ObjectRefSize = uint8(minimumSizeForInt(p.trailer.NumObjects)) - - p.writer.Write([]byte("bplist00")) - - offtable := make([]uint64, p.trailer.NumObjects) - for i, pval := range p.objtable { - offtable[i] = uint64(p.writer.BytesWritten()) - p.writePlistValue(pval) - } - - p.trailer.OffsetIntSize = uint8(minimumSizeForInt(uint64(p.writer.BytesWritten()))) - p.trailer.TopObject = p.objmap[rootpval] - p.trailer.OffsetTableOffset = uint64(p.writer.BytesWritten()) - - for _, offset := range offtable { - p.writeSizedInt(offset, int(p.trailer.OffsetIntSize)) - } - - binary.Write(p.writer, binary.BigEndian, p.trailer) -} - -func (p *bplistGenerator) writePlistValue(pval *plistValue) { - if pval == nil { - return - } - - switch pval.kind { - case Dictionary: - p.writeDictionaryTag(pval.value.(*dictionary)) - case Array: - p.writeArrayTag(pval.value.([]*plistValue)) - case String: - p.writeStringTag(pval.value.(string)) - case Integer: - p.writeIntTag(pval.value.(signedInt).value) - case Real: - p.writeRealTag(pval.value.(sizedFloat).value, pval.value.(sizedFloat).bits) - case Boolean: - p.writeBoolTag(pval.value.(bool)) - case Data: - p.writeDataTag(pval.value.([]byte)) - case Date: - p.writeDateTag(pval.value.(time.Time)) - } -} - -func minimumSizeForInt(n uint64) int { - switch { - case n <= uint64(0xff): - return 1 - case n <= uint64(0xffff): - return 2 - case n <= uint64(0xffffffff): - return 4 - default: - return 8 - } - panic(errors.New("illegal integer size")) -} - -func (p *bplistGenerator) writeSizedInt(n uint64, nbytes int) { - var val interface{} - switch nbytes { - case 1: - val = uint8(n) - case 2: - val = uint16(n) - case 4: - val = uint32(n) - case 8: - val = n - default: - panic(errors.New("illegal integer size")) - } - binary.Write(p.writer, binary.BigEndian, val) -} - -func (p *bplistGenerator) writeBoolTag(v bool) { - tag := uint8(bpTagBoolFalse) - if v { - tag = bpTagBoolTrue - } - binary.Write(p.writer, binary.BigEndian, tag) -} - -func (p *bplistGenerator) writeIntTag(n uint64) { - var tag uint8 - var val interface{} - switch { - case n <= uint64(0xff): - val = uint8(n) - tag = bpTagInteger | 0x0 - case n <= uint64(0xffff): - val = uint16(n) - tag = bpTagInteger | 0x1 - case n <= uint64(0xffffffff): - val = uint32(n) - tag = bpTagInteger | 0x2 - default: - val = n - tag = bpTagInteger | 0x3 - } - - binary.Write(p.writer, binary.BigEndian, tag) - binary.Write(p.writer, binary.BigEndian, val) -} - -func (p *bplistGenerator) writeRealTag(n float64, bits int) { - var tag uint8 = bpTagReal | 0x3 - var val interface{} = n - if bits == 32 { - val = float32(n) - tag = bpTagReal | 0x2 - } - - binary.Write(p.writer, binary.BigEndian, tag) - binary.Write(p.writer, binary.BigEndian, val) -} - -func (p *bplistGenerator) writeDateTag(t time.Time) { - tag := uint8(bpTagDate) | 0x3 - val := float64(t.In(time.UTC).UnixNano()) / float64(time.Second) - val -= 978307200 // Adjust to Apple Epoch - - binary.Write(p.writer, binary.BigEndian, tag) - binary.Write(p.writer, binary.BigEndian, val) -} - -func (p *bplistGenerator) writeCountedTag(tag uint8, count uint64) { - marker := tag - if count >= 0xF { - marker |= 0xF - } else { - marker |= uint8(count) - } - - binary.Write(p.writer, binary.BigEndian, marker) - - if count >= 0xF { - p.writeIntTag(count) - } -} - -func (p *bplistGenerator) writeDataTag(data []byte) { - p.writeCountedTag(bpTagData, uint64(len(data))) - binary.Write(p.writer, binary.BigEndian, data) -} - -func (p *bplistGenerator) writeStringTag(str string) { - for _, r := range str { - if r > 0xFF { - utf16Runes := utf16.Encode([]rune(str)) - p.writeCountedTag(bpTagUTF16String, uint64(len(utf16Runes))) - binary.Write(p.writer, binary.BigEndian, utf16Runes) - return - } - } - - p.writeCountedTag(bpTagASCIIString, uint64(len(str))) - binary.Write(p.writer, binary.BigEndian, []byte(str)) -} - -func (p *bplistGenerator) writeDictionaryTag(dict *dictionary) { - p.writeCountedTag(bpTagDictionary, uint64(dict.count)) - vals := make([]uint64, dict.count*2) - cnt := dict.count - for i, k := range dict.keys { - keyIdx, ok := p.uniqmap[k] - if !ok { - panic(errors.New("failed to find key " + k + " in object map during serialization")) - } - vals[i] = keyIdx - } - for i, v := range dict.values { - objIdx, ok := p.indexForPlistValue(v) - if !ok { - panic(errors.New("failed to find value in object map during serialization")) - } - vals[i+cnt] = objIdx - } - - for _, v := range vals { - p.writeSizedInt(v, int(p.trailer.ObjectRefSize)) - } -} - -func (p *bplistGenerator) writeArrayTag(arr []*plistValue) { - p.writeCountedTag(bpTagArray, uint64(len(arr))) - for _, v := range arr { - objIdx, ok := p.indexForPlistValue(v) - if !ok { - panic(errors.New("failed to find value in object map during serialization")) - } - - p.writeSizedInt(objIdx, int(p.trailer.ObjectRefSize)) - } -} - -func (p *bplistGenerator) Indent(i string) { - // There's nothing to indent. -} - -func newBplistGenerator(w io.Writer) *bplistGenerator { - return &bplistGenerator{ - writer: &countedWriter{Writer: mustWriter{w}}, - } -} - -type bplistParser struct { - reader io.ReadSeeker - version int - buf []byte - objrefs map[uint64]*plistValue - offtable []uint64 - trailer bplistTrailer - trailerOffset int64 -} - -func (p *bplistParser) parseDocument() (pval *plistValue, parseError error) { - defer func() { - if r := recover(); r != nil { - if _, ok := r.(runtime.Error); ok { - panic(r) - } - if _, ok := r.(invalidPlistError); ok { - parseError = r.(error) - } else { - // Wrap all non-invalid-plist errors. - parseError = plistParseError{"binary", r.(error)} - } - } - }() - - magic := make([]byte, 6) - ver := make([]byte, 2) - p.reader.Seek(0, 0) - p.reader.Read(magic) - if !bytes.Equal(magic, []byte("bplist")) { - panic(invalidPlistError{"binary", errors.New("mismatched magic")}) - } - - _, err := p.reader.Read(ver) - if err != nil { - panic(err) - } - - p.version = int(mustParseInt(string(ver), 10, 0)) - - if p.version > 1 { - panic(fmt.Errorf("unexpected version %d", p.version)) - } - - p.objrefs = make(map[uint64]*plistValue) - p.trailerOffset, err = p.reader.Seek(-32, 2) - if err != nil && err != io.EOF { - panic(err) - } - - err = binary.Read(p.reader, binary.BigEndian, &p.trailer) - if err != nil && err != io.EOF { - panic(err) - } - - if p.trailer.NumObjects > uint64(math.Pow(2, 8*float64(p.trailer.ObjectRefSize))) { - panic(fmt.Errorf("binary property list contains more objects (%v) than its object ref size (%v bytes) can support", p.trailer.NumObjects, p.trailer.ObjectRefSize)) - } - - if p.trailer.TopObject >= p.trailer.NumObjects { - panic(fmt.Errorf("top object index %v is out of range (only %v objects exist)", p.trailer.TopObject, p.trailer.NumObjects)) - } - p.offtable = make([]uint64, p.trailer.NumObjects) - - // SEEK_SET - _, err = p.reader.Seek(int64(p.trailer.OffsetTableOffset), 0) - if err != nil && err != io.EOF { - panic(err) - } - - for i := uint64(0); i < p.trailer.NumObjects; i++ { - off := p.readSizedInt(int(p.trailer.OffsetIntSize)) - if off >= uint64(p.trailerOffset) { - panic(fmt.Errorf("object %v starts beyond end of plist trailer (%v vs %v)", i, off, p.trailerOffset)) - } - p.offtable[i] = off - } - - for _, off := range p.offtable { - p.valueAtOffset(off) - } - - pval = p.valueAtOffset(p.offtable[p.trailer.TopObject]) - return -} - -func (p *bplistParser) readSizedInt(nbytes int) uint64 { - switch nbytes { - case 1: - var val uint8 - binary.Read(p.reader, binary.BigEndian, &val) - return uint64(val) - case 2: - var val uint16 - binary.Read(p.reader, binary.BigEndian, &val) - return uint64(val) - case 4: - var val uint32 - binary.Read(p.reader, binary.BigEndian, &val) - return uint64(val) - case 8: - var val uint64 - binary.Read(p.reader, binary.BigEndian, &val) - return uint64(val) - case 16: - var high, low uint64 - binary.Read(p.reader, binary.BigEndian, &high) - binary.Read(p.reader, binary.BigEndian, &low) - // TODO: int128 support (!) - return uint64(low) - } - panic(errors.New("illegal integer size")) -} - -func (p *bplistParser) countForTag(tag uint8) uint64 { - cnt := uint64(tag & 0x0F) - if cnt == 0xF { - var intTag uint8 - binary.Read(p.reader, binary.BigEndian, &intTag) - cnt = p.readSizedInt(1 << (intTag & 0xF)) - } - return cnt -} - -func (p *bplistParser) valueAtOffset(off uint64) *plistValue { - if pval, ok := p.objrefs[off]; ok { - return pval - } - pval := p.parseTagAtOffset(int64(off)) - p.objrefs[off] = pval - return pval -} - -func (p *bplistParser) parseTagAtOffset(off int64) *plistValue { - var tag uint8 - _, err := p.reader.Seek(off, 0) - if err != nil { - panic(err) - } - err = binary.Read(p.reader, binary.BigEndian, &tag) - if err != nil { - panic(err) - } - - switch tag & 0xF0 { - case bpTagNull: - switch tag & 0x0F { - case bpTagBoolTrue, bpTagBoolFalse: - return &plistValue{Boolean, tag == bpTagBoolTrue} - } - return nil - case bpTagInteger: - val := p.readSizedInt(1 << (tag & 0xF)) - return &plistValue{Integer, signedInt{val, false}} - case bpTagReal: - nbytes := 1 << (tag & 0x0F) - switch nbytes { - case 4: - var val float32 - binary.Read(p.reader, binary.BigEndian, &val) - return &plistValue{Real, sizedFloat{float64(val), 32}} - case 8: - var val float64 - binary.Read(p.reader, binary.BigEndian, &val) - return &plistValue{Real, sizedFloat{float64(val), 64}} - } - panic(errors.New("illegal float size")) - case bpTagDate: - var val float64 - binary.Read(p.reader, binary.BigEndian, &val) - - // Apple Epoch is 20110101000000Z - // Adjust for UNIX Time - val += 978307200 - - sec, fsec := math.Modf(val) - time := time.Unix(int64(sec), int64(fsec*float64(time.Second))).In(time.UTC) - return &plistValue{Date, time} - case bpTagData: - cnt := p.countForTag(tag) - if int64(cnt) > p.trailerOffset-int64(off) { - panic(fmt.Errorf("data at %x longer than file (%v bytes, max is %v)", off, cnt, p.trailerOffset-int64(off))) - } - - bytes := make([]byte, cnt) - binary.Read(p.reader, binary.BigEndian, bytes) - return &plistValue{Data, bytes} - case bpTagASCIIString, bpTagUTF16String: - cnt := p.countForTag(tag) - if int64(cnt) > p.trailerOffset-int64(off) { - panic(fmt.Errorf("string at %x longer than file (%v bytes, max is %v)", off, cnt, p.trailerOffset-int64(off))) - } - - if tag&0xF0 == bpTagASCIIString { - bytes := make([]byte, cnt) - binary.Read(p.reader, binary.BigEndian, bytes) - return &plistValue{String, string(bytes)} - } else { - bytes := make([]uint16, cnt) - binary.Read(p.reader, binary.BigEndian, bytes) - runes := utf16.Decode(bytes) - return &plistValue{String, string(runes)} - } - case bpTagUID: // Somehow different than int: low half is nbytes - 1 instead of log2(nbytes) - val := p.readSizedInt(int(tag&0xF) + 1) - return &plistValue{Integer, signedInt{val, false}} - case bpTagDictionary: - cnt := p.countForTag(tag) - - subvalues := make(map[string]*plistValue) - indices := make([]uint64, cnt*2) - for i := uint64(0); i < cnt*2; i++ { - idx := p.readSizedInt(int(p.trailer.ObjectRefSize)) - - if idx >= p.trailer.NumObjects { - panic(fmt.Errorf("dictionary contains invalid entry index %d (max %d)", idx, p.trailer.NumObjects)) - } - - indices[i] = idx - } - for i := uint64(0); i < cnt; i++ { - keyOffset := p.offtable[indices[i]] - valueOffset := p.offtable[indices[i+cnt]] - if keyOffset == uint64(off) { - panic(fmt.Errorf("dictionary contains self-referential key %x (index %d)", off, i)) - } - if valueOffset == uint64(off) { - panic(fmt.Errorf("dictionary contains self-referential value %x (index %d)", off, i)) - } - - kval := p.valueAtOffset(keyOffset) - if kval == nil || kval.kind != String { - panic(fmt.Errorf("dictionary contains non-string key at index %d", i)) - } - - key, ok := kval.value.(string) - if !ok { - panic(fmt.Errorf("string-type plist value contains non-string at index %d", i)) - } - subvalues[key] = p.valueAtOffset(valueOffset) - } - - return &plistValue{Dictionary, &dictionary{m: subvalues}} - case bpTagArray: - cnt := p.countForTag(tag) - - arr := make([]*plistValue, cnt) - indices := make([]uint64, cnt) - for i := uint64(0); i < cnt; i++ { - idx := p.readSizedInt(int(p.trailer.ObjectRefSize)) - - if idx >= p.trailer.NumObjects { - panic(fmt.Errorf("array contains invalid entry index %d (max %d)", idx, p.trailer.NumObjects)) - } - - indices[i] = idx - } - for i := uint64(0); i < cnt; i++ { - valueOffset := p.offtable[indices[i]] - if valueOffset == uint64(off) { - panic(fmt.Errorf("array contains self-referential value %x (index %d)", off, i)) - } - arr[i] = p.valueAtOffset(valueOffset) - } - - return &plistValue{Array, arr} - } - panic(fmt.Errorf("unexpected atom 0x%2.02x at offset %d", tag, off)) -} - -func newBplistParser(r io.ReadSeeker) *bplistParser { - return &bplistParser{reader: r} -} diff --git a/vendor/github.com/DHowett/go-plist/bplist_generator.go b/vendor/github.com/DHowett/go-plist/bplist_generator.go new file mode 100644 index 0000000..5b6513d --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/bplist_generator.go @@ -0,0 +1,290 @@ +package plist + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "time" + "unicode/utf16" +) + +func bplistMinimumIntSize(n uint64) int { + switch { + case n <= uint64(0xff): + return 1 + case n <= uint64(0xffff): + return 2 + case n <= uint64(0xffffffff): + return 4 + default: + return 8 + } +} + +func bplistValueShouldUnique(pval cfValue) bool { + switch pval.(type) { + case cfString, *cfNumber, *cfReal, cfDate, cfData: + return true + } + return false +} + +type bplistGenerator struct { + writer *countedWriter + objmap map[interface{}]uint64 // maps pValue.hash()es to object locations + objtable []cfValue + trailer bplistTrailer +} + +func (p *bplistGenerator) flattenPlistValue(pval cfValue) { + key := pval.hash() + if bplistValueShouldUnique(pval) { + if _, ok := p.objmap[key]; ok { + return + } + } + + p.objmap[key] = uint64(len(p.objtable)) + p.objtable = append(p.objtable, pval) + + switch pval := pval.(type) { + case *cfDictionary: + pval.sort() + for _, k := range pval.keys { + p.flattenPlistValue(cfString(k)) + } + for _, v := range pval.values { + p.flattenPlistValue(v) + } + case *cfArray: + for _, v := range pval.values { + p.flattenPlistValue(v) + } + } +} + +func (p *bplistGenerator) indexForPlistValue(pval cfValue) (uint64, bool) { + v, ok := p.objmap[pval.hash()] + return v, ok +} + +func (p *bplistGenerator) generateDocument(root cfValue) { + p.objtable = make([]cfValue, 0, 16) + p.objmap = make(map[interface{}]uint64) + p.flattenPlistValue(root) + + p.trailer.NumObjects = uint64(len(p.objtable)) + p.trailer.ObjectRefSize = uint8(bplistMinimumIntSize(p.trailer.NumObjects)) + + p.writer.Write([]byte("bplist00")) + + offtable := make([]uint64, p.trailer.NumObjects) + for i, pval := range p.objtable { + offtable[i] = uint64(p.writer.BytesWritten()) + p.writePlistValue(pval) + } + + p.trailer.OffsetIntSize = uint8(bplistMinimumIntSize(uint64(p.writer.BytesWritten()))) + p.trailer.TopObject = p.objmap[root.hash()] + p.trailer.OffsetTableOffset = uint64(p.writer.BytesWritten()) + + for _, offset := range offtable { + p.writeSizedInt(offset, int(p.trailer.OffsetIntSize)) + } + + binary.Write(p.writer, binary.BigEndian, p.trailer) +} + +func (p *bplistGenerator) writePlistValue(pval cfValue) { + if pval == nil { + return + } + + switch pval := pval.(type) { + case *cfDictionary: + p.writeDictionaryTag(pval) + case *cfArray: + p.writeArrayTag(pval.values) + case cfString: + p.writeStringTag(string(pval)) + case *cfNumber: + p.writeIntTag(pval.value) + case *cfReal: + if pval.wide { + p.writeRealTag(pval.value, 64) + } else { + p.writeRealTag(pval.value, 32) + } + case cfBoolean: + p.writeBoolTag(bool(pval)) + case cfData: + p.writeDataTag([]byte(pval)) + case cfDate: + p.writeDateTag(time.Time(pval)) + case cfUID: + p.writeUIDTag(UID(pval)) + default: + panic(fmt.Errorf("unknown plist type %t", pval)) + } +} + +func (p *bplistGenerator) writeSizedInt(n uint64, nbytes int) { + var val interface{} + switch nbytes { + case 1: + val = uint8(n) + case 2: + val = uint16(n) + case 4: + val = uint32(n) + case 8: + val = n + default: + panic(errors.New("illegal integer size")) + } + binary.Write(p.writer, binary.BigEndian, val) +} + +func (p *bplistGenerator) writeBoolTag(v bool) { + tag := uint8(bpTagBoolFalse) + if v { + tag = bpTagBoolTrue + } + binary.Write(p.writer, binary.BigEndian, tag) +} + +func (p *bplistGenerator) writeIntTag(n uint64) { + var tag uint8 + var val interface{} + switch { + case n <= uint64(0xff): + val = uint8(n) + tag = bpTagInteger | 0x0 + case n <= uint64(0xffff): + val = uint16(n) + tag = bpTagInteger | 0x1 + case n <= uint64(0xffffffff): + val = uint32(n) + tag = bpTagInteger | 0x2 + default: + val = n + tag = bpTagInteger | 0x3 + } + + binary.Write(p.writer, binary.BigEndian, tag) + binary.Write(p.writer, binary.BigEndian, val) +} + +func (p *bplistGenerator) writeUIDTag(u UID) { + nbytes := bplistMinimumIntSize(uint64(u)) + tag := uint8(bpTagUID | (nbytes - 1)) + + binary.Write(p.writer, binary.BigEndian, tag) + p.writeSizedInt(uint64(u), nbytes) +} + +func (p *bplistGenerator) writeRealTag(n float64, bits int) { + var tag uint8 = bpTagReal | 0x3 + var val interface{} = n + if bits == 32 { + val = float32(n) + tag = bpTagReal | 0x2 + } + + binary.Write(p.writer, binary.BigEndian, tag) + binary.Write(p.writer, binary.BigEndian, val) +} + +func (p *bplistGenerator) writeDateTag(t time.Time) { + tag := uint8(bpTagDate) | 0x3 + val := float64(t.In(time.UTC).UnixNano()) / float64(time.Second) + val -= 978307200 // Adjust to Apple Epoch + + binary.Write(p.writer, binary.BigEndian, tag) + binary.Write(p.writer, binary.BigEndian, val) +} + +func (p *bplistGenerator) writeCountedTag(tag uint8, count uint64) { + marker := tag + if count >= 0xF { + marker |= 0xF + } else { + marker |= uint8(count) + } + + binary.Write(p.writer, binary.BigEndian, marker) + + if count >= 0xF { + p.writeIntTag(count) + } +} + +func (p *bplistGenerator) writeDataTag(data []byte) { + p.writeCountedTag(bpTagData, uint64(len(data))) + binary.Write(p.writer, binary.BigEndian, data) +} + +func (p *bplistGenerator) writeStringTag(str string) { + for _, r := range str { + if r > 0x7F { + utf16Runes := utf16.Encode([]rune(str)) + p.writeCountedTag(bpTagUTF16String, uint64(len(utf16Runes))) + binary.Write(p.writer, binary.BigEndian, utf16Runes) + return + } + } + + p.writeCountedTag(bpTagASCIIString, uint64(len(str))) + binary.Write(p.writer, binary.BigEndian, []byte(str)) +} + +func (p *bplistGenerator) writeDictionaryTag(dict *cfDictionary) { + // assumption: sorted already; flattenPlistValue did this. + cnt := len(dict.keys) + p.writeCountedTag(bpTagDictionary, uint64(cnt)) + vals := make([]uint64, cnt*2) + for i, k := range dict.keys { + // invariant: keys have already been "uniqued" (as PStrings) + keyIdx, ok := p.objmap[cfString(k).hash()] + if !ok { + panic(errors.New("failed to find key " + k + " in object map during serialization")) + } + vals[i] = keyIdx + } + + for i, v := range dict.values { + // invariant: values have already been "uniqued" + objIdx, ok := p.indexForPlistValue(v) + if !ok { + panic(errors.New("failed to find value in object map during serialization")) + } + vals[i+cnt] = objIdx + } + + for _, v := range vals { + p.writeSizedInt(v, int(p.trailer.ObjectRefSize)) + } +} + +func (p *bplistGenerator) writeArrayTag(arr []cfValue) { + p.writeCountedTag(bpTagArray, uint64(len(arr))) + for _, v := range arr { + objIdx, ok := p.indexForPlistValue(v) + if !ok { + panic(errors.New("failed to find value in object map during serialization")) + } + + p.writeSizedInt(objIdx, int(p.trailer.ObjectRefSize)) + } +} + +func (p *bplistGenerator) Indent(i string) { + // There's nothing to indent. +} + +func newBplistGenerator(w io.Writer) *bplistGenerator { + return &bplistGenerator{ + writer: &countedWriter{Writer: mustWriter{w}}, + } +} diff --git a/vendor/github.com/DHowett/go-plist/bplist_parser.go b/vendor/github.com/DHowett/go-plist/bplist_parser.go new file mode 100644 index 0000000..224354b --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/bplist_parser.go @@ -0,0 +1,338 @@ +package plist + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "io/ioutil" + "math" + "runtime" + "time" + "unicode/utf16" +) + +type offset uint64 + +type bplistParser struct { + buffer []byte + + reader io.ReadSeeker + version int + objects []cfValue // object ID to object + trailer bplistTrailer + trailerOffset uint64 + + containerStack []offset // slice of object offsets; manipulated during container deserialization +} + +func (p *bplistParser) validateDocumentTrailer() { + if p.trailer.OffsetTableOffset >= p.trailerOffset { + panic(fmt.Errorf("offset table beyond beginning of trailer (0x%x, trailer@0x%x)", p.trailer.OffsetTableOffset, p.trailerOffset)) + } + + if p.trailer.OffsetTableOffset < 9 { + panic(fmt.Errorf("offset table begins inside header (0x%x)", p.trailer.OffsetTableOffset)) + } + + if p.trailerOffset > (p.trailer.NumObjects*uint64(p.trailer.OffsetIntSize))+p.trailer.OffsetTableOffset { + panic(errors.New("garbage between offset table and trailer")) + } + + if p.trailer.OffsetTableOffset+(uint64(p.trailer.OffsetIntSize)*p.trailer.NumObjects) > p.trailerOffset { + panic(errors.New("offset table isn't long enough to address every object")) + } + + maxObjectRef := uint64(1) << (8 * p.trailer.ObjectRefSize) + if p.trailer.NumObjects > maxObjectRef { + panic(fmt.Errorf("more objects (%v) than object ref size (%v bytes) can support", p.trailer.NumObjects, p.trailer.ObjectRefSize)) + } + + if p.trailer.OffsetIntSize < uint8(8) && (uint64(1)<<(8*p.trailer.OffsetIntSize)) <= p.trailer.OffsetTableOffset { + panic(errors.New("offset size isn't big enough to address entire file")) + } + + if p.trailer.TopObject >= p.trailer.NumObjects { + panic(fmt.Errorf("top object #%d is out of range (only %d exist)", p.trailer.TopObject, p.trailer.NumObjects)) + } +} + +func (p *bplistParser) parseDocument() (pval cfValue, parseError error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + + parseError = plistParseError{"binary", r.(error)} + } + }() + + p.buffer, _ = ioutil.ReadAll(p.reader) + + l := len(p.buffer) + if l < 40 { + panic(errors.New("not enough data")) + } + + if !bytes.Equal(p.buffer[0:6], []byte{'b', 'p', 'l', 'i', 's', 't'}) { + panic(errors.New("incomprehensible magic")) + } + + p.version = int(((p.buffer[6] - '0') * 10) + (p.buffer[7] - '0')) + + if p.version > 1 { + panic(fmt.Errorf("unexpected version %d", p.version)) + } + + p.trailerOffset = uint64(l - 32) + p.trailer = bplistTrailer{ + SortVersion: p.buffer[p.trailerOffset+5], + OffsetIntSize: p.buffer[p.trailerOffset+6], + ObjectRefSize: p.buffer[p.trailerOffset+7], + NumObjects: binary.BigEndian.Uint64(p.buffer[p.trailerOffset+8:]), + TopObject: binary.BigEndian.Uint64(p.buffer[p.trailerOffset+16:]), + OffsetTableOffset: binary.BigEndian.Uint64(p.buffer[p.trailerOffset+24:]), + } + + p.validateDocumentTrailer() + + // INVARIANTS: + // - Entire offset table is before trailer + // - Offset table begins after header + // - Offset table can address entire document + // - Object IDs are big enough to support the number of objects in this plist + // - Top object is in range + + p.objects = make([]cfValue, p.trailer.NumObjects) + + pval = p.objectAtIndex(p.trailer.TopObject) + return +} + +// parseSizedInteger returns a 128-bit integer as low64, high64 +func (p *bplistParser) parseSizedInteger(off offset, nbytes int) (uint64, uint64, offset) { + switch nbytes { + case 1: + return uint64(p.buffer[off]), 0, off + offset(nbytes) + case 2: + return uint64(binary.BigEndian.Uint16(p.buffer[off:])), 0, off + offset(nbytes) + case 4: + return uint64(binary.BigEndian.Uint32(p.buffer[off:])), 0, off + offset(nbytes) + case 8: + return binary.BigEndian.Uint64(p.buffer[off:]), 0, off + offset(nbytes) + case 16: + return binary.BigEndian.Uint64(p.buffer[off+8:]), binary.BigEndian.Uint64(p.buffer[off:]), off + offset(nbytes) + } + panic(errors.New("illegal integer size")) +} + +func (p *bplistParser) parseObjectRefAtOffset(off offset) (uint64, offset) { + oid, _, next := p.parseSizedInteger(off, int(p.trailer.ObjectRefSize)) + return oid, next +} + +func (p *bplistParser) parseOffsetAtOffset(off offset) (offset, offset) { + parsedOffset, _, next := p.parseSizedInteger(off, int(p.trailer.OffsetIntSize)) + return offset(parsedOffset), next +} + +func (p *bplistParser) objectAtIndex(index uint64) cfValue { + if index >= p.trailer.NumObjects { + panic(fmt.Errorf("invalid object#%d (max %d)", index, p.trailer.NumObjects)) + } + + if pval := p.objects[index]; pval != nil { + return pval + } + + off, _ := p.parseOffsetAtOffset(offset(p.trailer.OffsetTableOffset + (index * uint64(p.trailer.OffsetIntSize)))) + if off > offset(p.trailer.OffsetTableOffset-1) { + panic(fmt.Errorf("object#%d starts beyond beginning of object table (0x%x, table@0x%x)", index, off, p.trailer.OffsetTableOffset)) + } + + pval := p.parseTagAtOffset(off) + p.objects[index] = pval + return pval + +} + +func (p *bplistParser) pushNestedObject(off offset) { + for _, v := range p.containerStack { + if v == off { + p.panicNestedObject(off) + } + } + p.containerStack = append(p.containerStack, off) +} + +func (p *bplistParser) panicNestedObject(off offset) { + ids := "" + for _, v := range p.containerStack { + ids += fmt.Sprintf("0x%x > ", v) + } + + // %s0x%d: ids above ends with " > " + panic(fmt.Errorf("self-referential collection@0x%x (%s0x%x) cannot be deserialized", off, ids, off)) +} + +func (p *bplistParser) popNestedObject() { + p.containerStack = p.containerStack[:len(p.containerStack)-1] +} + +func (p *bplistParser) parseTagAtOffset(off offset) cfValue { + tag := p.buffer[off] + + switch tag & 0xF0 { + case bpTagNull: + switch tag & 0x0F { + case bpTagBoolTrue, bpTagBoolFalse: + return cfBoolean(tag == bpTagBoolTrue) + } + case bpTagInteger: + lo, hi, _ := p.parseIntegerAtOffset(off) + return &cfNumber{ + signed: hi == 0xFFFFFFFFFFFFFFFF, // a signed integer is stored as a 128-bit integer with the top 64 bits set + value: lo, + } + case bpTagReal: + nbytes := 1 << (tag & 0x0F) + switch nbytes { + case 4: + bits := binary.BigEndian.Uint32(p.buffer[off+1:]) + return &cfReal{wide: false, value: float64(math.Float32frombits(bits))} + case 8: + bits := binary.BigEndian.Uint64(p.buffer[off+1:]) + return &cfReal{wide: true, value: math.Float64frombits(bits)} + } + panic(errors.New("illegal float size")) + case bpTagDate: + bits := binary.BigEndian.Uint64(p.buffer[off+1:]) + val := math.Float64frombits(bits) + + // Apple Epoch is 20110101000000Z + // Adjust for UNIX Time + val += 978307200 + + sec, fsec := math.Modf(val) + time := time.Unix(int64(sec), int64(fsec*float64(time.Second))).In(time.UTC) + return cfDate(time) + case bpTagData: + data := p.parseDataAtOffset(off) + return cfData(data) + case bpTagASCIIString: + str := p.parseASCIIStringAtOffset(off) + return cfString(str) + case bpTagUTF16String: + str := p.parseUTF16StringAtOffset(off) + return cfString(str) + case bpTagUID: // Somehow different than int: low half is nbytes - 1 instead of log2(nbytes) + lo, _, _ := p.parseSizedInteger(off+1, int(tag&0xF)+1) + return cfUID(lo) + case bpTagDictionary: + return p.parseDictionaryAtOffset(off) + case bpTagArray: + return p.parseArrayAtOffset(off) + } + panic(fmt.Errorf("unexpected atom 0x%2.02x at offset 0x%x", tag, off)) +} + +func (p *bplistParser) parseIntegerAtOffset(off offset) (uint64, uint64, offset) { + tag := p.buffer[off] + return p.parseSizedInteger(off+1, 1<<(tag&0xF)) +} + +func (p *bplistParser) countForTagAtOffset(off offset) (uint64, offset) { + tag := p.buffer[off] + cnt := uint64(tag & 0x0F) + if cnt == 0xF { + cnt, _, off = p.parseIntegerAtOffset(off + 1) + return cnt, off + } + return cnt, off + 1 +} + +func (p *bplistParser) parseDataAtOffset(off offset) []byte { + len, start := p.countForTagAtOffset(off) + if start+offset(len) > offset(p.trailer.OffsetTableOffset) { + panic(fmt.Errorf("data@0x%x too long (%v bytes, max is %v)", off, len, p.trailer.OffsetTableOffset-uint64(start))) + } + return p.buffer[start : start+offset(len)] +} + +func (p *bplistParser) parseASCIIStringAtOffset(off offset) string { + len, start := p.countForTagAtOffset(off) + if start+offset(len) > offset(p.trailer.OffsetTableOffset) { + panic(fmt.Errorf("ascii string@0x%x too long (%v bytes, max is %v)", off, len, p.trailer.OffsetTableOffset-uint64(start))) + } + + return zeroCopy8BitString(p.buffer, int(start), int(len)) +} + +func (p *bplistParser) parseUTF16StringAtOffset(off offset) string { + len, start := p.countForTagAtOffset(off) + bytes := len * 2 + if start+offset(bytes) > offset(p.trailer.OffsetTableOffset) { + panic(fmt.Errorf("utf16 string@0x%x too long (%v bytes, max is %v)", off, bytes, p.trailer.OffsetTableOffset-uint64(start))) + } + + u16s := make([]uint16, len) + for i := offset(0); i < offset(len); i++ { + u16s[i] = binary.BigEndian.Uint16(p.buffer[start+(i*2):]) + } + runes := utf16.Decode(u16s) + return string(runes) +} + +func (p *bplistParser) parseObjectListAtOffset(off offset, count uint64) []cfValue { + if off+offset(count*uint64(p.trailer.ObjectRefSize)) > offset(p.trailer.OffsetTableOffset) { + panic(fmt.Errorf("list@0x%x length (%v) puts its end beyond the offset table at 0x%x", off, count, p.trailer.OffsetTableOffset)) + } + objects := make([]cfValue, count) + + next := off + var oid uint64 + for i := uint64(0); i < count; i++ { + oid, next = p.parseObjectRefAtOffset(next) + objects[i] = p.objectAtIndex(oid) + } + + return objects +} + +func (p *bplistParser) parseDictionaryAtOffset(off offset) *cfDictionary { + p.pushNestedObject(off) + defer p.popNestedObject() + + // a dictionary is an object list of [key key key val val val] + cnt, start := p.countForTagAtOffset(off) + objects := p.parseObjectListAtOffset(start, cnt*2) + + keys := make([]string, cnt) + for i := uint64(0); i < cnt; i++ { + if str, ok := objects[i].(cfString); ok { + keys[i] = string(str) + } else { + panic(fmt.Errorf("dictionary@0x%x contains non-string key at index %d", off, i)) + } + } + + return &cfDictionary{ + keys: keys, + values: objects[cnt:], + } +} + +func (p *bplistParser) parseArrayAtOffset(off offset) *cfArray { + p.pushNestedObject(off) + defer p.popNestedObject() + + // an array is just an object list + cnt, start := p.countForTagAtOffset(off) + return &cfArray{p.parseObjectListAtOffset(start, cnt)} +} + +func newBplistParser(r io.ReadSeeker) *bplistParser { + return &bplistParser{reader: r} +} diff --git a/vendor/github.com/DHowett/go-plist/decode.go b/vendor/github.com/DHowett/go-plist/decode.go index a927d6d..4c64667 100644 --- a/vendor/github.com/DHowett/go-plist/decode.go +++ b/vendor/github.com/DHowett/go-plist/decode.go @@ -8,7 +8,7 @@ import ( ) type parser interface { - parseDocument() (*plistValue, error) + parseDocument() (cfValue, error) } // A Decoder reads a property list from an input stream. @@ -38,7 +38,7 @@ func (p *Decoder) Decode(v interface{}) (err error) { p.reader.Seek(0, 0) var parser parser - var pval *plistValue + var pval cfValue if bytes.Equal(header, []byte("bplist")) { parser = newBplistParser(p.reader) pval, err = parser.parseDocument() @@ -93,6 +93,7 @@ func NewDecoder(r io.ReadSeeker) *Decoder { // in the interface value. If the interface value is nil, Unmarshal stores one of the following in the interface value: // // string, bool, uint64, float64 +// plist.UID for "CoreFoundation Keyed Archiver UIDs" (convertible to uint64) // []byte, for plist data // []interface{}, for plist arrays // map[string]interface{}, for plist dictionaries diff --git a/vendor/github.com/DHowett/go-plist/encode.go b/vendor/github.com/DHowett/go-plist/encode.go index 743da9c..f81309b 100644 --- a/vendor/github.com/DHowett/go-plist/encode.go +++ b/vendor/github.com/DHowett/go-plist/encode.go @@ -9,7 +9,7 @@ import ( ) type generator interface { - generateDocument(*plistValue) + generateDocument(cfValue) Indent(string) } diff --git a/vendor/github.com/DHowett/go-plist/marshal.go b/vendor/github.com/DHowett/go-plist/marshal.go index 81e79b1..c461dfb 100644 --- a/vendor/github.com/DHowett/go-plist/marshal.go +++ b/vendor/github.com/DHowett/go-plist/marshal.go @@ -25,45 +25,76 @@ func isEmptyValue(v reflect.Value) bool { } var ( - textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() - timeType = reflect.TypeOf((*time.Time)(nil)).Elem() + plistMarshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() + textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() + timeType = reflect.TypeOf((*time.Time)(nil)).Elem() ) -func (p *Encoder) marshalTextInterface(marshalable encoding.TextMarshaler) *plistValue { +func implementsInterface(val reflect.Value, interfaceType reflect.Type) (interface{}, bool) { + if val.CanInterface() && val.Type().Implements(interfaceType) { + return val.Interface(), true + } + + if val.CanAddr() { + pv := val.Addr() + if pv.CanInterface() && pv.Type().Implements(interfaceType) { + return pv.Interface(), true + } + } + return nil, false +} + +func (p *Encoder) marshalPlistInterface(marshalable Marshaler) cfValue { + value, err := marshalable.MarshalPlist() + if err != nil { + panic(err) + } + return p.marshal(reflect.ValueOf(value)) +} + +// marshalTextInterface marshals a TextMarshaler to a plist string. +func (p *Encoder) marshalTextInterface(marshalable encoding.TextMarshaler) cfValue { s, err := marshalable.MarshalText() if err != nil { panic(err) } - return &plistValue{String, string(s)} + return cfString(s) } -func (p *Encoder) marshalStruct(typ reflect.Type, val reflect.Value) *plistValue { +// marshalStruct marshals a reflected struct value to a plist dictionary +func (p *Encoder) marshalStruct(typ reflect.Type, val reflect.Value) cfValue { tinfo, _ := getTypeInfo(typ) - dict := &dictionary{ - m: make(map[string]*plistValue, len(tinfo.fields)), + dict := &cfDictionary{ + keys: make([]string, 0, len(tinfo.fields)), + values: make([]cfValue, 0, len(tinfo.fields)), } for _, finfo := range tinfo.fields { value := finfo.value(val) if !value.IsValid() || finfo.omitEmpty && isEmptyValue(value) { continue } - dict.m[finfo.name] = p.marshal(value) + dict.keys = append(dict.keys, finfo.name) + dict.values = append(dict.values, p.marshal(value)) } - return &plistValue{Dictionary, dict} + return dict } -func (p *Encoder) marshalTime(val reflect.Value) *plistValue { +func (p *Encoder) marshalTime(val reflect.Value) cfValue { time := val.Interface().(time.Time) - return &plistValue{Date, time} + return cfDate(time) } -func (p *Encoder) marshal(val reflect.Value) *plistValue { +func (p *Encoder) marshal(val reflect.Value) cfValue { if !val.IsValid() { return nil } + if receiver, can := implementsInterface(val, plistMarshalerType); can { + return p.marshalPlistInterface(receiver.(Marshaler)) + } + // time.Time implements TextMarshaler, but we need to store it in RFC3339 if val.Type() == timeType { return p.marshalTime(val) @@ -76,14 +107,8 @@ func (p *Encoder) marshal(val reflect.Value) *plistValue { } // Check for text marshaler. - if val.CanInterface() && val.Type().Implements(textMarshalerType) { - return p.marshalTextInterface(val.Interface().(encoding.TextMarshaler)) - } - if val.CanAddr() { - pv := val.Addr() - if pv.CanInterface() && pv.Type().Implements(textMarshalerType) { - return p.marshalTextInterface(pv.Interface().(encoding.TextMarshaler)) - } + if receiver, can := implementsInterface(val, textMarshalerType); can { + return p.marshalTextInterface(receiver.(encoding.TextMarshaler)) } // Descend into pointers or interfaces @@ -98,21 +123,27 @@ func (p *Encoder) marshal(val reflect.Value) *plistValue { typ := val.Type() + if typ == uidType { + return cfUID(val.Uint()) + } + if val.Kind() == reflect.Struct { return p.marshalStruct(typ, val) } switch val.Kind() { case reflect.String: - return &plistValue{String, val.String()} + return cfString(val.String()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return &plistValue{Integer, signedInt{uint64(val.Int()), true}} + return &cfNumber{signed: true, value: uint64(val.Int())} case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return &plistValue{Integer, signedInt{uint64(val.Uint()), false}} - case reflect.Float32, reflect.Float64: - return &plistValue{Real, sizedFloat{val.Float(), val.Type().Bits()}} + return &cfNumber{signed: false, value: val.Uint()} + case reflect.Float32: + return &cfReal{wide: false, value: val.Float()} + case reflect.Float64: + return &cfReal{wide: true, value: val.Float()} case reflect.Bool: - return &plistValue{Boolean, val.Bool()} + return cfBoolean(val.Bool()) case reflect.Slice, reflect.Array: if typ.Elem().Kind() == reflect.Uint8 { bytes := []byte(nil) @@ -122,15 +153,15 @@ func (p *Encoder) marshal(val reflect.Value) *plistValue { bytes = make([]byte, val.Len()) reflect.Copy(reflect.ValueOf(bytes), val) } - return &plistValue{Data, bytes} + return cfData(bytes) } else { - subvalues := make([]*plistValue, val.Len()) - for idx, length := 0, val.Len(); idx < length; idx++ { - if subpval := p.marshal(val.Index(idx)); subpval != nil { - subvalues[idx] = subpval + values := make([]cfValue, val.Len()) + for i, length := 0, val.Len(); i < length; i++ { + if subpval := p.marshal(val.Index(i)); subpval != nil { + values[i] = subpval } } - return &plistValue{Array, subvalues} + return &cfArray{values} } case reflect.Map: if typ.Key().Kind() != reflect.String { @@ -138,17 +169,18 @@ func (p *Encoder) marshal(val reflect.Value) *plistValue { } l := val.Len() - dict := &dictionary{ - m: make(map[string]*plistValue, l), + dict := &cfDictionary{ + keys: make([]string, 0, l), + values: make([]cfValue, 0, l), } for _, keyv := range val.MapKeys() { if subpval := p.marshal(val.MapIndex(keyv)); subpval != nil { - dict.m[keyv.String()] = subpval + dict.keys = append(dict.keys, keyv.String()) + dict.values = append(dict.values, subpval) } } - return &plistValue{Dictionary, dict} + return dict default: panic(&unknownTypeError{typ}) } - return nil } diff --git a/vendor/github.com/DHowett/go-plist/plist.go b/vendor/github.com/DHowett/go-plist/plist.go index 17201fd..a4078b2 100644 --- a/vendor/github.com/DHowett/go-plist/plist.go +++ b/vendor/github.com/DHowett/go-plist/plist.go @@ -2,7 +2,6 @@ package plist import ( "reflect" - "sort" ) // Property list format constants @@ -27,85 +26,6 @@ var FormatNames = map[int]string{ GNUStepFormat: "GNUStep", } -type plistKind uint - -const ( - Invalid plistKind = iota - Dictionary - Array - String - Integer - Real - Boolean - Data - Date -) - -var plistKindNames map[plistKind]string = map[plistKind]string{ - Invalid: "invalid", - Dictionary: "dictionary", - Array: "array", - String: "string", - Integer: "integer", - Real: "real", - Boolean: "boolean", - Data: "data", - Date: "date", -} - -type plistValue struct { - kind plistKind - value interface{} -} - -type signedInt struct { - value uint64 - signed bool -} - -type sizedFloat struct { - value float64 - bits int -} - -type dictionary struct { - count int - m map[string]*plistValue - keys sort.StringSlice - values []*plistValue -} - -func (d *dictionary) Len() int { - return d.count -} - -func (d *dictionary) Less(i, j int) bool { - return d.keys.Less(i, j) -} - -func (d *dictionary) Swap(i, j int) { - d.keys.Swap(i, j) - d.values[i], d.values[j] = d.values[j], d.values[i] -} - -func (d *dictionary) populateArrays() { - if d.count > 0 { - return - } - - l := len(d.m) - d.count = l - d.keys = make([]string, l) - d.values = make([]*plistValue, l) - i := 0 - for k, v := range d.m { - d.keys[i] = k - d.values[i] = v - i++ - } - sort.Sort(d) -} - type unknownTypeError struct { typ reflect.Type } @@ -139,3 +59,27 @@ func (e plistParseError) Error() string { } return s } + +// A UID represents a unique object identifier. UIDs are serialized in a manner distinct from +// that of integers. +// +// UIDs cannot be serialized in OpenStepFormat or GNUStepFormat property lists. +type UID uint64 + +// Marshaler is the interface implemented by types that can marshal themselves into valid +// property list objects. The returned value is marshaled in place of the original value +// implementing Marshaler +// +// If an error is returned by MarshalPlist, marshaling stops and the error is returned. +type Marshaler interface { + MarshalPlist() (interface{}, error) +} + +// Unmarshaler is the interface implemented by types that can unmarshal themselves from +// property list objects. The UnmarshalPlist method receives a function that may +// be called to unmarshal the original property list value into a field or variable. +// +// It is safe to call the unmarshal function more than once. +type Unmarshaler interface { + UnmarshalPlist(unmarshal func(interface{}) error) error +} diff --git a/vendor/github.com/DHowett/go-plist/plist_types.go b/vendor/github.com/DHowett/go-plist/plist_types.go new file mode 100644 index 0000000..954ef34 --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/plist_types.go @@ -0,0 +1,139 @@ +package plist + +import ( + "hash/crc32" + "sort" + "time" +) + +type cfValue interface { + typeName() string + hash() interface{} +} + +type cfDictionary struct { + keys sort.StringSlice + values []cfValue +} + +func (*cfDictionary) typeName() string { + return "dictionary" +} + +func (p *cfDictionary) hash() interface{} { + return p +} + +func (p *cfDictionary) Len() int { + return len(p.keys) +} + +func (p *cfDictionary) Less(i, j int) bool { + return p.keys.Less(i, j) +} + +func (p *cfDictionary) Swap(i, j int) { + p.keys.Swap(i, j) + p.values[i], p.values[j] = p.values[j], p.values[i] +} + +func (p *cfDictionary) sort() { + sort.Sort(p) +} + +type cfArray struct { + values []cfValue +} + +func (*cfArray) typeName() string { + return "array" +} + +func (p *cfArray) hash() interface{} { + return p +} + +type cfString string + +func (cfString) typeName() string { + return "string" +} + +func (p cfString) hash() interface{} { + return string(p) +} + +type cfNumber struct { + signed bool + value uint64 +} + +func (*cfNumber) typeName() string { + return "integer" +} + +func (p *cfNumber) hash() interface{} { + if p.signed { + return int64(p.value) + } + return p.value +} + +type cfReal struct { + wide bool + value float64 +} + +func (cfReal) typeName() string { + return "real" +} + +func (p *cfReal) hash() interface{} { + if p.wide { + return p.value + } + return float32(p.value) +} + +type cfBoolean bool + +func (cfBoolean) typeName() string { + return "boolean" +} + +func (p cfBoolean) hash() interface{} { + return bool(p) +} + +type cfUID UID + +func (cfUID) typeName() string { + return "UID" +} + +func (p cfUID) hash() interface{} { + return p +} + +type cfData []byte + +func (cfData) typeName() string { + return "data" +} + +func (p cfData) hash() interface{} { + // Data are uniqued by their checksums. + // Todo: Look at calculating this only once and storing it somewhere; + // crc32 is fairly quick, however. + return crc32.ChecksumIEEE([]byte(p)) +} + +type cfDate time.Time + +func (cfDate) typeName() string { + return "date" +} + +func (p cfDate) hash() interface{} { + return time.Time(p) +} diff --git a/vendor/github.com/DHowett/go-plist/text.go b/vendor/github.com/DHowett/go-plist/text.go deleted file mode 100644 index 60cb0db..0000000 --- a/vendor/github.com/DHowett/go-plist/text.go +++ /dev/null @@ -1,569 +0,0 @@ -package plist - -import ( - "bufio" - "encoding/hex" - "errors" - "io" - "runtime" - "strconv" - "strings" - "time" -) - -type textPlistGenerator struct { - writer io.Writer - format int - - quotableTable *[4]uint64 - - indent string - depth int - - dictKvDelimiter, dictEntryDelimiter, arrayDelimiter []byte -} - -var ( - textPlistTimeLayout = "2006-01-02 15:04:05 -0700" - padding = "0000" -) - -func (p *textPlistGenerator) generateDocument(pval *plistValue) { - p.writePlistValue(pval) -} - -func (p *textPlistGenerator) plistQuotedString(str string) string { - if str == "" { - return `""` - } - s := "" - quot := false - for _, r := range str { - if r > 0xFF { - quot = true - s += `\U` - us := strconv.FormatInt(int64(r), 16) - s += padding[len(us):] - s += us - } else if r > 0x7F { - quot = true - s += `\` - us := strconv.FormatInt(int64(r), 8) - s += padding[1+len(us):] - s += us - } else { - c := uint8(r) - if (*p.quotableTable)[c/64]&(1<<(c%64)) > 0 { - quot = true - } - - switch c { - case '\a': - s += `\a` - case '\b': - s += `\b` - case '\v': - s += `\v` - case '\f': - s += `\f` - case '\\': - s += `\\` - case '"': - s += `\"` - case '\t', '\r', '\n': - fallthrough - default: - s += string(c) - } - } - } - if quot { - s = `"` + s + `"` - } - return s -} - -func (p *textPlistGenerator) deltaIndent(depthDelta int) { - if depthDelta < 0 { - p.depth-- - } else if depthDelta > 0 { - p.depth++ - } -} - -func (p *textPlistGenerator) writeIndent() { - if len(p.indent) == 0 { - return - } - if len(p.indent) > 0 { - p.writer.Write([]byte("\n")) - for i := 0; i < p.depth; i++ { - io.WriteString(p.writer, p.indent) - } - } -} - -func (p *textPlistGenerator) writePlistValue(pval *plistValue) { - if pval == nil { - return - } - - switch pval.kind { - case Dictionary: - p.writer.Write([]byte(`{`)) - p.deltaIndent(1) - dict := pval.value.(*dictionary) - dict.populateArrays() - for i, k := range dict.keys { - p.writeIndent() - io.WriteString(p.writer, p.plistQuotedString(k)) - p.writer.Write(p.dictKvDelimiter) - p.writePlistValue(dict.values[i]) - p.writer.Write(p.dictEntryDelimiter) - } - p.deltaIndent(-1) - p.writeIndent() - p.writer.Write([]byte(`}`)) - case Array: - p.writer.Write([]byte(`(`)) - p.deltaIndent(1) - values := pval.value.([]*plistValue) - for _, v := range values { - p.writeIndent() - p.writePlistValue(v) - p.writer.Write(p.arrayDelimiter) - } - p.deltaIndent(-1) - p.writeIndent() - p.writer.Write([]byte(`)`)) - case String: - io.WriteString(p.writer, p.plistQuotedString(pval.value.(string))) - case Integer: - if p.format == GNUStepFormat { - p.writer.Write([]byte(`<*I`)) - } - if pval.value.(signedInt).signed { - io.WriteString(p.writer, strconv.FormatInt(int64(pval.value.(signedInt).value), 10)) - } else { - io.WriteString(p.writer, strconv.FormatUint(pval.value.(signedInt).value, 10)) - } - if p.format == GNUStepFormat { - p.writer.Write([]byte(`>`)) - } - case Real: - if p.format == GNUStepFormat { - p.writer.Write([]byte(`<*R`)) - } - io.WriteString(p.writer, strconv.FormatFloat(pval.value.(sizedFloat).value, 'g', -1, 64)) - if p.format == GNUStepFormat { - p.writer.Write([]byte(`>`)) - } - case Boolean: - b := pval.value.(bool) - if p.format == GNUStepFormat { - if b { - p.writer.Write([]byte(`<*BY>`)) - } else { - p.writer.Write([]byte(`<*BN>`)) - } - } else { - if b { - p.writer.Write([]byte(`1`)) - } else { - p.writer.Write([]byte(`0`)) - } - } - case Data: - b := pval.value.([]byte) - var hexencoded [9]byte - var l int - var asc = 9 - hexencoded[8] = ' ' - - p.writer.Write([]byte(`<`)) - for i := 0; i < len(b); i += 4 { - l = i + 4 - if l >= len(b) { - l = len(b) - // We no longer need the space - or the rest of the buffer. - // (we used >= above to get this part without another conditional :P) - asc = (l - i) * 2 - } - // Fill the buffer (only up to 8 characters, to preserve the space we implicitly include - // at the end of every encode) - hex.Encode(hexencoded[:8], b[i:l]) - io.WriteString(p.writer, string(hexencoded[:asc])) - } - p.writer.Write([]byte(`>`)) - case Date: - if p.format == GNUStepFormat { - p.writer.Write([]byte(`<*D`)) - io.WriteString(p.writer, pval.value.(time.Time).In(time.UTC).Format(textPlistTimeLayout)) - p.writer.Write([]byte(`>`)) - } else { - io.WriteString(p.writer, p.plistQuotedString(pval.value.(time.Time).In(time.UTC).Format(textPlistTimeLayout))) - } - } -} - -func (p *textPlistGenerator) Indent(i string) { - p.indent = i - if i == "" { - p.dictKvDelimiter = []byte(`=`) - } else { - // For pretty-printing - p.dictKvDelimiter = []byte(` = `) - } -} - -func newTextPlistGenerator(w io.Writer, format int) *textPlistGenerator { - table := &osQuotable - if format == GNUStepFormat { - table = &gsQuotable - } - return &textPlistGenerator{ - writer: mustWriter{w}, - format: format, - quotableTable: table, - dictKvDelimiter: []byte(`=`), - arrayDelimiter: []byte(`,`), - dictEntryDelimiter: []byte(`;`), - } -} - -type byteReader interface { - io.Reader - io.ByteScanner - Peek(n int) ([]byte, error) - ReadBytes(delim byte) ([]byte, error) -} - -type textPlistParser struct { - reader byteReader - whitespaceReplacer *strings.Replacer - format int -} - -func (p *textPlistParser) parseDocument() (pval *plistValue, parseError error) { - defer func() { - if r := recover(); r != nil { - if _, ok := r.(runtime.Error); ok { - panic(r) - } - if _, ok := r.(invalidPlistError); ok { - parseError = r.(error) - } else { - // Wrap all non-invalid-plist errors. - parseError = plistParseError{"text", r.(error)} - } - } - }() - pval = p.parsePlistValue() - return -} - -func (p *textPlistParser) chugWhitespace() { -ws: - for { - c, err := p.reader.ReadByte() - if err != nil && err != io.EOF { - panic(err) - } - if whitespace[c/64]&(1<<(c%64)) == 0 { - if c == '/' && err != io.EOF { - // A / at the end of the file is not the begining of a comment. - cs, err := p.reader.Peek(1) - if err != nil && err != io.EOF { - panic(err) - } - c = cs[0] - switch c { - case '/': - for { - c, err = p.reader.ReadByte() - if err != nil && err != io.EOF { - panic(err) - } else if err == io.EOF { - break - } - // TODO: UTF-8 - if c == '\n' || c == '\r' { - break - } - } - case '*': - // Peek returned a value here, so it is safe to read. - _, _ = p.reader.ReadByte() - star := false - for { - c, err = p.reader.ReadByte() - if err != nil { - panic(err) - } - if c == '*' { - star = true - } else if c == '/' && star { - break - } else { - star = false - } - } - default: - p.reader.UnreadByte() // Not the beginning of a // or /* comment - break ws - } - continue - } - p.reader.UnreadByte() - break - } - } -} - -func (p *textPlistParser) parseQuotedString() *plistValue { - escaping := false - s := "" - for { - byt, err := p.reader.ReadByte() - // EOF here is an error: we're inside a quoted string! - if err != nil { - panic(err) - } - c := rune(byt) - if !escaping { - if c == '"' { - break - } else if c == '\\' { - escaping = true - continue - } - } else { - escaping = false - // Everything that is not listed here passes through unharmed. - switch c { - case 'a': - c = '\a' - case 'b': - c = '\b' - case 'v': - c = '\v' - case 'f': - c = '\f' - case 't': - c = '\t' - case 'r': - c = '\r' - case 'n': - c = '\n' - case 'x', 'u', 'U': // hex and unicode - l := 4 - if c == 'x' { - l = 2 - } - hex := make([]byte, l) - p.reader.Read(hex) - newc := mustParseInt(string(hex), 16, 16) - c = rune(newc) - case '0', '1', '2', '3', '4', '5', '6', '7': // octal! - oct := make([]byte, 3) - oct[0] = uint8(c) - p.reader.Read(oct[1:]) - newc := mustParseInt(string(oct), 8, 16) - c = rune(newc) - } - } - s += string(c) - } - return &plistValue{String, s} -} - -func (p *textPlistParser) parseUnquotedString() *plistValue { - s := "" - for { - c, err := p.reader.ReadByte() - if err != nil { - if err == io.EOF { - break - } - panic(err) - } - // if we encounter a character that must be quoted, we're done. - // the GNUStep quote table is more lax here, so we use it instead of the OpenStep one. - if gsQuotable[c/64]&(1<<(c%64)) > 0 { - p.reader.UnreadByte() - break - } - s += string(c) - } - return &plistValue{String, s} -} - -func (p *textPlistParser) parseDictionary() *plistValue { - var keypv *plistValue - subval := make(map[string]*plistValue) - for { - p.chugWhitespace() - - c, err := p.reader.ReadByte() - // EOF here is an error: we're inside a dictionary! - if err != nil { - panic(err) - } - - if c == '}' { - break - } else if c == '"' { - keypv = p.parseQuotedString() - } else { - p.reader.UnreadByte() // Whoops, ate part of the string - keypv = p.parseUnquotedString() - } - if keypv == nil { - // TODO better error - panic(errors.New("missing dictionary key")) - } - - p.chugWhitespace() - c, err = p.reader.ReadByte() - if err != nil { - panic(err) - } - - if c != '=' { - panic(errors.New("missing = in dictionary")) - } - - // whitespace is guzzled within - val := p.parsePlistValue() - - p.chugWhitespace() - c, err = p.reader.ReadByte() - if err != nil { - panic(err) - } - - if c != ';' { - panic(errors.New("missing ; in dictionary")) - } - - subval[keypv.value.(string)] = val - } - return &plistValue{Dictionary, &dictionary{m: subval}} -} - -func (p *textPlistParser) parseArray() *plistValue { - subval := make([]*plistValue, 0, 10) - for { - c, err := p.reader.ReadByte() - // EOF here is an error: we're inside an array! - if err != nil { - panic(err) - } - - if c == ')' { - break - } else if c == ',' { - continue - } - - p.reader.UnreadByte() - pval := p.parsePlistValue() - if pval.kind == String && pval.value.(string) == "" { - continue - } - subval = append(subval, pval) - } - return &plistValue{Array, subval} -} - -func (p *textPlistParser) parseGNUStepValue(v []byte) *plistValue { - if len(v) < 3 { - panic(errors.New("invalid GNUStep extended value")) - } - typ := v[1] - v = v[2:] - switch typ { - case 'I': - if v[0] == '-' { - n := mustParseInt(string(v), 10, 64) - return &plistValue{Integer, signedInt{uint64(n), true}} - } else { - n := mustParseUint(string(v), 10, 64) - return &plistValue{Integer, signedInt{n, false}} - } - case 'R': - n := mustParseFloat(string(v), 64) - return &plistValue{Real, sizedFloat{n, 64}} - case 'B': - b := v[0] == 'Y' - return &plistValue{Boolean, b} - case 'D': - t, err := time.Parse(textPlistTimeLayout, string(v)) - if err != nil { - panic(err) - } - - return &plistValue{Date, t.In(time.UTC)} - } - panic(errors.New("invalid GNUStep type " + string(typ))) - return nil -} - -func (p *textPlistParser) parsePlistValue() *plistValue { - for { - p.chugWhitespace() - - c, err := p.reader.ReadByte() - if err != nil && err != io.EOF { - panic(err) - } - switch c { - case '<': - bytes, err := p.reader.ReadBytes('>') - if err != nil { - panic(err) - } - bytes = bytes[:len(bytes)-1] - - if len(bytes) == 0 { - panic(errors.New("invalid empty angle-bracketed element")) - } - - if bytes[0] == '*' { - p.format = GNUStepFormat - return p.parseGNUStepValue(bytes) - } else { - s := p.whitespaceReplacer.Replace(string(bytes)) - data, err := hex.DecodeString(s) - if err != nil { - panic(err) - } - return &plistValue{Data, data} - } - case '"': - return p.parseQuotedString() - case '{': - return p.parseDictionary() - case '(': - return p.parseArray() - default: - p.reader.UnreadByte() // Place back in buffer for parseUnquotedString - return p.parseUnquotedString() - } - } - return nil -} - -func newTextPlistParser(r io.Reader) *textPlistParser { - var reader byteReader - if rd, ok := r.(byteReader); ok { - reader = rd - } else { - reader = bufio.NewReader(r) - } - return &textPlistParser{ - reader: reader, - whitespaceReplacer: strings.NewReplacer("\t", "", "\n", "", " ", "", "\r", ""), - format: OpenStepFormat, - } -} diff --git a/vendor/github.com/DHowett/go-plist/text_generator.go b/vendor/github.com/DHowett/go-plist/text_generator.go new file mode 100644 index 0000000..53078ba --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/text_generator.go @@ -0,0 +1,226 @@ +package plist + +import ( + "encoding/hex" + "io" + "strconv" + "time" +) + +type textPlistGenerator struct { + writer io.Writer + format int + + quotableTable *characterSet + + indent string + depth int + + dictKvDelimiter, dictEntryDelimiter, arrayDelimiter []byte +} + +var ( + textPlistTimeLayout = "2006-01-02 15:04:05 -0700" + padding = "0000" +) + +func (p *textPlistGenerator) generateDocument(pval cfValue) { + p.writePlistValue(pval) +} + +func (p *textPlistGenerator) plistQuotedString(str string) string { + if str == "" { + return `""` + } + s := "" + quot := false + for _, r := range str { + if r > 0xFF { + quot = true + s += `\U` + us := strconv.FormatInt(int64(r), 16) + s += padding[len(us):] + s += us + } else if r > 0x7F { + quot = true + s += `\` + us := strconv.FormatInt(int64(r), 8) + s += padding[1+len(us):] + s += us + } else { + c := uint8(r) + if p.quotableTable.ContainsByte(c) { + quot = true + } + + switch c { + case '\a': + s += `\a` + case '\b': + s += `\b` + case '\v': + s += `\v` + case '\f': + s += `\f` + case '\\': + s += `\\` + case '"': + s += `\"` + case '\t', '\r', '\n': + fallthrough + default: + s += string(c) + } + } + } + if quot { + s = `"` + s + `"` + } + return s +} + +func (p *textPlistGenerator) deltaIndent(depthDelta int) { + if depthDelta < 0 { + p.depth-- + } else if depthDelta > 0 { + p.depth++ + } +} + +func (p *textPlistGenerator) writeIndent() { + if len(p.indent) == 0 { + return + } + if len(p.indent) > 0 { + p.writer.Write([]byte("\n")) + for i := 0; i < p.depth; i++ { + io.WriteString(p.writer, p.indent) + } + } +} + +func (p *textPlistGenerator) writePlistValue(pval cfValue) { + if pval == nil { + return + } + + switch pval := pval.(type) { + case *cfDictionary: + pval.sort() + p.writer.Write([]byte(`{`)) + p.deltaIndent(1) + for i, k := range pval.keys { + p.writeIndent() + io.WriteString(p.writer, p.plistQuotedString(k)) + p.writer.Write(p.dictKvDelimiter) + p.writePlistValue(pval.values[i]) + p.writer.Write(p.dictEntryDelimiter) + } + p.deltaIndent(-1) + p.writeIndent() + p.writer.Write([]byte(`}`)) + case *cfArray: + p.writer.Write([]byte(`(`)) + p.deltaIndent(1) + for _, v := range pval.values { + p.writeIndent() + p.writePlistValue(v) + p.writer.Write(p.arrayDelimiter) + } + p.deltaIndent(-1) + p.writeIndent() + p.writer.Write([]byte(`)`)) + case cfString: + io.WriteString(p.writer, p.plistQuotedString(string(pval))) + case *cfNumber: + if p.format == GNUStepFormat { + p.writer.Write([]byte(`<*I`)) + } + if pval.signed { + io.WriteString(p.writer, strconv.FormatInt(int64(pval.value), 10)) + } else { + io.WriteString(p.writer, strconv.FormatUint(pval.value, 10)) + } + if p.format == GNUStepFormat { + p.writer.Write([]byte(`>`)) + } + case *cfReal: + if p.format == GNUStepFormat { + p.writer.Write([]byte(`<*R`)) + } + // GNUstep does not differentiate between 32/64-bit floats. + io.WriteString(p.writer, strconv.FormatFloat(pval.value, 'g', -1, 64)) + if p.format == GNUStepFormat { + p.writer.Write([]byte(`>`)) + } + case cfBoolean: + if p.format == GNUStepFormat { + if pval { + p.writer.Write([]byte(`<*BY>`)) + } else { + p.writer.Write([]byte(`<*BN>`)) + } + } else { + if pval { + p.writer.Write([]byte(`1`)) + } else { + p.writer.Write([]byte(`0`)) + } + } + case cfData: + var hexencoded [9]byte + var l int + var asc = 9 + hexencoded[8] = ' ' + + p.writer.Write([]byte(`<`)) + b := []byte(pval) + for i := 0; i < len(b); i += 4 { + l = i + 4 + if l >= len(b) { + l = len(b) + // We no longer need the space - or the rest of the buffer. + // (we used >= above to get this part without another conditional :P) + asc = (l - i) * 2 + } + // Fill the buffer (only up to 8 characters, to preserve the space we implicitly include + // at the end of every encode) + hex.Encode(hexencoded[:8], b[i:l]) + io.WriteString(p.writer, string(hexencoded[:asc])) + } + p.writer.Write([]byte(`>`)) + case cfDate: + if p.format == GNUStepFormat { + p.writer.Write([]byte(`<*D`)) + io.WriteString(p.writer, time.Time(pval).In(time.UTC).Format(textPlistTimeLayout)) + p.writer.Write([]byte(`>`)) + } else { + io.WriteString(p.writer, p.plistQuotedString(time.Time(pval).In(time.UTC).Format(textPlistTimeLayout))) + } + } +} + +func (p *textPlistGenerator) Indent(i string) { + p.indent = i + if i == "" { + p.dictKvDelimiter = []byte(`=`) + } else { + // For pretty-printing + p.dictKvDelimiter = []byte(` = `) + } +} + +func newTextPlistGenerator(w io.Writer, format int) *textPlistGenerator { + table := &osQuotable + if format == GNUStepFormat { + table = &gsQuotable + } + return &textPlistGenerator{ + writer: mustWriter{w}, + format: format, + quotableTable: table, + dictKvDelimiter: []byte(`=`), + arrayDelimiter: []byte(`,`), + dictEntryDelimiter: []byte(`;`), + } +} diff --git a/vendor/github.com/DHowett/go-plist/text_parser.go b/vendor/github.com/DHowett/go-plist/text_parser.go new file mode 100644 index 0000000..7e49d6f --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/text_parser.go @@ -0,0 +1,515 @@ +package plist + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "io/ioutil" + "runtime" + "strings" + "time" + "unicode/utf16" + "unicode/utf8" +) + +type textPlistParser struct { + reader io.Reader + format int + + input string + start int + pos int + width int +} + +func convertU16(buffer []byte, bo binary.ByteOrder) (string, error) { + if len(buffer)%2 != 0 { + return "", errors.New("truncated utf16") + } + + tmp := make([]uint16, len(buffer)/2) + for i := 0; i < len(buffer); i += 2 { + tmp[i/2] = bo.Uint16(buffer[i : i+2]) + } + return string(utf16.Decode(tmp)), nil +} + +func guessEncodingAndConvert(buffer []byte) (string, error) { + if len(buffer) >= 3 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF { + // UTF-8 BOM + return zeroCopy8BitString(buffer, 3, len(buffer)-3), nil + } else if len(buffer) >= 2 { + // UTF-16 guesses + + switch { + // stream is big-endian (BOM is FE FF or head is 00 XX) + case (buffer[0] == 0xFE && buffer[1] == 0xFF): + return convertU16(buffer[2:], binary.BigEndian) + case (buffer[0] == 0 && buffer[1] != 0): + return convertU16(buffer, binary.BigEndian) + + // stream is little-endian (BOM is FE FF or head is XX 00) + case (buffer[0] == 0xFF && buffer[1] == 0xFE): + return convertU16(buffer[2:], binary.LittleEndian) + case (buffer[0] != 0 && buffer[1] == 0): + return convertU16(buffer, binary.LittleEndian) + } + } + + // fallback: assume ASCII (not great!) + return zeroCopy8BitString(buffer, 0, len(buffer)), nil +} + +func (p *textPlistParser) parseDocument() (pval cfValue, parseError error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + // Wrap all non-invalid-plist errors. + parseError = plistParseError{"text", r.(error)} + } + }() + + buffer, err := ioutil.ReadAll(p.reader) + if err != nil { + panic(err) + } + + p.input, err = guessEncodingAndConvert(buffer) + if err != nil { + panic(err) + } + + val := p.parsePlistValue() + + p.skipWhitespaceAndComments() + if p.peek() != eof { + if _, ok := val.(cfString); !ok { + p.error("garbage after end of document") + } + + p.start = 0 + p.pos = 0 + val = p.parseDictionary(true) + } + + pval = val + + return +} + +const eof rune = -1 + +func (p *textPlistParser) error(e string, args ...interface{}) { + line := strings.Count(p.input[:p.pos], "\n") + char := p.pos - strings.LastIndex(p.input[:p.pos], "\n") - 1 + panic(fmt.Errorf("%s at line %d character %d", fmt.Sprintf(e, args...), line, char)) +} + +func (p *textPlistParser) next() rune { + if int(p.pos) >= len(p.input) { + p.width = 0 + return eof + } + r, w := utf8.DecodeRuneInString(p.input[p.pos:]) + p.width = w + p.pos += p.width + return r +} + +func (p *textPlistParser) backup() { + p.pos -= p.width +} + +func (p *textPlistParser) peek() rune { + r := p.next() + p.backup() + return r +} + +func (p *textPlistParser) emit() string { + s := p.input[p.start:p.pos] + p.start = p.pos + return s +} + +func (p *textPlistParser) ignore() { + p.start = p.pos +} + +func (p *textPlistParser) empty() bool { + return p.start == p.pos +} + +func (p *textPlistParser) scanUntil(ch rune) { + if x := strings.IndexRune(p.input[p.pos:], ch); x >= 0 { + p.pos += x + return + } + p.pos = len(p.input) +} + +func (p *textPlistParser) scanUntilAny(chs string) { + if x := strings.IndexAny(p.input[p.pos:], chs); x >= 0 { + p.pos += x + return + } + p.pos = len(p.input) +} + +func (p *textPlistParser) scanCharactersInSet(ch *characterSet) { + for ch.Contains(p.next()) { + } + p.backup() +} + +func (p *textPlistParser) scanCharactersNotInSet(ch *characterSet) { + var r rune + for { + r = p.next() + if r == eof || ch.Contains(r) { + break + } + } + p.backup() +} + +func (p *textPlistParser) skipWhitespaceAndComments() { + for { + p.scanCharactersInSet(&whitespace) + if strings.HasPrefix(p.input[p.pos:], "//") { + p.scanCharactersNotInSet(&newlineCharacterSet) + } else if strings.HasPrefix(p.input[p.pos:], "/*") { + if x := strings.Index(p.input[p.pos:], "*/"); x >= 0 { + p.pos += x + 2 // skip the */ as well + continue // consume more whitespace + } else { + p.error("unexpected eof in block comment") + } + } else { + break + } + } + p.ignore() +} + +func (p *textPlistParser) parseOctalDigits(max int) uint64 { + var val uint64 + + for i := 0; i < max; i++ { + r := p.next() + + if r >= '0' && r <= '7' { + val <<= 3 + val |= uint64((r - '0')) + } else { + p.backup() + break + } + } + return val +} + +func (p *textPlistParser) parseHexDigits(max int) uint64 { + var val uint64 + + for i := 0; i < max; i++ { + r := p.next() + + if r >= 'a' && r <= 'f' { + val <<= 4 + val |= 10 + uint64((r - 'a')) + } else if r >= 'A' && r <= 'F' { + val <<= 4 + val |= 10 + uint64((r - 'A')) + } else if r >= '0' && r <= '9' { + val <<= 4 + val |= uint64((r - '0')) + } else { + p.backup() + break + } + } + return val +} + +// the \ has already been consumed +func (p *textPlistParser) parseEscape() string { + var s string + switch p.next() { + case 'a': + s = "\a" + case 'b': + s = "\b" + case 'v': + s = "\v" + case 'f': + s = "\f" + case 't': + s = "\t" + case 'r': + s = "\r" + case 'n': + s = "\n" + case '\\': + s = `\` + case '"': + s = `"` + case 'x': + s = string(rune(p.parseHexDigits(2))) + case 'u', 'U': + s = string(rune(p.parseHexDigits(4))) + case '0', '1', '2', '3', '4', '5', '6', '7': + p.backup() // we've already consumed one of the digits + s = string(rune(p.parseOctalDigits(3))) + default: + p.backup() // everything else should be accepted + } + p.ignore() // skip the entire escape sequence + return s +} + +// the " has already been consumed +func (p *textPlistParser) parseQuotedString() cfString { + p.ignore() // ignore the " + + slowPath := false + s := "" + + for { + p.scanUntilAny(`"\`) + switch p.peek() { + case eof: + p.error("unexpected eof in quoted string") + case '"': + section := p.emit() + p.pos++ // skip " + if !slowPath { + return cfString(section) + } else { + s += section + return cfString(s) + } + case '\\': + slowPath = true + s += p.emit() + p.next() // consume \ + s += p.parseEscape() + } + } +} + +func (p *textPlistParser) parseUnquotedString() cfString { + p.scanCharactersNotInSet(&gsQuotable) + s := p.emit() + if s == "" { + p.error("invalid unquoted string (found an unquoted character that should be quoted?)") + } + + return cfString(s) +} + +// the { has already been consumed +func (p *textPlistParser) parseDictionary(ignoreEof bool) *cfDictionary { + //p.ignore() // ignore the { + var keypv cfValue + keys := make([]string, 0, 32) + values := make([]cfValue, 0, 32) +outer: + for { + p.skipWhitespaceAndComments() + + switch p.next() { + case eof: + if !ignoreEof { + p.error("unexpected eof in dictionary") + } + fallthrough + case '}': + break outer + case '"': + keypv = p.parseQuotedString() + default: + p.backup() + keypv = p.parseUnquotedString() + } + + // INVARIANT: key can't be nil; parseQuoted and parseUnquoted + // will panic out before they return nil. + + p.skipWhitespaceAndComments() + + var val cfValue + n := p.next() + if n == ';' { + val = keypv + } else if n == '=' { + // whitespace is consumed within + val = p.parsePlistValue() + + p.skipWhitespaceAndComments() + + if p.next() != ';' { + p.error("missing ; in dictionary") + } + } else { + p.error("missing = in dictionary") + } + + keys = append(keys, string(keypv.(cfString))) + values = append(values, val) + } + + return &cfDictionary{keys: keys, values: values} +} + +// the ( has already been consumed +func (p *textPlistParser) parseArray() *cfArray { + //p.ignore() // ignore the ( + values := make([]cfValue, 0, 32) +outer: + for { + p.skipWhitespaceAndComments() + + switch p.next() { + case eof: + p.error("unexpected eof in array") + case ')': + break outer // done here + case ',': + continue // restart; ,) is valid and we don't want to blow it + default: + p.backup() + } + + pval := p.parsePlistValue() // whitespace is consumed within + if str, ok := pval.(cfString); ok && string(str) == "" { + // Empty strings in arrays are apparently skipped? + // TODO: Figure out why this was implemented. + continue + } + values = append(values, pval) + } + return &cfArray{values} +} + +// the <* have already been consumed +func (p *textPlistParser) parseGNUStepValue() cfValue { + typ := p.next() + p.ignore() + p.scanUntil('>') + + if typ == eof || typ == '>' || p.empty() || p.peek() == eof { + p.error("invalid GNUStep extended value") + } + + v := p.emit() + p.next() // consume the > + + switch typ { + case 'I': + if v[0] == '-' { + n := mustParseInt(v, 10, 64) + return &cfNumber{signed: true, value: uint64(n)} + } else { + n := mustParseUint(v, 10, 64) + return &cfNumber{signed: false, value: n} + } + case 'R': + n := mustParseFloat(v, 64) + return &cfReal{wide: true, value: n} // TODO(DH) 32/64 + case 'B': + b := v[0] == 'Y' + return cfBoolean(b) + case 'D': + t, err := time.Parse(textPlistTimeLayout, v) + if err != nil { + p.error(err.Error()) + } + + return cfDate(t.In(time.UTC)) + } + p.error("invalid GNUStep type " + string(typ)) + return nil +} + +// The < has already been consumed +func (p *textPlistParser) parseHexData() cfData { + buf := make([]byte, 256) + i := 0 + c := 0 + + for { + r := p.next() + switch r { + case eof: + p.error("unexpected eof in data") + case '>': + if c&1 == 1 { + p.error("uneven number of hex digits in data") + } + p.ignore() + return cfData(buf[:i]) + case ' ', '\t', '\n', '\r', '\u2028', '\u2029': // more lax than apple here: skip spaces + continue + } + + buf[i] <<= 4 + if r >= 'a' && r <= 'f' { + buf[i] |= 10 + byte((r - 'a')) + } else if r >= 'A' && r <= 'F' { + buf[i] |= 10 + byte((r - 'A')) + } else if r >= '0' && r <= '9' { + buf[i] |= byte((r - '0')) + } else { + p.error("unexpected hex digit `%c'", r) + } + + c++ + if c&1 == 0 { + i++ + if i >= len(buf) { + realloc := make([]byte, len(buf)*2) + copy(realloc, buf) + buf = realloc + } + } + } +} + +func (p *textPlistParser) parsePlistValue() cfValue { + for { + p.skipWhitespaceAndComments() + + switch p.next() { + case eof: + return &cfDictionary{} + case '<': + if p.next() == '*' { + p.format = GNUStepFormat + return p.parseGNUStepValue() + } + + p.backup() + return p.parseHexData() + case '"': + return p.parseQuotedString() + case '{': + return p.parseDictionary(false) + case '(': + return p.parseArray() + default: + p.backup() + return p.parseUnquotedString() + } + } +} + +func newTextPlistParser(r io.Reader) *textPlistParser { + return &textPlistParser{ + reader: r, + format: OpenStepFormat, + } +} diff --git a/vendor/github.com/DHowett/go-plist/text_tables.go b/vendor/github.com/DHowett/go-plist/text_tables.go index ec6586b..319c55c 100644 --- a/vendor/github.com/DHowett/go-plist/text_tables.go +++ b/vendor/github.com/DHowett/go-plist/text_tables.go @@ -1,9 +1,19 @@ package plist +type characterSet [4]uint64 + +func (s *characterSet) Contains(ch rune) bool { + return ch >= 0 && ch <= 255 && s.ContainsByte(byte(ch)) +} + +func (s *characterSet) ContainsByte(ch byte) bool { + return (s[ch/64]&(1<<(ch%64)) > 0) +} + // Bitmap of characters that must be inside a quoted string // when written to an old-style property list // Low bits represent lower characters, and each uint64 represents 64 characters. -var gsQuotable = [4]uint64{ +var gsQuotable = characterSet{ 0x78001385ffffffff, 0xa800000138000000, 0xffffffffffffffff, @@ -11,16 +21,23 @@ var gsQuotable = [4]uint64{ } // 7f instead of 3f in the top line: CFOldStylePlist.c says . is valid, but they quote it. -var osQuotable = [4]uint64{ +var osQuotable = characterSet{ 0xf4007f6fffffffff, 0xf8000001f8000001, 0xffffffffffffffff, 0xffffffffffffffff, } -var whitespace = [4]uint64{ +var whitespace = characterSet{ 0x0000000100003f00, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, } + +var newlineCharacterSet = characterSet{ + 0x0000000000002400, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, +} diff --git a/vendor/github.com/DHowett/go-plist/unmarshal.go b/vendor/github.com/DHowett/go-plist/unmarshal.go index fc50ed4..e38cbe5 100644 --- a/vendor/github.com/DHowett/go-plist/unmarshal.go +++ b/vendor/github.com/DHowett/go-plist/unmarshal.go @@ -4,35 +4,57 @@ import ( "encoding" "fmt" "reflect" + "runtime" "time" ) type incompatibleDecodeTypeError struct { - typ reflect.Type - pKind plistKind + dest reflect.Type + src string // type name (from cfValue) } func (u *incompatibleDecodeTypeError) Error() string { - return fmt.Sprintf("plist: type mismatch: tried to decode %v into value of type %v", plistKindNames[u.pKind], u.typ) + return fmt.Sprintf("plist: type mismatch: tried to decode plist type `%v' into value of type `%v'", u.src, u.dest) } var ( - textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() + plistUnmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem() + textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() + uidType = reflect.TypeOf(UID(0)) ) func isEmptyInterface(v reflect.Value) bool { return v.Kind() == reflect.Interface && v.NumMethod() == 0 } -func (p *Decoder) unmarshalTextInterface(pval *plistValue, unmarshalable encoding.TextUnmarshaler) { - err := unmarshalable.UnmarshalText([]byte(pval.value.(string))) +func (p *Decoder) unmarshalPlistInterface(pval cfValue, unmarshalable Unmarshaler) { + err := unmarshalable.UnmarshalPlist(func(i interface{}) (err error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + err = r.(error) + } + }() + p.unmarshal(pval, reflect.ValueOf(i)) + return + }) + if err != nil { panic(err) } } -func (p *Decoder) unmarshalTime(pval *plistValue, val reflect.Value) { - val.Set(reflect.ValueOf(pval.value.(time.Time))) +func (p *Decoder) unmarshalTextInterface(pval cfString, unmarshalable encoding.TextUnmarshaler) { + err := unmarshalable.UnmarshalText([]byte(pval)) + if err != nil { + panic(err) + } +} + +func (p *Decoder) unmarshalTime(pval cfDate, val reflect.Value) { + val.Set(reflect.ValueOf(time.Time(pval))) } func (p *Decoder) unmarshalLaxString(s string, val reflect.Value) { @@ -64,11 +86,11 @@ func (p *Decoder) unmarshalLaxString(s string, val reflect.Value) { } fallthrough default: - panic(&incompatibleDecodeTypeError{val.Type(), String}) + panic(&incompatibleDecodeTypeError{val.Type(), "string"}) } } -func (p *Decoder) unmarshal(pval *plistValue, val reflect.Value) { +func (p *Decoder) unmarshal(pval cfValue, val reflect.Value) { if pval == nil { return } @@ -86,86 +108,101 @@ func (p *Decoder) unmarshal(pval *plistValue, val reflect.Value) { return } - incompatibleTypeError := &incompatibleDecodeTypeError{val.Type(), pval.kind} + incompatibleTypeError := &incompatibleDecodeTypeError{val.Type(), pval.typeName()} // time.Time implements TextMarshaler, but we need to parse it as RFC3339 - if pval.kind == Date { + if date, ok := pval.(cfDate); ok { if val.Type() == timeType { - p.unmarshalTime(pval, val) + p.unmarshalTime(date, val) return } panic(incompatibleTypeError) } - if val.CanInterface() && val.Type().Implements(textUnmarshalerType) && val.Type() != timeType { - p.unmarshalTextInterface(pval, val.Interface().(encoding.TextUnmarshaler)) + if receiver, can := implementsInterface(val, plistUnmarshalerType); can { + p.unmarshalPlistInterface(pval, receiver.(Unmarshaler)) return } - if val.CanAddr() { - pv := val.Addr() - if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) && val.Type() != timeType { - p.unmarshalTextInterface(pval, pv.Interface().(encoding.TextUnmarshaler)) + if val.Type() != timeType { + if receiver, can := implementsInterface(val, textUnmarshalerType); can { + if str, ok := pval.(cfString); ok { + p.unmarshalTextInterface(str, receiver.(encoding.TextUnmarshaler)) + } else { + panic(incompatibleTypeError) + } return } } typ := val.Type() - switch pval.kind { - case String: + switch pval := pval.(type) { + case cfString: if val.Kind() == reflect.String { - val.SetString(pval.value.(string)) + val.SetString(string(pval)) return } if p.lax { - p.unmarshalLaxString(pval.value.(string), val) + p.unmarshalLaxString(string(pval), val) return } panic(incompatibleTypeError) - case Integer: + case *cfNumber: switch val.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - val.SetInt(int64(pval.value.(signedInt).value)) + val.SetInt(int64(pval.value)) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - val.SetUint(pval.value.(signedInt).value) + val.SetUint(pval.value) default: panic(incompatibleTypeError) } - case Real: + case *cfReal: if val.Kind() == reflect.Float32 || val.Kind() == reflect.Float64 { - val.SetFloat(pval.value.(sizedFloat).value) + // TODO: Consider warning on a downcast (storing a 64-bit value in a 32-bit reflect) + val.SetFloat(pval.value) } else { panic(incompatibleTypeError) } - case Boolean: + case cfBoolean: if val.Kind() == reflect.Bool { - val.SetBool(pval.value.(bool)) + val.SetBool(bool(pval)) } else { panic(incompatibleTypeError) } - case Data: + case cfData: if val.Kind() == reflect.Slice && typ.Elem().Kind() == reflect.Uint8 { - val.SetBytes(pval.value.([]byte)) + val.SetBytes([]byte(pval)) } else { panic(incompatibleTypeError) } - case Array: + case cfUID: + if val.Type() == uidType { + val.SetUint(uint64(pval)) + } else { + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + val.SetInt(int64(pval)) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + val.SetUint(uint64(pval)) + default: + panic(incompatibleTypeError) + } + } + case *cfArray: p.unmarshalArray(pval, val) - case Dictionary: + case *cfDictionary: p.unmarshalDictionary(pval, val) } } -func (p *Decoder) unmarshalArray(pval *plistValue, val reflect.Value) { - subvalues := pval.value.([]*plistValue) - +func (p *Decoder) unmarshalArray(a *cfArray, val reflect.Value) { var n int if val.Kind() == reflect.Slice { // Slice of element values. // Grow slice. - cnt := len(subvalues) + val.Len() + cnt := len(a.values) + val.Len() if cnt >= val.Cap() { ncap := 2 * cnt if ncap < 4 { @@ -178,22 +215,22 @@ func (p *Decoder) unmarshalArray(pval *plistValue, val reflect.Value) { n = val.Len() val.SetLen(cnt) } else if val.Kind() == reflect.Array { - if len(subvalues) > val.Cap() { - panic(fmt.Errorf("plist: attempted to unmarshal %d values into an array of size %d", len(subvalues), val.Cap())) + if len(a.values) > val.Cap() { + panic(fmt.Errorf("plist: attempted to unmarshal %d values into an array of size %d", len(a.values), val.Cap())) } } else { - panic(&incompatibleDecodeTypeError{val.Type(), pval.kind}) + panic(&incompatibleDecodeTypeError{val.Type(), a.typeName()}) } // Recur to read element into slice. - for _, sval := range subvalues { + for _, sval := range a.values { p.unmarshal(sval, val.Index(n)) n++ } return } -func (p *Decoder) unmarshalDictionary(pval *plistValue, val reflect.Value) { +func (p *Decoder) unmarshalDictionary(dict *cfDictionary, val reflect.Value) { typ := val.Type() switch val.Kind() { case reflect.Struct: @@ -202,17 +239,23 @@ func (p *Decoder) unmarshalDictionary(pval *plistValue, val reflect.Value) { panic(err) } - subvalues := pval.value.(*dictionary).m + entries := make(map[string]cfValue, len(dict.keys)) + for i, k := range dict.keys { + sval := dict.values[i] + entries[k] = sval + } + for _, finfo := range tinfo.fields { - p.unmarshal(subvalues[finfo.name], finfo.value(val)) + p.unmarshal(entries[finfo.name], finfo.value(val)) } case reflect.Map: if val.IsNil() { val.Set(reflect.MakeMap(typ)) } - subvalues := pval.value.(*dictionary).m - for k, sval := range subvalues { + for i, k := range dict.keys { + sval := dict.values[i] + keyv := reflect.ValueOf(k).Convert(typ.Key()) mapElem := val.MapIndex(keyv) if !mapElem.IsValid() { @@ -223,53 +266,54 @@ func (p *Decoder) unmarshalDictionary(pval *plistValue, val reflect.Value) { val.SetMapIndex(keyv, mapElem) } default: - panic(&incompatibleDecodeTypeError{typ, pval.kind}) + panic(&incompatibleDecodeTypeError{typ, dict.typeName()}) } } /* *Interface is modelled after encoding/json */ -func (p *Decoder) valueInterface(pval *plistValue) interface{} { - switch pval.kind { - case String: - return pval.value.(string) - case Integer: - if pval.value.(signedInt).signed { - return int64(pval.value.(signedInt).value) +func (p *Decoder) valueInterface(pval cfValue) interface{} { + switch pval := pval.(type) { + case cfString: + return string(pval) + case *cfNumber: + if pval.signed { + return int64(pval.value) } - return pval.value.(signedInt).value - case Real: - bits := pval.value.(sizedFloat).bits - switch bits { - case 32: - return float32(pval.value.(sizedFloat).value) - case 64: - return pval.value.(sizedFloat).value + return pval.value + case *cfReal: + if pval.wide { + return pval.value + } else { + return float32(pval.value) } - case Boolean: - return pval.value.(bool) - case Array: - return p.arrayInterface(pval.value.([]*plistValue)) - case Dictionary: - return p.dictionaryInterface(pval.value.(*dictionary)) - case Data: - return pval.value.([]byte) - case Date: - return pval.value.(time.Time) + case cfBoolean: + return bool(pval) + case *cfArray: + return p.arrayInterface(pval) + case *cfDictionary: + return p.dictionaryInterface(pval) + case cfData: + return []byte(pval) + case cfDate: + return time.Time(pval) + case cfUID: + return UID(pval) } return nil } -func (p *Decoder) arrayInterface(subvalues []*plistValue) []interface{} { - out := make([]interface{}, len(subvalues)) - for i, subv := range subvalues { +func (p *Decoder) arrayInterface(a *cfArray) []interface{} { + out := make([]interface{}, len(a.values)) + for i, subv := range a.values { out[i] = p.valueInterface(subv) } return out } -func (p *Decoder) dictionaryInterface(dict *dictionary) map[string]interface{} { +func (p *Decoder) dictionaryInterface(dict *cfDictionary) map[string]interface{} { out := make(map[string]interface{}) - for k, subv := range dict.m { + for i, k := range dict.keys { + subv := dict.values[i] out[k] = p.valueInterface(subv) } return out diff --git a/vendor/github.com/DHowett/go-plist/xml_generator.go b/vendor/github.com/DHowett/go-plist/xml_generator.go new file mode 100644 index 0000000..9302ef0 --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/xml_generator.go @@ -0,0 +1,145 @@ +package plist + +import ( + "encoding/base64" + "encoding/xml" + "io" + "math" + "time" +) + +const xmlDOCTYPE = ` +` + +type xmlPlistGenerator struct { + writer io.Writer + xmlEncoder *xml.Encoder +} + +func (p *xmlPlistGenerator) generateDocument(root cfValue) { + io.WriteString(p.writer, xml.Header) + io.WriteString(p.writer, xmlDOCTYPE) + + plistStartElement := xml.StartElement{ + Name: xml.Name{ + Space: "", + Local: "plist", + }, + Attr: []xml.Attr{{ + Name: xml.Name{ + Space: "", + Local: "version"}, + Value: "1.0"}, + }, + } + + p.xmlEncoder.EncodeToken(plistStartElement) + + p.writePlistValue(root) + + p.xmlEncoder.EncodeToken(plistStartElement.End()) + p.xmlEncoder.Flush() +} + +func (p *xmlPlistGenerator) writeDictionary(dict *cfDictionary) { + dict.sort() + startElement := xml.StartElement{Name: xml.Name{Local: "dict"}} + p.xmlEncoder.EncodeToken(startElement) + for i, k := range dict.keys { + p.xmlEncoder.EncodeElement(k, xml.StartElement{Name: xml.Name{Local: "key"}}) + p.writePlistValue(dict.values[i]) + } + p.xmlEncoder.EncodeToken(startElement.End()) +} + +func (p *xmlPlistGenerator) writeArray(a *cfArray) { + startElement := xml.StartElement{Name: xml.Name{Local: "array"}} + p.xmlEncoder.EncodeToken(startElement) + for _, v := range a.values { + p.writePlistValue(v) + } + p.xmlEncoder.EncodeToken(startElement.End()) +} + +func (p *xmlPlistGenerator) writePlistValue(pval cfValue) { + if pval == nil { + return + } + + defer p.xmlEncoder.Flush() + + if dict, ok := pval.(*cfDictionary); ok { + p.writeDictionary(dict) + return + } else if a, ok := pval.(*cfArray); ok { + p.writeArray(a) + return + } else if uid, ok := pval.(cfUID); ok { + p.writeDictionary(&cfDictionary{ + keys: []string{"CF$UID"}, + values: []cfValue{ + &cfNumber{ + signed: false, + value: uint64(uid), + }, + }, + }) + return + } + + // Everything here and beyond is encoded the same way: value + key := "" + var encodedValue interface{} = pval + + switch pval := pval.(type) { + case cfString: + key = "string" + case *cfNumber: + key = "integer" + if pval.signed { + encodedValue = int64(pval.value) + } else { + encodedValue = pval.value + } + case *cfReal: + key = "real" + encodedValue = pval.value + switch { + case math.IsInf(pval.value, 1): + encodedValue = "inf" + case math.IsInf(pval.value, -1): + encodedValue = "-inf" + case math.IsNaN(pval.value): + encodedValue = "nan" + } + case cfBoolean: + key = "false" + b := bool(pval) + if b { + key = "true" + } + encodedValue = "" + case cfData: + key = "data" + encodedValue = xml.CharData(base64.StdEncoding.EncodeToString([]byte(pval))) + case cfDate: + key = "date" + encodedValue = time.Time(pval).In(time.UTC).Format(time.RFC3339) + } + + if key != "" { + err := p.xmlEncoder.EncodeElement(encodedValue, xml.StartElement{Name: xml.Name{Local: key}}) + if err != nil { + panic(err) + } + } +} + +func (p *xmlPlistGenerator) Indent(i string) { + p.xmlEncoder.Indent("", i) +} + +func newXMLPlistGenerator(w io.Writer) *xmlPlistGenerator { + mw := mustWriter{w} + return &xmlPlistGenerator{mw, xml.NewEncoder(mw)} +} diff --git a/vendor/github.com/DHowett/go-plist/xml.go b/vendor/github.com/DHowett/go-plist/xml_parser.go similarity index 53% rename from vendor/github.com/DHowett/go-plist/xml.go rename to vendor/github.com/DHowett/go-plist/xml_parser.go index 17090c1..8d8cfd1 100644 --- a/vendor/github.com/DHowett/go-plist/xml.go +++ b/vendor/github.com/DHowett/go-plist/xml_parser.go @@ -6,124 +6,11 @@ import ( "errors" "fmt" "io" - "math" "runtime" "strings" "time" ) -const xmlDOCTYPE = ` -` - -type xmlPlistGenerator struct { - writer io.Writer - xmlEncoder *xml.Encoder -} - -func (p *xmlPlistGenerator) generateDocument(pval *plistValue) { - io.WriteString(p.writer, xml.Header) - io.WriteString(p.writer, xmlDOCTYPE) - - plistStartElement := xml.StartElement{ - Name: xml.Name{ - Space: "", - Local: "plist", - }, - Attr: []xml.Attr{{ - Name: xml.Name{ - Space: "", - Local: "version"}, - Value: "1.0"}, - }, - } - - p.xmlEncoder.EncodeToken(plistStartElement) - - p.writePlistValue(pval) - - p.xmlEncoder.EncodeToken(plistStartElement.End()) - p.xmlEncoder.Flush() -} - -func (p *xmlPlistGenerator) writePlistValue(pval *plistValue) { - if pval == nil { - return - } - - defer p.xmlEncoder.Flush() - - key := "" - encodedValue := pval.value - switch pval.kind { - case Dictionary: - startElement := xml.StartElement{Name: xml.Name{Local: "dict"}} - p.xmlEncoder.EncodeToken(startElement) - dict := encodedValue.(*dictionary) - dict.populateArrays() - for i, k := range dict.keys { - p.xmlEncoder.EncodeElement(k, xml.StartElement{Name: xml.Name{Local: "key"}}) - p.writePlistValue(dict.values[i]) - } - p.xmlEncoder.EncodeToken(startElement.End()) - case Array: - startElement := xml.StartElement{Name: xml.Name{Local: "array"}} - p.xmlEncoder.EncodeToken(startElement) - values := encodedValue.([]*plistValue) - for _, v := range values { - p.writePlistValue(v) - } - p.xmlEncoder.EncodeToken(startElement.End()) - case String: - key = "string" - case Integer: - key = "integer" - if pval.value.(signedInt).signed { - encodedValue = int64(pval.value.(signedInt).value) - } else { - encodedValue = pval.value.(signedInt).value - } - case Real: - key = "real" - encodedValue = pval.value.(sizedFloat).value - switch { - case math.IsInf(pval.value.(sizedFloat).value, 1): - encodedValue = "inf" - case math.IsInf(pval.value.(sizedFloat).value, -1): - encodedValue = "-inf" - case math.IsNaN(pval.value.(sizedFloat).value): - encodedValue = "nan" - } - case Boolean: - key = "false" - b := pval.value.(bool) - if b { - key = "true" - } - encodedValue = "" - case Data: - key = "data" - encodedValue = xml.CharData(base64.StdEncoding.EncodeToString(pval.value.([]byte))) - case Date: - key = "date" - encodedValue = pval.value.(time.Time).In(time.UTC).Format(time.RFC3339) - } - if key != "" { - err := p.xmlEncoder.EncodeElement(encodedValue, xml.StartElement{Name: xml.Name{Local: key}}) - if err != nil { - panic(err) - } - } -} - -func (p *xmlPlistGenerator) Indent(i string) { - p.xmlEncoder.Indent("", i) -} - -func newXMLPlistGenerator(w io.Writer) *xmlPlistGenerator { - mw := mustWriter{w} - return &xmlPlistGenerator{mw, xml.NewEncoder(mw)} -} - type xmlPlistParser struct { reader io.Reader xmlDecoder *xml.Decoder @@ -131,7 +18,7 @@ type xmlPlistParser struct { ntags int } -func (p *xmlPlistParser) parseDocument() (pval *plistValue, parseError error) { +func (p *xmlPlistParser) parseDocument() (pval cfValue, parseError error) { defer func() { if r := recover(); r != nil { if _, ok := r.(runtime.Error); ok { @@ -162,7 +49,7 @@ func (p *xmlPlistParser) parseDocument() (pval *plistValue, parseError error) { } } -func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) *plistValue { +func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) cfValue { var charData xml.CharData switch element.Name.Local { case "plist": @@ -189,7 +76,7 @@ func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) *plistValue { panic(err) } - return &plistValue{String, string(charData)} + return cfString(charData) case "integer": p.ntags++ err := p.xmlDecoder.DecodeElement(&charData, &element) @@ -205,11 +92,11 @@ func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) *plistValue { if s[0] == '-' { s, base := unsignedGetBase(s[1:]) n := mustParseInt("-"+s, base, 64) - return &plistValue{Integer, signedInt{uint64(n), true}} + return &cfNumber{signed: true, value: uint64(n)} } else { s, base := unsignedGetBase(s) n := mustParseUint(s, base, 64) - return &plistValue{Integer, signedInt{n, false}} + return &cfNumber{signed: false, value: n} } case "real": p.ntags++ @@ -219,13 +106,13 @@ func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) *plistValue { } n := mustParseFloat(string(charData), 64) - return &plistValue{Real, sizedFloat{n, 64}} + return &cfReal{wide: true, value: n} case "true", "false": p.ntags++ p.xmlDecoder.Skip() b := element.Name.Local == "true" - return &plistValue{Boolean, b} + return cfBoolean(b) case "date": p.ntags++ err := p.xmlDecoder.DecodeElement(&charData, &element) @@ -238,7 +125,7 @@ func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) *plistValue { panic(err) } - return &plistValue{Date, t} + return cfDate(t) case "data": p.ntags++ err := p.xmlDecoder.DecodeElement(&charData, &element) @@ -255,11 +142,12 @@ func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) *plistValue { panic(err) } - return &plistValue{Data, bytes[:l]} + return cfData(bytes[:l]) case "dict": p.ntags++ var key *string - var subvalues map[string]*plistValue = make(map[string]*plistValue) + keys := make([]string, 0, 32) + values := make([]cfValue, 0, 32) for { token, err := p.xmlDecoder.Token() if err != nil { @@ -282,15 +170,23 @@ func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) *plistValue { if key == nil { panic(errors.New("missing key in dictionary")) } - subvalues[*key] = p.parseXMLElement(el) + keys = append(keys, *key) + values = append(values, p.parseXMLElement(el)) key = nil } } } - return &plistValue{Dictionary, &dictionary{m: subvalues}} + + if len(keys) == 1 && keys[0] == "CF$UID" && len(values) == 1 { + if integer, ok := values[0].(*cfNumber); ok { + return cfUID(integer.value) + } + } + + return &cfDictionary{keys: keys, values: values} case "array": p.ntags++ - var subvalues []*plistValue = make([]*plistValue, 0, 10) + values := make([]cfValue, 0, 10) for { token, err := p.xmlDecoder.Token() if err != nil { @@ -302,10 +198,10 @@ func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) *plistValue { } if el, ok := token.(xml.StartElement); ok { - subvalues = append(subvalues, p.parseXMLElement(el)) + values = append(values, p.parseXMLElement(el)) } } - return &plistValue{Array, subvalues} + return &cfArray{values} } err := fmt.Errorf("encountered unknown element %s", element.Name.Local) if p.ntags == 0 { diff --git a/vendor/github.com/DHowett/go-plist/zerocopy.go b/vendor/github.com/DHowett/go-plist/zerocopy.go new file mode 100644 index 0000000..999f401 --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/zerocopy.go @@ -0,0 +1,20 @@ +// +build !appengine + +package plist + +import ( + "reflect" + "unsafe" +) + +func zeroCopy8BitString(buf []byte, off int, len int) string { + if len == 0 { + return "" + } + + var s string + hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) + hdr.Data = uintptr(unsafe.Pointer(&buf[off])) + hdr.Len = len + return s +} diff --git a/vendor/github.com/DHowett/go-plist/zerocopy_appengine.go b/vendor/github.com/DHowett/go-plist/zerocopy_appengine.go new file mode 100644 index 0000000..dbd9a1a --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/zerocopy_appengine.go @@ -0,0 +1,7 @@ +// +build appengine + +package plist + +func zeroCopy8BitString(buf []byte, off int, len int) string { + return string(buf[off : off+len]) +} diff --git a/vendor/github.com/codeskyblue/go-accesslog/.gitignore b/vendor/github.com/codeskyblue/go-accesslog/.gitignore new file mode 100644 index 0000000..9ed3b07 --- /dev/null +++ b/vendor/github.com/codeskyblue/go-accesslog/.gitignore @@ -0,0 +1 @@ +*.test diff --git a/vendor/github.com/codeskyblue/go-accesslog/README.markdown b/vendor/github.com/codeskyblue/go-accesslog/README.markdown new file mode 100644 index 0000000..7cf4a6f --- /dev/null +++ b/vendor/github.com/codeskyblue/go-accesslog/README.markdown @@ -0,0 +1,49 @@ +Custom format HTTP access logger in golang +========================================== + +## Description + +A library to build your own HTTP access logger. + +## Usage + +Provide a class that implements `accesslog.Logger` interface to make a logging HTTP handler. + +``` golang +type LogRecord struct { + Time time.Time + Ip, Method, Uri, Protocol, Username string + Status int + Size int64 + ElapsedTime time.Duration + CustomRecords map[string]string +} + +type Logger interface { + Log(record LogRecord) +} +``` + +## Example + +``` golang +import ( + "log" + "net/http" + + accesslog "github.com/mash/go-accesslog" +) + +type logger struct { +} + +func (l logger) Log(record accesslog.LogRecord) { + log.Println(record.Method + " " + record.Uri) +} + +func main() { + l := logger{} + handler := http.FileServer(http.Dir(".")) + http.ListenAndServe(":8080", accesslog.NewLoggingHandler(handler, l)) +} +``` diff --git a/vendor/github.com/codeskyblue/go-accesslog/accesslog.go b/vendor/github.com/codeskyblue/go-accesslog/accesslog.go new file mode 100644 index 0000000..931b3d5 --- /dev/null +++ b/vendor/github.com/codeskyblue/go-accesslog/accesslog.go @@ -0,0 +1,172 @@ +package accesslog + +import ( + "bufio" + "fmt" + "net" + "net/http" + "strings" + "time" +) + +type LogRecord struct { + Time time.Time + Ip, Method, Uri, Protocol, Username, Host string + Status int + Size int64 + ElapsedTime time.Duration + RequestHeader http.Header + CustomRecords map[string]string +} + +type LoggingWriter struct { + http.ResponseWriter + logRecord LogRecord +} + +func (r *LoggingWriter) Write(p []byte) (int, error) { + if r.logRecord.Status == 0 { + // The status will be StatusOK if WriteHeader has not been called yet + r.logRecord.Status = http.StatusOK + } + written, err := r.ResponseWriter.Write(p) + r.logRecord.Size += int64(written) + return written, err +} + +func (r *LoggingWriter) WriteHeader(status int) { + r.logRecord.Status = status + r.ResponseWriter.WriteHeader(status) +} + +// w.(accesslogger.LoggingWriter).SetCustomLogRecord("X-User-Id", "3") +func (r *LoggingWriter) SetCustomLogRecord(key, value string) { + if r.logRecord.CustomRecords == nil { + r.logRecord.CustomRecords = map[string]string{} + } + r.logRecord.CustomRecords[key] = value +} + +func (r *LoggingWriter) CloseNotify() <-chan bool { + return r.ResponseWriter.(http.CloseNotifier).CloseNotify() +} + +func (r *LoggingWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + if hijacker, ok := r.ResponseWriter.(http.Hijacker); ok { + return hijacker.Hijack() + } + return nil, nil, fmt.Errorf("ResponseWriter doesn't support Hijacker interface") +} + +type Logger interface { + Log(record LogRecord) +} + +type LoggingHandler struct { + handler http.Handler + logger Logger + logBefore bool +} + +func NewLoggingHandler(handler http.Handler, logger Logger) http.Handler { + return &LoggingHandler{ + handler: handler, + logger: logger, + logBefore: false, + } +} + +func NewAroundLoggingHandler(handler http.Handler, logger Logger) http.Handler { + return &LoggingHandler{ + handler: handler, + logger: logger, + logBefore: true, + } +} + +func NewLoggingMiddleware(logger Logger) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + handler := NewLoggingHandler(next, logger) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler.ServeHTTP(w, r) + }) + } +} + +func NewAroundLoggingMiddleware(logger Logger) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + handler := NewAroundLoggingHandler(next, logger) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler.ServeHTTP(w, r) + }) + } +} + +// readIp return the real ip when behide nginx or apache +func (h *LoggingHandler) realIp(r *http.Request) string { + ip, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + ip = r.RemoteAddr + } + if ip != "127.0.0.1" { + return ip + } + // Check if behide nginx or apache + xRealIP := r.Header.Get("X-Real-Ip") + xForwardedFor := r.Header.Get("X-Forwarded-For") + + for _, address := range strings.Split(xForwardedFor, ",") { + address = strings.TrimSpace(address) + if address != "" { + return address + } + } + + if xRealIP != "" { + return xRealIP + } + return ip +} + +func (h *LoggingHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + ip := h.realIp(r) + username := "-" + if r.URL.User != nil { + if name := r.URL.User.Username(); name != "" { + username = name + } + } + + startTime := time.Now() + writer := &LoggingWriter{ + ResponseWriter: rw, + logRecord: LogRecord{ + Time: startTime.UTC(), + Ip: ip, + Method: r.Method, + Uri: r.RequestURI, + Username: username, + Protocol: r.Proto, + Host: r.Host, + Status: 0, + Size: 0, + ElapsedTime: time.Duration(0), + RequestHeader: r.Header, + }, + } + + if h.logBefore { + writer.SetCustomLogRecord("at", "before") + h.logger.Log(writer.logRecord) + } + h.handler.ServeHTTP(writer, r) + finishTime := time.Now() + + writer.logRecord.Time = finishTime.UTC() + writer.logRecord.ElapsedTime = finishTime.Sub(startTime) + + if h.logBefore { + writer.SetCustomLogRecord("at", "after") + } + h.logger.Log(writer.logRecord) +} diff --git a/vendor/github.com/gorilla/context/context.go b/vendor/github.com/gorilla/context/context.go index da280ae..81cb128 100644 --- a/vendor/github.com/gorilla/context/context.go +++ b/vendor/github.com/gorilla/context/context.go @@ -6,32 +6,31 @@ package context import ( "net/http" - "net/url" "sync" "time" ) var ( mutex sync.RWMutex - data = make(map[*url.URL]map[interface{}]interface{}) - datat = make(map[*url.URL]int64) + data = make(map[*http.Request]map[interface{}]interface{}) + datat = make(map[*http.Request]int64) ) // Set stores a value for a given key in a given request. func Set(r *http.Request, key, val interface{}) { mutex.Lock() - if data[r.URL] == nil { - data[r.URL] = make(map[interface{}]interface{}) - datat[r.URL] = time.Now().Unix() + if data[r] == nil { + data[r] = make(map[interface{}]interface{}) + datat[r] = time.Now().Unix() } - data[r.URL][key] = val + data[r][key] = val mutex.Unlock() } // Get returns a value stored for a given key in a given request. func Get(r *http.Request, key interface{}) interface{} { mutex.RLock() - if ctx := data[r.URL]; ctx != nil { + if ctx := data[r]; ctx != nil { value := ctx[key] mutex.RUnlock() return value @@ -43,8 +42,8 @@ func Get(r *http.Request, key interface{}) interface{} { // GetOk returns stored value and presence state like multi-value return of map access. func GetOk(r *http.Request, key interface{}) (interface{}, bool) { mutex.RLock() - if _, ok := data[r.URL]; ok { - value, ok := data[r.URL][key] + if _, ok := data[r]; ok { + value, ok := data[r][key] mutex.RUnlock() return value, ok } @@ -55,7 +54,7 @@ func GetOk(r *http.Request, key interface{}) (interface{}, bool) { // GetAll returns all stored values for the request as a map. Nil is returned for invalid requests. func GetAll(r *http.Request) map[interface{}]interface{} { mutex.RLock() - if context, ok := data[r.URL]; ok { + if context, ok := data[r]; ok { result := make(map[interface{}]interface{}, len(context)) for k, v := range context { result[k] = v @@ -71,7 +70,7 @@ func GetAll(r *http.Request) map[interface{}]interface{} { // the request was registered. func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) { mutex.RLock() - context, ok := data[r.URL] + context, ok := data[r] result := make(map[interface{}]interface{}, len(context)) for k, v := range context { result[k] = v @@ -83,8 +82,8 @@ func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) { // Delete removes a value stored for a given key in a given request. func Delete(r *http.Request, key interface{}) { mutex.Lock() - if data[r.URL] != nil { - delete(data[r.URL], key) + if data[r] != nil { + delete(data[r], key) } mutex.Unlock() } @@ -95,14 +94,14 @@ func Delete(r *http.Request, key interface{}) { // variables at the end of a request lifetime. See ClearHandler(). func Clear(r *http.Request) { mutex.Lock() - clear(r.URL) + clear(r) mutex.Unlock() } // clear is Clear without the lock. -func clear(u *url.URL) { - delete(data, u) - delete(datat, u) +func clear(r *http.Request) { + delete(data, r) + delete(datat, r) } // Purge removes request data stored for longer than maxAge, in seconds. @@ -119,13 +118,13 @@ func Purge(maxAge int) int { count := 0 if maxAge <= 0 { count = len(data) - data = make(map[*url.URL]map[interface{}]interface{}) - datat = make(map[*url.URL]int64) + data = make(map[*http.Request]map[interface{}]interface{}) + datat = make(map[*http.Request]int64) } else { min := time.Now().Unix() - int64(maxAge) - for u := range data { - if datat[u] < min { - clear(u) + for r := range data { + if datat[r] < min { + clear(r) count++ } } diff --git a/vendor/golang.org/x/net/AUTHORS b/vendor/golang.org/x/net/AUTHORS new file mode 100644 index 0000000..15167cd --- /dev/null +++ b/vendor/golang.org/x/net/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/net/CONTRIBUTORS b/vendor/golang.org/x/net/CONTRIBUTORS new file mode 100644 index 0000000..1c4577e --- /dev/null +++ b/vendor/golang.org/x/net/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS.