2020-10-08 15:06:42 +08:00
package waf
import (
2023-11-29 17:00:06 +08:00
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
2023-11-15 15:10:25 +08:00
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
2023-11-29 17:00:06 +08:00
"github.com/TeaOSLab/EdgeNode/internal/compressions"
2023-11-15 15:10:25 +08:00
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
2021-07-18 15:51:49 +08:00
"github.com/TeaOSLab/EdgeNode/internal/utils"
2023-11-15 15:10:25 +08:00
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
2020-10-08 15:06:42 +08:00
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
2023-11-16 08:44:07 +08:00
wafutils "github.com/TeaOSLab/EdgeNode/internal/waf/utils"
2020-10-08 15:06:42 +08:00
"github.com/iwind/TeaGo/logs"
2023-11-29 17:00:06 +08:00
"github.com/iwind/TeaGo/maps"
2023-11-15 15:10:25 +08:00
"github.com/iwind/TeaGo/rands"
2021-07-18 15:51:49 +08:00
"github.com/iwind/TeaGo/types"
2023-11-15 15:10:25 +08:00
stringutil "github.com/iwind/TeaGo/utils/string"
2023-11-29 17:00:06 +08:00
"io"
2020-10-08 15:06:42 +08:00
"net/http"
2023-11-29 17:00:06 +08:00
"net/url"
"strconv"
2021-07-18 15:51:49 +08:00
"strings"
2020-10-08 15:06:42 +08:00
"time"
)
2023-11-28 18:07:27 +08:00
const captchaIdName = "GOEDGE_WAF_CAPTCHA_ID"
2021-07-18 15:51:49 +08:00
var captchaValidator = NewCaptchaValidator ( )
2023-11-30 17:25:41 +08:00
var captchaGenerator = NewCaptchaGenerator ( )
2020-10-08 15:06:42 +08:00
type CaptchaValidator struct {
}
2021-07-18 15:51:49 +08:00
func NewCaptchaValidator ( ) * CaptchaValidator {
return & CaptchaValidator { }
}
2023-11-15 15:10:25 +08:00
func ( this * CaptchaValidator ) Run ( req requests . Request , writer http . ResponseWriter , defaultCaptchaType firewallconfigs . ServerCaptchaType ) {
2022-05-21 11:17:53 +08:00
var info = req . WAFRaw ( ) . URL . Query ( ) . Get ( "info" )
2021-07-18 15:51:49 +08:00
if len ( info ) == 0 {
2023-06-11 10:46:20 +08:00
req . ProcessResponseHeaders ( writer . Header ( ) , http . StatusBadRequest )
2021-07-18 15:51:49 +08:00
writer . WriteHeader ( http . StatusBadRequest )
_ , _ = writer . Write ( [ ] byte ( "invalid request" ) )
return
}
m , err := utils . SimpleDecryptMap ( info )
if err != nil {
2023-06-12 18:07:07 +08:00
req . ProcessResponseHeaders ( writer . Header ( ) , http . StatusBadRequest )
writer . WriteHeader ( http . StatusBadRequest )
2021-07-18 15:51:49 +08:00
_ , _ = writer . Write ( [ ] byte ( "invalid request" ) )
return
}
2022-05-20 11:56:06 +08:00
var timestamp = m . GetInt64 ( "timestamp" )
2021-07-18 15:51:49 +08:00
if timestamp < time . Now ( ) . Unix ( ) - 600 { // 10分钟之后信息过期
2023-06-12 18:07:07 +08:00
req . ProcessResponseHeaders ( writer . Header ( ) , http . StatusTemporaryRedirect )
2022-05-21 11:17:53 +08:00
http . Redirect ( writer , req . WAFRaw ( ) , m . GetString ( "url" ) , http . StatusTemporaryRedirect )
2021-07-18 15:51:49 +08:00
return
}
2022-05-21 11:17:53 +08:00
var actionId = m . GetInt64 ( "actionId" )
2021-07-18 15:51:49 +08:00
var setId = m . GetInt64 ( "setId" )
var originURL = m . GetString ( "url" )
2022-05-20 11:56:06 +08:00
var policyId = m . GetInt64 ( "policyId" )
var groupId = m . GetInt64 ( "groupId" )
2022-05-21 11:17:53 +08:00
var waf = SharedWAFManager . FindWAF ( policyId )
if waf == nil {
2023-06-12 18:07:07 +08:00
req . ProcessResponseHeaders ( writer . Header ( ) , http . StatusTemporaryRedirect )
2022-05-21 11:17:53 +08:00
http . Redirect ( writer , req . WAFRaw ( ) , originURL , http . StatusTemporaryRedirect )
return
}
var actionConfig = waf . FindAction ( actionId )
if actionConfig == nil {
2023-06-12 18:07:07 +08:00
req . ProcessResponseHeaders ( writer . Header ( ) , http . StatusTemporaryRedirect )
2022-05-21 11:17:53 +08:00
http . Redirect ( writer , req . WAFRaw ( ) , originURL , http . StatusTemporaryRedirect )
return
}
captchaActionConfig , ok := actionConfig . ( * CaptchaAction )
if ! ok {
2023-06-12 18:07:07 +08:00
req . ProcessResponseHeaders ( writer . Header ( ) , http . StatusTemporaryRedirect )
2022-05-21 11:17:53 +08:00
http . Redirect ( writer , req . WAFRaw ( ) , originURL , http . StatusTemporaryRedirect )
return
}
2023-11-15 15:10:25 +08:00
var captchaType = captchaActionConfig . CaptchaType
if len ( defaultCaptchaType ) > 0 && defaultCaptchaType != firewallconfigs . ServerCaptchaTypeNone {
captchaType = defaultCaptchaType
}
2023-11-29 17:00:06 +08:00
// check geetest
if captchaType == firewallconfigs . CaptchaTypeGeeTest {
if waf . DefaultCaptchaAction . GeeTestConfig == nil || ! waf . DefaultCaptchaAction . GeeTestConfig . IsOn {
captchaType = firewallconfigs . CaptchaTypeDefault
} else if captchaActionConfig . GeeTestConfig == nil {
captchaActionConfig . GeeTestConfig = waf . DefaultCaptchaAction . GeeTestConfig
}
}
2023-11-28 18:07:27 +08:00
if req . WAFRaw ( ) . Method == http . MethodPost && len ( req . WAFRaw ( ) . FormValue ( captchaIdName ) ) > 0 {
2023-11-15 15:10:25 +08:00
switch captchaType {
case firewallconfigs . CaptchaTypeOneClick :
this . validateOneClickForm ( captchaActionConfig , policyId , groupId , setId , originURL , req , writer )
case firewallconfigs . CaptchaTypeSlide :
this . validateSlideForm ( captchaActionConfig , policyId , groupId , setId , originURL , req , writer )
2023-11-29 17:00:06 +08:00
case firewallconfigs . CaptchaTypeGeeTest :
this . validateGeeTestForm ( captchaActionConfig , policyId , groupId , setId , originURL , req , writer )
2023-11-15 15:10:25 +08:00
default :
this . validateVerifyCodeForm ( captchaActionConfig , policyId , groupId , setId , originURL , req , writer )
}
2020-10-08 15:06:42 +08:00
} else {
2023-11-28 18:07:27 +08:00
var captchaId = req . WAFRaw ( ) . URL . Query ( ) . Get ( captchaIdName )
if len ( captchaId ) > 0 {
// 增加计数
CaptchaIncreaseFails ( req , captchaActionConfig , policyId , groupId , setId , CaptchaPageCodeImage )
this . showImage ( captchaActionConfig , req , writer , captchaType )
} else {
// 增加计数
CaptchaIncreaseFails ( req , captchaActionConfig , policyId , groupId , setId , CaptchaPageCodeShow )
2023-11-28 20:39:42 +08:00
this . show ( captchaActionConfig , setId , originURL , req , writer , captchaType )
2023-11-28 18:07:27 +08:00
}
2023-11-15 15:10:25 +08:00
}
}
2023-11-28 20:39:42 +08:00
func ( this * CaptchaValidator ) show ( actionConfig * CaptchaAction , setId int64 , originURL string , req requests . Request , writer http . ResponseWriter , captchaType firewallconfigs . ServerCaptchaType ) {
// validated yet?
if SharedIPWhiteList . Contains ( wafutils . ComposeIPType ( setId , req ) , actionConfig . Scope , req . WAFServerId ( ) , req . WAFRemoteIP ( ) ) {
http . Redirect ( writer , req . WAFRaw ( ) , originURL , http . StatusSeeOther )
return
}
2023-11-15 15:10:25 +08:00
switch captchaType {
case firewallconfigs . CaptchaTypeOneClick :
this . showOneClickForm ( actionConfig , req , writer )
case firewallconfigs . CaptchaTypeSlide :
this . showSlideForm ( actionConfig , req , writer )
2023-11-29 17:00:06 +08:00
case firewallconfigs . CaptchaTypeGeeTest :
this . showGeeTestForm ( actionConfig , req , writer , originURL )
2023-11-15 15:10:25 +08:00
default :
this . showVerifyCodesForm ( actionConfig , req , writer )
2020-10-08 15:06:42 +08:00
}
}
2023-11-28 18:07:27 +08:00
func ( this * CaptchaValidator ) showImage ( actionConfig * CaptchaAction , req requests . Request , writer http . ResponseWriter , captchaType firewallconfigs . ServerCaptchaType ) {
switch captchaType {
case firewallconfigs . CaptchaTypeOneClick :
// stub
case firewallconfigs . CaptchaTypeSlide :
// stub
2023-11-29 17:00:06 +08:00
case firewallconfigs . CaptchaTypeGeeTest :
// stub
2023-11-28 18:07:27 +08:00
default :
this . showVerifyImage ( actionConfig , req , writer )
}
}
2023-11-15 15:10:25 +08:00
func ( this * CaptchaValidator ) showVerifyCodesForm ( actionConfig * CaptchaAction , req requests . Request , writer http . ResponseWriter ) {
2020-10-08 15:06:42 +08:00
// show captcha
2022-05-21 11:17:53 +08:00
var countLetters = 6
if actionConfig . CountLetters > 0 && actionConfig . CountLetters <= 10 {
countLetters = int ( actionConfig . CountLetters )
}
2023-11-30 17:25:41 +08:00
var captchaId = captchaGenerator . NewCaptcha ( countLetters )
2020-10-08 15:06:42 +08:00
2022-05-21 11:17:53 +08:00
var lang = actionConfig . Lang
2021-07-18 15:51:49 +08:00
if len ( lang ) == 0 {
2022-05-21 11:17:53 +08:00
var acceptLanguage = req . WAFRaw ( ) . Header . Get ( "Accept-Language" )
2021-07-18 15:51:49 +08:00
if len ( acceptLanguage ) > 0 {
langIndex := strings . Index ( acceptLanguage , "," )
if langIndex > 0 {
lang = acceptLanguage [ : langIndex ]
}
}
}
if len ( lang ) == 0 {
lang = "en-US"
}
2023-08-08 15:39:00 +08:00
var msgTitle string
var msgPrompt string
var msgButtonTitle string
var msgRequestId string
2021-07-18 15:51:49 +08:00
switch lang {
case "en-US" :
msgTitle = "Verify Yourself"
msgPrompt = "Input verify code above:"
msgButtonTitle = "Verify Yourself"
2021-12-02 12:08:59 +08:00
msgRequestId = "Request ID"
2021-07-18 15:51:49 +08:00
case "zh-CN" :
msgTitle = "身份验证"
msgPrompt = "请输入上面的验证码"
msgButtonTitle = "提交验证"
2021-12-02 12:08:59 +08:00
msgRequestId = "请求ID"
2022-09-24 20:02:29 +08:00
case "zh-TW" :
msgTitle = "身份驗證"
msgPrompt = "請輸入上面的驗證碼"
msgButtonTitle = "提交驗證"
msgRequestId = "請求ID"
2021-07-18 15:51:49 +08:00
default :
msgTitle = "Verify Yourself"
msgPrompt = "Input verify code above:"
msgButtonTitle = "Verify Yourself"
2021-12-02 12:08:59 +08:00
msgRequestId = "Request ID"
2021-07-18 15:51:49 +08:00
}
2022-05-21 11:17:53 +08:00
var msgCss = ""
var requestIdBox = ` <address> ` + msgRequestId + ` : ` + req . Format ( "${requestId}" ) + ` </address> `
var msgFooter = ""
// 默认设置
if actionConfig . UIIsOn {
if len ( actionConfig . UIPrompt ) > 0 {
msgPrompt = actionConfig . UIPrompt
}
if len ( actionConfig . UIButtonTitle ) > 0 {
msgButtonTitle = actionConfig . UIButtonTitle
}
2022-05-21 20:02:35 +08:00
if len ( actionConfig . UITitle ) > 0 {
msgTitle = actionConfig . UITitle
}
2022-05-21 11:17:53 +08:00
if len ( actionConfig . UICss ) > 0 {
msgCss = actionConfig . UICss
}
if ! actionConfig . UIShowRequestId {
requestIdBox = ""
}
if len ( actionConfig . UIFooter ) > 0 {
msgFooter = actionConfig . UIFooter
}
2022-05-21 20:02:35 +08:00
}
var body = ` < form method = "POST" >
2023-11-28 18:07:27 +08:00
< input type = "hidden" name = "` + captchaIdName + `" value = "` + captchaId + `" / >
2022-05-21 20:02:35 +08:00
< div class = "ui-image" >
2023-11-28 18:07:27 +08:00
< p id = "ui-captcha-image-prompt" > loading ... < / p >
< img id = "ui-captcha-image" src = "` + req.WAFRaw().URL.String() + `&` + captchaIdName + `=` + captchaId + `" alt = "" / >
2022-05-21 20:02:35 +08:00
< / div >
< div class = "ui-input" >
2023-11-15 15:10:25 +08:00
< p class = "ui-prompt" > ` + msgPrompt + ` < / p >
2023-02-16 14:44:56 +08:00
< input type = "text" name = "GOEDGE_WAF_CAPTCHA_CODE" id = "GOEDGE_WAF_CAPTCHA_CODE" size = "` + types.String(countLetters*17/10) + `" maxlength = "` + types.String(countLetters) + `" autocomplete = "off" z - index = "1" class = "input" / >
2022-05-21 20:02:35 +08:00
< / div >
< div class = "ui-button" >
< button type = "submit" style = "line-height:24px;margin-top:10px" > ` + msgButtonTitle + ` < / button >
< / div >
< / form >
` + requestIdBox + `
2023-11-15 15:10:25 +08:00
` + msgFooter
2022-05-21 20:02:35 +08:00
// Body
if actionConfig . UIIsOn {
2022-05-21 11:17:53 +08:00
if len ( actionConfig . UIBody ) > 0 {
var index = strings . Index ( actionConfig . UIBody , "${body}" )
if index < 0 {
body = actionConfig . UIBody + body
} else {
body = actionConfig . UIBody [ : index ] + body + actionConfig . UIBody [ index + 7 : ] // 7是"${body}"的长度
}
}
}
2023-06-11 10:46:20 +08:00
var msgHTML = ` < ! DOCTYPE html >
2024-01-09 15:39:52 +08:00
< html lang = "` + lang + `" >
2020-10-08 15:06:42 +08:00
< head >
2021-07-18 15:51:49 +08:00
< title > ` + msgTitle + ` < / title >
2022-01-16 16:57:25 +08:00
< meta name = "viewport" content = "width=device-width, initial-scale=1, user-scalable=0" >
2022-05-21 11:17:53 +08:00
< meta charset = "UTF-8" / >
2021-07-18 15:51:49 +08:00
< script type = "text/javascript" >
if ( window . addEventListener != null ) {
2023-11-28 18:07:27 +08:00
document . addEventListener ( "DOMContentLoaded" , function ( ) {
document . getElementById ( "ui-captcha-image" ) . addEventListener ( "load" , function ( ) {
var promptBox = document . getElementById ( "ui-captcha-image-prompt" ) ;
promptBox . parentNode . removeChild ( promptBox ) ;
} ) ;
2021-07-18 15:51:49 +08:00
} )
2023-11-28 18:07:27 +08:00
window . addEventListener ( "load" , function ( ) {
document . getElementById ( "GOEDGE_WAF_CAPTCHA_CODE" ) . focus ( ) ;
} ) ;
2021-07-18 15:51:49 +08:00
}
< / script >
2021-12-02 12:08:59 +08:00
< style type = "text/css" >
2024-01-09 15:39:52 +08:00
* { font - size : 12 px ; }
form { max - width : 20 em ; margin : 0 auto ; text - align : center ; font - family : Roboto , "Helvetica Neue Light" , "Helvetica Neue" , Helvetica , Arial , "Lucida Grande" , sans - serif ; }
. ui - prompt { font - size : 1.2 rem ; }
. input { font - size : 16 px ; line - height : 24 px ; letter - spacing : 0.2 em ; min - width : 5 em ; text - align : center ; background : # fff ; border : 1 px solid rgba ( 0 , 0 , 0 , 0.38 ) ; color : rgba ( 0 , 0 , 0 , 0.87 ) ; outline : none ; border - radius : 4 px ; padding : 0.75 rem 0.75 rem ; }
. input : focus { border : 1 px # 3 f51b5 solid ; outline : none ; }
2021-12-02 12:08:59 +08:00
address { margin - top : 1 em ; padding - top : 0.5 em ; border - top : 1 px # ccc solid ; text - align : center ; }
2024-01-09 15:39:52 +08:00
button { background : # 3 f51b5 ; color : # fff ; cursor : pointer ; padding : 0.571 rem 0.75 rem ; min - width : 8 rem ; font - size : 1 rem ; border : 0 none ; border - radius : 4 px ; }
2022-05-21 11:17:53 +08:00
` + msgCss + `
2021-12-02 12:08:59 +08:00
< / style >
2020-10-08 15:06:42 +08:00
< / head >
2022-05-21 11:17:53 +08:00
< body > ` + body + `
2020-10-08 15:06:42 +08:00
< / body >
2023-06-11 10:46:20 +08:00
< / html > `
req . ProcessResponseHeaders ( writer . Header ( ) , http . StatusOK )
writer . Header ( ) . Set ( "Content-Type" , "text/html; charset=utf-8" )
writer . Header ( ) . Set ( "Content-Length" , types . String ( len ( msgHTML ) ) )
writer . WriteHeader ( http . StatusOK )
_ , _ = writer . Write ( [ ] byte ( msgHTML ) )
2020-10-08 15:06:42 +08:00
}
2023-11-28 18:07:27 +08:00
func ( this * CaptchaValidator ) showVerifyImage ( actionConfig * CaptchaAction , req requests . Request , writer http . ResponseWriter ) {
var captchaId = req . WAFRaw ( ) . URL . Query ( ) . Get ( captchaIdName )
if len ( captchaId ) == 0 {
return
}
writer . Header ( ) . Set ( "Content-Type" , "image/png" )
2023-11-30 17:25:41 +08:00
err := captchaGenerator . WriteImage ( writer , captchaId , 200 , 100 )
2023-11-28 18:07:27 +08:00
if err != nil {
logs . Error ( err )
return
}
}
2023-11-15 15:10:25 +08:00
func ( this * CaptchaValidator ) validateVerifyCodeForm ( actionConfig * CaptchaAction , policyId int64 , groupId int64 , setId int64 , originURL string , req requests . Request , writer http . ResponseWriter ) ( allow bool ) {
2023-11-28 18:07:27 +08:00
var captchaId = req . WAFRaw ( ) . FormValue ( captchaIdName )
2020-10-08 15:06:42 +08:00
if len ( captchaId ) > 0 {
2022-05-21 11:17:53 +08:00
var captchaCode = req . WAFRaw ( ) . FormValue ( "GOEDGE_WAF_CAPTCHA_CODE" )
2023-11-30 17:25:41 +08:00
if captchaGenerator . Verify ( captchaId , captchaCode ) {
2022-05-20 11:56:06 +08:00
// 清除计数
2022-05-21 20:02:35 +08:00
CaptchaDeleteCacheKey ( req )
2022-01-16 16:54:13 +08:00
2021-07-18 15:51:49 +08:00
var life = CaptchaSeconds
if actionConfig . Life > 0 {
life = types . Int ( actionConfig . Life )
}
// 加入到白名单
2023-11-16 08:44:07 +08:00
SharedIPWhiteList . RecordIP ( wafutils . ComposeIPType ( setId , req ) , actionConfig . Scope , req . WAFServerId ( ) , req . WAFRemoteIP ( ) , time . Now ( ) . Unix ( ) + int64 ( life ) , policyId , false , groupId , setId , "" )
2021-07-18 15:51:49 +08:00
2023-06-12 18:07:07 +08:00
req . ProcessResponseHeaders ( writer . Header ( ) , http . StatusSeeOther )
2022-05-21 11:17:53 +08:00
http . Redirect ( writer , req . WAFRaw ( ) , originURL , http . StatusSeeOther )
2020-10-08 15:06:42 +08:00
return false
} else {
2022-01-16 16:54:13 +08:00
// 增加计数
2022-05-21 20:02:35 +08:00
if ! CaptchaIncreaseFails ( req , actionConfig , policyId , groupId , setId , CaptchaPageCodeSubmit ) {
2022-05-20 11:56:06 +08:00
return false
2022-01-16 16:54:13 +08:00
}
2023-06-12 18:07:07 +08:00
req . ProcessResponseHeaders ( writer . Header ( ) , http . StatusSeeOther )
2022-05-21 11:17:53 +08:00
http . Redirect ( writer , req . WAFRaw ( ) , req . WAFRaw ( ) . URL . String ( ) , http . StatusSeeOther )
2020-10-08 15:06:42 +08:00
}
}
return true
}
2023-11-15 15:10:25 +08:00
func ( this * CaptchaValidator ) showOneClickForm ( actionConfig * CaptchaAction , req requests . Request , writer http . ResponseWriter ) {
var lang = actionConfig . Lang
if len ( lang ) == 0 {
var acceptLanguage = req . WAFRaw ( ) . Header . Get ( "Accept-Language" )
if len ( acceptLanguage ) > 0 {
langIndex := strings . Index ( acceptLanguage , "," )
if langIndex > 0 {
lang = acceptLanguage [ : langIndex ]
}
}
}
if len ( lang ) == 0 {
lang = "en-US"
}
var msgTitle string
var msgPrompt string
var msgRequestId string
switch lang {
case "zh-CN" :
msgTitle = "身份验证"
msgPrompt = "我不是机器人"
msgRequestId = "请求ID"
case "zh-TW" :
msgTitle = "身份驗證"
msgPrompt = "我不是機器人"
msgRequestId = "請求ID"
default :
msgTitle = "Verify Yourself"
msgPrompt = "I'm not a robot"
msgRequestId = "Request ID"
}
var msgCss = ""
var requestIdBox = ` <address> ` + msgRequestId + ` : ` + req . Format ( "${requestId}" ) + ` </address> `
var msgFooter = ""
// 默认设置
if actionConfig . OneClickUIIsOn {
if len ( actionConfig . OneClickUIPrompt ) > 0 {
msgPrompt = actionConfig . OneClickUIPrompt
}
if len ( actionConfig . OneClickUITitle ) > 0 {
msgTitle = actionConfig . OneClickUITitle
}
if len ( actionConfig . OneClickUICss ) > 0 {
msgCss = actionConfig . OneClickUICss
}
if ! actionConfig . OneClickUIShowRequestId {
requestIdBox = ""
}
if len ( actionConfig . OneClickUIFooter ) > 0 {
msgFooter = actionConfig . OneClickUIFooter
}
}
var captchaId = stringutil . Md5 ( req . WAFRemoteIP ( ) + "@" + stringutil . Rand ( 32 ) )
var nonce = rands . Int64 ( )
if ! ttlcache . SharedInt64Cache . Write ( "WAF_CAPTCHA:" + captchaId , nonce , fasttime . Now ( ) . Unix ( ) + 600 ) {
return
}
var body = ` < form method = "POST" id = "ui-form" >
2023-11-28 18:07:27 +08:00
< input type = "hidden" name = "` + captchaIdName + `" value = "` + captchaId + `" / >
2023-11-15 15:10:25 +08:00
< div class = "ui-input" >
< div class = "ui-checkbox" id = "checkbox" > < / div >
< p class = "ui-prompt" > ` + msgPrompt + ` < / p >
< / div >
< / form >
` + requestIdBox + `
` + msgFooter
// Body
if actionConfig . OneClickUIIsOn {
if len ( actionConfig . OneClickUIBody ) > 0 {
var index = strings . Index ( actionConfig . OneClickUIBody , "${body}" )
if index < 0 {
body = actionConfig . OneClickUIBody + body
} else {
body = actionConfig . OneClickUIBody [ : index ] + body + actionConfig . OneClickUIBody [ index + 7 : ] // 7是"${body}"的长度
}
}
}
var msgHTML = ` < ! DOCTYPE html >
< html >
< head >
< title > ` + msgTitle + ` < / title >
< meta name = "viewport" content = "width=device-width, initial-scale=1, user-scalable=0" >
< meta charset = "UTF-8" / >
< script type = "text/javascript" >
window . addEventListener ( "load" , function ( ) { var t = document . getElementById ( "checkbox" ) , n = ! 1 ; t . addEventListener ( "click" , function ( ) { var e ; t . className = "ui-checkbox checked" , n || ( n = ! 0 , ( e = document . createElement ( "input" ) ) . setAttribute ( "name" , "nonce" ) , e . setAttribute ( "type" , "hidden" ) , e . setAttribute ( "value" , "` + types.String(nonce) + `" ) , document . getElementById ( "ui-form" ) . appendChild ( e ) , document . getElementById ( "ui-form" ) . submit ( ) ) } ) } ) ;
< / script >
< style type = "text/css" >
2023-11-16 08:57:20 +08:00
form { max - width : 20 em ; margin : 0 auto ; text - align : center ; }
2023-11-15 15:10:25 +08:00
. ui - input { position : relative ; padding - top : 1 em ; height : 2.2 em ; background : # eee ; }
. ui - checkbox { width : 16 px ; height : 16 px ; border : 1 px # 999 solid ; float : left ; margin - left : 1 em ; cursor : pointer ; }
. ui - checkbox . checked { background : # 276 AC6 ; }
. ui - prompt { float : left ; margin : 0 ; margin - left : 0.5 em ; padding : 0 ; line - height : 1.2 ; }
address { margin - top : 1 em ; padding - top : 0.5 em ; border - top : 1 px # ccc solid ; text - align : center ; clear : both ; }
` + msgCss + `
< / style >
< / head >
< body > ` + body + `
< / body >
< / html > `
req . ProcessResponseHeaders ( writer . Header ( ) , http . StatusOK )
writer . Header ( ) . Set ( "Content-Type" , "text/html; charset=utf-8" )
writer . Header ( ) . Set ( "Content-Length" , types . String ( len ( msgHTML ) ) )
writer . WriteHeader ( http . StatusOK )
_ , _ = writer . Write ( [ ] byte ( msgHTML ) )
}
func ( this * CaptchaValidator ) validateOneClickForm ( actionConfig * CaptchaAction , policyId int64 , groupId int64 , setId int64 , originURL string , req requests . Request , writer http . ResponseWriter ) ( allow bool ) {
2023-11-28 18:07:27 +08:00
var captchaId = req . WAFRaw ( ) . FormValue ( captchaIdName )
2023-11-15 15:10:25 +08:00
var nonce = req . WAFRaw ( ) . FormValue ( "nonce" )
if len ( captchaId ) > 0 {
var key = "WAF_CAPTCHA:" + captchaId
var cacheItem = ttlcache . SharedInt64Cache . Read ( key )
ttlcache . SharedInt64Cache . Delete ( key )
if cacheItem != nil {
// 清除计数
CaptchaDeleteCacheKey ( req )
if cacheItem . Value == types . Int64 ( nonce ) {
var life = CaptchaSeconds
if actionConfig . Life > 0 {
life = types . Int ( actionConfig . Life )
}
// 加入到白名单
2023-11-16 08:44:07 +08:00
SharedIPWhiteList . RecordIP ( wafutils . ComposeIPType ( setId , req ) , actionConfig . Scope , req . WAFServerId ( ) , req . WAFRemoteIP ( ) , time . Now ( ) . Unix ( ) + int64 ( life ) , policyId , false , groupId , setId , "" )
2023-11-15 15:10:25 +08:00
req . ProcessResponseHeaders ( writer . Header ( ) , http . StatusSeeOther )
http . Redirect ( writer , req . WAFRaw ( ) , originURL , http . StatusSeeOther )
return false
}
} else {
// 增加计数
if ! CaptchaIncreaseFails ( req , actionConfig , policyId , groupId , setId , CaptchaPageCodeSubmit ) {
return false
}
req . ProcessResponseHeaders ( writer . Header ( ) , http . StatusSeeOther )
http . Redirect ( writer , req . WAFRaw ( ) , req . WAFRaw ( ) . URL . String ( ) , http . StatusSeeOther )
}
}
return true
}
func ( this * CaptchaValidator ) showSlideForm ( actionConfig * CaptchaAction , req requests . Request , writer http . ResponseWriter ) {
var lang = actionConfig . Lang
if len ( lang ) == 0 {
var acceptLanguage = req . WAFRaw ( ) . Header . Get ( "Accept-Language" )
if len ( acceptLanguage ) > 0 {
langIndex := strings . Index ( acceptLanguage , "," )
if langIndex > 0 {
lang = acceptLanguage [ : langIndex ]
}
}
}
if len ( lang ) == 0 {
lang = "en-US"
}
var msgTitle string
var msgPrompt string
var msgRequestId string
switch lang {
case "zh-CN" :
msgTitle = "身份验证"
msgPrompt = "滑动上面方块到右侧解锁"
msgRequestId = "请求ID"
case "zh-TW" :
msgTitle = "身份驗證"
msgPrompt = "滑動上面方塊到右側解鎖"
msgRequestId = "請求ID"
default :
msgTitle = "Verify Yourself"
msgPrompt = "Slide to Unlock"
msgRequestId = "Request ID"
}
var msgCss = ""
var requestIdBox = ` <address> ` + msgRequestId + ` : ` + req . Format ( "${requestId}" ) + ` </address> `
var msgFooter = ""
// 默认设置
if actionConfig . OneClickUIIsOn {
if len ( actionConfig . OneClickUIPrompt ) > 0 {
msgPrompt = actionConfig . OneClickUIPrompt
}
if len ( actionConfig . OneClickUITitle ) > 0 {
msgTitle = actionConfig . OneClickUITitle
}
if len ( actionConfig . OneClickUICss ) > 0 {
msgCss = actionConfig . OneClickUICss
}
if ! actionConfig . OneClickUIShowRequestId {
requestIdBox = ""
}
if len ( actionConfig . OneClickUIFooter ) > 0 {
msgFooter = actionConfig . OneClickUIFooter
}
}
var captchaId = stringutil . Md5 ( req . WAFRemoteIP ( ) + "@" + stringutil . Rand ( 32 ) )
var nonce = rands . Int64 ( )
if ! ttlcache . SharedInt64Cache . Write ( "WAF_CAPTCHA:" + captchaId , nonce , fasttime . Now ( ) . Unix ( ) + 600 ) {
return
}
var body = ` < form method = "POST" id = "ui-form" >
2023-11-28 18:07:27 +08:00
< input type = "hidden" name = "` + captchaIdName + `" value = "` + captchaId + `" / >
2023-11-15 15:10:25 +08:00
< div class = "ui-input" id = "input" >
< div class = "ui-progress-bar" id = "progress-bar" > < / div >
< div class = "ui-handler" id = "handler" > < / div >
< div class = "ui-handler-placeholder" id = "handler-placeholder" > < / div >
< img alt = "" src = "data:image/jpeg;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAACHklEQVR4nO3cu4oUQRgF4EI3UhDXRNRQUFnBC97XwIcRfQf3ZfYdTAxMFMwMzUw18q6JCOpaxayBTItdDd3V9v99cOKt5pyZnWlmJiUAAAAAAAAAAAAAAAAAAAAAgOgO5pzLOdn6IEzvXs6bnL39PM+53PRETKaUv9eRLzm3G56LCWykPx/5XSPYbnY6Rncm/b383/mcc6vVARnXqfTvARjBwpUXfH1HcLPRGRlRebVf/tf3GcGnZASLVF7olUe4Z4LAakZQnglutDkmYzICjAAjINWP4HqbYzImI8AIqBvBx2QEi1Q7gmttjsmYjAAjoH4EV9sckzEZAUaAEZDqRvAh50qbYzKm5iMoH3F+kPNi/w/I9PmW+g2g5G3Ohc4mBziQ87jij8s8Ur6TcLqjz2r3Z3AxMixP1uus93AGFyLD8jPn6HqldR7N4EJk+AA21yutszODC5FhedbRZ7UjOS9ncDFSl/LO4WxHn4Mcy9nN+TqDC5N+5Y9yQ6j80sWmTJ47qe5GkFvCC3IxrW7s9CnfR8YWRvmBKT8w5Qem/MAuJeWHVcp/l5QfkvIDU35gyg+spnzfDF4Y5QdWfjtQ+UEpPzDlB1bKf5+UH5LyAzufVu/f+77P90mehSmfylV+UIdzfiRP+2GdSB754b1Oyg/tblJ+eGUEr9Kq+O85T3O2mp6IJo7nHGp9CAAAAAAAAAAAAAAAAAAAAADgf/ILsUB70laSdmQAAAAASUVORK5CYII=" / >
< / div >
< p class = "ui-prompt" > ` + msgPrompt + ` < / p >
< / form >
` + requestIdBox + `
` + msgFooter
// Body
if actionConfig . OneClickUIIsOn {
if len ( actionConfig . OneClickUIBody ) > 0 {
var index = strings . Index ( actionConfig . OneClickUIBody , "${body}" )
if index < 0 {
body = actionConfig . OneClickUIBody + body
} else {
body = actionConfig . OneClickUIBody [ : index ] + body + actionConfig . OneClickUIBody [ index + 7 : ] // 7是"${body}"的长度
}
}
}
var msgHTML = ` < ! DOCTYPE html >
< html >
< head >
< title > ` + msgTitle + ` < / title >
< meta name = "viewport" content = "width=device-width, initial-scale=1, user-scalable=0" >
< meta charset = "UTF-8" / >
< script type = "text/javascript" >
window . addEventListener ( "load" , function ( ) { var n = document . getElementById ( "input" ) , s = document . getElementById ( "handler" ) , o = document . getElementById ( "progress-bar" ) , d = ! 1 , u = 0 , t = n . offsetLeft , c = s . offsetLeft , i = n . offsetWidth - s . offsetWidth - s . offsetLeft , f = ! 1 ; function e ( e ) { e . preventDefault ( ) , d = ! 0 , u = null != e . touches && 0 < e . touches . length ? e . touches [ 0 ] . clientX - t : e . offsetX } function l ( e ) { var t ; d && ( t = e . x , null != e . touches && 0 < e . touches . length && ( t = e . touches [ 0 ] . clientX ) , ( t = t - n . offsetLeft - u ) < c ? t = c : i < t && ( t = i ) , s . style . cssText = "margin-left: " + t + "px" , 0 < t && ( o . style . cssText = "width: " + ( t + s . offsetWidth + 4 ) + "px" ) ) } function r ( e ) { var t ; d = d && ! 1 , s . offsetLeft < i - 4 ? ( s . style . cssText = "margin-left: " + c + "px" , n . style . cssText = "background: #eee" , o . style . cssText = "width: 0px" ) : ( s . style . cssText = "margin-left: " + i + "px" , n . style . cssText = "background: #a5dc86" , f || ( f = ! 0 , ( t = document . createElement ( "input" ) ) . setAttribute ( "name" , "nonce" ) , t . setAttribute ( "type" , "hidden" ) , t . setAttribute ( "value" , "` + types.String(nonce) + `" ) , document . getElementById ( "ui-form" ) . appendChild ( t ) , document . getElementById ( "ui-form" ) . submit ( ) ) ) } void 0 != = document . ontouchstart ? ( s . addEventListener ( "touchstart" , e ) , document . addEventListener ( "touchmove" , l ) , document . addEventListener ( "touchend" , r ) ) : ( s . addEventListener ( "mousedown" , e ) , window . addEventListener ( "mousemove" , l ) , window . addEventListener ( "mouseup" , r ) ) } ) ;
< / script >
< style type = "text/css" >
2023-11-16 08:57:20 +08:00
form { max - width : 20 em ; margin : 5 em auto ; text - align : center ; }
2023-11-15 15:10:25 +08:00
. ui - input {
height : 4 em ;
background : # eee ;
border : 1 px # ccc solid ;
text - align : left ;
position : relative ;
}
. ui - input . ui - progress - bar {
background : # a5dc86 ;
position : absolute ;
top : 0 ;
left : 0 ;
bottom : 0 ;
}
. ui - handler , . ui - handler - placeholder {
width : 3.6 em ;
height : 3.6 em ;
margin : 0.2 em ;
background : # 276 AC6 ;
border - radius : 0.6 em ;
display : inline - block ;
cursor : pointer ;
z - index : 10 ;
position : relative ;
}
. ui - handler - placeholder {
background : none ;
border : 1 px # ccc dashed ;
position : absolute ;
right : - 1 px ;
top : 0 ;
bottom : 0 ;
}
. ui - input img {
position : absolute ;
top : 0 ;
bottom : 0 ;
height : 100 % ;
left : 8 em ;
opacity : 5 % ;
}
. ui - prompt { float : left ; margin : 1 em 0 ; padding : 0 ; line - height : 1.2 ; }
address { margin - top : 1 em ; padding - top : 0.5 em ; border - top : 1 px # ccc solid ; text - align : center ; clear : both ; }
` + msgCss + `
< / style >
< / head >
< body > ` + body + `
< / body >
< / html > `
req . ProcessResponseHeaders ( writer . Header ( ) , http . StatusOK )
writer . Header ( ) . Set ( "Content-Type" , "text/html; charset=utf-8" )
writer . Header ( ) . Set ( "Content-Length" , types . String ( len ( msgHTML ) ) )
writer . WriteHeader ( http . StatusOK )
_ , _ = writer . Write ( [ ] byte ( msgHTML ) )
}
func ( this * CaptchaValidator ) validateSlideForm ( actionConfig * CaptchaAction , policyId int64 , groupId int64 , setId int64 , originURL string , req requests . Request , writer http . ResponseWriter ) ( allow bool ) {
2023-11-28 18:07:27 +08:00
var captchaId = req . WAFRaw ( ) . FormValue ( captchaIdName )
2023-11-15 15:10:25 +08:00
var nonce = req . WAFRaw ( ) . FormValue ( "nonce" )
if len ( captchaId ) > 0 {
var key = "WAF_CAPTCHA:" + captchaId
var cacheItem = ttlcache . SharedInt64Cache . Read ( key )
ttlcache . SharedInt64Cache . Delete ( key )
if cacheItem != nil {
// 清除计数
CaptchaDeleteCacheKey ( req )
if cacheItem . Value == types . Int64 ( nonce ) {
var life = CaptchaSeconds
if actionConfig . Life > 0 {
life = types . Int ( actionConfig . Life )
}
// 加入到白名单
2023-11-16 08:44:07 +08:00
SharedIPWhiteList . RecordIP ( wafutils . ComposeIPType ( setId , req ) , actionConfig . Scope , req . WAFServerId ( ) , req . WAFRemoteIP ( ) , time . Now ( ) . Unix ( ) + int64 ( life ) , policyId , false , groupId , setId , "" )
2023-11-15 15:10:25 +08:00
req . ProcessResponseHeaders ( writer . Header ( ) , http . StatusSeeOther )
http . Redirect ( writer , req . WAFRaw ( ) , originURL , http . StatusSeeOther )
return false
}
} else {
// 增加计数
if ! CaptchaIncreaseFails ( req , actionConfig , policyId , groupId , setId , CaptchaPageCodeSubmit ) {
return false
}
req . ProcessResponseHeaders ( writer . Header ( ) , http . StatusSeeOther )
http . Redirect ( writer , req . WAFRaw ( ) , req . WAFRaw ( ) . URL . String ( ) , http . StatusSeeOther )
}
}
return true
}
2023-11-29 17:00:06 +08:00
func ( this * CaptchaValidator ) validateGeeTestForm ( actionConfig * CaptchaAction , policyId int64 , groupId int64 , setId int64 , originURL string , req requests . Request , writer http . ResponseWriter ) ( allow bool ) {
var geeTestConfig = actionConfig . GeeTestConfig
if geeTestConfig == nil || ! geeTestConfig . IsOn {
return
}
defer func ( ) {
if allow {
// 清除计数
CaptchaDeleteCacheKey ( req )
var life = CaptchaSeconds
if actionConfig . Life > 0 {
life = types . Int ( actionConfig . Life )
}
// 加入到白名单
SharedIPWhiteList . RecordIP ( wafutils . ComposeIPType ( setId , req ) , actionConfig . Scope , req . WAFServerId ( ) , req . WAFRemoteIP ( ) , time . Now ( ) . Unix ( ) + int64 ( life ) , policyId , false , groupId , setId , "" )
writer . WriteHeader ( http . StatusOK )
} else {
// 增加计数
CaptchaIncreaseFails ( req , actionConfig , policyId , groupId , setId , CaptchaPageCodeSubmit )
writer . WriteHeader ( http . StatusBadRequest )
}
} ( )
if req . WAFRaw ( ) . Body == nil || req . WAFRaw ( ) . ContentLength <= 0 || req . WAFRaw ( ) . ContentLength > 2048 {
return false
}
data , err := io . ReadAll ( req . WAFRaw ( ) . Body )
if err != nil {
return false
}
var m = maps . Map { }
err = json . Unmarshal ( data , & m )
if err != nil {
return false
}
const GeeTestAPIServer = "https://gcaptcha4.geetest.com"
var GeeTestAPIURL = GeeTestAPIServer + "/validate" + "?captcha_id=" + geeTestConfig . CaptchaId
var lotNumber = m . GetString ( "lot_number" )
var hash = hmac . New ( sha256 . New , [ ] byte ( geeTestConfig . CaptchaKey ) )
hash . Write ( [ ] byte ( lotNumber ) )
var signToken = hex . EncodeToString ( hash . Sum ( nil ) )
var query = url . Values {
"lot_number" : [ ] string { lotNumber } ,
"captcha_output" : [ ] string { m . GetString ( "captcha_output" ) } ,
"pass_token" : [ ] string { m . GetString ( "pass_token" ) } ,
"gen_time" : [ ] string { m . GetString ( "gen_time" ) } ,
"sign_token" : [ ] string { signToken } ,
}
resp , err := geeTestHTTPClient . PostForm ( GeeTestAPIURL , query )
defer func ( ) {
if resp != nil && resp . Body != nil {
_ = resp . Body . Close ( )
}
} ( )
if err != nil || resp . StatusCode != http . StatusOK {
// 放行,避免阻塞业务
allow = true
return
}
data , err = io . ReadAll ( resp . Body )
if err != nil {
// 放行,避免阻塞业务
allow = true
return
}
var resultMap = maps . Map { }
err = json . Unmarshal ( data , & resultMap )
if err != nil {
// 放行,避免阻塞业务
allow = true
return
}
allow = resultMap . GetString ( "result" ) == "success"
return allow
}
func ( this * CaptchaValidator ) showGeeTestForm ( actionConfig * CaptchaAction , req requests . Request , writer http . ResponseWriter , originURL string ) {
var geeTestConfig = actionConfig . GeeTestConfig
if geeTestConfig == nil || ! geeTestConfig . IsOn {
return
}
var lang = actionConfig . Lang
if len ( lang ) == 0 {
var acceptLanguage = req . WAFRaw ( ) . Header . Get ( "Accept-Language" )
if len ( acceptLanguage ) > 0 {
langIndex := strings . Index ( acceptLanguage , "," )
if langIndex > 0 {
lang = acceptLanguage [ : langIndex ]
}
}
}
if len ( lang ) == 0 {
lang = "en-US"
}
var msgTitle string
switch lang {
case "zh-CN" :
msgTitle = "身份验证"
case "zh-TW" :
msgTitle = "身份驗證"
default :
msgTitle = "Verify Yourself"
}
var msgHTML = ` < ! DOCTYPE html >
< html >
< head >
< title > ` + msgTitle + ` < / title >
< meta name = "viewport" content = "width=device-width, initial-scale=1, user-scalable=0" >
< meta charset = "UTF-8" / >
< script type = "text/javascript" src = "//static.geetest.com/v4/gt4.js" > < / script >
< script type = "text/javascript" > ` + axiosJavascript + ` < / script >
< / head >
< body >
< script >
var originURL = ` + strconv.Quote(originURL) + ` ;
initGeetest4 ( {
captchaId : ` + strconv.Quote(geeTestConfig.CaptchaId) + ` ,
product : "bind" ,
} , function ( gt ) {
gt . onSuccess ( function ( ) {
var result = gt . getValidate ( ) ;
axios . post ( "` + req.WAFRaw().URL.String() + " & " + captchaIdName + `=none" , result , {
"Content-Type" : "application/json"
} ) . then ( function ( resp ) {
if ( resp . status == 200 ) {
window . location = originURL ;
}
} ) ;
} )
gt . showCaptcha ( ) ;
} ) ;
< / script >
< / body >
< / html > `
req . ProcessResponseHeaders ( writer . Header ( ) , http . StatusOK )
writer . Header ( ) . Set ( "Content-Type" , "text/html; charset=utf-8" )
this . compressWrite ( req . WAFRaw ( ) , writer , [ ] byte ( msgHTML ) )
}
func ( this * CaptchaValidator ) compressWrite ( req * http . Request , writer http . ResponseWriter , htmlContent [ ] byte ) {
var acceptEncoding = req . Header . Get ( "Accept-Encoding" )
if strings . Contains ( acceptEncoding , "gzip" ) {
this . compressGzip ( writer , htmlContent )
} else if strings . Contains ( acceptEncoding , "br" ) {
this . compressBR ( writer , htmlContent )
} else {
writer . Header ( ) . Set ( "Content-Length" , types . String ( len ( htmlContent ) ) )
writer . WriteHeader ( http . StatusOK )
_ , _ = writer . Write ( htmlContent )
}
}
func ( this * CaptchaValidator ) compressBR ( writer http . ResponseWriter , htmlContent [ ] byte ) {
compressWriter , err := compressions . NewBrotliWriter ( writer , 0 )
if err != nil {
writer . Header ( ) . Set ( "Content-Length" , types . String ( len ( htmlContent ) ) )
writer . WriteHeader ( http . StatusOK )
_ , _ = writer . Write ( htmlContent )
return
}
writer . Header ( ) . Set ( "Content-Encoding" , "br" )
writer . WriteHeader ( http . StatusOK )
_ , _ = compressWriter . Write ( htmlContent )
_ = compressWriter . Close ( )
}
func ( this * CaptchaValidator ) compressGzip ( writer http . ResponseWriter , htmlContent [ ] byte ) {
compressWriter , err := compressions . NewGzipWriter ( writer , 0 )
if err != nil {
writer . Header ( ) . Set ( "Content-Length" , types . String ( len ( htmlContent ) ) )
writer . WriteHeader ( http . StatusOK )
_ , _ = writer . Write ( htmlContent )
return
}
writer . Header ( ) . Set ( "Content-Encoding" , "gzip" )
writer . WriteHeader ( http . StatusOK )
_ , _ = compressWriter . Write ( htmlContent )
_ = compressWriter . Close ( )
}
const axiosJavascript = "!function(e,t){\"object\"==typeof exports&&\"undefined\"!=typeof module?module.exports=t():\"function\"==typeof define&&define.amd?define(t):(e=\"undefined\"!=typeof globalThis?globalThis:e||self).axios=t()}(this,(function(){\"use strict\";function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function t(t){for(var n=1;n<arguments.length;n++){var r=null!=arguments[n]?arguments[n]:{};n%2?e(Object(r),!0).forEach((function(e){a(t,e,r[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(r)):e(Object(r)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(r,e))}))}return t}function n(e){return n=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e},n(e)}function r(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function o(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function i(e,t,n){return t&&o(e.prototype,t),n&&o(e,n),Object.defineProperty(e,\"prototype\",{writable:!1}),e}function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function s(e,t){return c(e)||function(e,t){var n=null==e?null:\"undefined\"!=typeof Symbol&&e[Symbol.iterator]||e[\"@@iterator\"];if(null==n)return;var r,o,i=[],a=!0,s=!1;try{for(n=n.call(e);!(a=(r=n.next()).done)&&(i.push(r.value),!t||i.length!==t);a=!0);}catch(e){s=!0,o=e}finally{try{a||null==n.return||n.return()}finally{if(s)throw o}}return i}(e,t)||l(e,t)||p()}function u(e){return function(e){if(Array.isArray(e))return d(e)}(e)||f(e)||l(e)||function(){throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}()}function c(e){if(Array.isArray(e))return e}function f(e){if(\"undefined\"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e[\"@@iterator\"])return Array.from(e)}function l(e,t){if(e){if(\"string\"==typeof e)return d(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return\"Object\"===n&&e.constructor&&(n=e.constructor.name),\"Map\"===n||\"Set\"===n?Array.from(e):\"Arguments\"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?d(e,t):void 0}}function d(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}function p(){throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}function h(e,t){return function(){return e.apply(t,arguments)}}var m,y=Object.prototype.toString,v=Object.getPrototypeOf,b=(m=Object.create(null),function(e){var t=y.call(e);return m[t]||(m[t]=t.slice(8,-1).toLowerCase())}),g=function(e){return e=e.toLowerCase(),function(t){return b(t)===e}},w=function(e){return function(t){return n(t)===e}},O=Array.isArray,E=w(\"undefined\");var S=g(\"ArrayBuffer\");var R=w(\"string\"),A=w(\"function\"),j=w(\"number\"),T=function(e){return null!==e&&\"object\"===n(e)},P=function(e){if(\"object\"!==b(e))return!1;var t=v(e);return!(null!==t&&t!==Object.prototype&&null!==Object.getPrototypeOf(t)||Symbol.toStringTag in e||Symbol.iterator in e)},N=g(\"Date\"),x=g(\"File\"),C=g(\"Blob\"),k=g(\"FileList\"),_=g(\"URLSearchParams\");function F(e,t){var r,o,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},a=i.allOwnKeys,s=void 0!==a&&a;if(null!=e)if(\"object\" != = n ( e ) && ( e = [ e ] ) , O ( e ) ) for ( r = 0 , o = e . length ; r < o ; r ++ ) t . call ( null , e [ r ] , r , e ) ; else { var u , c = s ? Object . getOwnPropertyNames ( e ) : Object . keys ( e ) , f = c . length ; for ( r = 0 ; r < f ; r ++ ) u = c [ r ] , t . call ( null , e [ u ] , u , e ) } } function U ( e , t ) { t = t . toLowerCase ( ) ; for ( var n , r = Object . keys ( e ) , o = r .
var geeTestHTTPClient = & http . Client { Timeout : 5 * time . Second }