This commit is contained in:
unknown
2026-02-04 20:27:13 +08:00
commit 3b042d1dad
9410 changed files with 1488147 additions and 0 deletions

View File

@@ -0,0 +1,194 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"regexp"
)
// CCCheckpoint ${cc.arg}
// TODO implement more traffic rules
type CCCheckpoint struct {
Checkpoint
}
func (this *CCCheckpoint) Init() {
}
func (this *CCCheckpoint) Start() {
}
func (this *CCCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = 0
periodString, ok := options["period"]
if !ok {
return
}
var period = types.Int(periodString)
if period < 1 {
return
}
if period > 7*86400 {
period = 7 * 86400
}
var userType = types.String(options["userType"])
var userField = types.String(options["userField"])
var userIndex = types.Int(options["userIndex"])
if param == "requests" { // requests
var key = ""
switch userType {
case "ip":
key = req.WAFRemoteIP()
case "cookie":
if len(userField) == 0 {
key = req.WAFRemoteIP()
} else {
cookie, _ := req.WAFRaw().Cookie(userField)
if cookie != nil {
v := cookie.Value
if userIndex > 0 && len(v) > userIndex {
v = v[userIndex:]
}
key = "USER@" + userType + "@" + userField + "@" + v
}
}
case "get":
if len(userField) == 0 {
key = req.WAFRemoteIP()
} else {
v := req.WAFRaw().URL.Query().Get(userField)
if userIndex > 0 && len(v) > userIndex {
v = v[userIndex:]
}
key = "USER@" + userType + "@" + userField + "@" + v
}
case "post":
if len(userField) == 0 {
key = req.WAFRemoteIP()
} else {
v := req.WAFRaw().PostFormValue(userField)
if userIndex > 0 && len(v) > userIndex {
v = v[userIndex:]
}
key = "USER@" + userType + "@" + userField + "@" + v
}
case "header":
if len(userField) == 0 {
key = req.WAFRemoteIP()
} else {
v := req.WAFRaw().Header.Get(userField)
if userIndex > 0 && len(v) > userIndex {
v = v[userIndex:]
}
key = "USER@" + userType + "@" + userField + "@" + v
}
default:
key = req.WAFRemoteIP()
}
if len(key) == 0 {
key = req.WAFRemoteIP()
}
value = counters.SharedCounter.IncreaseKey(types.String(ruleId)+"@WAF_CC@"+key, types.Int(period))
}
return
}
func (this *CCCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *CCCheckpoint) ParamOptions() *ParamOptions {
option := NewParamOptions()
option.AddParam("请求数", "requests")
return option
}
func (this *CCCheckpoint) Options() []OptionInterface {
options := []OptionInterface{}
// period
{
option := NewFieldOption("统计周期", "period")
option.Value = "60"
option.RightLabel = "秒"
option.Size = 8
option.MaxLength = 8
option.Validate = func(value string) (ok bool, message string) {
if regexp.MustCompile(`^\d+$`).MatchString(value) {
ok = true
return
}
message = "周期需要是一个整数数字"
return
}
options = append(options, option)
}
// type
{
option := NewOptionsOption("用户识别读取来源", "userType")
option.Size = 10
option.SetOptions([]maps.Map{
{
"name": "IP",
"value": "ip",
},
{
"name": "Cookie",
"value": "cookie",
},
{
"name": "URL参数",
"value": "get",
},
{
"name": "POST参数",
"value": "post",
},
{
"name": "HTTP Header",
"value": "header",
},
})
options = append(options, option)
}
// user field
{
option := NewFieldOption("用户识别字段", "userField")
option.Comment = "识别用户的唯一性字段在用户读取来源不是IP时使用"
options = append(options, option)
}
// user value index
{
option := NewFieldOption("字段读取位置", "userIndex")
option.Size = 5
option.MaxLength = 5
option.Comment = "读取用户识别字段的位置从0开始比如user12345的数字ID 12345的位置就是5在用户读取来源不是IP时使用"
options = append(options, option)
}
return options
}
func (this *CCCheckpoint) Stop() {
}
func (this *CCCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheDisabled
}

View File

@@ -0,0 +1,99 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package checkpoints
import (
"fmt"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
wafutils "github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"path/filepath"
"strings"
)
// CC2Checkpoint 新的CC
type CC2Checkpoint struct {
Checkpoint
}
func (this *CC2Checkpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var keys = options.GetSlice("keys")
var keyValues = []string{}
var hasRemoteAddr = false
for _, key := range keys {
if key == "${remoteAddr}" || key == "${rawRemoteAddr}" {
hasRemoteAddr = true
}
keyValues = append(keyValues, req.Format(types.String(key)))
}
if len(keyValues) == 0 {
return
}
var period = options.GetInt("period")
if period <= 0 {
period = 60
} else if period > 7*86400 {
period = 7 * 86400
}
/**var threshold = options.GetInt64("threshold")
if threshold <= 0 {
threshold = 1000
}**/
if options.GetBool("ignoreCommonFiles") {
var rawReq = req.WAFRaw()
if len(rawReq.Referer()) > 0 {
var ext = filepath.Ext(rawReq.URL.Path)
if len(ext) > 0 && utils.IsCommonFileExtension(ext) {
return
}
}
}
var ccKey = "WAF-CC-" + types.String(ruleId) + "-" + strings.Join(keyValues, "@")
var ccValue = counters.SharedCounter.IncreaseKey(ccKey, period)
value = ccValue
// 基于指纹统计
var enableFingerprint = true
if options.Has("enableFingerprint") && !options.GetBool("enableFingerprint") {
enableFingerprint = false
}
if hasRemoteAddr && enableFingerprint {
var fingerprint = req.WAFFingerprint()
if len(fingerprint) > 0 {
var fpKeyValues = []string{}
for _, key := range keys {
if key == "${remoteAddr}" || key == "${rawRemoteAddr}" {
fpKeyValues = append(fpKeyValues, fmt.Sprintf("%x", fingerprint))
continue
}
fpKeyValues = append(fpKeyValues, req.Format(types.String(key)))
}
var fpCCKey = "WAF-CC-" + types.String(ruleId) + "-" + strings.Join(fpKeyValues, "@")
var fpValue = counters.SharedCounter.IncreaseKey(fpCCKey, period)
if fpValue > ccValue {
value = fpValue
}
}
}
return
}
func (this *CC2Checkpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *CC2Checkpoint) CacheLife() wafutils.CacheLife {
return wafutils.CacheDisabled
}

View File

@@ -0,0 +1,43 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/maps"
"net/http"
"testing"
)
func TestCCCheckpoint_RequestValue(t *testing.T) {
raw, err := http.NewRequest(http.MethodGet, "http://teaos.cn/", nil)
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(raw)
req.WAFRaw().RemoteAddr = "127.0.0.1"
checkpoint := new(CCCheckpoint)
checkpoint.Init()
checkpoint.Start()
options := maps.Map{
"period": "5",
}
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
req.WAFRaw().RemoteAddr = "127.0.0.2"
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
req.WAFRaw().RemoteAddr = "127.0.0.1"
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
req.WAFRaw().RemoteAddr = "127.0.0.2"
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
req.WAFRaw().RemoteAddr = "127.0.0.2"
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
req.WAFRaw().RemoteAddr = "127.0.0.2"
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
}

View File

