web.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 "bytes"
20 "crypto/sha512"
21 "database/sql"
22 "fmt"
23 "html/template"
24 "io"
25 notrand "math/rand"
26 "mime/multipart"
27 "net/http"
28 "net/http/fcgi"
29 "net/url"
30 "os"
31 "os/signal"
32 "regexp"
33 "sort"
34 "strconv"
35 "strings"
36 "syscall"
37 "time"
38 "unicode/utf8"
39
40 "github.com/gorilla/mux"
41 "humungus.tedunangst.com/r/webs/cache"
42 "humungus.tedunangst.com/r/webs/gencache"
43 "humungus.tedunangst.com/r/webs/httpsig"
44 "humungus.tedunangst.com/r/webs/junk"
45 "humungus.tedunangst.com/r/webs/login"
46 "humungus.tedunangst.com/r/webs/rss"
47 "humungus.tedunangst.com/r/webs/templates"
48)
49
50var readviews *templates.Template
51
52var userSep = "u"
53var honkSep = "h"
54
55var develMode = false
56
57var allemus []Emu
58
59func getuserstyle(u *login.UserInfo) template.HTMLAttr {
60 if u == nil {
61 return ""
62 }
63 user, _ := butwhatabout(u.Username)
64 class := template.HTMLAttr("")
65 if user.Options.SkinnyCSS {
66 class += `class="skinny"`
67 }
68 return class
69}
70
71func getmaplink(u *login.UserInfo) string {
72 if u == nil {
73 return "osm"
74 }
75 user, _ := butwhatabout(u.Username)
76 ml := user.Options.MapLink
77 if ml == "" {
78 ml = "osm"
79 }
80 return ml
81}
82
83func getInfo(r *http.Request) map[string]interface{} {
84 templinfo := make(map[string]interface{})
85 templinfo["StyleParam"] = getassetparam(viewDir + "/views/style.css")
86 templinfo["LocalStyleParam"] = getassetparam(dataDir + "/views/local.css")
87 templinfo["JSParam"] = getassetparam(viewDir + "/views/honkpage.js")
88 templinfo["MiscJSParam"] = getassetparam(viewDir + "/views/misc.js")
89 templinfo["LocalJSParam"] = getassetparam(dataDir + "/views/local.js")
90 templinfo["ServerName"] = serverName
91 templinfo["IconName"] = iconName
92 templinfo["UserSep"] = userSep
93 if r == nil {
94 return templinfo
95 }
96 if u := login.GetUserInfo(r); u != nil {
97 templinfo["UserInfo"], _ = butwhatabout(u.Username)
98 templinfo["UserStyle"] = getuserstyle(u)
99 var combos []string
100 combocache.Get(u.UserID, &combos)
101 templinfo["Combos"] = combos
102 }
103 return templinfo
104}
105
106var oldnews = gencache.New(gencache.Options[string, []byte]{
107 Fill: func(url string) ([]byte, bool) {
108 templinfo := getInfo(nil)
109 var honks []*Honk
110 var userid int64 = -1
111
112 templinfo["ServerMessage"] = serverMsg
113 switch url {
114 case "/events":
115 honks = geteventhonks(userid)
116 templinfo["ServerMessage"] = "some recent and upcoming events"
117 default:
118 templinfo["ShowRSS"] = true
119 honks = getpublichonks()
120 }
121 reverbolate(userid, honks)
122 templinfo["Honks"] = honks
123 templinfo["MapLink"] = getmaplink(nil)
124 var buf bytes.Buffer
125 err := readviews.Execute(&buf, "honkpage.html", templinfo)
126 if err != nil {
127 elog.Print(err)
128 }
129 return buf.Bytes(), true
130
131 },
132 Duration: 1 * time.Minute,
133})
134
135func lonelypage(w http.ResponseWriter, r *http.Request) {
136 page, _ := oldnews.Get(r.URL.Path)
137 if !develMode {
138 w.Header().Set("Cache-Control", "max-age=60")
139 }
140 w.Write(page)
141}
142
143func homepage(w http.ResponseWriter, r *http.Request) {
144 u := login.GetUserInfo(r)
145 if u == nil {
146 lonelypage(w, r)
147 return
148 }
149 templinfo := getInfo(r)
150 var honks []*Honk
151 var userid int64 = -1
152
153 templinfo["ServerMessage"] = serverMsg
154 if u == nil || r.URL.Path == "/front" {
155 switch r.URL.Path {
156 case "/events":
157 honks = geteventhonks(userid)
158 templinfo["ServerMessage"] = "some recent and upcoming events"
159 default:
160 templinfo["ShowRSS"] = true
161 honks = getpublichonks()
162 }
163 } else {
164 userid = u.UserID
165 switch r.URL.Path {
166 case "/atme":
167 templinfo["ServerMessage"] = "at me!"
168 templinfo["PageName"] = "atme"
169 honks = gethonksforme(userid, 0)
170 honks = osmosis(honks, userid, false)
171 menewnone(userid)
172 templinfo["UserInfo"], _ = butwhatabout(u.Username)
173 case "/longago":
174 templinfo["ServerMessage"] = "long ago and far away!"
175 templinfo["PageName"] = "longago"
176 honks = gethonksfromlongago(userid, 0)
177 honks = osmosis(honks, userid, false)
178 case "/events":
179 templinfo["ServerMessage"] = "some recent and upcoming events"
180 templinfo["PageName"] = "events"
181 honks = geteventhonks(userid)
182 honks = osmosis(honks, userid, true)
183 case "/first":
184 templinfo["PageName"] = "first"
185 honks = gethonksforuserfirstclass(userid, 0)
186 honks = osmosis(honks, userid, true)
187 case "/saved":
188 templinfo["ServerMessage"] = "saved honks"
189 templinfo["PageName"] = "saved"
190 honks = getsavedhonks(userid, 0)
191 default:
192 templinfo["PageName"] = "home"
193 honks = gethonksforuser(userid, 0)
194 honks = osmosis(honks, userid, true)
195 }
196 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
197 }
198
199 honkpage(w, u, honks, templinfo)
200}
201
202func showemus(w http.ResponseWriter, r *http.Request) {
203 templinfo := getInfo(r)
204 templinfo["Emus"] = allemus
205 err := readviews.Execute(w, "emus.html", templinfo)
206 if err != nil {
207 elog.Print(err)
208 }
209}
210
211func showfunzone(w http.ResponseWriter, r *http.Request) {
212 var emunames, memenames []string
213 emuext := make(map[string]string)
214 dir, err := os.Open(dataDir + "/emus")
215 if err == nil {
216 emunames, _ = dir.Readdirnames(0)
217 dir.Close()
218 }
219 for i, e := range emunames {
220 if len(e) > 4 {
221 emunames[i] = e[:len(e)-4]
222 emuext[emunames[i]] = e[len(e)-4:]
223 }
224 }
225 dir, err = os.Open(dataDir + "/memes")
226 if err == nil {
227 memenames, _ = dir.Readdirnames(0)
228 dir.Close()
229 }
230 sort.Strings(emunames)
231 sort.Strings(memenames)
232 templinfo := getInfo(r)
233 templinfo["Emus"] = emunames
234 templinfo["Emuext"] = emuext
235 templinfo["Memes"] = memenames
236 err = readviews.Execute(w, "funzone.html", templinfo)
237 if err != nil {
238 elog.Print(err)
239 }
240}
241
242func showrss(w http.ResponseWriter, r *http.Request) {
243 name := mux.Vars(r)["name"]
244
245 var honks []*Honk
246 if name != "" {
247 honks = gethonksbyuser(name, false, 0)
248 } else {
249 honks = getpublichonks()
250 }
251 reverbolate(-1, honks)
252
253 home := fmt.Sprintf("https://%s/", serverName)
254 base := home
255 if name != "" {
256 home += "u/" + name
257 name += " "
258 }
259 feed := rss.Feed{
260 Title: name + "honk",
261 Link: home,
262 Description: name + "honk rss",
263 Image: &rss.Image{
264 URL: base + "icon.png",
265 Title: name + "honk rss",
266 Link: home,
267 },
268 }
269 var modtime time.Time
270 for _, honk := range honks {
271 if !firstclass(honk) {
272 continue
273 }
274 desc := string(honk.HTML)
275 if t := honk.Time; t != nil {
276 desc += fmt.Sprintf(`<p>Time: %s`, t.StartTime.Local().Format("03:04PM EDT Mon Jan 02"))
277 if t.Duration != 0 {
278 desc += fmt.Sprintf(`<br>Duration: %s`, t.Duration)
279 }
280 }
281 if p := honk.Place; p != nil {
282 desc += string(templates.Sprintf(`<p>Location: <a href="%s">%s</a> %f %f`,
283 p.Url, p.Name, p.Latitude, p.Longitude))
284 }
285 for _, d := range honk.Donks {
286 desc += string(templates.Sprintf(`<p><a href="%s">Attachment: %s</a>`,
287 d.URL, d.Desc))
288 if strings.HasPrefix(d.Media, "image") {
289 desc += string(templates.Sprintf(`<img src="%s">`, d.URL))
290 }
291 }
292
293 feed.Items = append(feed.Items, &rss.Item{
294 Title: fmt.Sprintf("%s %s %s", honk.Username, honk.What, honk.XID),
295 Description: rss.CData{Data: desc},
296 Link: honk.URL,
297 PubDate: honk.Date.Format(time.RFC1123),
298 Guid: &rss.Guid{IsPermaLink: true, Value: honk.URL},
299 })
300 if honk.Date.After(modtime) {
301 modtime = honk.Date
302 }
303 }
304 if !develMode {
305 w.Header().Set("Cache-Control", "max-age=300")
306 w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
307 }
308
309 err := feed.Write(w)
310 if err != nil {
311 elog.Printf("error writing rss: %s", err)
312 }
313}
314
315func crappola(j junk.Junk) bool {
316 t, _ := j.GetString("type")
317 a, _ := j.GetString("actor")
318 o, _ := j.GetString("object")
319 if t == "Delete" && a == o {
320 dlog.Printf("crappola from %s", a)
321 return true
322 }
323 return false
324}
325
326func ping(user *WhatAbout, who string) {
327 if targ := fullname(who, user.ID); targ != "" {
328 who = targ
329 }
330 if !strings.HasPrefix(who, "https:") {
331 who = gofish(who)
332 }
333 if who == "" {
334 ilog.Printf("nobody to ping!")
335 return
336 }
337 var box *Box
338 ok := boxofboxes.Get(who, &box)
339 if !ok {
340 ilog.Printf("no inbox to ping %s", who)
341 return
342 }
343 ilog.Printf("sending ping to %s", box.In)
344 j := junk.New()
345 j["@context"] = itiswhatitis
346 j["type"] = "Ping"
347 j["id"] = user.URL + "/ping/" + xfiltrate()
348 j["actor"] = user.URL
349 j["to"] = who
350 ki := ziggy(user.ID)
351 if ki == nil {
352 return
353 }
354 err := PostJunk(ki.keyname, ki.seckey, box.In, j)
355 if err != nil {
356 elog.Printf("can't send ping: %s", err)
357 return
358 }
359 ilog.Printf("sent ping to %s: %s", who, j["id"])
360}
361
362func pong(user *WhatAbout, who string, obj string) {
363 var box *Box
364 ok := boxofboxes.Get(who, &box)
365 if !ok {
366 ilog.Printf("no inbox to pong %s", who)
367 return
368 }
369 j := junk.New()
370 j["@context"] = itiswhatitis
371 j["type"] = "Pong"
372 j["id"] = user.URL + "/pong/" + xfiltrate()
373 j["actor"] = user.URL
374 j["to"] = who
375 j["object"] = obj
376 ki := ziggy(user.ID)
377 if ki == nil {
378 return
379 }
380 err := PostJunk(ki.keyname, ki.seckey, box.In, j)
381 if err != nil {
382 elog.Printf("can't send pong: %s", err)
383 return
384 }
385}
386
387func inbox(w http.ResponseWriter, r *http.Request) {
388 name := mux.Vars(r)["name"]
389 user, err := butwhatabout(name)
390 if err != nil {
391 http.NotFound(w, r)
392 return
393 }
394 if stealthmode(user.ID, r) {
395 http.NotFound(w, r)
396 return
397 }
398 var buf bytes.Buffer
399 limiter := io.LimitReader(r.Body, 1*1024*1024)
400 io.Copy(&buf, limiter)
401 payload := buf.Bytes()
402 j, err := junk.FromBytes(payload)
403 if err != nil {
404 ilog.Printf("bad payload: %s", err)
405 ilog.Writer().Write(payload)
406 ilog.Writer().Write([]byte{'\n'})
407 return
408 }
409
410 if crappola(j) {
411 return
412 }
413 what := firstofmany(j, "type")
414 obj, _ := j.GetString("object")
415 if what == "Like" || what == "Dislike" || (what == "EmojiReact" && originate(obj) != serverName) {
416 return
417 }
418 who, _ := j.GetString("actor")
419 if rejectactor(user.ID, who) {
420 return
421 }
422
423 keyname, err := httpsig.VerifyRequest(r, payload, zaggy)
424 if err != nil && keyname != "" {
425 savingthrow(keyname)
426 keyname, err = httpsig.VerifyRequest(r, payload, zaggy)
427 }
428 if err != nil {
429 ilog.Printf("inbox message failed signature for %s from %s: %s", keyname, r.Header.Get("X-Forwarded-For"), err)
430 if keyname != "" {
431 ilog.Printf("bad signature from %s", keyname)
432 }
433 http.Error(w, "what did you call me?", http.StatusTeapot)
434 return
435 }
436 origin := keymatch(keyname, who)
437 if origin == "" {
438 ilog.Printf("keyname actor mismatch: %s <> %s", keyname, who)
439 return
440 }
441
442 switch what {
443 case "Ping":
444 id, _ := j.GetString("id")
445 ilog.Printf("ping from %s: %s", who, id)
446 pong(user, who, id)
447 case "Pong":
448 ilog.Printf("pong from %s: %s", who, obj)
449 case "Follow":
450 if obj != user.URL {
451 ilog.Printf("can't follow %s", obj)
452 return
453 }
454 followme(user, who, who, j)
455 case "Accept":
456 followyou2(user, j)
457 case "Reject":
458 nofollowyou2(user, j)
459 case "Update":
460 obj, ok := j.GetMap("object")
461 if ok {
462 what := firstofmany(obj, "type")
463 switch what {
464 case "Service":
465 fallthrough
466 case "Person":
467 return
468 case "Question":
469 return
470 }
471 }
472 go xonksaver(user, j, origin)
473 case "Undo":
474 obj, ok := j.GetMap("object")
475 if !ok {
476 folxid, ok := j.GetString("object")
477 if ok && originate(folxid) == origin {
478 unfollowme(user, "", "", j)
479 }
480 return
481 }
482 what := firstofmany(obj, "type")
483 switch what {
484 case "Follow":
485 unfollowme(user, who, who, j)
486 case "Announce":
487 xid, _ := obj.GetString("object")
488 dlog.Printf("undo announce: %s", xid)
489 case "Like":
490 default:
491 ilog.Printf("unknown undo: %s", what)
492 }
493 case "EmojiReact":
494 obj, ok := j.GetString("object")
495 if ok {
496 content, _ := j.GetString("content")
497 addreaction(user, obj, who, content)
498 }
499 default:
500 go saveandcheck(user, j, origin)
501 }
502}
503
504func saveandcheck(user *WhatAbout, j junk.Junk, origin string) {
505 xonk := xonksaver(user, j, origin)
506 if xonk == nil {
507 return
508 }
509 if sname := shortname(user.ID, xonk.Honker); sname == "" {
510 dlog.Printf("received unexpected activity from %s", xonk.Honker)
511 if xonk.Whofore == 0 {
512 dlog.Printf("it's not even for me!")
513 }
514 }
515}
516
517func serverinbox(w http.ResponseWriter, r *http.Request) {
518 user := getserveruser()
519 if stealthmode(user.ID, r) {
520 http.NotFound(w, r)
521 return
522 }
523 var buf bytes.Buffer
524 io.Copy(&buf, r.Body)
525 payload := buf.Bytes()
526 j, err := junk.FromBytes(payload)
527 if err != nil {
528 ilog.Printf("bad payload: %s", err)
529 ilog.Writer().Write(payload)
530 ilog.Writer().Write([]byte{'\n'})
531 return
532 }
533 if crappola(j) {
534 return
535 }
536 keyname, err := httpsig.VerifyRequest(r, payload, zaggy)
537 if err != nil && keyname != "" {
538 savingthrow(keyname)
539 keyname, err = httpsig.VerifyRequest(r, payload, zaggy)
540 }
541 if err != nil {
542 ilog.Printf("inbox message failed signature for %s from %s: %s", keyname, r.Header.Get("X-Forwarded-For"), err)
543 if keyname != "" {
544 ilog.Printf("bad signature from %s", keyname)
545 }
546 http.Error(w, "what did you call me?", http.StatusTeapot)
547 return
548 }
549 who, _ := j.GetString("actor")
550 origin := keymatch(keyname, who)
551 if origin == "" {
552 ilog.Printf("keyname actor mismatch: %s <> %s", keyname, who)
553 return
554 }
555 if rejectactor(user.ID, who) {
556 return
557 }
558 re_ont := regexp.MustCompile("https://" + serverName + "/o/([\\pL[:digit:]]+)")
559 what := firstofmany(j, "type")
560 dlog.Printf("server got a %s", what)
561 switch what {
562 case "Follow":
563 obj, _ := j.GetString("object")
564 if obj == user.URL {
565 ilog.Printf("can't follow the server!")
566 return
567 }
568 m := re_ont.FindStringSubmatch(obj)
569 if len(m) != 2 {
570 ilog.Printf("not sure how to handle this")
571 return
572 }
573 ont := "#" + m[1]
574
575 followme(user, who, ont, j)
576 case "Undo":
577 obj, ok := j.GetMap("object")
578 if !ok {
579 ilog.Printf("unknown undo no object")
580 return
581 }
582 what := firstofmany(obj, "type")
583 if what != "Follow" {
584 ilog.Printf("unknown undo: %s", what)
585 return
586 }
587 targ, _ := obj.GetString("object")
588 m := re_ont.FindStringSubmatch(targ)
589 if len(m) != 2 {
590 ilog.Printf("not sure how to handle this")
591 return
592 }
593 ont := "#" + m[1]
594 unfollowme(user, who, ont, j)
595 default:
596 ilog.Printf("unhandled server activity: %s", what)
597 dumpactivity(j)
598 }
599}
600
601func serveractor(w http.ResponseWriter, r *http.Request) {
602 user := getserveruser()
603 if stealthmode(user.ID, r) {
604 http.NotFound(w, r)
605 return
606 }
607 j := junkuser(user)
608 j.Write(w)
609}
610
611func ximport(w http.ResponseWriter, r *http.Request) {
612 u := login.GetUserInfo(r)
613 xid := strings.TrimSpace(r.FormValue("q"))
614 xonk := getxonk(u.UserID, xid)
615 if xonk == nil {
616 p, _ := investigate(xid)
617 if p != nil {
618 xid = p.XID
619 }
620 j, err := GetJunk(u.UserID, xid)
621 if err != nil {
622 http.Error(w, "error getting external object", http.StatusInternalServerError)
623 ilog.Printf("error getting external object: %s", err)
624 return
625 }
626 allinjest(originate(xid), j)
627 dlog.Printf("importing %s", xid)
628 user, _ := butwhatabout(u.Username)
629
630 info, _ := somethingabout(j)
631 if info == nil {
632 xonk = xonksaver(user, j, originate(xid))
633 } else if info.What == SomeActor {
634 outbox, _ := j.GetString("outbox")
635 gimmexonks(user, outbox)
636 http.Redirect(w, r, "/h?xid="+url.QueryEscape(xid), http.StatusSeeOther)
637 return
638 } else if info.What == SomeCollection {
639 gimmexonks(user, xid)
640 http.Redirect(w, r, "/xzone", http.StatusSeeOther)
641 return
642 }
643 }
644 convoy := ""
645 if xonk != nil {
646 convoy = xonk.Convoy
647 }
648 http.Redirect(w, r, "/t?c="+url.QueryEscape(convoy), http.StatusSeeOther)
649}
650
651func xzone(w http.ResponseWriter, r *http.Request) {
652 u := login.GetUserInfo(r)
653 rows, err := stmtRecentHonkers.Query(u.UserID, u.UserID)
654 if err != nil {
655 elog.Printf("query err: %s", err)
656 return
657 }
658 defer rows.Close()
659 var honkers []Honker
660 for rows.Next() {
661 var xid string
662 rows.Scan(&xid)
663 honkers = append(honkers, Honker{XID: xid})
664 }
665 rows.Close()
666 for i := range honkers {
667 _, honkers[i].Handle = handles(honkers[i].XID)
668 }
669 templinfo := getInfo(r)
670 templinfo["XCSRF"] = login.GetCSRF("ximport", r)
671 templinfo["Honkers"] = honkers
672 err = readviews.Execute(w, "xzone.html", templinfo)
673 if err != nil {
674 elog.Print(err)
675 }
676}
677
678var oldoutbox = cache.New(cache.Options{Filler: func(name string) ([]byte, bool) {
679 user, err := butwhatabout(name)
680 if err != nil {
681 return nil, false
682 }
683 honks := gethonksbyuser(name, false, 0)
684 if len(honks) > 20 {
685 honks = honks[0:20]
686 }
687
688 var jonks []junk.Junk
689 for _, h := range honks {
690 j, _ := jonkjonk(user, h)
691 jonks = append(jonks, j)
692 }
693
694 j := junk.New()
695 j["@context"] = itiswhatitis
696 j["id"] = user.URL + "/outbox"
697 j["attributedTo"] = user.URL
698 j["type"] = "OrderedCollection"
699 j["totalItems"] = len(jonks)
700 j["orderedItems"] = jonks
701
702 return j.ToBytes(), true
703}, Duration: 1 * time.Minute})
704
705func outbox(w http.ResponseWriter, r *http.Request) {
706 name := mux.Vars(r)["name"]
707 user, err := butwhatabout(name)
708 if err != nil {
709 http.NotFound(w, r)
710 return
711 }
712 if stealthmode(user.ID, r) {
713 http.NotFound(w, r)
714 return
715 }
716 var j []byte
717 ok := oldoutbox.Get(name, &j)
718 if ok {
719 w.Header().Set("Content-Type", theonetruename)
720 w.Write(j)
721 } else {
722 http.NotFound(w, r)
723 }
724}
725
726var oldempties = cache.New(cache.Options{Filler: func(url string) ([]byte, bool) {
727 colname := "/followers"
728 if strings.HasSuffix(url, "/following") {
729 colname = "/following"
730 }
731 user := fmt.Sprintf("https://%s%s", serverName, url[:len(url)-10])
732 j := junk.New()
733 j["@context"] = itiswhatitis
734 j["id"] = user + colname
735 j["attributedTo"] = user
736 j["type"] = "OrderedCollection"
737 j["totalItems"] = 0
738 j["orderedItems"] = []junk.Junk{}
739
740 return j.ToBytes(), true
741}})
742
743func emptiness(w http.ResponseWriter, r *http.Request) {
744 name := mux.Vars(r)["name"]
745 user, err := butwhatabout(name)
746 if err != nil {
747 http.NotFound(w, r)
748 return
749 }
750 if stealthmode(user.ID, r) {
751 http.NotFound(w, r)
752 return
753 }
754 var j []byte
755 ok := oldempties.Get(r.URL.Path, &j)
756 if ok {
757 w.Header().Set("Content-Type", theonetruename)
758 w.Write(j)
759 } else {
760 http.NotFound(w, r)
761 }
762}
763
764func showuser(w http.ResponseWriter, r *http.Request) {
765 name := mux.Vars(r)["name"]
766 user, err := butwhatabout(name)
767 if err != nil {
768 ilog.Printf("user not found %s: %s", name, err)
769 http.NotFound(w, r)
770 return
771 }
772 if stealthmode(user.ID, r) {
773 http.NotFound(w, r)
774 return
775 }
776 if friendorfoe(r.Header.Get("Accept")) {
777 j, ok := asjonker(name)
778 if ok {
779 w.Header().Set("Content-Type", theonetruename)
780 w.Write(j)
781 } else {
782 http.NotFound(w, r)
783 }
784 return
785 }
786 u := login.GetUserInfo(r)
787 if u != nil && u.Username != name {
788 u = nil
789 }
790 honks := gethonksbyuser(name, u != nil, 0)
791 templinfo := getInfo(r)
792 templinfo["PageName"] = "user"
793 templinfo["PageArg"] = name
794 templinfo["Name"] = user.Name
795 templinfo["WhatAbout"] = user.HTAbout
796 templinfo["ServerMessage"] = ""
797 templinfo["APAltLink"] = templates.Sprintf("<link href='%s' rel='alternate' type='application/activity+json'>", user.URL)
798 if u != nil {
799 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
800 }
801 honkpage(w, u, honks, templinfo)
802}
803
804func showhonker(w http.ResponseWriter, r *http.Request) {
805 u := login.GetUserInfo(r)
806 name := mux.Vars(r)["name"]
807 var honks []*Honk
808 if name == "" {
809 name = r.FormValue("xid")
810 honks = gethonksbyxonker(u.UserID, name, 0)
811 } else {
812 honks = gethonksbyhonker(u.UserID, name, 0)
813 }
814 miniform := templates.Sprintf(`<form action="/submithonker" method="POST">
815<input type="hidden" name="CSRF" value="%s">
816<input type="hidden" name="url" value="%s">
817<button tabindex=1 name="add honker" value="add honker">add honker</button>
818</form>`, login.GetCSRF("submithonker", r), name)
819 msg := templates.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>%s`, name, name, miniform)
820 templinfo := getInfo(r)
821 templinfo["PageName"] = "honker"
822 templinfo["PageArg"] = name
823 templinfo["ServerMessage"] = msg
824 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
825 honkpage(w, u, honks, templinfo)
826}
827
828func showcombo(w http.ResponseWriter, r *http.Request) {
829 name := mux.Vars(r)["name"]
830 u := login.GetUserInfo(r)
831 honks := gethonksbycombo(u.UserID, name, 0)
832 honks = osmosis(honks, u.UserID, true)
833 templinfo := getInfo(r)
834 templinfo["PageName"] = "combo"
835 templinfo["PageArg"] = name
836 templinfo["ServerMessage"] = "honks by combo: " + name
837 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
838 honkpage(w, u, honks, templinfo)
839}
840func showconvoy(w http.ResponseWriter, r *http.Request) {
841 c := r.FormValue("c")
842 u := login.GetUserInfo(r)
843 honks := gethonksbyconvoy(u.UserID, c, 0)
844 templinfo := getInfo(r)
845 if len(honks) > 0 {
846 templinfo["TopHID"] = honks[0].ID
847 }
848 honks = osmosis(honks, u.UserID, false)
849 //reversehonks(honks)
850 honks = threadsort(honks)
851 templinfo["PageName"] = "convoy"
852 templinfo["PageArg"] = c
853 templinfo["ServerMessage"] = "honks in convoy: " + c
854 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
855 honkpage(w, u, honks, templinfo)
856}
857func showsearch(w http.ResponseWriter, r *http.Request) {
858 q := r.FormValue("q")
859 if strings.HasPrefix(q, "https://") {
860 ximport(w, r)
861 return
862 }
863 u := login.GetUserInfo(r)
864 honks := gethonksbysearch(u.UserID, q, 0)
865 templinfo := getInfo(r)
866 templinfo["PageName"] = "search"
867 templinfo["PageArg"] = q
868 templinfo["ServerMessage"] = "honks for search: " + q
869 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
870 honkpage(w, u, honks, templinfo)
871}
872func showontology(w http.ResponseWriter, r *http.Request) {
873 name := mux.Vars(r)["name"]
874 u := login.GetUserInfo(r)
875 var userid int64 = -1
876 if u != nil {
877 userid = u.UserID
878 }
879 honks := gethonksbyontology(userid, "#"+name, 0)
880 if friendorfoe(r.Header.Get("Accept")) {
881 if len(honks) > 40 {
882 honks = honks[0:40]
883 }
884
885 var xids []string
886 for _, h := range honks {
887 xids = append(xids, h.XID)
888 }
889
890 user := getserveruser()
891
892 j := junk.New()
893 j["@context"] = itiswhatitis
894 j["id"] = fmt.Sprintf("https://%s/o/%s", serverName, name)
895 j["name"] = "#" + name
896 j["attributedTo"] = user.URL
897 j["type"] = "OrderedCollection"
898 j["totalItems"] = len(xids)
899 j["orderedItems"] = xids
900
901 j.Write(w)
902 return
903 }
904
905 templinfo := getInfo(r)
906 templinfo["ServerMessage"] = "honks by ontology: " + name
907 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
908 honkpage(w, u, honks, templinfo)
909}
910
911type Ont struct {
912 Name string
913 Count int64
914}
915
916func thelistingoftheontologies(w http.ResponseWriter, r *http.Request) {
917 u := login.GetUserInfo(r)
918 var userid int64 = -1
919 if u != nil {
920 userid = u.UserID
921 }
922 rows, err := stmtAllOnts.Query(userid)
923 if err != nil {
924 elog.Printf("selection error: %s", err)
925 return
926 }
927 defer rows.Close()
928 var onts []Ont
929 for rows.Next() {
930 var o Ont
931 err := rows.Scan(&o.Name, &o.Count)
932 if err != nil {
933 elog.Printf("error scanning ont: %s", err)
934 continue
935 }
936 if utf8.RuneCountInString(o.Name) > 24 {
937 continue
938 }
939 o.Name = o.Name[1:]
940 onts = append(onts, o)
941 }
942 sort.Slice(onts, func(i, j int) bool {
943 return onts[i].Name < onts[j].Name
944 })
945 if u == nil && !develMode {
946 w.Header().Set("Cache-Control", "max-age=300")
947 }
948 templinfo := getInfo(r)
949 templinfo["Onts"] = onts
950 templinfo["FirstRune"] = func(s string) rune { r, _ := utf8.DecodeRuneInString(s); return r }
951 err = readviews.Execute(w, "onts.html", templinfo)
952 if err != nil {
953 elog.Print(err)
954 }
955}
956
957type Track struct {
958 xid string
959 who string
960}
961
962func getbacktracks(xid string) []string {
963 c := make(chan bool)
964 dumptracks <- c
965 <-c
966 row := stmtGetTracks.QueryRow(xid)
967 var rawtracks string
968 err := row.Scan(&rawtracks)
969 if err != nil {
970 if err != sql.ErrNoRows {
971 elog.Printf("error scanning tracks: %s", err)
972 }
973 return nil
974 }
975 var rcpts []string
976 for _, f := range strings.Split(rawtracks, " ") {
977 idx := strings.LastIndexByte(f, '#')
978 if idx != -1 {
979 f = f[:idx]
980 }
981 if !strings.HasPrefix(f, "https://") {
982 f = fmt.Sprintf("%%https://%s/inbox", f)
983 }
984 rcpts = append(rcpts, f)
985 }
986 return rcpts
987}
988
989func savetracks(tracks map[string][]string) {
990 db := opendatabase()
991 tx, err := db.Begin()
992 if err != nil {
993 elog.Printf("savetracks begin error: %s", err)
994 return
995 }
996 defer func() {
997 err := tx.Commit()
998 if err != nil {
999 elog.Printf("savetracks commit error: %s", err)
1000 }
1001
1002 }()
1003 stmtGetTracks, err := tx.Prepare("select fetches from tracks where xid = ?")
1004 if err != nil {
1005 elog.Printf("savetracks error: %s", err)
1006 return
1007 }
1008 stmtNewTracks, err := tx.Prepare("insert into tracks (xid, fetches) values (?, ?)")
1009 if err != nil {
1010 elog.Printf("savetracks error: %s", err)
1011 return
1012 }
1013 stmtUpdateTracks, err := tx.Prepare("update tracks set fetches = ? where xid = ?")
1014 if err != nil {
1015 elog.Printf("savetracks error: %s", err)
1016 return
1017 }
1018 count := 0
1019 for xid, f := range tracks {
1020 count += len(f)
1021 var prev string
1022 row := stmtGetTracks.QueryRow(xid)
1023 err := row.Scan(&prev)
1024 if err == sql.ErrNoRows {
1025 f = oneofakind(f)
1026 stmtNewTracks.Exec(xid, strings.Join(f, " "))
1027 } else if err == nil {
1028 all := append(strings.Split(prev, " "), f...)
1029 all = oneofakind(all)
1030 stmtUpdateTracks.Exec(strings.Join(all, " "))
1031 } else {
1032 elog.Printf("savetracks error: %s", err)
1033 }
1034 }
1035 dlog.Printf("saved %d new fetches", count)
1036}
1037
1038var trackchan = make(chan Track)
1039var dumptracks = make(chan chan bool)
1040
1041func tracker() {
1042 timeout := 4 * time.Minute
1043 sleeper := time.NewTimer(timeout)
1044 tracks := make(map[string][]string)
1045 workinprogress++
1046 for {
1047 select {
1048 case track := <-trackchan:
1049 tracks[track.xid] = append(tracks[track.xid], track.who)
1050 case <-sleeper.C:
1051 if len(tracks) > 0 {
1052 go savetracks(tracks)
1053 tracks = make(map[string][]string)
1054 }
1055 sleeper.Reset(timeout)
1056 case c := <-dumptracks:
1057 if len(tracks) > 0 {
1058 savetracks(tracks)
1059 }
1060 c <- true
1061 case <-endoftheworld:
1062 if len(tracks) > 0 {
1063 savetracks(tracks)
1064 }
1065 readyalready <- true
1066 return
1067 }
1068 }
1069}
1070
1071var re_keyholder = regexp.MustCompile(`keyId="([^"]+)"`)
1072
1073func trackback(xid string, r *http.Request) {
1074 agent := r.UserAgent()
1075 who := originate(agent)
1076 sig := r.Header.Get("Signature")
1077 if sig != "" {
1078 m := re_keyholder.FindStringSubmatch(sig)
1079 if len(m) == 2 {
1080 who = m[1]
1081 }
1082 }
1083 if who != "" {
1084 trackchan <- Track{xid: xid, who: who}
1085 }
1086}
1087
1088func sameperson(h1, h2 *Honk) bool {
1089 n1, n2 := h1.Honker, h2.Honker
1090 if h1.Oonker != "" {
1091 n1 = h1.Oonker
1092 }
1093 if h2.Oonker != "" {
1094 n2 = h2.Oonker
1095 }
1096 return n1 == n2
1097}
1098
1099func threadsort(honks []*Honk) []*Honk {
1100 sort.Slice(honks, func(i, j int) bool {
1101 return honks[i].Date.Before(honks[j].Date)
1102 })
1103 honkx := make(map[string]*Honk)
1104 kids := make(map[string][]*Honk)
1105 for _, h := range honks {
1106 honkx[h.XID] = h
1107 rid := h.RID
1108 kids[rid] = append(kids[rid], h)
1109 }
1110 done := make(map[*Honk]bool)
1111 var thread []*Honk
1112 var nextlevel func(p *Honk)
1113 level := 0
1114 nextlevel = func(p *Honk) {
1115 levelup := level < 4
1116 if pp := honkx[p.RID]; p.RID == "" || (pp != nil && sameperson(p, pp)) {
1117 levelup = false
1118 }
1119 if level > 0 && len(kids[p.RID]) == 1 {
1120 if pp := honkx[p.RID]; pp != nil && len(kids[pp.RID]) == 1 {
1121 levelup = false
1122 }
1123 }
1124 if levelup {
1125 level++
1126 }
1127 p.Style += fmt.Sprintf(" level%d", level)
1128 childs := kids[p.XID]
1129 if false {
1130 sort.SliceStable(childs, func(i, j int) bool {
1131 return sameperson(childs[i], p) && !sameperson(childs[j], p)
1132 })
1133 }
1134 if true {
1135 sort.SliceStable(childs, func(i, j int) bool {
1136 return !sameperson(childs[i], p) && sameperson(childs[j], p)
1137 })
1138 }
1139 for _, h := range childs {
1140 if !done[h] {
1141 done[h] = true
1142 thread = append(thread, h)
1143 nextlevel(h)
1144 }
1145 }
1146 if levelup {
1147 level--
1148 }
1149 }
1150 for _, h := range honks {
1151 if !done[h] && h.RID == "" {
1152 done[h] = true
1153 thread = append(thread, h)
1154 nextlevel(h)
1155 }
1156 }
1157 for _, h := range honks {
1158 if !done[h] {
1159 done[h] = true
1160 thread = append(thread, h)
1161 nextlevel(h)
1162 }
1163 }
1164 return thread
1165}
1166
1167func honkology(honk *Honk) template.HTML {
1168 var user *WhatAbout
1169 ok := somenumberedusers.Get(honk.UserID, &user)
1170 if !ok {
1171 return ""
1172 }
1173 title := fmt.Sprintf("%s: %s", user.Display, honk.Precis)
1174 imgurl := avatarURL(user)
1175 for _, d := range honk.Donks {
1176 if d.Local && strings.HasPrefix(d.Media, "image") {
1177 imgurl = d.URL
1178 break
1179 }
1180 }
1181 short := honk.Noise
1182 if len(short) > 160 {
1183 short = short[0:160] + "..."
1184 }
1185 return templates.Sprintf(
1186 `<meta property="og:title" content="%s" />
1187<meta property="og:type" content="article" />
1188<meta property="article:author" content="%s" />
1189<meta property="og:url" content="%s" />
1190<meta property="og:image" content="%s" />
1191<meta property="og:description" content="%s" />`,
1192 title, user.URL, honk.XID, imgurl, short)
1193}
1194
1195func showonehonk(w http.ResponseWriter, r *http.Request) {
1196 name := mux.Vars(r)["name"]
1197 user, err := butwhatabout(name)
1198 if err != nil {
1199 http.NotFound(w, r)
1200 return
1201 }
1202 if stealthmode(user.ID, r) {
1203 http.NotFound(w, r)
1204 return
1205 }
1206 xid := fmt.Sprintf("https://%s%s", serverName, r.URL.Path)
1207
1208 if friendorfoe(r.Header.Get("Accept")) {
1209 j, ok := gimmejonk(xid)
1210 if ok {
1211 trackback(xid, r)
1212 w.Header().Set("Content-Type", theonetruename)
1213 w.Write(j)
1214 } else {
1215 http.NotFound(w, r)
1216 }
1217 return
1218 }
1219 honk := getxonk(user.ID, xid)
1220 if honk == nil {
1221 http.NotFound(w, r)
1222 return
1223 }
1224 u := login.GetUserInfo(r)
1225 if u != nil && u.UserID != user.ID {
1226 u = nil
1227 }
1228 if !honk.Public {
1229 if u == nil {
1230 http.NotFound(w, r)
1231 return
1232
1233 }
1234 honks := []*Honk{honk}
1235 donksforhonks(honks)
1236 templinfo := getInfo(r)
1237 templinfo["ServerMessage"] = "one honk maybe more"
1238 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1239 honkpage(w, u, honks, templinfo)
1240 return
1241 }
1242
1243 templinfo := getInfo(r)
1244 rawhonks := gethonksbyconvoy(honk.UserID, honk.Convoy, 0)
1245 //reversehonks(rawhonks)
1246 rawhonks = threadsort(rawhonks)
1247 var honks []*Honk
1248 for i, h := range rawhonks {
1249 if h.XID == xid {
1250 templinfo["Honkology"] = honkology(h)
1251 if i > 0 {
1252 h.Style += " glow"
1253 }
1254 }
1255 if h.Public && (h.Whofore == 2 || h.IsAcked()) {
1256 honks = append(honks, h)
1257 }
1258 }
1259
1260 templinfo["ServerMessage"] = "one honk maybe more"
1261 if u != nil {
1262 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1263 }
1264 templinfo["APAltLink"] = templates.Sprintf("<link href='%s' rel='alternate' type='application/activity+json'>", xid)
1265 honkpage(w, u, honks, templinfo)
1266}
1267
1268func honkpage(w http.ResponseWriter, u *login.UserInfo, honks []*Honk, templinfo map[string]interface{}) {
1269 var userid int64 = -1
1270 if u != nil {
1271 userid = u.UserID
1272 templinfo["User"], _ = butwhatabout(u.Username)
1273 }
1274 reverbolate(userid, honks)
1275 templinfo["Honks"] = honks
1276 templinfo["MapLink"] = getmaplink(u)
1277 if templinfo["TopHID"] == nil {
1278 if len(honks) > 0 {
1279 templinfo["TopHID"] = honks[0].ID
1280 } else {
1281 templinfo["TopHID"] = 0
1282 }
1283 }
1284 if u == nil && !develMode {
1285 w.Header().Set("Cache-Control", "max-age=60")
1286 }
1287 err := readviews.Execute(w, "honkpage.html", templinfo)
1288 if err != nil {
1289 elog.Print(err)
1290 }
1291}
1292
1293func saveuser(w http.ResponseWriter, r *http.Request) {
1294 whatabout := r.FormValue("whatabout")
1295 whatabout = strings.Replace(whatabout, "\r", "", -1)
1296 u := login.GetUserInfo(r)
1297 user, _ := butwhatabout(u.Username)
1298 db := opendatabase()
1299
1300 options := user.Options
1301 if r.FormValue("skinny") == "skinny" {
1302 options.SkinnyCSS = true
1303 } else {
1304 options.SkinnyCSS = false
1305 }
1306 if r.FormValue("omitimages") == "omitimages" {
1307 options.OmitImages = true
1308 } else {
1309 options.OmitImages = false
1310 }
1311 if r.FormValue("mentionall") == "mentionall" {
1312 options.MentionAll = true
1313 } else {
1314 options.MentionAll = false
1315 }
1316 if r.FormValue("inlineqts") == "inlineqts" {
1317 options.InlineQuotes = true
1318 } else {
1319 options.InlineQuotes = false
1320 }
1321 if r.FormValue("maps") == "apple" {
1322 options.MapLink = "apple"
1323 } else {
1324 options.MapLink = ""
1325 }
1326 options.Reaction = r.FormValue("reaction")
1327
1328 sendupdate := false
1329 ava := re_avatar.FindString(whatabout)
1330 if ava != "" {
1331 whatabout = re_avatar.ReplaceAllString(whatabout, "")
1332 ava = ava[7:]
1333 if ava[0] == ' ' {
1334 ava = ava[1:]
1335 }
1336 ava = fmt.Sprintf("https://%s/meme/%s", serverName, ava)
1337 }
1338 if ava != options.Avatar {
1339 options.Avatar = ava
1340 sendupdate = true
1341 }
1342 ban := re_banner.FindString(whatabout)
1343 if ban != "" {
1344 whatabout = re_banner.ReplaceAllString(whatabout, "")
1345 ban = ban[7:]
1346 if ban[0] == ' ' {
1347 ban = ban[1:]
1348 }
1349 ban = fmt.Sprintf("https://%s/meme/%s", serverName, ban)
1350 }
1351 if ban != options.Banner {
1352 options.Banner = ban
1353 sendupdate = true
1354 }
1355 whatabout = strings.TrimSpace(whatabout)
1356 if whatabout != user.About {
1357 sendupdate = true
1358 }
1359 j, err := jsonify(options)
1360 if err == nil {
1361 _, err = db.Exec("update users set about = ?, options = ? where username = ?", whatabout, j, u.Username)
1362 }
1363 if err != nil {
1364 elog.Printf("error bouting what: %s", err)
1365 }
1366 somenamedusers.Clear(u.Username)
1367 somenumberedusers.Clear(u.UserID)
1368 oldjonkers.Clear(u.Username)
1369
1370 if sendupdate {
1371 updateMe(u.Username)
1372 }
1373
1374 http.Redirect(w, r, "/account", http.StatusSeeOther)
1375}
1376
1377func bonkit(xid string, user *WhatAbout) {
1378 dlog.Printf("bonking %s", xid)
1379
1380 xonk := getxonk(user.ID, xid)
1381 if xonk == nil {
1382 return
1383 }
1384 if !xonk.Public {
1385 return
1386 }
1387 if xonk.IsBonked() {
1388 return
1389 }
1390 donksforhonks([]*Honk{xonk})
1391
1392 _, err := stmtUpdateFlags.Exec(flagIsBonked, xonk.ID)
1393 if err != nil {
1394 elog.Printf("error acking bonk: %s", err)
1395 }
1396
1397 oonker := xonk.Oonker
1398 if oonker == "" {
1399 oonker = xonk.Honker
1400 }
1401 dt := time.Now().UTC()
1402 bonk := &Honk{
1403 UserID: user.ID,
1404 Username: user.Name,
1405 What: "bonk",
1406 Honker: user.URL,
1407 Oonker: oonker,
1408 XID: xonk.XID,
1409 RID: xonk.RID,
1410 Noise: xonk.Noise,
1411 Precis: xonk.Precis,
1412 URL: xonk.URL,
1413 Date: dt,
1414 Donks: xonk.Donks,
1415 Whofore: 2,
1416 Convoy: xonk.Convoy,
1417 Audience: []string{thewholeworld, oonker},
1418 Public: true,
1419 Format: xonk.Format,
1420 Place: xonk.Place,
1421 Onts: xonk.Onts,
1422 Time: xonk.Time,
1423 }
1424
1425 err = savehonk(bonk)
1426 if err != nil {
1427 elog.Printf("uh oh")
1428 return
1429 }
1430
1431 go honkworldwide(user, bonk)
1432}
1433
1434func submitbonk(w http.ResponseWriter, r *http.Request) {
1435 xid := r.FormValue("xid")
1436 userinfo := login.GetUserInfo(r)
1437 user, _ := butwhatabout(userinfo.Username)
1438
1439 bonkit(xid, user)
1440
1441 if r.FormValue("js") != "1" {
1442 templinfo := getInfo(r)
1443 templinfo["ServerMessage"] = "Bonked!"
1444 err := readviews.Execute(w, "msg.html", templinfo)
1445 if err != nil {
1446 elog.Print(err)
1447 }
1448 }
1449}
1450
1451func sendzonkofsorts(xonk *Honk, user *WhatAbout, what string, aux string) {
1452 zonk := &Honk{
1453 What: what,
1454 XID: xonk.XID,
1455 Date: time.Now().UTC(),
1456 Audience: oneofakind(xonk.Audience),
1457 Noise: aux,
1458 }
1459 zonk.Public = loudandproud(zonk.Audience)
1460
1461 dlog.Printf("announcing %sed honk: %s", what, xonk.XID)
1462 go honkworldwide(user, zonk)
1463}
1464
1465func zonkit(w http.ResponseWriter, r *http.Request) {
1466 wherefore := r.FormValue("wherefore")
1467 what := r.FormValue("what")
1468 userinfo := login.GetUserInfo(r)
1469 user, _ := butwhatabout(userinfo.Username)
1470
1471 if wherefore == "save" {
1472 xonk := getxonk(userinfo.UserID, what)
1473 if xonk != nil {
1474 _, err := stmtUpdateFlags.Exec(flagIsSaved, xonk.ID)
1475 if err != nil {
1476 elog.Printf("error saving: %s", err)
1477 }
1478 }
1479 return
1480 }
1481
1482 if wherefore == "unsave" {
1483 xonk := getxonk(userinfo.UserID, what)
1484 if xonk != nil {
1485 _, err := stmtClearFlags.Exec(flagIsSaved, xonk.ID)
1486 if err != nil {
1487 elog.Printf("error unsaving: %s", err)
1488 }
1489 }
1490 return
1491 }
1492
1493 if wherefore == "react" {
1494 reaction := user.Options.Reaction
1495 if r2 := r.FormValue("reaction"); r2 != "" {
1496 reaction = r2
1497 }
1498 if reaction == "none" {
1499 return
1500 }
1501 xonk := getxonk(userinfo.UserID, what)
1502 if xonk != nil {
1503 _, err := stmtUpdateFlags.Exec(flagIsReacted, xonk.ID)
1504 if err != nil {
1505 elog.Printf("error saving: %s", err)
1506 }
1507 sendzonkofsorts(xonk, user, "react", reaction)
1508 }
1509 return
1510 }
1511
1512 // my hammer is too big, oh well
1513 defer oldjonks.Flush()
1514
1515 if wherefore == "ack" {
1516 xonk := getxonk(userinfo.UserID, what)
1517 if xonk != nil && !xonk.IsAcked() {
1518 _, err := stmtUpdateFlags.Exec(flagIsAcked, xonk.ID)
1519 if err != nil {
1520 elog.Printf("error acking: %s", err)
1521 }
1522 sendzonkofsorts(xonk, user, "ack", "")
1523 }
1524 return
1525 }
1526
1527 if wherefore == "deack" {
1528 xonk := getxonk(userinfo.UserID, what)
1529 if xonk != nil && xonk.IsAcked() {
1530 _, err := stmtClearFlags.Exec(flagIsAcked, xonk.ID)
1531 if err != nil {
1532 elog.Printf("error deacking: %s", err)
1533 }
1534 sendzonkofsorts(xonk, user, "deack", "")
1535 }
1536 return
1537 }
1538
1539 if wherefore == "bonk" {
1540 user, _ := butwhatabout(userinfo.Username)
1541 bonkit(what, user)
1542 return
1543 }
1544
1545 if wherefore == "unbonk" {
1546 xonk := getbonk(userinfo.UserID, what)
1547 if xonk != nil {
1548 deletehonk(xonk.ID)
1549 xonk = getxonk(userinfo.UserID, what)
1550 _, err := stmtClearFlags.Exec(flagIsBonked, xonk.ID)
1551 if err != nil {
1552 elog.Printf("error unbonking: %s", err)
1553 }
1554 sendzonkofsorts(xonk, user, "unbonk", "")
1555 }
1556 return
1557 }
1558
1559 if wherefore == "untag" {
1560 xonk := getxonk(userinfo.UserID, what)
1561 if xonk != nil {
1562 _, err := stmtUpdateFlags.Exec(flagIsUntagged, xonk.ID)
1563 if err != nil {
1564 elog.Printf("error untagging: %s", err)
1565 }
1566 }
1567 var badparents map[string]bool
1568 untagged.GetAndLock(userinfo.UserID, &badparents)
1569 badparents[what] = true
1570 untagged.Unlock()
1571 return
1572 }
1573
1574 ilog.Printf("zonking %s %s", wherefore, what)
1575 if wherefore == "zonk" {
1576 xonk := getxonk(userinfo.UserID, what)
1577 if xonk != nil {
1578 deletehonk(xonk.ID)
1579 if xonk.Whofore == 2 || xonk.Whofore == 3 {
1580 sendzonkofsorts(xonk, user, "zonk", "")
1581 }
1582 }
1583 }
1584 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
1585 if err != nil {
1586 elog.Printf("error saving zonker: %s", err)
1587 return
1588 }
1589}
1590
1591func edithonkpage(w http.ResponseWriter, r *http.Request) {
1592 u := login.GetUserInfo(r)
1593 user, _ := butwhatabout(u.Username)
1594 xid := r.FormValue("xid")
1595 honk := getxonk(u.UserID, xid)
1596 if !canedithonk(user, honk) {
1597 http.Error(w, "no editing that please", http.StatusInternalServerError)
1598 return
1599 }
1600
1601 noise := honk.Noise
1602
1603 honks := []*Honk{honk}
1604 donksforhonks(honks)
1605 reverbolate(u.UserID, honks)
1606 templinfo := getInfo(r)
1607 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1608 templinfo["Honks"] = honks
1609 templinfo["MapLink"] = getmaplink(u)
1610 templinfo["Noise"] = noise
1611 templinfo["SavedPlace"] = honk.Place
1612 if tm := honk.Time; tm != nil {
1613 templinfo["ShowTime"] = " "
1614 templinfo["StartTime"] = tm.StartTime.Format("2006-01-02 15:04")
1615 if tm.Duration != 0 {
1616 templinfo["Duration"] = tm.Duration
1617 }
1618 }
1619 templinfo["ServerMessage"] = "honk edit"
1620 templinfo["IsPreview"] = true
1621 templinfo["UpdateXID"] = honk.XID
1622 if len(honk.Donks) > 0 {
1623 var savedfiles []string
1624 for _, d := range honk.Donks {
1625 savedfiles = append(savedfiles, fmt.Sprintf("%s:%d", d.XID, d.FileID))
1626 }
1627 templinfo["SavedFile"] = strings.Join(savedfiles, ",")
1628 }
1629 err := readviews.Execute(w, "honkpage.html", templinfo)
1630 if err != nil {
1631 elog.Print(err)
1632 }
1633}
1634
1635func newhonkpage(w http.ResponseWriter, r *http.Request) {
1636 u := login.GetUserInfo(r)
1637 rid := r.FormValue("rid")
1638 noise := ""
1639
1640 xonk := getxonk(u.UserID, rid)
1641 if xonk != nil {
1642 _, replto := handles(xonk.Honker)
1643 if replto != "" {
1644 noise = "@" + replto + " "
1645 }
1646 }
1647
1648 templinfo := getInfo(r)
1649 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1650 templinfo["InReplyTo"] = rid
1651 templinfo["Noise"] = noise
1652 templinfo["ServerMessage"] = "compose honk"
1653 templinfo["IsPreview"] = true
1654 err := readviews.Execute(w, "honkpage.html", templinfo)
1655 if err != nil {
1656 elog.Print(err)
1657 }
1658}
1659
1660func canedithonk(user *WhatAbout, honk *Honk) bool {
1661 if honk == nil || honk.Honker != user.URL || honk.What == "bonk" {
1662 return false
1663 }
1664 return true
1665}
1666
1667func submitdonk(w http.ResponseWriter, r *http.Request) ([]*Donk, error) {
1668 if !strings.HasPrefix(strings.ToLower(r.Header.Get("Content-Type")), "multipart/form-data") {
1669 return nil, nil
1670 }
1671 var donks []*Donk
1672 for i, hdr := range r.MultipartForm.File["donk"] {
1673 if i > 16 {
1674 break
1675 }
1676 donk, err := formtodonk(w, r, hdr)
1677 if err != nil {
1678 return nil, err
1679 }
1680 donks = append(donks, donk)
1681 }
1682 return donks, nil
1683}
1684
1685func formtodonk(w http.ResponseWriter, r *http.Request, filehdr *multipart.FileHeader) (*Donk, error) {
1686 file, err := filehdr.Open()
1687 if err != nil {
1688 if err == http.ErrMissingFile {
1689 return nil, nil
1690 }
1691 elog.Printf("error reading donk: %s", err)
1692 http.Error(w, "error reading donk", http.StatusUnsupportedMediaType)
1693 return nil, err
1694 }
1695 var buf bytes.Buffer
1696 io.Copy(&buf, file)
1697 file.Close()
1698 data := buf.Bytes()
1699 var media, name string
1700 img, err := bigshrink(data)
1701 if err == nil {
1702 data = img.Data
1703 format := img.Format
1704 media = "image/" + format
1705 if format == "jpeg" {
1706 format = "jpg"
1707 }
1708 if format == "svg+xml" {
1709 format = "svg"
1710 }
1711 name = xfiltrate() + "." + format
1712 } else {
1713 ct := http.DetectContentType(data)
1714 switch ct {
1715 case "application/pdf":
1716 maxsize := 10000000
1717 if len(data) > maxsize {
1718 ilog.Printf("bad image: %s too much pdf: %d", err, len(data))
1719 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1720 return nil, err
1721 }
1722 media = ct
1723 name = filehdr.Filename
1724 if name == "" {
1725 name = xfiltrate() + ".pdf"
1726 }
1727 default:
1728 maxsize := 100000
1729 if len(data) > maxsize {
1730 ilog.Printf("bad image: %s too much text: %d", err, len(data))
1731 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1732 return nil, err
1733 }
1734 for i := 0; i < len(data); i++ {
1735 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
1736 ilog.Printf("bad image: %s not text: %d", err, data[i])
1737 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1738 return nil, err
1739 }
1740 }
1741 media = "text/plain"
1742 name = filehdr.Filename
1743 if name == "" {
1744 name = xfiltrate() + ".txt"
1745 }
1746 }
1747 }
1748 desc := strings.TrimSpace(r.FormValue("donkdesc"))
1749 if desc == "" {
1750 desc = name
1751 }
1752 fileid, xid, err := savefileandxid(name, desc, "", media, true, data)
1753 if err != nil {
1754 elog.Printf("unable to save image: %s", err)
1755 http.Error(w, "failed to save attachment", http.StatusUnsupportedMediaType)
1756 return nil, err
1757 }
1758 d := &Donk{
1759 FileID: fileid,
1760 XID: xid,
1761 Desc: desc,
1762 Local: true,
1763 }
1764 return d, nil
1765}
1766
1767func websubmithonk(w http.ResponseWriter, r *http.Request) {
1768 h := submithonk(w, r)
1769 if h == nil {
1770 return
1771 }
1772 http.Redirect(w, r, h.XID[len(serverName)+8:], http.StatusSeeOther)
1773}
1774
1775// what a hot mess this function is
1776func submithonk(w http.ResponseWriter, r *http.Request) *Honk {
1777 rid := r.FormValue("rid")
1778 noise := r.FormValue("noise")
1779 format := r.FormValue("format")
1780 if format == "" {
1781 format = "markdown"
1782 }
1783 if !(format == "markdown" || format == "html") {
1784 http.Error(w, "unknown format", 500)
1785 return nil
1786 }
1787
1788 userinfo := login.GetUserInfo(r)
1789 user, _ := butwhatabout(userinfo.Username)
1790
1791 dt := time.Now().UTC()
1792 updatexid := r.FormValue("updatexid")
1793 var honk *Honk
1794 if updatexid != "" {
1795 honk = getxonk(userinfo.UserID, updatexid)
1796 if !canedithonk(user, honk) {
1797 http.Error(w, "no editing that please", http.StatusInternalServerError)
1798 return nil
1799 }
1800 honk.Date = dt
1801 honk.What = "update"
1802 honk.Format = format
1803 } else {
1804 xid := fmt.Sprintf("%s/%s/%s", user.URL, honkSep, xfiltrate())
1805 what := "honk"
1806 honk = &Honk{
1807 UserID: userinfo.UserID,
1808 Username: userinfo.Username,
1809 What: what,
1810 Honker: user.URL,
1811 XID: xid,
1812 Date: dt,
1813 Format: format,
1814 }
1815 }
1816
1817 var convoy string
1818 noise = strings.Replace(noise, "\r", "", -1)
1819 if updatexid == "" && rid == "" {
1820 noise = re_convoy.ReplaceAllStringFunc(noise, func(m string) string {
1821 convoy = m[7:]
1822 convoy = strings.TrimSpace(convoy)
1823 if !re_convalidate.MatchString(convoy) {
1824 convoy = ""
1825 }
1826 return ""
1827 })
1828 }
1829 noise = quickrename(noise, userinfo.UserID)
1830 noise = hooterize(noise)
1831 honk.Noise = noise
1832 precipitate(honk)
1833 noise = honk.Noise
1834 recategorize(honk)
1835 translate(honk)
1836
1837 if rid != "" {
1838 xonk := getxonk(userinfo.UserID, rid)
1839 if xonk == nil {
1840 http.Error(w, "replyto disappeared", http.StatusNotFound)
1841 return nil
1842 }
1843 if xonk.Public {
1844 honk.Audience = append(honk.Audience, xonk.Audience...)
1845 }
1846 convoy = xonk.Convoy
1847 for i, a := range honk.Audience {
1848 if a == thewholeworld {
1849 honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
1850 break
1851 }
1852 }
1853 honk.RID = rid
1854 if xonk.Precis != "" && honk.Precis == "" {
1855 honk.Precis = xonk.Precis
1856 if !re_dangerous.MatchString(honk.Precis) {
1857 honk.Precis = "re: " + honk.Precis
1858 }
1859 }
1860 } else if updatexid == "" {
1861 honk.Audience = []string{thewholeworld}
1862 }
1863 if honk.Noise != "" && honk.Noise[0] == '@' {
1864 honk.Audience = append(grapevine(honk.Mentions), honk.Audience...)
1865 } else {
1866 honk.Audience = append(honk.Audience, grapevine(honk.Mentions)...)
1867 }
1868
1869 if convoy == "" {
1870 convoy = "data:,electrichonkytonk-" + xfiltrate()
1871 }
1872 butnottooloud(honk.Audience)
1873 honk.Audience = oneofakind(honk.Audience)
1874 if len(honk.Audience) == 0 {
1875 ilog.Printf("honk to nowhere")
1876 http.Error(w, "honk to nowhere...", http.StatusNotFound)
1877 return nil
1878 }
1879 honk.Public = loudandproud(honk.Audience)
1880 honk.Convoy = convoy
1881
1882 donkxid := strings.Join(r.Form["donkxid"], ",")
1883 if donkxid == "" {
1884 donks, err := submitdonk(w, r)
1885 if err != nil && err != http.ErrMissingFile {
1886 return nil
1887 }
1888 if len(donks) > 0 {
1889 honk.Donks = append(honk.Donks, donks...)
1890 var xids []string
1891 for _, d := range honk.Donks {
1892 xids = append(xids, fmt.Sprintf("%s:%d", d.XID, d.FileID))
1893 }
1894 donkxid = strings.Join(xids, ",")
1895 }
1896 } else {
1897 xids := strings.Split(donkxid, ",")
1898 for i, xid := range xids {
1899 if i > 16 {
1900 break
1901 }
1902 p := strings.Split(xid, ":")
1903 xid = p[0]
1904 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1905 var donk *Donk
1906 if len(p) > 1 {
1907 fileid, _ := strconv.ParseInt(p[1], 10, 0)
1908 donk = finddonkid(fileid, url)
1909 } else {
1910 donk = finddonk(url)
1911 }
1912 if donk != nil {
1913 honk.Donks = append(honk.Donks, donk)
1914 } else {
1915 ilog.Printf("can't find file: %s", xid)
1916 }
1917 }
1918 }
1919 memetize(honk)
1920 imaginate(honk)
1921
1922 placename := strings.TrimSpace(r.FormValue("placename"))
1923 placelat := strings.TrimSpace(r.FormValue("placelat"))
1924 placelong := strings.TrimSpace(r.FormValue("placelong"))
1925 placeurl := strings.TrimSpace(r.FormValue("placeurl"))
1926 if placename != "" || placelat != "" || placelong != "" || placeurl != "" {
1927 p := new(Place)
1928 p.Name = placename
1929 p.Latitude, _ = strconv.ParseFloat(placelat, 64)
1930 p.Longitude, _ = strconv.ParseFloat(placelong, 64)
1931 p.Url = placeurl
1932 honk.Place = p
1933 }
1934 timestart := strings.TrimSpace(r.FormValue("timestart"))
1935 if timestart != "" {
1936 t := new(Time)
1937 now := time.Now().Local()
1938 for _, layout := range []string{"2006-01-02 3:04pm", "2006-01-02 15:04", "3:04pm", "15:04"} {
1939 start, err := time.ParseInLocation(layout, timestart, now.Location())
1940 if err == nil {
1941 if start.Year() == 0 {
1942 start = time.Date(now.Year(), now.Month(), now.Day(), start.Hour(), start.Minute(), 0, 0, now.Location())
1943 }
1944 t.StartTime = start
1945 break
1946 }
1947 }
1948 timeend := r.FormValue("timeend")
1949 dur := parseDuration(timeend)
1950 if dur != 0 {
1951 t.Duration = Duration(dur)
1952 }
1953 if !t.StartTime.IsZero() {
1954 honk.What = "event"
1955 honk.Time = t
1956 }
1957 }
1958
1959 if honk.Public {
1960 honk.Whofore = 2
1961 } else {
1962 honk.Whofore = 3
1963 }
1964
1965 // back to markdown
1966 honk.Noise = noise
1967
1968 if r.FormValue("preview") == "preview" {
1969 honks := []*Honk{honk}
1970 reverbolate(userinfo.UserID, honks)
1971 templinfo := getInfo(r)
1972 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1973 templinfo["Honks"] = honks
1974 templinfo["MapLink"] = getmaplink(userinfo)
1975 templinfo["InReplyTo"] = r.FormValue("rid")
1976 templinfo["Noise"] = r.FormValue("noise")
1977 templinfo["SavedFile"] = donkxid
1978 if tm := honk.Time; tm != nil {
1979 templinfo["ShowTime"] = " "
1980 templinfo["StartTime"] = tm.StartTime.Format("2006-01-02 15:04")
1981 if tm.Duration != 0 {
1982 templinfo["Duration"] = tm.Duration
1983 }
1984 }
1985 templinfo["IsPreview"] = true
1986 templinfo["UpdateXID"] = updatexid
1987 templinfo["ServerMessage"] = "honk preview"
1988 err := readviews.Execute(w, "honkpage.html", templinfo)
1989 if err != nil {
1990 elog.Print(err)
1991 }
1992 return nil
1993 }
1994
1995 if updatexid != "" {
1996 updatehonk(honk)
1997 oldjonks.Clear(honk.XID)
1998 } else {
1999 err := savehonk(honk)
2000 if err != nil {
2001 elog.Printf("uh oh")
2002 return nil
2003 }
2004 }
2005
2006 // reload for consistency
2007 honk.Donks = nil
2008 donksforhonks([]*Honk{honk})
2009
2010 go honkworldwide(user, honk)
2011
2012 return honk
2013}
2014
2015func showhonkers(w http.ResponseWriter, r *http.Request) {
2016 userinfo := login.GetUserInfo(r)
2017 templinfo := getInfo(r)
2018 templinfo["Honkers"] = gethonkers(userinfo.UserID)
2019 templinfo["HonkerCSRF"] = login.GetCSRF("submithonker", r)
2020 err := readviews.Execute(w, "honkers.html", templinfo)
2021 if err != nil {
2022 elog.Print(err)
2023 }
2024}
2025
2026func showchatter(w http.ResponseWriter, r *http.Request) {
2027 u := login.GetUserInfo(r)
2028 chatnewnone(u.UserID)
2029 chatter := loadchatter(u.UserID)
2030 for _, chat := range chatter {
2031 for _, ch := range chat.Chonks {
2032 filterchonk(ch)
2033 }
2034 }
2035
2036 templinfo := getInfo(r)
2037 templinfo["Chatter"] = chatter
2038 templinfo["ChonkCSRF"] = login.GetCSRF("sendchonk", r)
2039 err := readviews.Execute(w, "chatter.html", templinfo)
2040 if err != nil {
2041 elog.Print(err)
2042 }
2043}
2044
2045func submitchonk(w http.ResponseWriter, r *http.Request) {
2046 u := login.GetUserInfo(r)
2047 user, _ := butwhatabout(u.Username)
2048 noise := r.FormValue("noise")
2049 target := r.FormValue("target")
2050 format := "markdown"
2051 dt := time.Now().UTC()
2052 xid := fmt.Sprintf("%s/%s/%s", user.URL, "chonk", xfiltrate())
2053
2054 if !strings.HasPrefix(target, "https://") {
2055 target = fullname(target, u.UserID)
2056 }
2057 if target == "" {
2058 http.Error(w, "who is that?", http.StatusInternalServerError)
2059 return
2060 }
2061 ch := Chonk{
2062 UserID: u.UserID,
2063 XID: xid,
2064 Who: user.URL,
2065 Target: target,
2066 Date: dt,
2067 Noise: noise,
2068 Format: format,
2069 }
2070 donks, err := submitdonk(w, r)
2071 if err != nil && err != http.ErrMissingFile {
2072 return
2073 }
2074 if len(donks) > 0 {
2075 ch.Donks = append(ch.Donks, donks...)
2076 }
2077
2078 translatechonk(&ch)
2079 savechonk(&ch)
2080 // reload for consistency
2081 ch.Donks = nil
2082 donksforchonks([]*Chonk{&ch})
2083 go sendchonk(user, &ch)
2084
2085 http.Redirect(w, r, "/chatter", http.StatusSeeOther)
2086}
2087
2088var combocache = cache.New(cache.Options{Filler: func(userid int64) ([]string, bool) {
2089 honkers := gethonkers(userid)
2090 var combos []string
2091 for _, h := range honkers {
2092 combos = append(combos, h.Combos...)
2093 }
2094 for i, c := range combos {
2095 if c == "-" {
2096 combos[i] = ""
2097 }
2098 }
2099 combos = oneofakind(combos)
2100 sort.Strings(combos)
2101 return combos, true
2102}, Invalidator: &honkerinvalidator})
2103
2104func showcombos(w http.ResponseWriter, r *http.Request) {
2105 userinfo := login.GetUserInfo(r)
2106 var combos []string
2107 combocache.Get(userinfo.UserID, &combos)
2108 templinfo := getInfo(r)
2109 err := readviews.Execute(w, "combos.html", templinfo)
2110 if err != nil {
2111 elog.Print(err)
2112 }
2113}
2114
2115func websubmithonker(w http.ResponseWriter, r *http.Request) {
2116 h := submithonker(w, r)
2117 if h == nil {
2118 return
2119 }
2120 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
2121}
2122
2123func submithonker(w http.ResponseWriter, r *http.Request) *Honker {
2124 u := login.GetUserInfo(r)
2125 user, _ := butwhatabout(u.Username)
2126 name := strings.TrimSpace(r.FormValue("name"))
2127 url := strings.TrimSpace(r.FormValue("url"))
2128 peep := r.FormValue("peep")
2129 combos := strings.TrimSpace(r.FormValue("combos"))
2130 combos = " " + combos + " "
2131 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
2132
2133 re_namecheck := regexp.MustCompile("^[\\pL[:digit:]_.-]+$")
2134 if name != "" && !re_namecheck.MatchString(name) {
2135 http.Error(w, "please use a plainer name", http.StatusInternalServerError)
2136 return nil
2137 }
2138
2139 var meta HonkerMeta
2140 meta.Notes = strings.TrimSpace(r.FormValue("notes"))
2141 mj, _ := jsonify(&meta)
2142
2143 defer honkerinvalidator.Clear(u.UserID)
2144
2145 // mostly dummy, fill in later...
2146 h := &Honker{
2147 ID: honkerid,
2148 }
2149
2150 if honkerid > 0 {
2151 if r.FormValue("delete") == "delete" {
2152 unfollowyou(user, honkerid, false)
2153 stmtDeleteHonker.Exec(honkerid)
2154 return h
2155 }
2156 if r.FormValue("unsub") == "unsub" {
2157 unfollowyou(user, honkerid, false)
2158 }
2159 if r.FormValue("sub") == "sub" {
2160 followyou(user, honkerid, false)
2161 }
2162 _, err := stmtUpdateHonker.Exec(name, combos, mj, honkerid, u.UserID)
2163 if err != nil {
2164 elog.Printf("update honker err: %s", err)
2165 return nil
2166 }
2167 return h
2168 }
2169
2170 if url == "" {
2171 http.Error(w, "subscribing to nothing?", http.StatusInternalServerError)
2172 return nil
2173 }
2174
2175 flavor := "presub"
2176 if peep == "peep" {
2177 flavor = "peep"
2178 }
2179
2180 var err error
2181 honkerid, err = savehonker(user, url, name, flavor, combos, mj)
2182 if err != nil {
2183 http.Error(w, "had some trouble with that: "+err.Error(), http.StatusInternalServerError)
2184 return nil
2185 }
2186 if flavor == "presub" {
2187 followyou(user, honkerid, false)
2188 }
2189 h.ID = honkerid
2190 return h
2191}
2192
2193func hfcspage(w http.ResponseWriter, r *http.Request) {
2194 userinfo := login.GetUserInfo(r)
2195
2196 filters := getfilters(userinfo.UserID, filtAny)
2197
2198 templinfo := getInfo(r)
2199 templinfo["Filters"] = filters
2200 templinfo["FilterCSRF"] = login.GetCSRF("filter", r)
2201 err := readviews.Execute(w, "hfcs.html", templinfo)
2202 if err != nil {
2203 elog.Print(err)
2204 }
2205}
2206
2207func savehfcs(w http.ResponseWriter, r *http.Request) {
2208 userinfo := login.GetUserInfo(r)
2209 itsok := r.FormValue("itsok")
2210 if itsok == "iforgiveyou" {
2211 hfcsid, _ := strconv.ParseInt(r.FormValue("hfcsid"), 10, 0)
2212 _, err := stmtDeleteFilter.Exec(userinfo.UserID, hfcsid)
2213 if err != nil {
2214 elog.Printf("error deleting filter: %s", err)
2215 }
2216 filtInvalidator.Clear(userinfo.UserID)
2217 http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
2218 return
2219 }
2220
2221 filt := new(Filter)
2222 filt.Name = strings.TrimSpace(r.FormValue("name"))
2223 filt.Date = time.Now().UTC()
2224 filt.Actor = strings.TrimSpace(r.FormValue("actor"))
2225 filt.IncludeAudience = r.FormValue("incaud") == "yes"
2226 filt.Text = strings.TrimSpace(r.FormValue("filttext"))
2227 filt.IsReply = r.FormValue("isreply") == "yes"
2228 filt.IsAnnounce = r.FormValue("isannounce") == "yes"
2229 filt.AnnounceOf = strings.TrimSpace(r.FormValue("announceof"))
2230 filt.Reject = r.FormValue("doreject") == "yes"
2231 filt.SkipMedia = r.FormValue("doskipmedia") == "yes"
2232 filt.Hide = r.FormValue("dohide") == "yes"
2233 filt.Collapse = r.FormValue("docollapse") == "yes"
2234 filt.Rewrite = strings.TrimSpace(r.FormValue("filtrewrite"))
2235 filt.Replace = strings.TrimSpace(r.FormValue("filtreplace"))
2236 if dur := parseDuration(r.FormValue("filtduration")); dur > 0 {
2237 filt.Expiration = time.Now().UTC().Add(dur)
2238 }
2239 filt.Notes = strings.TrimSpace(r.FormValue("filtnotes"))
2240
2241 if filt.Actor == "" && filt.Text == "" && !filt.IsAnnounce {
2242 ilog.Printf("blank filter")
2243 http.Error(w, "can't save a blank filter", http.StatusInternalServerError)
2244 return
2245 }
2246
2247 j, err := jsonify(filt)
2248 if err == nil {
2249 _, err = stmtSaveFilter.Exec(userinfo.UserID, j)
2250 }
2251 if err != nil {
2252 elog.Printf("error saving filter: %s", err)
2253 }
2254
2255 filtInvalidator.Clear(userinfo.UserID)
2256 http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
2257}
2258
2259func accountpage(w http.ResponseWriter, r *http.Request) {
2260 u := login.GetUserInfo(r)
2261 user, _ := butwhatabout(u.Username)
2262 templinfo := getInfo(r)
2263 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
2264 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
2265 templinfo["User"] = user
2266 about := user.About
2267 if ava := user.Options.Avatar; ava != "" {
2268 about += "\n\navatar: " + ava[strings.LastIndexByte(ava, '/')+1:]
2269 }
2270 if ban := user.Options.Banner; ban != "" {
2271 about += "\n\nbanner: " + ban[strings.LastIndexByte(ban, '/')+1:]
2272 }
2273 templinfo["WhatAbout"] = about
2274 err := readviews.Execute(w, "account.html", templinfo)
2275 if err != nil {
2276 elog.Print(err)
2277 }
2278}
2279
2280func dochpass(w http.ResponseWriter, r *http.Request) {
2281 err := login.ChangePassword(w, r)
2282 if err != nil {
2283 elog.Printf("error changing password: %s", err)
2284 }
2285 http.Redirect(w, r, "/account", http.StatusSeeOther)
2286}
2287
2288var oldfingers = cache.New(cache.Options{Filler: func(orig string) ([]byte, bool) {
2289 if strings.HasPrefix(orig, "acct:") {
2290 orig = orig[5:]
2291 }
2292 name := orig
2293 idx := strings.LastIndexByte(name, '/')
2294 if idx != -1 {
2295 name = name[idx+1:]
2296 if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
2297 ilog.Printf("foreign request rejected")
2298 name = ""
2299 }
2300 } else {
2301 idx = strings.IndexByte(name, '@')
2302 if idx != -1 {
2303 name = name[:idx]
2304 if !(name+"@"+serverName == orig || name+"@"+masqName == orig) {
2305 ilog.Printf("foreign request rejected")
2306 name = ""
2307 }
2308 }
2309 }
2310 user, err := butwhatabout(name)
2311 if err != nil {
2312 return nil, false
2313 }
2314
2315 j := junk.New()
2316 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, masqName)
2317 j["aliases"] = []string{user.URL}
2318 l := junk.New()
2319 l["rel"] = "self"
2320 l["type"] = `application/activity+json`
2321 l["href"] = user.URL
2322 j["links"] = []junk.Junk{l}
2323 return j.ToBytes(), true
2324}})
2325
2326func fingerlicker(w http.ResponseWriter, r *http.Request) {
2327 orig := r.FormValue("resource")
2328
2329 dlog.Printf("finger lick: %s", orig)
2330
2331 var j []byte
2332 ok := oldfingers.Get(orig, &j)
2333 if ok {
2334 w.Header().Set("Content-Type", "application/jrd+json")
2335 w.Write(j)
2336 } else {
2337 http.NotFound(w, r)
2338 }
2339}
2340
2341func somedays() string {
2342 secs := 432000 + notrand.Int63n(432000)
2343 return fmt.Sprintf("%d", secs)
2344}
2345
2346func lookatme(ava string) string {
2347 if strings.Contains(ava, serverName+"/"+userSep) {
2348 idx := strings.LastIndexByte(ava, '/')
2349 if idx < len(ava) {
2350 name := ava[idx+1:]
2351 user, _ := butwhatabout(name)
2352 if user != nil && user.URL == ava {
2353 return user.Options.Avatar
2354 }
2355 }
2356 }
2357 return ""
2358}
2359
2360func avatate(w http.ResponseWriter, r *http.Request) {
2361 if develMode {
2362 loadAvatarColors()
2363 }
2364 n := r.FormValue("a")
2365 if redir := lookatme(n); redir != "" {
2366 http.Redirect(w, r, redir, http.StatusSeeOther)
2367 return
2368 }
2369 a := genAvatar(n)
2370 if !develMode {
2371 w.Header().Set("Cache-Control", "max-age="+somedays())
2372 }
2373 w.Write(a)
2374}
2375
2376func serveviewasset(w http.ResponseWriter, r *http.Request) {
2377 serveasset(w, r, viewDir)
2378}
2379func servedataasset(w http.ResponseWriter, r *http.Request) {
2380 if r.URL.Path == "/favicon.ico" {
2381 r.URL.Path = "/icon.png"
2382 }
2383 serveasset(w, r, dataDir)
2384}
2385
2386func serveasset(w http.ResponseWriter, r *http.Request, basedir string) {
2387 if !develMode {
2388 w.Header().Set("Cache-Control", "max-age=7776000")
2389 }
2390 http.ServeFile(w, r, basedir+"/views"+r.URL.Path)
2391}
2392func servehelp(w http.ResponseWriter, r *http.Request) {
2393 name := mux.Vars(r)["name"]
2394 if !develMode {
2395 w.Header().Set("Cache-Control", "max-age=3600")
2396 }
2397 http.ServeFile(w, r, viewDir+"/docs/"+name)
2398}
2399func servehtml(w http.ResponseWriter, r *http.Request) {
2400 u := login.GetUserInfo(r)
2401 templinfo := getInfo(r)
2402 templinfo["AboutMsg"] = aboutMsg
2403 templinfo["LoginMsg"] = loginMsg
2404 templinfo["HonkVersion"] = softwareVersion
2405 if r.URL.Path == "/about" {
2406 templinfo["Sensors"] = getSensors()
2407 }
2408 if u == nil && !develMode {
2409 w.Header().Set("Cache-Control", "max-age=60")
2410 }
2411 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
2412 if err != nil {
2413 elog.Print(err)
2414 }
2415}
2416func serveemu(w http.ResponseWriter, r *http.Request) {
2417 emu := mux.Vars(r)["emu"]
2418
2419 w.Header().Set("Cache-Control", "max-age="+somedays())
2420 http.ServeFile(w, r, dataDir+"/emus/"+emu)
2421}
2422func servememe(w http.ResponseWriter, r *http.Request) {
2423 meme := mux.Vars(r)["meme"]
2424
2425 w.Header().Set("Cache-Control", "max-age="+somedays())
2426 _, err := os.Stat(dataDir + "/memes/" + meme)
2427 if err == nil {
2428 http.ServeFile(w, r, dataDir+"/memes/"+meme)
2429 } else {
2430 mux.Vars(r)["xid"] = meme
2431 servefile(w, r)
2432 }
2433}
2434
2435func servefile(w http.ResponseWriter, r *http.Request) {
2436 xid := mux.Vars(r)["xid"]
2437 var media string
2438 var data []byte
2439 row := stmtGetFileData.QueryRow(xid)
2440 err := row.Scan(&media, &data)
2441 if err != nil {
2442 elog.Printf("error loading file: %s", err)
2443 http.NotFound(w, r)
2444 return
2445 }
2446 w.Header().Set("Content-Type", media)
2447 w.Header().Set("X-Content-Type-Options", "nosniff")
2448 w.Header().Set("Cache-Control", "max-age="+somedays())
2449 w.Write(data)
2450}
2451
2452func nomoroboto(w http.ResponseWriter, r *http.Request) {
2453 io.WriteString(w, "User-agent: *\n")
2454 io.WriteString(w, "Disallow: /a\n")
2455 io.WriteString(w, "Disallow: /d/\n")
2456 io.WriteString(w, "Disallow: /meme/\n")
2457 io.WriteString(w, "Disallow: /o\n")
2458 io.WriteString(w, "Disallow: /o/\n")
2459 io.WriteString(w, "Disallow: /help/\n")
2460 for _, u := range allusers() {
2461 fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
2462 }
2463}
2464
2465type Hydration struct {
2466 Tophid int64
2467 Srvmsg template.HTML
2468 Honks string
2469 MeCount int64
2470 ChatCount int64
2471}
2472
2473func webhydra(w http.ResponseWriter, r *http.Request) {
2474 u := login.GetUserInfo(r)
2475 userid := u.UserID
2476 templinfo := getInfo(r)
2477 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
2478 page := r.FormValue("page")
2479
2480 wanted, _ := strconv.ParseInt(r.FormValue("tophid"), 10, 0)
2481
2482 var hydra Hydration
2483
2484 var honks []*Honk
2485 switch page {
2486 case "atme":
2487 honks = gethonksforme(userid, wanted)
2488 honks = osmosis(honks, userid, false)
2489 menewnone(userid)
2490 hydra.Srvmsg = "at me!"
2491 case "longago":
2492 honks = gethonksfromlongago(userid, wanted)
2493 honks = osmosis(honks, userid, false)
2494 hydra.Srvmsg = "from long ago"
2495 case "home":
2496 honks = gethonksforuser(userid, wanted)
2497 honks = osmosis(honks, userid, true)
2498 hydra.Srvmsg = serverMsg
2499 case "first":
2500 honks = gethonksforuserfirstclass(userid, wanted)
2501 honks = osmosis(honks, userid, true)
2502 hydra.Srvmsg = "first class only"
2503 case "saved":
2504 honks = getsavedhonks(userid, wanted)
2505 templinfo["PageName"] = "saved"
2506 hydra.Srvmsg = "saved honks"
2507 case "combo":
2508 c := r.FormValue("c")
2509 honks = gethonksbycombo(userid, c, wanted)
2510 honks = osmosis(honks, userid, false)
2511 hydra.Srvmsg = templates.Sprintf("honks by combo: %s", c)
2512 case "convoy":
2513 c := r.FormValue("c")
2514 honks = gethonksbyconvoy(userid, c, wanted)
2515 honks = osmosis(honks, userid, false)
2516 honks = threadsort(honks)
2517 reversehonks(honks)
2518 hydra.Srvmsg = templates.Sprintf("honks in convoy: %s", c)
2519 case "honker":
2520 xid := r.FormValue("xid")
2521 honks = gethonksbyxonker(userid, xid, wanted)
2522 miniform := templates.Sprintf(`<form action="/submithonker" method="POST">
2523 <input type="hidden" name="CSRF" value="%s">
2524 <input type="hidden" name="url" value="%s">
2525 <button tabindex=1 name="add honker" value="add honker">add honker</button>
2526 </form>`, login.GetCSRF("submithonker", r), xid)
2527 msg := templates.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>%s`, xid, xid, miniform)
2528 hydra.Srvmsg = msg
2529 case "user":
2530 uname := r.FormValue("uname")
2531 honks = gethonksbyuser(uname, u != nil && u.Username == uname, wanted)
2532 hydra.Srvmsg = templates.Sprintf("honks by user: %s", uname)
2533 default:
2534 http.NotFound(w, r)
2535 }
2536
2537 if len(honks) > 0 {
2538 hydra.Tophid = honks[0].ID
2539 } else {
2540 hydra.Tophid = wanted
2541 }
2542 reverbolate(userid, honks)
2543
2544 user, _ := butwhatabout(u.Username)
2545
2546 var buf strings.Builder
2547 templinfo["Honks"] = honks
2548 templinfo["MapLink"] = getmaplink(u)
2549 templinfo["User"], _ = butwhatabout(u.Username)
2550 err := readviews.Execute(&buf, "honkfrags.html", templinfo)
2551 if err != nil {
2552 elog.Printf("frag error: %s", err)
2553 return
2554 }
2555 hydra.Honks = buf.String()
2556 hydra.MeCount = user.Options.MeCount
2557 hydra.ChatCount = user.Options.ChatCount
2558 w.Header().Set("Content-Type", "application/json")
2559 j, _ := jsonify(&hydra)
2560 io.WriteString(w, j)
2561}
2562
2563var honkline = make(chan bool)
2564
2565func honkhonkline() {
2566 for {
2567 select {
2568 case honkline <- true:
2569 default:
2570 return
2571 }
2572 }
2573}
2574
2575func apihandler(w http.ResponseWriter, r *http.Request) {
2576 u := login.GetUserInfo(r)
2577 userid := u.UserID
2578 action := r.FormValue("action")
2579 wait, _ := strconv.ParseInt(r.FormValue("wait"), 10, 0)
2580 dlog.Printf("api request '%s' on behalf of %s", action, u.Username)
2581 switch action {
2582 case "honk":
2583 h := submithonk(w, r)
2584 if h == nil {
2585 return
2586 }
2587 fmt.Fprintf(w, "%s", h.XID)
2588 case "donk":
2589 donks, err := submitdonk(w, r)
2590 if err != nil {
2591 http.Error(w, err.Error(), http.StatusBadRequest)
2592 return
2593 }
2594 if len(donks) == 0 {
2595 http.Error(w, "missing donk", http.StatusBadRequest)
2596 return
2597 }
2598 d := donks[0]
2599 donkxid := fmt.Sprintf("%s:%d", d.XID, d.FileID)
2600 w.Write([]byte(donkxid))
2601 case "zonkit":
2602 zonkit(w, r)
2603 case "gethonks":
2604 var honks []*Honk
2605 wanted, _ := strconv.ParseInt(r.FormValue("after"), 10, 0)
2606 page := r.FormValue("page")
2607 var waitchan <-chan time.Time
2608 requery:
2609 switch page {
2610 case "atme":
2611 honks = gethonksforme(userid, wanted)
2612 honks = osmosis(honks, userid, false)
2613 menewnone(userid)
2614 case "longago":
2615 honks = gethonksfromlongago(userid, wanted)
2616 honks = osmosis(honks, userid, false)
2617 case "home":
2618 honks = gethonksforuser(userid, wanted)
2619 honks = osmosis(honks, userid, true)
2620 case "myhonks":
2621 honks = gethonksbyuser(u.Username, true, wanted)
2622 honks = osmosis(honks, userid, true)
2623 default:
2624 http.Error(w, "unknown page", http.StatusNotFound)
2625 return
2626 }
2627 if len(honks) == 0 && wait > 0 {
2628 if waitchan == nil {
2629 waitchan = time.After(time.Duration(wait) * time.Second)
2630 }
2631 select {
2632 case <-honkline:
2633 goto requery
2634 case <-waitchan:
2635 }
2636 }
2637 reverbolate(userid, honks)
2638 j := junk.New()
2639 j["honks"] = honks
2640 j.Write(w)
2641 case "sendactivity":
2642 user, _ := butwhatabout(u.Username)
2643 public := r.FormValue("public") == "1"
2644 rcpts := boxuprcpts(user, r.Form["rcpt"], public)
2645 msg := []byte(r.FormValue("msg"))
2646 for rcpt := range rcpts {
2647 go deliverate(userid, rcpt, msg)
2648 }
2649 case "gethonkers":
2650 j := junk.New()
2651 j["honkers"] = gethonkers(u.UserID)
2652 j.Write(w)
2653 case "savehonker":
2654 h := submithonker(w, r)
2655 if h == nil {
2656 return
2657 }
2658 fmt.Fprintf(w, "%d", h.ID)
2659 default:
2660 http.Error(w, "unknown action", http.StatusNotFound)
2661 return
2662 }
2663}
2664
2665func fiveoh(w http.ResponseWriter, r *http.Request) {
2666 if !develMode {
2667 return
2668 }
2669 fd, err := os.OpenFile("violations.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
2670 if err != nil {
2671 elog.Printf("error opening violations! %s", err)
2672 return
2673 }
2674 defer fd.Close()
2675 io.Copy(fd, r.Body)
2676 fd.WriteString("\n")
2677}
2678
2679var endoftheworld = make(chan bool)
2680var readyalready = make(chan bool)
2681var workinprogress = 0
2682
2683func enditall() {
2684 sig := make(chan os.Signal, 1)
2685 signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
2686 <-sig
2687 ilog.Printf("stopping...")
2688 for i := 0; i < workinprogress; i++ {
2689 endoftheworld <- true
2690 }
2691 ilog.Printf("waiting...")
2692 for i := 0; i < workinprogress; i++ {
2693 <-readyalready
2694 }
2695 ilog.Printf("apocalypse")
2696 os.Exit(0)
2697}
2698
2699var preservehooks []func()
2700
2701func bgmonitor() {
2702 for {
2703 when := time.Now().Add(-3 * 24 * time.Hour).UTC().Format(dbtimeformat)
2704 _, err := stmtDeleteOldXonkers.Exec("pubkey", when)
2705 if err != nil {
2706 elog.Printf("error deleting old xonkers: %s", err)
2707 }
2708 zaggies.Flush()
2709 time.Sleep(50 * time.Minute)
2710 }
2711}
2712
2713func addcspheaders(next http.Handler) http.Handler {
2714 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2715 policy := "default-src 'none'; script-src 'self'; connect-src 'self'; style-src 'self'; img-src 'self'; media-src 'self'"
2716 if develMode {
2717 policy += "; report-uri /csp-violation"
2718 }
2719 w.Header().Set("Content-Security-Policy", policy)
2720 next.ServeHTTP(w, r)
2721 })
2722}
2723
2724func emuinit() {
2725 var emunames []string
2726 dir, err := os.Open(dataDir + "/emus")
2727 if err == nil {
2728 emunames, _ = dir.Readdirnames(0)
2729 dir.Close()
2730 }
2731 for _, e := range emunames {
2732 if len(e) <= 4 {
2733 continue
2734 }
2735 ext := e[len(e)-4:]
2736 emu := Emu{
2737 ID: fmt.Sprintf("/emu/%s", e),
2738 Name: e[:len(e)-4],
2739 Type: "image/" + ext[1:],
2740 }
2741 allemus = append(allemus, emu)
2742 }
2743 sort.Slice(allemus, func(i, j int) bool {
2744 return allemus[i].Name < allemus[j].Name
2745 })
2746}
2747
2748var savedassetparams = make(map[string]string)
2749
2750func getassetparam(file string) string {
2751 if p, ok := savedassetparams[file]; ok {
2752 return p
2753 }
2754 data, err := os.ReadFile(file)
2755 if err != nil {
2756 return ""
2757 }
2758 hasher := sha512.New()
2759 hasher.Write(data)
2760
2761 return fmt.Sprintf("?v=%.8x", hasher.Sum(nil))
2762}
2763
2764var usefcgi bool
2765
2766func serve() {
2767 db := opendatabase()
2768 login.Init(login.InitArgs{Db: db, Logger: ilog, Insecure: develMode, SameSiteStrict: !develMode})
2769
2770 listener, err := openListener()
2771 if err != nil {
2772 elog.Fatal(err)
2773 }
2774 runBackendServer()
2775 go enditall()
2776 go redeliverator()
2777 go tracker()
2778 go bgmonitor()
2779 go qotd()
2780 loadLingo()
2781 emuinit()
2782
2783 readviews = templates.Load(develMode,
2784 viewDir+"/views/honkpage.html",
2785 viewDir+"/views/honkfrags.html",
2786 viewDir+"/views/honkers.html",
2787 viewDir+"/views/chatter.html",
2788 viewDir+"/views/hfcs.html",
2789 viewDir+"/views/combos.html",
2790 viewDir+"/views/honkform.html",
2791 viewDir+"/views/honk.html",
2792 viewDir+"/views/account.html",
2793 viewDir+"/views/about.html",
2794 viewDir+"/views/funzone.html",
2795 viewDir+"/views/login.html",
2796 viewDir+"/views/xzone.html",
2797 viewDir+"/views/msg.html",
2798 viewDir+"/views/header.html",
2799 viewDir+"/views/onts.html",
2800 viewDir+"/views/emus.html",
2801 viewDir+"/views/honkpage.js",
2802 )
2803 if !develMode {
2804 assets := []string{
2805 viewDir + "/views/style.css",
2806 dataDir + "/views/local.css",
2807 viewDir + "/views/honkpage.js",
2808 viewDir + "/views/misc.js",
2809 dataDir + "/views/local.js",
2810 }
2811 for _, s := range assets {
2812 savedassetparams[s] = getassetparam(s)
2813 }
2814 loadAvatarColors()
2815 }
2816
2817 for _, h := range preservehooks {
2818 h()
2819 }
2820
2821 mux := mux.NewRouter()
2822 mux.Use(addcspheaders)
2823 mux.Use(login.Checker)
2824
2825 mux.Handle("/api", login.TokenRequired(http.HandlerFunc(apihandler)))
2826
2827 posters := mux.Methods("POST").Subrouter()
2828 getters := mux.Methods("GET").Subrouter()
2829
2830 getters.HandleFunc("/", homepage)
2831 getters.HandleFunc("/home", homepage)
2832 getters.HandleFunc("/front", homepage)
2833 getters.HandleFunc("/events", homepage)
2834 getters.HandleFunc("/robots.txt", nomoroboto)
2835 getters.HandleFunc("/rss", showrss)
2836 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}", showuser)
2837 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/"+honkSep+"/{xid:[\\pL[:digit:]]+}", showonehonk)
2838 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/rss", showrss)
2839 posters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/inbox", inbox)
2840 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/outbox", outbox)
2841 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/followers", emptiness)
2842 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/following", emptiness)
2843 getters.HandleFunc("/a", avatate)
2844 getters.HandleFunc("/o", thelistingoftheontologies)
2845 getters.HandleFunc("/o/{name:.+}", showontology)
2846 getters.HandleFunc("/d/{xid:[\\pL[:digit:].]+}", servefile)
2847 getters.HandleFunc("/emu/{emu:[^.]*[^/]+}", serveemu)
2848 getters.HandleFunc("/meme/{meme:[^.]*[^/]+}", servememe)
2849 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
2850
2851 getters.HandleFunc("/flag/{code:.+}", showflag)
2852
2853 getters.HandleFunc("/server", serveractor)
2854 posters.HandleFunc("/server/inbox", serverinbox)
2855 posters.HandleFunc("/inbox", serverinbox)
2856
2857 posters.HandleFunc("/csp-violation", fiveoh)
2858
2859 getters.HandleFunc("/style.css", serveviewasset)
2860 getters.HandleFunc("/honkpage.js", serveviewasset)
2861 getters.HandleFunc("/misc.js", serveviewasset)
2862 getters.HandleFunc("/local.css", servedataasset)
2863 getters.HandleFunc("/local.js", servedataasset)
2864 getters.HandleFunc("/icon.png", servedataasset)
2865 getters.HandleFunc("/favicon.ico", servedataasset)
2866
2867 getters.HandleFunc("/about", servehtml)
2868 getters.HandleFunc("/login", servehtml)
2869 posters.HandleFunc("/dologin", login.LoginFunc)
2870 getters.HandleFunc("/logout", login.LogoutFunc)
2871 getters.HandleFunc("/help/{name:[\\pL[:digit:]_.-]+}", servehelp)
2872
2873 loggedin := mux.NewRoute().Subrouter()
2874 loggedin.Use(login.Required)
2875 loggedin.HandleFunc("/first", homepage)
2876 loggedin.HandleFunc("/chatter", showchatter)
2877 loggedin.Handle("/sendchonk", login.CSRFWrap("sendchonk", http.HandlerFunc(submitchonk)))
2878 loggedin.HandleFunc("/saved", homepage)
2879 loggedin.HandleFunc("/account", accountpage)
2880 loggedin.HandleFunc("/funzone", showfunzone)
2881 loggedin.HandleFunc("/chpass", dochpass)
2882 loggedin.HandleFunc("/atme", homepage)
2883 loggedin.HandleFunc("/longago", homepage)
2884 loggedin.HandleFunc("/hfcs", hfcspage)
2885 loggedin.HandleFunc("/xzone", xzone)
2886 loggedin.HandleFunc("/newhonk", newhonkpage)
2887 loggedin.HandleFunc("/edit", edithonkpage)
2888 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(websubmithonk)))
2889 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(submitbonk)))
2890 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
2891 loggedin.Handle("/savehfcs", login.CSRFWrap("filter", http.HandlerFunc(savehfcs)))
2892 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
2893 loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
2894 loggedin.HandleFunc("/honkers", showhonkers)
2895 loggedin.HandleFunc("/h/{name:[\\pL[:digit:]_.-]+}", showhonker)
2896 loggedin.HandleFunc("/h", showhonker)
2897 loggedin.HandleFunc("/c/{name:[\\pL[:digit:]_.-]+}", showcombo)
2898 loggedin.HandleFunc("/c", showcombos)
2899 loggedin.HandleFunc("/t", showconvoy)
2900 loggedin.HandleFunc("/q", showsearch)
2901 loggedin.HandleFunc("/hydra", webhydra)
2902 loggedin.HandleFunc("/emus", showemus)
2903 loggedin.Handle("/submithonker", login.CSRFWrap("submithonker", http.HandlerFunc(websubmithonker)))
2904
2905 if usefcgi {
2906 err = fcgi.Serve(listener, mux)
2907 } else {
2908 err = http.Serve(listener, mux)
2909 }
2910
2911 err = http.Serve(listener, mux)
2912 if err != nil {
2913 elog.Fatal(err)
2914 }
2915}