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
26type Filter struct {
27 ID int64 `json:"-"`
28 Actions []filtType `json:"-"`
29 Name string
30 Date time.Time
31 Actor string `json:",omitempty"`
32 IncludeAudience bool `json:",omitempty"`
33 Text string `json:",omitempty"`
34 re_text *regexp.Regexp
35 IsAnnounce bool `json:",omitempty"`
36 AnnounceOf string `json:",omitempty"`
37 Reject bool `json:",omitempty"`
38 SkipMedia bool `json:",omitempty"`
39 Hide bool `json:",omitempty"`
40 Collapse bool `json:",omitempty"`
41 Rewrite string `json:",omitempty"`
42 re_rewrite *regexp.Regexp
43 Replace string `json:",omitempty"`
44}
45
46type filtType uint
47
48const (
49 filtNone filtType = iota
50 filtAny
51 filtReject
52 filtSkipMedia
53 filtHide
54 filtCollapse
55 filtRewrite
56)
57
58var filtNames = []string{"None", "Any", "Reject", "SkipMedia", "Hide", "Collapse", "Rewrite"}
59
60func (ft filtType) String() string {
61 return filtNames[ft]
62}
63
64type afiltermap map[filtType][]*Filter
65
66var filtcache = cacheNew(func(userid int64) (afiltermap, bool) {
67 rows, err := stmtGetFilters.Query(userid)
68 if err != nil {
69 log.Printf("error querying filters: %s", err)
70 return nil, false
71 }
72 defer rows.Close()
73
74 filtmap := make(afiltermap)
75 for rows.Next() {
76 filt := new(Filter)
77 var j string
78 var filterid int64
79 err = rows.Scan(&filterid, &j)
80 if err == nil {
81 err = unjsonify(j, filt)
82 }
83 if err != nil {
84 log.Printf("error scanning filter: %s", err)
85 continue
86 }
87 if filt.Text != "" {
88 filt.re_text, err = regexp.Compile("\\b(?i:" + filt.Text + ")\\b")
89 if err != nil {
90 log.Printf("error compiling filter text: %s", err)
91 continue
92 }
93 }
94 if filt.Rewrite != "" {
95 filt.re_rewrite, err = regexp.Compile("\\b(?i:" + filt.Rewrite + ")\\b")
96 if err != nil {
97 log.Printf("error compiling filter rewrite: %s", err)
98 continue
99 }
100 }
101 filt.ID = filterid
102 if filt.Reject {
103 filt.Actions = append(filt.Actions, filtReject)
104 filtmap[filtReject] = append(filtmap[filtReject], filt)
105 }
106 if filt.SkipMedia {
107 filt.Actions = append(filt.Actions, filtSkipMedia)
108 filtmap[filtSkipMedia] = append(filtmap[filtSkipMedia], filt)
109 }
110 if filt.Hide {
111 filt.Actions = append(filt.Actions, filtHide)
112 filtmap[filtHide] = append(filtmap[filtHide], filt)
113 }
114 if filt.Collapse {
115 filt.Actions = append(filt.Actions, filtCollapse)
116 filtmap[filtCollapse] = append(filtmap[filtCollapse], filt)
117 }
118 if filt.Rewrite != "" {
119 filt.Actions = append(filt.Actions, filtRewrite)
120 filtmap[filtRewrite] = append(filtmap[filtRewrite], filt)
121 }
122 filtmap[filtAny] = append(filtmap[filtAny], filt)
123 }
124 sorting := filtmap[filtAny]
125 sort.Slice(filtmap[filtAny], func(i, j int) bool {
126 return sorting[i].Name < sorting[j].Name
127 })
128 return filtmap, true
129})
130
131func getfilters(userid int64, scope filtType) []*Filter {
132 var filtmap afiltermap
133 ok := filtcache.Get(userid, &filtmap)
134 if ok {
135 return filtmap[scope]
136 }
137 return nil
138}
139
140func rejectorigin(userid int64, origin string) bool {
141 if o := originate(origin); o != "" {
142 origin = o
143 }
144 filts := getfilters(userid, filtReject)
145 for _, f := range filts {
146 if f.Actor == origin {
147 log.Printf("rejecting origin: %s", origin)
148 return true
149 }
150 }
151 return false
152}
153
154func rejectactor(userid int64, actor string) bool {
155 origin := originate(actor)
156 filts := getfilters(userid, filtReject)
157 for _, f := range filts {
158 if f.Actor == actor || (origin != "" && f.Actor == origin) {
159 log.Printf("rejecting actor: %s", actor)
160 return true
161 }
162 }
163 return false
164}
165
166func stealthmode(userid int64, r *http.Request) bool {
167 agent := r.UserAgent()
168 agent = originate(agent)
169 if agent != "" {
170 fake := rejectorigin(userid, agent)
171 if fake {
172 log.Printf("faking 404 for %s", agent)
173 return true
174 }
175 }
176 return false
177}
178
179func matchfilter(h *Honk, f *Filter) bool {
180 match := true
181 if match && f.Actor != "" {
182 match = false
183 if f.Actor == h.Honker || f.Actor == h.Oonker {
184 match = true
185 }
186 if !match && (f.Actor == originate(h.Honker) ||
187 f.Actor == originate(h.Oonker) ||
188 f.Actor == originate(h.XID)) {
189 match = true
190 }
191 if !match && f.IncludeAudience {
192 for _, a := range h.Audience {
193 if f.Actor == a || f.Actor == originate(a) {
194 match = true
195 break
196 }
197 }
198 }
199 }
200 if match && f.IsAnnounce {
201 match = false
202 if (f.AnnounceOf == "" && h.Oonker != "") || f.AnnounceOf == h.Oonker ||
203 f.AnnounceOf == originate(h.Oonker) {
204 match = true
205 }
206 }
207 if match && f.Text != "" {
208 match = false
209 re := f.re_text
210 if re.MatchString(h.Noise) || re.MatchString(h.Precis) {
211 match = true
212 }
213 if !match {
214 for _, d := range h.Donks {
215 if re.MatchString(d.Desc) {
216 match = true
217 }
218 }
219 }
220 }
221 return match
222}
223
224func rejectxonk(xonk *Honk) bool {
225 filts := getfilters(xonk.UserID, filtReject)
226 for _, f := range filts {
227 if matchfilter(xonk, f) {
228 log.Printf("rejecting %s because %s", xonk.XID, f.Actor)
229 return true
230 }
231 }
232 return false
233}
234
235func skipMedia(xonk *Honk) bool {
236 filts := getfilters(xonk.UserID, filtSkipMedia)
237 for _, f := range filts {
238 if matchfilter(xonk, f) {
239 return true
240 }
241 }
242 return false
243}
244
245func unsee(userid int64, h *Honk) {
246 filts := getfilters(userid, filtCollapse)
247 for _, f := range filts {
248 if matchfilter(h, f) {
249 bad := f.Text
250 if f.Actor != "" {
251 bad = f.Actor
252 }
253 if h.Precis == "" {
254 h.Precis = bad
255 }
256 h.Open = ""
257 break
258 }
259 }
260 filts = getfilters(userid, filtRewrite)
261 for _, f := range filts {
262 if matchfilter(h, f) {
263 h.Noise = f.re_rewrite.ReplaceAllString(h.Noise, f.Replace)
264 }
265 }
266}
267
268func osmosis(honks []*Honk, userid int64) []*Honk {
269 filts := getfilters(userid, filtHide)
270 j := 0
271outer:
272 for _, h := range honks {
273 for _, f := range filts {
274 if matchfilter(h, f) {
275 continue outer
276 }
277 }
278 honks[j] = h
279 j++
280 }
281 honks = honks[0:j]
282 return honks
283}