mirror of
https://github.com/cedricve/go-onvif.git
synced 2025-09-27 12:52:15 +08:00

Updated discoverDevices and createUserToken functions to handle errors properly. This includes the rare possibility createUserToken could send a new SOAP fault message if a uuid can not be generated (only in out-of-memory situations). Added vendoring to prevent future dependency issues. Side Note: Had vendoring not been added, the uuid dependency would cause problems if this go-onvif package was added to a project using vendoring. This is due to go defaulting to a last commit to the master branch, while vendoring defaults to the last published version. This quirk was obvious after seeing the uuid package pushed the breaking change to the master without also pushing it as a new version.
324 lines
10 KiB
Go
324 lines
10 KiB
Go
// Copyright 2012-2014 Charles Banning. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file
|
|
|
|
package mxj
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
)
|
|
|
|
// ------------------------------ write JSON -----------------------
|
|
|
|
// Just a wrapper on json.Marshal.
|
|
// If option safeEncoding is'true' then safe encoding of '<', '>' and '&'
|
|
// is preserved. (see encoding/json#Marshal, encoding/json#Encode)
|
|
func (mv Map) Json(safeEncoding ...bool) ([]byte, error) {
|
|
var s bool
|
|
if len(safeEncoding) == 1 {
|
|
s = safeEncoding[0]
|
|
}
|
|
|
|
b, err := json.Marshal(mv)
|
|
|
|
if !s {
|
|
b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1)
|
|
b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1)
|
|
b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1)
|
|
}
|
|
return b, err
|
|
}
|
|
|
|
// Just a wrapper on json.MarshalIndent.
|
|
// If option safeEncoding is'true' then safe encoding of '<' , '>' and '&'
|
|
// is preserved. (see encoding/json#Marshal, encoding/json#Encode)
|
|
func (mv Map) JsonIndent(prefix, indent string, safeEncoding ...bool) ([]byte, error) {
|
|
var s bool
|
|
if len(safeEncoding) == 1 {
|
|
s = safeEncoding[0]
|
|
}
|
|
|
|
b, err := json.MarshalIndent(mv, prefix, indent)
|
|
if !s {
|
|
b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1)
|
|
b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1)
|
|
b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1)
|
|
}
|
|
return b, err
|
|
}
|
|
|
|
// The following implementation is provided for symmetry with NewMapJsonReader[Raw]
|
|
// The names will also provide a key for the number of return arguments.
|
|
|
|
// Writes the Map as JSON on the Writer.
|
|
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved.
|
|
func (mv Map) JsonWriter(jsonWriter io.Writer, safeEncoding ...bool) error {
|
|
b, err := mv.Json(safeEncoding...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = jsonWriter.Write(b)
|
|
return err
|
|
}
|
|
|
|
// Writes the Map as JSON on the Writer. []byte is the raw JSON that was written.
|
|
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved.
|
|
func (mv Map) JsonWriterRaw(jsonWriter io.Writer, safeEncoding ...bool) ([]byte, error) {
|
|
b, err := mv.Json(safeEncoding...)
|
|
if err != nil {
|
|
return b, err
|
|
}
|
|
|
|
_, err = jsonWriter.Write(b)
|
|
return b, err
|
|
}
|
|
|
|
// Writes the Map as pretty JSON on the Writer.
|
|
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved.
|
|
func (mv Map) JsonIndentWriter(jsonWriter io.Writer, prefix, indent string, safeEncoding ...bool) error {
|
|
b, err := mv.JsonIndent(prefix, indent, safeEncoding...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = jsonWriter.Write(b)
|
|
return err
|
|
}
|
|
|
|
// Writes the Map as pretty JSON on the Writer. []byte is the raw JSON that was written.
|
|
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved.
|
|
func (mv Map) JsonIndentWriterRaw(jsonWriter io.Writer, prefix, indent string, safeEncoding ...bool) ([]byte, error) {
|
|
b, err := mv.JsonIndent(prefix, indent, safeEncoding...)
|
|
if err != nil {
|
|
return b, err
|
|
}
|
|
|
|
_, err = jsonWriter.Write(b)
|
|
return b, err
|
|
}
|
|
|
|
// --------------------------- read JSON -----------------------------
|
|
|
|
// Decode numericvalues as json.Number type Map values - see encoding/json#Number.
|
|
// NOTE: this is for decoding JSON into a Map with NewMapJson(), NewMapJsonReader(),
|
|
// etc.; it does not affect NewMapXml(), etc. The XML encoders mv.Xml() and mv.XmlIndent()
|
|
// do recognize json.Number types; a JSON object can be decoded to a Map with json.Number
|
|
// value types and the resulting Map can be correctly encoded into a XML object.
|
|
var JsonUseNumber bool
|
|
|
|
// Just a wrapper on json.Unmarshal
|
|
// Converting JSON to XML is a simple as:
|
|
// ...
|
|
// mapVal, merr := mxj.NewMapJson(jsonVal)
|
|
// if merr != nil {
|
|
// // handle error
|
|
// }
|
|
// xmlVal, xerr := mapVal.Xml()
|
|
// if xerr != nil {
|
|
// // handle error
|
|
// }
|
|
// NOTE: as a special case, passing a list, e.g., [{"some-null-value":"", "a-non-null-value":"bar"}],
|
|
// will be interpreted as having the root key 'object' prepended - {"object":[ ... ]} - to unmarshal to a Map.
|
|
// See mxj/j2x/j2x_test.go.
|
|
func NewMapJson(jsonVal []byte) (Map, error) {
|
|
// empty or nil begets empty
|
|
if len(jsonVal) == 0 {
|
|
m := make(map[string]interface{}, 0)
|
|
return m, nil
|
|
}
|
|
// handle a goofy case ...
|
|
if jsonVal[0] == '[' {
|
|
jsonVal = []byte(`{"object":` + string(jsonVal) + `}`)
|
|
}
|
|
m := make(map[string]interface{})
|
|
// err := json.Unmarshal(jsonVal, &m)
|
|
buf := bytes.NewReader(jsonVal)
|
|
dec := json.NewDecoder(buf)
|
|
if JsonUseNumber {
|
|
dec.UseNumber()
|
|
}
|
|
err := dec.Decode(&m)
|
|
return m, err
|
|
}
|
|
|
|
// Retrieve a Map value from an io.Reader.
|
|
// NOTE: The raw JSON off the reader is buffered to []byte using a ByteReader. If the io.Reader is an
|
|
// os.File, there may be significant performance impact. If the io.Reader is wrapping a []byte
|
|
// value in-memory, however, such as http.Request.Body you CAN use it to efficiently unmarshal
|
|
// a JSON object.
|
|
func NewMapJsonReader(jsonReader io.Reader) (Map, error) {
|
|
jb, err := getJson(jsonReader)
|
|
if err != nil || len(*jb) == 0 {
|
|
return nil, err
|
|
}
|
|
|
|
// Unmarshal the 'presumed' JSON string
|
|
return NewMapJson(*jb)
|
|
}
|
|
|
|
// Retrieve a Map value and raw JSON - []byte - from an io.Reader.
|
|
// NOTE: The raw JSON off the reader is buffered to []byte using a ByteReader. If the io.Reader is an
|
|
// os.File, there may be significant performance impact. If the io.Reader is wrapping a []byte
|
|
// value in-memory, however, such as http.Request.Body you CAN use it to efficiently unmarshal
|
|
// a JSON object and retrieve the raw JSON in a single call.
|
|
func NewMapJsonReaderRaw(jsonReader io.Reader) (Map, []byte, error) {
|
|
jb, err := getJson(jsonReader)
|
|
if err != nil || len(*jb) == 0 {
|
|
return nil, *jb, err
|
|
}
|
|
|
|
// Unmarshal the 'presumed' JSON string
|
|
m, merr := NewMapJson(*jb)
|
|
return m, *jb, merr
|
|
}
|
|
|
|
// Pull the next JSON string off the stream: just read from first '{' to its closing '}'.
|
|
// Returning a pointer to the slice saves 16 bytes - maybe unnecessary, but internal to package.
|
|
func getJson(rdr io.Reader) (*[]byte, error) {
|
|
bval := make([]byte, 1)
|
|
jb := make([]byte, 0)
|
|
var inQuote, inJson bool
|
|
var parenCnt int
|
|
var previous byte
|
|
|
|
// scan the input for a matched set of {...}
|
|
// json.Unmarshal will handle syntax checking.
|
|
for {
|
|
_, err := rdr.Read(bval)
|
|
if err != nil {
|
|
if err == io.EOF && inJson && parenCnt > 0 {
|
|
return &jb, fmt.Errorf("no closing } for JSON string: %s", string(jb))
|
|
}
|
|
return &jb, err
|
|
}
|
|
switch bval[0] {
|
|
case '{':
|
|
if !inQuote {
|
|
parenCnt++
|
|
inJson = true
|
|
}
|
|
case '}':
|
|
if !inQuote {
|
|
parenCnt--
|
|
}
|
|
if parenCnt < 0 {
|
|
return nil, fmt.Errorf("closing } without opening {: %s", string(jb))
|
|
}
|
|
case '"':
|
|
if inQuote {
|
|
if previous == '\\' {
|
|
break
|
|
}
|
|
inQuote = false
|
|
} else {
|
|
inQuote = true
|
|
}
|
|
case '\n', '\r', '\t', ' ':
|
|
if !inQuote {
|
|
continue
|
|
}
|
|
}
|
|
if inJson {
|
|
jb = append(jb, bval[0])
|
|
if parenCnt == 0 {
|
|
break
|
|
}
|
|
}
|
|
previous = bval[0]
|
|
}
|
|
|
|
return &jb, nil
|
|
}
|
|
|
|
// ------------------------------- JSON Reader handler via Map values -----------------------
|
|
|
|
// Default poll delay to keep Handler from spinning on an open stream
|
|
// like sitting on os.Stdin waiting for imput.
|
|
var jhandlerPollInterval = time.Duration(1e6)
|
|
|
|
// While unnecessary, we make HandleJsonReader() have the same signature as HandleXmlReader().
|
|
// This avoids treating one or other as a special case and discussing the underlying stdlib logic.
|
|
|
|
// Bulk process JSON using handlers that process a Map value.
|
|
// 'rdr' is an io.Reader for the JSON (stream).
|
|
// 'mapHandler' is the Map processing handler. Return of 'false' stops io.Reader processing.
|
|
// 'errHandler' is the error processor. Return of 'false' stops io.Reader processing and returns the error.
|
|
// Note: mapHandler() and errHandler() calls are blocking, so reading and processing of messages is serialized.
|
|
// This means that you can stop reading the file on error or after processing a particular message.
|
|
// To have reading and handling run concurrently, pass argument to a go routine in handler and return 'true'.
|
|
func HandleJsonReader(jsonReader io.Reader, mapHandler func(Map) bool, errHandler func(error) bool) error {
|
|
var n int
|
|
for {
|
|
m, merr := NewMapJsonReader(jsonReader)
|
|
n++
|
|
|
|
// handle error condition with errhandler
|
|
if merr != nil && merr != io.EOF {
|
|
merr = fmt.Errorf("[jsonReader: %d] %s", n, merr.Error())
|
|
if ok := errHandler(merr); !ok {
|
|
// caused reader termination
|
|
return merr
|
|
}
|
|
continue
|
|
}
|
|
|
|
// pass to maphandler
|
|
if len(m) != 0 {
|
|
if ok := mapHandler(m); !ok {
|
|
break
|
|
}
|
|
} else if merr != io.EOF {
|
|
<-time.After(jhandlerPollInterval)
|
|
}
|
|
|
|
if merr == io.EOF {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Bulk process JSON using handlers that process a Map value and the raw JSON.
|
|
// 'rdr' is an io.Reader for the JSON (stream).
|
|
// 'mapHandler' is the Map and raw JSON - []byte - processor. Return of 'false' stops io.Reader processing.
|
|
// 'errHandler' is the error and raw JSON processor. Return of 'false' stops io.Reader processing and returns the error.
|
|
// Note: mapHandler() and errHandler() calls are blocking, so reading and processing of messages is serialized.
|
|
// This means that you can stop reading the file on error or after processing a particular message.
|
|
// To have reading and handling run concurrently, pass argument(s) to a go routine in handler and return 'true'.
|
|
func HandleJsonReaderRaw(jsonReader io.Reader, mapHandler func(Map, []byte) bool, errHandler func(error, []byte) bool) error {
|
|
var n int
|
|
for {
|
|
m, raw, merr := NewMapJsonReaderRaw(jsonReader)
|
|
n++
|
|
|
|
// handle error condition with errhandler
|
|
if merr != nil && merr != io.EOF {
|
|
merr = fmt.Errorf("[jsonReader: %d] %s", n, merr.Error())
|
|
if ok := errHandler(merr, raw); !ok {
|
|
// caused reader termination
|
|
return merr
|
|
}
|
|
continue
|
|
}
|
|
|
|
// pass to maphandler
|
|
if len(m) != 0 {
|
|
if ok := mapHandler(m, raw); !ok {
|
|
break
|
|
}
|
|
} else if merr != io.EOF {
|
|
<-time.After(jhandlerPollInterval)
|
|
}
|
|
|
|
if merr == io.EOF {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|