MITM proxy
This commit is contained in:
329
filters/filter_http.go
Normal file
329
filters/filter_http.go
Normal file
@@ -0,0 +1,329 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user