Skip to content

Commit 918642f

Browse files
enwikuna0xJacky
andauthored
Refactor nginx path resolution with improved regex and fallback (#1414)
* Refactor nginx path resolution with improved regex and fallback Updated regex patterns for extracting nginx configuration paths and added fallback mechanisms for determining paths on different operating systems. Improved error handling and logging for better debugging. * enhance: nginx path parsing and add tests #1412, #1414 --------- Co-authored-by: 0xJacky <[email protected]>
1 parent 98e83f1 commit 918642f

File tree

2 files changed

+211
-44
lines changed

2 files changed

+211
-44
lines changed

internal/nginx/resolve_path.go

Lines changed: 110 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package nginx
22

33
import (
4+
"os"
45
"path/filepath"
5-
"regexp"
66
"runtime"
77
"strings"
88

@@ -34,16 +34,62 @@ func resolvePath(path string) string {
3434
return path
3535
}
3636

37+
func extractConfigureArg(out, flag string) string {
38+
if out == "" || flag == "" {
39+
return ""
40+
}
41+
42+
if !strings.HasPrefix(flag, "--") {
43+
flag = "--" + flag
44+
}
45+
46+
needle := flag + "="
47+
idx := strings.Index(out, needle)
48+
if idx == -1 {
49+
return ""
50+
}
51+
52+
start := idx + len(needle)
53+
if start >= len(out) {
54+
return ""
55+
}
56+
57+
value := out[start:]
58+
value = strings.TrimLeft(value, " \t")
59+
if value == "" {
60+
return ""
61+
}
62+
63+
if value[0] == '"' || value[0] == '\'' {
64+
quoteChar := value[0]
65+
rest := value[1:]
66+
closingIdx := strings.IndexByte(rest, quoteChar)
67+
if closingIdx == -1 {
68+
return strings.TrimSpace(rest)
69+
}
70+
return strings.TrimSpace(rest[:closingIdx])
71+
}
72+
73+
cut := len(value)
74+
if idx := strings.Index(value, " --"); idx != -1 && idx < cut {
75+
cut = idx
76+
}
77+
if idx := strings.IndexAny(value, "\r\n"); idx != -1 && idx < cut {
78+
cut = idx
79+
}
80+
81+
return strings.TrimSpace(value[:cut])
82+
}
83+
3784
// GetPrefix returns the prefix of the nginx executable
3885
func GetPrefix() string {
3986
if nginxPrefix != "" {
4087
return nginxPrefix
4188
}
4289

4390
out := getNginxV()
44-
r, _ := regexp.Compile(`--prefix=(\S+)`)
45-
match := r.FindStringSubmatch(out)
46-
if len(match) < 1 {
91+
prefix := extractConfigureArg(out, "--prefix")
92+
if prefix == "" {
4793
logger.Debug("nginx.GetPrefix len(match) < 1")
4894
if runtime.GOOS == "windows" {
4995
nginxPrefix = GetNginxExeDir()
@@ -53,21 +99,29 @@ func GetPrefix() string {
5399
return nginxPrefix
54100
}
55101

56-
nginxPrefix = resolvePath(match[1])
102+
nginxPrefix = resolvePath(prefix)
57103
return nginxPrefix
58104
}
59105

60-
// GetConfPath returns the path of the nginx configuration file
106+
// GetConfPath returns the nginx configuration directory (e.g. "/etc/nginx").
107+
// It tries to derive it from `nginx -V --conf-path=...`.
108+
// If parsing fails, it falls back to a reasonable default instead of returning "".
61109
func GetConfPath(dir ...string) (confPath string) {
62110
if settings.NginxSettings.ConfigDir == "" {
63111
out := getNginxV()
64-
r, _ := regexp.Compile("--conf-path=(.*)/(.*.conf)")
65-
match := r.FindStringSubmatch(out)
66-
if len(match) < 1 {
67-
logger.Error("nginx.GetConfPath len(match) < 1")
68-
return ""
112+
fullConf := extractConfigureArg(out, "--conf-path")
113+
114+
if fullConf != "" {
115+
confPath = filepath.Dir(fullConf)
116+
} else {
117+
if runtime.GOOS == "windows" {
118+
confPath = GetPrefix()
119+
} else {
120+
confPath = "/etc/nginx"
121+
}
122+
123+
logger.Debug("nginx.GetConfPath fallback used", "base", confPath)
69124
}
70-
confPath = match[1]
71125
} else {
72126
confPath = settings.NginxSettings.ConfigDir
73127
}
@@ -81,35 +135,59 @@ func GetConfPath(dir ...string) (confPath string) {
81135
return joined
82136
}
83137

84-
// GetConfEntryPath returns the path of the nginx configuration file
138+
// GetConfEntryPath returns the absolute path to the main nginx.conf.
139+
// It prefers the value from `nginx -V --conf-path=...`.
140+
// If that can't be parsed, it falls back to "<confDir>/nginx.conf".
85141
func GetConfEntryPath() (path string) {
86142
if settings.NginxSettings.ConfigPath == "" {
87143
out := getNginxV()
88-
r, _ := regexp.Compile("--conf-path=(.*.conf)")
89-
match := r.FindStringSubmatch(out)
90-
if len(match) < 1 {
91-
logger.Error("nginx.GetConfEntryPath len(match) < 1")
92-
return ""
144+
path = extractConfigureArg(out, "--conf-path")
145+
146+
if path == "" {
147+
baseDir := GetConfPath()
148+
149+
if baseDir != "" {
150+
path = filepath.Join(baseDir, "nginx.conf")
151+
} else {
152+
logger.Error("nginx.GetConfEntryPath: cannot determine nginx.conf path")
153+
path = ""
154+
}
93155
}
94-
path = match[1]
95156
} else {
96157
path = settings.NginxSettings.ConfigPath
97158
}
98159

99160
return resolvePath(path)
100161
}
101162

102-
// GetPIDPath returns the path of the nginx PID file
163+
// GetPIDPath returns the nginx master process PID file path.
164+
// We try to read it from `nginx -V --pid-path=...`.
165+
// If that fails (which often happens in container images), we probe common
166+
// locations like /run/nginx.pid and /var/run/nginx.pid instead of just failing.
103167
func GetPIDPath() (path string) {
104168
if settings.NginxSettings.PIDPath == "" {
105169
out := getNginxV()
106-
r, _ := regexp.Compile("--pid-path=(.*.pid)")
107-
match := r.FindStringSubmatch(out)
108-
if len(match) < 1 {
109-
logger.Error("pid path not found in nginx -V output")
110-
return ""
170+
path = extractConfigureArg(out, "--pid-path")
171+
172+
if path == "" {
173+
candidates := []string{
174+
"/var/run/nginx.pid",
175+
"/run/nginx.pid",
176+
}
177+
178+
for _, c := range candidates {
179+
if _, err := os.Stat(c); err == nil {
180+
logger.Debug("GetPIDPath fallback hit", "path", c)
181+
path = c
182+
break
183+
}
184+
}
185+
186+
if path == "" {
187+
logger.Error("GetPIDPath: could not determine PID path")
188+
return ""
189+
}
111190
}
112-
path = match[1]
113191
} else {
114192
path = settings.NginxSettings.PIDPath
115193
}
@@ -128,10 +206,8 @@ func GetAccessLogPath() (path string) {
128206

129207
if path == "" {
130208
out := getNginxV()
131-
r, _ := regexp.Compile(`--http-log-path=(\S+)`)
132-
match := r.FindStringSubmatch(out)
133-
if len(match) > 1 {
134-
path = match[1]
209+
path = extractConfigureArg(out, "--http-log-path")
210+
if path != "" {
135211
resolvedPath := resolvePath(path)
136212

137213
// Check if the matched path exists but is not a regular file
@@ -159,10 +235,8 @@ func GetErrorLogPath() string {
159235

160236
if path == "" {
161237
out := getNginxV()
162-
r, _ := regexp.Compile(`--error-log-path=(\S+)`)
163-
match := r.FindStringSubmatch(out)
164-
if len(match) > 1 {
165-
path = match[1]
238+
path = extractConfigureArg(out, "--error-log-path")
239+
if path != "" {
166240
resolvedPath := resolvePath(path)
167241

168242
// Check if the matched path exists but is not a regular file
@@ -189,16 +263,8 @@ func GetModulesPath() string {
189263
// First try to get from nginx -V output
190264
out := getNginxV()
191265
if out != "" {
192-
// Look for --modules-path in the output
193-
if strings.Contains(out, "--modules-path=") {
194-
parts := strings.Split(out, "--modules-path=")
195-
if len(parts) > 1 {
196-
// Extract the path
197-
path := strings.Split(parts[1], " ")[0]
198-
// Remove quotes if present
199-
path = strings.Trim(path, "\"")
200-
return resolvePath(path)
201-
}
266+
if path := extractConfigureArg(out, "--modules-path"); path != "" {
267+
return resolvePath(path)
202268
}
203269
}
204270

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package nginx
2+
3+
import (
4+
"fmt"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/0xJacky/Nginx-UI/settings"
9+
)
10+
11+
func TestExtractConfigureArg(t *testing.T) {
12+
t.Parallel()
13+
14+
output := `
15+
nginx version: nginx/1.25.2
16+
configure arguments: --prefix="/Program Files/Nginx" --conf-path='/Program Files/Nginx/conf/nginx.conf' --pid-path=/var/run/nginx.pid
17+
`
18+
19+
tests := []struct {
20+
name string
21+
flag string
22+
want string
23+
}{
24+
{
25+
name: "double quoted conf path",
26+
flag: "--conf-path",
27+
want: "/Program Files/Nginx/conf/nginx.conf",
28+
},
29+
{
30+
name: "single quoted conf path alias",
31+
flag: "conf-path",
32+
want: "/Program Files/Nginx/conf/nginx.conf",
33+
},
34+
{
35+
name: "unquoted pid path",
36+
flag: "pid-path",
37+
want: "/var/run/nginx.pid",
38+
},
39+
{
40+
name: "missing flag",
41+
flag: "--http-log-path",
42+
want: "",
43+
},
44+
{
45+
name: "prefix parsing",
46+
flag: "prefix",
47+
want: "/Program Files/Nginx",
48+
},
49+
}
50+
51+
for _, tt := range tests {
52+
tt := tt
53+
t.Run(tt.name, func(t *testing.T) {
54+
if got := extractConfigureArg(output, tt.flag); got != tt.want {
55+
t.Fatalf("extractConfigureArg(%q) = %q, want %q", tt.flag, got, tt.want)
56+
}
57+
})
58+
}
59+
}
60+
61+
func TestGetConfAndPidPathsHandleSpaces(t *testing.T) {
62+
originalConfigDir := settings.NginxSettings.ConfigDir
63+
originalConfigPath := settings.NginxSettings.ConfigPath
64+
originalPIDPath := settings.NginxSettings.PIDPath
65+
originalNginxVOutput := nginxVOutput
66+
67+
t.Cleanup(func() {
68+
settings.NginxSettings.ConfigDir = originalConfigDir
69+
settings.NginxSettings.ConfigPath = originalConfigPath
70+
settings.NginxSettings.PIDPath = originalPIDPath
71+
nginxVOutput = originalNginxVOutput
72+
})
73+
74+
settings.NginxSettings.ConfigDir = ""
75+
settings.NginxSettings.ConfigPath = ""
76+
settings.NginxSettings.PIDPath = ""
77+
78+
sampleConf := "/Program Files/nginx/conf/nginx.conf"
79+
samplePID := "/Program Files/nginx/logs/nginx.pid"
80+
81+
nginxVOutput = fmt.Sprintf(`
82+
nginx version: nginx/1.25.2
83+
configure arguments: --conf-path="%s" --pid-path="%s"
84+
`, sampleConf, samplePID)
85+
86+
confDir := GetConfPath()
87+
expectedConfDir := filepath.Dir(sampleConf)
88+
if confDir != expectedConfDir {
89+
t.Fatalf("GetConfPath() = %q, want %q", confDir, expectedConfDir)
90+
}
91+
92+
confEntry := GetConfEntryPath()
93+
if confEntry != sampleConf {
94+
t.Fatalf("GetConfEntryPath() = %q, want %q", confEntry, sampleConf)
95+
}
96+
97+
pidPath := GetPIDPath()
98+
if pidPath != samplePID {
99+
t.Fatalf("GetPIDPath() = %q, want %q", pidPath, samplePID)
100+
}
101+
}

0 commit comments

Comments
 (0)