330 lines
7.8 KiB
Go
330 lines
7.8 KiB
Go
package filters
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/AdguardTeam/AdGuardHome/util"
|
|
"github.com/AdguardTeam/golibs/jsonutil"
|
|
"github.com/AdguardTeam/golibs/log"
|
|
)
|
|
|
|
// Print to log and set HTTP error message
|
|
func httpError(r *http.Request, w http.ResponseWriter, code int, format string, args ...interface{}) {
|
|
text := fmt.Sprintf(format, args...)
|
|
log.Info("Filters: %s %s: %s", r.Method, r.URL, text)
|
|
http.Error(w, text, code)
|
|
}
|
|
|
|
// IsValidURL - return TRUE if URL or file path is valid
|
|
func IsValidURL(rawurl string) bool {
|
|
if filepath.IsAbs(rawurl) {
|
|
// this is a file path
|
|
return util.FileExists(rawurl)
|
|
}
|
|
|
|
url, err := url.ParseRequestURI(rawurl)
|
|
if err != nil {
|
|
return false //Couldn't even parse the rawurl
|
|
}
|
|
if len(url.Scheme) == 0 {
|
|
return false //No Scheme found
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (f *Filtering) getFilterModule(t string) Filters {
|
|
switch t {
|
|
case "blocklist":
|
|
return f.dnsBlocklist
|
|
|
|
case "whitelist":
|
|
return f.dnsAllowlist
|
|
|
|
case "proxylist":
|
|
return f.Proxylist
|
|
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (f *Filtering) restartMods(t string) {
|
|
fN := f.getFilterModule(t)
|
|
fN.NotifyObserver(EventBeforeUpdate)
|
|
fN.NotifyObserver(EventAfterUpdate)
|
|
}
|
|
|
|
func (f *Filtering) handleFilterAdd(w http.ResponseWriter, r *http.Request) {
|
|
type reqJSON struct {
|
|
Name string `json:"name"`
|
|
URL string `json:"url"`
|
|
Type string `json:"type"`
|
|
}
|
|
req := reqJSON{}
|
|
_, err := jsonutil.DecodeObject(&req, r.Body)
|
|
if err != nil {
|
|
httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
|
return
|
|
}
|
|
|
|
filterN := f.getFilterModule(req.Type)
|
|
if filterN == nil {
|
|
httpError(r, w, http.StatusBadRequest, "invalid type: %s", req.Type)
|
|
return
|
|
}
|
|
|
|
filt := Filter{
|
|
Enabled: true,
|
|
Name: req.Name,
|
|
URL: req.URL,
|
|
}
|
|
err = filterN.Add(filt)
|
|
if err != nil {
|
|
httpError(r, w, http.StatusBadRequest, "add filter: %s", err)
|
|
return
|
|
}
|
|
|
|
f.conf.ConfigModified()
|
|
|
|
f.restartMods(req.Type)
|
|
}
|
|
|
|
func (f *Filtering) handleFilterRemove(w http.ResponseWriter, r *http.Request) {
|
|
type reqJSON struct {
|
|
URL string `json:"url"`
|
|
Type string `json:"type"`
|
|
}
|
|
req := reqJSON{}
|
|
_, err := jsonutil.DecodeObject(&req, r.Body)
|
|
if err != nil {
|
|
httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
|
return
|
|
}
|
|
|
|
filterN := f.getFilterModule(req.Type)
|
|
if filterN == nil {
|
|
httpError(r, w, http.StatusBadRequest, "invalid type: %s", req.Type)
|
|
return
|
|
}
|
|
|
|
removed := filterN.Delete(req.URL)
|
|
if removed == nil {
|
|
httpError(r, w, http.StatusInternalServerError, "no filter with such URL")
|
|
return
|
|
}
|
|
|
|
f.conf.ConfigModified()
|
|
|
|
if removed.Enabled {
|
|
f.restartMods(req.Type)
|
|
}
|
|
|
|
err = os.Remove(removed.Path)
|
|
if err != nil {
|
|
log.Error("os.Remove: %s", err)
|
|
}
|
|
}
|
|
|
|
func (f *Filtering) handleFilterModify(w http.ResponseWriter, r *http.Request) {
|
|
type propsJSON struct {
|
|
Name string `json:"name"`
|
|
URL string `json:"url"`
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
type reqJSON struct {
|
|
URL string `json:"url"`
|
|
Type string `json:"type"`
|
|
Data propsJSON `json:"data"`
|
|
}
|
|
req := reqJSON{}
|
|
_, err := jsonutil.DecodeObject(&req, r.Body)
|
|
if err != nil {
|
|
httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
|
return
|
|
}
|
|
|
|
filterN := f.getFilterModule(req.Type)
|
|
if filterN == nil {
|
|
httpError(r, w, http.StatusBadRequest, "invalid type: %s", req.Type)
|
|
return
|
|
}
|
|
|
|
st, _, err := filterN.Modify(req.URL, req.Data.Enabled, req.Data.Name, req.Data.URL)
|
|
if err != nil {
|
|
httpError(r, w, http.StatusBadRequest, "%s", err)
|
|
return
|
|
}
|
|
|
|
f.conf.ConfigModified()
|
|
|
|
if st == StatusChangedEnabled ||
|
|
st == StatusChangedURL {
|
|
|
|
// TODO StatusChangedURL: delete old file
|
|
|
|
f.restartMods(req.Type)
|
|
}
|
|
}
|
|
|
|
func (f *Filtering) handleFilteringSetRules(w http.ResponseWriter, r *http.Request) {
|
|
body, err := ioutil.ReadAll(r.Body)
|
|
if err != nil {
|
|
httpError(r, w, http.StatusBadRequest, "Failed to read request body: %s", err)
|
|
return
|
|
}
|
|
|
|
f.conf.UserRules = strings.Split(string(body), "\n")
|
|
f.conf.ConfigModified()
|
|
f.restartMods("blocklist")
|
|
}
|
|
|
|
func (f *Filtering) handleFilteringRefresh(w http.ResponseWriter, r *http.Request) {
|
|
type reqJSON struct {
|
|
Type string `json:"type"`
|
|
}
|
|
req := reqJSON{}
|
|
_, err := jsonutil.DecodeObject(&req, r.Body)
|
|
if err != nil {
|
|
httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
|
return
|
|
}
|
|
|
|
filterN := f.getFilterModule(req.Type)
|
|
if filterN == nil {
|
|
httpError(r, w, http.StatusBadRequest, "invalid type: %s", req.Type)
|
|
return
|
|
}
|
|
|
|
filterN.Refresh(0)
|
|
}
|
|
|
|
type filterJSON struct {
|
|
ID int64 `json:"id"`
|
|
Enabled bool `json:"enabled"`
|
|
URL string `json:"url"`
|
|
Name string `json:"name"`
|
|
RulesCount uint32 `json:"rules_count"`
|
|
LastUpdated string `json:"last_updated"`
|
|
}
|
|
|
|
func filterToJSON(f Filter) filterJSON {
|
|
fj := filterJSON{
|
|
ID: int64(f.ID),
|
|
Enabled: f.Enabled,
|
|
URL: f.URL,
|
|
Name: f.Name,
|
|
RulesCount: uint32(f.RuleCount),
|
|
}
|
|
|
|
if !f.LastUpdated.IsZero() {
|
|
fj.LastUpdated = f.LastUpdated.Format(time.RFC3339)
|
|
}
|
|
|
|
return fj
|
|
}
|
|
|
|
// Get filtering configuration
|
|
func (f *Filtering) handleFilteringStatus(w http.ResponseWriter, r *http.Request) {
|
|
type respJSON struct {
|
|
Enabled bool `json:"enabled"`
|
|
Interval uint32 `json:"interval"` // in hours
|
|
|
|
Filters []filterJSON `json:"filters"`
|
|
WhitelistFilters []filterJSON `json:"whitelist_filters"`
|
|
UserRules []string `json:"user_rules"`
|
|
|
|
Proxylist []filterJSON `json:"proxy_filters"`
|
|
}
|
|
resp := respJSON{}
|
|
|
|
resp.Enabled = f.conf.Enabled
|
|
resp.Interval = f.conf.UpdateIntervalHours
|
|
resp.UserRules = f.conf.UserRules
|
|
|
|
f0 := f.dnsBlocklist.List(0)
|
|
f1 := f.dnsAllowlist.List(0)
|
|
f2 := f.Proxylist.List(0)
|
|
|
|
for _, filt := range f0 {
|
|
fj := filterToJSON(filt)
|
|
resp.Filters = append(resp.Filters, fj)
|
|
}
|
|
for _, filt := range f1 {
|
|
fj := filterToJSON(filt)
|
|
resp.WhitelistFilters = append(resp.WhitelistFilters, fj)
|
|
}
|
|
for _, filt := range f2 {
|
|
fj := filterToJSON(filt)
|
|
resp.Proxylist = append(resp.Proxylist, fj)
|
|
}
|
|
|
|
jsonVal, err := json.Marshal(resp)
|
|
if err != nil {
|
|
httpError(r, w, http.StatusInternalServerError, "json encode: %s", err)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write(jsonVal)
|
|
}
|
|
|
|
// Set filtering configuration
|
|
func (f *Filtering) handleFilteringConfig(w http.ResponseWriter, r *http.Request) {
|
|
type reqJSON struct {
|
|
Enabled bool `json:"enabled"`
|
|
Interval uint32 `json:"interval"`
|
|
}
|
|
req := reqJSON{}
|
|
_, err := jsonutil.DecodeObject(&req, r.Body)
|
|
if err != nil {
|
|
httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
|
return
|
|
}
|
|
if !CheckFiltersUpdateIntervalHours(req.Interval) {
|
|
httpError(r, w, http.StatusBadRequest, "Unsupported interval")
|
|
return
|
|
}
|
|
|
|
restart := false
|
|
if f.conf.Enabled != req.Enabled {
|
|
restart = true
|
|
}
|
|
f.conf.Enabled = req.Enabled
|
|
f.conf.UpdateIntervalHours = req.Interval
|
|
|
|
c := Conf{}
|
|
c.UpdateIntervalHours = req.Interval
|
|
f.dnsBlocklist.SetConfig(c)
|
|
f.dnsAllowlist.SetConfig(c)
|
|
f.Proxylist.SetConfig(c)
|
|
|
|
f.conf.ConfigModified()
|
|
|
|
if restart {
|
|
f.restartMods("blocklist")
|
|
}
|
|
}
|
|
|
|
// registerWebHandlers - register handlers
|
|
func (f *Filtering) registerWebHandlers() {
|
|
f.conf.HTTPRegister("GET", "/control/filtering/status", f.handleFilteringStatus)
|
|
f.conf.HTTPRegister("POST", "/control/filtering/config", f.handleFilteringConfig)
|
|
f.conf.HTTPRegister("POST", "/control/filtering/add_url", f.handleFilterAdd)
|
|
f.conf.HTTPRegister("POST", "/control/filtering/remove_url", f.handleFilterRemove)
|
|
f.conf.HTTPRegister("POST", "/control/filtering/set_url", f.handleFilterModify)
|
|
f.conf.HTTPRegister("POST", "/control/filtering/refresh", f.handleFilteringRefresh)
|
|
f.conf.HTTPRegister("POST", "/control/filtering/set_rules", f.handleFilteringSetRules)
|
|
}
|
|
|
|
// CheckFiltersUpdateIntervalHours - verify update interval
|
|
func CheckFiltersUpdateIntervalHours(i uint32) bool {
|
|
return i == 0 || i == 1 || i == 12 || i == 1*24 || i == 3*24 || i == 7*24
|
|
}
|