all repos — honk @ 5f05bd2a3f027a410dfa5ccc49ad1a31c759fbfa

my fork of honk

hfcs.go (view raw)

  1//
  2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
  3//
  4// Permission to use, copy, modify, and distribute this software for any
  5// purpose with or without fee is hereby granted, provided that the above
  6// copyright notice and this permission notice appear in all copies.
  7//
  8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 15
 16package main
 17
 18import (
 19	"log"
 20	"net/http"
 21	"regexp"
 22	"sort"
 23	"time"
 24
 25	"humungus.tedunangst.com/r/webs/cache"
 26)
 27
 28type Filter struct {
 29	ID              int64      `json:"-"`
 30	Actions         []filtType `json:"-"`
 31	Name            string
 32	Date            time.Time
 33	Actor           string `json:",omitempty"`
 34	IncludeAudience bool   `json:",omitempty"`
 35	Text            string `json:",omitempty"`
 36	re_text         *regexp.Regexp
 37	IsAnnounce      bool   `json:",omitempty"`
 38	AnnounceOf      string `json:",omitempty"`
 39	Reject          bool   `json:",omitempty"`
 40	SkipMedia       bool   `json:",omitempty"`
 41	Hide            bool   `json:",omitempty"`
 42	Collapse        bool   `json:",omitempty"`
 43	Rewrite         string `json:",omitempty"`
 44	re_rewrite      *regexp.Regexp
 45	Replace         string `json:",omitempty"`
 46}
 47
 48type filtType uint
 49
 50const (
 51	filtNone filtType = iota
 52	filtAny
 53	filtReject
 54	filtSkipMedia
 55	filtHide
 56	filtCollapse
 57	filtRewrite
 58)
 59
 60var filtNames = []string{"None", "Any", "Reject", "SkipMedia", "Hide", "Collapse", "Rewrite"}
 61
 62func (ft filtType) String() string {
 63	return filtNames[ft]
 64}
 65
 66type afiltermap map[filtType][]*Filter
 67
 68var filtcache = cache.New(cache.Options{Filler: func(userid int64) (afiltermap, bool) {
 69	rows, err := stmtGetFilters.Query(userid)
 70	if err != nil {
 71		log.Printf("error querying filters: %s", err)
 72		return nil, false
 73	}
 74	defer rows.Close()
 75
 76	filtmap := make(afiltermap)
 77	for rows.Next() {
 78		filt := new(Filter)
 79		var j string
 80		var filterid int64
 81		err = rows.Scan(&filterid, &j)
 82		if err == nil {
 83			err = unjsonify(j, filt)
 84		}
 85		if err != nil {
 86			log.Printf("error scanning filter: %s", err)
 87			continue
 88		}
 89		if filt.Text != "" {
 90			filt.re_text, err = regexp.Compile("\\b(?i:" + filt.Text + ")\\b")
 91			if err != nil {
 92				log.Printf("error compiling filter text: %s", err)
 93				continue
 94			}
 95		}
 96		if filt.Rewrite != "" {
 97			filt.re_rewrite, err = regexp.Compile("\\b(?i:" + filt.Rewrite + ")\\b")
 98			if err != nil {
 99				log.Printf("error compiling filter rewrite: %s", err)
100				continue
101			}
102		}
103		filt.ID = filterid
104		if filt.Reject {
105			filt.Actions = append(filt.Actions, filtReject)
106			filtmap[filtReject] = append(filtmap[filtReject], filt)
107		}
108		if filt.SkipMedia {
109			filt.Actions = append(filt.Actions, filtSkipMedia)
110			filtmap[filtSkipMedia] = append(filtmap[filtSkipMedia], filt)
111		}
112		if filt.Hide {
113			filt.Actions = append(filt.Actions, filtHide)
114			filtmap[filtHide] = append(filtmap[filtHide], filt)
115		}
116		if filt.Collapse {
117			filt.Actions = append(filt.Actions, filtCollapse)
118			filtmap[filtCollapse] = append(filtmap[filtCollapse], filt)
119		}
120		if filt.Rewrite != "" {
121			filt.Actions = append(filt.Actions, filtRewrite)
122			filtmap[filtRewrite] = append(filtmap[filtRewrite], filt)
123		}
124		filtmap[filtAny] = append(filtmap[filtAny], filt)
125	}
126	sorting := filtmap[filtAny]
127	sort.Slice(filtmap[filtAny], func(i, j int) bool {
128		return sorting[i].Name < sorting[j].Name
129	})
130	return filtmap, true
131}})
132
133func getfilters(userid int64, scope filtType) []*Filter {
134	var filtmap afiltermap
135	ok := filtcache.Get(userid, &filtmap)
136	if ok {
137		return filtmap[scope]
138	}
139	return nil
140}
141
142func rejectorigin(userid int64, origin string) bool {
143	if o := originate(origin); o != "" {
144		origin = o
145	}
146	filts := getfilters(userid, filtReject)
147	for _, f := range filts {
148		if f.IsAnnounce || f.Text != "" {
149			continue
150		}
151		if f.Actor == origin {
152			log.Printf("rejecting origin: %s", origin)
153			return true
154		}
155	}
156	return false
157}
158
159func rejectactor(userid int64, actor string) bool {
160	origin := originate(actor)
161	filts := getfilters(userid, filtReject)
162	for _, f := range filts {
163		if f.IsAnnounce || f.Text != "" {
164			continue
165		}
166		if f.Actor == actor || (origin != "" && f.Actor == origin) {
167			log.Printf("rejecting actor: %s", actor)
168			return true
169		}
170	}
171	return false
172}
173
174func stealthmode(userid int64, r *http.Request) bool {
175	agent := r.UserAgent()
176	agent = originate(agent)
177	if agent != "" {
178		fake := rejectorigin(userid, agent)
179		if fake {
180			log.Printf("faking 404 for %s", agent)
181			return true
182		}
183	}
184	return false
185}
186
187func matchfilter(h *Honk, f *Filter) bool {
188	match := true
189	if match && f.Actor != "" {
190		match = false
191		if f.Actor == h.Honker || f.Actor == h.Oonker {
192			match = true
193		}
194		if !match && (f.Actor == originate(h.Honker) ||
195			f.Actor == originate(h.Oonker) ||
196			f.Actor == originate(h.XID)) {
197			match = true
198		}
199		if !match && f.IncludeAudience {
200			for _, a := range h.Audience {
201				if f.Actor == a || f.Actor == originate(a) {
202					match = true
203					break
204				}
205			}
206		}
207	}
208	if match && f.IsAnnounce {
209		match = false
210		if (f.AnnounceOf == "" && h.Oonker != "") || f.AnnounceOf == h.Oonker ||
211			f.AnnounceOf == originate(h.Oonker) {
212			match = true
213		}
214	}
215	if match && f.Text != "" {
216		match = false
217		re := f.re_text
218		if re.MatchString(h.Noise) || re.MatchString(h.Precis) {
219			match = true
220		}
221		if !match {
222			for _, d := range h.Donks {
223				if re.MatchString(d.Desc) {
224					match = true
225				}
226			}
227		}
228	}
229	return match
230}
231
232func rejectxonk(xonk *Honk) bool {
233	filts := getfilters(xonk.UserID, filtReject)
234	for _, f := range filts {
235		if matchfilter(xonk, f) {
236			log.Printf("rejecting %s because %s", xonk.XID, f.Actor)
237			return true
238		}
239	}
240	return false
241}
242
243func skipMedia(xonk *Honk) bool {
244	filts := getfilters(xonk.UserID, filtSkipMedia)
245	for _, f := range filts {
246		if matchfilter(xonk, f) {
247			return true
248		}
249	}
250	return false
251}
252
253func unsee(userid int64, h *Honk) {
254	filts := getfilters(userid, filtCollapse)
255	for _, f := range filts {
256		if matchfilter(h, f) {
257			bad := f.Text
258			if f.Actor != "" {
259				bad = f.Actor
260			}
261			if h.Precis == "" {
262				h.Precis = bad
263			}
264			h.Open = ""
265			break
266		}
267	}
268	filts = getfilters(userid, filtRewrite)
269	for _, f := range filts {
270		if matchfilter(h, f) {
271			h.Noise = f.re_rewrite.ReplaceAllString(h.Noise, f.Replace)
272		}
273	}
274}
275
276func osmosis(honks []*Honk, userid int64) []*Honk {
277	filts := getfilters(userid, filtHide)
278	j := 0
279outer:
280	for _, h := range honks {
281		for _, f := range filts {
282			if matchfilter(h, f) {
283				continue outer
284			}
285		}
286		honks[j] = h
287		j++
288	}
289	honks = honks[0:j]
290	return honks
291}