Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit cfc5966

Browse files
Harden line break detection
Handle multi-line breaks use in the single file app format, as users from different OSes (windows/unix) can use different line breaks inadvertently, and this is still valid YAML Signed-off-by: Silvin Lubecki <[email protected]>
1 parent cadefc2 commit cfc5966

File tree

4 files changed

+65
-10
lines changed

4 files changed

+65
-10
lines changed

loader/loader.go

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,64 @@ import (
77
"os"
88
"path/filepath"
99

10-
"github.com/docker/cli/cli/compose/loader"
11-
12-
"github.com/docker/app/specification"
13-
"github.com/docker/cli/cli/compose/schema"
14-
1510
"github.com/docker/app/internal"
11+
"github.com/docker/app/specification"
1612
"github.com/docker/app/types"
13+
"github.com/docker/cli/cli/compose/loader"
14+
"github.com/docker/cli/cli/compose/schema"
1715
"github.com/docker/docker/pkg/archive"
1816
"github.com/pkg/errors"
1917
)
2018

19+
var (
20+
crlf = []byte{'\r', '\n'}
21+
lf = []byte{'\n'}
22+
delimiters = [][]byte{
23+
[]byte("\r\n---\r\n"),
24+
[]byte("\n---\r\n"),
25+
[]byte("\r\n---\n"),
26+
[]byte("\n---\n"),
27+
}
28+
)
29+
30+
// useCRLF detects which line break should be used
31+
func useCRLF(data []byte) bool {
32+
nbCrlf := bytes.Count(data, crlf)
33+
nbLf := bytes.Count(data, lf)
34+
switch {
35+
case nbCrlf == nbLf:
36+
// document contains only CRLF
37+
return true
38+
case nbCrlf == 0:
39+
// document does not contain any CRLF
40+
return false
41+
default:
42+
// document contains mixed line breaks, so use the OS default
43+
return bytes.Equal(defaultLineBreak, crlf)
44+
}
45+
}
46+
47+
// splitSingleFile split a multidocument using all possible document delimiters
48+
func splitSingleFile(data []byte) [][]byte {
49+
parts := [][]byte{data}
50+
for _, delimiter := range delimiters {
51+
var intermediate [][]byte
52+
for _, part := range parts {
53+
intermediate = append(intermediate, bytes.Split(part, delimiter)...)
54+
}
55+
parts = intermediate
56+
}
57+
return parts
58+
}
59+
2160
// LoadFromSingleFile loads a docker app from a single-file format (as a reader)
2261
func LoadFromSingleFile(path string, r io.Reader, ops ...func(*types.App) error) (*types.App, error) {
2362
data, err := ioutil.ReadAll(r)
2463
if err != nil {
2564
return nil, errors.Wrap(err, "error reading single-file")
2665
}
27-
hasCRLF := bytes.Contains(data, []byte{'\r', '\n'})
2866

29-
parts := bytes.Split(data, types.YamlSingleFileSeparator(hasCRLF))
67+
parts := splitSingleFile(data)
3068
if len(parts) != 3 {
3169
return nil, errors.Errorf("malformed single-file application: expected 3 documents, got %d", len(parts))
3270
}
@@ -51,11 +89,12 @@ func LoadFromSingleFile(path string, r io.Reader, ops ...func(*types.App) error)
5189
return nil, errors.New("malformed single-file application")
5290
}
5391
}
92+
5493
appOps := append([]func(*types.App) error{
5594
types.WithComposes(compose),
5695
types.WithParameters(params),
5796
types.Metadata(metadata),
58-
types.WithCRLF(hasCRLF),
97+
types.WithCRLF(useCRLF(data)),
5998
}, ops...)
6099
return types.NewApp(path, appOps...)
61100
}

loader/loader_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ const (
2020
metadata = `name: my-app
2121
version: 1.0.0
2222
`
23-
compose = `services:
23+
compose = `version: "3.1"
24+
services:
2425
web:
2526
image: nginx
26-
version: "3.1"
2727
`
2828
params = `foo: bar
2929
`
@@ -42,6 +42,10 @@ func TestLoadFromSingleFile(t *testing.T) {
4242
name: "carriage-return-line-feed",
4343
file: fmt.Sprintf("%s\r\n---\r\n%s\r\n---\r\n%s", metadata, compose, params),
4444
},
45+
{
46+
name: "mixed-carriage-return-line-feed",
47+
file: fmt.Sprintf("%s\r\n---\r\n%s\r\n---\n%s", metadata, compose, params),
48+
},
4549
{
4650
name: "unordered-documents",
4751
file: fmt.Sprintf("%s\n---\n%s\n---\n%s", params, metadata, compose),

loader/loader_unix.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// +build !windows
2+
3+
package loader
4+
5+
var (
6+
defaultLineBreak = []byte{'\n'}
7+
)

loader/loader_windows.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package loader
2+
3+
var (
4+
defaultLineBreak = []byte{'\r', '\n'}
5+
)

0 commit comments

Comments
 (0)