all repos — honk @ v0.8.0

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	match := true
218	if match && f.Actor != "" {
219		match = false
220		if f.Actor == h.Honker || f.Actor == h.Oonker {
221			match = true
222		}
223		if !match && (f.Actor == originate(h.Honker) ||
224			f.Actor == originate(h.Oonker) ||
225			f.Actor == originate(h.XID)) {
226			match = true
227		}
228		if !match && f.IncludeAudience {
229			for _, a := range h.Audience {
230				if f.Actor == a || f.Actor == originate(a) {
231					match = true
232					break
233				}
234			}
235		}
236	}
237	if match && f.IsAnnounce {
238		match = false
239		if (f.AnnounceOf == "" && h.Oonker != "") || f.AnnounceOf == h.Oonker ||
240			f.AnnounceOf == originate(h.Oonker) {
241			match = true
242		}
243	}
244	if match && f.Text != "" {
245		match = false
246		re := f.re_text
247		if re.MatchString(h.Noise) || re.MatchString(h.Precis) {
248			match = true
249		}
250		if !match {
251			for _, d := range h.Donks {
252				if re.MatchString(d.Desc) {
253					match = true
254				}
255			}
256		}
257	}
258	return match
259}
260
261func rejectxonk(xonk *Honk) bool {
262	filts := getfilters(xonk.UserID, filtReject)
263	for _, f := range filts {
264		if matchfilter(xonk, f) {
265			log.Printf("rejecting %s because %s", xonk.XID, f.Actor)
266			return true
267		}
268	}
269	return false
270}
271
272func skipMedia(xonk *Honk) bool {
273	filts := getfilters(xonk.UserID, filtSkipMedia)
274	for _, f := range filts {
275		if matchfilter(xonk, f) {
276			return true
277		}
278	}
279	return false
280}
281
282func unsee(userid int64, h *Honk) {
283	filts := getfilters(userid, filtCollapse)
284	for _, f := range filts {
285		if matchfilter(h, f) {
286			bad := f.Text
287			if f.Actor != "" {
288				bad = f.Actor
289			}
290			if h.Precis == "" {
291				h.Precis = bad
292			}
293			h.Open = ""
294			break
295		}
296	}
297	filts = getfilters(userid, filtRewrite)
298	for _, f := range filts {
299		if matchfilter(h, f) {
300			h.Noise = f.re_rewrite.ReplaceAllString(h.Noise, f.Replace)
301		}
302	}
303}
304
305var untagged = cache.New(cache.Options{Filler: func(userid int64) (map[string]bool, bool) {
306	rows, err := stmtUntagged.Query(userid)
307	if err != nil {
308		log.Printf("error query untagged: %s", err)
309		return nil, false
310	}
311	defer rows.Close()
312	bad := make(map[string]bool)
313	for rows.Next() {
314		var xid, rid string
315		var flags int64
316		err = rows.Scan(&xid, &rid, &flags)
317		if err != nil {
318			log.Printf("error scanning untag: %s", err)
319			continue
320		}
321		if flags&flagIsUntagged != 0 {
322			bad[xid] = true
323		}
324		if bad[rid] {
325			bad[xid] = true
326		}
327	}
328	return bad, true
329}})
330
331func osmosis(honks []*Honk, userid int64, withfilt bool) []*Honk {
332	var badparents map[string]bool
333	untagged.GetAndLock(userid, &badparents)
334	j := 0
335	reversehonks(honks)
336	for _, h := range honks {
337		if badparents[h.RID] {
338			badparents[h.XID] = true
339			continue
340		}
341		honks[j] = h
342		j++
343	}
344	untagged.Unlock()
345	honks = honks[0:j]
346	reversehonks(honks)
347	if !withfilt {
348		return honks
349	}
350	filts := getfilters(userid, filtHide)
351	j = 0
352outer:
353	for _, h := range honks {
354		for _, f := range filts {
355			if matchfilter(h, f) {
356				continue outer
357			}
358		}
359		honks[j] = h
360		j++
361	}
362	honks = honks[0:j]
363	return honks
364}