Skip to content

Commit b224485

Browse files
committed
feat(crypto): Allow to trim input lines when writing messages
1 parent 3484816 commit b224485

File tree

8 files changed

+215
-15
lines changed

8 files changed

+215
-15
lines changed

crypto/encrypt_decrypt_test.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -924,6 +924,32 @@ func TestEncryptDecryptKey(t *testing.T) {
924924
}
925925
}
926926

927+
func TestEncryptDecryptStreamTrimmedLines(t *testing.T) {
928+
for _, material := range testMaterialForProfiles {
929+
t.Run(material.profileName, func(t *testing.T) {
930+
encHandle, _ := material.pgp.Encryption().
931+
Recipients(material.keyRingTestPublic).
932+
SigningKeys(material.keyRingTestPrivate).
933+
TrimLines().
934+
New()
935+
decHandle, _ := material.pgp.Decryption().
936+
DecryptionKeys(material.keyRingTestPrivate).
937+
VerificationKeys(material.keyRingTestPublic).
938+
New()
939+
testEncryptDecryptStreamWithExpected(
940+
t,
941+
[]byte("text with \r \t \n trimmed\n \t"),
942+
[]byte("text with\n trimmed\n"),
943+
nil,
944+
encHandle,
945+
decHandle,
946+
len(material.keyRingTestPrivate.entities),
947+
Bytes,
948+
)
949+
})
950+
}
951+
}
952+
927953
func TestEncryptCompressionApplied(t *testing.T) {
928954
const numReplicas = 10
929955
builder := strings.Builder{}
@@ -1169,6 +1195,28 @@ func testEncryptDecryptStream(
11691195
decHandle PGPDecryption,
11701196
numberOfSigsToVerify int,
11711197
encoding int8,
1198+
) {
1199+
testEncryptDecryptStreamWithExpected(
1200+
t,
1201+
messageBytes,
1202+
messageBytes,
1203+
metadata,
1204+
encHandle,
1205+
decHandle,
1206+
numberOfSigsToVerify,
1207+
encoding,
1208+
)
1209+
}
1210+
1211+
func testEncryptDecryptStreamWithExpected(
1212+
t *testing.T,
1213+
messageBytes []byte,
1214+
expected []byte,
1215+
metadata *LiteralMetadata,
1216+
encHandle PGPEncryption,
1217+
decHandle PGPDecryption,
1218+
numberOfSigsToVerify int,
1219+
encoding int8,
11721220
) {
11731221
messageReader := bytes.NewReader(messageBytes)
11741222
var ciphertextBuf bytes.Buffer
@@ -1200,7 +1248,7 @@ func testEncryptDecryptStream(
12001248
if err != nil {
12011249
t.Fatal("Expected no error while reading the decrypted data, got:", err)
12021250
}
1203-
if !bytes.Equal(decryptedBytes, messageBytes) {
1251+
if !bytes.Equal(decryptedBytes, expected) {
12041252
t.Fatalf("Expected the decrypted data to be %s got %s", string(decryptedBytes), string(messageBytes))
12051253
}
12061254
if numberOfSigsToVerify > 0 {

crypto/encryption_handle.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ type encryptionHandle struct {
5555
// Is only considered if DetachedSignature is not set.
5656
PlainDetachedSignature bool
5757
IsUTF8 bool
58+
// TrimLines trims each end of the line in the input message before encryption.
59+
// Remove trailing spaces, carriage returns and tabs from each line (separated by \n characters).
60+
TrimLines bool
5861
// ExternalSignature allows to include an external signature into
5962
// the encrypted message.
6063
ExternalSignature []byte
@@ -332,6 +335,9 @@ func (eh *encryptionHandle) encryptingWriters(keys, data, detachedSignature Writ
332335
openpgp.NewCanonicalTextWriteCloser(messageWriter),
333336
)
334337
}
338+
if eh.TrimLines {
339+
messageWriter = internal.NewTrimWriteCloser(messageWriter)
340+
}
335341
return messageWriter, nil
336342
}
337343

crypto/encryption_handle_builder.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,13 @@ func (ehb *EncryptionHandleBuilder) Utf8() *EncryptionHandleBuilder {
148148
return ehb
149149
}
150150

151+
// TrimLines enables that each line in the input message is trimmed before encryption.
152+
// Trim removes trailing spaces, carriage returns and tabs from each line (separated by \n characters).
153+
func (ehb *EncryptionHandleBuilder) TrimLines() *EncryptionHandleBuilder {
154+
ehb.handle.TrimLines = true
155+
return ehb
156+
}
157+
151158
// DetachedSignature indicates that the message should be signed,
152159
// but the signature should not be included in the same pgp message as the input data.
153160
// Instead the detached signature is encrypted in a separate pgp message.

crypto/sign_handle.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@ import (
1616
)
1717

1818
type signatureHandle struct {
19-
SignKeyRing *KeyRing
20-
SignContext *SigningContext
21-
IsUTF8 bool
22-
Detached bool
19+
SignKeyRing *KeyRing
20+
SignContext *SigningContext
21+
IsUTF8 bool
22+
Detached bool
23+
// TrimLines trims each end of the line in the input message before encryption.
24+
// Remove trailing spaces, carriage returns and tabs from each line (separated by \n characters).
25+
TrimLines bool
2326
ArmorHeaders map[string]string
2427
profile SignProfile
2528
clock Clock
@@ -87,6 +90,9 @@ func (sh *signatureHandle) SigningWriter(outputWriter Writer, encoding int8) (me
8790
openpgp.NewCanonicalTextWriteCloser(messageWriter),
8891
)
8992
}
93+
if sh.TrimLines {
94+
messageWriter = internal.NewTrimWriteCloser(messageWriter)
95+
}
9096
return messageWriter, nil
9197
}
9298

crypto/sign_handle_builder.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ func (shb *SignHandleBuilder) Utf8() *SignHandleBuilder {
6868
return shb
6969
}
7070

71+
// TrimLines enables that each line in the input message is trimmed before encryption.
72+
// Trim removes trailing spaces, carriage returns and tabs from each line (separated by \n characters).
73+
func (shb *SignHandleBuilder) TrimLines() *SignHandleBuilder {
74+
shb.handle.TrimLines = true
75+
return shb
76+
}
77+
7178
// SignTime sets the internal clock to always return
7279
// the supplied unix time for signing instead of the device time.
7380
func (shb *SignHandleBuilder) SignTime(unixTime int64) *SignHandleBuilder {

internal/trim_lines_writer.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package internal
2+
3+
import (
4+
"bytes"
5+
"io"
6+
)
7+
8+
func trim(p []byte) []byte {
9+
return bytes.TrimRight(p, " \t\r")
10+
}
11+
12+
func NewTrimWriteCloser(internal io.WriteCloser) *TrimWriteCloser {
13+
return NewTrimWriteCloserWithBufferSize(internal, 256)
14+
}
15+
16+
func NewTrimWriteCloserWithBufferSize(internal io.WriteCloser, size int) *TrimWriteCloser {
17+
return &TrimWriteCloser{
18+
internal: internal,
19+
whitespace: bytes.NewBuffer(make([]byte, 0, size)),
20+
err: nil,
21+
}
22+
}
23+
24+
type TrimWriteCloser struct {
25+
internal io.WriteCloser
26+
whitespace *bytes.Buffer
27+
err error
28+
}
29+
30+
func (w *TrimWriteCloser) Write(p []byte) (n int, err error) {
31+
n = len(p)
32+
if w.err != nil {
33+
return 0, err
34+
}
35+
for index := bytes.IndexByte(p, '\n'); index != -1; index = bytes.IndexByte(p, '\n') {
36+
trimmedSuffixLine := trim(p[:index])
37+
bufferWhitespace := w.whitespace.Bytes()
38+
if len(bufferWhitespace) > 0 {
39+
if len(trimmedSuffixLine) != 0 {
40+
if _, err = w.internal.Write(bufferWhitespace); err != nil {
41+
w.err = err
42+
return 0, err
43+
}
44+
}
45+
w.whitespace.Reset()
46+
}
47+
if len(trimmedSuffixLine) < len(p[:index]) {
48+
if _, err = w.internal.Write(trimmedSuffixLine); err != nil {
49+
w.err = err
50+
return index, err
51+
}
52+
if _, err = w.internal.Write([]byte("\n")); err != nil {
53+
w.err = err
54+
return index + 1, err
55+
}
56+
} else {
57+
if _, err = w.internal.Write(p[:index+1]); err != nil {
58+
w.err = err
59+
return index + 1, err
60+
}
61+
}
62+
p = p[index+1:]
63+
}
64+
65+
if len(p) > 0 {
66+
nonWhitespace := trim(p)
67+
if len(nonWhitespace) > 0 && w.whitespace.Len() > 0 {
68+
if _, err = w.internal.Write(w.whitespace.Bytes()); err != nil {
69+
w.err = err
70+
return n - len(p), err
71+
}
72+
w.whitespace.Reset()
73+
}
74+
if _, err = w.internal.Write(nonWhitespace); err != nil {
75+
w.err = err
76+
return n - len(p), err
77+
}
78+
79+
if _, err = w.whitespace.Write(p[len(nonWhitespace):]); err != nil {
80+
w.err = err
81+
return n - len(p), err
82+
}
83+
}
84+
return n, nil
85+
}
86+
87+
func (w *TrimWriteCloser) Close() error {
88+
return w.internal.Close()
89+
}

internal/trim_lines_writer_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package internal
2+
3+
import (
4+
"bytes"
5+
"strings"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func testTrimWriteCloser(t *testing.T, test string) {
12+
testBytes := []byte(test)
13+
for _, batchSize := range []int{1, 2, 3, 7, 11} {
14+
var buff bytes.Buffer
15+
w := &noOpWriteCloser{
16+
writer: &buff,
17+
}
18+
trimWriter := NewTrimWriteCloser(w)
19+
for ind := 0; ind < len(testBytes); ind += batchSize {
20+
end := ind + batchSize
21+
if end > len(testBytes) {
22+
end = len(testBytes)
23+
}
24+
if _, err := trimWriter.Write(testBytes[ind:end]); err != nil {
25+
t.Fatal(err)
26+
}
27+
}
28+
if err := trimWriter.Close(); err != nil {
29+
t.Fatal(err)
30+
}
31+
assert.Equal(t, TrimEachLine(test), buff.String())
32+
}
33+
}
34+
35+
func TestTrimWriteCloser(t *testing.T) {
36+
testTrimWriteCloser(t, "\n \t \r")
37+
testTrimWriteCloser(t, "this is a test \n \t \n\n")
38+
testTrimWriteCloser(t, "saf\n \t sdf\n \r\rsd \t fsdf\n")
39+
testTrimWriteCloser(t, "BEGIN:VCARD\r\nVERSION:4.0\r\nFN;PREF=1: \r\nEND:VCARD")
40+
testTrimWriteCloser(t, strings.Repeat("\r \nthis is a test \n \t \n\n)", 10000))
41+
}

internal/utf8.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -164,19 +164,15 @@ func NewUtf8CheckWriteCloser(wrap io.WriteCloser) *Utf8CheckWriteCloser {
164164
}
165165

166166
func (cw *Utf8CheckWriteCloser) Write(p []byte) (n int, err error) {
167-
err = cw.check(p)
168-
if err != nil {
169-
return
167+
if err = cw.check(p); err != nil {
168+
return 0, err
170169
}
171-
n, err = cw.internal.Write(p)
172-
return
170+
return cw.internal.Write(p)
173171
}
174172

175173
func (cw *Utf8CheckWriteCloser) Close() (err error) {
176-
err = cw.close()
177-
if err != nil {
178-
return
174+
if err = cw.close(); err != nil {
175+
return err
179176
}
180-
err = cw.internal.Close()
181-
return
177+
return cw.internal.Close()
182178
}

0 commit comments

Comments
 (0)