Files
navegator/pkg/browser/profile_cookies.go
T
Developer cbefb93020 feat: gestión avanzada de cookies
Implementa sistema completo de import/export y gestión de cookies.

Incluye:
- GetAllCookies() y FilterCookies() para búsqueda
- ExportCookiesToFile() / ImportCookiesFromFile() en JSON y Netscape
- DeleteCookiesByDomain() para limpieza
- ListProfiles() para gestión de perfiles
- Comando CLI cookies.go con subcomandos

Formatos soportados: JSON estándar y Netscape cookies.txt

Archivo: pkg/browser/profile_cookies.go, cmd/cookies.go
2026-03-25 00:47:52 +01:00

366 lines
8.5 KiB
Go

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
}