@@ -0,0 +1,59 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
)
type Checkpoint struct {
priority int
}
func (this *Checkpoint) Init() {
}
func (this *Checkpoint) IsRequest() bool {
return true
}
func (this *Checkpoint) IsComposed() bool {
return false
}
func (this *Checkpoint) ParamOptions() *ParamOptions {
return nil
}
func (this *Checkpoint) Options() []OptionInterface {
return nil
}
func (this *Checkpoint) Start() {
}
func (this *Checkpoint) Stop() {
}
func (this *Checkpoint) SetPriority(priority int) {
this.priority = priority
}
func (this *Checkpoint) Priority() int {
return this.priority
}
func (this *Checkpoint) RequestBodyIsEmpty(req requests.Request) bool {
if req.WAFRaw().ContentLength == 0 {
return true
}
var method = req.WAFRaw().Method
if method == http.MethodHead || method == http.MethodGet {
return true
}
return false
}

View File

@@ -0,0 +1,11 @@
package checkpoints
// CheckpointDefinition check point definition
type CheckpointDefinition struct {
Name string
Description string
Prefix string
HasParams bool // has sub params
Instance CheckpointInterface
Priority int
}

View File

@@ -0,0 +1,46 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
// CheckpointInterface Check Point
type CheckpointInterface interface {
// Init initialize
Init()
// IsRequest is request?
IsRequest() bool
// IsComposed is composed?
IsComposed() bool
// RequestValue get request value
RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error)
// ResponseValue get response value
ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error)
// ParamOptions param option list
ParamOptions() *ParamOptions
// Options options
Options() []OptionInterface
// Start start
Start()
// Stop stop
Stop()
// SetPriority set priority
SetPriority(priority int)
// Priority get priority
Priority() int
// CacheLife regexp cache life
CacheLife() utils.CacheLife
}

View File

@@ -0,0 +1,5 @@
package checkpoints
type OptionInterface interface {
Type() string
}

View File

@@ -0,0 +1,26 @@
package checkpoints
// attach option
type FieldOption struct {
Name string
Code string
Value string // default value
IsRequired bool
Size int
Comment string
Placeholder string
RightLabel string
MaxLength int
Validate func(value string) (ok bool, message string)
}
func NewFieldOption(name string, code string) *FieldOption {
return &FieldOption{
Name: name,
Code: code,
}
}
func (this *FieldOption) Type() string {
return "field"
}

View File

@@ -0,0 +1,30 @@
package checkpoints
import "github.com/iwind/TeaGo/maps"
type OptionsOption struct {
Name string
Code string
Value string // default value
IsRequired bool
Size int
Comment string
RightLabel string
Validate func(value string) (ok bool, message string)
Options []maps.Map
}
func NewOptionsOption(name string, code string) *OptionsOption {
return &OptionsOption{
Name: name,
Code: code,
}
}
func (this *OptionsOption) Type() string {
return "options"
}
func (this *OptionsOption) SetOptions(options []maps.Map) {
this.Options = options
}

View File

@@ -0,0 +1,21 @@
package checkpoints
type KeyValue struct {
Name string `json:"name"`
Value string `json:"value"`
}
type ParamOptions struct {
Options []*KeyValue `json:"options"`
}
func NewParamOptions() *ParamOptions {
return &ParamOptions{}
}
func (this *ParamOptions) AddParam(name string, value string) {
this.Options = append(this.Options, &KeyValue{
Name: name,
Value: value,
})
}

View File

@@ -0,0 +1,60 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
// RequestAllCheckpoint ${requestAll}
type RequestAllCheckpoint struct {
Checkpoint
}
func (this *RequestAllCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var valueBytes = [][]byte{}
if len(req.WAFRaw().RequestURI) > 0 {
valueBytes = append(valueBytes, []byte(req.WAFRaw().RequestURI))
} else if req.WAFRaw().URL != nil {
valueBytes = append(valueBytes, []byte(req.WAFRaw().URL.RequestURI()))
}
if this.RequestBodyIsEmpty(req) {
value = valueBytes
return
}
if req.WAFRaw().Body != nil {
var bodyData = req.WAFGetCacheBody()
hasRequestBody = true
if len(bodyData) == 0 {
data, err := req.WAFReadBody(req.WAFMaxRequestSize()) // read body
if err != nil {
return "", hasRequestBody, err, nil
}
bodyData = data
req.WAFSetCacheBody(data)
req.WAFRestoreBody(data)
}
if len(bodyData) > 0 {
valueBytes = append(valueBytes, bodyData)
}
}
value = valueBytes
return
}
func (this *RequestAllCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = ""
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestAllCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,76 @@
package checkpoints
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/types"
"io"
"net/http"
"runtime"
"strings"
"testing"
)
func TestRequestAllCheckpoint_RequestValue(t *testing.T) {
req, err := http.NewRequest(http.MethodPost, "http://teaos.cn/hello/world", bytes.NewBuffer([]byte("123456")))
if err != nil {
t.Fatal(err)
}
checkpoint := new(RequestAllCheckpoint)
v, _, sysErr, userErr := checkpoint.RequestValue(requests.NewTestRequest(req), "", nil, 1)
if sysErr != nil {
t.Fatal(sysErr)
}
if userErr != nil {
t.Fatal(userErr)
}
if v != nil {
vv, ok := v.([][]byte)
if ok {
for _, v2 := range vv {
t.Log(string(v2), ":", v2)
}
}
}
body, err := io.ReadAll(req.Body)
if err != nil {
t.Fatal(err)
}
t.Log(string(body))
}
func TestRequestAllCheckpoint_RequestValue_Max(t *testing.T) {
req, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte(strings.Repeat("123456", 10240000))))
if err != nil {
t.Fatal(err)
}
checkpoint := new(RequestBodyCheckpoint)
value, _, err, _ := checkpoint.RequestValue(requests.NewTestRequest(req), "", nil, 1)
if err != nil {
t.Fatal(err)
}
t.Log("value bytes:", len(types.String(value)))
body, err := io.ReadAll(req.Body)
if err != nil {
t.Fatal(err)
}
t.Log("raw bytes:", len(body))
}
func BenchmarkRequestAllCheckpoint_RequestValue(b *testing.B) {
runtime.GOMAXPROCS(1)
req, err := http.NewRequest(http.MethodPost, "http://teaos.cn/hello/world", bytes.NewBuffer(bytes.Repeat([]byte("HELLO"), 1024)))
if err != nil {
b.Fatal(err)
}
checkpoint := new(RequestAllCheckpoint)
for i := 0; i < b.N; i++ {
_, _, _, _ = checkpoint.RequestValue(requests.NewTestRequest(req), "", nil, 1)
}
}

View File

