return error in case of non-existent parameters in the configuration file

This commit is contained in:
aler9 2021-09-27 14:59:27 +02:00 committed by Alessandro Ros
parent b748e6d0ff
commit 1f540a2aaf
4 changed files with 175 additions and 71 deletions

View File

@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"os"
"reflect"
"time"
"github.com/aler9/gortsplib/pkg/headers"
@ -72,6 +73,7 @@ func loadFromFile(fpath string, conf *Conf) (bool, error) {
m2[k.(string)] = convert(v)
}
return m2
case []interface{}:
a2 := make([]interface{}, len(x))
for i, v := range x {
@ -79,10 +81,59 @@ func loadFromFile(fpath string, conf *Conf) (bool, error) {
}
return a2
}
return i
}
temp = convert(temp)
// check for non-existent parameters
var checkNonExistentFields func(what interface{}, ref interface{}) error
checkNonExistentFields = func(what interface{}, ref interface{}) error {
if what == nil {
return nil
}
ma, ok := what.(map[string]interface{})
if !ok {
return fmt.Errorf("not a map")
}
for k, v := range ma {
fi := func() reflect.Type {
rr := reflect.TypeOf(ref)
for i := 0; i < rr.NumField(); i++ {
f := rr.Field(i)
if f.Tag.Get("json") == k {
return f.Type
}
}
return nil
}()
if fi == nil {
return fmt.Errorf("non-existent parameter: '%s'", k)
}
if fi == reflect.TypeOf(map[string]*PathConf{}) && v != nil {
ma2, ok := v.(map[string]interface{})
if !ok {
return fmt.Errorf("parameter %s is not a map", k)
}
for k2, v2 := range ma2 {
err := checkNonExistentFields(v2, reflect.Zero(fi.Elem().Elem()).Interface())
if err != nil {
return fmt.Errorf("parameter %s, key %s: %s", k, k2, err)
}
}
}
}
return nil
}
err = checkNonExistentFields(temp, Conf{})
if err != nil {
return true, err
}
// convert the generic map into JSON
byts, err = json.Marshal(temp)
if err != nil {
@ -170,7 +221,7 @@ func Load(fpath string) (*Conf, bool, error) {
return conf, found, nil
}
// CheckAndFillMissing checks the configuration for errors and fills missing fields.
// CheckAndFillMissing checks the configuration for errors and fills missing parameters.
func (conf *Conf) CheckAndFillMissing() error {
if conf.LogLevel == 0 {
conf.LogLevel = LogLevel(logger.Info)

View File

@ -29,27 +29,57 @@ func writeTempFile(byts []byte) (string, error) {
}
func TestConfFromFile(t *testing.T) {
tmpf, err := writeTempFile([]byte(`
paths:
cam1:
runOnDemandStartTimeout: 5s
`))
require.NoError(t, err)
defer os.Remove(tmpf)
func() {
tmpf, err := writeTempFile([]byte(
"paths:\n" +
" cam1:\n" +
" runOnDemandStartTimeout: 5s\n"))
require.NoError(t, err)
defer os.Remove(tmpf)
conf, hasFile, err := Load(tmpf)
require.NoError(t, err)
require.Equal(t, true, hasFile)
conf, hasFile, err := Load(tmpf)
require.NoError(t, err)
require.Equal(t, true, hasFile)
pa, ok := conf.Paths["cam1"]
require.Equal(t, true, ok)
require.Equal(t, &PathConf{
Source: "publisher",
SourceOnDemandStartTimeout: 10 * StringDuration(time.Second),
SourceOnDemandCloseAfter: 10 * StringDuration(time.Second),
RunOnDemandStartTimeout: 5 * StringDuration(time.Second),
RunOnDemandCloseAfter: 10 * StringDuration(time.Second),
}, pa)
pa, ok := conf.Paths["cam1"]
require.Equal(t, true, ok)
require.Equal(t, &PathConf{
Source: "publisher",
SourceOnDemandStartTimeout: 10 * StringDuration(time.Second),
SourceOnDemandCloseAfter: 10 * StringDuration(time.Second),
RunOnDemandStartTimeout: 5 * StringDuration(time.Second),
RunOnDemandCloseAfter: 10 * StringDuration(time.Second),
}, pa)
}()
func() {
tmpf, err := writeTempFile([]byte(``))
require.NoError(t, err)
defer os.Remove(tmpf)
_, _, err = Load(tmpf)
require.NoError(t, err)
}()
func() {
tmpf, err := writeTempFile([]byte(`paths:`))
require.NoError(t, err)
defer os.Remove(tmpf)
_, _, err = Load(tmpf)
require.NoError(t, err)
}()
func() {
tmpf, err := writeTempFile([]byte(
"paths:\n" +
" mypath:\n"))
require.NoError(t, err)
defer os.Remove(tmpf)
_, _, err = Load(tmpf)
require.NoError(t, err)
}()
}
func TestConfFromFileAndEnv(t *testing.T) {
@ -96,11 +126,9 @@ func TestConfFromEnvOnly(t *testing.T) {
func TestConfEncryption(t *testing.T) {
key := "testing123testin"
plaintext := `
paths:
path1:
path2:
`
plaintext := "paths:\n" +
" path1:\n" +
" path2:\n"
encryptedConf := func() string {
var secretKey [32]byte
@ -132,3 +160,25 @@ paths:
_, ok = conf.Paths["path2"]
require.Equal(t, true, ok)
}
func TestConfErrorNonExistentParameter(t *testing.T) {
func() {
tmpf, err := writeTempFile([]byte(`invalid: param`))
require.NoError(t, err)
defer os.Remove(tmpf)
_, _, err = Load(tmpf)
require.Equal(t, "non-existent parameter: 'invalid'", err.Error())
}()
func() {
tmpf, err := writeTempFile([]byte("paths:\n" +
" mypath:\n" +
" invalid: parameter\n"))
require.NoError(t, err)
defer os.Remove(tmpf)
_, _, err = Load(tmpf)
require.Equal(t, "parameter paths, key mypath: non-existent parameter: 'invalid'", err.Error())
}()
}

View File

@ -21,6 +21,7 @@ type Protocols map[Protocol]struct{}
// MarshalJSON marshals a Protocols into JSON.
func (d Protocols) MarshalJSON() ([]byte, error) {
out := make([]string, len(d))
i := 0
for p := range d {
var v string
@ -36,7 +37,8 @@ func (d Protocols) MarshalJSON() ([]byte, error) {
v = "tcp"
}
out = append(out, v)
out[i] = v
i++
}
return json.Marshal(out)

View File

@ -39,49 +39,49 @@ func fillStruct(dest interface{}, source interface{}) {
}
func cloneStruct(dest interface{}, source interface{}) {
enc, _ := json.Marshal(dest)
_ = json.Unmarshal(enc, source)
enc, _ := json.Marshal(source)
_ = json.Unmarshal(enc, dest)
}
func loadConfData(ctx *gin.Context) (interface{}, error) {
var in struct {
// general
LogLevel *string `json:"logLevel"`
LogDestinations *[]string `json:"logDestinations"`
LogFile *string `json:"logFile"`
ReadTimeout *conf.StringDuration `json:"readTimeout"`
WriteTimeout *conf.StringDuration `json:"writeTimeout"`
ReadBufferCount *int `json:"readBufferCount"`
API *bool `json:"api"`
APIAddress *string `json:"apiAddress"`
Metrics *bool `json:"metrics"`
MetricsAddress *string `json:"metricsAddress"`
PPROF *bool `json:"pprof"`
PPROFAddress *string `json:"pprofAddress"`
RunOnConnect *string `json:"runOnConnect"`
RunOnConnectRestart *bool `json:"runOnConnectRestart"`
LogLevel *conf.LogLevel `json:"logLevel"`
LogDestinations *conf.LogDestinations `json:"logDestinations"`
LogFile *string `json:"logFile"`
ReadTimeout *conf.StringDuration `json:"readTimeout"`
WriteTimeout *conf.StringDuration `json:"writeTimeout"`
ReadBufferCount *int `json:"readBufferCount"`
API *bool `json:"api"`
APIAddress *string `json:"apiAddress"`
Metrics *bool `json:"metrics"`
MetricsAddress *string `json:"metricsAddress"`
PPROF *bool `json:"pprof"`
PPROFAddress *string `json:"pprofAddress"`
RunOnConnect *string `json:"runOnConnect"`
RunOnConnectRestart *bool `json:"runOnConnectRestart"`
// rtsp
RTSPDisable *bool `json:"rtspDisable"`
Protocols *[]string `json:"protocols"`
Encryption *string `json:"encryption"`
RTSPAddress *string `json:"rtspAddress"`
RTSPSAddress *string `json:"rtspsAddress"`
RTPAddress *string `json:"rtpAddress"`
RTCPAddress *string `json:"rtcpAddress"`
MulticastIPRange *string `json:"multicastIPRange"`
MulticastRTPPort *int `json:"multicastRTPPort"`
MulticastRTCPPort *int `json:"multicastRTCPPort"`
ServerKey *string `json:"serverKey"`
ServerCert *string `json:"serverCert"`
AuthMethods *[]string `json:"authMethods"`
ReadBufferSize *int `json:"readBufferSize"`
// RTSP
RTSPDisable *bool `json:"rtspDisable"`
Protocols *conf.Protocols `json:"protocols"`
Encryption *conf.Encryption `json:"encryption"`
RTSPAddress *string `json:"rtspAddress"`
RTSPSAddress *string `json:"rtspsAddress"`
RTPAddress *string `json:"rtpAddress"`
RTCPAddress *string `json:"rtcpAddress"`
MulticastIPRange *string `json:"multicastIPRange"`
MulticastRTPPort *int `json:"multicastRTPPort"`
MulticastRTCPPort *int `json:"multicastRTCPPort"`
ServerKey *string `json:"serverKey"`
ServerCert *string `json:"serverCert"`
AuthMethods *conf.AuthMethods `json:"authMethods"`
ReadBufferSize *int `json:"readBufferSize"`
// rtmp
// RTMP
RTMPDisable *bool `json:"rtmpDisable"`
RTMPAddress *string `json:"rtmpAddress"`
// hls
// HLS
HLSDisable *bool `json:"hlsDisable"`
HLSAddress *string `json:"hlsAddress"`
HLSAlwaysRemux *bool `json:"hlsAlwaysRemux"`
@ -101,7 +101,7 @@ func loadConfPathData(ctx *gin.Context) (interface{}, error) {
var in struct {
// source
Source *string `json:"source"`
SourceProtocol *string `json:"sourceProtocol"`
SourceProtocol *conf.SourceProtocol `json:"sourceProtocol"`
SourceAnyPortEnable *bool `json:"sourceAnyPortEnable"`
SourceFingerprint *string `json:"sourceFingerprint"`
SourceOnDemand *bool `json:"sourceOnDemand"`
@ -112,12 +112,12 @@ func loadConfPathData(ctx *gin.Context) (interface{}, error) {
Fallback *string `json:"fallback"`
// authentication
PublishUser *string `json:"publishUser"`
PublishPass *string `json:"publishPass"`
PublishIPs *[]string `json:"publishIPs"`
ReadUser *string `json:"readUser"`
ReadPass *string `json:"readPass"`
ReadIPs *[]string `json:"readIPs"`
PublishUser *string `json:"publishUser"`
PublishPass *string `json:"publishPass"`
PublishIPs *conf.IPsOrNets `json:"publishIPs"`
ReadUser *string `json:"readUser"`
ReadPass *string `json:"readPass"`
ReadIPs *conf.IPsOrNets `json:"readIPs"`
// custom commands
RunOnInit *string `json:"runOnInit"`
@ -363,7 +363,7 @@ func (a *api) onConfigSet(ctx *gin.Context) {
defer a.mutex.Unlock()
var newConf conf.Conf
cloneStruct(a.conf, &newConf)
cloneStruct(&newConf, a.conf)
fillStruct(&newConf, in)
err = newConf.CheckAndFillMissing()
@ -399,15 +399,16 @@ func (a *api) onConfigPathsAdd(ctx *gin.Context) {
defer a.mutex.Unlock()
var newConf conf.Conf
cloneStruct(a.conf, &newConf)
cloneStruct(&newConf, a.conf)
if _, ok := newConf.Paths[name]; ok {
ctx.AbortWithStatus(http.StatusBadRequest)
return
}
newConfPath := &conf.PathConf{}
newConfPath := &conf.PathConf{}
fillStruct(newConfPath, in)
newConf.Paths[name] = newConfPath
err = newConf.CheckAndFillMissing()
@ -443,7 +444,7 @@ func (a *api) onConfigPathsEdit(ctx *gin.Context) {
defer a.mutex.Unlock()
var newConf conf.Conf
cloneStruct(a.conf, &newConf)
cloneStruct(&newConf, a.conf)
newConfPath, ok := newConf.Paths[name]
if !ok {
@ -480,7 +481,7 @@ func (a *api) onConfigPathsDelete(ctx *gin.Context) {
defer a.mutex.Unlock()
var newConf conf.Conf
cloneStruct(a.conf, &newConf)
cloneStruct(&newConf, a.conf)
if _, ok := newConf.Paths[name]; !ok {
ctx.AbortWithStatus(http.StatusBadRequest)