cbefb93020
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
366 lines
8.5 KiB
Go
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
|
|
}
|