@@ -0,0 +1,26 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestArgCheckpoint struct {
Checkpoint
}
func (this *RequestArgCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return req.WAFRaw().URL.Query().Get(param), hasRequestBody, nil, nil
}
func (this *RequestArgCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestArgCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,21 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
"testing"
)
func TestArgParam_RequestValue(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodGet, "http://teaos.cn/?name=lu", nil)
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(rawReq)
checkpoint := new(RequestArgCheckpoint)
t.Log(checkpoint.RequestValue(req, "name", nil, 1))
t.Log(checkpoint.ResponseValue(req, nil, "name", nil, 1))
t.Log(checkpoint.RequestValue(req, "name2", nil, 1))
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestArgsCheckpoint struct {
Checkpoint
}
func (this *RequestArgsCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRaw().URL.RawQuery
return
}
func (this *RequestArgsCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestArgsCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,50 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
// RequestBodyCheckpoint ${requestBody}
type RequestBodyCheckpoint struct {
Checkpoint
}
func (this *RequestBodyCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.RequestBodyIsEmpty(req) {
value = ""
return
}
if req.WAFRaw().Body == nil {
value = ""
return
}
var bodyData = req.WAFGetCacheBody()
hasRequestBody = true
if len(bodyData) == 0 {
data, err := req.WAFReadBody(req.WAFMaxRequestSize()) // read body
if err != nil {
return "", hasRequestBody, err, nil
}
bodyData = data
req.WAFSetCacheBody(data)
req.WAFRestoreBody(data)
}
return bodyData, hasRequestBody, nil, nil
}
func (this *RequestBodyCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestBodyCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheDisabled
}

View File

@@ -0,0 +1,48 @@
package checkpoints
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/types"
"io"
"net/http"
"strings"
"testing"
)
func TestRequestBodyCheckpoint_RequestValue(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte("123456")))
if err != nil {
t.Fatal(err)
}
var req = requests.NewTestRequest(rawReq)
checkpoint := new(RequestBodyCheckpoint)
t.Log(checkpoint.RequestValue(req, "", nil, 1))
body, err := io.ReadAll(rawReq.Body)
if err != nil {
t.Fatal(err)
}
t.Log(string(body))
t.Log(string(req.WAFGetCacheBody()))
}
func TestRequestBodyCheckpoint_RequestValue_Max(t *testing.T) {
req, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte(strings.Repeat("123456", 10240000))))
if err != nil {
t.Fatal(err)
}
checkpoint := new(RequestBodyCheckpoint)
value, _, err, _ := checkpoint.RequestValue(requests.NewTestRequest(req), "", nil, 1)
if err != nil {
t.Fatal(err)
}
t.Log("value bytes:", len(types.String(value)))
body, err := io.ReadAll(req.Body)
if err != nil {
t.Fatal(err)
}
t.Log("raw bytes:", len(body))
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestCNAMECheckpoint struct {
Checkpoint
}
func (this *RequestCNAMECheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.Format("${cname}")
return
}
func (this *RequestCNAMECheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestCNAMECheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestContentTypeCheckpoint struct {
Checkpoint
}
func (this *RequestContentTypeCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRaw().Header.Get("Content-Type")
return
}
func (this *RequestContentTypeCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestContentTypeCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,33 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestCookieCheckpoint struct {
Checkpoint
}
func (this *RequestCookieCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
cookie, err := req.WAFRaw().Cookie(param)
if err != nil {
value = ""
return
}
value = cookie.Value
return
}
func (this *RequestCookieCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestCookieCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,33 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"net/url"
"strings"
)
type RequestCookiesCheckpoint struct {
Checkpoint
}
func (this *RequestCookiesCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var cookies = []string{}
for _, cookie := range req.WAFRaw().Cookies() {
cookies = append(cookies, url.QueryEscape(cookie.Name)+"="+url.QueryEscape(cookie.Value))
}
value = strings.Join(cookies, "&")
return
}
func (this *RequestCookiesCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestCookiesCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,54 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"net/url"
)
// RequestFormArgCheckpoint ${requestForm.arg}
type RequestFormArgCheckpoint struct {
Checkpoint
}
func (this *RequestFormArgCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
hasRequestBody = true
if this.RequestBodyIsEmpty(req) {
value = ""
return
}
if req.WAFRaw().Body == nil {
value = ""
return
}
var bodyData = req.WAFGetCacheBody()
if len(bodyData) == 0 {
data, err := req.WAFReadBody(req.WAFMaxRequestSize()) // read body
if err != nil {
return "", hasRequestBody, err, nil
}
bodyData = data
req.WAFSetCacheBody(data)
req.WAFRestoreBody(data)
}
// TODO improve performance
values, _ := url.ParseQuery(string(bodyData))
return values.Get(param), hasRequestBody, nil, nil
}
func (this *RequestFormArgCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestFormArgCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,32 @@
package checkpoints
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"io"
"net/http"
"net/url"
"testing"
)
func TestRequestFormArgCheckpoint_RequestValue(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte("name=lu&age=20&encoded="+url.QueryEscape("<strong>ENCODED STRING</strong>"))))
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(rawReq)
req.WAFRaw().Header.Set("Content-Type", "application/x-www-form-urlencoded")
checkpoint := new(RequestFormArgCheckpoint)
t.Log(checkpoint.RequestValue(req, "name", nil, 1))
t.Log(checkpoint.RequestValue(req, "age", nil, 1))
t.Log(checkpoint.RequestValue(req, "Hello", nil, 1))
t.Log(checkpoint.RequestValue(req, "encoded", nil, 1))
body, err := io.ReadAll(req.WAFRaw().Body)
if err != nil {
t.Fatal(err)
}
t.Log(string(body))
}

View File

@@ -0,0 +1,45 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
)
type RequestGeneralHeaderLengthCheckpoint struct {
Checkpoint
}
func (this *RequestGeneralHeaderLengthCheckpoint) IsComposed() bool {
return true
}
func (this *RequestGeneralHeaderLengthCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = false
var headers = options.GetSlice("headers")
if len(headers) == 0 {
return
}
var length = options.GetInt("length")
for _, header := range headers {
v := req.WAFRaw().Header.Get(types.String(header))
if len(v) > length {
value = true
break
}
}
return
}
func (this *RequestGeneralHeaderLengthCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return
}
func (this *RequestGeneralHeaderLengthCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheDisabled
}

View File

