-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrequestfilter.go
More file actions
110 lines (97 loc) · 2.84 KB
/
requestfilter.go
File metadata and controls
110 lines (97 loc) · 2.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// Package requestfilter provides a Traefik middleware for filtering HTTP requests.
package requestfilter
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"regexp"
)
// Config the plugin configuration.
type Config struct {
FilterRegexes []string `json:"filterRegexes,omitempty"`
HTTPErrorMessage string `json:"httpErrorMessage,omitempty"`
PathOnly bool `json:"pathOnly,omitempty"`
BodyOnly bool `json:"bodyOnly,omitempty"`
}
// CreateConfig creates the default plugin configuration.
func CreateConfig() *Config {
return &Config{
FilterRegexes: []string{},
HTTPErrorMessage: "",
PathOnly: false,
BodyOnly: false,
}
}
// RequestFilter a request filtering plugin.
type RequestFilter struct {
next http.Handler
name string
filterRegexes []*regexp.Regexp
httpErrorMessage string
pathOnly bool
bodyOnly bool
}
// New creates a new RequestFilter plugin.
func New(_ context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
regexes := make([]*regexp.Regexp, 0, len(config.FilterRegexes))
for _, pattern := range config.FilterRegexes {
regex, err := regexp.Compile(pattern)
if err != nil {
return nil, fmt.Errorf("invalid filter regex '%s': %v", pattern, err)
}
regexes = append(regexes, regex)
}
// validate that at only one of pathOnly and bodyOnly is set
if config.PathOnly && config.BodyOnly {
//nolint:perfsprint
return nil, fmt.Errorf("only one of pathOnly and bodyOnly can be set")
}
return &RequestFilter{
next: next,
name: name,
filterRegexes: regexes,
httpErrorMessage: config.HTTPErrorMessage,
pathOnly: config.PathOnly,
bodyOnly: config.BodyOnly,
}, nil
}
func (r *RequestFilter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
//nolint:nestif
if len(r.filterRegexes) > 0 {
// Check URL path if not bodyOnly
if !r.bodyOnly {
for _, regex := range r.filterRegexes {
if regex.MatchString(req.URL.Path) {
r.block(rw)
return
}
}
}
// Check body for POST, PUT, and PATCH requests if not pathOnly
if !r.pathOnly && (req.Method == http.MethodPost || req.Method == http.MethodPut || req.Method == http.MethodPatch) {
body, err := io.ReadAll(req.Body)
if err != nil {
http.Error(rw, "Error reading request body", http.StatusInternalServerError)
return
}
for _, regex := range r.filterRegexes {
if regex.Match(body) {
r.block(rw)
return
}
}
// Replace the body with a new ReadCloser
req.Body = io.NopCloser(bytes.NewReader(body))
}
}
r.next.ServeHTTP(rw, req)
}
func (r *RequestFilter) block(rw http.ResponseWriter) {
if r.httpErrorMessage == "" {
http.Error(rw, "Request blocked", http.StatusForbidden)
} else {
http.Error(rw, r.httpErrorMessage, http.StatusForbidden)
}
}