From 54292d712e929df8ff63013b7d2d4ff20d188d5f Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Sun, 26 Sep 2021 15:50:35 +0200 Subject: [PATCH] convert the configuration into JSON before loading it --- apidocs/openapi.yaml | 4 +- go.mod | 2 +- go.sum | 3 +- internal/conf/conf.go | 195 +++++++++++++++++++------------- internal/conf/path.go | 68 +++++------ internal/conf/stringduration.go | 28 ----- internal/core/path_manager.go | 2 +- rtsp-simple-server.yml | 2 +- 8 files changed, 157 insertions(+), 147 deletions(-) diff --git a/apidocs/openapi.yaml b/apidocs/openapi.yaml index 1d02100a..fee5f1c7 100644 --- a/apidocs/openapi.yaml +++ b/apidocs/openapi.yaml @@ -303,7 +303,7 @@ paths: /v1/config/get: get: operationId: configGet - summary: returns the current configuration. + summary: returns the configuration. description: '' responses: '200': @@ -318,7 +318,7 @@ paths: /v1/config/set: post: operationId: configSet - summary: changes the current configuration. + summary: changes the configuration. description: all fields are optional. requestBody: required: true diff --git a/go.mod b/go.mod index 9aba2736..fcf5ccb0 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/stretchr/testify v1.6.1 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad gopkg.in/alecthomas/kingpin.v2 v2.2.6 - gopkg.in/yaml.v2 v2.2.8 + gopkg.in/yaml.v2 v2.4.0 ) replace github.com/notedit/rtmp => github.com/aler9/rtmp v0.0.0-20210403095203-3be4a5535927 diff --git a/go.sum b/go.sum index 97de03e8..96d5400a 100644 --- a/go.sum +++ b/go.sum @@ -100,7 +100,8 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/conf/conf.go b/internal/conf/conf.go index d4752a02..a3127a9b 100644 --- a/internal/conf/conf.go +++ b/internal/conf/conf.go @@ -2,6 +2,7 @@ package conf import ( "encoding/base64" + "encoding/json" "fmt" "io/ioutil" "os" @@ -53,98 +54,134 @@ func decrypt(key string, byts []byte) ([]byte, error) { return decrypted, nil } -// Conf is the main program configuration. -type Conf struct { - // general - LogLevel string `yaml:"logLevel" json:"logLevel"` - LogLevelParsed logger.Level `yaml:"-" json:"-"` - LogDestinations []string `yaml:"logDestinations" json:"logDestinations"` - LogDestinationsParsed map[logger.Destination]struct{} `yaml:"-" json:"-"` - LogFile string `yaml:"logFile" json:"logFile"` - ReadTimeout StringDuration `yaml:"readTimeout" json:"readTimeout"` - WriteTimeout StringDuration `yaml:"writeTimeout" json:"writeTimeout"` - ReadBufferCount int `yaml:"readBufferCount" json:"readBufferCount"` - API bool `yaml:"api" json:"api"` - APIAddress string `yaml:"apiAddress" json:"apiAddress"` - Metrics bool `yaml:"metrics" json:"metrics"` - MetricsAddress string `yaml:"metricsAddress" json:"metricsAddress"` - PPROF bool `yaml:"pprof" json:"pprof"` - PPROFAddress string `yaml:"pprofAddress" json:"pprofAddress"` - RunOnConnect string `yaml:"runOnConnect" json:"runOnConnect"` - RunOnConnectRestart bool `yaml:"runOnConnectRestart" json:"runOnConnectRestart"` - - // rtsp - RTSPDisable bool `yaml:"rtspDisable" json:"rtspDisable"` - Protocols []string `yaml:"protocols" json:"protocols"` - ProtocolsParsed map[Protocol]struct{} `yaml:"-" json:"-"` - Encryption string `yaml:"encryption" json:"encryption"` - EncryptionParsed Encryption `yaml:"-" json:"-"` - RTSPAddress string `yaml:"rtspAddress" json:"rtspAddress"` - RTSPSAddress string `yaml:"rtspsAddress" json:"rtspsAddress"` - RTPAddress string `yaml:"rtpAddress" json:"rtpAddress"` - RTCPAddress string `yaml:"rtcpAddress" json:"rtcpAddress"` - MulticastIPRange string `yaml:"multicastIPRange" json:"multicastIPRange"` - MulticastRTPPort int `yaml:"multicastRTPPort" json:"multicastRTPPort"` - MulticastRTCPPort int `yaml:"multicastRTCPPort" json:"multicastRTCPPort"` - ServerKey string `yaml:"serverKey" json:"serverKey"` - ServerCert string `yaml:"serverCert" json:"serverCert"` - AuthMethods []string `yaml:"authMethods" json:"authMethods"` - AuthMethodsParsed []headers.AuthMethod `yaml:"-" json:"-"` - ReadBufferSize int `yaml:"readBufferSize" json:"readBufferSize"` - - // rtmp - RTMPDisable bool `yaml:"rtmpDisable" json:"rtmpDisable"` - RTMPAddress string `yaml:"rtmpAddress" json:"rtmpAddress"` - - // hls - HLSDisable bool `yaml:"hlsDisable" json:"hlsDisable"` - HLSAddress string `yaml:"hlsAddress" json:"hlsAddress"` - HLSAlwaysRemux bool `yaml:"hlsAlwaysRemux" json:"hlsAlwaysRemux"` - HLSSegmentCount int `yaml:"hlsSegmentCount" json:"hlsSegmentCount"` - HLSSegmentDuration StringDuration `yaml:"hlsSegmentDuration" json:"hlsSegmentDuration"` - HLSAllowOrigin string `yaml:"hlsAllowOrigin" json:"hlsAllowOrigin"` - - // paths - Paths map[string]*PathConf `yaml:"paths" json:"paths"` -} - -// Load loads a Conf. -func Load(fpath string) (*Conf, bool, error) { - conf := &Conf{} - - // read from file - found, err := func() (bool, error) { - // rtsp-simple-server.yml is optional - if fpath == "rtsp-simple-server.yml" { - if _, err := os.Stat(fpath); err != nil { - return false, nil - } +func loadFromFile(fpath string, conf *Conf) (bool, error) { + // rtsp-simple-server.yml is optional + // other configuration files are not + if fpath == "rtsp-simple-server.yml" { + if _, err := os.Stat(fpath); err != nil { + return false, nil } + } - byts, err := ioutil.ReadFile(fpath) + byts, err := ioutil.ReadFile(fpath) + if err != nil { + return true, err + } + + if key, ok := os.LookupEnv("RTSP_CONFKEY"); ok { + byts, err = decrypt(key, byts) if err != nil { return true, err } + } - if key, ok := os.LookupEnv("RTSP_CONFKEY"); ok { - byts, err = decrypt(key, byts) - if err != nil { - return true, err + // load YAML config into a generic map + var temp interface{} + err = yaml.Unmarshal(byts, &temp) + if err != nil { + return true, err + } + + // convert interface{} keys into string keys to avoid JSON errors + var convert func(i interface{}) interface{} + convert = func(i interface{}) interface{} { + switch x := i.(type) { + case map[interface{}]interface{}: + m2 := map[string]interface{}{} + for k, v := range x { + m2[k.(string)] = convert(v) } + return m2 + case []interface{}: + a2 := make([]interface{}, len(x)) + for i, v := range x { + a2[i] = convert(v) + } + return a2 } + return i + } + temp = convert(temp) - err = yaml.Unmarshal(byts, conf) - if err != nil { - return true, err - } + // convert the generic map into JSON + byts, err = json.Marshal(temp) + if err != nil { + return true, err + } + + // load the configuration from JSON + err = json.Unmarshal(byts, conf) + if err != nil { + return true, err + } + + return true, nil +} + +// Conf is a configuration. +type Conf struct { + // general + LogLevel string `json:"logLevel"` + LogLevelParsed logger.Level `json:"-"` + LogDestinations []string `json:"logDestinations"` + LogDestinationsParsed map[logger.Destination]struct{} `json:"-"` + LogFile string `json:"logFile"` + ReadTimeout StringDuration `json:"readTimeout"` + WriteTimeout 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"` + ProtocolsParsed map[Protocol]struct{} `json:"-"` + Encryption string `json:"encryption"` + EncryptionParsed Encryption `json:"-"` + 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"` + AuthMethodsParsed []headers.AuthMethod `json:"-"` + ReadBufferSize int `json:"readBufferSize"` + + // RTMP + RTMPDisable bool `json:"rtmpDisable"` + RTMPAddress string `json:"rtmpAddress"` + + // HLS + HLSDisable bool `json:"hlsDisable"` + HLSAddress string `json:"hlsAddress"` + HLSAlwaysRemux bool `json:"hlsAlwaysRemux"` + HLSSegmentCount int `json:"hlsSegmentCount"` + HLSSegmentDuration StringDuration `json:"hlsSegmentDuration"` + HLSAllowOrigin string `json:"hlsAllowOrigin"` + + // paths + Paths map[string]*PathConf `json:"paths"` +} + +// Load loads a Conf. +func Load(fpath string) (*Conf, bool, error) { + conf := &Conf{} - return true, nil - }() + found, err := loadFromFile(fpath, conf) if err != nil { return nil, false, err } - // read from environment err = loadFromEnvironment("RTSP", conf) if err != nil { return nil, false, err @@ -158,7 +195,7 @@ func Load(fpath string) (*Conf, bool, error) { return conf, found, nil } -// CheckAndFillMissing checks the configuration for errors and fill missing fields. +// CheckAndFillMissing checks the configuration for errors and fills missing fields. func (conf *Conf) CheckAndFillMissing() error { if conf.LogLevel == "" { conf.LogLevel = "info" diff --git a/internal/conf/path.go b/internal/conf/path.go index eb3ffd3d..513f6caf 100644 --- a/internal/conf/path.go +++ b/internal/conf/path.go @@ -43,8 +43,8 @@ func parseIPCidrList(in []string) ([]interface{}, error) { return ret, nil } -// CheckPathName checks if a path name is valid. -func CheckPathName(name string) error { +// IsValidPathName checks if a path name is valid. +func IsValidPathName(name string) error { if name == "" { return fmt.Errorf("cannot be empty") } @@ -66,42 +66,42 @@ func CheckPathName(name string) error { // PathConf is a path configuration. type PathConf struct { - Regexp *regexp.Regexp `yaml:"-" json:"-"` + Regexp *regexp.Regexp `json:"-"` // source - Source string `yaml:"source" json:"source"` - SourceProtocol string `yaml:"sourceProtocol" json:"sourceProtocol"` - SourceProtocolParsed *gortsplib.ClientProtocol `yaml:"-" json:"-"` - SourceAnyPortEnable bool `yaml:"sourceAnyPortEnable" json:"sourceAnyPortEnable"` - SourceFingerprint string `yaml:"sourceFingerprint" json:"sourceFingerprint"` - SourceOnDemand bool `yaml:"sourceOnDemand" json:"sourceOnDemand"` - SourceOnDemandStartTimeout StringDuration `yaml:"sourceOnDemandStartTimeout" json:"sourceOnDemandStartTimeout"` //nolint:lll - SourceOnDemandCloseAfter StringDuration `yaml:"sourceOnDemandCloseAfter" json:"sourceOnDemandCloseAfter"` - SourceRedirect string `yaml:"sourceRedirect" json:"sourceRedirect"` - DisablePublisherOverride bool `yaml:"disablePublisherOverride" json:"disablePublisherOverride"` - Fallback string `yaml:"fallback" json:"fallback"` + Source string `json:"source"` + SourceProtocol string `json:"sourceProtocol"` + SourceProtocolParsed *gortsplib.ClientProtocol `json:"-"` + SourceAnyPortEnable bool `json:"sourceAnyPortEnable"` + SourceFingerprint string `json:"sourceFingerprint"` + SourceOnDemand bool `json:"sourceOnDemand"` + SourceOnDemandStartTimeout StringDuration `json:"sourceOnDemandStartTimeout"` + SourceOnDemandCloseAfter StringDuration `json:"sourceOnDemandCloseAfter"` + SourceRedirect string `json:"sourceRedirect"` + DisablePublisherOverride bool `json:"disablePublisherOverride"` + Fallback string `json:"fallback"` // authentication - PublishUser string `yaml:"publishUser" json:"publishUser"` - PublishPass string `yaml:"publishPass" json:"publishPass"` - PublishIPs []string `yaml:"publishIPs" json:"publishIPs"` - PublishIPsParsed []interface{} `yaml:"-" json:"-"` - ReadUser string `yaml:"readUser" json:"readUser"` - ReadPass string `yaml:"readPass" json:"readPass"` - ReadIPs []string `yaml:"readIPs" json:"readIPs"` - ReadIPsParsed []interface{} `yaml:"-" json:"-"` + PublishUser string `json:"publishUser"` + PublishPass string `json:"publishPass"` + PublishIPs []string `json:"publishIPs"` + PublishIPsParsed []interface{} `json:"-"` + ReadUser string `json:"readUser"` + ReadPass string `json:"readPass"` + ReadIPs []string `json:"readIPs"` + ReadIPsParsed []interface{} `json:"-"` // custom commands - RunOnInit string `yaml:"runOnInit" json:"runOnInit"` - RunOnInitRestart bool `yaml:"runOnInitRestart" json:"runOnInitRestart"` - RunOnDemand string `yaml:"runOnDemand" json:"runOnDemand"` - RunOnDemandRestart bool `yaml:"runOnDemandRestart" json:"runOnDemandRestart"` - RunOnDemandStartTimeout StringDuration `yaml:"runOnDemandStartTimeout" json:"runOnDemandStartTimeout"` - RunOnDemandCloseAfter StringDuration `yaml:"runOnDemandCloseAfter" json:"runOnDemandCloseAfter"` - RunOnPublish string `yaml:"runOnPublish" json:"runOnPublish"` - RunOnPublishRestart bool `yaml:"runOnPublishRestart" json:"runOnPublishRestart"` - RunOnRead string `yaml:"runOnRead" json:"runOnRead"` - RunOnReadRestart bool `yaml:"runOnReadRestart" json:"runOnReadRestart"` + RunOnInit string `json:"runOnInit"` + RunOnInitRestart bool `json:"runOnInitRestart"` + RunOnDemand string `json:"runOnDemand"` + RunOnDemandRestart bool `json:"runOnDemandRestart"` + RunOnDemandStartTimeout StringDuration `json:"runOnDemandStartTimeout"` + RunOnDemandCloseAfter StringDuration `json:"runOnDemandCloseAfter"` + RunOnPublish string `json:"runOnPublish"` + RunOnPublishRestart bool `json:"runOnPublishRestart"` + RunOnRead string `json:"runOnRead"` + RunOnReadRestart bool `json:"runOnReadRestart"` } func (pconf *PathConf) checkAndFillMissing(name string) error { @@ -111,7 +111,7 @@ func (pconf *PathConf) checkAndFillMissing(name string) error { // normal path if name[0] != '~' { - err := CheckPathName(name) + err := IsValidPathName(name) if err != nil { return fmt.Errorf("invalid path name: %s (%s)", err, name) } @@ -245,7 +245,7 @@ func (pconf *PathConf) checkAndFillMissing(name string) error { if pconf.Fallback != "" { if strings.HasPrefix(pconf.Fallback, "/") { - err := CheckPathName(pconf.Fallback[1:]) + err := IsValidPathName(pconf.Fallback[1:]) if err != nil { return fmt.Errorf("'%s': %s", pconf.Fallback, err) } diff --git a/internal/conf/stringduration.go b/internal/conf/stringduration.go index e533a0dd..925c20c4 100644 --- a/internal/conf/stringduration.go +++ b/internal/conf/stringduration.go @@ -4,8 +4,6 @@ import ( "encoding/json" "errors" "time" - - "gopkg.in/yaml.v2" ) // StringDuration is a duration that is unmarshaled from a string. @@ -37,29 +35,3 @@ func (d *StringDuration) UnmarshalJSON(b []byte) error { *d = StringDuration(du) return nil } - -// MarshalYAML marshals a StringDuration into a string. -func (d StringDuration) MarshalYAML() ([]byte, error) { - return yaml.Marshal(time.Duration(d).String()) -} - -// UnmarshalYAML unmarshals a StringDuration from a string. -func (d *StringDuration) UnmarshalYAML(unmarshal func(interface{}) error) error { - var v interface{} - if err := unmarshal(&v); err != nil { - return err - } - - value, ok := v.(string) - if !ok { - return errors.New("invalid duration") - } - - du, err := time.ParseDuration(value) - if err != nil { - return err - } - - *d = StringDuration(du) - return nil -} diff --git a/internal/core/path_manager.go b/internal/core/path_manager.go index d238eb43..8fb84557 100644 --- a/internal/core/path_manager.go +++ b/internal/core/path_manager.go @@ -290,7 +290,7 @@ func (pm *pathManager) createPath(confName string, conf *conf.PathConf, name str } func (pm *pathManager) findPathConf(name string) (string, *conf.PathConf, error) { - err := conf.CheckPathName(name) + err := conf.IsValidPathName(name) if err != nil { return "", nil, fmt.Errorf("invalid path name: %s (%s)", err, name) } diff --git a/rtsp-simple-server.yml b/rtsp-simple-server.yml index ffa869f9..709137c2 100644 --- a/rtsp-simple-server.yml +++ b/rtsp-simple-server.yml @@ -54,7 +54,7 @@ rtspDisable: no protocols: [udp, multicast, tcp] # encrypt handshake and TCP streams with TLS (RTSPS). # available values are "no", "strict", "optional". -encryption: no +encryption: "no" # address of the TCP/RTSP listener. This is needed only when encryption is "no" or "optional". rtspAddress: :8554 # address of the TCP/TLS/RTSPS listener. This is needed only when encryption is "strict" or "optional".