Skip to content

Commit

Permalink
Support url safe base64
Browse files Browse the repository at this point in the history
  • Loading branch information
chenBright committed Aug 1, 2023
1 parent 4615906 commit 74d9579
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 0 deletions.
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ BUTIL_SRCS = [
"src/butil/at_exit.cc",
"src/butil/atomicops_internals_x86_gcc.cc",
"src/butil/base64.cc",
"src/butil/base64url.cc",
"src/butil/big_endian.cc",
"src/butil/cpu.cc",
"src/butil/debug/alias.cc",
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ set(BUTIL_SOURCES
${PROJECT_SOURCE_DIR}/src/butil/at_exit.cc
${PROJECT_SOURCE_DIR}/src/butil/atomicops_internals_x86_gcc.cc
${PROJECT_SOURCE_DIR}/src/butil/base64.cc
${PROJECT_SOURCE_DIR}/src/butil/base64url.cc
${PROJECT_SOURCE_DIR}/src/butil/big_endian.cc
${PROJECT_SOURCE_DIR}/src/butil/cpu.cc
${PROJECT_SOURCE_DIR}/src/butil/debug/alias.cc
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ BUTIL_SOURCES = \
src/butil/at_exit.cc \
src/butil/atomicops_internals_x86_gcc.cc \
src/butil/base64.cc \
src/butil/base64url.cc \
src/butil/big_endian.cc \
src/butil/cpu.cc \
src/butil/debug/alias.cc \
Expand Down
96 changes: 96 additions & 0 deletions src/butil/base64url.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "butil/base64.h"
#include "butil/base64url.h"

#include "third_party/modp_b64/modp_b64_data.h"

namespace butil {

// Base64url maps {+, /} to {-, _} in order for the encoded content to be safe
// to use in a URL. These characters will be translated by this implementation.
#define BASE64_CHARS "+/"
#define BASE64_URL_SAFE_CHARS "-_"
#define URL_SAFE_CHAR62 '-'
#define URL_SAFE_CHAR63 '_'

void Base64UrlEncode(const StringPiece& input,
Base64UrlEncodePolicy policy,
std::string* output) {
Base64Encode(input, output);

std::replace(output->begin(), output->end(), CHAR62, URL_SAFE_CHAR62);
std::replace(output->begin(), output->end(), CHAR63, URL_SAFE_CHAR63);

switch (policy) {
case Base64UrlEncodePolicy::INCLUDE_PADDING:
// The padding included in |*output| will not be amended.
break;
case Base64UrlEncodePolicy::OMIT_PADDING:
// The padding included in |*output| will be removed.
const size_t last_non_padding_pos =
output->find_last_not_of(CHARPAD);
if (last_non_padding_pos != std::string::npos) {
output->resize(last_non_padding_pos + 1);
}
break;
}
}

bool Base64UrlDecode(const StringPiece& input,
Base64UrlDecodePolicy policy,
std::string* output) {
// Characters outside of the base64url alphabet are disallowed, which includes
// the {+, /} characters found in the conventional base64 alphabet.
if (input.find_first_of(BASE64_CHARS) != std::string::npos)
return false;

const size_t required_padding_characters = input.size() % 4;
const bool needs_replacement =
input.find_first_of(BASE64_URL_SAFE_CHARS) != std::string::npos;

switch (policy) {
case Base64UrlDecodePolicy::REQUIRE_PADDING:
// Fail if the required padding is not included in |input|.
if (required_padding_characters > 0)
return false;
break;
case Base64UrlDecodePolicy::IGNORE_PADDING:
// Missing padding will be silently appended.
break;
case Base64UrlDecodePolicy::DISALLOW_PADDING:
// Fail if padding characters are included in |input|.
if (input.find_first_of(CHARPAD) != std::string::npos)
return false;
break;
}

// If the string either needs replacement of URL-safe characters to normal
// base64 ones, or additional padding, a copy of |input| needs to be made in
// order to make these adjustments without side effects.
if (required_padding_characters > 0 || needs_replacement) {
std::string base64_input;

size_t base64_input_size = input.size();
if (required_padding_characters > 0)
base64_input_size += 4 - required_padding_characters;

base64_input.reserve(base64_input_size);
input.AppendToString(&base64_input);

// Substitute the base64url URL-safe characters to their base64 equivalents.
std::replace(base64_input.begin(), base64_input.end(), URL_SAFE_CHAR62, CHAR62);
std::replace(base64_input.begin(), base64_input.end(), URL_SAFE_CHAR63, CHAR63);

// Append the necessary padding characters.
base64_input.resize(base64_input_size, '=');

return Base64Decode(base64_input, output);
}

return Base64Decode(input, output);
}

} // namespace butil
54 changes: 54 additions & 0 deletions src/butil/base64url.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef BASE_BASE64URL_H_
#define BASE_BASE64URL_H_

#include <string>

#include "butil/base_export.h"
#include "butil/strings/string_piece.h"

namespace butil {

enum class Base64UrlEncodePolicy {
// Include the trailing padding in the output, when necessary.
INCLUDE_PADDING,

// Remove the trailing padding from the output.
OMIT_PADDING
};

// Encodes the |input| string in base64url, defined in RFC 4648:
// https://tools.ietf.org/html/rfc4648#section-5
//
// The |policy| defines whether padding should be included or omitted from the
// encoded |*output|. |input| and |*output| may reference the same storage.
BUTIL_EXPORT void Base64UrlEncode(const StringPiece& input,
Base64UrlEncodePolicy policy,
std::string* output);

enum class Base64UrlDecodePolicy {
// Require inputs to contain trailing padding if non-aligned.
REQUIRE_PADDING,

// Accept inputs regardless of whether they have the correct padding.
IGNORE_PADDING,

// Reject inputs if they contain any trailing padding.
DISALLOW_PADDING
};

// Decodes the |input| string in base64url, defined in RFC 4648:
// https://tools.ietf.org/html/rfc4648#section-5
//
// The |policy| defines whether padding will be required, ignored or disallowed
// altogether. |input| and |*output| may reference the same storage.
BUTIL_EXPORT bool Base64UrlDecode(const StringPiece& input,
Base64UrlDecodePolicy policy,
std::string* output) WARN_UNUSED_RESULT;

} // namespace butil

#endif // BASE_BASE64URL_H_
1 change: 1 addition & 0 deletions test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ TEST_BUTIL_SOURCES = [
"at_exit_unittest.cc",
"atomicops_unittest.cc",
"base64_unittest.cc",
"base64url_unittest.cc",
"big_endian_unittest.cc",
"bits_unittest.cc",
"hash_tables_unittest.cc",
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ SET(TEST_BUTIL_SOURCES
${PROJECT_SOURCE_DIR}/test/at_exit_unittest.cc
${PROJECT_SOURCE_DIR}/test/atomicops_unittest.cc
${PROJECT_SOURCE_DIR}/test/base64_unittest.cc
${PROJECT_SOURCE_DIR}/test/base64url_unittest.cc
${PROJECT_SOURCE_DIR}/test/big_endian_unittest.cc
${PROJECT_SOURCE_DIR}/test/bits_unittest.cc
${PROJECT_SOURCE_DIR}/test/hash_tables_unittest.cc
Expand Down
1 change: 1 addition & 0 deletions test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ TEST_BUTIL_SOURCES = \
at_exit_unittest.cc \
atomicops_unittest.cc \
base64_unittest.cc \
base64url_unittest.cc \
big_endian_unittest.cc \
bits_unittest.cc \
hash_tables_unittest.cc \
Expand Down
110 changes: 110 additions & 0 deletions test/base64url_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "butil/base64url.h"

#include <gtest/gtest.h>

namespace butil {

TEST(Base64UrlTest, EncodeIncludePaddingPolicy) {
std::string output;
Base64UrlEncode("hello?world", Base64UrlEncodePolicy::INCLUDE_PADDING,
&output);

// Base64 version: aGVsbG8/d29ybGQ=
EXPECT_EQ("aGVsbG8_d29ybGQ=", output);

// Test for behavior for very short and empty strings.
Base64UrlEncode("??", Base64UrlEncodePolicy::INCLUDE_PADDING, &output);
EXPECT_EQ("Pz8=", output);

Base64UrlEncode("", Base64UrlEncodePolicy::INCLUDE_PADDING, &output);
EXPECT_EQ("", output);
}

TEST(Base64UrlTest, EncodeOmitPaddingPolicy) {
std::string output;
Base64UrlEncode("hello?world", Base64UrlEncodePolicy::OMIT_PADDING, &output);

// base64 version: aGVsbG8/d29ybGQ=
EXPECT_EQ("aGVsbG8_d29ybGQ", output);

// Test for behavior for very short and empty strings.
Base64UrlEncode("??", Base64UrlEncodePolicy::OMIT_PADDING, &output);
EXPECT_EQ("Pz8", output);

Base64UrlEncode("", Base64UrlEncodePolicy::OMIT_PADDING, &output);
EXPECT_EQ("", output);
}

TEST(Base64UrlTest, DecodeRequirePaddingPolicy) {
std::string output;
ASSERT_TRUE(Base64UrlDecode("aGVsbG8_d29ybGQ=",
Base64UrlDecodePolicy::REQUIRE_PADDING, &output));

EXPECT_EQ("hello?world", output);

ASSERT_FALSE(Base64UrlDecode(
"aGVsbG8_d29ybGQ", Base64UrlDecodePolicy::REQUIRE_PADDING, &output));

// Test for behavior for very short and empty strings.
ASSERT_TRUE(
Base64UrlDecode("Pz8=", Base64UrlDecodePolicy::REQUIRE_PADDING, &output));
EXPECT_EQ("??", output);

ASSERT_TRUE(
Base64UrlDecode("", Base64UrlDecodePolicy::REQUIRE_PADDING, &output));
EXPECT_EQ("", output);
}

TEST(Base64UrlTest, DecodeIgnorePaddingPolicy) {
std::string output;
ASSERT_TRUE(Base64UrlDecode("aGVsbG8_d29ybGQ",
Base64UrlDecodePolicy::IGNORE_PADDING, &output));

EXPECT_EQ("hello?world", output);

// Including the padding is accepted as well.
ASSERT_TRUE(Base64UrlDecode("aGVsbG8_d29ybGQ=",
Base64UrlDecodePolicy::IGNORE_PADDING, &output));

EXPECT_EQ("hello?world", output);
}

TEST(Base64UrlTest, DecodeDisallowPaddingPolicy) {
std::string output;
ASSERT_FALSE(Base64UrlDecode(
"aGVsbG8_d29ybGQ=", Base64UrlDecodePolicy::DISALLOW_PADDING, &output));

// The policy will allow the input when padding has been omitted.
ASSERT_TRUE(Base64UrlDecode(
"aGVsbG8_d29ybGQ", Base64UrlDecodePolicy::DISALLOW_PADDING, &output));

EXPECT_EQ("hello?world", output);
}

TEST(Base64UrlTest, DecodeDisallowsBase64Alphabet) {
std::string output;

// The "/" character is part of the conventional base64 alphabet, but has been
// substituted with "_" in the base64url alphabet.
ASSERT_FALSE(Base64UrlDecode(
"aGVsbG8/d29ybGQ=", Base64UrlDecodePolicy::REQUIRE_PADDING, &output));
}

TEST(Base64UrlTest, DecodeDisallowsPaddingOnly) {
std::string output;

ASSERT_FALSE(Base64UrlDecode(
"=", Base64UrlDecodePolicy::IGNORE_PADDING, &output));
ASSERT_FALSE(Base64UrlDecode(
"==", Base64UrlDecodePolicy::IGNORE_PADDING, &output));
ASSERT_FALSE(Base64UrlDecode(
"===", Base64UrlDecodePolicy::IGNORE_PADDING, &output));
ASSERT_FALSE(Base64UrlDecode(
"====", Base64UrlDecodePolicy::IGNORE_PADDING, &output));
}

} // namespace butil

0 comments on commit 74d9579

Please sign in to comment.