147 lines
3.5 KiB
Go
147 lines
3.5 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
const (
|
|
DefaultERPEnvironmentFile = ".env"
|
|
|
|
maximumERPEnvironmentFileBytes = 32 << 10
|
|
maximumERPEnvironmentLineBytes = 4 << 10
|
|
)
|
|
|
|
var (
|
|
errERPEnvironmentFileInvalid = errors.New("ERP .env file is invalid")
|
|
errERPEnvironmentLookup = errors.New("ERP .env lookup is required")
|
|
)
|
|
|
|
// WithERPEnvironmentFile returns a lookup that uses the process environment
|
|
// first and only falls back to approved ERP values from path.
|
|
func WithERPEnvironmentFile(
|
|
path string,
|
|
parent LookupEnvironment,
|
|
) (LookupEnvironment, error) {
|
|
if parent == nil {
|
|
return nil, errERPEnvironmentLookup
|
|
}
|
|
values, err := readERPEnvironmentFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return func(name string) (string, bool) {
|
|
if value, exists := parent(name); exists {
|
|
return value, true
|
|
}
|
|
value, exists := values[name]
|
|
return value, exists
|
|
}, nil
|
|
}
|
|
|
|
func readERPEnvironmentFile(path string) (map[string]string, error) {
|
|
values := make(map[string]string)
|
|
if path == "" {
|
|
return nil, errERPEnvironmentFileInvalid
|
|
}
|
|
file, err := os.Open(path)
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return values, nil
|
|
}
|
|
if err != nil {
|
|
return nil, errors.New("ERP .env file cannot be read")
|
|
}
|
|
defer file.Close()
|
|
|
|
contents, err := io.ReadAll(io.LimitReader(
|
|
file,
|
|
maximumERPEnvironmentFileBytes+1,
|
|
))
|
|
if err != nil || len(contents) > maximumERPEnvironmentFileBytes ||
|
|
!utf8.Valid(contents) || strings.IndexByte(string(contents), 0) >= 0 {
|
|
return nil, errERPEnvironmentFileInvalid
|
|
}
|
|
|
|
for _, rawLine := range strings.Split(string(contents), "\n") {
|
|
line := strings.TrimSuffix(rawLine, "\r")
|
|
if len(line) > maximumERPEnvironmentLineBytes {
|
|
return nil, errERPEnvironmentFileInvalid
|
|
}
|
|
line = strings.TrimSpace(line)
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(line, "export ") {
|
|
line = strings.TrimSpace(strings.TrimPrefix(line, "export "))
|
|
}
|
|
name, rawValue, found := strings.Cut(line, "=")
|
|
name = strings.TrimSpace(name)
|
|
if !found || !validEnvironmentName(name) {
|
|
return nil, errERPEnvironmentFileInvalid
|
|
}
|
|
if !isERPEnvironmentName(name) {
|
|
if strings.HasPrefix(name, "CMROUBAO_") {
|
|
return nil, errERPEnvironmentFileInvalid
|
|
}
|
|
continue
|
|
}
|
|
if _, exists := values[name]; exists {
|
|
return nil, errERPEnvironmentFileInvalid
|
|
}
|
|
value, err := dotenvValue(rawValue)
|
|
if err != nil {
|
|
return nil, errERPEnvironmentFileInvalid
|
|
}
|
|
values[name] = value
|
|
}
|
|
return values, nil
|
|
}
|
|
|
|
func dotenvValue(rawValue string) (string, error) {
|
|
value := strings.TrimSpace(rawValue)
|
|
if value == "" {
|
|
return value, nil
|
|
}
|
|
quote := value[0]
|
|
if quote == '\'' || quote == '"' {
|
|
if len(value) < 2 || value[len(value)-1] != quote {
|
|
return "", errERPEnvironmentFileInvalid
|
|
}
|
|
return value[1 : len(value)-1], nil
|
|
}
|
|
if strings.HasSuffix(value, "\"") || strings.HasSuffix(value, "'") {
|
|
return "", errERPEnvironmentFileInvalid
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func isERPEnvironmentName(name string) bool {
|
|
switch name {
|
|
case ShunyunbaoURLEnvironment,
|
|
ShunyunbaoUsernameEnvironment,
|
|
ShunyunbaoPasswordEnvironment:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func validEnvironmentName(name string) bool {
|
|
if name == "" {
|
|
return false
|
|
}
|
|
for index := 0; index < len(name); index++ {
|
|
character := name[index]
|
|
if (character >= 'A' && character <= 'Z') ||
|
|
(character >= '0' && character <= '9' && index > 0) ||
|
|
character == '_' {
|
|
continue
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|