Files
goclaw/cmd/browser_cookie_provider.go
2026-05-24 11:27:02 +07:00

132 lines
3.4 KiB
Go

package cmd
import (
"context"
"fmt"
"net/url"
"strings"
"time"
"github.com/go-rod/rod/lib/proto"
"github.com/google/uuid"
"github.com/nextlevelbuilder/goclaw/internal/store"
"github.com/nextlevelbuilder/goclaw/pkg/browser"
)
type storeBrowserCookieProvider struct {
cookies store.BrowserCookieStore
}
func newStoreBrowserCookieProvider(cookies store.BrowserCookieStore) browser.CookieProvider {
if cookies == nil {
return nil
}
return &storeBrowserCookieProvider{cookies: cookies}
}
func (p *storeBrowserCookieProvider) CookiesForURL(ctx context.Context, scope browser.BrowserScope, targetURL string) ([]*proto.NetworkCookieParam, error) {
u, err := url.Parse(targetURL)
if err != nil {
return nil, fmt.Errorf("parse target url: %w", err)
}
if u.Scheme != "http" && u.Scheme != "https" {
return nil, nil
}
storeScope, err := browserScopeToCookieScope(scope)
if err != nil {
return nil, err
}
cookies, err := p.cookies.List(ctx, storeScope, store.BrowserCookieFilter{})
if err != nil {
return nil, err
}
host := strings.ToLower(u.Hostname())
path := u.EscapedPath()
if path == "" {
path = "/"
}
now := time.Now().UTC()
params := make([]*proto.NetworkCookieParam, 0, len(cookies))
for _, c := range cookies {
if !browserCookieMatchesURL(c, host, path, now) {
continue
}
param := &proto.NetworkCookieParam{
Name: c.Name,
Value: c.Value,
URL: targetURL,
Path: c.Path,
Secure: c.Secure,
HTTPOnly: c.HTTPOnly,
SameSite: browserCookieSameSite(c.SameSite),
}
if strings.HasPrefix(c.Domain, ".") {
param.Domain = c.Domain
}
if c.ExpiresAt != nil {
param.Expires = proto.TimeSinceEpoch(float64(c.ExpiresAt.Unix()))
}
params = append(params, param)
}
return params, nil
}
func browserScopeToCookieScope(scope browser.BrowserScope) (store.BrowserCookieScope, error) {
tenantID := store.MasterTenantID
if strings.TrimSpace(scope.TenantID) != "" {
parsed, err := uuid.Parse(strings.TrimSpace(scope.TenantID))
if err != nil {
return store.BrowserCookieScope{}, fmt.Errorf("invalid browser tenant scope: %w", err)
}
tenantID = parsed
}
cookieScope := store.BrowserCookieScope{
TenantID: tenantID,
UserID: strings.TrimSpace(scope.UserID),
AgentID: strings.TrimSpace(scope.AgentID),
}
if err := cookieScope.Validate(); err != nil {
return store.BrowserCookieScope{}, err
}
return cookieScope, nil
}
func browserCookieMatchesURL(c store.BrowserCookie, host, requestPath string, now time.Time) bool {
domain := strings.ToLower(strings.TrimSpace(c.Domain))
if domain == "" {
return false
}
if c.ExpiresAt != nil && !c.ExpiresAt.After(now) {
return false
}
hostOnly := !strings.HasPrefix(domain, ".")
matchDomain := strings.TrimPrefix(domain, ".")
if hostOnly {
if host != matchDomain {
return false
}
} else if host != matchDomain && !strings.HasSuffix(host, "."+matchDomain) {
return false
}
cookiePath := c.Path
if cookiePath == "" {
cookiePath = "/"
}
return requestPath == cookiePath || strings.HasPrefix(requestPath, strings.TrimRight(cookiePath, "/")+"/")
}
func browserCookieSameSite(value string) proto.NetworkCookieSameSite {
switch strings.ToLower(strings.TrimSpace(value)) {
case "strict":
return proto.NetworkCookieSameSiteStrict
case "lax":
return proto.NetworkCookieSameSiteLax
case "none", "no_restriction", "no-restriction":
return proto.NetworkCookieSameSiteNone
default:
return ""
}
}