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}