From ec0916c59d0125ac785a50d3787fddbc451599bf Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood <nick@craig-wood.com>
Date: Mon, 12 Dec 2016 15:20:40 +0000
Subject: [PATCH] crypt: return unexpected EOF instead of Failed to
 authenticate decrypted block #873

Streams which truncated early with an EOF message would return a
"Failed to authenticate decrypted block" error.  While technically
correct, this isn't a helpful error message as it masks the underlying
problem.  This changes it to return "unexpected EOF" instead.

The rest of rclone knows it should retry such errors.
---
 crypt/cipher.go      |  5 +++++
 crypt/cipher_test.go | 34 ++++++++++++++++++++++++++++------
 2 files changed, 33 insertions(+), 6 deletions(-)

diff --git a/crypt/cipher.go b/crypt/cipher.go
index 58a81c71a..85ee101f1 100644
--- a/crypt/cipher.go
+++ b/crypt/cipher.go
@@ -579,6 +579,11 @@ func (fh *decrypter) fillBuffer() (err error) {
 	block := fh.buf
 	_, ok := secretbox.Open(block[:0], readBuf[:n], fh.nonce.pointer(), &fh.c.dataKey)
 	if !ok {
+		// if block wouldn't decode and got unexpected EOF
+		// then return that as it is probably a better error
+		if err != nil {
+			return err
+		}
 		return ErrorEncryptedBadBlock
 	}
 	fh.bufIndex = 0
diff --git a/crypt/cipher_test.go b/crypt/cipher_test.go
index 5bacdcdcd..0d4414bdd 100644
--- a/crypt/cipher_test.go
+++ b/crypt/cipher_test.go
@@ -917,14 +917,36 @@ func TestDecrypterRead(t *testing.T) {
 	c, err := newCipher(NameEncryptionStandard, "", "")
 	assert.NoError(t, err)
 
-	// Test truncating the header
-	for i := 1; i < blockHeaderSize; i++ {
-		cd := newCloseDetector(bytes.NewBuffer(file1[:len(file1)-i]))
+	// Test truncating the file at each possible point
+	for i := 0; i < len(file16)-1; i++ {
+		what := fmt.Sprintf("truncating to %d/%d", i, len(file16))
+		cd := newCloseDetector(bytes.NewBuffer(file16[:i]))
 		fh, err := c.newDecrypter(cd)
-		assert.NoError(t, err)
+		if i < fileHeaderSize {
+			assert.EqualError(t, err, ErrorEncryptedFileTooShort.Error(), what)
+			continue
+		}
+		if err != nil {
+			assert.NoError(t, err, what)
+			continue
+		}
 		_, err = ioutil.ReadAll(fh)
-		assert.Error(t, err, ErrorEncryptedFileBadHeader.Error())
-		assert.Equal(t, 0, cd.closed)
+		var expectedErr error
+		switch {
+		case i == fileHeaderSize:
+			// This would normally produce an error *except* on the first block
+			expectedErr = nil
+		case i <= fileHeaderSize+blockHeaderSize:
+			expectedErr = ErrorEncryptedFileBadHeader
+		default:
+			expectedErr = io.ErrUnexpectedEOF
+		}
+		if expectedErr != nil {
+			assert.EqualError(t, err, expectedErr.Error(), what)
+		} else {
+			assert.NoError(t, err, what)
+		}
+		assert.Equal(t, 0, cd.closed, what)
 	}
 
 	// Test producing an error on the file on Read the underlying file