golang判断字符串是否base64编码的字符串算法, 可准确判断是或否 附带单元测试用例和模糊测试用例

 Base64规定:

4 个 Base64 字符表示 3 个二进制字节,因为:64 * 64 * 64 * 64 = 256 * 256 * 256。
Base64 将三个字节转化成四个字节,因此 Base64 编码后的文本,会比原文本大出三分之一左右。
Base64共有65个字符,他们分别是: 大写字母 A-Z、小写字母 a-z、数字 0-9、符号 "+"、"/"(再加上作为垫字的 "=",垫字是当生成的 Base64 字符串的个数不是 4 的倍数时,添加在尾部的字符)。

Base64将其他所有符号都转换成这65个字符集中的字符

Base64字符判断思路

根据上面的规则,要判断一个字符串是否base64编码 就需要使用正则和其自带的编码和解码来判断,另外一个特例 就是单字符串为纯数字时 某些情况下利用自带的编解码都是可以通过的,这里将纯数字的情况全部视为非Base64字符

是否Base64字符判断golang实现代码

注意,下面的代码需要引入go依赖  go-str-utils,

安装方法 go get -u github.com/tekintian/go-str-utils


// 判断字符串是否是base64字符 可准确判断是或者否   纯数字被视为非base64字符
//
// Base64规定: 4 个 Base64 字符表示 3 个二进制字节,因为:64 * 64 * 64 * 64 = 256 * 256 * 256。
// Base64 将三个字节转化成四个字节。
// Base64共有65个字符,他们分别是: 大写字母 A-Z、小写字母 a-z、数字 0-9、符号 "+"、"/"(再加上作为垫字的 "=",
// 垫字是当生成的 Base64 字符串的个数不是 4 的倍数时,添加在尾部的字符)。
// Base64将其他所有符号都转换成这65个字符集中的字符。
// @author: TekinTian <tekintian@gmail.com>
// @see https://dev.tekin.cn/
func JudgeBase64(str string) bool {
	// 先断言字符串的长度是否符合base64规范, 即必须是4的倍数; 把这个放在开始,因为这个判断的时间复杂度最低
	if len(str) < 4 || len(str)%4 != 0 {
		return false
	}
	// 在用正则来判断 字符串的规则是否符合
	// 纯数字 这个是一个特例,他不是base64但是 某些情况下可以通过base64编码和解码 这里将他们全部视为非base64字符
	re, _ := GetRegexp(`^\d+$`)
	if re.MatchString(str) {
		return false
	}
	// 正则后向非断言  (?!\d+$) 断言后面非连续的纯数字
	// 判断字符串是否符合base64的编码格式 即是由64个字符和垫字符组成
	re, _ = GetRegexp(`^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$`)
	matched := re.MatchString(str)
	if !matched {
		return false
	}
	// 在使用自身带的解码和转码 在查看他么的值是否相等,如果相等说明是base64 否则不是
	deStr, err := base64.StdEncoding.DecodeString(str)
	if err != nil { // 无法解码,肯定不是base64
		return false
	}
	bs64Str := base64.StdEncoding.EncodeToString(deStr)
	//如果解码后再转码和原来的字符一样说明是base64 否则不是
	return str == bs64Str
}

JudgeBase64单元测试用例


// base64字符判断单元测试用例
func TestJudgeBase64(t *testing.T) {
	cases := []struct {
		str      string
		expected bool
	}{
		{str: "00000000", expected: false},
		{str: "JTIyMTIzJTIy", expected: true},
		{str: "JTIyMSUyMg==", expected: true},
		{str: "123456 dfadsfdagf!", expected: false},
		{str: "1", expected: false},
		{str: "123", expected: false},
		{str: "1234", expected: false},
		{str: "0000", expected: false},
	}
	for _, v := range cases {
		t.Run(v.str, func(t *testing.T) {
			// 业务逻辑测试
			ret := strutils.JudgeBase64(v.str)
			if ret != v.expected {
				t.Fatalf(" %v test failed, expected %v, got %v", v.str, v.expected, ret)
			}
		})
	}
}

