Files
cmroubao/backend-api/internal/config/envfile.go
T

149 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,
OCRAPIURLEnvironment,
ERPDebugLogEnvironment:
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
}