Files
QSfera/Server/vendor/github.com/ggwhite/go-masker/masker.go
T
Курнат Андрей 2315f25754 Initial QSfera import
2026-06-07 10:20:04 +03:00

572 lines
13 KiB
Go

// Package masker Provide mask format of Taiwan usually used(Name, Address, Email, ID ...etc.),
package masker
import (
"fmt"
"math"
"net/url"
"reflect"
"strings"
)
const tagName = "mask"
type mtype string
// Maske Types of format string
const (
MPassword mtype = "password"
MName mtype = "name"
MAddress mtype = "addr"
MEmail mtype = "email"
MMobile mtype = "mobile"
MTelephone mtype = "tel"
MID mtype = "id"
MCreditCard mtype = "credit"
MStruct mtype = "struct"
MURL mtype = "url"
)
// Masker is a instance to marshal masked string
type Masker struct {
mask string
}
func strLoop(str string, length int) string {
var mask string
for i := 1; i <= length; i++ {
mask += str
}
return mask
}
func (m *Masker) overlay(str string, overlay string, start int, end int) (overlayed string) {
r := []rune(str)
l := len([]rune(r))
if l == 0 {
return ""
}
if start < 0 {
start = 0
}
if start > l {
start = l
}
if end < 0 {
end = 0
}
if end > l {
end = l
}
if start > end {
tmp := start
start = end
end = tmp
}
overlayed = ""
overlayed += string(r[:start])
overlayed += overlay
overlayed += string(r[end:])
return overlayed
}
// Struct must input a interface{}, add tag mask on struct fields, after Struct(), return a pointer interface{} of input type and it will be masked with the tag format type
//
// Example:
//
// type Foo struct {
// Name string `mask:"name"`
// Email string `mask:"email"`
// Password string `mask:"password"`
// ID string `mask:"id"`
// Address string `mask:"addr"`
// Mobile string `mask:"mobile"`
// Telephone string `mask:"tel"`
// Credit string `mask:"credit"`
// Foo *Foo `mask:"struct"`
// }
//
// func main() {
// s := &Foo{
// Name: ...,
// Email: ...,
// Password: ...,
// Foo: &{
// Name: ...,
// Email: ...,
// Password: ...,
// }
// }
//
// m := masker.New()
//
// t, err := m.Struct(s)
//
// fmt.Println(t.(*Foo))
// }
func (m *Masker) Struct(s interface{}) (interface{}, error) {
if s == nil {
return nil, fmt.Errorf("input is nil")
}
var selem, tptr reflect.Value
st := reflect.TypeOf(s)
if st.Kind() == reflect.Ptr {
tptr = reflect.New(st.Elem())
selem = reflect.ValueOf(s).Elem()
} else {
tptr = reflect.New(st)
selem = reflect.ValueOf(s)
}
for i := 0; i < selem.NumField(); i++ {
if !selem.Type().Field(i).IsExported() {
continue
}
mtag := selem.Type().Field(i).Tag.Get(tagName)
if len(mtag) == 0 {
tptr.Elem().Field(i).Set(selem.Field(i))
continue
}
switch selem.Field(i).Type().Kind() {
default:
tptr.Elem().Field(i).Set(selem.Field(i))
case reflect.String:
tptr.Elem().Field(i).SetString(m.String(mtype(mtag), selem.Field(i).String()))
case reflect.Struct:
if mtype(mtag) == MStruct {
_t, err := m.Struct(selem.Field(i).Interface())
if err != nil {
return nil, err
}
tptr.Elem().Field(i).Set(reflect.ValueOf(_t).Elem())
}
case reflect.Ptr:
if selem.Field(i).IsNil() {
continue
}
if mtype(mtag) == MStruct {
_t, err := m.Struct(selem.Field(i).Interface())
if err != nil {
return nil, err
}
tptr.Elem().Field(i).Set(reflect.ValueOf(_t))
}
case reflect.Slice:
if selem.Field(i).IsNil() {
continue
}
if selem.Field(i).Type().Elem().Kind() == reflect.String {
orgval := selem.Field(i).Interface().([]string)
newval := make([]string, len(orgval))
for i, val := range selem.Field(i).Interface().([]string) {
newval[i] = m.String(mtype(mtag), val)
}
tptr.Elem().Field(i).Set(reflect.ValueOf(newval))
continue
}
if selem.Field(i).Type().Elem().Kind() == reflect.Struct && mtype(mtag) == MStruct {
newval := reflect.MakeSlice(selem.Field(i).Type(), 0, selem.Field(i).Len())
for j, l := 0, selem.Field(i).Len(); j < l; j++ {
_n, err := m.Struct(selem.Field(i).Index(j).Interface())
if err != nil {
return nil, err
}
newval = reflect.Append(newval, reflect.ValueOf(_n).Elem())
}
tptr.Elem().Field(i).Set(newval)
continue
}
if selem.Field(i).Type().Elem().Kind() == reflect.Ptr && mtype(mtag) == MStruct {
newval := reflect.MakeSlice(selem.Field(i).Type(), 0, selem.Field(i).Len())
for j, l := 0, selem.Field(i).Len(); j < l; j++ {
_n, err := m.Struct(selem.Field(i).Index(j).Interface())
if err != nil {
return nil, err
}
newval = reflect.Append(newval, reflect.ValueOf(_n))
}
tptr.Elem().Field(i).Set(newval)
continue
}
if selem.Field(i).Type().Elem().Kind() == reflect.Interface && mtype(mtag) == MStruct {
newval := reflect.MakeSlice(selem.Field(i).Type(), 0, selem.Field(i).Len())
for j, l := 0, selem.Field(i).Len(); j < l; j++ {
_n, err := m.Struct(selem.Field(i).Index(j).Interface())
if err != nil {
return nil, err
}
if reflect.TypeOf(selem.Field(i).Index(j).Interface()).Kind() != reflect.Ptr {
newval = reflect.Append(newval, reflect.ValueOf(_n).Elem())
} else {
newval = reflect.Append(newval, reflect.ValueOf(_n))
}
}
tptr.Elem().Field(i).Set(newval)
continue
}
case reflect.Interface:
if selem.Field(i).IsNil() {
continue
}
if mtype(mtag) != MStruct {
continue
}
_t, err := m.Struct(selem.Field(i).Interface())
if err != nil {
return nil, err
}
if reflect.TypeOf(selem.Field(i).Interface()).Kind() != reflect.Ptr {
tptr.Elem().Field(i).Set(reflect.ValueOf(_t).Elem())
} else {
tptr.Elem().Field(i).Set(reflect.ValueOf(_t))
}
}
}
return tptr.Interface(), nil
}
// String mask input string of the mask type
//
// Example:
//
// masker.String(masker.MName, "ggwhite")
// masker.String(masker.MID, "A123456789")
// masker.String(masker.MMobile, "0987987987")
func (m *Masker) String(t mtype, i string) string {
switch t {
default:
return i
case MPassword:
return m.Password(i)
case MName:
return m.Name(i)
case MAddress:
return m.Address(i)
case MEmail:
return m.Email(i)
case MMobile:
return m.Mobile(i)
case MID:
return m.ID(i)
case MTelephone:
return m.Telephone(i)
case MCreditCard:
return m.CreditCard(i)
case MURL:
return m.URL(i)
}
}
// Name mask the second letter and the third letter
//
// Example:
// input: ABCD
// output: A**D
func (m *Masker) Name(i string) string {
l := len([]rune(i))
if l == 0 {
return ""
}
// if has space
if strs := strings.Split(i, " "); len(strs) > 1 {
tmp := make([]string, len(strs))
for idx, str := range strs {
tmp[idx] = m.Name(str)
}
return strings.Join(tmp, " ")
}
if l == 2 || l == 3 {
return m.overlay(i, strLoop(instance.mask, len("**")), 1, 2)
}
if l > 3 {
return m.overlay(i, strLoop(instance.mask, len("**")), 1, 3)
}
return strLoop(instance.mask, len("**"))
}
// ID mask last 4 digits of ID number
//
// Example:
// input: A123456789
// output: A12345****
func (m *Masker) ID(i string) string {
l := len([]rune(i))
if l == 0 {
return ""
}
return m.overlay(i, strLoop(instance.mask, len("****")), 6, 10)
}
// Address keep first 6 letters, mask the rest
//
// Example:
// input: 台北市內湖區內湖路一段737巷1號1樓
// output: 台北市內湖區******
func (m *Masker) Address(i string) string {
l := len([]rune(i))
if l == 0 {
return ""
}
if l <= 6 {
return strLoop(instance.mask, len("******"))
}
return m.overlay(i, strLoop(instance.mask, len("******")), 6, math.MaxInt)
}
// CreditCard mask 6 digits from the 7'th digit
//
// Example:
// input1: 1234567890123456 (VISA, JCB, MasterCard)(len = 16)
// output1: 123456******3456
// input2: 123456789012345` (American Express)(len = 15)
// output2: 123456******345`
func (m *Masker) CreditCard(i string) string {
l := len([]rune(i))
if l == 0 {
return ""
}
return m.overlay(i, strLoop(instance.mask, len("******")), 6, 12)
}
// Email keep domain and the first 3 letters
//
// Example:
// input: ggw.chang@gmail.com
// output: ggw****@gmail.com
func (m *Masker) Email(i string) string {
l := len([]rune(i))
if l == 0 {
return ""
}
tmp := strings.Split(i, "@")
if len(tmp) == 1 {
return m.overlay(i, strLoop(instance.mask, len("****")), 3, 7)
}
addr := tmp[0]
domain := tmp[1]
addr = m.overlay(addr, strLoop(instance.mask, len("****")), 3, 7)
return addr + "@" + domain
}
// Mobile mask 3 digits from the 4'th digit
//
// Example:
// input: 0987654321
// output: 0987***321
func (m *Masker) Mobile(i string) string {
if len(i) == 0 {
return ""
}
return m.overlay(i, strLoop(instance.mask, len("***")), 4, 7)
}
// Telephone remove "(", ")", " ", "-" chart, and mask last 4 digits of telephone number, format to "(??)????-????"
//
// Example:
// input: 0227993078
// output: (02)2799-****
func (m *Masker) Telephone(i string) string {
l := len([]rune(i))
if l == 0 {
return ""
}
i = strings.Replace(i, " ", "", -1)
i = strings.Replace(i, "(", "", -1)
i = strings.Replace(i, ")", "", -1)
i = strings.Replace(i, "-", "", -1)
l = len([]rune(i))
if l != 10 && l != 8 {
return i
}
ans := ""
if l == 10 {
ans += "("
ans += i[:2]
ans += ")"
i = i[2:]
}
ans += i[:4]
ans += "-"
ans += "****"
return ans
}
// Password always return "************"
func (m *Masker) Password(i string) string {
l := len([]rune(i))
if l == 0 {
return ""
}
return strLoop(instance.mask, len("************"))
}
// URL mask the password part of the URL if exists
//
// Example:
// input: http://admin:mysecretpassword@localhost:1234/uri
// output:http://admin:xxxxx@localhost:1234/uri
func (m *Masker) URL(i string) string {
u, err := url.Parse(i)
if err != nil {
return i
}
return u.Redacted()
}
// New create Masker
func New() *Masker {
return &Masker{
mask: "*",
}
}
var instance *Masker
func init() {
instance = New()
}
// Struct must input a interface{}, add tag mask on struct fields, after Struct(), return a pointer interface{} of input type and it will be masked with the tag format type
//
// Example:
//
// type Foo struct {
// Name string `mask:"name"`
// Email string `mask:"email"`
// Password string `mask:"password"`
// ID string `mask:"id"`
// Address string `mask:"addr"`
// Mobile string `mask:"mobile"`
// Telephone string `mask:"tel"`
// Credit string `mask:"credit"`
// Foo *Foo `mask:"struct"`
// }
//
// func main() {
// s := &Foo{
// Name: ...,
// Email: ...,
// Password: ...,
// Foo: &{
// Name: ...,
// Email: ...,
// Password: ...,
// }
// }
//
// t, err := masker.Struct(s)
//
// fmt.Println(t.(*Foo))
// }
func Struct(s interface{}) (interface{}, error) {
return instance.Struct(s)
}
// String mask input string of the mask type
//
// Example:
//
// masker.String(masker.MName, "ggwhite")
// masker.String(masker.MID, "A123456789")
// masker.String(masker.MMobile, "0987987987")
func String(t mtype, i string) string {
return instance.String(t, i)
}
// Name mask the second letter and the third letter
//
// Example:
// input: ABCD
// output: A**D
func Name(i string) string {
return instance.Name(i)
}
// ID mask last 4 digits of ID number
//
// Example:
// input: A123456789
// output: A12345****
func ID(i string) string {
return instance.ID(i)
}
// Address keep first 6 letters, mask the rest
//
// Example:
// input: 台北市內湖區內湖路一段737巷1號1樓
// output: 台北市內湖區******
func Address(i string) string {
return instance.Address(i)
}
// CreditCard mask 6 digits from the 7'th digit
//
// Example:
// input1: 1234567890123456 (VISA, JCB, MasterCard)(len = 16)
// output1: 123456******3456
// input2: 123456789012345 (American Express)(len = 15)
// output2: 123456******345
func CreditCard(i string) string {
return instance.CreditCard(i)
}
// Email keep domain and the first 3 letters
//
// Example:
// input: ggw.chang@gmail.com
// output: ggw****@gmail.com
func Email(i string) string {
return instance.Email(i)
}
// Mobile mask 3 digits from the 4'th digit
//
// Example:
// input: 0987654321
// output: 0987***321
func Mobile(i string) string {
return instance.Mobile(i)
}
// Telephone remove "(", ")", " ", "-" chart, and mask last 4 digits of telephone number, format to "(??)????-????"
//
// Example:
// input: 0227993078
// output: (02)2799-****
func Telephone(i string) string {
return instance.Telephone(i)
}
// Password always return "************"
func Password(i string) string {
return instance.Password(i)
}
func SetMask(mask string) {
instance.mask = mask
}