Skip to content

Commit 12f04ef

Browse files
authored
Merge pull request moby#5052 from tonistiigi/shell-empty-string
shell: handle empty string for var replacements
2 parents 5deda4a + de3e9c4 commit 12f04ef

File tree

2 files changed

+107
-3
lines changed

2 files changed

+107
-3
lines changed

frontend/dockerfile/shell/lex.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,8 @@ func (sw *shellWord) processDollar() (string, error) {
430430
case '%', '#':
431431
// %/# matches the shortest pattern expansion, %%/## the longest
432432
greedy := false
433-
if word[0] == byte(ch) {
433+
434+
if len(word) > 0 && word[0] == byte(ch) {
434435
greedy = true
435436
word = word[1:]
436437
}

frontend/dockerfile/shell/lex_test.go

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,21 @@ func TestProcessWithMatches(t *testing.T) {
313313
matches: map[string]struct{}{"FOO": {}, "BAR": {}},
314314
unmatched: map[string]struct{}{"BAZ": {}},
315315
},
316+
{
317+
input: "${FOO:-}",
318+
envs: map[string]string{
319+
"FOO": "xxx",
320+
"BAR": "",
321+
},
322+
expected: "xxx",
323+
matches: map[string]struct{}{"FOO": {}},
324+
},
325+
{
326+
input: "${FOO:-}",
327+
envs: map[string]string{},
328+
expected: "",
329+
unmatched: map[string]struct{}{"FOO": {}},
330+
},
316331

317332
{
318333
input: "${FOO+aaa} ${BAR+bbb} ${BAZ+ccc}",
@@ -388,6 +403,15 @@ func TestProcessWithMatches(t *testing.T) {
388403
expectedErr: true,
389404
unmatched: map[string]struct{}{"BAZ": {}},
390405
},
406+
{
407+
input: "${BAZ:?}",
408+
envs: map[string]string{
409+
"FOO": "xxx",
410+
"BAR": "",
411+
},
412+
expectedErr: true,
413+
unmatched: map[string]struct{}{"BAZ": {}},
414+
},
391415

392416
{
393417
input: "${FOO=aaa}",
@@ -405,6 +429,11 @@ func TestProcessWithMatches(t *testing.T) {
405429
},
406430
expectedErr: true,
407431
},
432+
{
433+
input: "${FOO=}",
434+
envs: map[string]string{},
435+
expectedErr: true,
436+
},
408437
{
409438
// special characters in regular expressions
410439
// } needs to be escaped so it doesn't match the
@@ -426,12 +455,42 @@ func TestProcessWithMatches(t *testing.T) {
426455
expected: "y",
427456
matches: map[string]struct{}{"FOO": {}},
428457
},
458+
{
459+
input: "${FOO#*}",
460+
envs: map[string]string{"FOO": "xxyy"},
461+
expected: "xxyy",
462+
matches: map[string]struct{}{"FOO": {}},
463+
},
464+
{
465+
input: "${FOO#$BAR}",
466+
envs: map[string]string{"FOO": "xxyy", "BAR": "x"},
467+
expected: "xyy",
468+
matches: map[string]struct{}{"FOO": {}, "BAR": {}},
469+
},
470+
{
471+
input: "${FOO#$BAR}",
472+
envs: map[string]string{"FOO": "xxyy", "BAR": ""},
473+
expected: "xxyy",
474+
matches: map[string]struct{}{"FOO": {}, "BAR": {}},
475+
},
476+
{
477+
input: "${FOO#}",
478+
envs: map[string]string{"FOO": "xxyy"},
479+
expected: "xxyy",
480+
matches: map[string]struct{}{"FOO": {}},
481+
},
429482
{
430483
input: "${FOO##*x}",
431484
envs: map[string]string{"FOO": "xxyy"},
432485
expected: "yy",
433486
matches: map[string]struct{}{"FOO": {}},
434487
},
488+
{
489+
input: "${FOO##}",
490+
envs: map[string]string{"FOO": "xxyy"},
491+
expected: "xxyy",
492+
matches: map[string]struct{}{"FOO": {}},
493+
},
435494
{
436495
input: "${FOO#?\\?}",
437496
envs: map[string]string{"FOO": "???y"},
@@ -451,6 +510,18 @@ func TestProcessWithMatches(t *testing.T) {
451510
expected: "a",
452511
matches: map[string]struct{}{"FOO": {}},
453512
},
513+
{
514+
input: "${FOO%}",
515+
envs: map[string]string{"FOO": "xxyy"},
516+
expected: "xxyy",
517+
matches: map[string]struct{}{"FOO": {}},
518+
},
519+
{
520+
input: "${FOO%%$BAR}",
521+
envs: map[string]string{"FOO": "xxyy", "BAR": ""},
522+
expected: "xxyy",
523+
matches: map[string]struct{}{"FOO": {}, "BAR": {}},
524+
},
454525
{
455526
// test: wildcards
456527
input: "${FOO/$NEEDLE/.} - ${FOO//$NEEDLE/.}",
@@ -484,6 +555,38 @@ func TestProcessWithMatches(t *testing.T) {
484555
expected: "\\/tmp\\/foo.txt",
485556
matches: map[string]struct{}{"FOO": {}},
486557
},
558+
559+
// Following cases with empty/partial values are currently not
560+
// guaranteed behavior. Tests are provided to make sure partial
561+
// input does not cause runtime error.
562+
{
563+
input: "${FOO/$BAR/ww}",
564+
envs: map[string]string{"FOO": "xxyy", "BAR": ""},
565+
expected: "wwxxyy",
566+
matches: map[string]struct{}{"FOO": {}, "BAR": {}},
567+
},
568+
{
569+
input: "${FOO//ww}",
570+
envs: map[string]string{"FOO": "xxyy"},
571+
expectedErr: true,
572+
},
573+
{
574+
input: "${FOO//}",
575+
envs: map[string]string{"FOO": "xxyy"},
576+
expectedErr: true,
577+
},
578+
{
579+
input: "${FOO///}",
580+
envs: map[string]string{"FOO": "xxyy"},
581+
expected: "xxyy",
582+
matches: map[string]struct{}{"FOO": {}},
583+
},
584+
{
585+
input: "${FOO///}",
586+
envs: map[string]string{},
587+
expected: "",
588+
unmatched: map[string]struct{}{"FOO": {}},
589+
},
487590
}
488591

489592
for _, c := range tc {
@@ -500,12 +603,12 @@ func TestProcessWithMatches(t *testing.T) {
500603
require.NoError(t, err)
501604
require.Equal(t, c.expected, w)
502605

503-
require.Equal(t, len(c.matches), len(matches))
606+
require.Len(t, matches, len(c.matches), c.matches)
504607
for k := range c.matches {
505608
require.Contains(t, matches, k)
506609
}
507610

508-
require.Equal(t, len(c.unmatched), len(unmatched))
611+
require.Len(t, unmatched, len(c.unmatched), c.unmatched)
509612
for k := range c.unmatched {
510613
require.Contains(t, unmatched, k)
511614
}

0 commit comments

Comments
 (0)