Allow parsing base64 files

Redfish outputs base64 strings.  It would be useful if the CLI app could
read those strings in directly.

This commit breaks out a new method "header_valid" to allow tooling to
do an initial reading of a buffer to determine if it appears to be
correct before going further.  This allows the CLI app to attempt to
parse as a buffer, if that fails, attempt to parse as base64.

To support as many inputs as possible, this commit makes padding
optional.  It also allows a trailing \n as is present in many files.

Change-Id: I4fb759ecefc8ce1c757f1a9e7c4a2b2d220105d0
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/base64.c b/base64.c
index 5ed39c6..7ace17a 100644
--- a/base64.c
+++ b/base64.c
@@ -95,59 +95,70 @@
  */
 UINT8 *base64_decode(const CHAR8 *src, INT32 len, INT32 *out_len)
 {
-	UINT8 *out;
-	UINT8 *pos;
+	UINT8 *out = NULL;
+	UINT8 *pos = NULL;
 	UINT8 block[4];
-	UINT8 tmp;
-	INT32 block_index;
-	INT32 src_index;
-	UINT32 pad_count = 0;
+	INT32 block_index = 0;
+	INT32 src_index = 0;
 
 	if (!out_len) {
-		return NULL;
+		goto error;
 	}
 
 	// Malloc might be up to 2 larger dependent on padding
-	*out_len = len / 4 * 3;
+	*out_len = len / 4 * 3 + 2;
 	pos = out = malloc(*out_len);
 	if (out == NULL) {
-		return NULL;
+		goto error;
 	}
 
 	block_index = 0;
 	for (src_index = 0; src_index < len; src_index++) {
-		tmp = decode_table[(UINT8)src[src_index]];
-		if (tmp == 0x80) {
-			free(out);
-			return NULL;
+		char current_char = src[src_index];
+		if (current_char == '=') {
+			break;
+		}
+		// If the final character is a newline, as can occur in many editors
+		// then ignore it.
+		if (src_index + 1 == len && current_char == '\n') {
+			printf("Ignoring trailing newline.\n");
+			break;
 		}
 
-		if (src[src_index] == '=') {
-			pad_count++;
+		block[block_index] = decode_table[(UINT8)current_char];
+		if (block[block_index] == 0x80) {
+			printf("Invalid character \"%c\".\n", current_char);
+			goto error;
 		}
 
-		block[block_index] = tmp;
 		block_index++;
 		if (block_index == 4) {
 			*pos++ = (block[0] << 2) | (block[1] >> 4);
 			*pos++ = (block[1] << 4) | (block[2] >> 2);
 			*pos++ = (block[2] << 6) | block[3];
-			if (pad_count > 0) {
-				if (pad_count == 1) {
-					pos--;
-				} else if (pad_count == 2) {
-					pos -= 2;
-				} else {
-					/* Invalid pad_counting */
-					free(out);
-					return NULL;
-				}
-				break;
-			}
 			block_index = 0;
 		}
 	}
+	if (block_index == 0) {
+		// mod 4 Even number of characters, no padding.
+	} else if (block_index == 1) {
+		printf("Invalid base64 input length.  Last character truncated.\n");
+		goto error;
+	} else if (block_index == 2) {
+		*pos++ = (block[0] << 2) | (block[1] >> 4);
+	} else if (block_index == 3) {
+		*pos++ = (block[0] << 2) | (block[1] >> 4);
+		*pos++ = (block[1] << 4) | (block[2] >> 2);
+	} else {
+		/* Invalid pad_counting */
+		printf("Invalid base64 input length %d.\n", block_index);
+		goto error;
+	}
 
 	*out_len = pos - out;
 	return out;
+
+error:
+	free(out);
+	return NULL;
 }
diff --git a/cli-app/cper-convert.c b/cli-app/cper-convert.c
index 942d258..d1caeb6 100644
--- a/cli-app/cper-convert.c
+++ b/cli-app/cper-convert.c
@@ -13,6 +13,8 @@
 #include <libcper/log.h>
 #include <libcper/cper-parse.h>
 #include <libcper/json-schema.h>
+#include <libcper/Cper.h>
+#include <libcper/base64.h>
 
 void cper_to_json(char *in_file, char *out_file, int is_single_section);
 void json_to_cper(const char *in_file, const char *out_file);
@@ -94,12 +96,49 @@
 		return;
 	}
 
+	fseek(cper_file, 0, SEEK_END);
+	long fsize = ftell(cper_file);
+	fseek(cper_file, 0, SEEK_SET);
+
+	char *fbuff = malloc(fsize);
+	size_t readsize = fread(fbuff, 1, (long)fsize, cper_file);
+	if (readsize != (size_t)fsize) {
+		printf("Could not read CPER file '%s', read returned %ld bytes.\n",
+		       in_file, readsize);
+		return;
+	}
+
+	if (!header_valid(fbuff, readsize)) {
+		// Check if it's base64 encoded
+		int32_t decoded_len = 0;
+		UINT8 *decoded = base64_decode(fbuff, readsize, &decoded_len);
+		if (decoded == NULL) {
+			printf("base64 decode failed for CPER file '%s'.\n",
+			       in_file);
+			free(fbuff);
+			free(decoded);
+			return;
+		}
+		if (!header_valid((const char *)decoded, decoded_len)) {
+			printf("Invalid CPER file '%s'.\n", in_file);
+			free(fbuff);
+			free(decoded);
+			return;
+		}
+		// Swap the buffer to the base64 decoded buffer.
+		free(fbuff);
+		fbuff = (char *)decoded;
+
+		fsize = decoded_len;
+		decoded = NULL;
+	}
+
 	//Convert.
 	json_object *ir;
 	if (is_single_section) {
-		ir = cper_single_section_to_ir(cper_file);
+		ir = cper_buf_single_section_to_ir((UINT8 *)fbuff, readsize);
 	} else {
-		ir = cper_to_ir(cper_file);
+		ir = cper_buf_to_ir((UINT8 *)fbuff, fsize);
 	}
 	fclose(cper_file);
 
diff --git a/cper-parse.c b/cper-parse.c
index af7d424..87d577b 100644
--- a/cper-parse.c
+++ b/cper-parse.c
@@ -26,6 +26,41 @@
 json_object *cper_buf_section_to_ir(const void *cper_section_buf, size_t size,
 				    EFI_ERROR_SECTION_DESCRIPTOR *descriptor);
 
+static int header_signature_valid(EFI_COMMON_ERROR_RECORD_HEADER *header)
+{
+	if (header->SignatureStart != EFI_ERROR_RECORD_SIGNATURE_START) {
+		cper_print_log(
+			"Invalid CPER file: Invalid header (incorrect signature). %x\n",
+			header->SignatureStart);
+		return 0;
+	}
+	if (header->SignatureEnd != EFI_ERROR_RECORD_SIGNATURE_END) {
+		cper_print_log(
+			"Invalid CPER file: Invalid header (incorrect signature end). %x\n",
+			header->SignatureEnd);
+		return 0;
+	}
+	if (header->SectionCount == 0) {
+		cper_print_log(
+			"Invalid CPER file: Invalid section count (0).\n");
+		return 0;
+	}
+	return 1;
+}
+
+int header_valid(const char *cper_buf, size_t size)
+{
+	if (size < sizeof(EFI_COMMON_ERROR_RECORD_HEADER)) {
+		return 0;
+	}
+	EFI_COMMON_ERROR_RECORD_HEADER *header =
+		(EFI_COMMON_ERROR_RECORD_HEADER *)cper_buf;
+	if (!header_signature_valid(header)) {
+		return 0;
+	}
+	return header_signature_valid(header);
+}
+
 json_object *cper_buf_to_ir(const unsigned char *cper_buf, size_t size)
 {
 	json_object *parent = NULL;
@@ -35,28 +70,17 @@
 
 	const unsigned char *pos = cper_buf;
 	unsigned int remaining = size;
-
-	if (remaining < sizeof(EFI_COMMON_ERROR_RECORD_HEADER)) {
-		cper_print_log(
-			"Invalid CPER file: Invalid header (not enough bytes).\n");
+	if (size < sizeof(EFI_COMMON_ERROR_RECORD_HEADER)) {
 		goto fail;
 	}
-
-	EFI_COMMON_ERROR_RECORD_HEADER *header = NULL;
-	header = (EFI_COMMON_ERROR_RECORD_HEADER *)cper_buf;
+	EFI_COMMON_ERROR_RECORD_HEADER *header =
+		(EFI_COMMON_ERROR_RECORD_HEADER *)cper_buf;
+	if (!header_signature_valid(header)) {
+		goto fail;
+	}
 	pos += sizeof(EFI_COMMON_ERROR_RECORD_HEADER);
 	remaining -= sizeof(EFI_COMMON_ERROR_RECORD_HEADER);
-	if (header->SignatureStart != EFI_ERROR_RECORD_SIGNATURE_START) {
-		cper_print_log(
-			"Invalid CPER file: Invalid header (incorrect signature). %x\n",
-			header->SignatureStart);
-		goto fail;
-	}
-	if (header->SectionCount == 0) {
-		cper_print_log(
-			"Invalid CPER file: Invalid section count (0).\n");
-		goto fail;
-	}
+
 	if (remaining < sizeof(EFI_ERROR_SECTION_DESCRIPTOR)) {
 		cper_print_log(
 			"Invalid CPER file: Invalid section descriptor (section offset + length > size).\n");
diff --git a/include/libcper/cper-parse.h b/include/libcper/cper-parse.h
index 7f05817..06949a4 100644
--- a/include/libcper/cper-parse.h
+++ b/include/libcper/cper-parse.h
@@ -28,6 +28,8 @@
 			  "HW_ERROR_FLAGS_PREVERR",                            \
 			  "HW_ERROR_FLAGS_SIMULATED" }
 
+int header_valid(const char *cper_buf, size_t size);
+
 json_object *cper_to_ir(FILE *cper_file);
 json_object *cper_buf_to_ir(const unsigned char *cper_buf, size_t size);
 json_object *cper_single_section_to_ir(FILE *cper_section_file);
diff --git a/log.c b/log.c
index 2938a60..16f671f 100644
--- a/log.c
+++ b/log.c
@@ -13,7 +13,7 @@
 	CPER_LOG_NONE,
 	CPER_LOG_STDIO,
 	CPER_LOG_CUSTOM,
-} log_type = CPER_LOG_NONE;
+} log_type = CPER_LOG_STDIO;
 
 static void (*log_custom_fn)(const char *, va_list);
 
diff --git a/tests/base64_test.c b/tests/base64_test.c
index abc540c..6fc7825 100644
--- a/tests/base64_test.c
+++ b/tests/base64_test.c
@@ -5,23 +5,49 @@
 #include <assert.h>
 #include <string.h>
 
+const char *good_encode_outputs[] = {
+	"Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy",
+};
+const char *good_encode_inputs[] = {
+	"f", "fo", "foo", "foob", "fooba", "foobar",
+};
+
 void test_base64_encode_good()
 {
 	int32_t encoded_len = 0;
-	const char *data = "f";
-	char *encoded = base64_encode((unsigned char *)data, strlen(data),
-				      &encoded_len);
-	assert(encoded_len == 4);
-	assert(memcmp(encoded, "Zg==", encoded_len) == 0);
-	free(encoded);
+	for (long unsigned int i = 0;
+	     i < sizeof(good_encode_inputs) / sizeof(good_encode_inputs[0]);
+	     i++) {
+		const char *data = good_encode_inputs[i];
+		char *encoded = base64_encode((unsigned char *)data,
+					      strlen(data), &encoded_len);
+		assert((size_t)encoded_len == strlen(good_encode_outputs[i]));
+		assert(memcmp(encoded, good_encode_outputs[i], encoded_len) ==
+		       0);
+		free(encoded);
+	}
 }
 
+const char *good_decode_inputs[] = {
+	"Zg==", "Zg", "Zm8=", "Zm8", "Zm9v",
+};
+const char *good_decode_outputs[] = {
+	"f", "f", "fo", "fo", "foo",
+};
+
 void test_base64_decode_good()
 {
-	int32_t decoded_len = 0;
-	const char *data = "Zg==";
-	UINT8 *decoded = base64_decode(data, strlen(data), &decoded_len);
-	assert(decoded_len == 1);
-	assert(decoded[0] == 'f');
-	free(decoded);
+	for (long unsigned int i = 0;
+	     i < sizeof(good_decode_inputs) / sizeof(good_decode_inputs[0]);
+	     i++) {
+		int32_t decoded_len = 0;
+		const char *data = good_decode_inputs[i];
+		UINT8 *decoded =
+			base64_decode(data, strlen(data), &decoded_len);
+		assert(decoded != NULL);
+		assert((size_t)decoded_len == strlen(good_decode_outputs[i]));
+		assert(memcmp(decoded, good_decode_outputs[i], decoded_len) ==
+		       0);
+		free(decoded);
+	}
 }