@@ -0,0 +1,30 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestGeoCityNameCheckpoint struct {
Checkpoint
}
func (this *RequestGeoCityNameCheckpoint) IsComposed() bool {
return false
}
func (this *RequestGeoCityNameCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.Format("${geo.city.name}")
return
}
func (this *RequestGeoCityNameCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return this.RequestValue(req, param, options, ruleId)
}
func (this *RequestGeoCityNameCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,30 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestGeoCountryNameCheckpoint struct {
Checkpoint
}
func (this *RequestGeoCountryNameCheckpoint) IsComposed() bool {
return false
}
func (this *RequestGeoCountryNameCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.Format("${geo.country.name}")
return
}
func (this *RequestGeoCountryNameCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return this.RequestValue(req, param, options, ruleId)
}
func (this *RequestGeoCountryNameCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,30 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestGeoProvinceNameCheckpoint struct {
Checkpoint
}
func (this *RequestGeoProvinceNameCheckpoint) IsComposed() bool {
return false
}
func (this *RequestGeoProvinceNameCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.Format("${geo.province.name}")
return
}
func (this *RequestGeoProvinceNameCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return this.RequestValue(req, param, options, ruleId)
}
func (this *RequestGeoProvinceNameCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,33 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"strings"
)
type RequestHeaderCheckpoint struct {
Checkpoint
}
func (this *RequestHeaderCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
v, found := req.WAFRaw().Header[param]
if !found {
value = ""
return
}
value = strings.Join(v, ";")
return
}
func (this *RequestHeaderCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestHeaderCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,37 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestHeaderMaxLengthCheckpoint struct {
Checkpoint
}
func (this *RequestHeaderMaxLengthCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var maxLen int
for _, v := range req.WAFRaw().Header {
for _, subV := range v {
var l = len(subV)
if l > maxLen {
maxLen = l
}
}
}
value = maxLen
return
}
func (this *RequestHeaderMaxLengthCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestHeaderMaxLengthCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,32 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"strings"
)
type RequestHeaderNamesCheckpoint struct {
Checkpoint
}
func (this *RequestHeaderNamesCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var headerNames = []string{}
for k := range req.WAFRaw().Header {
headerNames = append(headerNames, k)
}
value = strings.Join(headerNames, "\n")
return
}
func (this *RequestHeaderNamesCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestHeaderNamesCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,23 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package checkpoints_test
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/checkpoints"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
"testing"
)
func TestRequestHeaderNamesCheckpoint_RequestValue(t *testing.T) {
var checkpoint = &checkpoints.RequestHeaderNamesCheckpoint{}
rawReq, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
if err != nil {
t.Fatal(err)
}
rawReq.Header.Set("Accept", "text/html")
rawReq.Header.Set("User-Agent", "Chrome")
rawReq.Header.Set("Accept-Encoding", "br, gzip")
var req = requests.NewTestRequest(rawReq)
t.Log(checkpoint.RequestValue(req, "", nil, 0))
}

View File

@@ -0,0 +1,36 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"sort"
"strings"
)
type RequestHeadersCheckpoint struct {
Checkpoint
}
func (this *RequestHeadersCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var headers = []string{}
for k, v := range req.WAFRaw().Header {
for _, subV := range v {
headers = append(headers, k+": "+subV)
}
}
sort.Strings(headers)
value = strings.Join(headers, "\n")
return
}
func (this *RequestHeadersCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestHeadersCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,33 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package checkpoints
import (
"net/http"
"runtime"
"sort"
"strings"
"testing"
)
func BenchmarkRequestHeadersCheckpoint_RequestValue(b *testing.B) {
runtime.GOMAXPROCS(1)
var header = http.Header{
"Content-Type": []string{"keep-alive"},
"User-Agent": []string{"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"},
"Accept-Encoding": []string{"gzip, deflate, br"},
"Referer": []string{"https://goedge.cn/"},
}
for i := 0; i < b.N; i++ {
var headers = []string{}
for k, v := range header {
for _, subV := range v {
headers = append(headers, k+": "+subV)
}
}
sort.Strings(headers)
_ = strings.Join(headers, "\n")
}
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestHostCheckpoint struct {
Checkpoint
}
func (this *RequestHostCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRaw().Host
return
}
func (this *RequestHostCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestHostCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,20 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
"testing"
)
func TestRequestHostCheckpoint_RequestValue(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodGet, "https://teaos.cn/?name=lu", nil)
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(rawReq)
req.WAFRaw().Header.Set("Host", "cloud.teaos.cn")
checkpoint := new(RequestHostCheckpoint)
t.Log(checkpoint.RequestValue(req, "", nil, 1))
}

View File

@@ -0,0 +1,31 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestIsCNAMECheckpoint struct {
Checkpoint
}
func (this *RequestIsCNAMECheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if req.Format("${cname}") == req.Format("${host}") {
value = 1
} else {
value = 0
}
return
}
func (this *RequestIsCNAMECheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestIsCNAMECheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,30 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestISPNameCheckpoint struct {
Checkpoint
}
func (this *RequestISPNameCheckpoint) IsComposed() bool {
return false
}
func (this *RequestISPNameCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.Format("${isp.name}")
return
}
func (this *RequestISPNameCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return this.RequestValue(req, param, options, ruleId)
}
func (this *RequestISPNameCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,54 @@
package checkpoints
import (
"encoding/json"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
wafutils "github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"strings"
)
// RequestJSONArgCheckpoint ${requestJSON.arg}
type RequestJSONArgCheckpoint struct {
Checkpoint
}
func (this *RequestJSONArgCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var bodyData = req.WAFGetCacheBody()
hasRequestBody = true
if len(bodyData) == 0 {
data, err := req.WAFReadBody(req.WAFMaxRequestSize()) // read body
if err != nil {
return "", hasRequestBody, err, nil
}
bodyData = data
req.WAFSetCacheBody(data)
defer req.WAFRestoreBody(data)
}
// TODO improve performance
var m any = nil
err := json.Unmarshal(bodyData, &m)
if err != nil || m == nil {
return "", hasRequestBody, nil, err
}
value = utils.Get(m, strings.Split(param, "."))
if value != nil {
return value, hasRequestBody, nil, err
}
return "", hasRequestBody, nil, nil
}
func (this *RequestJSONArgCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestJSONArgCheckpoint) CacheLife() wafutils.CacheLife {
return wafutils.CacheMiddleLife
}

View File

@@ -0,0 +1,99 @@
package checkpoints
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"io"
"net/http"
"testing"
)
func TestRequestJSONArgCheckpoint_RequestValue_Map(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte(`
{
"name": "lu",
"age": 20,
"books": [ "PHP", "Golang", "Python" ]
}
`)))
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(rawReq)
//req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
checkpoint := new(RequestJSONArgCheckpoint)
t.Log(checkpoint.RequestValue(req, "name", nil, 1))
t.Log(checkpoint.RequestValue(req, "age", nil, 1))
t.Log(checkpoint.RequestValue(req, "Hello", nil, 1))
t.Log(checkpoint.RequestValue(req, "", nil, 1))
t.Log(checkpoint.RequestValue(req, "books", nil, 1))
t.Log(checkpoint.RequestValue(req, "books.1", nil, 1))
body, err := io.ReadAll(req.WAFRaw().Body)
if err != nil {
t.Fatal(err)
}
t.Log(string(body))
}
func TestRequestJSONArgCheckpoint_RequestValue_Array(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte(`
[{
"name": "lu",
"age": 20,
"books": [ "PHP", "Golang", "Python" ]
}]
`)))
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(rawReq)
//req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
checkpoint := new(RequestJSONArgCheckpoint)
t.Log(checkpoint.RequestValue(req, "0.name", nil, 1))
t.Log(checkpoint.RequestValue(req, "0.age", nil, 1))
t.Log(checkpoint.RequestValue(req, "0.Hello", nil, 1))
t.Log(checkpoint.RequestValue(req, "", nil, 1))
t.Log(checkpoint.RequestValue(req, "0.books", nil, 1))
t.Log(checkpoint.RequestValue(req, "0.books.1", nil, 1))
body, err := io.ReadAll(req.WAFRaw().Body)
if err != nil {
t.Fatal(err)
}
t.Log(string(body))
}
func TestRequestJSONArgCheckpoint_RequestValue_Error(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte(`
[{
"name": "lu",
"age": 20,
"books": [ "PHP", "Golang", "Python" ]
}]
`)))
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(rawReq)
//req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
checkpoint := new(RequestJSONArgCheckpoint)
t.Log(checkpoint.RequestValue(req, "0.name", nil, 1))
t.Log(checkpoint.RequestValue(req, "0.age", nil, 1))
t.Log(checkpoint.RequestValue(req, "0.Hello", nil, 1))
t.Log(checkpoint.RequestValue(req, "", nil, 1))
t.Log(checkpoint.RequestValue(req, "0.books", nil, 1))
t.Log(checkpoint.RequestValue(req, "0.books.1", nil, 1))
body, err := io.ReadAll(req.WAFRaw().Body)
if err != nil {
t.Fatal(err)
}
t.Log(string(body))
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestLengthCheckpoint struct {
Checkpoint
}
func (this *RequestLengthCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRaw().ContentLength
return
}
func (this *RequestLengthCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestLengthCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestMethodCheckpoint struct {
Checkpoint
}
func (this *RequestMethodCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRaw().Method
return
}
func (this *RequestMethodCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestMethodCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,26 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestPathCheckpoint struct {
Checkpoint
}
func (this *RequestPathCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return req.WAFRaw().URL.Path, false, nil, nil
}
func (this *RequestPathCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestPathCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,18 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
"testing"
)
func TestRequestPathCheckpoint_RequestValue(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodGet, "http://teaos.cn/index?name=lu", nil)
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(rawReq)
checkpoint := new(RequestPathCheckpoint)
t.Log(checkpoint.RequestValue(req, "", nil, 1))
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestProtoCheckpoint struct {
Checkpoint
}
func (this *RequestProtoCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRaw().Proto
return
}
func (this *RequestProtoCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestProtoCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,33 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"net"
)
type RequestRawRemoteAddrCheckpoint struct {
Checkpoint
}
func (this *RequestRawRemoteAddrCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
host, _, err := net.SplitHostPort(req.WAFRaw().RemoteAddr)
if err == nil {
value = host
} else {
value = req.WAFRaw().RemoteAddr
}
return
}
func (this *RequestRawRemoteAddrCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestRawRemoteAddrCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestRefererCheckpoint struct {
Checkpoint
}
func (this *RequestRefererCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRaw().Referer()
return
}
func (this *RequestRefererCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestRefererCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,104 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package checkpoints
import (
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"net/url"
)
// RequestRefererBlockCheckpoint 防盗链
type RequestRefererBlockCheckpoint struct {
Checkpoint
}
// RequestValue 计算checkpoint值
// 选项allowEmpty, allowSameDomain, allowDomains
func (this *RequestRefererBlockCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var checkOrigin = options.GetBool("checkOrigin")
var referer = req.WAFRaw().Referer()
if len(referer) == 0 && checkOrigin {
var origin = req.WAFRaw().Header.Get("Origin")
if len(origin) > 0 && origin != "null" {
referer = "https://" + origin // 因为Origin都只有域名部分所以为了下面的URL 分析需要加上https://
}
}
if len(referer) == 0 {
if options.GetBool("allowEmpty") {
value = 1
return
}
value = 0
return
}
u, err := url.Parse(referer)
if err != nil {
value = 0
return
}
var host = u.Host
if options.GetBool("allowSameDomain") && host == req.WAFRaw().Host {
value = 1
return
}
// allow domains
var allowDomains = options.GetSlice("allowDomains")
var allowDomainStrings = []string{}
for _, domain := range allowDomains {
allowDomainStrings = append(allowDomainStrings, types.String(domain))
}
// deny domains
var denyDomains = options.GetSlice("denyDomains")
var denyDomainStrings = []string{}
for _, domain := range denyDomains {
denyDomainStrings = append(denyDomainStrings, types.String(domain))
}
if len(allowDomainStrings) == 0 {
if len(denyDomainStrings) > 0 {
if configutils.MatchDomains(denyDomainStrings, host) {
value = 0
} else {
value = 1
}
return
}
value = 0
return
}
if configutils.MatchDomains(allowDomainStrings, host) {
if len(denyDomainStrings) > 0 {
if configutils.MatchDomains(denyDomainStrings, host) {
value = 0
} else {
value = 1
}
return
}
value = 1
return
} else {
value = 0
}
return
}
func (this *RequestRefererBlockCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return
}
func (this *RequestRefererBlockCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,44 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestRefererOriginCheckpoint struct {
Checkpoint
}
func (this *RequestRefererOriginCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var s []string
var referer = req.WAFRaw().Referer()
if len(referer) > 0 {
s = append(s, referer)
}
var origin = req.WAFRaw().Header.Get("Origin")
if len(origin) > 0 {
s = append(s, origin)
}
if len(s) > 0 {
value = s
} else {
value = ""
}
return
}
func (this *RequestRefererOriginCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestRefererOriginCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,38 @@
package checkpoints_test
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/checkpoints"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
"testing"
)
func TestRequestRefererOriginCheckpoint_RequestValue(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
if err != nil {
t.Fatal(err)
}
var req = requests.NewTestRequest(rawReq)
var checkpoint = &checkpoints.RequestRefererOriginCheckpoint{}
{
t.Log(checkpoint.RequestValue(req, "", nil, 0))
}
{
rawReq.Header.Set("Referer", "https://example.com/hello.yaml")
t.Log(checkpoint.RequestValue(req, "", nil, 0))
}
{
rawReq.Header.Set("Origin", "https://example.com/world.yaml")
t.Log(checkpoint.RequestValue(req, "", nil, 0))
}
{
rawReq.Header.Del("Referer")
rawReq.Header.Set("Origin", "https://example.com/world.yaml")
t.Log(checkpoint.RequestValue(req, "", nil, 0))
}
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestRemoteAddrCheckpoint struct {
Checkpoint
}
func (this *RequestRemoteAddrCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRemoteIP()
return
}
func (this *RequestRemoteAddrCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestRemoteAddrCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,34 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"net"
)
type RequestRemotePortCheckpoint struct {
Checkpoint
}
func (this *RequestRemotePortCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
_, port, err := net.SplitHostPort(req.WAFRaw().RemoteAddr)
if err == nil {
value = types.Int(port)
} else {
value = 0
}
return
}
func (this *RequestRemotePortCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestRemotePortCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,32 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestRemoteUserCheckpoint struct {
Checkpoint
}
func (this *RequestRemoteUserCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
username, _, ok := req.WAFRaw().BasicAuth()
if !ok {
value = ""
return
}
value = username
return
}
func (this *RequestRemoteUserCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestRemoteUserCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestSchemeCheckpoint struct {
Checkpoint
}
func (this *RequestSchemeCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.Format("${scheme}")
return
}
func (this *RequestSchemeCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestSchemeCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,18 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
"testing"
)
func TestRequestSchemeCheckpoint_RequestValue(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodGet, "https://teaos.cn/?name=lu", nil)
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(rawReq)
checkpoint := new(RequestSchemeCheckpoint)
t.Log(checkpoint.RequestValue(req, "", nil, 1))
}

View File

@@ -0,0 +1,234 @@
package checkpoints
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"io"
"net/http"
"path/filepath"
"regexp"
"strconv"
"strings"
)
var multipartHeaderRegexp = regexp.MustCompile(`(?i)(?:^|\r\n)--+\w+\r\n((([\w-]+: .+)\r\n)+)`)
var multipartHeaderContentRangeRegexp = regexp.MustCompile(`/(\d+)`)
// RequestUploadCheckpoint ${requestUpload.arg}
type RequestUploadCheckpoint struct {
Checkpoint
}
func (this *RequestUploadCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.RequestBodyIsEmpty(req) {
value = ""
return
}
value = ""
if param == "minSize" || param == "maxSize" {
value = 0
}
if req.WAFRaw().Method != http.MethodPost {
return
}
if req.WAFRaw().Body == nil {
return
}
hasRequestBody = true
var requestContentLength = req.WAFRaw().ContentLength
var fields []string
var minSize int64
var maxSize int64
var names []string
var extensions []string
if requestContentLength <= req.WAFMaxRequestSize() { // full read
if req.WAFRaw().MultipartForm == nil {
var bodyData = req.WAFGetCacheBody()
if len(bodyData) == 0 {
data, err := req.WAFReadBody(req.WAFMaxRequestSize())
if err != nil {
sysErr = err
return
}
bodyData = data
req.WAFSetCacheBody(data)
defer req.WAFRestoreBody(data)
}
var oldBody = req.WAFRaw().Body
req.WAFRaw().Body = io.NopCloser(bytes.NewBuffer(bodyData))
err := req.WAFRaw().ParseMultipartForm(req.WAFMaxRequestSize())
if err == nil {
for field, files := range req.WAFRaw().MultipartForm.File {
if param == "field" {
fields = append(fields, field)
} else if param == "minSize" {
for _, file := range files {
if minSize == 0 || minSize > file.Size {
minSize = file.Size
}
}
} else if param == "maxSize" {
for _, file := range files {
if maxSize < file.Size {
maxSize = file.Size
}
}
} else if param == "name" {
for _, file := range files {
if !lists.ContainsString(names, file.Filename) {
names = append(names, file.Filename)
}
}
} else if param == "ext" {
for _, file := range files {
if len(file.Filename) > 0 {
exit := strings.ToLower(filepath.Ext(file.Filename))
if !lists.ContainsString(extensions, exit) {
extensions = append(extensions, exit)
}
}
}
}
}
}
// 还原
req.WAFRaw().Body = oldBody
if err != nil {
userErr = err
return
}
if req.WAFRaw().MultipartForm == nil {
return
}
}
} else { // read first part
var bodyData = req.WAFGetCacheBody()
if len(bodyData) == 0 {
data, err := req.WAFReadBody(req.WAFMaxRequestSize())
if err != nil {
sysErr = err
return
}
bodyData = data
req.WAFSetCacheBody(data)
defer req.WAFRestoreBody(data)
}
var subMatches = multipartHeaderRegexp.FindAllSubmatch(bodyData, -1)
for _, subMatch := range subMatches {
var headers = bytes.Split(subMatch[1], []byte{'\r', '\n'})
var partContentLength int64 = -1
for _, header := range headers {
if len(header) > 2 {
var kv = bytes.SplitN(header, []byte{':'}, 2)
if len(kv) == 2 {
var k = kv[0]
var v = kv[1]
switch string(bytes.ToLower(k)) {
case "content-disposition":
var props = bytes.Split(v, []byte{';', ' '})
for _, prop := range props {
var propKV = bytes.SplitN(prop, []byte{'='}, 2)
if len(propKV) == 2 {
var propValue = string(propKV[1])
switch string(propKV[0]) {
case "name":
if param == "field" {
propValue, _ = strconv.Unquote(propValue)
fields = append(fields, propValue)
}
case "filename":
if param == "name" {
propValue, _ = strconv.Unquote(propValue)
names = append(names, propValue)
} else if param == "ext" {
propValue, _ = strconv.Unquote(propValue)
extensions = append(extensions, strings.ToLower(filepath.Ext(propValue)))
}
}
}
}
case "content-range":
if partContentLength <= 0 {
var contentRange = multipartHeaderContentRangeRegexp.FindSubmatch(v)
if len(contentRange) >= 2 {
partContentLength = types.Int64(string(contentRange[1]))
}
}
case "content-length":
if partContentLength <= 0 {
partContentLength = types.Int64(string(v))
}
}
}
}
}
// minSize & maxSize
if partContentLength > 0 {
if param == "minSize" && (minSize == 0 /** not set yet **/ || partContentLength < minSize) {
minSize = partContentLength
} else if param == "maxSize" && partContentLength > maxSize {
maxSize = partContentLength
}
}
}
}
if param == "field" { // field
value = strings.Join(fields, ",")
} else if param == "minSize" { // minSize
if minSize == 0 && requestContentLength > 0 {
minSize = requestContentLength
}
value = minSize
} else if param == "maxSize" { // maxSize
if maxSize == 0 && requestContentLength > 0 {
maxSize = requestContentLength
}
value = maxSize
} else if param == "name" { // name
value = strings.Join(names, ",")
} else if param == "ext" { // ext
value = strings.Join(extensions, ",")
}
return
}
func (this *RequestUploadCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestUploadCheckpoint) ParamOptions() *ParamOptions {
option := NewParamOptions()
option.AddParam("最小文件尺寸", "minSize")
option.AddParam("最大文件尺寸", "maxSize")
option.AddParam("扩展名(如.txt)", "ext")
option.AddParam("原始文件名", "name")
option.AddParam("表单字段名", "field")
return option
}
func (this *RequestUploadCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,102 @@
package checkpoints
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"io"
"mime/multipart"
"net/http"
"testing"
)
func TestRequestUploadCheckpoint_RequestValue(t *testing.T) {
body := bytes.NewBuffer([]byte{})
writer := multipart.NewWriter(body)
{
part, err := writer.CreateFormField("name")
if err == nil {
_, err := part.Write([]byte("lu"))
if err != nil {
t.Fatal(err)
}
}
}
{
part, err := writer.CreateFormField("age")
if err == nil {
_, err = part.Write([]byte("20"))
if err != nil {
t.Fatal(err)
}
}
}
{
part, err := writer.CreateFormFile("myFile", "hello.txt")
if err == nil {
_, err = part.Write([]byte("Hello, World!"))
if err != nil {
t.Fatal(err)
}
}
}
{
part, err := writer.CreateFormFile("myFile2", "hello.PHP")
if err == nil {
_, err = part.Write([]byte("Hello, World, PHP!"))
if err != nil {
t.Fatal(err)
}
}
}
{
part, err := writer.CreateFormFile("myFile3", "hello.asp")
if err == nil {
_, err = part.Write([]byte("Hello, World, ASP Pages!"))
if err != nil {
t.Fatal(err)
}
}
}
{
part, err := writer.CreateFormFile("myFile4", "hello.asp")
if err == nil {
_, err = part.Write([]byte("Hello, World, ASP Pages!"))
if err != nil {
t.Fatal(err)
}
}
}
err := writer.Close()
if err != nil {
t.Fatal(err)
}
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn/", body)
if err != nil {
t.Fatal()
}
req := requests.NewTestRequest(rawReq)
req.WAFRaw().Header.Add("Content-Type", writer.FormDataContentType())
checkpoint := new(RequestUploadCheckpoint)
t.Log(checkpoint.RequestValue(req, "field", nil, 1))
t.Log(checkpoint.RequestValue(req, "minSize", nil, 1))
t.Log(checkpoint.RequestValue(req, "maxSize", nil, 1))
t.Log(checkpoint.RequestValue(req, "name", nil, 1))
t.Log(checkpoint.RequestValue(req, "ext", nil, 1))
data, err := io.ReadAll(req.WAFRaw().Body)
if err != nil {
t.Fatal(err)
}
t.Log(string(data))
}

View File

@@ -0,0 +1,31 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestURICheckpoint struct {
Checkpoint
}
func (this *RequestURICheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if len(req.WAFRaw().RequestURI) > 0 {
value = req.WAFRaw().RequestURI
} else if req.WAFRaw().URL != nil {
value = req.WAFRaw().URL.RequestURI()
}
return
}
func (this *RequestURICheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestURICheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,26 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestURLCheckpoint struct {
Checkpoint
}
func (this *RequestURLCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return req.Format("${requestURL}"), hasRequestBody, nil, nil
}
func (this *RequestURLCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestURLCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestUserAgentCheckpoint struct {
Checkpoint
}
func (this *RequestUserAgentCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRaw().UserAgent()
return
}
func (this *RequestUserAgentCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestUserAgentCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,52 @@
package checkpoints
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"io"
)
// ResponseBodyCheckpoint ${responseBody}
type ResponseBodyCheckpoint struct {
Checkpoint
}
func (this *ResponseBodyCheckpoint) IsRequest() bool {
return false
}
func (this *ResponseBodyCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = ""
return
}
func (this *ResponseBodyCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if resp.ContentLength == 0 {
value = ""
return
}
value = ""
if resp != nil && resp.Body != nil {
if len(resp.BodyData) > 0 {
value = string(resp.BodyData)
return
}
body, err := io.ReadAll(resp.Body)
if err != nil {
sysErr = err
return
}
resp.BodyData = body
_ = resp.Body.Close()
value = body
resp.Body = io.NopCloser(bytes.NewBuffer(body))
}
return
}
func (this *ResponseBodyCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,29 @@
package checkpoints
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"io"
"net/http"
"testing"
)
func TestResponseBodyCheckpoint_ResponseValue(t *testing.T) {
resp := requests.NewResponse(new(http.Response))
resp.StatusCode = 200
resp.Header = http.Header{}
resp.Header.Set("Hello", "World")
resp.Body = io.NopCloser(bytes.NewBuffer([]byte("Hello, World")))
checkpoint := new(ResponseBodyCheckpoint)
t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1))
t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1))
t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1))
t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1))
data, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
t.Log("after read:", string(data))
}

