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