ezhik.

Veil Browser

Прототип
GitHub ↗ Скачать ZIP
model.go3.0 KB · goraw
package session

import (
	"errors"
	"fmt"
	"net/url"
	"strings"
	"time"
)

const (
	maxTabs         = 50
	maxHistoryItems = 200
	maxTitleLength  = 200
	maxURLLength    = 4096
)

type PrivacySettings struct {
	Trackers    bool `json:"trackers"`
	Cookies     bool `json:"cookies"`
	Fingerprint bool `json:"fingerprint"`
	WebRTC      bool `json:"webrtc"`
}

type Tab struct {
	ID           string   `json:"id"`
	Title        string   `json:"title"`
	URL          string   `json:"url"`
	History      []string `json:"history"`
	HistoryIndex int      `json:"historyIndex"`
	Private      bool     `json:"private"`
}

type State struct {
	Tabs        []Tab           `json:"tabs"`
	ActiveTabID string          `json:"activeTabId"`
	Settings    PrivacySettings `json:"settings"`
}

type Session struct {
	ID        string    `json:"-"`
	State     State     `json:"state"`
	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`
	ExpiresAt time.Time `json:"expiresAt"`
}

func DefaultState(tabID string) State {
	return State{
		Tabs: []Tab{{
			ID:           tabID,
			Title:        "Новая вкладка",
			URL:          "veil://start",
			History:      []string{"veil://start"},
			HistoryIndex: 0,
		}},
		ActiveTabID: tabID,
		Settings: PrivacySettings{
			Trackers:    true,
			Cookies:     true,
			Fingerprint: true,
			WebRTC:      true,
		},
	}
}

func (state State) Validate() error {
	if len(state.Tabs) == 0 || len(state.Tabs) > maxTabs {
		return fmt.Errorf("tabs must contain between 1 and %d items", maxTabs)
	}

	seen := make(map[string]struct{}, len(state.Tabs))
	activeTabFound := false
	for _, tab := range state.Tabs {
		if tab.ID == "" || len(tab.ID) > 128 {
			return errors.New("tab id is invalid")
		}
		if _, exists := seen[tab.ID]; exists {
			return errors.New("tab ids must be unique")
		}
		seen[tab.ID] = struct{}{}
		activeTabFound = activeTabFound || tab.ID == state.ActiveTabID

		if len(tab.Title) > maxTitleLength {
			return errors.New("tab title is too long")
		}
		if len(tab.History) == 0 || len(tab.History) > maxHistoryItems {
			return fmt.Errorf("tab history must contain between 1 and %d items", maxHistoryItems)
		}
		if tab.HistoryIndex < 0 || tab.HistoryIndex >= len(tab.History) {
			return errors.New("history index is out of range")
		}
		if err := validateBrowserURL(tab.URL); err != nil {
			return fmt.Errorf("tab url: %w", err)
		}
		for _, item := range tab.History {
			if err := validateBrowserURL(item); err != nil {
				return fmt.Errorf("history url: %w", err)
			}
		}
	}

	if !activeTabFound {
		return errors.New("active tab does not exist")
	}
	return nil
}

func validateBrowserURL(value string) error {
	if value == "veil://start" {
		return nil
	}
	if value == "" || len(value) > maxURLLength || strings.ContainsAny(value, "\r\n") {
		return errors.New("url is invalid")
	}
	parsed, err := url.ParseRequestURI(value)
	if err != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") || parsed.Host == "" {
		return errors.New("only http and https urls are allowed")
	}
	return nil
}