View File

@@ -0,0 +1,33 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
// ResponseBytesSentCheckpoint ${bytesSent}
type ResponseBytesSentCheckpoint struct {
Checkpoint
}
func (this *ResponseBytesSentCheckpoint) IsRequest() bool {
return false
}
func (this *ResponseBytesSentCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = 0
return
}
func (this *ResponseBytesSentCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = 0
if resp != nil {
value = resp.ContentLength
}
return
}
func (this *ResponseBytesSentCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,49 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
)
type ResponseGeneralHeaderLengthCheckpoint struct {
Checkpoint
}
func (this *ResponseGeneralHeaderLengthCheckpoint) IsRequest() bool {
return false
}
func (this *ResponseGeneralHeaderLengthCheckpoint) IsComposed() bool {
return true
}
func (this *ResponseGeneralHeaderLengthCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return
}
func (this *ResponseGeneralHeaderLengthCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = false
headers := options.GetSlice("headers")
if len(headers) == 0 {
return
}
length := options.GetInt("length")
for _, header := range headers {
v := req.WAFRaw().Header.Get(types.String(header))
if len(v) > length {
value = true
break
}
}
return
}
func (this *ResponseGeneralHeaderLengthCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,34 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
// ResponseHeaderCheckpoint ${responseHeader.arg}
type ResponseHeaderCheckpoint struct {
Checkpoint
}
func (this *ResponseHeaderCheckpoint) IsRequest() bool {
return false
}
func (this *ResponseHeaderCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = ""
return
}
func (this *ResponseHeaderCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if resp != nil && resp.Header != nil {
value = resp.Header.Get(param)
} else {
value = ""
}
return
}
func (this *ResponseHeaderCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,17 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
"testing"
)
func TestResponseHeaderCheckpoint_ResponseValue(t *testing.T) {
resp := requests.NewResponse(new(http.Response))
resp.StatusCode = 200
resp.Header = http.Header{}
resp.Header.Set("Hello", "World")
checkpoint := new(ResponseHeaderCheckpoint)
t.Log(checkpoint.ResponseValue(nil, resp, "Hello", nil, 1))
}

View File

@@ -0,0 +1,32 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
// ResponseStatusCheckpoint ${bytesSent}
type ResponseStatusCheckpoint struct {
Checkpoint
}
func (this *ResponseStatusCheckpoint) IsRequest() bool {
return false
}
func (this *ResponseStatusCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = 0
return
}
func (this *ResponseStatusCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if resp != nil {
value = resp.StatusCode
}
return
}
func (this *ResponseStatusCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,15 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
"testing"
)
func TestResponseStatusCheckpoint_ResponseValue(t *testing.T) {
resp := requests.NewResponse(new(http.Response))
resp.StatusCode = 200
checkpoint := new(ResponseStatusCheckpoint)
t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1))
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
// SampleRequestCheckpoint just a sample checkpoint, copy and change it for your new checkpoint
type SampleRequestCheckpoint struct {
Checkpoint
}
func (this *SampleRequestCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return
}
func (this *SampleRequestCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *SampleRequestCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,28 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
// SampleResponseCheckpoint just a sample checkpoint, copy and change it for your new checkpoint
type SampleResponseCheckpoint struct {
Checkpoint
}
func (this *SampleResponseCheckpoint) IsRequest() bool {
return false
}
func (this *SampleResponseCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, sysErr error, userErr error) {
return
}
func (this *SampleResponseCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return
}
func (this *SampleResponseCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,378 @@
package checkpoints
// AllCheckpoints all check points list
var AllCheckpoints = []*CheckpointDefinition{
{
Name: "通用请求Header长度限制",
Prefix: "requestGeneralHeaderLength",
Description: "通用Header比如Cache-Control、Accept之类的长度限制防止缓冲区溢出攻击",
HasParams: false,
Instance: new(RequestGeneralHeaderLengthCheckpoint),
Priority: 100,
},
{
Name: "客户端地址IP",
Prefix: "remoteAddr",
Description: "试图通过分析X-Forwarded-For等Header获取的客户端地址比如192.168.1.100",
HasParams: false,
Instance: new(RequestRemoteAddrCheckpoint),
Priority: 100,
},
{
Name: "客户端源地址IP",
Prefix: "rawRemoteAddr",
Description: "直接连接的客户端地址比如192.168.1.100",
HasParams: false,
Instance: new(RequestRawRemoteAddrCheckpoint),
Priority: 100,
},
{
Name: "客户端端口",
Prefix: "remotePort",
Description: "直接连接的客户端地址端口",
HasParams: false,
Instance: new(RequestRemotePortCheckpoint),
Priority: 100,
},
{
Name: "客户端用户名",
Prefix: "remoteUser",
Description: "通过BasicAuth登录的客户端用户名",
HasParams: false,
Instance: new(RequestRemoteUserCheckpoint),
Priority: 100,
},
{
Name: "请求URI",
Prefix: "requestURI",
Description: "包含URL参数的请求URI类似于 /hello/world?lang=go",
HasParams: false,
Instance: new(RequestURICheckpoint),
Priority: 100,
},
{
Name: "请求路径",
Prefix: "requestPath",
Description: "不包含URL参数的请求路径类似于 /hello/world",
HasParams: false,
Instance: new(RequestPathCheckpoint),
Priority: 100,
},
{
Name: "请求URL",
Prefix: "requestURL",
Description: "完整的请求URL包含协议、域名、请求路径、参数等类似于 https://example.com/hello?name=lily",
HasParams: false,
Instance: new(RequestURLCheckpoint),
Priority: 100,
},
{
Name: "请求内容长度",
Prefix: "requestLength",
Description: "请求Header中的Content-Length",
HasParams: false,
Instance: new(RequestLengthCheckpoint),
Priority: 100,
},
{
Name: "请求体内容",
Prefix: "requestBody",
Description: "通常在POST或者PUT等操作时会附带请求体最大限制32M",
HasParams: false,
Instance: new(RequestBodyCheckpoint),
Priority: 5,
},
{
Name: "请求URI和请求体组合",
Prefix: "requestAll",
Description: "${requestURI}和${requestBody}组合",
HasParams: false,
Instance: new(RequestAllCheckpoint),
Priority: 5,
},
{
Name: "请求表单参数",
Prefix: "requestForm",
Description: "获取POST或者其他方法发送的表单参数最大请求体限制32M",
HasParams: true,
Instance: new(RequestFormArgCheckpoint),
Priority: 5,
},
{
Name: "上传文件",
Prefix: "requestUpload",
Description: "获取POST上传的文件信息最大请求体限制32M",
HasParams: true,
Instance: new(RequestUploadCheckpoint),
Priority: 20,
},
{
Name: "请求JSON参数",
Prefix: "requestJSON",
Description: "获取POST或者其他方法发送的JSON最大请求体限制32M使用点.)符号表示多级数据",
HasParams: true,
Instance: new(RequestJSONArgCheckpoint),
Priority: 5,
},
{
Name: "请求方法",
Prefix: "requestMethod",
Description: "比如GET、POST",
HasParams: false,
Instance: new(RequestMethodCheckpoint),
Priority: 100,
},
{
Name: "请求协议",
Prefix: "scheme",
Description: "比如http或https",
HasParams: false,
Instance: new(RequestSchemeCheckpoint),
Priority: 100,
},
{
Name: "HTTP协议版本",
Prefix: "proto",
Description: "比如HTTP/1.1",
HasParams: false,
Instance: new(RequestProtoCheckpoint),
Priority: 100,
},
{
Name: "主机名",
Prefix: "host",
Description: "比如teaos.cn",
HasParams: false,
Instance: new(RequestHostCheckpoint),
Priority: 100,
},
{
Name: "CNAME",
Prefix: "cname",
Description: "当前网站服务CNAME比如38b48e4f.goedge.cn",
HasParams: false,
Instance: new(RequestCNAMECheckpoint),
Priority: 100,
},
{
Name: "是否为CNAME",
Prefix: "isCNAME",
Description: "是否为CNAME值为1或0",
HasParams: false,
Instance: new(RequestIsCNAMECheckpoint),
Priority: 100,
},
{
Name: "请求来源",
Prefix: "refererOrigin",
Description: "请求报头中的Referer或Origin值",
HasParams: false,
Instance: new(RequestRefererOriginCheckpoint),
Priority: 100,
},
{
Name: "请求来源Referer",
Prefix: "referer",
Description: "请求Header中的Referer值",
HasParams: false,
Instance: new(RequestRefererCheckpoint),
Priority: 100,
},
{
Name: "客户端信息",
Prefix: "userAgent",
Description: "比如Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103",
HasParams: false,
Instance: new(RequestUserAgentCheckpoint),
Priority: 100,
},
{
Name: "内容类型",
Prefix: "contentType",
Description: "请求Header的Content-Type",
HasParams: false,
Instance: new(RequestContentTypeCheckpoint),
Priority: 100,
},
{
Name: "所有cookie组合字符串",
Prefix: "cookies",
Description: "比如sid=IxZVPFhE&city=beijing&uid=18237",
HasParams: false,
Instance: new(RequestCookiesCheckpoint),
Priority: 100,
},
{
Name: "单个cookie值",
Prefix: "cookie",
Description: "单个cookie值",
HasParams: true,
Instance: new(RequestCookieCheckpoint),
Priority: 100,
},
{
Name: "所有URL参数组合",
Prefix: "args",
Description: "比如name=lu&age=20",
HasParams: false,
Instance: new(RequestArgsCheckpoint),
Priority: 100,
},
{
Name: "单个URL参数值",
Prefix: "arg",
Description: "单个URL参数值",
HasParams: true,
Instance: new(RequestArgCheckpoint),
Priority: 100,
},
{
Name: "所有Header信息",
Prefix: "headers",
Description: "使用\\n隔开的Header信息字符串",
HasParams: false,
Instance: new(RequestHeadersCheckpoint),
Priority: 100,
},
{
Name: "所有请求报头名称",
Prefix: "headerNames",
Description: "使用换行符(\\n隔开的报头名称字符串每行一个名称",
HasParams: false,
Instance: new(RequestHeaderNamesCheckpoint),
Priority: 100,
},
{
Name: "单个报头值",
Prefix: "header",
Description: "单个报头值",
HasParams: true,
Instance: new(RequestHeaderCheckpoint),
Priority: 100,
},
{
Name: "请求报头最大长度",
Prefix: "headerMaxLength",
Description: "最长的请求报头的长度。",
HasParams: false,
Instance: new(RequestHeaderMaxLengthCheckpoint),
Priority: 100,
},
{
Name: "国家/地区名称",
Prefix: "geoCountryName",
Description: "国家/地区名称",
HasParams: false,
Instance: new(RequestGeoCountryNameCheckpoint),
Priority: 90,
},
{
Name: "省份名称",
Prefix: "geoProvinceName",
Description: "中国省份名称",
HasParams: false,
Instance: new(RequestGeoProvinceNameCheckpoint),
Priority: 90,
},
{
Name: "城市名称",
Prefix: "geoCityName",
Description: "中国城市名称",
HasParams: false,
Instance: new(RequestGeoCityNameCheckpoint),
Priority: 90,
},
{
Name: "ISP名称",
Prefix: "ispName",
Description: "ISP名称",
HasParams: false,
Instance: new(RequestISPNameCheckpoint),
Priority: 90,
},
{
Name: "CC统计",
Prefix: "cc",
Description: "统计某段时间段内的请求信息",
HasParams: true,
Instance: new(CCCheckpoint),
Priority: 10,
},
{
Name: "CC统计",
Prefix: "cc2",
Description: "统计某段时间段内的请求信息",
HasParams: true,
Instance: new(CC2Checkpoint),
Priority: 10,
},
{
Name: "防盗链",
Prefix: "refererBlock",
Description: "阻止一些域名访问引用本站资源",
HasParams: true,
Instance: new(RequestRefererBlockCheckpoint),
Priority: 20,
},
{
Name: "通用响应Header长度限制",
Prefix: "responseGeneralHeaderLength",
Description: "通用Header比如Cache-Control、Accept之类的长度限制防止缓冲区溢出攻击",
HasParams: false,
Instance: new(ResponseGeneralHeaderLengthCheckpoint),
Priority: 100,
},
{
Name: "响应状态码",
Prefix: "status",
Description: "响应状态码比如200、404、500",
HasParams: false,
Instance: new(ResponseStatusCheckpoint),
Priority: 100,
},
{
Name: "响应Header",
Prefix: "responseHeader",
Description: "响应Header值",
HasParams: true,
Instance: new(ResponseHeaderCheckpoint),
Priority: 100,
},
{
Name: "响应内容",
Prefix: "responseBody",
Description: "响应内容字符串",
HasParams: false,
Instance: new(ResponseBodyCheckpoint),
Priority: 5,
},
{
Name: "响应内容长度",
Prefix: "bytesSent",
Description: "响应内容长度通过响应的Header Content-Length获取",
HasParams: false,
Instance: new(ResponseBytesSentCheckpoint),
Priority: 100,
},
}
// FindCheckpoint find a check point
func FindCheckpoint(prefix string) CheckpointInterface {
for _, def := range AllCheckpoints {
if def.Prefix == prefix {
def.Instance.SetPriority(def.Priority)
return def.Instance
}
}
return nil
}
// FindCheckpointDefinition find a check point definition
func FindCheckpointDefinition(prefix string) *CheckpointDefinition {
for _, def := range AllCheckpoints {
if def.Prefix == prefix {
return def
}
}
return nil
}

View File

@@ -0,0 +1,31 @@
package checkpoints
import (
"fmt"
"strings"
"testing"
)
func TestFindCheckpointDefinition_Markdown(t *testing.T) {
result := []string{}
for _, def := range AllCheckpoints {
row := "## " + def.Name + "\n* 前缀:`${" + def.Prefix + "}`\n* 描述:" + def.Description
if def.HasParams {
row += "\n* 是否有子参数YES"
paramOptions := def.Instance.ParamOptions()
if paramOptions != nil && len(paramOptions.Options) > 0 {
row += "\n* 可选子参数"
for _, option := range paramOptions.Options {
row += "\n * `" + option.Name + "`:值为 `" + option.Value + "`"
}
}
} else {
row += "\n* 是否有子参数NO"
}
row += "\n"
result = append(result, row)
}
fmt.Print(strings.Join(result, "\n") + "\n")
}