JudgeBase64模糊测试用例


// 模糊测试命令: go test -fuzz=FuzzJudgeBase64 -fuzztime 30s
func FuzzJudgeBase64(f *testing.F) {
	// 测试种子语料
	testcases := []string{
		base64.StdEncoding.EncodeToString([]byte("hello world")),
		base64.StdEncoding.EncodeToString([]byte("你好中国")),
		base64.StdEncoding.EncodeToString([]byte("%$#@@*(*%$%")),
		base64.StdEncoding.EncodeToString([]byte("123")),
	}
	for _, tc := range testcases {
		f.Add(tc) // 提供种子语料库
	}
	f.Fuzz(func(t *testing.T, orig string) {
		// 首先需要对源语料 进行过滤, 因为这些情况 JudgeBase64 一定会返回false;
		judgeRes := strutils.JudgeBase64(orig)

		orig = strings.TrimSpace(orig)
		// 长度非4的倍数
		if (len(orig) < 4 || len(orig)%4 != 0) && !judgeRes {
			t.Skipf("测试数据 %v 长度不符合base64规则,跳过", orig)
		}
		re, _ := strutils.GetRegexp(`^\d+$`)
		//纯数字
		if re.MatchString(orig) && !judgeRes {
			t.Skipf("测试数据 %v 是纯数字,跳过", orig)
		}
		//包含非base64允许字符
		re, _ = strutils.GetRegexp(`^([0-9a-zA-Z+/=]+)$`)
		if !re.MatchString(orig) && !judgeRes {
			t.Skipf("测试数据 %v 包含非base64允许字符,跳过", orig)
		}

		// 其他情况
		if !judgeRes { // 判断结果为非base64
			// 再次进行验证, 将源进行base64解码,然后在对结果进行编码, 如果没有异常,且他们相等 那么说明JudgeBase64 判断失误! 否则判断成功
			b, err := base64.StdEncoding.DecodeString(orig)
			src := base64.StdEncoding.EncodeToString(b)
			if err == nil && fmt.Sprintf("%v", b) == fmt.Sprintf("%v", src) {
				t.Fatalf("%v JudgeBase64 Failed, expected true got false", orig)
			}
		}

	})

}

模糊测试结果

> go test -fuzz=FuzzJudgeBase64 -fuzztime 30s
fuzz: elapsed: 0s, gathering baseline coverage: 0/197 completed
fuzz: elapsed: 0s, gathering baseline coverage: 197/197 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 228550 (76179/sec), new interesting: 1 (total: 198)
fuzz: elapsed: 6s, execs: 439994 (70460/sec), new interesting: 1 (total: 198)
fuzz: elapsed: 9s, execs: 621605 (60546/sec), new interesting: 1 (total: 198)
fuzz: elapsed: 12s, execs: 787822 (55397/sec), new interesting: 2 (total: 199)
fuzz: elapsed: 15s, execs: 931280 (47820/sec), new interesting: 2 (total: 199)
fuzz: elapsed: 18s, execs: 1044367 (37695/sec), new interesting: 2 (total: 199)
fuzz: elapsed: 21s, execs: 1149541 (35062/sec), new interesting: 2 (total: 199)
fuzz: elapsed: 24s, execs: 1230256 (26902/sec), new interesting: 2 (total: 199)
fuzz: elapsed: 27s, execs: 1298371 (22711/sec), new interesting: 2 (total: 199)
fuzz: elapsed: 30s, execs: 1348064 (16565/sec), new interesting: 2 (total: 199)
fuzz: elapsed: 31s, execs: 1348064 (0/sec), new interesting: 2 (total: 199)
PASS
ok      github.com/tekintian/go-str-utils       32.364s

go-str-utils项目github: GitHub - tekintian/go-str-utils: go语言里面高效,符合使用习惯的字符串相关的实用工具函数 The effective, idiomatic golang utils for go string

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值