changeset 26310:e5643ff5adc1

lib: base64 - Fix incremental/streaming Base64 encoding with CRLF line endings. Line lengths became inconsistent due to a design error. The encoding itself remained valid. The linefeed is no longer appended to the write buffer when the destination buffer is full. Rather, a flag is set that makes the encoder emit the linefeed immediately next time the encoder is called with more buffer space. Appending it to the write buffer was wrong; it in fact needs to be prepended and in that case a flag is more efficient.
author Stephan Bosch <stephan.bosch@open-xchange.com>
date Wed, 04 Sep 2019 22:44:03 +0200
parents 379a7356c19c
children cef3ed72c74f
files src/lib/base64.c src/lib/base64.h
diffstat 2 files changed, 48 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib/base64.c	Thu Sep 05 01:17:55 2019 +0200
+++ b/src/lib/base64.c	Wed Sep 04 22:44:03 2019 +0200
@@ -131,6 +131,9 @@
 		out_size += lines * (crlf ? 2 : 1);
 	}
 
+	if (enc->pending_lf)
+		out_size++;
+
 	return out_size;
 }
 
@@ -145,6 +148,8 @@
 	unsigned char *start, *ptr, *end;
 	size_t src_pos;
 
+	i_assert(!enc->pending_lf);
+
 	/* determine how much we can write in destination buffer */
 	if (dst_avail == 0) {
 		*src_pos_r = 0;
@@ -295,6 +300,7 @@
 			const void *src, size_t src_size, size_t *src_pos_r,
 			buffer_t *dest)
 {
+	bool crlf = HAS_ALL_BITS(enc->flags, BASE64_ENCODE_FLAG_CRLF);
 	const unsigned char *src_c, *src_p;
 	size_t src_pos;
 
@@ -309,6 +315,16 @@
 		if (dst_avail == 0)
 			break;
 
+		/* Emit pending newline immediately */
+		if (enc->pending_lf) {
+			i_assert(crlf);
+			buffer_append_c(dest, '\n');
+			enc->pending_lf = FALSE;
+			dst_avail--;
+			if (dst_avail == 0)
+				break;
+		}
+
 		i_assert(enc->max_line_len > 0);
 		i_assert(enc->cur_line_len <= enc->max_line_len);
 		line_avail = I_MIN(enc->max_line_len - enc->cur_line_len,
@@ -335,15 +351,16 @@
 		if (dst_avail == 0)
 			break;
 
-		i_assert(enc->w_buf_len < sizeof(enc->w_buf));
 		if (src_size > 0 && enc->cur_line_len == enc->max_line_len) {
-			if (HAS_ALL_BITS(enc->flags, BASE64_ENCODE_FLAG_CRLF)) {
-				if (dst_avail >= 2)
+			if (crlf) {
+				if (dst_avail >= 2) {
+					/* emit the full CRLF sequence */
 					buffer_append(dest, "\r\n", 2);
-				else {
+				} else {
+					/* emit CR */
 					buffer_append_c(dest, '\r');
-					enc->w_buf[enc->w_buf_len] = '\n';
-					enc->w_buf_len++;
+					/* remember the LF */
+					enc->pending_lf = TRUE;
 				}
 			} else {
 				buffer_append_c(dest, '\n');
@@ -366,7 +383,7 @@
 	bool crlf = HAS_ALL_BITS(enc->flags, BASE64_ENCODE_FLAG_CRLF);
 	bool padding = HAS_NO_BITS(enc->flags, BASE64_ENCODE_FLAG_NO_PADDING);
 	unsigned char *ptr, *end;
-	size_t dst_avail, line_avail, write;
+	size_t dst_avail, line_avail, write_full, write;
 	unsigned int w_buf_pos = 0;
 
 	dst_avail = 0;
@@ -375,7 +392,7 @@
 
 	i_assert(!enc->finished);
 
-	if (enc->w_buf_len > 0) {
+	if (enc->w_buf_len > 0 || enc->pending_lf)  {
 		if (dst_avail == 0)
 			return FALSE;
 		i_assert(enc->w_buf_len <= sizeof(enc->w_buf));
@@ -412,39 +429,49 @@
 	}
 	enc->sub_pos = 0;
 
-	write = enc->w_buf_len;
+	write_full = write = enc->w_buf_len;
+	if (enc->pending_lf)
+		write_full++;
 	if (enc->max_line_len < SIZE_MAX && line_avail < write) {
 		unsigned int lines;
 
 		lines = I_MAX((write - line_avail) / enc->max_line_len, 1);
-		write += lines * (crlf ? 2 : 1);
+		write_full += lines * (crlf ? 2 : 1);
 	} else {
 		line_avail = write;
 	}
 
-	if (write == 0) {
+	if (write_full == 0) {
 		enc->finished = TRUE;
 		return TRUE;
 	}
 
 	i_assert(dest != NULL);
-	if (write > dst_avail)
-		write = dst_avail;
+	if (write_full > dst_avail)
+		write_full = dst_avail;
 
-	ptr = buffer_append_space_unsafe(dest, write);
-	end = ptr + write;
+	ptr = buffer_append_space_unsafe(dest, write_full);
+	end = ptr + write_full;
+	if (enc->pending_lf) {
+		ptr[0] = '\n';
+		dst_avail--;
+		ptr++;
+		enc->pending_lf = FALSE;
+	}
+	if (line_avail > dst_avail)
+		line_avail = dst_avail;
 	if (line_avail > 0) {
 		memcpy(ptr, enc->w_buf, line_avail);
 		ptr += line_avail;
 		w_buf_pos += line_avail;
 	}
 	while (ptr < end && w_buf_pos < enc->w_buf_len) {
+		enc->cur_line_len = 0;
 		if (crlf) {
 			ptr[0] = '\r';
 			ptr++;
 			if (ptr == end) {
-				i_assert(enc->w_buf_len < sizeof(enc->w_buf));
-				enc->w_buf[enc->w_buf_len++] = '\n';
+				enc->pending_lf = TRUE;
 				break;
 			}
 		}
@@ -458,6 +485,7 @@
 		memcpy(ptr, &enc->w_buf[w_buf_pos], write);
 		ptr += write;
 		w_buf_pos += write;
+		enc->cur_line_len += write;
 		i_assert(ptr <= end);
 	}
 	i_assert(ptr == end);
@@ -466,6 +494,8 @@
 		memmove(enc->w_buf, enc->w_buf + w_buf_pos, enc->w_buf_len);
 		return FALSE;
 	}
+	if (enc->pending_lf)
+		return FALSE;
 	enc->finished = TRUE;
 	return TRUE;
 }
--- a/src/lib/base64.h	Thu Sep 05 01:17:55 2019 +0200
+++ b/src/lib/base64.h	Wed Sep 04 22:44:03 2019 +0200
@@ -41,6 +41,7 @@
 	unsigned char w_buf[10];
 	unsigned int w_buf_len;
 
+	bool pending_lf:1;
 	bool finished:1;
 };