Apply compat patch

This commit is contained in:
yusing
2026-02-28 19:37:38 +08:00
parent 5f48f141ca
commit 27dc13a9a8
66 changed files with 418 additions and 739 deletions

View File

@@ -3,8 +3,8 @@ package provider
import (
"testing"
"github.com/docker/docker/api/types/container"
"github.com/goccy/go-yaml"
"github.com/moby/moby/api/types/container"
"github.com/yusing/godoxy/internal/docker"
"github.com/yusing/godoxy/internal/types"
expect "github.com/yusing/goutils/testing"
@@ -26,7 +26,7 @@ func TestParseDockerLabels(t *testing.T) {
Names: []string{"container"},
Labels: labels,
State: "running",
Ports: []container.PortSummary{
Ports: []container.Port{
{Type: "tcp", PrivatePort: 1234, PublicPort: 1234},
},
}, types.DockerProviderConfig{URL: "unix:///var/run/docker.sock"}),

View File

@@ -1,13 +1,12 @@
package provider
import (
"net/netip"
"testing"
"time"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
D "github.com/yusing/godoxy/internal/docker"
"github.com/yusing/godoxy/internal/route"
routeTypes "github.com/yusing/godoxy/internal/route/types"
@@ -276,7 +275,7 @@ func TestPrivateIPLocalhost(t *testing.T) {
NetworkSettings: &container.NetworkSettingsSummary{
Networks: map[string]*network.EndpointSettings{
"network": {
IPAddress: netip.MustParseAddr(testDockerIP),
IPAddress: testDockerIP,
},
},
},
@@ -294,7 +293,7 @@ func TestPrivateIPRemote(t *testing.T) {
NetworkSettings: &container.NetworkSettingsSummary{
Networks: map[string]*network.EndpointSettings{
"network": {
IPAddress: netip.MustParseAddr(testDockerIP),
IPAddress: testDockerIP,
},
},
},
@@ -316,11 +315,11 @@ func TestStreamDefaultValues(t *testing.T) {
NetworkSettings: &container.NetworkSettingsSummary{
Networks: map[string]*network.EndpointSettings{
"network": {
IPAddress: netip.MustParseAddr(privIP),
IPAddress: privIP,
},
},
},
Ports: []container.PortSummary{
Ports: []container.Port{
{Type: "udp", PrivatePort: privPort, PublicPort: pubPort},
},
}
@@ -373,7 +372,7 @@ func TestImplicitExcludeDatabase(t *testing.T) {
t.Run("exposed port detection", func(t *testing.T) {
r, ok := makeRoutes(&container.Summary{
Names: dummyNames,
Ports: []container.PortSummary{
Ports: []container.Port{
{Type: "tcp", PrivatePort: 5432, PublicPort: 5432},
},
})["a"]

View File

@@ -292,20 +292,6 @@ func parseAtBlockChain(src string, blockPos int) (CommandHandler, int, error) {
}
func lineEndsWithUnquotedOpenBrace(src string, lineStart int, lineEnd int) bool {
return lineEndsWithUnquotedToken(src, lineStart, lineEnd) == '{'
}
func lineContinuationOperator(src string, lineStart int, lineEnd int) byte {
token := lineEndsWithUnquotedToken(src, lineStart, lineEnd)
switch token {
case '|', '&':
return token
default:
return 0
}
}
func lineEndsWithUnquotedToken(src string, lineStart int, lineEnd int) byte {
quote := byte(0)
lastSignificant := byte(0)
atLineStart := true
@@ -348,22 +334,13 @@ func lineEndsWithUnquotedToken(src string, lineStart int, lineEnd int) byte {
atLineStart = false
prevIsSpace = false
}
if quote != 0 {
return 0
}
return lastSignificant
return quote == 0 && lastSignificant == '{'
}
// parseDoWithBlocks parses a do-body containing plain command lines and nested blocks.
// It returns the outer command handlers and the require phase.
//
// A nested block is recognized when a logical header ends with an unquoted '{'.
// Logical headers may span lines using trailing '|' or '&', for example:
//
// remote 127.0.0.1 |
// remote 192.168.0.0/16 {
// set header X-Remote-Type private
// }
// A nested block is recognized when a line ends with an unquoted '{' (ignoring trailing whitespace).
func parseDoWithBlocks(src string) (handlers []CommandHandler, err error) {
pos := 0
length := len(src)
@@ -423,38 +400,12 @@ func parseDoWithBlocks(src string) (handlers []CommandHandler, err error) {
linePos++
}
logicalEnd := linePos
for logicalEnd < length && src[logicalEnd] != '\n' {
logicalEnd++
lineEnd := linePos
for lineEnd < length && src[lineEnd] != '\n' {
lineEnd++
}
for linePos < length && lineContinuationOperator(src, linePos, logicalEnd) != 0 {
nextPos := logicalEnd
if nextPos < length && src[nextPos] == '\n' {
nextPos++
}
for nextPos < length {
c := rune(src[nextPos])
if c == '\n' {
nextPos++
continue
}
if c == '\r' || unicode.IsSpace(c) {
nextPos++
continue
}
break
}
if nextPos >= length {
break
}
logicalEnd = nextPos
for logicalEnd < length && src[logicalEnd] != '\n' {
logicalEnd++
}
}
if linePos < length && lineEndsWithUnquotedOpenBrace(src, linePos, logicalEnd) {
if linePos < length && lineEndsWithUnquotedOpenBrace(src, linePos, lineEnd) {
h, next, err := parseAtBlockChain(src, linePos)
if err != nil {
return nil, err
@@ -466,10 +417,10 @@ func parseDoWithBlocks(src string) (handlers []CommandHandler, err error) {
}
// Not a nested block; parse the rest of this line as a command.
if lerr := appendLineCommand(src[pos:logicalEnd]); lerr != nil {
if lerr := appendLineCommand(src[pos:lineEnd]); lerr != nil {
return nil, lerr
}
pos = logicalEnd
pos = lineEnd
lineStart = true
continue
}

View File

@@ -71,38 +71,3 @@ func TestIfElseBlockCommandServeHTTP_ConditionalMatchedNilDoNotFallsThrough(t *t
require.NoError(t, err)
assert.False(t, elseCalled)
}
func TestParseDoWithBlocks_MultilineBlockHeaderContinuation(t *testing.T) {
tests := []struct {
name string
src string
}{
{
name: "or continuation",
src: `
remote 127.0.0.1 |
remote 192.168.0.0/16 {
set header X-Remote-Type private
}
`,
},
{
name: "and continuation",
src: `
method GET &
remote 127.0.0.1 {
set header X-Remote-Type private
}
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
handlers, err := parseDoWithBlocks(tt.src)
require.NoError(t, err)
require.Len(t, handlers, 1)
require.IsType(t, IfBlockCommand{}, handlers[0])
})
}
}

View File

@@ -456,8 +456,7 @@ func TestHTTPFlow_NestedBlocks_RemoteOverride(t *testing.T) {
err := parseRules(`
header X-Test-Header {
set header X-Remote-Type public
remote 127.0.0.1 |
remote 192.168.0.0/16 {
remote 127.0.0.1 | remote 192.168.0.0/16 {
set header X-Remote-Type private
}
}

View File

@@ -505,70 +505,62 @@ var (
andSeps = [256]uint8{'&': 1, '\n': 1}
)
// splitAnd splits a condition string into AND parts.
// It treats '&' and newline as AND separators, except when a line ends with
// an unescaped '|' (OR continuation), where the newline stays in the same part.
// Empty parts are omitted.
func indexAnd(s string) int {
for i := range s {
if andSeps[s[i]] != 0 {
return i
}
}
return -1
}
func countAnd(s string) int {
n := 0
for i := range s {
if andSeps[s[i]] != 0 {
n++
}
}
return n
}
// splitAnd splits a string by "&" and "\n" with all spaces removed.
// empty strings are not included in the result.
func splitAnd(s string) []string {
if s == "" {
return []string{}
}
result := []string{}
forEachAndPart(s, func(part string) {
result = append(result, part)
})
return result
}
func lineEndsWithUnescapedPipe(s string, start, end int) bool {
for i := end - 1; i >= start; i-- {
if asciiSpace[s[i]] != 0 {
continue
n := countAnd(s)
a := make([]string, n+1)
i := 0
for i < n {
end := indexAnd(s)
if end == -1 {
break
}
if s[i] != '|' {
return false
beg := 0
// trim leading spaces
for beg < end && asciiSpace[s[beg]] != 0 {
beg++
}
escapes := 0
for j := i - 1; j >= start && s[j] == '\\'; j-- {
escapes++
// trim trailing spaces
next := end + 1
for end-1 > beg && asciiSpace[s[end-1]] != 0 {
end--
}
return escapes%2 == 0
// skip empty segments
if end > beg {
a[i] = s[beg:end]
i++
}
s = s[next:]
}
return false
}
func advanceSplitState(s string, i *int, quote *byte, brackets *int) bool {
c := s[*i]
if *quote != 0 {
if c == '\\' && *i+1 < len(s) {
*i++
return true
}
if c == *quote {
*quote = 0
}
return true
s = strings.TrimSpace(s)
if s != "" {
a[i] = s
i++
}
switch c {
case '\\':
if *i+1 < len(s) {
*i++
return true
}
case '"', '\'', '`':
*quote = c
return true
case '(':
*brackets++
return true
case ')':
if *brackets > 0 {
*brackets--
}
return true
}
return false
return a[:i]
}
// splitPipe splits a string by "|" but respects quotes, brackets, and escaped characters.
@@ -586,26 +578,8 @@ func splitPipe(s string) []string {
}
func forEachAndPart(s string, fn func(part string)) {
quote := byte(0)
brackets := 0
start := 0
for i := 0; i <= len(s); i++ {
if i < len(s) {
c := s[i]
if advanceSplitState(s, &i, &quote, &brackets) {
continue
}
if c == '\n' {
if brackets > 0 || lineEndsWithUnescapedPipe(s, start, i) {
continue
}
} else if c != '&' || brackets > 0 {
continue
}
}
if i < len(s) && andSeps[s[i]] == 0 {
continue
}
@@ -623,14 +597,30 @@ func forEachPipePart(s string, fn func(part string)) {
start := 0
for i := 0; i < len(s); i++ {
if advanceSplitState(s, &i, &quote, &brackets) {
continue
}
if s[i] == '|' && brackets == 0 {
if part := strings.TrimSpace(s[start:i]); part != "" {
fn(part)
switch s[i] {
case '\\':
if i+1 < len(s) {
i++
}
case '"', '\'', '`':
if quote == 0 && brackets == 0 {
quote = s[i]
} else if s[i] == quote {
quote = 0
}
case '(':
brackets++
case ')':
if brackets > 0 {
brackets--
}
case '|':
if quote == 0 && brackets == 0 {
if part := strings.TrimSpace(s[start:i]); part != "" {
fn(part)
}
start = i + 1
}
start = i + 1
}
}
if start < len(s) {

View File

@@ -1,8 +1,6 @@
package rules
import (
"net/http"
"net/url"
"testing"
gperr "github.com/yusing/goutils/errs"
@@ -135,16 +133,6 @@ func TestSplitAnd(t *testing.T) {
input: " rule1\nrule2 & rule3 ",
want: []string{"rule1", "rule2", "rule3"},
},
{
name: "newline_after_pipe_is_or_continuation",
input: "path /abc |\npath /bcd",
want: []string{"path /abc |\npath /bcd"},
},
{
name: "newline_after_pipe_with_spaces_is_or_continuation",
input: "path /abc | \n path /bcd",
want: []string{"path /abc | \n path /bcd"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -292,11 +280,6 @@ func TestParseOn(t *testing.T) {
input: `method GET | path regex("^(_next/static|_next/image|favicon.ico).*$") | header Authorization`,
wantErr: nil,
},
{
name: "pipe_multiline_continuation",
input: "path /abc |\npath /bcd |",
wantErr: nil,
},
}
for _, tt := range tests {
@@ -311,18 +294,3 @@ func TestParseOn(t *testing.T) {
})
}
}
func TestRuleOnParse_MultilineOrContinuation(t *testing.T) {
var on RuleOn
err := on.Parse("path /abc |\npath /bcd |")
expect.NoError(t, err)
w := http.ResponseWriter(nil)
reqABC := &http.Request{URL: &url.URL{Path: "/abc"}}
reqBCD := &http.Request{URL: &url.URL{Path: "/bcd"}}
reqXYZ := &http.Request{URL: &url.URL{Path: "/xyz"}}
expect.Equal(t, on.Check(w, reqABC), true)
expect.Equal(t, on.Check(w, reqBCD), true)
expect.Equal(t, on.Check(w, reqXYZ), false)
}

View File

@@ -27,21 +27,22 @@ type (
Example:
proxy.app1.rules: |
default {
rewrite / /index.html
serve /var/www/goaccess
}
header Connection Upgrade & header Upgrade websocket {
bypass
}
- name: default
do: |
rewrite / /index.html
serve /var/www/goaccess
- name: ws
on: |
header Connection Upgrade
header Upgrade websocket
do: bypass
proxy.app2.rules: |
default {
bypass
}
method POST | method PUT {
error 403 Forbidden
}
- name: default
do: bypass
- name: block POST and PUT
on: method POST | method PUT
do: error 403 Forbidden
*/
//nolint:recvcheck
Rules []Rule

View File

@@ -1,10 +1,10 @@
package route
import (
"encoding/json"
"errors"
"strconv"
"github.com/bytedance/sonic"
gperr "github.com/yusing/goutils/errs"
)
@@ -59,7 +59,7 @@ func (s Scheme) MarshalJSON() ([]byte, error) {
func (s *Scheme) UnmarshalJSON(data []byte) error {
var v string
if err := sonic.Unmarshal(data, &v); err != nil {
if err := json.Unmarshal(data, &v); err != nil {
return err
}
return s.Parse(v)