all repos — honk @ cd75a5ab453f4919d188181af6115ada258f4a5d

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	Expiration      time.Time
 47}
 48
 49type filtType uint
 50
 51const (
 52	filtNone filtType = iota
 53	filtAny
 54	filtReject
 55	filtSkipMedia
 56	filtHide
 57	filtCollapse
 58	filtRewrite
 59)
 60
 61var filtNames = []string{"None", "Any", "Reject", "SkipMedia", "Hide", "Collapse", "Rewrite"}
 62
 63func (ft filtType) String() string {
 64	return filtNames[ft]
 65}
 66
 67type afiltermap map[filtType][]*Filter
 68
 69var filtcache *cache.Cache
 70
 71func init() {
 72	// resolve init loop
 73	filtcache = cache.New(cache.Options{Filler: filtcachefiller})
 74}
 75
 76func filtcachefiller(userid int64) (afiltermap, bool) {
 77	rows, err := stmtGetFilters.Query(userid)
 78	if err != nil {
 79		log.Printf("error querying filters: %s", err)
 80		return nil, false
 81	}
 82	defer rows.Close()
 83
 84	now := time.Now()
 85
 86	var expflush time.Time
 87
 88	filtmap := make(afiltermap)
 89	for rows.Next() {
 90		filt := new(Filter)
 91		var j string
 92		var filterid int64
 93		err = rows.Scan(&filterid, &j)
 94		if err == nil {
 95			err = unjsonify(j, filt)
 96		}
 97		if err != nil {
 98			log.Printf("error scanning filter: %s", err)
 99			continue
100		}
101		if !filt.Expiration.IsZero() {
102			if filt.Expiration.Before(now) {
103				continue
104			}
105			if expflush.IsZero() || filt.Expiration.Before(expflush) {
106				expflush = filt.Expiration
107			}
108		}
109		if filt.Text != "" {
110			filt.re_text, err = regexp.Compile("\\b(?i:" + filt.Text + ")\\b")
111			if err != nil {
112				log.Printf("error compiling filter text: %s", err)
113				continue
114			}
115		}
116		if filt.Rewrite != "" {
117			filt.re_rewrite, err = regexp.Compile("\\b(?i:" + filt.Rewrite + ")\\b")
118			if err != nil {
119				log.Printf("error compiling filter rewrite: %s", err)
120				continue
121			}
122		}
123		filt.ID = filterid
124		if filt.Reject {
125			filt.Actions = append(filt.Actions, filtReject)
126			filtmap[filtReject] = append(filtmap[filtReject], filt)
127		}
128		if filt.SkipMedia {
129			filt.Actions = append(filt.Actions, filtSkipMedia)
130			filtmap[filtSkipMedia] = append(filtmap[filtSkipMedia], filt)
131		}
132		if filt.Hide {
133			filt.Actions = append(filt.Actions, filtHide)
134			filtmap[filtHide] = append(filtmap[filtHide], filt)
135		}
136		if filt.Collapse {
137			filt.Actions = append(filt.Actions, filtCollapse)
138			filtmap[filtCollapse] = append(filtmap[filtCollapse], filt)
139		}
140		if filt.Rewrite != "" {
141			filt.Actions = append(filt.Actions, filtRewrite)
142			filtmap[filtRewrite] = append(filtmap[filtRewrite], filt)
143		}
144		filtmap[filtAny] = append(filtmap[filtAny], filt)
145	}
146	sorting := filtmap[filtAny]
147	sort.Slice(filtmap[filtAny], func(i, j int) bool {
148		return sorting[i].Name < sorting[j].Name
149	})
150	if !expflush.IsZero() {
151		dur := expflush.Sub(now)
152		go filtcacheclear(userid, dur)
153	}
154	return filtmap, true
155}
156
157func filtcacheclear(userid int64, dur time.Duration) {
158	time.Sleep(dur + time.Second)
159	filtcache.Clear(userid)
160}
161
162func getfilters(userid int64, scope filtType) []*Filter {
163	var filtmap afiltermap
164	ok := filtcache.Get(userid, &filtmap)
165	if ok {
166		return filtmap[scope]
167	}
168	return nil
169}
170
171func rejectorigin(userid int64, origin string) bool {
172	if o := originate(origin); o != "" {
173		origin = o
174	}
175	filts := getfilters(userid, filtReject)
176	for _, f := range filts {
177		if f.IsAnnounce || f.Text != "" {
178			continue
179		}
180		if f.Actor == origin {
181			log.Printf("rejecting origin: %s", origin)
182			return true
183		}
184	}
185	return false
186}
187
188func rejectactor(userid int64, actor string) bool {
189	origin := originate(actor)
190	filts := getfilters(userid, filtReject)
191	for _, f := range filts {
192		if f.IsAnnounce || f.Text != "" {
193			continue
194		}
195		if f.Actor == actor || (origin != "" && f.Actor == origin) {
196			log.Printf("rejecting actor: %s", actor)
197			return true
198		}
199	}
200	return false
201}
202
203func stealthmode(userid int64, r *http.Request) bool {
204	agent := r.UserAgent()
205	agent = originate(agent)
206	if agent != "" {
207		fake := rejectorigin(userid, agent)
208		if fake {
209			log.Printf("faking 404 for %s", agent)
210			return true
211		}
212	}
213	return false
214}
215
216func matchfilter(h *Honk, f *Filter) bool {
217	return matchfilterX(h, f) != ""
218}
219
220func matchfilterX(h *Honk, f *Filter) string {
221	rv := ""
222	match := true
223	if match && f.Actor != "" {
224		match = false
225		if f.Actor == h.Honker || f.Actor == h.Oonker {
226			match = true
227			rv = f.Actor
228		}
229		if !match && (f.Actor == originate(h.Honker) ||
230			f.Actor == originate(h.Oonker) ||
231			f.Actor == originate(h.XID)) {
232			match = true
233			rv = f.Actor
234		}
235		if !match && f.IncludeAudience {
236			for _, a := range h.Audience {
237				if f.Actor == a || f.Actor == originate(a) {
238					match = true
239					rv = f.Actor
240					break
241				}
242			}
243		}
244	}
245	if match && f.IsAnnounce {
246		match = false
247		if (f.AnnounceOf == "" && h.Oonker != "") || f.AnnounceOf == h.Oonker ||
248			f.AnnounceOf == originate(h.Oonker) {
249			match = true
250			rv += " announce"
251		}
252	}
253	if match && f.Text != "" {
254		match = false
255		re := f.re_text
256		m := re.FindString(h.Precis)
257		if m == "" {
258			m = re.FindString(h.Noise)
259		}
260		if m == "" {
261			for _, d := range h.Donks {
262				m = re.FindString(d.Desc)
263				if m != "" {
264					break
265				}
266			}
267		}
268		if m != "" {
269			match = true
270			rv = m
271		}
272	}
273	if match {
274		return rv
275	}
276	return ""
277}
278
279func rejectxonk(xonk *Honk) bool {
280	filts := getfilters(xonk.UserID, filtReject)
281	for _, f := range filts {
282		if matchfilter(xonk, f) {
283			log.Printf("rejecting %s because %s", xonk.XID, f.Actor)
284			return true
285		}
286	}
287	return false
288}
289
290func skipMedia(xonk *Honk) bool {
291	filts := getfilters(xonk.UserID, filtSkipMedia)
292	for _, f := range filts {
293		if matchfilter(xonk, f) {
294			return true
295		}
296	}
297	return false
298}
299
300func unsee(userid int64, h *Honk) {
301	filts := getfilters(userid, filtCollapse)
302	for _, f := range filts {
303		if bad := matchfilterX(h, f); bad != "" {
304			if h.Precis == "" {
305				h.Precis = bad
306			}
307			h.Open = ""
308			break
309		}
310	}
311	filts = getfilters(userid, filtRewrite)
312	for _, f := range filts {
313		if matchfilter(h, f) {
314			h.Noise = f.re_rewrite.ReplaceAllString(h.Noise, f.Replace)
315		}
316	}
317}
318
319var untagged = cache.New(cache.Options{Filler: func(userid int64) (map[string]bool, bool) {
320	rows, err := stmtUntagged.Query(userid)
321	if err != nil {
322		log.Printf("error query untagged: %s", err)
323		return nil, false
324	}
325	defer rows.Close()
326	bad := make(map[string]bool)
327	for rows.Next() {
328		var xid, rid string
329		var flags int64
330		err = rows.Scan(&xid, &rid, &flags)
331		if err != nil {
332			log.Printf("error scanning untag: %s", err)
333			continue
334		}
335		if flags&flagIsUntagged != 0 {
336			bad[xid] = true
337		}
338		if bad[rid] {
339			bad[xid] = true
340		}
341	}
342	return bad, true
343}})
344
345func osmosis(honks []*Honk, userid int64, withfilt bool) []*Honk {
346	var badparents map[string]bool
347	untagged.GetAndLock(userid, &badparents)
348	j := 0
349	reversehonks(honks)
350	for _, h := range honks {
351		if badparents[h.RID] {
352			badparents[h.XID] = true
353			continue
354		}
355		honks[j] = h
356		j++
357	}
358	untagged.Unlock()
359	honks = honks[0:j]
360	reversehonks(honks)
361	if !withfilt {
362		return honks
363	}
364	filts := getfilters(userid, filtHide)
365	j = 0
366outer:
367	for _, h := range honks {
368		for _, f := range filts {
369			if matchfilter(h, f) {
370				continue outer
371			}
372		}
373		honks[j] = h
374		j++
375	}
376	honks = honks[0:j]
377	return honks
378}