f63f36805b
When a rewrite rule has both match and rewriter set, the body is only sent to the rewriter plugin if it matches the pattern. A nil pattern (non-empty match) means unconditional rewrite via the plugin. Coincidence: fix indentation drift in the previous commit's rewrite condition blocks (tab alignment was off by one level).
142 lines
3.4 KiB
Go
142 lines
3.4 KiB
Go
package forwarder
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/go-gost/core/chain"
|
|
"github.com/go-gost/core/logger"
|
|
xnet "github.com/go-gost/x/internal/net"
|
|
xrecorder "github.com/go-gost/x/recorder"
|
|
"golang.org/x/net/http/httpguts"
|
|
)
|
|
|
|
func upgradeType(h http.Header) string {
|
|
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
|
|
return ""
|
|
}
|
|
return h.Get("Upgrade")
|
|
}
|
|
|
|
func (h *Sniffer) handleUpgradeResponse(ctx context.Context, rw, cc io.ReadWriteCloser, req *http.Request, res *http.Response, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
|
|
reqUpType := upgradeType(req.Header)
|
|
resUpType := upgradeType(res.Header)
|
|
if !strings.EqualFold(reqUpType, resUpType) {
|
|
return fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType)
|
|
}
|
|
|
|
res.Body = nil
|
|
if err := res.Write(rw); err != nil {
|
|
return fmt.Errorf("response write: %v", err)
|
|
}
|
|
|
|
if reqUpType == "websocket" && h.Websocket {
|
|
return h.sniffingWebsocketFrame(ctx, rw, cc, ro, log)
|
|
}
|
|
|
|
return xnet.Pipe(ctx, rw, cc)
|
|
}
|
|
|
|
func rewriteRespBody(ctx context.Context, resp *http.Response, rewrites ...chain.HTTPBodyRewriteSettings) error {
|
|
if resp == nil || len(rewrites) == 0 || resp.ContentLength <= 0 {
|
|
return nil
|
|
}
|
|
|
|
if encoding := resp.Header.Get("Content-Encoding"); encoding != "" {
|
|
return nil
|
|
}
|
|
|
|
body, err := drainBody(resp.Body)
|
|
if err != nil || body == nil {
|
|
return err
|
|
}
|
|
|
|
contentType, _, _ := strings.Cut(resp.Header.Get("Content-Type"), ";")
|
|
for _, rewrite := range rewrites {
|
|
rewriteType := rewrite.Type
|
|
if rewriteType == "" {
|
|
rewriteType = "text/html"
|
|
}
|
|
if rewriteType != "*" && !strings.Contains(rewriteType, contentType) {
|
|
continue
|
|
}
|
|
|
|
if rewrite.Rewriter != nil {
|
|
if rewrite.Pattern == nil || rewrite.Pattern.Match(body) {
|
|
body, err = rewrite.Rewriter.Rewrite(ctx, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else if rewrite.Pattern != nil {
|
|
body = rewrite.Pattern.ReplaceAll(body, rewrite.Replacement)
|
|
}
|
|
}
|
|
|
|
resp.Body = io.NopCloser(bytes.NewReader(body))
|
|
resp.ContentLength = int64(len(body))
|
|
|
|
return nil
|
|
}
|
|
|
|
func drainBody(b io.ReadCloser) (body []byte, err error) {
|
|
if b == nil || b == http.NoBody {
|
|
return nil, nil
|
|
}
|
|
var buf bytes.Buffer
|
|
if _, err = buf.ReadFrom(b); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = b.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
func rewriteReqBody(ctx context.Context, req *http.Request, rewrites ...chain.HTTPBodyRewriteSettings) error {
|
|
if req == nil || len(rewrites) == 0 || req.Body == nil || req.ContentLength <= 0 {
|
|
return nil
|
|
}
|
|
|
|
if encoding := req.Header.Get("Content-Encoding"); encoding != "" {
|
|
return nil
|
|
}
|
|
|
|
body, err := drainBody(req.Body)
|
|
if err != nil || body == nil {
|
|
return err
|
|
}
|
|
|
|
contentType, _, _ := strings.Cut(req.Header.Get("Content-Type"), ";")
|
|
for _, rewrite := range rewrites {
|
|
rewriteType := rewrite.Type
|
|
if rewriteType == "" {
|
|
rewriteType = "text/html"
|
|
}
|
|
if rewriteType != "*" && !strings.Contains(rewriteType, contentType) {
|
|
continue
|
|
}
|
|
|
|
if rewrite.Rewriter != nil {
|
|
if rewrite.Pattern == nil || rewrite.Pattern.Match(body) {
|
|
body, err = rewrite.Rewriter.Rewrite(ctx, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else if rewrite.Pattern != nil {
|
|
body = rewrite.Pattern.ReplaceAll(body, rewrite.Replacement)
|
|
}
|
|
}
|
|
|
|
req.Body = io.NopCloser(bytes.NewReader(body))
|
|
req.ContentLength = int64(len(body))
|
|
|
|
return nil
|
|
}
|
|
|