From 3e55b9fe622957444c928b41f02e6e965589e247 Mon Sep 17 00:00:00 2001
From: Julien Boeuf <jboeuf@google.com>
Date: Thu, 25 Jun 2015 17:54:19 +0200
Subject: [PATCH] Base64 decode improvements.

- Allow ablity to specify the length for decoding.
- Allow for non-padded encodings to be decoded properly.
---
 src/core/security/base64.c       | 99 +++++++++++++++++++++-----------
 src/core/security/base64.h       |  4 ++
 test/core/security/base64_test.c | 38 ++++++++++++
 3 files changed, 108 insertions(+), 33 deletions(-)

diff --git a/src/core/security/base64.c b/src/core/security/base64.c
index 3f28c09611..d029e33928 100644
--- a/src/core/security/base64.c
+++ b/src/core/security/base64.c
@@ -120,7 +120,68 @@ char *grpc_base64_encode(const void *vdata, size_t data_size, int url_safe,
 }
 
 gpr_slice grpc_base64_decode(const char *b64, int url_safe) {
-  size_t b64_len = strlen(b64);
+  return grpc_base64_decode_with_len(b64, strlen(b64), url_safe);
+}
+
+static void decode_one_char(const unsigned char *codes, unsigned char *result,
+                            size_t *result_offset) {
+  gpr_uint32 packed = (codes[0] << 2) | (codes[1] >> 4);
+  result[(*result_offset)++] = (unsigned char)packed;
+}
+
+static void decode_two_chars(const unsigned char *codes, unsigned char *result,
+                             size_t *result_offset) {
+  gpr_uint32 packed = (codes[0] << 10) | (codes[1] << 4) | (codes[2] >> 2);
+  result[(*result_offset)++] = (unsigned char)(packed >> 8);
+  result[(*result_offset)++] = (unsigned char)(packed);
+}
+
+static int decode_group(const unsigned char *codes, size_t num_codes,
+                        unsigned char *result, size_t *result_offset) {
+  GPR_ASSERT(num_codes <= 4);
+
+  /* Short end groups that may not have padding. */
+  if (num_codes == 1) {
+    gpr_log(GPR_ERROR, "Invalid group. Must be at least 2 bytes.");
+    return 0;
+  }
+  if (num_codes == 2) {
+    decode_one_char(codes, result, result_offset);
+    return 1;
+  }
+  if (num_codes == 3) {
+    decode_two_chars(codes, result, result_offset);
+    return 1;
+  }
+
+  /* Regular 4 byte groups with padding or not. */
+  GPR_ASSERT(num_codes == 4);
+  if (codes[0] == GRPC_BASE64_PAD_BYTE || codes[1] == GRPC_BASE64_PAD_BYTE) {
+    gpr_log(GPR_ERROR, "Invalid padding detected.");
+    return 0;
+  }
+  if (codes[2] == GRPC_BASE64_PAD_BYTE) {
+    if (codes[3] == GRPC_BASE64_PAD_BYTE) {
+      decode_one_char(codes, result, result_offset);
+      } else {
+      gpr_log(GPR_ERROR, "Invalid padding detected.");
+      return 0;
+    }
+  } else if (codes[3] == GRPC_BASE64_PAD_BYTE) {
+    decode_two_chars(codes, result, result_offset);
+  } else {
+    /* No padding. */
+    gpr_uint32 packed =
+        (codes[0] << 18) | (codes[1] << 12) | (codes[2] << 6) | codes[3];
+    result[(*result_offset)++] = (unsigned char)(packed >> 16);
+    result[(*result_offset)++] = (unsigned char)(packed >> 8);
+    result[(*result_offset)++] = (unsigned char)(packed);
+  }
+  return 1;
+}
+
+gpr_slice grpc_base64_decode_with_len(const char *b64, size_t b64_len,
+                                      int url_safe) {
   gpr_slice result = gpr_slice_malloc(b64_len);
   unsigned char *current = GPR_SLICE_START_PTR(result);
   size_t result_size = 0;
@@ -151,43 +212,15 @@ gpr_slice grpc_base64_decode(const char *b64, int url_safe) {
     } else {
       codes[num_codes++] = (unsigned char)code;
       if (num_codes == 4) {
-        if (codes[0] == GRPC_BASE64_PAD_BYTE ||
-            codes[1] == GRPC_BASE64_PAD_BYTE) {
-          gpr_log(GPR_ERROR, "Invalid padding detected.");
-          goto fail;
-        }
-        if (codes[2] == GRPC_BASE64_PAD_BYTE) {
-          if (codes[3] == GRPC_BASE64_PAD_BYTE) {
-            /* Double padding. */
-            gpr_uint32 packed = (gpr_uint32)((codes[0] << 2) | (codes[1] >> 4));
-            current[result_size++] = (unsigned char)packed;
-          } else {
-            gpr_log(GPR_ERROR, "Invalid padding detected.");
-            goto fail;
-          }
-        } else if (codes[3] == GRPC_BASE64_PAD_BYTE) {
-          /* Single padding. */
-          gpr_uint32 packed =
-              (gpr_uint32)((codes[0] << 10) | (codes[1] << 4) | (codes[2] >> 2));
-          current[result_size++] = (unsigned char)(packed >> 8);
-          current[result_size++] = (unsigned char)(packed);
-        } else {
-          /* No padding. */
-          gpr_uint32 packed =
-              (gpr_uint32)((codes[0] << 18) | (codes[1] << 12) | (codes[2] << 6) | codes[3]);
-          current[result_size++] = (unsigned char)(packed >> 16);
-          current[result_size++] = (unsigned char)(packed >> 8);
-          current[result_size++] = (unsigned char)(packed);
-        }
+        if (!decode_group(codes, num_codes, current, &result_size)) goto fail;
         num_codes = 0;
       }
     }
   }
 
