engine/flags.go
Rama main · réplica de lectura del repositorio original.
package engine
import (
"flag"
"math"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/sirupsen/logrus"
"github.com/xyproto/algernon/cachemode"
"github.com/xyproto/algernon/platformdep"
"github.com/xyproto/algernon/themes"
"github.com/xyproto/algernon/utils"
"github.com/xyproto/datablock"
"github.com/xyproto/env/v2"
"github.com/xyproto/files"
)
// positionalArgs is the result of classifying Algernon's free-form positional arguments
type positionalArgs struct {
// ServerDirOrFilename is an existing directory or file given on the command line
ServerDirOrFilename string
// ServerAddr is set from a "host:port", ":port" or bare web-port argument
ServerAddr string
// ServerAddr2 is set when a second address-like argument is found
ServerAddr2 string
// RedisAddr is set when an argument looks like a Redis address (":6379" or ":6380")
RedisAddr string
// Remaining holds unclassified arguments, in the legacy order: cert, key, redis address, redis db index
Remaining []string
ServerDirSet bool
RedisAddrFromArgs bool
}
// Parse the flags, return the default hostname
func (ac *Config) handleFlags(serverTempDir string) {
var (
// The short version of some flags
serveJustHTTPShort, autoRefreshShort, productionModeShort,
debugModeShort, serverModeShort, useBoltShort, devModeShort,
showVersionShort, quietModeShort, cacheFileStatShort, simpleModeShort,
noBannerShort, quitAfterFirstRequestShort, verboseModeShort,
serveJustQUICShort, onlyLuaModeShort, redirectShort,
serverAddDomainShort, nonInteractive bool
// Used when setting the cache mode
cacheModeString string
// Used if disabling cache compression
rawCache bool
// Used if disabling the database backend
noDatabase bool
)
// The default for running the redis server on Windows is to listen
// to "localhost:port", but not just ":port".
host := ""
if platformdep.IsWindows {
host = "localhost"
// Default Bolt database file
ac.defaultBoltFilename = filepath.Join(serverTempDir, "algernon.db")
// Default log file
ac.defaultLogFile = filepath.Join(serverTempDir, "algernon.log")
}
// The usage function that provides more help (for --help or -h)
flag.Usage = generateUsageFunction(ac)
// Commandline flag configuration
flag.StringVar(&ac.serverDirOrFilename, "dir", ".", "Server directory")
flag.StringVar(&ac.serverAddr, "addr", "", "Server [host][:port] (ie \":443\" or \"[::1]:443\")")
flag.StringVar(&ac.serve.httpAddr, "http-addr", "", "HTTP (non-TLS) [host][:port]")
flag.StringVar(&ac.serve.httpsAddr, "https-addr", "", "HTTPS (TLS) [host][:port]")
flag.StringVar(&ac.serve.serverCert, "cert", "cert.pem", "Server certificate")
flag.StringVar(&ac.serve.serverKey, "key", "key.pem", "Server key")
flag.StringVar(&ac.redisAddr, "redis", "", "Redis [host][:port] (ie \""+ac.defaultRedisColonPort+"\")")
flag.IntVar(&ac.redisDBindex, "dbindex", 0, "Redis database index")
flag.StringVar(&ac.serverConfScript, "conf", "serverconf.lua", "Server configuration written in Lua")
flag.StringVar(&ac.serverLogFile, "log", "", "Server log file")
flag.StringVar(&ac.internalLogFilename, "internal", os.DevNull, "Internal log file")
flag.BoolVar(&ac.serveJustHTTP2, "http2only", false, "Serve HTTP/2, not HTTPS + HTTP/2")
flag.BoolVar(&ac.serveJustHTTP, "httponly", false, "Serve plain old HTTP")
flag.BoolVar(&ac.productionMode, "prod", false, "Production mode (when running as a system service)")
flag.BoolVar(&ac.debugMode, "debug", false, "Debug mode")
flag.BoolVar(&ac.verboseMode, "verbose", false, "Verbose logging")
flag.BoolVar(&ac.serve.redirectHTTP, "redirect", false, "Redirect HTTP traffic to HTTPS if both are enabled")
flag.BoolVar(&ac.autoRefresh, "autorefresh", false, "Enable the auto-refresh feature")
flag.StringVar(&ac.autoRefreshDir, "watchdir", "", "Directory to watch (also enables auto-refresh)")
flag.StringVar(&ac.eventAddr, "eventserver", "", "SSE [host][:port] (ie \""+ac.defaultEventColonPort+"\")")
flag.StringVar(&ac.eventRefresh, "eventrefresh", ac.defaultEventRefresh, "Event refresh interval (ie \""+ac.defaultEventRefresh+"\")")
flag.BoolVar(&ac.serverMode, "server", false, "Same as --noninteractive") // deprecated, use --noninteractive or -s
flag.BoolVar(&nonInteractive, "noninteractive", false, "Non-interactive mode (disable debug + interactive mode)")
flag.StringVar(&ac.mariadbDSN, "maria", "", "MariaDB/MySQL connection string (DSN)")
flag.StringVar(&ac.mariaDatabase, "mariadb", "", "MariaDB/MySQL database name")
flag.StringVar(&ac.postgresDSN, "postgres", "", "PostgreSQL connection string (DSN)")
flag.StringVar(&ac.postgresDatabase, "postgresdb", "", "PostgreSQL database name")
flag.StringVar(&ac.sqliteConnectionString, "sqlite", "", "SQLite filename / connection string")
flag.BoolVar(&ac.useBolt, "bolt", false, "Use the default Bolt filename")
flag.StringVar(&ac.boltFilename, "boltdb", "", "Bolt database filename")
flag.Int64Var(&ac.limitRequests, "limit", ac.defaultLimit, "Limit clients to a number of requests per second")
flag.BoolVar(&ac.disableRateLimiting, "nolimit", false, "Disable rate limiting")
flag.BoolVar(&ac.devMode, "dev", false, "Development mode")
flag.BoolVar(&ac.showVersion, "version", false, "Version")
flag.StringVar(&cacheModeString, "cache", "", "Cache everything but Amber, Lua, GCSS and Markdown")
flag.Uint64Var(&ac.cacheSize, "cachesize", ac.defaultCacheSize, "Cache size, in bytes")
flag.Uint64Var(&ac.largeFileSize, "largesize", ac.defaultLargeFileSize, "Threshold for not reading static files into memory, in bytes")
flag.Uint64Var(&ac.writeTimeout, "timeout", 10, "Timeout when writing to a client, in seconds")
flag.BoolVar(&ac.quietMode, "quiet", false, "Quiet")
flag.BoolVar(&rawCache, "rawcache", false, "Disable cache compression")
flag.StringVar(&ac.serverHeaderName, "servername", ac.versionString, "Server header name")
flag.BoolVar(&ac.cacheFileStat, "statcache", false, "Cache os.Stat")
flag.BoolVar(&ac.serverAddDomain, "domain", false, "Look for files in the directory named the same as the hostname")
flag.BoolVar(&ac.simpleMode, "simple", false, "Serve a directory of files over HTTP")
flag.StringVar(&ac.openExecutable, "open", "", "Open URL after serving, with an application")
flag.BoolVar(&ac.quitAfterFirstRequest, "quit", false, "Quit after the first request")
flag.BoolVar(&ac.noCache, "nocache", false, "Disable caching")
flag.BoolVar(&ac.noHeaders, "noheaders", false, "Don't set any HTTP headers by default")
flag.BoolVar(&ac.stricterHeaders, "stricter", false, "Stricter HTTP headers")
flag.StringVar(&ac.defaultTheme, "theme", themes.DefaultTheme, "Theme for Markdown and directory listings")
flag.BoolVar(&ac.noBanner, "nobanner", false, "Don't show a banner at start")
flag.BoolVar(&ac.ctrldTwice, "ctrld", false, "Press ctrl-d twice to exit")
if quicEnabled {
flag.BoolVar(&ac.serveJustQUIC, "quic", false, "Serve just QUIC")
}
flag.BoolVar(&noDatabase, "nodb", false, "No database backend")
flag.BoolVar(&ac.onlyLuaMode, "lua", false, "Only present the Lua REPL")
flag.StringVar(&ac.combinedAccessLogFilename, "accesslog", "", "Combined access log filename")
flag.StringVar(&ac.commonAccessLogFilename, "ncsa", "", "NCSA access log filename")
flag.BoolVar(&ac.clearDefaultPathPrefixes, "clear", false, "Clear the default URI prefixes for handling permissions")
flag.StringVar(&ac.cookieSecret, "cookiesecret", "", "Secret to be used when setting and getting login cookies")
flag.BoolVar(&ac.serve.useCertMagic, "letsencrypt", false, "Use Let's Encrypt for all served domains and serve regular HTTPS")
flag.BoolVar(&ac.serve.useCertMagicStaging, "testcert", false, "Use the Let's Encrypt staging CA instead of the production CA (for testing)")
flag.BoolVar(&ac.hideDotfiles, "hide-dotfiles", false, "Hide files and directories starting with '.'")
flag.BoolVar(&ac.strictSymlinks, "strict-symlinks", false, "Don't follow symlinks that lead out of the served directory")
flag.StringVar(&ac.dirBaseURL, "dirbaseurl", "", "Base URL for the directory listing (optional)")
flag.Func("proxy", "Reverse proxy to ENDPOINT, or /PREFIX=ENDPOINT (can be given several times)", func(value string) error {
rp, err := ParseReverseProxyFlag(value)
if err != nil {
return err
}
if ac.reverseProxyConfig == nil {
ac.reverseProxyConfig = NewReverseProxyConfig()
}
ac.reverseProxyConfig.Add(rp)
return nil
})
// The short versions of some flags
flag.BoolVar(&serveJustHTTPShort, "t", false, "Serve plain old HTTP")
flag.BoolVar(&autoRefreshShort, "a", false, "Enable the auto-refresh feature")
flag.BoolVar(&serverModeShort, "s", false, "Non-interactive mode (short for --noninteractive)")
flag.BoolVar(&useBoltShort, "b", false, "Use the default Bolt filename")
flag.BoolVar(&productionModeShort, "p", false, "Production mode (when running as a system service)")
flag.BoolVar(&debugModeShort, "d", false, "Debug mode")
flag.BoolVar(&devModeShort, "e", false, "Development mode")
flag.BoolVar(&showVersionShort, "v", false, "Version")
flag.BoolVar(&verboseModeShort, "V", false, "Verbose")
flag.BoolVar(&quietModeShort, "q", false, "Quiet")
flag.BoolVar(&cacheFileStatShort, "c", false, "Cache os.Stat")
flag.BoolVar(&simpleModeShort, "x", false, "Simple mode")
flag.BoolVar(&ac.openURLAfterServing, "o", false, "Open URL after serving")
flag.BoolVar(&quitAfterFirstRequestShort, "z", false, "Quit after the first request")
flag.BoolVar(&ac.markdownMode, "m", false, "Markdown mode")
flag.BoolVar(&noBannerShort, "n", false, "Don't show a banner at start")
if quicEnabled {
flag.BoolVar(&serveJustQUICShort, "u", false, "Serve just QUIC")
}
flag.BoolVar(&onlyLuaModeShort, "l", false, "Only present the Lua REPL")
flag.BoolVar(&redirectShort, "r", false, "Redirect HTTP traffic to HTTPS, if both are enabled")
flag.BoolVar(&serverAddDomainShort, "D", false, "Look for files in the directory named the same as the hostname")
flag.Parse()
// Accept both long and short versions of some flags
ac.serveJustHTTP = ac.serveJustHTTP || serveJustHTTPShort
ac.autoRefresh = ac.autoRefresh || autoRefreshShort
ac.debugMode = ac.debugMode || debugModeShort
ac.serverMode = ac.serverMode || serverModeShort
ac.serverAddDomain = ac.serverAddDomain || serverAddDomainShort
// --noninteractive / -s is the preferred way to enable server mode
if nonInteractive || serverModeShort {
ac.serverMode = true
}
// Track which settings were explicitly set from the command line
if ac.serverMode {
ac.serverModeFromCLI = true
}
if ac.serve.redirectHTTP {
ac.serve.redirectFromCLI = true
}
if ac.serve.useCertMagic {
ac.serve.certMagicFromCLI = true
}
ac.useBolt = ac.useBolt || useBoltShort
ac.productionMode = ac.productionMode || productionModeShort
ac.devMode = ac.devMode || devModeShort
ac.showVersion = ac.showVersion || showVersionShort
ac.quietMode = ac.quietMode || quietModeShort
ac.cacheFileStat = ac.cacheFileStat || cacheFileStatShort
ac.simpleMode = ac.simpleMode || simpleModeShort
ac.openURLAfterServing = ac.openURLAfterServing || (ac.openExecutable != "")
ac.quitAfterFirstRequest = ac.quitAfterFirstRequest || quitAfterFirstRequestShort
ac.verboseMode = ac.verboseMode || verboseModeShort
ac.noBanner = ac.noBanner || noBannerShort
if quicEnabled {
ac.serveJustQUIC = ac.serveJustQUIC || serveJustQUICShort
}
ac.onlyLuaMode = ac.onlyLuaMode || onlyLuaModeShort
ac.serve.redirectHTTP = ac.serve.redirectHTTP || redirectShort
// --largesize is passed to http.MaxBytesReader as int64; reject
// values that would wrap to negative rather than silently truncate.
if ac.largeFileSize > math.MaxInt64 {
logrus.Fatalf("--largesize is too large: %d exceeds the maximum supported value of %d bytes", ac.largeFileSize, int64(math.MaxInt64))
}
// Serve a single Markdown file once, and open it in the browser
if ac.markdownMode {
ac.quietMode = true
ac.openURLAfterServing = true
ac.quitAfterFirstRequest = true
}
// If only using the Lua REPL, don't include the banner, and don't serve anything
if ac.onlyLuaMode {
ac.noBanner = true
ac.debugMode = true
ac.serverConfScript = ""
}
// Check if IGNOREEOF is set
if ignoreEOF := env.Int("IGNOREEOF", 0); ignoreEOF > 1 {
ac.ctrldTwice = true
}
// Disable verbose mode if quiet mode has been enabled
if ac.quietMode {
ac.verboseMode = false
}
// Enable cache compression unless raw cache is specified
ac.cacheCompression = !rawCache
ac.redisAddrSpecified = ac.redisAddr != ""
if ac.redisAddr == "" {
// The default host and port
ac.redisAddr = utils.JoinHostPort(host, ac.defaultRedisColonPort)
}
// May be overridden by devMode
if ac.serverMode {
ac.debugMode = false
}
if noDatabase {
ac.boltFilename = os.DevNull
}
// TODO: If flags are set in addition to -p or -e, don't override those
// when -p or -e is set.
// Change several defaults if production mode is enabled
switch {
case ac.productionMode:
// Use system directories
ac.serverDirOrFilename = "/srv/algernon"
ac.serve.serverCert = "/etc/algernon/cert.pem"
ac.serve.serverKey = "/etc/algernon/key.pem"
ac.cacheMode = cachemode.Production
ac.serverMode = true
case ac.devMode:
// Change several defaults if development mode is enabled
ac.serveJustHTTP = true
// serverLogFile = defaultLogFile
ac.debugMode = true
// TODO: Make it possible to set --limit to the default limit also when -e is used
if ac.limitRequests == ac.defaultLimit {
ac.limitRequests = 700 // Increase the rate limit considerably
}
ac.cacheMode = cachemode.Development
case ac.simpleMode:
ac.useBolt = true
ac.boltFilename = os.DevNull
ac.serveJustHTTP = true
ac.serverMode = true
ac.cacheMode = cachemode.Off
ac.noCache = true
ac.disableRateLimiting = true
ac.clearDefaultPathPrefixes = true
ac.noHeaders = true
ac.writeTimeout = 3600 * 24
}
if ac.onlyLuaMode {
// Use a random database, so that several lua REPLs can be started without colliding,
// but only if the current default bolt database file can not be opened.
if ac.boltFilename != os.DevNull && !files.CanRead(ac.boltFilename) {
tempFile, err := os.CreateTemp("", "algernon_repl*.db")
if err == nil { // no issue
ac.boltFilename = tempFile.Name()
}
}
}
// If a watch directory is given, enable the auto refresh feature
if ac.autoRefreshDir != "" {
ac.autoRefresh = true
}
// If nocache is given, disable the cache
if ac.noCache {
ac.cacheMode = cachemode.Off
ac.cacheFileStat = false
}
// Convert the request limit to a string
ac.limitRequestsString = strconv.FormatInt(ac.limitRequests, 10)
// If auto-refresh is enabled, change the caching and disable minification
if ac.autoRefresh {
if cacheModeString == "" {
// Disable caching by default, when auto-refresh is enabled
ac.cacheMode = cachemode.Off
ac.cacheFileStat = false
}
// Readable output is more useful than minified output during development
ac.jsxOptions.MinifyWhitespace = false
ac.jsxOptions.MinifyIdentifiers = false
ac.jsxOptions.MinifySyntax = false
}
// The cache flag overrides the settings from the other modes
if cacheModeString != "" {
ac.cacheMode = cachemode.New(cacheModeString)
}
// Disable cache entirely if cacheSize is set to 0
if ac.cacheSize == 0 {
ac.cacheMode = cachemode.Off
}
// Set cacheSize to 0 if the cache is disabled
if ac.cacheMode == cachemode.Off {
ac.cacheSize = 0
}
// If cache mode is unset, use the dev mode
if ac.cacheMode == cachemode.Unset {
ac.cacheMode = cachemode.Default
}
if ac.cacheMode == cachemode.Small {
ac.cacheMaxEntitySize = ac.defaultCacheMaxEntitySize
}
// For backward compatibility with previous versions of Algernon.
// Classify positional arguments by content rather than by position,
// so that the directory and address can be given in any order.
fs := datablock.NewFileStat(ac.cacheFileStat, ac.defaultStatCacheRefresh)
classified := classifyPositionalArgs(dropSelfArg(flag.Args()), func(p string) bool {
return fs.IsDir(p) || fs.Exists(p)
})
if classified.ServerDirSet {
ac.serverDirOrFilename = classified.ServerDirOrFilename
}
if classified.ServerAddr != "" {
ac.serverAddr = classified.ServerAddr
}
// Two positional addresses: first = HTTP, second = HTTPS (only if flags didn't set them)
if classified.ServerAddr2 != "" {
if ac.serve.httpAddr == "" {
ac.serve.httpAddr = classified.ServerAddr
}
if ac.serve.httpsAddr == "" {
ac.serve.httpsAddr = classified.ServerAddr2
}
ac.serverAddr = "" // will be derived from httpAddr/httpsAddr in serve.go
}
// Track that port configuration came from the command line. This includes
// a single positional "host:port" (classified into ac.serverAddr), so that
// flag-driven modes like -p do not silently override the user's choice.
if ac.serve.httpAddr != "" || ac.serve.httpsAddr != "" || ac.serverAddr != "" {
ac.serve.portConfigFromCLI = true
}
if classified.RedisAddrFromArgs {
ac.redisAddr = classified.RedisAddr
ac.redisAddrSpecified = true
}
remainingArgs := classified.Remaining
redisAddrFromArgs := classified.RedisAddrFromArgs
// Clean up path in ac.serverDirOrFilename
// .Rel calls .Clean on the result.
if pwd, err := os.Getwd(); err == nil { // no error
if cleanPath, err := filepath.Rel(pwd, ac.serverDirOrFilename); err == nil { // no error
ac.serverDirOrFilename = cleanPath
}
}
// Handle remaining unclassified positional arguments for backward compatibility.
// Expected order: cert file, key file, redis address, redis db index.
if len(remainingArgs) > 0 {
ac.serve.serverCert = remainingArgs[0]
}
if len(remainingArgs) > 1 {
ac.serve.serverKey = remainingArgs[1]
}
if len(remainingArgs) > 2 && !redisAddrFromArgs {
ac.redisAddr = remainingArgs[2]
ac.redisAddrSpecified = true
}
dbIndexArgIndex := 3
if redisAddrFromArgs {
dbIndexArgIndex = 2
}
if len(remainingArgs) > dbIndexArgIndex {
// Convert the dbindex from string to int
DBindex, err := strconv.Atoi(remainingArgs[dbIndexArgIndex])
if err == nil { // no error
ac.redisDBindex = DBindex
}
}
// Add the serverConfScript to the list of configuration scripts to be read and executed
if ac.serverConfScript != "" && ac.serverConfScript != os.DevNull {
ac.serverConfigurationFilenames = append(ac.serverConfigurationFilenames, ac.serverConfScript, filepath.Join(ac.serverDirOrFilename, ac.serverConfScript))
}
ac.serverHost = host
// CertMagic and Let's Encrypt
if ac.serve.useCertMagic {
logrus.Info("Use Cert Magic")
if dirEntries, err := os.ReadDir(ac.serverDirOrFilename); err != nil {
logrus.Error("Could not use Cert Magic:" + err.Error())
ac.serve.useCertMagic = false
} else {
for _, dirEntry := range dirEntries {
basename := filepath.Base(dirEntry.Name())
dirOrSymlink := dirEntry.IsDir() || ((dirEntry.Type() & os.ModeSymlink) == os.ModeSymlink)
// TODO: Confirm that the symlink is a symlink to a directory, if it's a symlink
if dirOrSymlink && strings.Contains(basename, ".") && !strings.HasPrefix(basename, ".") && !strings.HasSuffix(basename, ".old") {
ac.serve.certMagicDomains = append(ac.serve.certMagicDomains, basename)
}
}
// Using Let's Encrypt implies --domain, to search for suitable directories in the directory to be served
ac.serverAddDomain = true
}
}
}
// Set the values that has not been set by flags nor scripts (and can be set by both)
// Returns true if a "ready function" has been run.
func (ac *Config) finalConfiguration(host string) bool {
// Set the server host and port (commandline flags overrides Lua configuration)
if ac.serverAddr == "" {
if ac.serverAddrLua != "" {
ac.serverAddr = ac.serverAddrLua
} else {
ac.serverAddr = utils.JoinHostPort(host, ac.defaultWebColonPort)
}
}
// Set the event server host and port
if ac.eventAddr == "" {
ac.eventAddr = utils.JoinHostPort(host, ac.defaultEventColonPort)
}
// Turn off debug mode if production mode is enabled
if ac.productionMode {
// Turn off debug mode
ac.debugMode = false
}
hasReadyFunction := ac.serverReadyFunctionLua != nil
// Run the Lua function specified with the OnReady function, if available
if hasReadyFunction {
// Useful for outputting configuration information after both
// configuration scripts have been run and flags have been parsed
ac.serverReadyFunctionLua()
}
return hasReadyFunction
}
// dropSelfArg returns args with any entry that refers to the running executable removed.
// This guards against Docker images whose ENTRYPOINT is paired with a redundant
// CMD ["/bin/algernon"], which Docker appends as a positional argument, causing the
// executable path to be picked up as the server directory.
func dropSelfArg(args []string) []string {
candidates := map[string]struct{}{}
add := func(p string) {
if p == "" {
return
}
candidates[p] = struct{}{}
if abs, err := filepath.Abs(p); err == nil {
candidates[abs] = struct{}{}
}
}
if len(os.Args) > 0 {
add(os.Args[0])
}
if exe, err := os.Executable(); err == nil {
add(exe)
if resolved, err := filepath.EvalSymlinks(exe); err == nil {
add(resolved)
}
}
if len(candidates) == 0 {
return args
}
isSelf := func(arg string) bool {
if _, ok := candidates[arg]; ok {
return true
}
if abs, err := filepath.Abs(arg); err == nil {
if _, ok := candidates[abs]; ok {
return true
}
}
return false
}
out := args[:0:0]
for _, a := range args {
if isSelf(a) {
continue
}
out = append(out, a)
}
return out
}
// classifyPositionalArgs splits free-form positional arguments into the categories Algernon
// historically supported. If pathExists is nil, the serverDir check is skipped.
func classifyPositionalArgs(args []string, pathExists func(string) bool) positionalArgs {
if pathExists == nil {
pathExists = func(string) bool { return false }
}
looksLikeCertOrKey := func(arg string) bool {
switch strings.ToLower(filepath.Ext(arg)) {
case ".pem", ".crt", ".cer", ".key":
return true
default:
return false
}
}
looksLikeServerFile := func(arg string) bool {
switch strings.ToLower(filepath.Ext(arg)) {
case ".lua", ".md", ".markdown", ".zip", ".alg":
return true
default:
return false
}
}
looksLikeRedisAddr := func(arg string) bool {
return strings.HasSuffix(arg, ":6379") || strings.HasSuffix(arg, ":6380")
}
looksLikeWebPort := func(port string) bool {
return port == "80" || port == "8080" || strings.HasSuffix(port, "000")
}
var result positionalArgs
for _, arg := range args {
if looksLikeCertOrKey(arg) {
result.Remaining = append(result.Remaining, arg)
continue
}
if !result.ServerDirSet && (pathExists(arg) || looksLikeServerFile(arg)) {
if strings.HasSuffix(arg, string(os.PathSeparator)) {
result.ServerDirOrFilename = arg[:len(arg)-1]
} else {
result.ServerDirOrFilename = arg
}
result.ServerDirSet = true
continue
}
if looksLikeRedisAddr(arg) {
result.RedisAddr = arg
result.RedisAddrFromArgs = true
continue
}
// Check if this looks like a host:port address (handles IPv4, IPv6, and port-only)
if _, _, err := net.SplitHostPort(arg); err == nil {
// Valid host:port format, assume this is a web server address
if result.ServerAddr == "" {
result.ServerAddr = arg
} else if result.ServerAddr2 == "" {
result.ServerAddr2 = arg
} else {
result.Remaining = append(result.Remaining, arg)
}
continue
}
if _, err := strconv.Atoi(arg); err == nil { // no error
// If in doubt, assume this is the web server port.
if looksLikeWebPort(arg) || !result.RedisAddrFromArgs {
addr := net.JoinHostPort("", arg)
if result.ServerAddr == "" {
result.ServerAddr = addr
} else if result.ServerAddr2 == "" {
result.ServerAddr2 = addr
} else {
result.Remaining = append(result.Remaining, arg)
}
} else {
result.Remaining = append(result.Remaining, arg)
}
continue
}
result.Remaining = append(result.Remaining, arg)
}
return result
}