package rewriter import ( "context" "net/http" "net/url" "forge.cadoles.com/cadoles/bouncer/internal/proxy/director" "forge.cadoles.com/cadoles/bouncer/internal/rule" ruleHTTP "forge.cadoles.com/cadoles/bouncer/internal/rule/http" "forge.cadoles.com/cadoles/bouncer/internal/store" "github.com/pkg/errors" ) type RequestVars struct { Request RequestVar `expr:"request"` OriginalURL URLVar `expr:"original_url"` } type URLVar struct { Scheme string `expr:"scheme"` Opaque string `expr:"opaque"` User UserVar `expr:"user"` Host string `expr:"host"` Path string `expr:"path"` RawPath string `expr:"raw_path"` RawQuery string `expr:"raw_query"` Fragment string `expr:"fragment"` RawFragment string `expr:"raw_fragment"` } func fromURL(url *url.URL) URLVar { return URLVar{ Scheme: url.Scheme, Opaque: url.Opaque, User: UserVar{ Username: url.User.Username(), Password: func() string { passwd, _ := url.User.Password() return passwd }(), }, Host: url.Host, Path: url.Path, RawPath: url.RawPath, RawQuery: url.RawQuery, Fragment: url.Fragment, RawFragment: url.RawFragment, } } type UserVar struct { Username string `expr:"username"` Password string `expr:"password"` } type RequestVar struct { Method string `expr:"method"` URL URLVar `expr:"url"` RawURL string `expr:"raw_url"` Proto string `expr:"proto"` ProtoMajor int `expr:"proto_major"` ProtoMinor int `expr:"proto_minor"` Header map[string][]string `expr:"header"` ContentLength int64 `expr:"content_length"` TransferEncoding []string `expr:"transfer_encoding"` Host string `expr:"host"` Trailer map[string][]string `expr:"trailer"` RemoteAddr string `expr:"remote_addr"` RequestURI string `expr:"request_uri"` } func fromRequest(r *http.Request) RequestVar { return RequestVar{ Method: r.Method, URL: fromURL(r.URL), RawURL: r.URL.String(), Proto: r.Proto, ProtoMajor: r.ProtoMajor, ProtoMinor: r.ProtoMinor, Header: r.Header, ContentLength: r.ContentLength, TransferEncoding: r.TransferEncoding, Host: r.Host, Trailer: r.Trailer, RemoteAddr: r.RemoteAddr, RequestURI: r.RequestURI, } } func (l *Layer) applyRequestRules(ctx context.Context, r *http.Request, layer *store.Layer, options *LayerOptions) error { rules := options.Rules.Request if len(rules) == 0 { return nil } engine, err := l.getRequestRuleEngine(ctx, layer, options) if err != nil { return errors.WithStack(err) } originalURL, err := director.OriginalURL(ctx) if err != nil { return errors.WithStack(err) } vars := &RequestVars{ OriginalURL: fromURL(originalURL), Request: fromRequest(r), } ctx = ruleHTTP.WithRequest(ctx, r) if _, err := engine.Apply(ctx, vars); err != nil { return errors.WithStack(err) } return nil } func (l *Layer) getRequestRuleEngine(ctx context.Context, layer *store.Layer, options *LayerOptions) (*rule.Engine[*RequestVars], error) { key := string(layer.Proxy) + "-" + string(layer.Name) revisionedEngine := l.requestRuleEngineCache.Get(key) engine, err := revisionedEngine.Get(ctx, layer.Revision, options) if err != nil { return nil, errors.WithStack(err) } return engine, nil } type ResponseVars struct { OriginalURL URLVar `expr:"original_url"` Request RequestVar `expr:"request"` Response ResponseVar `expr:"response"` } type ResponseVar struct { Status string `expr:"status"` StatusCode int `expr:"status_code"` Proto string `expr:"proto"` ProtoMajor int `expr:"proto_major"` ProtoMinor int `expr:"proto_minor"` Header map[string][]string `expr:"header"` ContentLength int64 `expr:"content_length"` TransferEncoding []string `expr:"transfer_encoding"` Uncompressed bool `expr:"uncompressed"` Trailer map[string][]string `expr:"trailer"` } func (l *Layer) applyResponseRules(ctx context.Context, r *http.Response, layer *store.Layer, options *LayerOptions) error { rules := options.Rules.Response if len(rules) == 0 { return nil } engine, err := l.getResponseRuleEngine(ctx, layer, options) if err != nil { return errors.WithStack(err) } originalURL, err := director.OriginalURL(ctx) if err != nil { return errors.WithStack(err) } vars := &ResponseVars{ OriginalURL: fromURL(originalURL), Request: fromRequest(r.Request), Response: ResponseVar{ Proto: r.Proto, ProtoMajor: r.ProtoMajor, ProtoMinor: r.ProtoMinor, Header: r.Header, ContentLength: r.ContentLength, TransferEncoding: r.TransferEncoding, Trailer: r.Trailer, Status: r.Status, StatusCode: r.StatusCode, }, } ctx = ruleHTTP.WithResponse(ctx, r) ctx = ruleHTTP.WithRequest(ctx, r.Request) if _, err := engine.Apply(ctx, vars); err != nil { return errors.WithStack(err) } return nil } func (l *Layer) getResponseRuleEngine(ctx context.Context, layer *store.Layer, options *LayerOptions) (*rule.Engine[*ResponseVars], error) { key := string(layer.Proxy) + "-" + string(layer.Name) revisionedEngine := l.responseRuleEngineCache.Get(key) engine, err := revisionedEngine.Get(ctx, layer.Revision, options) if err != nil { return nil, errors.WithStack(err) } return engine, nil }