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