@@ -0,0 +1,133 @@
|
||||
package cors
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Origin *[]string //传入空指针,表示允许"*", 传入空数组表示禁止跨域
|
||||
Methods []string //Methods
|
||||
AllowedHeaders []string //AllowHeaders
|
||||
ExposedHeaders []string //exposeHeader
|
||||
Credentials bool //cookie
|
||||
MaxAge int64 //缓存MaxAge
|
||||
PreflightContinue bool //在遇到Options的时候,继续,而不是返回请求
|
||||
OptionsSuccessStatus int //Options时,返回的状态码,默认204
|
||||
}
|
||||
|
||||
func configureOrigin(o *Options, c *gin.Context) map[string]string {
|
||||
reqOrigin := c.GetHeader("Origin")
|
||||
if o.Origin == nil {
|
||||
return map[string]string{"Access-Control-Allow-Origin": "*"}
|
||||
}
|
||||
headers := make(map[string]string)
|
||||
isAllowed := false
|
||||
for _, allowed := range *o.Origin {
|
||||
if allowed == reqOrigin {
|
||||
isAllowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if isAllowed {
|
||||
headers["Access-Control-Allow-Origin"] = reqOrigin
|
||||
} else {
|
||||
headers["Access-Control-Allow-Origin"] = strconv.FormatBool(false)
|
||||
}
|
||||
headers["Vary"] = "Origin"
|
||||
return headers
|
||||
}
|
||||
|
||||
func configureMethods(o *Options) map[string]string {
|
||||
headers := make(map[string]string)
|
||||
if len(o.Methods) > 0 {
|
||||
headers["Access-Control-Allow-Methods"] = strings.ToUpper(strings.Join(o.Methods, ","))
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func configureCredentials(o *Options) map[string]string {
|
||||
headers := make(map[string]string)
|
||||
if o.Credentials {
|
||||
headers["Access-Control-Allow-Credentials"] = strconv.FormatBool(true)
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func configureAllowedHeaders(o *Options, c *gin.Context) map[string]string {
|
||||
headers := make(map[string]string)
|
||||
if len(o.AllowedHeaders) > 0 {
|
||||
headers["Access-Control-Allow-Headers"] = strings.Join(o.AllowedHeaders, ",")
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func configureExposedHeaders(o *Options) map[string]string {
|
||||
var headers = make(map[string]string)
|
||||
if len(o.ExposedHeaders) > 0 {
|
||||
headers["Access-Control-Expose-Headers"] = strings.Join(o.ExposedHeaders, ",")
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func configureMaxAge(o *Options) map[string]string {
|
||||
var headers = make(map[string]string)
|
||||
if o.MaxAge >= 0 {
|
||||
headers["Access-Control-Max-Age"] = strconv.FormatInt(o.MaxAge, 10)
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func mergeMap(m ...map[string]string) map[string]string {
|
||||
r := make(map[string]string)
|
||||
for _, t := range m {
|
||||
for k, v := range t {
|
||||
r[k] = v
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func applyHeader(c *gin.Context, h map[string]string) {
|
||||
for k, v := range h {
|
||||
c.Header(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Cors 跨域处理
|
||||
func Cors(o *Options) gin.HandlerFunc {
|
||||
if o.OptionsSuccessStatus == 0 {
|
||||
o.OptionsSuccessStatus = http.StatusNoContent
|
||||
}
|
||||
return func(c *gin.Context) {
|
||||
method := c.Request.Method
|
||||
//c.Header("Access-Control-Allow-Origin", "*")
|
||||
//c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token,Authorization,Token,Content-Length,Etag,Content-Range,Accept-Ranges,User-Agent,Range,Bucket,Content-Disposition,Signature,X-Forwarded-For,X-Real-Ip")
|
||||
if method == "OPTIONS" {
|
||||
h := mergeMap(
|
||||
configureOrigin(o, c),
|
||||
configureCredentials(o),
|
||||
configureMethods(o),
|
||||
configureAllowedHeaders(o, c),
|
||||
configureMaxAge(o),
|
||||
configureExposedHeaders(o),
|
||||
)
|
||||
applyHeader(c, h)
|
||||
if o.PreflightContinue {
|
||||
return
|
||||
}
|
||||
c.Header("Content-Length", "0")
|
||||
c.AbortWithStatus(o.OptionsSuccessStatus)
|
||||
return
|
||||
}
|
||||
h := mergeMap(
|
||||
configureOrigin(o, c),
|
||||
configureCredentials(o),
|
||||
configureExposedHeaders(o),
|
||||
)
|
||||
applyHeader(c, h)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package ginzap
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"91porn-server/common/hevcpull"
|
||||
"91porn-server/common/log"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Logger(ctxKeys []string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
start := time.Now()
|
||||
path := c.Request.URL.Path
|
||||
query := hevcpull.RedactText(c.Request.URL.RawQuery)
|
||||
c.Next()
|
||||
end := time.Now()
|
||||
latency := end.Sub(start)
|
||||
if len(c.Errors) > 0 {
|
||||
for _, e := range c.Errors.Errors() {
|
||||
log.Error(e, log.R(c))
|
||||
}
|
||||
} else {
|
||||
l := []log.Field{
|
||||
log.R(c),
|
||||
log.Any("status", c.Writer.Status()),
|
||||
log.Any("method", c.Request.Method),
|
||||
log.Any("path", path),
|
||||
log.Any("query", query),
|
||||
log.Any("user-agent", c.Request.UserAgent()),
|
||||
log.Any("time", end.Format("2006-01-02 15:04:05")),
|
||||
log.Any("latency", latency),
|
||||
}
|
||||
for _, k := range ctxKeys {
|
||||
v, e := c.Get(k)
|
||||
if e {
|
||||
l = append(l, log.Any(k, v))
|
||||
}
|
||||
}
|
||||
log.Info(path, l...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RecoveryWithZap returns a gin.HandlerFunc (middleware)
|
||||
// that recovers from any panics and logs requests using uber-go/zap.
|
||||
// All errors are logged using zap.Error().
|
||||
// stack means whether output the stack info.
|
||||
// The stack info is easy to find where the error occurs but the stack info is too large.
|
||||
func Recovery(stack bool, do func(err interface{})) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if do != nil {
|
||||
do(err)
|
||||
}
|
||||
// Check for a broken connection, as it is not really a
|
||||
// condition that warrants a panic stack trace.
|
||||
var brokenPipe bool
|
||||
if ne, ok := err.(*net.OpError); ok {
|
||||
if se, ok := ne.Err.(*os.SyscallError); ok {
|
||||
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
|
||||
brokenPipe = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Record request identity without exposing authentication headers or body.
|
||||
requestFields := []log.Field{
|
||||
log.R(c),
|
||||
log.Any("method", c.Request.Method),
|
||||
log.Any("path", c.Request.URL.Path),
|
||||
log.Any("panic", err),
|
||||
}
|
||||
if brokenPipe {
|
||||
log.Error(c.Request.URL.Path, requestFields...)
|
||||
// If the connection is dead, we can't write a status to it.
|
||||
c.Error(err.(error)) // nolint: errcheck
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if stack {
|
||||
requestFields = append(requestFields, log.Any("stack", string(debug.Stack())))
|
||||
}
|
||||
log.Error("[Recovery from panic]", requestFields...)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
|
||||
"result": false,
|
||||
"error": http.StatusText(http.StatusInternalServerError),
|
||||
})
|
||||
}
|
||||
}()
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package ginzap
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"91porn-server/common/hevcpull"
|
||||
)
|
||||
|
||||
func TestSignedPullQueryIsRedactedBeforeLogging(t *testing.T) {
|
||||
const signature = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
query := "hevc_exp=1&hevc_sig=" + signature
|
||||
safe := hevcpull.RedactText(query)
|
||||
if strings.Contains(safe, signature) || !strings.Contains(safe, "[REDACTED]") {
|
||||
t.Fatalf("signed pull query was not redacted: %s", safe)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package ip
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"91porn-server/common/constant"
|
||||
"91porn-server/common/stderr"
|
||||
"91porn-server/models/v/ipwhitemod"
|
||||
"91porn-server/web/webg"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RealIP 获取真实IP
|
||||
func RealIP(c *gin.Context) {
|
||||
ip := c.ClientIP()
|
||||
c.Set(constant.CtxIP, ip)
|
||||
}
|
||||
|
||||
var accessForbidMsg = gin.H{
|
||||
"code": stderr.ErrAccessForbid,
|
||||
"msg": stderr.ErrAccessForbid.Msg(),
|
||||
}
|
||||
|
||||
// RealIP 获取真实IP
|
||||
func CheckWhiteIP(ctx *gin.Context) {
|
||||
//检查ip是否在白名单中
|
||||
// ipFlag, err := webg.Redis.SISMember(constant.IPWhiteRedisKey, constant.CtxIP)
|
||||
// if webg.Conf.EnableIPWhite.IsEnable && (err != nil || !ipFlag) {
|
||||
if webg.Conf.EnableIPWhite.IsEnable {
|
||||
iPWhite, err := ipwhitemod.FindOneByIp(ctx.ClientIP())
|
||||
if err != nil || iPWhite.IP == "" {
|
||||
ctx.AbortWithStatusJSON(http.StatusOK, accessForbidMsg)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package requestid
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func New(opts ...Option) gin.HandlerFunc {
|
||||
var cfg config = config{
|
||||
generator: DefaultFenerator,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(&cfg)
|
||||
}
|
||||
return func(c *gin.Context) {
|
||||
rid := strings.TrimSpace(c.GetHeader(HeaderKey))
|
||||
clientProvided := rid != "" && len(rid) <= 128
|
||||
if !clientProvided {
|
||||
rid = cfg.generator()
|
||||
}
|
||||
c.Request.Header.Set(HeaderKey, rid)
|
||||
c.Header(HeaderKey, rid)
|
||||
c.Set(ContextKey, rid)
|
||||
c.Set(ClientProvidedContextKey, clientProvided)
|
||||
}
|
||||
}
|
||||
|
||||
// FromClient 仅返回客户端显式提供且长度合规的请求ID。
|
||||
func FromClient(c *gin.Context) (string, bool) {
|
||||
if c == nil {
|
||||
return "", false
|
||||
}
|
||||
provided, ok := c.Get(ClientProvidedContextKey)
|
||||
if !ok || provided != true {
|
||||
return "", false
|
||||
}
|
||||
return c.GetHeader(HeaderKey), true
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package requestid
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestMiddlewareClientProvidedRequestID(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
var (
|
||||
gotID string
|
||||
gotProvided bool
|
||||
)
|
||||
router := gin.New()
|
||||
router.Use(New(WithGenerator(func() string { return "generated-id" })))
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
gotID, gotProvided = FromClient(c)
|
||||
c.Status(http.StatusNoContent)
|
||||
})
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
request.Header.Set(HeaderKey, " client-id ")
|
||||
response := httptest.NewRecorder()
|
||||
router.ServeHTTP(response, request)
|
||||
|
||||
if gotID != "client-id" || !gotProvided {
|
||||
t.Fatalf("FromClient() = (%q, %v), want (client-id, true)", gotID, gotProvided)
|
||||
}
|
||||
if got := response.Header().Get(HeaderKey); got != "client-id" {
|
||||
t.Fatalf("response %s = %q, want client-id", HeaderKey, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMiddlewareGeneratedRequestIDIsTracingOnly(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
requestID string
|
||||
}{
|
||||
{name: "missing"},
|
||||
{name: "blank", requestID: " "},
|
||||
{name: "too long", requestID: strings.Repeat("x", 129)},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var (
|
||||
gotID string
|
||||
gotProvided bool
|
||||
)
|
||||
router := gin.New()
|
||||
router.Use(New(WithGenerator(func() string { return "generated-id" })))
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
gotID, gotProvided = FromClient(c)
|
||||
c.Status(http.StatusNoContent)
|
||||
})
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
if tt.requestID != "" {
|
||||
request.Header.Set(HeaderKey, tt.requestID)
|
||||
}
|
||||
response := httptest.NewRecorder()
|
||||
router.ServeHTTP(response, request)
|
||||
|
||||
if gotID != "" || gotProvided {
|
||||
t.Fatalf("FromClient() = (%q, %v), want empty and false", gotID, gotProvided)
|
||||
}
|
||||
if got := request.Header.Get(HeaderKey); got != "generated-id" {
|
||||
t.Fatalf("request %s = %q, want generated-id", HeaderKey, got)
|
||||
}
|
||||
if got := response.Header().Get(HeaderKey); got != "generated-id" {
|
||||
t.Fatalf("response %s = %q, want generated-id", HeaderKey, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package requestid
|
||||
|
||||
import uuid "github.com/satori/go.uuid"
|
||||
|
||||
const (
|
||||
HeaderKey = "X-Request-ID"
|
||||
ContextKey = "Ctx-Request-ID"
|
||||
// ClientProvidedContextKey 标记请求ID是否由客户端显式提供;
|
||||
// 服务端自动生成的ID仅用于链路追踪,不应自动开启业务幂等缓存。
|
||||
ClientProvidedContextKey = "Ctx-Request-ID-Client-Provided"
|
||||
)
|
||||
|
||||
type RequestIDGenerator func() string
|
||||
|
||||
type config struct {
|
||||
generator RequestIDGenerator
|
||||
}
|
||||
|
||||
type Option func(*config)
|
||||
|
||||
func WithGenerator(g RequestIDGenerator) Option {
|
||||
return func(c *config) {
|
||||
c.generator = g
|
||||
}
|
||||
}
|
||||
|
||||
var DefaultFenerator = func() string {
|
||||
return uuid.NewV4().String()
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package ua
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"91porn-server/common/constant"
|
||||
"91porn-server/common/log"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type UA struct {
|
||||
UserAgent string
|
||||
DevID string //设备id 唯一标识
|
||||
DevType string //设备类型 华为nova7
|
||||
SysType string //pc
|
||||
Ver string //1.0
|
||||
BuildID string //
|
||||
Mac string //
|
||||
GlobalDevID string //全局唯一表示
|
||||
Terminal string //终端 0-客户端;1-h5(ios)端;2-web端
|
||||
IsH5 string
|
||||
SystemVersion string
|
||||
SystemName string
|
||||
DeviceBrand string
|
||||
DeviceModel string
|
||||
SID string
|
||||
}
|
||||
|
||||
func UAer(c *gin.Context) {
|
||||
userAgent := c.GetHeader("X-User-Agent") //兼容H5 UserAgent
|
||||
if userAgent == "" {
|
||||
userAgent = c.Request.UserAgent()
|
||||
}
|
||||
if userAgent == "" {
|
||||
log.Warn("user-agent empty")
|
||||
return
|
||||
}
|
||||
ua := Parse(userAgent)
|
||||
if sid := strings.TrimSpace(c.GetHeader("sid")); sid != "" {
|
||||
ua.SID = sid
|
||||
}
|
||||
c.Set(constant.CtxUA, ua)
|
||||
}
|
||||
|
||||
func Parse(userAgent string) UA {
|
||||
var uastr string
|
||||
ua := UA{}
|
||||
decodeUa, err := url.QueryUnescape(userAgent)
|
||||
if err != nil {
|
||||
log.Warn("URLDecode user-agent error", log.Any("ua", userAgent))
|
||||
uastr = userAgent
|
||||
} else {
|
||||
uastr = decodeUa
|
||||
}
|
||||
uas := strings.Split(uastr, ";")
|
||||
for _, v := range uas {
|
||||
vss := strings.SplitN(strings.TrimSpace(v), "=", 2)
|
||||
if len(vss) < 2 {
|
||||
log.Warn("user-agent miss", log.Any("ua", userAgent))
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(vss[0])
|
||||
value := strings.TrimSpace(vss[1])
|
||||
switch key {
|
||||
case "DevID":
|
||||
ua.DevID = value
|
||||
case "DevType":
|
||||
ua.DevType = value
|
||||
case "SysType":
|
||||
ua.SysType = value
|
||||
case "Ver":
|
||||
ua.Ver = value
|
||||
case "BuildID":
|
||||
ua.BuildID = value
|
||||
case "Mac":
|
||||
ua.Mac = value
|
||||
case "GlobalDevID":
|
||||
ua.GlobalDevID = value
|
||||
case "Terminal":
|
||||
ua.Terminal = value
|
||||
case "IsH5":
|
||||
ua.IsH5 = value
|
||||
case "SystemVersion":
|
||||
ua.SystemVersion = value
|
||||
case "SystemName":
|
||||
ua.SystemName = value
|
||||
case "DeviceBrand":
|
||||
ua.DeviceBrand = value
|
||||
case "DeviceModel", "device_model":
|
||||
ua.DeviceModel = value
|
||||
case "SID", "Sid", "sid":
|
||||
ua.SID = value
|
||||
default:
|
||||
log.Warn("user-agent unknown", log.Any("key", key), log.Any("ua", userAgent))
|
||||
}
|
||||
}
|
||||
ua.UserAgent = uastr
|
||||
return ua
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package ua
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"91porn-server/common/constant"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestUAerParsesSIDFromHeaderAndUserAgent(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
body := `{"devID":"91porn_google_3695e94c7226d787","qrCnt":"","devType":"lynx:36:id=CP1A.260305.018","sysType":"android","ver":"1.0.6","buildID":"com.noporn.newchatone_zero_six","devToken":"Qp6IluBGdByscQvXczvffmfC7YQxO6zm3Rr/A52FBpk=","cutInfos":""}`
|
||||
req := httptest.NewRequest("POST", "/api/app/mine/login", strings.NewReader(body))
|
||||
req.Header.Set("sid", "3a73a39a03cf450893f89909e31d4bea")
|
||||
req.Header.Set("user-agent", "DevID%3D91porn_google_3695e94c7226d787%3BDevType%3Dlynx%3A36%3Aid%3DCP1A.260305.018%3BSysType%3Dandroid%3BVer%3D1.0.6%3BBuildID%3Dcom.noporn.newchatone_zero_six%3BDeviceBrand%3Dgoogle%3BSystemName%3DAndroid%3BSystemVersion%3D16")
|
||||
req.Header.Set("x-api-key", "timestamp=1776140330;sign=f8c0476aa413e3e21ae6c8e2ad02b0c0f6af6d25;nonce=d8b1f060-6c3b-458b-b059-7ef87d2e9f79")
|
||||
req.Header.Set("accept-encoding", "*")
|
||||
req.Header.Set("device", "android")
|
||||
req.Header.Set("host", "91pornht.remoces.com")
|
||||
req.Header.Set("authorization", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0aW1lc3RhbXAiOjE3NzYxNDAyNTc3ODk4NjUyMDAsInR5cGUiOjAsInVpZCI6MzAzNzQzfQ.1tXpLY-FsDu9HzkT5SVVbMaOMtzkgLU5PBZzvHm-X0I")
|
||||
req.Header.Set("api_version", "1.0.0")
|
||||
req.Header.Set("content-type", "application/json;charset=UTF-8")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
|
||||
UAer(c)
|
||||
|
||||
value, ok := c.Get(constant.CtxUA)
|
||||
if !ok {
|
||||
t.Fatal("UAer did not set UA in context")
|
||||
}
|
||||
got, ok := value.(UA)
|
||||
if !ok {
|
||||
t.Fatalf("context UA type = %T, want ua.UA", value)
|
||||
}
|
||||
|
||||
if got.SID != "3a73a39a03cf450893f89909e31d4bea" {
|
||||
t.Fatalf("SID = %q, want header sid", got.SID)
|
||||
}
|
||||
if got.DevID != "91porn_google_3695e94c7226d787" {
|
||||
t.Fatalf("DevID = %q", got.DevID)
|
||||
}
|
||||
if got.DevType != "lynx:36:id=CP1A.260305.018" {
|
||||
t.Fatalf("DevType = %q", got.DevType)
|
||||
}
|
||||
if got.SysType != "android" {
|
||||
t.Fatalf("SysType = %q", got.SysType)
|
||||
}
|
||||
if got.Ver != "1.0.6" {
|
||||
t.Fatalf("Ver = %q", got.Ver)
|
||||
}
|
||||
if got.BuildID != "com.noporn.newchatone_zero_six" {
|
||||
t.Fatalf("BuildID = %q", got.BuildID)
|
||||
}
|
||||
if got.DeviceBrand != "google" {
|
||||
t.Fatalf("DeviceBrand = %q", got.DeviceBrand)
|
||||
}
|
||||
if got.SystemName != "Android" {
|
||||
t.Fatalf("SystemName = %q", got.SystemName)
|
||||
}
|
||||
if got.SystemVersion != "16" {
|
||||
t.Fatalf("SystemVersion = %q", got.SystemVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAcceptsSIDKeyVariants(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
userAgent string
|
||||
want string
|
||||
}{
|
||||
{name: "sid", userAgent: "DevID=device;sid=lower", want: "lower"},
|
||||
{name: "Sid", userAgent: "DevID=device;Sid=title", want: "title"},
|
||||
{name: "SID", userAgent: "DevID=device;SID=upper", want: "upper"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := Parse(tc.userAgent)
|
||||
if got.SID != tc.want {
|
||||
t.Fatalf("SID = %q, want %q", got.SID, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user