fun.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 "crypto/rand"
20 "crypto/rsa"
21 "fmt"
22 "html/template"
23 "log"
24 "net/http"
25 "os"
26 "regexp"
27 "strings"
28 "sync"
29
30 "golang.org/x/net/html"
31 "humungus.tedunangst.com/r/webs/htfilter"
32 "humungus.tedunangst.com/r/webs/httpsig"
33)
34
35func reverbolate(userid int64, honks []*Honk) {
36 filt := htfilter.New()
37 filt.Imager = replaceimg
38 zilences := getzilences(userid)
39 for _, h := range honks {
40 h.What += "ed"
41 if h.What == "tonked" {
42 h.What = "honked back"
43 h.Style = "subtle"
44 }
45 if !h.Public {
46 h.Style += " limited"
47 }
48 translate(h)
49 if h.Whofore == 2 || h.Whofore == 3 {
50 h.URL = h.XID
51 if h.What != "bonked" {
52 h.Noise = re_memes.ReplaceAllString(h.Noise, "")
53 h.Noise = mentionize(h.Noise)
54 h.Noise = ontologize(h.Noise)
55 }
56 h.Username, h.Handle = handles(h.Honker)
57 } else {
58 _, h.Handle = handles(h.Honker)
59 h.Username = h.Handle
60 if len(h.Username) > 20 {
61 h.Username = h.Username[:20] + ".."
62 }
63 if h.URL == "" {
64 h.URL = h.XID
65 }
66 }
67 if h.Oonker != "" {
68 _, h.Oondle = handles(h.Oonker)
69 }
70 zap := make(map[*Donk]bool)
71 h.Precis = unpucker(h.Precis)
72 h.Noise = unpucker(h.Noise)
73 h.Open = "open"
74 if userid == -1 {
75 if h.Precis != "" {
76 h.Open = ""
77 }
78 } else {
79 if badword := unsee(zilences, h.Precis, h.Noise, h.Donks); badword != "" {
80 if h.Precis == "" {
81 h.Precis = badword
82 }
83 h.Open = ""
84 } else if h.Precis == "unspecified horror" {
85 h.Precis = ""
86 }
87 }
88 if len(h.Noise) > 4000 && h.Open == "open" {
89 if h.Precis == "" {
90 h.Precis = "really freaking long"
91 }
92 h.Open = ""
93 }
94
95 h.HTPrecis, _ = filt.String(h.Precis)
96 h.HTML, _ = filt.String(h.Noise)
97 emuxifier := func(e string) string {
98 for _, d := range h.Donks {
99 if d.Name == e {
100 zap[d] = true
101 if d.Local {
102 return fmt.Sprintf(`<img class="emu" title="%s" src="/d/%s">`, d.Name, d.XID)
103 }
104 }
105 }
106 return e
107 }
108 h.HTPrecis = template.HTML(re_emus.ReplaceAllStringFunc(string(h.HTPrecis), emuxifier))
109 h.HTML = template.HTML(re_emus.ReplaceAllStringFunc(string(h.HTML), emuxifier))
110 j := 0
111 for i := 0; i < len(h.Donks); i++ {
112 if !zap[h.Donks[i]] {
113 h.Donks[j] = h.Donks[i]
114 j++
115 }
116 }
117 h.Donks = h.Donks[:j]
118 }
119}
120
121func replaceimg(node *html.Node) string {
122 src := htfilter.GetAttr(node, "src")
123 alt := htfilter.GetAttr(node, "alt")
124 //title := GetAttr(node, "title")
125 if htfilter.HasClass(node, "Emoji") && alt != "" {
126 return alt
127 }
128 alt = html.EscapeString(alt)
129 src = html.EscapeString(src)
130 d := finddonk(src)
131 if d != nil {
132 src = fmt.Sprintf("https://%s/d/%s", serverName, d.XID)
133 return fmt.Sprintf(`<img alt="%s" title="%s" src="%s">`, alt, alt, src)
134 }
135 return fmt.Sprintf(`<img alt="%s" src="<a href="%s">%s<a>">`, alt, src, src)
136}
137
138func inlineimgs(node *html.Node) string {
139 src := htfilter.GetAttr(node, "src")
140 alt := htfilter.GetAttr(node, "alt")
141 //title := GetAttr(node, "title")
142 if htfilter.HasClass(node, "Emoji") && alt != "" {
143 return alt
144 }
145 alt = html.EscapeString(alt)
146 src = html.EscapeString(src)
147 if !strings.HasPrefix(src, "https://"+serverName+"/") {
148 d := savedonk(src, "image", alt, "image", true)
149 if d != nil {
150 src = fmt.Sprintf("https://%s/d/%s", serverName, d.XID)
151 }
152 }
153 log.Printf("inline img with src: %s", src)
154 return fmt.Sprintf(`<img alt="%s" title="%s" src="%s>`, alt, alt, src)
155}
156
157func translate(honk *Honk) {
158 if honk.Format == "html" {
159 return
160 }
161 noise := honk.Noise
162 if strings.HasPrefix(noise, "DZ:") {
163 idx := strings.Index(noise, "\n")
164 if idx == -1 {
165 honk.Precis = noise
166 noise = ""
167 } else {
168 honk.Precis = noise[:idx]
169 noise = noise[idx+1:]
170 }
171 }
172 honk.Precis = strings.TrimSpace(honk.Precis)
173
174 noise = strings.TrimSpace(noise)
175 noise = quickrename(noise, honk.UserID)
176 noise = obfusbreak(noise)
177
178 honk.Noise = noise
179 honk.Onts = oneofakind(ontologies(honk.Noise))
180}
181
182func unsee(zilences []*regexp.Regexp, precis string, noise string, donks []*Donk) string {
183 for _, z := range zilences {
184 if z.MatchString(precis) || z.MatchString(noise) {
185 if precis == "" {
186 w := z.String()
187 return w[6 : len(w)-3]
188 }
189 return precis
190 }
191 for _, d := range donks {
192 if z.MatchString(d.Desc) {
193 if precis == "" {
194 w := z.String()
195 return w[6 : len(w)-3]
196 }
197 return precis
198 }
199 }
200 }
201 return ""
202}
203
204func osmosis(honks []*Honk, userid int64) []*Honk {
205 zords := getzords(userid)
206 j := 0
207outer:
208 for _, h := range honks {
209 for _, z := range zords {
210 if z.MatchString(h.Precis) || z.MatchString(h.Noise) {
211 continue outer
212 }
213 for _, d := range h.Donks {
214 if z.MatchString(d.Desc) {
215 continue outer
216 }
217 }
218 }
219 honks[j] = h
220 j++
221 }
222 honks = honks[0:j]
223 return honks
224}
225
226func shortxid(xid string) string {
227 idx := strings.LastIndexByte(xid, '/')
228 if idx == -1 {
229 return xid
230 }
231 return xid[idx+1:]
232}
233
234func xfiltrate() string {
235 letters := "BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz1234567891234567891234"
236 var b [18]byte
237 rand.Read(b[:])
238 for i, c := range b {
239 b[i] = letters[c&63]
240 }
241 s := string(b[:])
242 return s
243}
244
245var re_hashes = regexp.MustCompile(`(?:^| )#[[:alnum:]][[:alnum:]_-]*`)
246
247func ontologies(s string) []string {
248 m := re_hashes.FindAllString(s, -1)
249 j := 0
250 for _, h := range m {
251 if h[0] == '&' {
252 continue
253 }
254 if h[0] != '#' {
255 h = h[1:]
256 }
257 m[j] = h
258 j++
259 }
260 return m[:j]
261}
262
263type Mention struct {
264 who string
265 where string
266}
267
268var re_mentions = regexp.MustCompile(`@[[:alnum:]._-]+@[[:alnum:].-]*[[:alnum:]]`)
269var re_urltions = regexp.MustCompile(`@https://\S+`)
270
271func grapevine(s string) []string {
272 var mentions []string
273 m := re_mentions.FindAllString(s, -1)
274 for i := range m {
275 where := gofish(m[i])
276 if where != "" {
277 mentions = append(mentions, where)
278 }
279 }
280 m = re_urltions.FindAllString(s, -1)
281 for i := range m {
282 mentions = append(mentions, m[i][1:])
283 }
284 return mentions
285}
286
287func bunchofgrapes(s string) []Mention {
288 m := re_mentions.FindAllString(s, -1)
289 var mentions []Mention
290 for i := range m {
291 where := gofish(m[i])
292 if where != "" {
293 mentions = append(mentions, Mention{who: m[i], where: where})
294 }
295 }
296 m = re_urltions.FindAllString(s, -1)
297 for i := range m {
298 mentions = append(mentions, Mention{who: m[i][1:], where: m[i][1:]})
299 }
300 return mentions
301}
302
303type Emu struct {
304 ID string
305 Name string
306}
307
308var re_link = regexp.MustCompile(`@?https?://[^\s"]+[\w/)]`)
309var re_emus = regexp.MustCompile(`:[[:alnum:]_-]+:`)
310
311func herdofemus(noise string) []Emu {
312 m := re_emus.FindAllString(noise, -1)
313 m = oneofakind(m)
314 var emus []Emu
315 for _, e := range m {
316 fname := e[1 : len(e)-1]
317 _, err := os.Stat("emus/" + fname + ".png")
318 if err != nil {
319 continue
320 }
321 url := fmt.Sprintf("https://%s/emu/%s.png", serverName, fname)
322 emus = append(emus, Emu{ID: url, Name: e})
323 }
324 return emus
325}
326
327var re_memes = regexp.MustCompile("meme: ?([[:alnum:]_.-]+)")
328
329func memetize(honk *Honk) {
330 repl := func(x string) string {
331 name := x[5:]
332 if name[0] == ' ' {
333 name = name[1:]
334 }
335 fd, err := os.Open("memes/" + name)
336 if err != nil {
337 log.Printf("no meme for %s", name)
338 return x
339 }
340 var peek [512]byte
341 n, _ := fd.Read(peek[:])
342 ct := http.DetectContentType(peek[:n])
343 fd.Close()
344
345 url := fmt.Sprintf("https://%s/meme/%s", serverName, name)
346 fileid, err := savefile("", name, name, url, ct, false, nil)
347 if err != nil {
348 log.Printf("error saving meme: %s", err)
349 return x
350 }
351 var d Donk
352 d.FileID = fileid
353 d.XID = ""
354 d.Name = name
355 d.Media = ct
356 d.URL = url
357 d.Local = false
358 honk.Donks = append(honk.Donks, &d)
359 return ""
360 }
361 honk.Noise = re_memes.ReplaceAllStringFunc(honk.Noise, repl)
362}
363
364var re_bolder = regexp.MustCompile(`(^|\W)\*\*([\w\s,.!?':_-]+)\*\*($|\W)`)
365var re_italicer = regexp.MustCompile(`(^|\W)\*([\w\s,.!?':_-]+)\*($|\W)`)
366var re_bigcoder = regexp.MustCompile("```\n?((?s:.*?))\n?```\n?")
367var re_coder = regexp.MustCompile("`([^`]*)`")
368var re_quoter = regexp.MustCompile(`(?m:^> (.*)\n?)`)
369
370func markitzero(s string) string {
371 var bigcodes []string
372 bigsaver := func(code string) string {
373 bigcodes = append(bigcodes, code)
374 return "``````"
375 }
376 s = re_bigcoder.ReplaceAllStringFunc(s, bigsaver)
377 var lilcodes []string
378 lilsaver := func(code string) string {
379 lilcodes = append(lilcodes, code)
380 return "`x`"
381 }
382 s = re_coder.ReplaceAllStringFunc(s, lilsaver)
383 s = re_bolder.ReplaceAllString(s, "$1<b>$2</b>$3")
384 s = re_italicer.ReplaceAllString(s, "$1<i>$2</i>$3")
385 s = re_quoter.ReplaceAllString(s, "<blockquote>$1</blockquote><p>")
386 lilun := func(s string) string {
387 code := lilcodes[0]
388 lilcodes = lilcodes[1:]
389 return code
390 }
391 s = re_coder.ReplaceAllStringFunc(s, lilun)
392 bigun := func(s string) string {
393 code := bigcodes[0]
394 bigcodes = bigcodes[1:]
395 return code
396 }
397 s = re_bigcoder.ReplaceAllStringFunc(s, bigun)
398 s = re_bigcoder.ReplaceAllString(s, "<pre><code>$1</code></pre><p>")
399 s = re_coder.ReplaceAllString(s, "<code>$1</code>")
400 return s
401}
402
403func obfusbreak(s string) string {
404 s = strings.TrimSpace(s)
405 s = strings.Replace(s, "\r", "", -1)
406 s = html.EscapeString(s)
407 // dammit go
408 s = strings.Replace(s, "'", "'", -1)
409 linkfn := func(url string) string {
410 if url[0] == '@' {
411 return url
412 }
413 addparen := false
414 adddot := false
415 if strings.HasSuffix(url, ")") && strings.IndexByte(url, '(') == -1 {
416 url = url[:len(url)-1]
417 addparen = true
418 }
419 if strings.HasSuffix(url, ".") {
420 url = url[:len(url)-1]
421 adddot = true
422 }
423 url = fmt.Sprintf(`<a class="mention u-url" href="%s">%s</a>`, url, url)
424 if adddot {
425 url += "."
426 }
427 if addparen {
428 url += ")"
429 }
430 return url
431 }
432 s = re_link.ReplaceAllStringFunc(s, linkfn)
433
434 s = markitzero(s)
435
436 s = strings.Replace(s, "\n", "<br>", -1)
437 return s
438}
439
440var re_quickmention = regexp.MustCompile("(^| )@[[:alnum:]]+ ")
441
442func quickrename(s string, userid int64) string {
443 return re_quickmention.ReplaceAllStringFunc(s, func(m string) string {
444 prefix := ""
445 if m[0] == ' ' {
446 prefix = " "
447 m = m[1:]
448 }
449 prefix += "@"
450 m = m[1:]
451 m = m[:len(m)-1]
452
453 row := stmtOneHonker.QueryRow(m, userid)
454 var xid string
455 err := row.Scan(&xid)
456 if err == nil {
457 _, name := handles(xid)
458 if name != "" {
459 m = name
460 }
461 }
462 return prefix + m + " "
463 })
464}
465
466func mentionize(s string) string {
467 s = re_mentions.ReplaceAllStringFunc(s, func(m string) string {
468 where := gofish(m)
469 if where == "" {
470 return m
471 }
472 who := m[0 : 1+strings.IndexByte(m[1:], '@')]
473 return fmt.Sprintf(`<span class="h-card"><a class="u-url mention" href="%s">%s</a></span>`,
474 html.EscapeString(where), html.EscapeString(who))
475 })
476 s = re_urltions.ReplaceAllStringFunc(s, func(m string) string {
477 return fmt.Sprintf(`<span class="h-card"><a class="u-url mention" href="%s">%s</a></span>`,
478 html.EscapeString(m[1:]), html.EscapeString(m))
479 })
480 return s
481}
482
483func ontologize(s string) string {
484 s = re_hashes.ReplaceAllStringFunc(s, func(o string) string {
485 if o[0] == '&' {
486 return o
487 }
488 p := ""
489 h := o
490 if h[0] != '#' {
491 p = h[:1]
492 h = h[1:]
493 }
494 return fmt.Sprintf(`%s<a class="mention u-url" href="https://%s/o/%s">%s</a>`, p, serverName,
495 strings.ToLower(h[1:]), h)
496 })
497 return s
498}
499
500var re_unurl = regexp.MustCompile("https://([^/]+).*/([^/]+)")
501var re_urlhost = regexp.MustCompile("https://([^/ ]+)")
502
503func originate(u string) string {
504 m := re_urlhost.FindStringSubmatch(u)
505 if len(m) > 1 {
506 return m[1]
507 }
508 return ""
509}
510
511var allhandles = make(map[string]string)
512var handlelock sync.Mutex
513
514// handle, handle@host
515func handles(xid string) (string, string) {
516 if xid == "" {
517 return "", ""
518 }
519 handlelock.Lock()
520 handle := allhandles[xid]
521 handlelock.Unlock()
522 if handle == "" {
523 handle = findhandle(xid)
524 handlelock.Lock()
525 allhandles[xid] = handle
526 handlelock.Unlock()
527 }
528 if handle == xid {
529 return xid, xid
530 }
531 return handle, handle + "@" + originate(xid)
532}
533
534func findhandle(xid string) string {
535 row := stmtGetXonker.QueryRow(xid, "handle")
536 var handle string
537 err := row.Scan(&handle)
538 if err != nil {
539 p, _ := investigate(xid)
540 if p == nil {
541 m := re_unurl.FindStringSubmatch(xid)
542 if len(m) > 2 {
543 handle = m[2]
544 } else {
545 handle = xid
546 }
547 } else {
548 handle = p.Handle
549 }
550 _, err = stmtSaveXonker.Exec(xid, handle, "handle")
551 if err != nil {
552 log.Printf("error saving handle: %s", err)
553 }
554 }
555 return handle
556}
557
558var handleprelock sync.Mutex
559
560func prehandle(xid string) {
561 handleprelock.Lock()
562 defer handleprelock.Unlock()
563 handles(xid)
564}
565
566func prepend(s string, x []string) []string {
567 return append([]string{s}, x...)
568}
569
570// pleroma leaks followers addressed posts to followers
571func butnottooloud(aud []string) {
572 for i, a := range aud {
573 if strings.HasSuffix(a, "/followers") {
574 aud[i] = ""
575 }
576 }
577}
578
579func keepitquiet(aud []string) bool {
580 for _, a := range aud {
581 if a == thewholeworld {
582 return false
583 }
584 }
585 return true
586}
587
588func firstclass(honk *Honk) bool {
589 return honk.Audience[0] == thewholeworld
590}
591
592func oneofakind(a []string) []string {
593 var x []string
594 for n, s := range a {
595 if s != "" {
596 x = append(x, s)
597 for i := n + 1; i < len(a); i++ {
598 if a[i] == s {
599 a[i] = ""
600 }
601 }
602 }
603 }
604 return x
605}
606
607var ziggies = make(map[string]*rsa.PrivateKey)
608var zaggies = make(map[string]*rsa.PublicKey)
609var ziggylock sync.Mutex
610
611func ziggy(username string) (keyname string, key *rsa.PrivateKey) {
612 ziggylock.Lock()
613 key = ziggies[username]
614 ziggylock.Unlock()
615 if key == nil {
616 db := opendatabase()
617 row := db.QueryRow("select seckey from users where username = ?", username)
618 var data string
619 row.Scan(&data)
620 var err error
621 key, _, err = httpsig.DecodeKey(data)
622 if err != nil {
623 log.Printf("error decoding %s seckey: %s", username, err)
624 return
625 }
626 ziggylock.Lock()
627 ziggies[username] = key
628 ziggylock.Unlock()
629 }
630 keyname = fmt.Sprintf("https://%s/%s/%s#key", serverName, userSep, username)
631 return
632}
633
634func zaggy(keyname string) (key *rsa.PublicKey) {
635 ziggylock.Lock()
636 key = zaggies[keyname]
637 ziggylock.Unlock()
638 if key != nil {
639 return
640 }
641 row := stmtGetXonker.QueryRow(keyname, "pubkey")
642 var data string
643 err := row.Scan(&data)
644 if err != nil {
645 log.Printf("hitting the webs for missing pubkey: %s", keyname)
646 j, err := GetJunk(keyname)
647 if err != nil {
648 log.Printf("error getting %s pubkey: %s", keyname, err)
649 return
650 }
651 keyobj, ok := j.GetMap("publicKey")
652 if ok {
653 j = keyobj
654 }
655 data, ok = j.GetString("publicKeyPem")
656 if !ok {
657 log.Printf("error finding %s pubkey", keyname)
658 return
659 }
660 _, ok = j.GetString("owner")
661 if !ok {
662 log.Printf("error finding %s pubkey owner", keyname)
663 return
664 }
665 _, key, err = httpsig.DecodeKey(data)
666 if err != nil {
667 log.Printf("error decoding %s pubkey: %s", keyname, err)
668 return
669 }
670 _, err = stmtSaveXonker.Exec(keyname, data, "pubkey")
671 if err != nil {
672 log.Printf("error saving key: %s", err)
673 }
674 } else {
675 _, key, err = httpsig.DecodeKey(data)
676 if err != nil {
677 log.Printf("error decoding %s pubkey: %s", keyname, err)
678 return
679 }
680 }
681 ziggylock.Lock()
682 zaggies[keyname] = key
683 ziggylock.Unlock()
684 return
685}
686
687func makeitworksomehowwithoutregardforkeycontinuity(keyname string, r *http.Request, payload []byte) (string, error) {
688 _, err := stmtDeleteXonker.Exec(keyname, "pubkey")
689 if err != nil {
690 log.Printf("error deleting key: %s", err)
691 }
692 ziggylock.Lock()
693 delete(zaggies, keyname)
694 ziggylock.Unlock()
695 return httpsig.VerifyRequest(r, payload, zaggy)
696}
697
698var thumbbiters map[int64]map[string]bool
699var zoggles map[int64]map[string]bool
700var zordses map[int64][]*regexp.Regexp
701var zilences map[int64][]*regexp.Regexp
702var thumblock sync.Mutex
703
704func bitethethumbs() {
705 rows, err := stmtThumbBiters.Query()
706 if err != nil {
707 log.Printf("error getting thumbbiters: %s", err)
708 return
709 }
710 defer rows.Close()
711
712 thumblock.Lock()
713 defer thumblock.Unlock()
714 thumbbiters = make(map[int64]map[string]bool)
715 zoggles = make(map[int64]map[string]bool)
716 zordses = make(map[int64][]*regexp.Regexp)
717 zilences = make(map[int64][]*regexp.Regexp)
718 for rows.Next() {
719 var userid int64
720 var name, wherefore string
721 err = rows.Scan(&userid, &name, &wherefore)
722 if err != nil {
723 log.Printf("error scanning zonker: %s", err)
724 continue
725 }
726 if wherefore == "zord" || wherefore == "zilence" {
727 zord := "\\b(?i:" + name + ")\\b"
728 re, err := regexp.Compile(zord)
729 if err != nil {
730 log.Printf("error compiling zord: %s", err)
731 } else {
732 if wherefore == "zord" {
733 zordses[userid] = append(zordses[userid], re)
734 } else {
735 zilences[userid] = append(zilences[userid], re)
736 }
737 }
738 }
739 if wherefore == "zoggle" {
740 m := zoggles[userid]
741 if m == nil {
742 m = make(map[string]bool)
743 zoggles[userid] = m
744 }
745 m[name] = true
746 }
747 if wherefore == "zonker" || wherefore == "zomain" {
748 m := thumbbiters[userid]
749 if m == nil {
750 m = make(map[string]bool)
751 thumbbiters[userid] = m
752 }
753 m[name] = true
754 }
755 }
756}
757
758func getzords(userid int64) []*regexp.Regexp {
759 thumblock.Lock()
760 defer thumblock.Unlock()
761 return zordses[userid]
762}
763
764func getzilences(userid int64) []*regexp.Regexp {
765 thumblock.Lock()
766 defer thumblock.Unlock()
767 return zilences[userid]
768}
769
770func thoudostbitethythumb(userid int64, who []string, objid string) bool {
771 thumblock.Lock()
772 biters := thumbbiters[userid]
773 thumblock.Unlock()
774 objwhere := originate(objid)
775 if objwhere != "" && biters[objwhere] {
776 log.Printf("thumbbiter: %s", objid)
777 return true
778 }
779 for _, w := range who {
780 if biters[w] {
781 log.Printf("thumbbiter: %s", w)
782 return true
783 }
784 where := originate(w)
785 if where != "" {
786 if biters[where] {
787 log.Printf("thumbbiter: %s", w)
788 return true
789 }
790 }
791 }
792 return false
793}
794
795func stealthmode(userid int64, r *http.Request) bool {
796 agent := r.UserAgent()
797 agent = originate(agent)
798 addr := r.Header.Get("X-Forwarded-For")
799 thumblock.Lock()
800 biters := thumbbiters[userid]
801 thumblock.Unlock()
802 fake := (agent != "" && biters[agent]) || (addr != "" && biters[addr])
803 if fake {
804 log.Printf("faking 404 for %s from %s", agent, addr)
805 }
806 return fake
807}
808
809func keymatch(keyname string, actor string) string {
810 hash := strings.IndexByte(keyname, '#')
811 if hash == -1 {
812 hash = len(keyname)
813 }
814 owner := keyname[0:hash]
815 if owner == actor {
816 return originate(actor)
817 }
818 return ""
819}