Skip to content

Commit 35d0138

Browse files
committed
fix(tomcat): XML-escape WAR filename in context descriptor
WAR filenames containing XML-sensitive characters (&, <, >) produced an invalid Tomcat context descriptor via unescaped docBase. Escape with xml.EscapeText before interpolation.
1 parent 09f1846 commit 35d0138

2 files changed

Lines changed: 42 additions & 1 deletion

File tree

src/java/containers/tomcat.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package containers
22

33
import (
4+
"bytes"
5+
"encoding/xml"
46
"fmt"
57
"io"
68
"net/http"
@@ -640,7 +642,13 @@ func (t *TomcatContainer) Finalize() error {
640642
}
641643
if len(warMatches) == 1 {
642644
warFilename := filepath.Base(warMatches[0])
643-
contextContent := fmt.Sprintf("<Context docBase=\"${user.home}/app/%s\" reloadable=\"false\">\n</Context>\n", warFilename)
645+
// Escape XML-sensitive characters (&, <, >, ", ') so a WAR filename
646+
// containing them cannot produce an invalid Tomcat context descriptor.
647+
var escapedWarFilename bytes.Buffer
648+
if err := xml.EscapeText(&escapedWarFilename, []byte(warFilename)); err != nil {
649+
return fmt.Errorf("failed to XML-escape WAR filename %q: %w", warFilename, err)
650+
}
651+
contextContent := fmt.Sprintf("<Context docBase=\"${user.home}/app/%s\" reloadable=\"false\">\n</Context>\n", escapedWarFilename.String())
644652

645653
contextXMLDir := filepath.Dir(contextXMLPath)
646654
if err := os.MkdirAll(contextXMLDir, 0755); err != nil {

src/java/containers/tomcat_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package containers_test
22

33
import (
4+
"encoding/xml"
45
"os"
56
"path/filepath"
67

@@ -334,6 +335,38 @@ var _ = Describe("Tomcat Container", func() {
334335
Expect(string(content)).To(ContainSubstring(`docBase="${user.home}/app/myapp.war"`))
335336
})
336337

338+
It("XML-escapes WAR filenames containing XML-sensitive characters", func() {
339+
// A WAR filename may legally contain &, <, > which are XML-sensitive.
340+
// Interpolated raw into the docBase attribute they produce an invalid
341+
// descriptor that Tomcat fails to parse. The filename must be escaped.
342+
warName := "my&a<b>c.war"
343+
Expect(os.WriteFile(filepath.Join(buildDir, warName), []byte("fakewar"), 0644)).To(Succeed())
344+
345+
err := container.Finalize()
346+
Expect(err).NotTo(HaveOccurred())
347+
348+
tomcatDir := filepath.Join(depsDir, "0", "tomcat")
349+
contextFile := filepath.Join(tomcatDir, "conf", "Catalina", "localhost", "ROOT.xml")
350+
Expect(contextFile).To(BeAnExistingFile())
351+
content, _ := os.ReadFile(contextFile)
352+
contentStr := string(content)
353+
354+
// Raw sensitive characters must not leak into the descriptor.
355+
Expect(contentStr).NotTo(ContainSubstring("my&a<b>c.war"),
356+
"raw unescaped WAR filename produced invalid XML:\n%s", contentStr)
357+
// Escaped form present.
358+
Expect(contentStr).To(ContainSubstring("my&amp;a&lt;b&gt;c.war"),
359+
"expected XML-escaped WAR filename in docBase:\n%s", contentStr)
360+
361+
// The descriptor must be well-formed XML.
362+
var parsed struct {
363+
DocBase string `xml:"docBase,attr"`
364+
}
365+
Expect(xml.Unmarshal(content, &parsed)).To(Succeed(),
366+
"generated descriptor is not well-formed XML:\n%s", contentStr)
367+
Expect(parsed.DocBase).To(Equal("${user.home}/app/my&a<b>c.war"))
368+
})
369+
337370
It("removes ROOT.xml when packaged WAR uses non-root context_path", func() {
338371
Expect(os.WriteFile(filepath.Join(buildDir, "myapp.war"), []byte("fakewar"), 0644)).To(Succeed())
339372
os.Setenv("JBP_CONFIG_TOMCAT", `{tomcat: {context_path: /my/path}}`)

0 commit comments

Comments
 (0)