Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
coverage.txt
+12
View File
@@ -0,0 +1,12 @@
language: go
go:
- "1.11"
env:
- env GO111MODULE=on
script:
- go test -v -race -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 White Chang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+228
View File
@@ -0,0 +1,228 @@
# Golang Masker
[![Build Status](https://travis-ci.org/ggwhite/go-masker.svg?branch=master)](https://travis-ci.org/ggwhite/go-masker)
[![codecov](https://codecov.io/gh/ggwhite/go-masker/branch/master/graph/badge.svg)](https://codecov.io/gh/ggwhite/go-masker)
[![Go Report Card](https://goreportcard.com/badge/github.com/ggwhite/go-masker)](https://goreportcard.com/report/github.com/ggwhite/go-masker)
[![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/ggwhite/go-masker/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/ggwhite/go-masker?status.svg)](https://godoc.org/github.com/ggwhite/go-masker)
[![Release](https://img.shields.io/github/release/ggwhite/go-masker.svg?style=flat-square)](https://github.com/ggwhite/go-masker/releases/latest)
Golang Masker is a simple utility of creating a mask for sensitive information.
* [Getting Started](#Getting-Started)
# Getting Started
```
$ go get -u github.com/ggwhite/go-masker
```
## Demo
There are two ways to get a masker instance:
#### 1. Get a instance directly from go-masker package
``` golang
package main
import (
masker "github.com/ggwhite/go-masker"
)
func main() {
masker.Name("ggwhite")
masker.ID("A123456789")
masker.Mobile("0978978978")
}
```
#### 2. Get a instance via `masker.New()`
``` golang
package main
import (
masker "github.com/ggwhite/go-masker"
)
func main() {
m := masker.New()
m.Name("ggwhite")
m.ID("A123456789")
m.Mobile("0978978978")
}
```
## Mask Types
|Type |Const |Tag |Description |
|:----------:|:-----------:|:---------:|:------------------------------------------------------------------------------------------------------|
|Name |MName |name |mask the second letter and the third letter |
|Password |MPassword |password |always return `************` |
|Address |MAddress |addr |keep first 6 letters, mask the rest |
|Email |MEmail |email |keep domain and the first 3 letters |
|Mobile |MMobile |mobile |mask 3 digits from the 4'th digit |
|Telephone |MTelephone |tel |remove `(`, `)`, ` `, `-` chart, and mask last 4 digits of telephone number, format to `(??)????-????` |
|ID |MID |id |mask last 4 digits of ID number |
|CreditCard |MCreditCard |credit |mask 6 digits from the 7'th digit |
|Struct |MStruct |struct |mask the struct |
|URL |MURL |url |mask the password field if present, eg http://admin:mysecretpassword@localhost:1234/uri |
## Mask the `String`
`String` method requires two parameters, a mask type CONST and a string:
``` golang
package main
import (
masker "github.com/ggwhite/go-masker"
)
func main() {
masker.String(masker.MName, "ggwhite")
masker.String(masker.MID, "A123456789")
masker.String(masker.MMobile, "0987987987")
}
```
Result:
```
g**hite
A12345****
0987***987
```
## Custom Mask
``` golang
package main
import (
masker "github.com/ggwhite/go-masker"
)
func main() {
masker.String(masker.MName, "ggwhite")
masker.String(masker.MID, "A123456789")
masker.SetMask("-")
masker.String(masker.MMobile, "0987987987")
}
```
Result:
```
g**hite
A12345****
0987---987
```
## Mask the `Struct`
You can define your struct and add tag `mask` to let masker know what kind of the format to mask.
> Field must be **public** in the struct.
``` golang
package main
import (
"log"
masker "github.com/ggwhite/go-masker"
)
type Foo struct {
Name string `mask:"name"`
Mobile string `mask:"mobile"`
}
func main() {
foo := &Foo{
Name: "ggwhite",
Mobile: "0987987987",
}
t, err := masker.Struct(foo)
log.Println(t)
log.Println(t.(*Foo))
log.Println(err)
}
```
Result:
```
t = &{g**hite 0987***987}
err = <nil>
```
### Struct contain struct
``` golang
package main
import (
masker "github.com/ggwhite/go-masker"
)
type Foo struct {
Name string `mask:"name"`
Mobile string `mask:"mobile"`
Qoo *Qoo `mask:"struct"`
}
type Qoo struct {
Name string `mask:"name"`
Telephone string `mask:"tel"`
}
func main() {
foo := &Foo{
Name: "ggwhite",
Mobile: "0987987987",
Qoo: &Qoo{
Name: "gino",
Telephone: "0287658765",
},
}
t, err := masker.Struct(foo)
log.Println(t)
log.Println(t.(*Foo).Qoo)
log.Println(err)
}
```
Result:
```
t = &{g**hite 0987***987 0xc00000a080}
t.Qoo = &{g**o (02)8765-****}
err = <nil>
```
### Struct contain string slice
``` golang
package main
import (
masker "github.com/ggwhite/go-masker"
)
type Foo struct {
Name string `mask:"name"`
Mobile string `mask:"mobile"`
IDs []string `mask:"id"`
}
func main() {
foo := &Foo{
Name: "ggwhite",
Mobile: "0987987987",
IDs: []string{
"A123456789",
"A987654321",
},
}
t, err := masker.Struct(foo)
log.Println(t)
log.Println(err)
}
```
Result:
```
t = &{g**hite 0987***987 [A12345**** A98765****]}
err = <nil>
```
+571
View File
@@ -0,0 +1,571 @@
// 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
}