github.com/mholt/caddy-l4@v0.0.0-20241104153248-ec8fae209322/modules/l4regexp/matcher.go (about)

     1  // Copyright 2024 VNXME
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package l4regexp
    16  
    17  import (
    18  	"io"
    19  	"regexp"
    20  	"strconv"
    21  
    22  	"github.com/caddyserver/caddy/v2"
    23  	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
    24  	"github.com/mholt/caddy-l4/layer4"
    25  )
    26  
    27  func init() {
    28  	caddy.RegisterModule(&MatchRegexp{})
    29  }
    30  
    31  // MatchRegexp is able to match any connections with regular expressions.
    32  type MatchRegexp struct {
    33  	Count   uint16 `json:"count,omitempty"`
    34  	Pattern string `json:"pattern,omitempty"`
    35  
    36  	compiled *regexp.Regexp
    37  }
    38  
    39  // CaddyModule returns the Caddy module information.
    40  func (m *MatchRegexp) CaddyModule() caddy.ModuleInfo {
    41  	return caddy.ModuleInfo{
    42  		ID:  "layer4.matchers.regexp",
    43  		New: func() caddy.Module { return new(MatchRegexp) },
    44  	}
    45  }
    46  
    47  // Match returns true if the connection bytes match the regular expression.
    48  func (m *MatchRegexp) Match(cx *layer4.Connection) (bool, error) {
    49  	// Read a number of bytes
    50  	buf := make([]byte, m.Count)
    51  	n, err := io.ReadFull(cx, buf)
    52  	if err != nil || n < int(m.Count) {
    53  		return false, err
    54  	}
    55  
    56  	// Match these bytes against the regular expression
    57  	return m.compiled.Match(buf), nil
    58  }
    59  
    60  // Provision parses m's regular expression and sets m's minimum read bytes count.
    61  func (m *MatchRegexp) Provision(_ caddy.Context) (err error) {
    62  	repl := caddy.NewReplacer()
    63  	if m.Count == 0 {
    64  		m.Count = minCount
    65  	}
    66  	m.compiled, err = regexp.Compile(repl.ReplaceAll(m.Pattern, ""))
    67  	if err != nil {
    68  		return err
    69  	}
    70  	return nil
    71  }
    72  
    73  // UnmarshalCaddyfile sets up the MatchRegexp from Caddyfile tokens. Syntax:
    74  //
    75  //	regexp <pattern> [<count>]
    76  func (m *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
    77  	_, wrapper := d.Next(), d.Val() // consume wrapper name
    78  
    79  	// One or two same-line argument must be provided
    80  	if d.CountRemainingArgs() == 0 || d.CountRemainingArgs() > 2 {
    81  		return d.ArgErr()
    82  	}
    83  
    84  	_, m.Pattern = d.NextArg(), d.Val()
    85  	if d.NextArg() {
    86  		val, err := strconv.ParseUint(d.Val(), 10, 16)
    87  		if err != nil {
    88  			return d.Errf("parsing %s count: %v", wrapper, err)
    89  		}
    90  		m.Count = uint16(val)
    91  	}
    92  
    93  	// No blocks are supported
    94  	if d.NextBlock(d.Nesting()) {
    95  		return d.Errf("malformed %s option: blocks are not supported", wrapper)
    96  	}
    97  
    98  	return nil
    99  }
   100  
   101  // Interface guards
   102  var (
   103  	_ caddy.Provisioner     = (*MatchRegexp)(nil)
   104  	_ caddyfile.Unmarshaler = (*MatchRegexp)(nil)
   105  	_ layer4.ConnMatcher    = (*MatchRegexp)(nil)
   106  )
   107  
   108  const (
   109  	minCount uint16 = 4 // by default, read this many bytes to match against
   110  )