Skip to content

Commit 053373a

Browse files
augustoromanmholt
authored andcommitted
caddyfile: Fix multi-file snippets and import literals. (caddyserver#2205)
* Fix a few import problems: snippets and import literals. Two problems are fixed by this code simplification: 1. Snippets defined in one import file are strangely not available in another. 2. If an imported file had a directive with an argument "import", then the rest of the tokens on the line would be converted to absolute filepaths. An example of caddyserver#2 would be the following directive in an imported file: basicauth / import secret In this case, the password would actually be an absolute path to the file 'secret' (whether or not it exists) in the directory of the imported Caddyfile. The problem was the blind token processing to fix import paths in the imported tokens without considering the context of the 'import' token. My first inclination was to just add more context (detect 'import' tokens at the beginning of lines and check the value tokens against defined snippets), however I eventually realized that we already do all of this in the parser, so the code was redundant. Instead we just use the current token's File property when importing. This works fine with imported tokens since they already have the absolute path to the imported file! Fixes caddyserver#2204 * renamed file2 -> fileName * Fix copy/pasted comment in test. * Change gzip example to basicauth example. This makes it more clear how the import side effect is detrimental.
1 parent e263566 commit 053373a

File tree

2 files changed

+93
-36
lines changed

2 files changed

+93
-36
lines changed

caddyfile/parse.go

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,10 @@ func (p *parser) doImport() error {
251251
if p.definedSnippets != nil && p.definedSnippets[importPattern] != nil {
252252
importedTokens = p.definedSnippets[importPattern]
253253
} else {
254-
// make path relative to Caddyfile rather than current working directory (issue #867)
255-
// and then use glob to get list of matching filenames
256-
absFile, err := filepath.Abs(p.Dispenser.filename)
254+
// make path relative to the file of the _token_ being processed rather
255+
// than current working directory (issue #867) and then use glob to get
256+
// list of matching filenames
257+
absFile, err := filepath.Abs(p.Dispenser.File())
257258
if err != nil {
258259
return p.Errf("Failed to get absolute path of file: %s: %v", p.Dispenser.filename, err)
259260
}
@@ -290,30 +291,6 @@ func (p *parser) doImport() error {
290291
if err != nil {
291292
return err
292293
}
293-
294-
var importLine int
295-
for i, token := range newTokens {
296-
if token.Text == "import" {
297-
importLine = token.Line
298-
continue
299-
}
300-
if token.Line == importLine {
301-
var abs string
302-
if filepath.IsAbs(token.Text) {
303-
abs = token.Text
304-
} else if !filepath.IsAbs(importFile) {
305-
abs = filepath.Join(filepath.Dir(absFile), token.Text)
306-
} else {
307-
abs = filepath.Join(filepath.Dir(importFile), token.Text)
308-
}
309-
newTokens[i] = Token{
310-
Text: abs,
311-
Line: token.Line,
312-
File: token.File,
313-
}
314-
}
315-
}
316-
317294
importedTokens = append(importedTokens, newTokens...)
318295
}
319296
}

caddyfile/parse_test.go

Lines changed: 89 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -527,15 +527,15 @@ func testParser(input string) parser {
527527
}
528528

529529
func TestSnippets(t *testing.T) {
530-
p := testParser(`(common) {
531-
gzip foo
532-
errors stderr
533-
534-
}
535-
http://example.com {
536-
import common
537-
}
538-
`)
530+
p := testParser(`
531+
(common) {
532+
gzip foo
533+
errors stderr
534+
}
535+
http://example.com {
536+
import common
537+
}
538+
`)
539539
blocks, err := p.parseAll()
540540
if err != nil {
541541
t.Fatal(err)
@@ -561,3 +561,83 @@ func TestSnippets(t *testing.T) {
561561
}
562562

563563
}
564+
565+
func writeStringToTempFileOrDie(t *testing.T, str string) (pathToFile string) {
566+
file, err := ioutil.TempFile("", t.Name())
567+
if err != nil {
568+
panic(err) // get a stack trace so we know where this was called from.
569+
}
570+
if _, err := file.WriteString(str); err != nil {
571+
panic(err)
572+
}
573+
if err := file.Close(); err != nil {
574+
panic(err)
575+
}
576+
return file.Name()
577+
}
578+
579+
func TestImportedFilesIgnoreNonDirectiveImportTokens(t *testing.T) {
580+
fileName := writeStringToTempFileOrDie(t, `
581+
http://example.com {
582+
# This isn't an import directive, it's just an arg with value 'import'
583+
basicauth / import password
584+
}
585+
`)
586+
// Parse the root file that imports the other one.
587+
p := testParser(`import ` + fileName)
588+
blocks, err := p.parseAll()
589+
if err != nil {
590+
t.Fatal(err)
591+
}
592+
for _, b := range blocks {
593+
t.Log(b.Keys)
594+
t.Log(b.Tokens)
595+
}
596+
auth := blocks[0].Tokens["basicauth"]
597+
line := auth[0].Text + " " + auth[1].Text + " " + auth[2].Text + " " + auth[3].Text
598+
if line != "basicauth / import password" {
599+
// Previously, it would be changed to:
600+
// basicauth / import /path/to/test/dir/password
601+
// referencing a file that (probably) doesn't exist and changing the
602+
// password!
603+
t.Errorf("Expected basicauth tokens to be 'basicauth / import password' but got %#q", line)
604+
}
605+
}
606+
607+
func TestSnippetAcrossMultipleFiles(t *testing.T) {
608+
// Make the derived Caddyfile that expects (common) to be defined.
609+
fileName := writeStringToTempFileOrDie(t, `
610+
http://example.com {
611+
import common
612+
}
613+
`)
614+
615+
// Parse the root file that defines (common) and then imports the other one.
616+
p := testParser(`
617+
(common) {
618+
gzip foo
619+
}
620+
import ` + fileName + `
621+
`)
622+
623+
blocks, err := p.parseAll()
624+
if err != nil {
625+
t.Fatal(err)
626+
}
627+
for _, b := range blocks {
628+
t.Log(b.Keys)
629+
t.Log(b.Tokens)
630+
}
631+
if len(blocks) != 1 {
632+
t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
633+
}
634+
if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual {
635+
t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
636+
}
637+
if len(blocks[0].Tokens) != 1 {
638+
t.Fatalf("Server block should have tokens from import")
639+
}
640+
if actual, expected := blocks[0].Tokens["gzip"][0].Text, "gzip"; expected != actual {
641+
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
642+
}
643+
}

0 commit comments

Comments
 (0)