package browser import ( "context" "encoding/json" "fmt" "os" "path/filepath" "strings" ) // CookieFormat formato de archivo de cookies type CookieFormat string const ( CookieFormatJSON CookieFormat = "json" // JSON estándar CookieFormatNetscape CookieFormat = "netscape" // cookies.txt formato Netscape ) // CookieFilter filtro para búsqueda de cookies type CookieFilter struct { Domain string // Filtrar por dominio (ej: ".example.com") Name string // Filtrar por nombre exacto Path string // Filtrar por path } // GetAllCookies obtiene todas las cookies del navegador actual func (b *Browser) GetAllCookies(ctx context.Context) ([]*Cookie, error) { result, err := b.cdpClient.SendCommand(ctx, "Network.getAllCookies", nil) if err != nil { return nil, fmt.Errorf("error getting all cookies: %w", err) } var cookies []*Cookie if cookiesData, ok := result["cookies"].([]interface{}); ok { for _, cookieData := range cookiesData { if cookieMap, ok := cookieData.(map[string]interface{}); ok { cookie := parseCookieFromMap(cookieMap) cookies = append(cookies, cookie) } } } return cookies, nil } // FilterCookies obtiene cookies que coinciden con filtros func (b *Browser) FilterCookies(ctx context.Context, filter CookieFilter) ([]*Cookie, error) { allCookies, err := b.GetAllCookies(ctx) if err != nil { return nil, err } var filtered []*Cookie for _, cookie := range allCookies { match := true if filter.Domain != "" && !strings.Contains(cookie.Domain, filter.Domain) { match = false } if filter.Name != "" && cookie.Name != filter.Name { match = false } if filter.Path != "" && cookie.Path != filter.Path { match = false } if match { filtered = append(filtered, cookie) } } return filtered, nil } // ExportCookiesToFile exporta cookies a archivo func (b *Browser) ExportCookiesToFile(ctx context.Context, filepath string, format CookieFormat) error { cookies, err := b.GetAllCookies(ctx) if err != nil { return err } var content string switch format { case CookieFormatJSON: content, err = cookiesToJSON(cookies) case CookieFormatNetscape: content = cookiesToNetscape(cookies) default: return fmt.Errorf("unsupported format: %s", format) } if err != nil { return fmt.Errorf("error formatting cookies: %w", err) } if err := os.WriteFile(filepath, []byte(content), 0600); err != nil { return fmt.Errorf("error writing cookies file: %w", err) } return nil } // ImportCookiesFromFile importa cookies desde archivo func (b *Browser) ImportCookiesFromFile(ctx context.Context, filepath string, format CookieFormat) error { data, err := os.ReadFile(filepath) if err != nil { return fmt.Errorf("error reading cookies file: %w", err) } var cookies []*Cookie switch format { case CookieFormatJSON: cookies, err = cookiesFromJSON(data) case CookieFormatNetscape: cookies, err = cookiesFromNetscape(string(data)) default: return fmt.Errorf("unsupported format: %s", format) } if err != nil { return fmt.Errorf("error parsing cookies: %w", err) } // Establecer cada cookie for _, cookie := range cookies { if err := b.SetCookie(ctx, cookie); err != nil { return fmt.Errorf("error setting cookie %s: %w", cookie.Name, err) } } return nil } // DeleteCookiesByDomain elimina todas las cookies de un dominio específico func (b *Browser) DeleteCookiesByDomain(ctx context.Context, domain string) error { cookies, err := b.FilterCookies(ctx, CookieFilter{Domain: domain}) if err != nil { return err } for _, cookie := range cookies { params := map[string]interface{}{ "name": cookie.Name, "domain": cookie.Domain, "path": cookie.Path, } _, err := b.cdpClient.SendCommand(ctx, "Network.deleteCookies", params) if err != nil { return fmt.Errorf("error deleting cookie %s: %w", cookie.Name, err) } } return nil } // cookiesToJSON convierte cookies a formato JSON func cookiesToJSON(cookies []*Cookie) (string, error) { // Convertir a formato más simple para export type SimpleCookie struct { Name string `json:"name"` Value string `json:"value"` Domain string `json:"domain"` Path string `json:"path"` Expires float64 `json:"expires,omitempty"` HTTPOnly bool `json:"httpOnly,omitempty"` Secure bool `json:"secure,omitempty"` SameSite string `json:"sameSite,omitempty"` } simple := make([]SimpleCookie, len(cookies)) for i, c := range cookies { simple[i] = SimpleCookie{ Name: c.Name, Value: c.Value, Domain: c.Domain, Path: c.Path, Expires: c.Expires, HTTPOnly: c.HTTPOnly, Secure: c.Secure, SameSite: c.SameSite, } } bytes, err := json.MarshalIndent(simple, "", " ") if err != nil { return "", err } return string(bytes), nil } // cookiesFromJSON parsea cookies desde JSON func cookiesFromJSON(data []byte) ([]*Cookie, error) { type SimpleCookie struct { Name string `json:"name"` Value string `json:"value"` Domain string `json:"domain"` Path string `json:"path"` Expires float64 `json:"expires"` HTTPOnly bool `json:"httpOnly"` Secure bool `json:"secure"` SameSite string `json:"sameSite"` } var simple []SimpleCookie if err := json.Unmarshal(data, &simple); err != nil { return nil, err } cookies := make([]*Cookie, len(simple)) for i, s := range simple { cookies[i] = &Cookie{ Name: s.Name, Value: s.Value, Domain: s.Domain, Path: s.Path, Expires: s.Expires, HTTPOnly: s.HTTPOnly, Secure: s.Secure, SameSite: s.SameSite, } } return cookies, nil } // cookiesToNetscape convierte cookies a formato Netscape cookies.txt func cookiesToNetscape(cookies []*Cookie) string { var lines []string lines = append(lines, "# Netscape HTTP Cookie File") lines = append(lines, "# This is a generated file. Do not edit.") lines = append(lines, "") for _, c := range cookies { // Formato: domain flag path secure expiration name value domain := c.Domain if !strings.HasPrefix(domain, ".") { domain = "." + domain } flag := "TRUE" secure := "FALSE" if c.Secure { secure = "TRUE" } expiration := "0" if c.Expires > 0 { expiration = fmt.Sprintf("%.0f", c.Expires) } line := fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\t%s", domain, flag, c.Path, secure, expiration, c.Name, c.Value) lines = append(lines, line) } return strings.Join(lines, "\n") } // cookiesFromNetscape parsea cookies desde formato Netscape func cookiesFromNetscape(data string) ([]*Cookie, error) { var cookies []*Cookie lines := strings.Split(data, "\n") for _, line := range lines { line = strings.TrimSpace(line) if line == "" || strings.HasPrefix(line, "#") { continue } parts := strings.Split(line, "\t") if len(parts) != 7 { continue } cookie := &Cookie{ Domain: parts[0], Path: parts[2], Secure: parts[3] == "TRUE", Name: parts[5], Value: parts[6], } // Parse expiration if parts[4] != "0" { fmt.Sscanf(parts[4], "%f", &cookie.Expires) } cookies = append(cookies, cookie) } return cookies, nil } // parseCookieFromMap parsea una cookie desde un map CDP func parseCookieFromMap(data map[string]interface{}) *Cookie { cookie := &Cookie{} if name, ok := data["name"].(string); ok { cookie.Name = name } if value, ok := data["value"].(string); ok { cookie.Value = value } if domain, ok := data["domain"].(string); ok { cookie.Domain = domain } if path, ok := data["path"].(string); ok { cookie.Path = path } if expires, ok := data["expires"].(float64); ok { cookie.Expires = expires } if httpOnly, ok := data["httpOnly"].(bool); ok { cookie.HTTPOnly = httpOnly } if secure, ok := data["secure"].(bool); ok { cookie.Secure = secure } if sameSite, ok := data["sameSite"].(string); ok { cookie.SameSite = sameSite } return cookie } // Profile representa un perfil de navegador type Profile struct { Name string Path string } // ListProfiles lista todos los perfiles disponibles en ~/.navegator/profiles func ListProfiles() ([]Profile, error) { homeDir, err := os.UserHomeDir() if err != nil { return nil, err } profilesDir := filepath.Join(homeDir, ".navegator", "profiles") entries, err := os.ReadDir(profilesDir) if err != nil { if os.IsNotExist(err) { return []Profile{}, nil } return nil, err } var profiles []Profile for _, entry := range entries { if entry.IsDir() { profiles = append(profiles, Profile{ Name: entry.Name(), Path: filepath.Join(profilesDir, entry.Name()), }) } } return profiles, nil }