Skip to content

Commit 235c751

Browse files
authored
fix: respect base path in minified UI HTML (#6227)
1 parent 0efc6e9 commit 235c751

3 files changed

Lines changed: 116 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## v1.11.4 [unreleased]
2+
3+
### Bug Fixes
4+
5+
1. [#6227](https://github.com/influxdata/chronograf/pull/6227): Respect base path in minified UI HTML.
6+
17
## v1.11.3 [2026-05-27]
28

39
### Other

server/url_prefixer.go

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -104,47 +104,77 @@ func (up *URLPrefixer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
104104

105105
// setup a buffer which is the max length of our target attrs
106106
b := make([]byte, up.maxlen(up.Attrs...))
107-
io.ReadFull(nextRead, b) // prime the buffer with the start of the input
108-
buf := bytes.NewBuffer(b)
107+
n, _ := io.ReadFull(nextRead, b) // prime the buffer with the start of the input
108+
buf := bytes.NewBuffer(b[:n])
109109

110110
// Read next handler's response byte by byte
111111
src := bufio.NewScanner(nextRead)
112112
src.Split(bufio.ScanBytes)
113113
for {
114114
window := buf.Bytes()
115115

116+
if len(window) == 0 {
117+
flusher.Flush()
118+
break
119+
}
120+
116121
// advance a byte if window is not a src attr
117-
if matchlen, match := up.match(window, up.Attrs...); matchlen == 0 {
122+
matchlen, match := up.match(window, up.Attrs...)
123+
if matchlen == 0 {
124+
rw.Write(buf.Next(1))
125+
writtenCount++
126+
118127
if src.Scan() {
119-
// shift the next byte into buf
120-
rw.Write(buf.Next(1))
121-
writtenCount++
122128
buf.Write(src.Bytes())
129+
} else if err := src.Err(); err != nil {
130+
up.Logger.
131+
WithField("component", "prefixer").
132+
Error("Error encountered while scanning: err:", err)
133+
}
123134

124-
if writtenCount >= ChunkSize {
125-
flusher.Flush()
126-
writtenCount = 0
127-
}
128-
} else {
129-
if err := src.Err(); err != nil {
130-
up.Logger.
131-
WithField("component", "prefixer").
132-
Error("Error encountered while scanning: err:", err)
133-
}
134-
rw.Write(window)
135+
if writtenCount >= ChunkSize {
135136
flusher.Flush()
136-
break
137+
writtenCount = 0
137138
}
139+
138140
continue
139-
} else {
140-
buf.Next(matchlen) // advance to the relative URL
141-
for i := 0; i < matchlen; i++ {
142-
src.Scan()
143-
buf.Write(src.Bytes())
141+
}
142+
143+
buf.Next(matchlen) // advance to the relative URL
144+
for i := 0; i < matchlen; i++ {
145+
src.Scan()
146+
buf.Write(src.Bytes())
147+
}
148+
if bytes.Equal(match, []byte(`src=/`)) {
149+
if bytes.HasPrefix(buf.Bytes(), []byte(`/`)) {
150+
rw.Write(match)
151+
continue
152+
}
153+
rw.Write([]byte(`src=`))
154+
io.WriteString(rw, up.Prefix)
155+
continue
156+
}
157+
if bytes.Equal(match, []byte(`href=/`)) {
158+
if bytes.HasPrefix(buf.Bytes(), []byte(`/`)) {
159+
rw.Write(match)
160+
continue
144161
}
145-
rw.Write(match) // add the src attr to the output
146-
io.WriteString(rw, up.Prefix) // write the prefix
162+
rw.Write([]byte(`href=`))
163+
io.WriteString(rw, up.Prefix)
164+
continue
165+
}
166+
if bytes.Equal(match, []byte(`data-basepath`)) {
167+
if len(buf.Bytes()) > 0 && bytes.ContainsAny(buf.Bytes()[:1], "> \t\r\n") {
168+
rw.Write([]byte(`data-basepath="`))
169+
io.WriteString(rw, up.Prefix)
170+
rw.Write([]byte(`"`))
171+
continue
172+
}
173+
rw.Write(match)
174+
continue
147175
}
176+
rw.Write(match) // add the src attr to the output
177+
io.WriteString(rw, up.Prefix) // write the prefix
148178
}
149179
}
150180

@@ -154,7 +184,7 @@ func (up *URLPrefixer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
154184
// targets. The matching []byte is also returned as the second return parameter
155185
func (up *URLPrefixer) match(subject []byte, targets ...[]byte) (int, []byte) {
156186
for _, target := range targets {
157-
if bytes.Equal(subject[:len(target)], target) {
187+
if len(subject) >= len(target) && bytes.Equal(subject[:len(target)], target) {
158188
return len(target), target
159189
}
160190
}
@@ -186,7 +216,10 @@ func NewDefaultURLPrefixer(prefix string, next http.Handler, lg chronograf.Logge
186216
[]byte(`src="`),
187217
[]byte(`href="`),
188218
[]byte(`url(`),
219+
[]byte(`src=/`),
220+
[]byte(`href=/`),
189221
[]byte(`data-basepath="`), // for forwarding basepath to frontend
222+
[]byte(`data-basepath`),
190223
},
191224
}
192225
}

server/url_prefixer_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,54 @@ func Test_Server_Prefixer_IgnoreJsAndSvg(t *testing.T) {
210210
}
211211
}
212212
}
213+
214+
func Test_Server_Prefixer_RewritesMinifiedHTML(t *testing.T) {
215+
t.Parallel()
216+
217+
tests := []struct {
218+
name string
219+
subject string
220+
expected string
221+
}{
222+
{"Unquoted href", `<link rel=stylesheet href=/ui.css>`, `<link rel=stylesheet href=/chronograf/ui.css>`},
223+
{"Unquoted src", `<script src=/ui.js></script>`, `<script src=/chronograf/ui.js></script>`},
224+
{"Empty data-basepath", `<div id=react-root data-basepath></div>`, `<div id=react-root data-basepath="/chronograf/"></div>`},
225+
{"Quoted data-basepath", `<div id=react-root data-basepath=""></div>`, `<div id=react-root data-basepath="/chronograf/"></div>`},
226+
{
227+
"Minified index HTML",
228+
`<link rel=stylesheet href=/ui.f8bbc9c4.css><link rel="icon shortcut" href=/favicon.70d63073.ico><div id=react-root data-basepath></div><script type=module src=/ui.eb3e79e6.js></script>`,
229+
`<link rel=stylesheet href=/chronograf/ui.f8bbc9c4.css><link rel="icon shortcut" href=/chronograf/favicon.70d63073.ico><div id=react-root data-basepath="/chronograf/"></div><script type=module src=/chronograf/ui.eb3e79e6.js></script>`,
230+
},
231+
}
232+
233+
for _, test := range tests {
234+
backend := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
235+
fmt.Fprint(w, test.subject)
236+
})
237+
238+
pfx := server.NewDefaultURLPrefixer("/chronograf/", backend, nil)
239+
240+
ts := httptest.NewServer(pfx)
241+
defer ts.Close()
242+
243+
res, err := http.Get(ts.URL)
244+
if err != nil {
245+
t.Fatal("Unexpected error fetching from prefixer: err:", err)
246+
}
247+
248+
actual, err := ioutil.ReadAll(res.Body)
249+
if err != nil {
250+
t.Fatal("Unable to read prefixed body: err:", err)
251+
}
252+
253+
if string(actual) != test.expected {
254+
t.Error(
255+
test.name,
256+
":\n Unsuccessful prefixing.\n\tWant:",
257+
fmt.Sprintf("%+q", test.expected),
258+
"\n\tGot: ",
259+
fmt.Sprintf("%+q", string(actual)),
260+
)
261+
}
262+
}
263+
}

0 commit comments

Comments
 (0)