Skip to content

Commit 67e08db

Browse files
author
Paul Fawkesley
committed
factor out, test & fix line folding from serialize
1 parent 9cfe476 commit 67e08db

File tree

2 files changed

+99
-12
lines changed

2 files changed

+99
-12
lines changed

property.go

+28-11
Original file line numberDiff line numberDiff line change
@@ -63,18 +63,35 @@ func (property *BaseProperty) serialize(w io.Writer) {
6363
}
6464
fmt.Fprint(b, ":")
6565
fmt.Fprint(b, property.Value)
66-
r := b.String()
67-
if len(r) > 75 {
68-
fmt.Fprint(w, r[:75], "\r\n")
69-
r = r[75:]
70-
fmt.Fprint(w, " ")
71-
}
72-
for len(r) > 74 {
73-
fmt.Fprint(w, r[:74], "\r\n")
74-
r = r[74:]
75-
fmt.Fprint(w, " ")
66+
foldLine(b.String(), w)
67+
}
68+
69+
// foldLine converts a line into multiple lines, where no line is longer than 75 octets (bytes)
70+
// as described in https://tools.ietf.org/html/rfc5545#section-3.1
71+
// Many runes are encoded with multiple bytes. foldLine ensures lines are not split mid-rune.
72+
func foldLine(longLine string, w io.Writer) {
73+
octetsThisLine := 0
74+
75+
for _, thisRune := range []rune(longLine) {
76+
octetsThisRune := len(string(thisRune))
77+
78+
if octetsThisLine+octetsThisRune > 75 {
79+
_, err := fmt.Fprintf(w, "\r\n ")
80+
if err != nil {
81+
panic(err)
82+
}
83+
84+
octetsThisLine = 1 // initial whitespace character after \r\n
85+
}
86+
87+
octetsThisLine += octetsThisRune
88+
89+
_, err := fmt.Fprintf(w, string(thisRune))
90+
if err != nil {
91+
panic(err)
92+
}
7693
}
77-
fmt.Fprint(w, r, "\r\n")
94+
fmt.Fprint(w, "\r\n")
7895
}
7996

8097
type IANAProperty struct {

property_test.go

+71-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package ics
22

3-
import "testing"
3+
import (
4+
"bytes"
5+
"testing"
6+
)
47

58
func TestPropertyParse(t *testing.T) {
69
tests := []struct {
@@ -24,3 +27,70 @@ func TestPropertyParse(t *testing.T) {
2427
}
2528
}
2629
}
30+
31+
func TestFoldLine(t *testing.T) {
32+
seventyFive := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
33+
threeByte := `⌘`
34+
35+
tests := []struct {
36+
name string
37+
line string
38+
expected string
39+
}{
40+
{
41+
name: "74 octet line",
42+
line: seventyFive[0:74],
43+
expected: seventyFive[0:74] + "\r\n",
44+
},
45+
{
46+
name: "75 octet line",
47+
line: seventyFive,
48+
expected: seventyFive + "\r\n",
49+
},
50+
{
51+
name: "76 octet line",
52+
line: seventyFive + "b",
53+
expected: seventyFive + "\r\n b" + "\r\n",
54+
},
55+
{
56+
name: "77 octet line",
57+
line: seventyFive + "bb",
58+
expected: seventyFive + "\r\n bb" + "\r\n",
59+
},
60+
{
61+
name: "75x2 octet line",
62+
line: seventyFive + seventyFive,
63+
expected: seventyFive + "\r\n " + seventyFive[0:74] + "\r\n " + "a" + "\r\n",
64+
},
65+
{
66+
name: "72 bytes followed by 3-byte character",
67+
line: seventyFive[0:72] + threeByte,
68+
expected: seventyFive[0:72] + threeByte + "\r\n", // 72+3 is ok, no need to fold
69+
},
70+
{
71+
name: "73 bytes followed by 3-byte character",
72+
line: seventyFive[0:73] + threeByte,
73+
expected: seventyFive[0:73] + "\r\n " + threeByte + "\r\n",
74+
},
75+
{
76+
name: "74 bytes followed by 3-byte character",
77+
line: seventyFive[0:74] + threeByte,
78+
expected: seventyFive[0:74] + "\r\n " + threeByte + "\r\n",
79+
},
80+
}
81+
82+
for _, test := range tests {
83+
t.Run(test.name, func(t *testing.T) {
84+
buf := bytes.NewBuffer(nil)
85+
foldLine(test.line, buf)
86+
got := buf.String()
87+
assertEqual(t, test.expected, got)
88+
})
89+
}
90+
}
91+
92+
func assertEqual(t *testing.T, expected string, got string) {
93+
if got != expected {
94+
t.Errorf("\n--- expected ---\n%s\n--- got ---\n%s\n---\n", expected, got)
95+
}
96+
}

0 commit comments

Comments
 (0)