package fa import ( "context" "strings" ) // reqOverrideKey scopes the per-request override on a context. The // transport reads it; nothing outside this package can synthesize one. type reqOverrideKey struct{} // requestOverride is the resolved diff between the client's default // config and the options passed to a single call. The transport applies // it on every outbound request whose context carries one. // // Only request-level fields appear here. Client-only fields (rate limiter, // http.Client, retries, parser flags) are deliberately absent: passing the // corresponding Option to a call is a silent no-op, which is documented on // each Option. type requestOverride struct { cookies Cookies cf CFCookies sfw SFWMode userAgent string } // applyRequestOptions resolves per-call options on top of the client's // config and, if any request-level field actually changed, returns a // context carrying a *requestOverride for the transport to read. When no // options are passed or none of them touch request-level fields, the // original context is returned unchanged so the hot path stays // allocation-free. func (c *Client) applyRequestOptions(ctx context.Context, opts []Option) context.Context { if len(opts) == 0 { return ctx } cfg := c.cfg for _, o := range opts { if o != nil { o(&cfg) } } if cfg.cookies == c.cfg.cookies && cfg.cf == c.cfg.cf && cfg.sfw == c.cfg.sfw && cfg.userAgent == c.cfg.userAgent { return ctx } return context.WithValue(ctx, reqOverrideKey{}, &requestOverride{ cookies: cfg.cookies, cf: cfg.cf, sfw: cfg.sfw, userAgent: cfg.userAgent, }) } // requestOverrideFrom extracts the override the transport should apply // to this request, or nil if none was attached. func requestOverrideFrom(ctx context.Context) *requestOverride { if ctx == nil { return nil } ov, _ := ctx.Value(reqOverrideKey{}).(*requestOverride) return ov } // touchesCookies reports whether this override should replace the Cookie // header. A UA-only override leaves the header (and thus the jar's // cookies) alone. func (o *requestOverride) touchesCookies() bool { return o.cookies.A != "" || o.cookies.B != "" || o.cf.Clearance != "" || o.sfw != SFWAuto } // cookieHeader renders the override's cookies into a single Cookie // header value. The transport writes this verbatim, replacing whatever // the shared cookie jar produced from c.http's jar. func (o *requestOverride) cookieHeader() string { var parts []string if o.cookies.A != "" { parts = append(parts, "a="+o.cookies.A) } if o.cookies.B != "" { parts = append(parts, "b="+o.cookies.B) } if o.cf.Clearance != "" { parts = append(parts, "cf_clearance="+o.cf.Clearance) } switch o.sfw { case SFWOn: parts = append(parts, "sfw=1") case SFWOff: parts = append(parts, "sfw=0") } return strings.Join(parts, "; ") }