package filtering import ( "encoding/json" "fmt" "net/http" "time" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/schedule" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/urlfilter/rules" "golang.org/x/exp/slices" ) // serviceRules maps a service ID to its filtering rules. var serviceRules map[string][]*rules.NetworkRule // serviceIDs contains service IDs sorted alphabetically. var serviceIDs []string // initBlockedServices initializes package-level blocked service data. func initBlockedServices() { l := len(blockedServices) serviceIDs = make([]string, l) serviceRules = make(map[string][]*rules.NetworkRule, l) for i, s := range blockedServices { netRules := make([]*rules.NetworkRule, 0, len(s.Rules)) for _, text := range s.Rules { rule, err := rules.NewNetworkRule(text, BlockedSvcsListID) if err != nil { log.Error("parsing blocked service %q rule %q: %s", s.ID, text, err) continue } netRules = append(netRules, rule) } serviceIDs[i] = s.ID serviceRules[s.ID] = netRules } slices.Sort(serviceIDs) log.Debug("filtering: initialized %d services", l) } // BlockedServices is the configuration of blocked services. type BlockedServices struct { // Schedule is blocked services schedule for every day of the week. Schedule *schedule.Weekly `yaml:"schedule"` // IDs is the names of blocked services. IDs []string `yaml:"ids"` } // Clone returns a deep copy of blocked services. func (s *BlockedServices) Clone() (c *BlockedServices) { if s == nil { return nil } return &BlockedServices{ Schedule: s.Schedule.Clone(), IDs: slices.Clone(s.IDs), } } // Validate returns an error if blocked services contain unknown service ID. s // must not be nil. func (s *BlockedServices) Validate() (err error) { for _, id := range s.IDs { _, ok := serviceRules[id] if !ok { return fmt.Errorf("unknown blocked-service %q", id) } } return nil } // ApplyBlockedServices - set blocked services settings for this DNS request func (d *DNSFilter) ApplyBlockedServices(setts *Settings) { d.confLock.RLock() defer d.confLock.RUnlock() setts.ServicesRules = []ServiceEntry{} bsvc := d.BlockedServices // TODO(s.chzhen): Use startTime from [dnsforward.dnsContext]. if !bsvc.Schedule.Contains(time.Now()) { d.ApplyBlockedServicesList(setts, bsvc.IDs) } } // ApplyBlockedServicesList appends filtering rules to the settings. func (d *DNSFilter) ApplyBlockedServicesList(setts *Settings, list []string) { for _, name := range list { rules, ok := serviceRules[name] if !ok { log.Error("unknown service name: %s", name) continue } setts.ServicesRules = append(setts.ServicesRules, ServiceEntry{ Name: name, Rules: rules, }) } } func (d *DNSFilter) handleBlockedServicesIDs(w http.ResponseWriter, r *http.Request) { _ = aghhttp.WriteJSONResponse(w, r, serviceIDs) } func (d *DNSFilter) handleBlockedServicesAll(w http.ResponseWriter, r *http.Request) { _ = aghhttp.WriteJSONResponse(w, r, struct { BlockedServices []blockedService `json:"blocked_services"` }{ BlockedServices: blockedServices, }) } func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) { d.confLock.RLock() list := d.Config.BlockedServices.IDs d.confLock.RUnlock() _ = aghhttp.WriteJSONResponse(w, r, list) } func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) { list := []string{} err := json.NewDecoder(r.Body).Decode(&list) if err != nil { aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err) return } d.confLock.Lock() d.Config.BlockedServices.IDs = list d.confLock.Unlock() log.Debug("Updated blocked services list: %d", len(list)) d.Config.ConfigModified() }