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}