-  if (num_codes != 0) {
-    gpr_log(GPR_ERROR, "Invalid base64.");
-    gpr_slice_unref(result);
-    return gpr_empty_slice();
+  if (num_codes != 0 &&
+      !decode_group(codes, num_codes, current, &result_size)) {
+    goto fail;
   }
   GPR_SLICE_SET_LENGTH(result, result_size);
   return result;
diff --git a/src/core/security/base64.h b/src/core/security/base64.h
index 6a7cd8e96c..b9abc07b52 100644
--- a/src/core/security/base64.h
+++ b/src/core/security/base64.h
@@ -45,4 +45,8 @@ char *grpc_base64_encode(const void *data, size_t data_size, int url_safe,
    slice in case of failure. */
 gpr_slice grpc_base64_decode(const char *b64, int url_safe);
 
+/* Same as above except that the length is provided by the caller. */
+gpr_slice grpc_base64_decode_with_len(const char *b64, size_t b64_len,
+                                      int url_safe);
+
 #endif  /* GRPC_INTERNAL_CORE_SECURITY_BASE64_H */
diff --git a/test/core/security/base64_test.c b/test/core/security/base64_test.c
index a922896bc3..f8b7ebf554 100644
--- a/test/core/security/base64_test.c
+++ b/test/core/security/base64_test.c
@@ -169,6 +169,43 @@ static void test_rfc4648_test_vectors(void) {
   gpr_free(b64);
 }
 
+static void test_unpadded_decode(void) {
+  gpr_slice decoded;
+
+  decoded = grpc_base64_decode("Zm9vYmFy", 0);
+  GPR_ASSERT(!GPR_SLICE_IS_EMPTY(decoded));
+  GPR_ASSERT(gpr_slice_str_cmp(decoded, "foobar") == 0);
+  gpr_slice_unref(decoded);
+
+  decoded = grpc_base64_decode("Zm9vYmE", 0);
+  GPR_ASSERT(!GPR_SLICE_IS_EMPTY(decoded));
+  GPR_ASSERT(gpr_slice_str_cmp(decoded, "fooba") == 0);
+  gpr_slice_unref(decoded);
+
+  decoded = grpc_base64_decode("Zm9vYg", 0);
+  GPR_ASSERT(!GPR_SLICE_IS_EMPTY(decoded));
+  GPR_ASSERT(gpr_slice_str_cmp(decoded, "foob") == 0);
+  gpr_slice_unref(decoded);
+
+  decoded = grpc_base64_decode("Zm9v", 0);
+  GPR_ASSERT(!GPR_SLICE_IS_EMPTY(decoded));
+  GPR_ASSERT(gpr_slice_str_cmp(decoded, "foo") == 0);
+  gpr_slice_unref(decoded);
+
+  decoded = grpc_base64_decode("Zm8", 0);
+  GPR_ASSERT(!GPR_SLICE_IS_EMPTY(decoded));
+  GPR_ASSERT(gpr_slice_str_cmp(decoded, "fo") == 0);
+  gpr_slice_unref(decoded);
+
+  decoded = grpc_base64_decode("Zg", 0);
+  GPR_ASSERT(!GPR_SLICE_IS_EMPTY(decoded));
+  GPR_ASSERT(gpr_slice_str_cmp(decoded, "f") == 0);
+  gpr_slice_unref(decoded);
+
+  decoded = grpc_base64_decode("", 0);
+  GPR_ASSERT(GPR_SLICE_IS_EMPTY(decoded));
+}
+
 int main(int argc, char **argv) {
   grpc_test_init(argc, argv);
   test_simple_encode_decode_b64_no_multiline();
@@ -181,5 +218,6 @@ int main(int argc, char **argv) {
   test_full_range_encode_decode_b64_urlsafe_multiline();
   test_url_safe_unsafe_mismtach_failure();
   test_rfc4648_test_vectors();
+  test_unpadded_decode();
   return 0;
 }
-- 
GitLab