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 "context"
21 "database/sql"
22 "fmt"
23 "html/template"
24 "io"
25 notrand "math/rand"
26 "mime/multipart"
27 "net/http"
28 "net/url"
29 "os"
30 "os/signal"
31 "path"
32 "regexp"
33 "sort"
34 "strconv"
35 "strings"
36 "syscall"
37 "time"
38 "unicode/utf8"
39
40 "github.com/gorilla/handlers"
41 "github.com/gorilla/mux"
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, _ := j.GetString("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, _ := obj.GetString("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, _ := obj.GetString("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, _ := j.GetString("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, _ := obj.GetString("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 emunames []string
1271 dir, err := os.Open(dataDir + "/emus")
1272 if err == nil {
1273 emunames, _ = dir.Readdirnames(0)
1274 dir.Close()
1275 }
1276 for i, e := range emunames {
1277 if len(e) > 4 {
1278 emunames[i] = e[:len(e)-4]
1279 }
1280 }
1281 templinfo["Emus"] = emunames
1282 var userid int64 = -1
1283 if u != nil {
1284 userid = u.UserID
1285 templinfo["User"], _ = butwhatabout(u.Username)
1286 }
1287 reverbolate(userid, honks)
1288 templinfo["Honks"] = honks
1289 templinfo["MapLink"] = getmaplink(u)
1290 if templinfo["TopHID"] == nil {
1291 if len(honks) > 0 {
1292 templinfo["TopHID"] = honks[0].ID
1293 } else {
1294 templinfo["TopHID"] = 0
1295 }
1296 }
1297 if u == nil && !develMode {
1298 w.Header().Set("Cache-Control", "max-age=60")
1299 }
1300 err = readviews.Execute(w, "honkpage.html", templinfo)
1301 if err != nil {
1302 elog.Print(err)
1303 }
1304}
1305
1306func saveuser(w http.ResponseWriter, r *http.Request) {
1307 whatabout := r.FormValue("whatabout")
1308 whatabout = strings.Replace(whatabout, "\r", "", -1)
1309 u := login.GetUserInfo(r)
1310 user, _ := butwhatabout(u.Username)
1311 db := opendatabase()
1312
1313 options := user.Options
1314 if r.FormValue("skinny") == "skinny" {
1315 options.SkinnyCSS = true
1316 } else {
1317 options.SkinnyCSS = false
1318 }
1319 if r.FormValue("omitimages") == "omitimages" {
1320 options.OmitImages = true
1321 } else {
1322 options.OmitImages = false
1323 }
1324 if r.FormValue("mentionall") == "mentionall" {
1325 options.MentionAll = true
1326 } else {
1327 options.MentionAll = false
1328 }
1329 if r.FormValue("inlineqts") == "inlineqts" {
1330 options.InlineQuotes = true
1331 } else {
1332 options.InlineQuotes = false
1333 }
1334 if r.FormValue("maps") == "apple" {
1335 options.MapLink = "apple"
1336 } else {
1337 options.MapLink = ""
1338 }
1339
1340 sendupdate := false
1341 if r.FormValue("displayname") != "" {
1342 options.CustomDisplay = r.FormValue("displayname")
1343 _, err := db.Exec("update users set displayname = ? where username = ?", options.CustomDisplay, u.Username)
1344 if err != nil {
1345 elog.Printf("error setting displayname: %s", err)
1346 }
1347 sendupdate = true
1348 } else {
1349 options.CustomDisplay = ""
1350 }
1351
1352 options.Reaction = r.FormValue("reaction")
1353
1354 ava := re_avatar.FindString(whatabout)
1355 if ava != "" {
1356 whatabout = re_avatar.ReplaceAllString(whatabout, "")
1357 ava = ava[7:]
1358 if ava[0] == ' ' {
1359 ava = ava[1:]
1360 }
1361 ava = fmt.Sprintf("https://%s/meme/%s", serverName, ava)
1362 }
1363 if ava != options.Avatar {
1364 options.Avatar = ava
1365 sendupdate = true
1366 }
1367 ban := re_banner.FindString(whatabout)
1368 if ban != "" {
1369 whatabout = re_banner.ReplaceAllString(whatabout, "")
1370 ban = ban[7:]
1371 if ban[0] == ' ' {
1372 ban = ban[1:]
1373 }
1374 ban = fmt.Sprintf("https://%s/meme/%s", serverName, ban)
1375 }
1376 if ban != options.Banner {
1377 options.Banner = ban
1378 sendupdate = true
1379 }
1380 whatabout = strings.TrimSpace(whatabout)
1381 if whatabout != user.About {
1382 sendupdate = true
1383 }
1384 j, err := jsonify(options)
1385 if err == nil {
1386 _, err = db.Exec("update users set about = ?, options = ? where username = ?", whatabout, j, u.Username)
1387 }
1388 if err != nil {
1389 elog.Printf("error bouting what: %s", err)
1390 }
1391 somenamedusers.Clear(u.Username)
1392 somenumberedusers.Clear(u.UserID)
1393 oldjonkers.Clear(u.Username)
1394
1395 if sendupdate {
1396 updateMe(u.Username)
1397 }
1398
1399 http.Redirect(w, r, "/account", http.StatusSeeOther)
1400}
1401
1402func bonkit(xid string, user *WhatAbout) {
1403 dlog.Printf("bonking %s", xid)
1404
1405 xonk := getxonk(user.ID, xid)
1406 if xonk == nil {
1407 return
1408 }
1409 if !xonk.Public {
1410 return
1411 }
1412 if xonk.IsBonked() {
1413 return
1414 }
1415 donksforhonks([]*Honk{xonk})
1416
1417 _, err := stmtUpdateFlags.Exec(flagIsBonked, xonk.ID)
1418 if err != nil {
1419 elog.Printf("error acking bonk: %s", err)
1420 }
1421
1422 oonker := xonk.Oonker
1423 if oonker == "" {
1424 oonker = xonk.Honker
1425 }
1426 dt := time.Now().UTC()
1427 bonk := &Honk{
1428 UserID: user.ID,
1429 Username: user.Name,
1430 What: "bonk",
1431 Honker: user.URL,
1432 Oonker: oonker,
1433 XID: xonk.XID,
1434 RID: xonk.RID,
1435 Noise: xonk.Noise,
1436 Precis: xonk.Precis,
1437 URL: xonk.URL,
1438 Date: dt,
1439 Donks: xonk.Donks,
1440 Whofore: 2,
1441 Convoy: xonk.Convoy,
1442 Audience: []string{thewholeworld, oonker},
1443 Public: true,
1444 Format: xonk.Format,
1445 Place: xonk.Place,
1446 Onts: xonk.Onts,
1447 Time: xonk.Time,
1448 }
1449
1450 err = savehonk(bonk)
1451 if err != nil {
1452 elog.Printf("uh oh")
1453 return
1454 }
1455
1456 go honkworldwide(user, bonk)
1457}
1458
1459func submitbonk(w http.ResponseWriter, r *http.Request) {
1460 xid := r.FormValue("xid")
1461 userinfo := login.GetUserInfo(r)
1462 user, _ := butwhatabout(userinfo.Username)
1463
1464 bonkit(xid, user)
1465
1466 if r.FormValue("js") != "1" {
1467 templinfo := getInfo(r)
1468 templinfo["ServerMessage"] = "Bonked!"
1469 err := readviews.Execute(w, "msg.html", templinfo)
1470 if err != nil {
1471 elog.Print(err)
1472 }
1473 }
1474}
1475
1476func sendzonkofsorts(xonk *Honk, user *WhatAbout, what string, aux string) {
1477 zonk := &Honk{
1478 What: what,
1479 XID: xonk.XID,
1480 Date: time.Now().UTC(),
1481 Audience: oneofakind(xonk.Audience),
1482 Noise: aux,
1483 }
1484 zonk.Public = loudandproud(zonk.Audience)
1485
1486 dlog.Printf("announcing %sed honk: %s", what, xonk.XID)
1487 go honkworldwide(user, zonk)
1488}
1489
1490func zonkit(w http.ResponseWriter, r *http.Request) {
1491 wherefore := r.FormValue("wherefore")
1492 what := r.FormValue("what")
1493 userinfo := login.GetUserInfo(r)
1494 user, _ := butwhatabout(userinfo.Username)
1495
1496 if wherefore == "save" {
1497 xonk := getxonk(userinfo.UserID, what)
1498 if xonk != nil {
1499 _, err := stmtUpdateFlags.Exec(flagIsSaved, xonk.ID)
1500 if err != nil {
1501 elog.Printf("error saving: %s", err)
1502 }
1503 }
1504 return
1505 }
1506
1507 if wherefore == "unsave" {
1508 xonk := getxonk(userinfo.UserID, what)
1509 if xonk != nil {
1510 _, err := stmtClearFlags.Exec(flagIsSaved, xonk.ID)
1511 if err != nil {
1512 elog.Printf("error unsaving: %s", err)
1513 }
1514 }
1515 return
1516 }
1517
1518 if wherefore == "react" {
1519 reaction := user.Options.Reaction
1520 if r2 := r.FormValue("reaction"); r2 != "" {
1521 reaction = r2
1522 }
1523 if reaction == "none" {
1524 return
1525 }
1526 xonk := getxonk(userinfo.UserID, what)
1527 if xonk != nil {
1528 _, err := stmtUpdateFlags.Exec(flagIsReacted, xonk.ID)
1529 if err != nil {
1530 elog.Printf("error saving: %s", err)
1531 }
1532 sendzonkofsorts(xonk, user, "react", reaction)
1533 }
1534 return
1535 }
1536
1537 // my hammer is too big, oh well
1538 defer oldjonks.Flush()
1539
1540 if wherefore == "ack" {
1541 xonk := getxonk(userinfo.UserID, what)
1542 if xonk != nil && !xonk.IsAcked() {
1543 _, err := stmtUpdateFlags.Exec(flagIsAcked, xonk.ID)
1544 if err != nil {
1545 elog.Printf("error acking: %s", err)
1546 }
1547 sendzonkofsorts(xonk, user, "ack", "")
1548 }
1549 return
1550 }
1551
1552 if wherefore == "deack" {
1553 xonk := getxonk(userinfo.UserID, what)
1554 if xonk != nil && xonk.IsAcked() {
1555 _, err := stmtClearFlags.Exec(flagIsAcked, xonk.ID)
1556 if err != nil {
1557 elog.Printf("error deacking: %s", err)
1558 }
1559 sendzonkofsorts(xonk, user, "deack", "")
1560 }
1561 return
1562 }
1563
1564 if wherefore == "bonk" {
1565 user, _ := butwhatabout(userinfo.Username)
1566 bonkit(what, user)
1567 return
1568 }
1569
1570 if wherefore == "unbonk" {
1571 xonk := getbonk(userinfo.UserID, what)
1572 if xonk != nil {
1573 deletehonk(xonk.ID)
1574 xonk = getxonk(userinfo.UserID, what)
1575 _, err := stmtClearFlags.Exec(flagIsBonked, xonk.ID)
1576 if err != nil {
1577 elog.Printf("error unbonking: %s", err)
1578 }
1579 sendzonkofsorts(xonk, user, "unbonk", "")
1580 }
1581 return
1582 }
1583
1584 if wherefore == "untag" {
1585 xonk := getxonk(userinfo.UserID, what)
1586 if xonk != nil {
1587 _, err := stmtUpdateFlags.Exec(flagIsUntagged, xonk.ID)
1588 if err != nil {
1589 elog.Printf("error untagging: %s", err)
1590 }
1591 }
1592 var badparents map[string]bool
1593 untagged.GetAndLock(userinfo.UserID, &badparents)
1594 badparents[what] = true
1595 untagged.Unlock()
1596 return
1597 }
1598
1599 ilog.Printf("zonking %s %s", wherefore, what)
1600 if wherefore == "zonk" {
1601 xonk := getxonk(userinfo.UserID, what)
1602 if xonk != nil {
1603 deletehonk(xonk.ID)
1604 if xonk.Whofore == 2 || xonk.Whofore == 3 {
1605 sendzonkofsorts(xonk, user, "zonk", "")
1606 }
1607 }
1608 }
1609 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
1610 if err != nil {
1611 elog.Printf("error saving zonker: %s", err)
1612 return
1613 }
1614}
1615
1616func edithonkpage(w http.ResponseWriter, r *http.Request) {
1617 u := login.GetUserInfo(r)
1618 user, _ := butwhatabout(u.Username)
1619 xid := r.FormValue("xid")
1620 honk := getxonk(u.UserID, xid)
1621 if !canedithonk(user, honk) {
1622 http.Error(w, "no editing that please", http.StatusInternalServerError)
1623 return
1624 }
1625
1626 noise := honk.Noise
1627
1628 honks := []*Honk{honk}
1629 donksforhonks(honks)
1630 reverbolate(u.UserID, honks)
1631 templinfo := getInfo(r)
1632 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1633 templinfo["Honks"] = honks
1634 templinfo["MapLink"] = getmaplink(u)
1635 templinfo["Noise"] = noise
1636 templinfo["SavedPlace"] = honk.Place
1637 if tm := honk.Time; tm != nil {
1638 templinfo["ShowTime"] = " "
1639 templinfo["StartTime"] = tm.StartTime.Format("2006-01-02 15:04")
1640 if tm.Duration != 0 {
1641 templinfo["Duration"] = tm.Duration
1642 }
1643 }
1644 templinfo["ServerMessage"] = "honk edit"
1645 templinfo["IsPreview"] = true
1646 templinfo["UpdateXID"] = honk.XID
1647 if len(honk.Donks) > 0 {
1648 var savedfiles []string
1649 for _, d := range honk.Donks {
1650 savedfiles = append(savedfiles, fmt.Sprintf("%s:%d", d.XID, d.FileID))
1651 }
1652 templinfo["SavedFile"] = strings.Join(savedfiles, ",")
1653 }
1654 err := readviews.Execute(w, "honkpage.html", templinfo)
1655 if err != nil {
1656 elog.Print(err)
1657 }
1658}
1659
1660func newhonkpage(w http.ResponseWriter, r *http.Request) {
1661 u := login.GetUserInfo(r)
1662 rid := r.FormValue("rid")
1663 noise := ""
1664
1665 xonk := getxonk(u.UserID, rid)
1666 if xonk != nil {
1667 _, replto := handles(xonk.Honker)
1668 if replto != "" {
1669 noise = "@" + replto + " "
1670 }
1671 }
1672
1673 templinfo := getInfo(r)
1674 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1675 templinfo["InReplyTo"] = rid
1676 templinfo["Noise"] = noise
1677 templinfo["ServerMessage"] = "compose honk"
1678 templinfo["IsPreview"] = true
1679 err := readviews.Execute(w, "honkpage.html", templinfo)
1680 if err != nil {
1681 elog.Print(err)
1682 }
1683}
1684
1685func canedithonk(user *WhatAbout, honk *Honk) bool {
1686 if honk == nil || honk.Honker != user.URL || honk.What == "bonk" {
1687 return false
1688 }
1689 return true
1690}
1691
1692func submitdonk(w http.ResponseWriter, r *http.Request) ([]*Donk, error) {
1693 if !strings.HasPrefix(strings.ToLower(r.Header.Get("Content-Type")), "multipart/form-data") {
1694 return nil, nil
1695 }
1696 var donks []*Donk
1697 for i, hdr := range r.MultipartForm.File["donk"] {
1698 if i > 16 {
1699 break
1700 }
1701 donk, err := formtodonk(w, r, hdr)
1702 if err != nil {
1703 return nil, err
1704 }
1705 donks = append(donks, donk)
1706 }
1707 return donks, nil
1708}
1709
1710func formtodonk(w http.ResponseWriter, r *http.Request, filehdr *multipart.FileHeader) (*Donk, error) {
1711 file, err := filehdr.Open()
1712 if err != nil {
1713 if err == http.ErrMissingFile {
1714 return nil, nil
1715 }
1716 elog.Printf("error reading donk: %s", err)
1717 http.Error(w, "error reading donk", http.StatusUnsupportedMediaType)
1718 return nil, err
1719 }
1720 var buf bytes.Buffer
1721 io.Copy(&buf, file)
1722 file.Close()
1723 data := buf.Bytes()
1724 var media, name string
1725 img, err := bigshrink(data)
1726 if err == nil {
1727 data = img.Data
1728 format := img.Format
1729 media = "image/" + format
1730 if format == "jpeg" {
1731 format = "jpg"
1732 }
1733 if format == "svg+xml" {
1734 format = "svg"
1735 }
1736 name = xfiltrate() + "." + format
1737 } else {
1738 ct := http.DetectContentType(data)
1739 switch ct {
1740 case "application/pdf":
1741 maxsize := 10000000
1742 if len(data) > maxsize {
1743 ilog.Printf("bad image: %s too much pdf: %d", err, len(data))
1744 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1745 return nil, err
1746 }
1747 media = ct
1748 name = filehdr.Filename
1749 if name == "" {
1750 name = xfiltrate() + ".pdf"
1751 }
1752 default:
1753 maxsize := 100000
1754 if len(data) > maxsize {
1755 ilog.Printf("bad image: %s too much text: %d", err, len(data))
1756 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1757 return nil, err
1758 }
1759 for i := 0; i < len(data); i++ {
1760 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
1761 ilog.Printf("bad image: %s not text: %d", err, data[i])
1762 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1763 return nil, err
1764 }
1765 }
1766 media = "text/plain"
1767 name = filehdr.Filename
1768 if name == "" {
1769 name = xfiltrate() + ".txt"
1770 }
1771 }
1772 }
1773 desc := strings.TrimSpace(r.FormValue("donkdesc"))
1774 if desc == "" {
1775 desc = name
1776 }
1777 fileid, xid, err := savefileandxid(name, desc, "", media, true, data)
1778 if err != nil {
1779 elog.Printf("unable to save image: %s", err)
1780 http.Error(w, "failed to save attachment", http.StatusUnsupportedMediaType)
1781 return nil, err
1782 }
1783 d := &Donk{
1784 FileID: fileid,
1785 XID: xid,
1786 Desc: desc,
1787 Local: true,
1788 }
1789 return d, nil
1790}
1791
1792func websubmithonk(w http.ResponseWriter, r *http.Request) {
1793 h := submithonk(w, r)
1794 if h == nil {
1795 return
1796 }
1797 http.Redirect(w, r, h.XID[len(serverName)+8:], http.StatusSeeOther)
1798}
1799
1800// what a hot mess this function is
1801func submithonk(w http.ResponseWriter, r *http.Request) *Honk {
1802 rid := r.FormValue("rid")
1803 noise := r.FormValue("noise")
1804 format := r.FormValue("format")
1805 if format == "" {
1806 format = "markdown"
1807 }
1808 if !(format == "markdown" || format == "html") {
1809 http.Error(w, "unknown format", 500)
1810 return nil
1811 }
1812
1813 userinfo := login.GetUserInfo(r)
1814 user, _ := butwhatabout(userinfo.Username)
1815
1816 dt := time.Now().UTC()
1817 updatexid := r.FormValue("updatexid")
1818 var honk *Honk
1819 if updatexid != "" {
1820 honk = getxonk(userinfo.UserID, updatexid)
1821 if !canedithonk(user, honk) {
1822 http.Error(w, "no editing that please", http.StatusInternalServerError)
1823 return nil
1824 }
1825 honk.Date = dt
1826 honk.What = "update"
1827 honk.Format = format
1828 } else {
1829 xid := fmt.Sprintf("%s/%s/%s", user.URL, honkSep, xfiltrate())
1830 what := "honk"
1831 honk = &Honk{
1832 UserID: userinfo.UserID,
1833 Username: userinfo.Username,
1834 What: what,
1835 Honker: user.URL,
1836 XID: xid,
1837 Date: dt,
1838 Format: format,
1839 }
1840 }
1841
1842 var convoy string
1843 noise = strings.Replace(noise, "\r", "", -1)
1844 if updatexid == "" && rid == "" {
1845 noise = re_convoy.ReplaceAllStringFunc(noise, func(m string) string {
1846 convoy = m[7:]
1847 convoy = strings.TrimSpace(convoy)
1848 if !re_convalidate.MatchString(convoy) {
1849 convoy = ""
1850 }
1851 return ""
1852 })
1853 }
1854 noise = quickrename(noise, userinfo.UserID)
1855 noise = hooterize(noise)
1856 honk.Noise = noise
1857 precipitate(honk)
1858 noise = honk.Noise
1859 recategorize(honk)
1860 translate(honk)
1861
1862 if rid != "" {
1863 xonk := getxonk(userinfo.UserID, rid)
1864 if xonk == nil {
1865 http.Error(w, "replyto disappeared", http.StatusNotFound)
1866 return nil
1867 }
1868 if xonk.Public {
1869 honk.Audience = append(honk.Audience, xonk.Audience...)
1870 }
1871 convoy = xonk.Convoy
1872 for i, a := range honk.Audience {
1873 if a == thewholeworld {
1874 honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
1875 break
1876 }
1877 }
1878 honk.RID = rid
1879 if xonk.Precis != "" && honk.Precis == "" {
1880 honk.Precis = xonk.Precis
1881 if !re_dangerous.MatchString(honk.Precis) {
1882 honk.Precis = "re: " + honk.Precis
1883 }
1884 }
1885 } else if updatexid == "" {
1886 honk.Audience = []string{thewholeworld}
1887 }
1888 if honk.Noise != "" && honk.Noise[0] == '@' {
1889 honk.Audience = append(grapevine(honk.Mentions), honk.Audience...)
1890 } else {
1891 honk.Audience = append(honk.Audience, grapevine(honk.Mentions)...)
1892 }
1893
1894 if convoy == "" {
1895 convoy = fmt.Sprintf("data:,%s-", masqName) + xfiltrate()
1896 }
1897 butnottooloud(honk.Audience)
1898 honk.Audience = oneofakind(honk.Audience)
1899 if len(honk.Audience) == 0 {
1900 ilog.Printf("honk to nowhere")
1901 http.Error(w, "honk to nowhere...", http.StatusNotFound)
1902 return nil
1903 }
1904 honk.Public = loudandproud(honk.Audience)
1905 honk.Convoy = convoy
1906 donkxid := strings.Join(r.Form["donkxid"], ",")
1907 if donkxid == "" {
1908 donks, err := submitdonk(w, r)
1909 if err != nil && err != http.ErrMissingFile {
1910 return nil
1911 }
1912 if len(donks) > 0 {
1913 honk.Donks = append(honk.Donks, donks...)
1914 var xids []string
1915 for _, d := range honk.Donks {
1916 xids = append(xids, fmt.Sprintf("%s:%d", d.XID, d.FileID))
1917 }
1918 donkxid = strings.Join(xids, ",")
1919 }
1920 } else {
1921 xids := strings.Split(donkxid, ",")
1922 for i, xid := range xids {
1923 if i > 16 {
1924 break
1925 }
1926 p := strings.Split(xid, ":")
1927 xid = p[0]
1928 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1929 var donk *Donk
1930 if len(p) > 1 {
1931 fileid, _ := strconv.ParseInt(p[1], 10, 0)
1932 donk = finddonkid(fileid, url)
1933 } else {
1934 donk = finddonk(url)
1935 }
1936 if donk != nil {
1937 honk.Donks = append(honk.Donks, donk)
1938 } else {
1939 ilog.Printf("can't find file: %s", xid)
1940 }
1941 }
1942 }
1943 memetize(honk)
1944 imaginate(honk)
1945
1946 placename := strings.TrimSpace(r.FormValue("placename"))
1947 placelat := strings.TrimSpace(r.FormValue("placelat"))
1948 placelong := strings.TrimSpace(r.FormValue("placelong"))
1949 placeurl := strings.TrimSpace(r.FormValue("placeurl"))
1950 if placename != "" || placelat != "" || placelong != "" || placeurl != "" {
1951 p := new(Place)
1952 p.Name = placename
1953 p.Latitude, _ = strconv.ParseFloat(placelat, 64)
1954 p.Longitude, _ = strconv.ParseFloat(placelong, 64)
1955 p.Url = placeurl
1956 honk.Place = p
1957 }
1958 timestart := strings.TrimSpace(r.FormValue("timestart"))
1959 if timestart != "" {
1960 t := new(Time)
1961 now := time.Now().Local()
1962 for _, layout := range []string{"2006-01-02 3:04pm", "2006-01-02 15:04", "3:04pm", "15:04"} {
1963 start, err := time.ParseInLocation(layout, timestart, now.Location())
1964 if err == nil {
1965 if start.Year() == 0 {
1966 start = time.Date(now.Year(), now.Month(), now.Day(), start.Hour(), start.Minute(), 0, 0, now.Location())
1967 }
1968 t.StartTime = start
1969 break
1970 }
1971 }
1972 timeend := r.FormValue("timeend")
1973 dur := parseDuration(timeend)
1974 if dur != 0 {
1975 t.Duration = Duration(dur)
1976 }
1977 if !t.StartTime.IsZero() {
1978 honk.What = "event"
1979 honk.Time = t
1980 }
1981 }
1982
1983 if honk.Public {
1984 honk.Whofore = 2
1985 } else {
1986 honk.Whofore = 3
1987 }
1988
1989 // back to markdown
1990 honk.Noise = noise
1991
1992 if r.FormValue("preview") == "preview" {
1993 honks := []*Honk{honk}
1994 reverbolate(userinfo.UserID, honks)
1995 templinfo := getInfo(r)
1996 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1997 templinfo["Honks"] = honks
1998 templinfo["MapLink"] = getmaplink(userinfo)
1999 templinfo["InReplyTo"] = r.FormValue("rid")
2000 templinfo["Noise"] = r.FormValue("noise")
2001 templinfo["SavedFile"] = donkxid
2002 if tm := honk.Time; tm != nil {
2003 templinfo["ShowTime"] = " "
2004 templinfo["StartTime"] = tm.StartTime.Format("2006-01-02 15:04")
2005 if tm.Duration != 0 {
2006 templinfo["Duration"] = tm.Duration
2007 }
2008 }
2009 templinfo["IsPreview"] = true
2010 templinfo["UpdateXID"] = updatexid
2011 templinfo["ServerMessage"] = "honk preview"
2012 err := readviews.Execute(w, "honkpage.html", templinfo)
2013 if err != nil {
2014 elog.Print(err)
2015 }
2016 return nil
2017 }
2018
2019 if updatexid != "" {
2020 updatehonk(honk)
2021 oldjonks.Clear(honk.XID)
2022 } else {
2023 err := savehonk(honk)
2024 if err != nil {
2025 elog.Printf("uh oh")
2026 return nil
2027 }
2028 }
2029
2030 // reload for consistency
2031 honk.Donks = nil
2032 donksforhonks([]*Honk{honk})
2033
2034 go honkworldwide(user, honk)
2035
2036 return honk
2037}
2038
2039func showhonkers(w http.ResponseWriter, r *http.Request) {
2040 userinfo := login.GetUserInfo(r)
2041 templinfo := getInfo(r)
2042 templinfo["Honkers"] = gethonkers(userinfo.UserID)
2043 templinfo["HonkerCSRF"] = login.GetCSRF("submithonker", r)
2044 err := readviews.Execute(w, "honkers.html", templinfo)
2045 if err != nil {
2046 elog.Print(err)
2047 }
2048}
2049
2050func showchatter(w http.ResponseWriter, r *http.Request) {
2051 u := login.GetUserInfo(r)
2052 chatnewnone(u.UserID)
2053 chatter := loadchatter(u.UserID)
2054 for _, chat := range chatter {
2055 for _, ch := range chat.Chonks {
2056 filterchonk(ch)
2057 }
2058 }
2059
2060 templinfo := getInfo(r)
2061 templinfo["Chatter"] = chatter
2062 templinfo["ChonkCSRF"] = login.GetCSRF("sendchonk", r)
2063 err := readviews.Execute(w, "chatter.html", templinfo)
2064 if err != nil {
2065 elog.Print(err)
2066 }
2067}
2068
2069func submitchonk(w http.ResponseWriter, r *http.Request) {
2070 u := login.GetUserInfo(r)
2071 user, _ := butwhatabout(u.Username)
2072 noise := r.FormValue("noise")
2073 target := r.FormValue("target")
2074 format := "markdown"
2075 dt := time.Now().UTC()
2076 xid := fmt.Sprintf("%s/%s/%s", user.URL, "chonk", xfiltrate())
2077
2078 if !strings.HasPrefix(target, "https://") {
2079 target = fullname(target, u.UserID)
2080 }
2081 if target == "" {
2082 http.Error(w, "who is that?", http.StatusInternalServerError)
2083 return
2084 }
2085 ch := Chonk{
2086 UserID: u.UserID,
2087 XID: xid,
2088 Who: user.URL,
2089 Target: target,
2090 Date: dt,
2091 Noise: noise,
2092 Format: format,
2093 }
2094 donks, err := submitdonk(w, r)
2095 if err != nil && err != http.ErrMissingFile {
2096 return
2097 }
2098 if len(donks) > 0 {
2099 ch.Donks = append(ch.Donks, donks...)
2100 }
2101
2102 translatechonk(&ch)
2103 savechonk(&ch)
2104 // reload for consistency
2105 ch.Donks = nil
2106 donksforchonks([]*Chonk{&ch})
2107 go sendchonk(user, &ch)
2108
2109 http.Redirect(w, r, "/chatter", http.StatusSeeOther)
2110}
2111
2112var combocache = cache.New(cache.Options{Filler: func(userid int64) ([]string, bool) {
2113 honkers := gethonkers(userid)
2114 var combos []string
2115 for _, h := range honkers {
2116 combos = append(combos, h.Combos...)
2117 }
2118 for i, c := range combos {
2119 if c == "-" {
2120 combos[i] = ""
2121 }
2122 }
2123 combos = oneofakind(combos)
2124 sort.Strings(combos)
2125 return combos, true
2126}, Invalidator: &honkerinvalidator})
2127
2128func showcombos(w http.ResponseWriter, r *http.Request) {
2129 userinfo := login.GetUserInfo(r)
2130 var combos []string
2131 combocache.Get(userinfo.UserID, &combos)
2132 templinfo := getInfo(r)
2133 err := readviews.Execute(w, "combos.html", templinfo)
2134 if err != nil {
2135 elog.Print(err)
2136 }
2137}
2138
2139func websubmithonker(w http.ResponseWriter, r *http.Request) {
2140 h := submithonker(w, r)
2141 if h == nil {
2142 return
2143 }
2144 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
2145}
2146
2147func submithonker(w http.ResponseWriter, r *http.Request) *Honker {
2148 u := login.GetUserInfo(r)
2149 user, _ := butwhatabout(u.Username)
2150 name := strings.TrimSpace(r.FormValue("name"))
2151 url := strings.TrimSpace(r.FormValue("url"))
2152 peep := r.FormValue("peep")
2153 combos := strings.TrimSpace(r.FormValue("combos"))
2154 combos = " " + combos + " "
2155 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
2156
2157 re_namecheck := regexp.MustCompile("^[\\pL[:digit:]_.-]+$")
2158 if name != "" && !re_namecheck.MatchString(name) {
2159 http.Error(w, "please use a plainer name", http.StatusInternalServerError)
2160 return nil
2161 }
2162
2163 var meta HonkerMeta
2164 meta.Notes = strings.TrimSpace(r.FormValue("notes"))
2165 mj, _ := jsonify(&meta)
2166
2167 defer honkerinvalidator.Clear(u.UserID)
2168
2169 // mostly dummy, fill in later...
2170 h := &Honker{
2171 ID: honkerid,
2172 }
2173
2174 if honkerid > 0 {
2175 if r.FormValue("delete") == "delete" {
2176 unfollowyou(user, honkerid, false)
2177 stmtDeleteHonker.Exec(honkerid)
2178 return h
2179 }
2180 if r.FormValue("unsub") == "unsub" {
2181 unfollowyou(user, honkerid, false)
2182 }
2183 if r.FormValue("sub") == "sub" {
2184 followyou(user, honkerid, false)
2185 }
2186 _, err := stmtUpdateHonker.Exec(name, combos, mj, honkerid, u.UserID)
2187 if err != nil {
2188 elog.Printf("update honker err: %s", err)
2189 return nil
2190 }
2191 return h
2192 }
2193
2194 if url == "" {
2195 http.Error(w, "subscribing to nothing?", http.StatusInternalServerError)
2196 return nil
2197 }
2198
2199 flavor := "presub"
2200 if peep == "peep" {
2201 flavor = "peep"
2202 }
2203
2204 var err error
2205 honkerid, err = savehonker(user, url, name, flavor, combos, mj)
2206 if err != nil {
2207 http.Error(w, "had some trouble with that: "+err.Error(), http.StatusInternalServerError)
2208 return nil
2209 }
2210 if flavor == "presub" {
2211 followyou(user, honkerid, false)
2212 }
2213 h.ID = honkerid
2214 return h
2215}
2216
2217func hfcspage(w http.ResponseWriter, r *http.Request) {
2218 userinfo := login.GetUserInfo(r)
2219
2220 filters := getfilters(userinfo.UserID, filtAny)
2221
2222 templinfo := getInfo(r)
2223 templinfo["Filters"] = filters
2224 templinfo["FilterCSRF"] = login.GetCSRF("filter", r)
2225 err := readviews.Execute(w, "hfcs.html", templinfo)
2226 if err != nil {
2227 elog.Print(err)
2228 }
2229}
2230
2231func savehfcs(w http.ResponseWriter, r *http.Request) {
2232 userinfo := login.GetUserInfo(r)
2233 itsok := r.FormValue("itsok")
2234 if itsok == "iforgiveyou" {
2235 hfcsid, _ := strconv.ParseInt(r.FormValue("hfcsid"), 10, 0)
2236 _, err := stmtDeleteFilter.Exec(userinfo.UserID, hfcsid)
2237 if err != nil {
2238 elog.Printf("error deleting filter: %s", err)
2239 }
2240 filtInvalidator.Clear(userinfo.UserID)
2241 http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
2242 return
2243 }
2244
2245 filt := new(Filter)
2246 filt.Name = strings.TrimSpace(r.FormValue("name"))
2247 filt.Date = time.Now().UTC()
2248 filt.Actor = strings.TrimSpace(r.FormValue("actor"))
2249 filt.IncludeAudience = r.FormValue("incaud") == "yes"
2250 filt.Text = strings.TrimSpace(r.FormValue("filttext"))
2251 filt.IsReply = r.FormValue("isreply") == "yes"
2252 filt.IsAnnounce = r.FormValue("isannounce") == "yes"
2253 filt.AnnounceOf = strings.TrimSpace(r.FormValue("announceof"))
2254 filt.Reject = r.FormValue("doreject") == "yes"
2255 filt.SkipMedia = r.FormValue("doskipmedia") == "yes"
2256 filt.Hide = r.FormValue("dohide") == "yes"
2257 filt.Collapse = r.FormValue("docollapse") == "yes"
2258 filt.Rewrite = strings.TrimSpace(r.FormValue("filtrewrite"))
2259 filt.Replace = strings.TrimSpace(r.FormValue("filtreplace"))
2260 if dur := parseDuration(r.FormValue("filtduration")); dur > 0 {
2261 filt.Expiration = time.Now().UTC().Add(dur)
2262 }
2263 filt.Notes = strings.TrimSpace(r.FormValue("filtnotes"))
2264
2265 if filt.Actor == "" && filt.Text == "" && !filt.IsAnnounce {
2266 ilog.Printf("blank filter")
2267 http.Error(w, "can't save a blank filter", http.StatusInternalServerError)
2268 return
2269 }
2270
2271 j, err := jsonify(filt)
2272 if err == nil {
2273 _, err = stmtSaveFilter.Exec(userinfo.UserID, j)
2274 }
2275 if err != nil {
2276 elog.Printf("error saving filter: %s", err)
2277 }
2278
2279 filtInvalidator.Clear(userinfo.UserID)
2280 http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
2281}
2282
2283func accountpage(w http.ResponseWriter, r *http.Request) {
2284 u := login.GetUserInfo(r)
2285 user, _ := butwhatabout(u.Username)
2286 templinfo := getInfo(r)
2287 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
2288 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
2289 templinfo["User"] = user
2290 about := user.About
2291 if ava := user.Options.Avatar; ava != "" {
2292 about += "\n\navatar: " + ava[strings.LastIndexByte(ava, '/')+1:]
2293 }
2294 if ban := user.Options.Banner; ban != "" {
2295 about += "\n\nbanner: " + ban[strings.LastIndexByte(ban, '/')+1:]
2296 }
2297 templinfo["WhatAbout"] = about
2298 err := readviews.Execute(w, "account.html", templinfo)
2299 if err != nil {
2300 elog.Print(err)
2301 }
2302}
2303
2304func dochpass(w http.ResponseWriter, r *http.Request) {
2305 err := login.ChangePassword(w, r)
2306 if err != nil {
2307 elog.Printf("error changing password: %s", err)
2308 }
2309 http.Redirect(w, r, "/account", http.StatusSeeOther)
2310}
2311
2312var oldfingers = cache.New(cache.Options{Filler: func(orig string) ([]byte, bool) {
2313 if strings.HasPrefix(orig, "acct:") {
2314 orig = orig[5:]
2315 }
2316 name := orig
2317 idx := strings.LastIndexByte(name, '/')
2318 if idx != -1 {
2319 name = name[idx+1:]
2320 if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
2321 ilog.Printf("foreign request rejected")
2322 name = ""
2323 }
2324 } else {
2325 idx = strings.IndexByte(name, '@')
2326 if idx != -1 {
2327 name = name[:idx]
2328 if !(name+"@"+serverName == orig || name+"@"+masqName == orig) {
2329 ilog.Printf("foreign request rejected")
2330 name = ""
2331 }
2332 }
2333 }
2334 user, err := butwhatabout(name)
2335 if err != nil {
2336 return nil, false
2337 }
2338
2339 j := junk.New()
2340 pretty := fmt.Sprintf("https://%s/@%s", serverName, name)
2341 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, masqName)
2342 j["aliases"] = []string{pretty, user.URL}
2343 l := junk.New()
2344 l["rel"] = "self"
2345 l["type"] = `application/activity+json`
2346 l["href"] = user.URL
2347 j["links"] = []junk.Junk{l}
2348 l2 := junk.New()
2349 l2["rel"] = "http://webfinger.net/rel/profile-page"
2350 l2["type"] = "text/html"
2351 l2["href"] = pretty
2352 j["links"] = []junk.Junk{l, l2}
2353
2354 return j.ToBytes(), true
2355}})
2356
2357func fingerlicker(w http.ResponseWriter, r *http.Request) {
2358 orig := r.FormValue("resource")
2359
2360 dlog.Printf("finger lick: %s", orig)
2361
2362 var j []byte
2363 ok := oldfingers.Get(orig, &j)
2364 if ok {
2365 w.Header().Set("Content-Type", "application/jrd+json")
2366 w.Write(j)
2367 } else {
2368 http.NotFound(w, r)
2369 }
2370}
2371
2372func knowninformation(w http.ResponseWriter, r *http.Request) {
2373 j := junk.New()
2374 l := junk.New()
2375
2376 l["rel"] = `http://nodeinfo.diaspora.software/ns/schema/2.0`
2377 l["href"] = fmt.Sprintf("https://%s/nodeinfo/2.0", serverName)
2378 j["links"] = []junk.Junk{l}
2379
2380 w.Header().Set("Content-Type", "application/json")
2381 j.Write(w)
2382}
2383
2384func actualinformation(w http.ResponseWriter, r *http.Request) {
2385 j := junk.New()
2386
2387 soft := junk.New()
2388 soft["name"] = "honk"
2389 soft["version"] = softwareVersion
2390
2391 services := junk.New()
2392 services["inbound"] = []string{}
2393 services["outbound"] = []string{"rss2.0"}
2394
2395 users := junk.New()
2396 users["total"] = getusercount()
2397 users["activeHalfyear"] = getactiveusercount(6)
2398 users["activeMonth"] = getactiveusercount(1)
2399
2400 usage := junk.New()
2401 usage["users"] = users
2402 usage["localPosts"] = getlocalhonkcount()
2403
2404 j["version"] = "2.0"
2405 j["protocols"] = []string{"activitypub"}
2406 j["software"] = soft
2407 j["services"] = services
2408 j["openRegistrations"] = false
2409 j["usage"] = usage
2410
2411 w.Header().Set("Content-Type", "application/json")
2412 j.Write(w)
2413}
2414
2415func somedays() string {
2416 secs := 432000 + notrand.Int63n(432000)
2417 return fmt.Sprintf("%d", secs)
2418}
2419
2420func isurl(s string) bool {
2421 u, err := url.Parse(s)
2422 return err == nil && u.Scheme != "" && u.Host != ""
2423}
2424
2425func avatateautogen(r *http.Request) []byte {
2426 n := r.FormValue("a")
2427 return genAvatar(n)
2428}
2429
2430func avatate(w http.ResponseWriter, r *http.Request) {
2431 if develMode {
2432 loadAvatarColors()
2433 }
2434 var a []byte
2435 n := r.FormValue("a")
2436
2437 if isurl(n) {
2438 uinfo := login.GetUserInfo(r)
2439 if uinfo != nil {
2440 j, err := GetJunkFast(uinfo.UserID, n)
2441 if err != nil {
2442 dlog.Println("avatating: getting junk:", err)
2443 a = avatateautogen(r)
2444 }
2445 pfpurl, _ := j.GetString("icon", "url")
2446 res, err := http.Get(pfpurl)
2447 if err != nil {
2448 dlog.Println("avatating: getting pfp url:", err)
2449 a = avatateautogen(r)
2450 } else {
2451 defer res.Body.Close()
2452
2453 pfpbytes, err := io.ReadAll(res.Body)
2454 if err != nil {
2455 dlog.Println("avatating: bruh shits clapped:", err)
2456 a = avatateautogen(r)
2457 }
2458 a = pfpbytes
2459 }
2460 } else {
2461 a = avatateautogen(r)
2462 }
2463 } else {
2464 a = avatateautogen(r)
2465 }
2466
2467 if !develMode {
2468 w.Header().Set("Cache-Control", "max-age="+somedays())
2469 }
2470
2471 w.Write(a)
2472}
2473
2474func serveviewasset(w http.ResponseWriter, r *http.Request) {
2475 serveasset(w, r, viewDir)
2476}
2477func servedataasset(w http.ResponseWriter, r *http.Request) {
2478 if r.URL.Path == "/favicon.ico" {
2479 r.URL.Path = "/icon.png"
2480 }
2481 serveasset(w, r, dataDir)
2482}
2483
2484func serveasset(w http.ResponseWriter, r *http.Request, basedir string) {
2485 if !develMode {
2486 w.Header().Set("Cache-Control", "max-age=7776000")
2487 }
2488 http.ServeFile(w, r, basedir+"/views"+r.URL.Path)
2489}
2490func servehelp(w http.ResponseWriter, r *http.Request) {
2491 name := mux.Vars(r)["name"]
2492 if !develMode {
2493 w.Header().Set("Cache-Control", "max-age=3600")
2494 }
2495 http.ServeFile(w, r, viewDir+"/docs/"+name)
2496}
2497func servehtml(w http.ResponseWriter, r *http.Request) {
2498 u := login.GetUserInfo(r)
2499 templinfo := getInfo(r)
2500 templinfo["AboutMsg"] = aboutMsg
2501 templinfo["LoginMsg"] = loginMsg
2502 templinfo["HonkVersion"] = softwareVersion
2503 if r.URL.Path == "/about" {
2504 templinfo["Sensors"] = getSensors()
2505 }
2506 if u == nil && !develMode {
2507 w.Header().Set("Cache-Control", "max-age=60")
2508 }
2509 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
2510 if err != nil {
2511 elog.Print(err)
2512 }
2513}
2514func serveemu(w http.ResponseWriter, r *http.Request) {
2515 emu := mux.Vars(r)["emu"]
2516
2517 w.Header().Set("Cache-Control", "max-age="+somedays())
2518 http.ServeFile(w, r, dataDir+"/emus/"+emu)
2519}
2520func servememe(w http.ResponseWriter, r *http.Request) {
2521 meme := mux.Vars(r)["meme"]
2522
2523 w.Header().Set("Cache-Control", "max-age="+somedays())
2524 http.ServeFile(w, r, dataDir+"/memes/"+meme)
2525}
2526
2527func servefile(w http.ResponseWriter, r *http.Request) {
2528 xid := mux.Vars(r)["xid"]
2529 var media string
2530 var data []byte
2531 row := stmtGetFileData.QueryRow(xid)
2532 err := row.Scan(&media, &data)
2533 if err != nil {
2534 elog.Printf("error loading file: %s", err)
2535 http.NotFound(w, r)
2536 return
2537 }
2538 w.Header().Set("Content-Type", media)
2539 w.Header().Set("X-Content-Type-Options", "nosniff")
2540 w.Header().Set("Cache-Control", "max-age="+somedays())
2541 w.Write(data)
2542}
2543
2544func nomoroboto(w http.ResponseWriter, r *http.Request) {
2545 io.WriteString(w, "User-agent: *\n")
2546 io.WriteString(w, "Disallow: /a\n")
2547 io.WriteString(w, "Disallow: /d/\n")
2548 io.WriteString(w, "Disallow: /meme/\n")
2549 io.WriteString(w, "Disallow: /o\n")
2550 io.WriteString(w, "Disallow: /o/\n")
2551 io.WriteString(w, "Disallow: /help/\n")
2552 for _, u := range allusers() {
2553 fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
2554 }
2555}
2556
2557type Hydration struct {
2558 Tophid int64
2559 Srvmsg template.HTML
2560 Honks string
2561 MeCount int64
2562 ChatCount int64
2563}
2564
2565func webhydra(w http.ResponseWriter, r *http.Request) {
2566 u := login.GetUserInfo(r)
2567 userid := u.UserID
2568 templinfo := getInfo(r)
2569 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
2570 page := r.FormValue("page")
2571
2572 wanted, _ := strconv.ParseInt(r.FormValue("tophid"), 10, 0)
2573
2574 var hydra Hydration
2575
2576 var honks []*Honk
2577 switch page {
2578 case "atme":
2579 honks = gethonksforme(userid, wanted)
2580 honks = osmosis(honks, userid, false)
2581 menewnone(userid)
2582 hydra.Srvmsg = "at me!"
2583 case "longago":
2584 honks = gethonksfromlongago(userid, wanted)
2585 honks = osmosis(honks, userid, false)
2586 hydra.Srvmsg = "from long ago"
2587 case "home":
2588 honks = gethonksforuser(userid, wanted)
2589 honks = osmosis(honks, userid, true)
2590 hydra.Srvmsg = serverMsg
2591 case "first":
2592 honks = gethonksforuserfirstclass(userid, wanted)
2593 honks = osmosis(honks, userid, true)
2594 hydra.Srvmsg = "first class only"
2595 case "saved":
2596 honks = getsavedhonks(userid, wanted)
2597 templinfo["PageName"] = "saved"
2598 hydra.Srvmsg = "saved honks"
2599 case "combo":
2600 c := r.FormValue("c")
2601 honks = gethonksbycombo(userid, c, wanted)
2602 honks = osmosis(honks, userid, false)
2603 hydra.Srvmsg = templates.Sprintf("honks by combo: %s", c)
2604 case "convoy":
2605 c := r.FormValue("c")
2606 honks = gethonksbyconvoy(userid, c, wanted)
2607 honks = osmosis(honks, userid, false)
2608 honks = threadsort(honks)
2609 reversehonks(honks)
2610 hydra.Srvmsg = templates.Sprintf("honks in convoy: %s", c)
2611 case "honker":
2612 xid := r.FormValue("xid")
2613 honks = gethonksbyxonker(userid, xid, wanted)
2614 miniform := templates.Sprintf(`<form action="/submithonker" method="POST">
2615 <input type="hidden" name="CSRF" value="%s">
2616 <input type="hidden" name="url" value="%s">
2617 <button tabindex=1 name="add honker" value="add honker">add honker</button>
2618 </form>`, login.GetCSRF("submithonker", r), xid)
2619 msg := templates.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>%s`, xid, xid, miniform)
2620 hydra.Srvmsg = msg
2621 case "user":
2622 uname := r.FormValue("uname")
2623 honks = gethonksbyuser(uname, u != nil && u.Username == uname, wanted)
2624 hydra.Srvmsg = templates.Sprintf("honks by user: %s", uname)
2625 default:
2626 http.NotFound(w, r)
2627 }
2628
2629 if len(honks) > 0 {
2630 hydra.Tophid = honks[0].ID
2631 } else {
2632 hydra.Tophid = wanted
2633 }
2634 reverbolate(userid, honks)
2635
2636 user, _ := butwhatabout(u.Username)
2637
2638 var buf strings.Builder
2639 templinfo["Honks"] = honks
2640 templinfo["MapLink"] = getmaplink(u)
2641 templinfo["User"], _ = butwhatabout(u.Username)
2642 err := readviews.Execute(&buf, "honkfrags.html", templinfo)
2643 if err != nil {
2644 elog.Printf("frag error: %s", err)
2645 return
2646 }
2647 hydra.Honks = buf.String()
2648 hydra.MeCount = user.Options.MeCount
2649 hydra.ChatCount = user.Options.ChatCount
2650 w.Header().Set("Content-Type", "application/json")
2651 j, _ := jsonify(&hydra)
2652 io.WriteString(w, j)
2653}
2654
2655var honkline = make(chan bool)
2656
2657func honkhonkline() {
2658 for {
2659 select {
2660 case honkline <- true:
2661 default:
2662 return
2663 }
2664 }
2665}
2666
2667func apihandler(w http.ResponseWriter, r *http.Request) {
2668 u := login.GetUserInfo(r)
2669 userid := u.UserID
2670 action := r.FormValue("action")
2671 wait, _ := strconv.ParseInt(r.FormValue("wait"), 10, 0)
2672 dlog.Printf("api request '%s' on behalf of %s", action, u.Username)
2673 switch action {
2674 case "honk":
2675 h := submithonk(w, r)
2676 if h == nil {
2677 return
2678 }
2679
2680 fmt.Fprintf(w, "%s", h.XID)
2681 case "donk":
2682 donks, err := submitdonk(w, r)
2683 if err != nil {
2684 http.Error(w, err.Error(), http.StatusBadRequest)
2685 return
2686 }
2687 if len(donks) == 0 {
2688 http.Error(w, "missing donk", http.StatusBadRequest)
2689 return
2690 }
2691 d := donks[0]
2692 donkxid := fmt.Sprintf("%s:%d", d.XID, d.FileID)
2693 w.Write([]byte(donkxid))
2694 case "zonkit":
2695 zonkit(w, r)
2696 case "gethonks":
2697 var honks []*Honk
2698 wanted, _ := strconv.ParseInt(r.FormValue("after"), 10, 0)
2699 page := r.FormValue("page")
2700 var waitchan <-chan time.Time
2701 requery:
2702 switch page {
2703 case "atme":
2704 honks = gethonksforme(userid, wanted)
2705 honks = osmosis(honks, userid, false)
2706 menewnone(userid)
2707 case "longago":
2708 honks = gethonksfromlongago(userid, wanted)
2709 honks = osmosis(honks, userid, false)
2710 case "home":
2711 honks = gethonksforuser(userid, wanted)
2712 honks = osmosis(honks, userid, true)
2713 case "myhonks":
2714 honks = gethonksbyuser(u.Username, true, wanted)
2715 honks = osmosis(honks, userid, true)
2716 default:
2717 http.Error(w, "unknown page", http.StatusNotFound)
2718 return
2719 }
2720 if len(honks) == 0 && wait > 0 {
2721 if waitchan == nil {
2722 waitchan = time.After(time.Duration(wait) * time.Second)
2723 }
2724 select {
2725 case <-honkline:
2726 goto requery
2727 case <-waitchan:
2728 }
2729 }
2730 reverbolate(userid, honks)
2731 j := junk.New()
2732 j["honks"] = honks
2733 j.Write(w)
2734 case "sendactivity":
2735 user, _ := butwhatabout(u.Username)
2736 public := r.FormValue("public") == "1"
2737 rcpts := boxuprcpts(user, r.Form["rcpt"], public)
2738 msg := []byte(r.FormValue("msg"))
2739 for rcpt := range rcpts {
2740 go deliverate(userid, rcpt, msg)
2741 }
2742 case "gethonkers":
2743 j := junk.New()
2744 j["honkers"] = gethonkers(u.UserID)
2745 j.Write(w)
2746 case "savehonker":
2747 h := submithonker(w, r)
2748 if h == nil {
2749 return
2750 }
2751 fmt.Fprintf(w, "%d", h.ID)
2752 default:
2753 http.Error(w, "unknown action", http.StatusNotFound)
2754 return
2755 }
2756}
2757
2758func fiveoh(w http.ResponseWriter, r *http.Request) {
2759 if !develMode {
2760 return
2761 }
2762 fd, err := os.OpenFile("violations.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
2763 if err != nil {
2764 elog.Printf("error opening violations! %s", err)
2765 return
2766 }
2767 defer fd.Close()
2768 io.Copy(fd, r.Body)
2769 fd.WriteString("\n")
2770}
2771
2772var endoftheworld = make(chan bool)
2773var readyalready = make(chan bool)
2774var workinprogress = 0
2775
2776func enditall() {
2777 sig := make(chan os.Signal, 1)
2778 signal.Notify(sig, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
2779 <-sig
2780 ilog.Printf("stopping...")
2781 for i := 0; i < workinprogress; i++ {
2782 endoftheworld <- true
2783 }
2784 ilog.Printf("waiting...")
2785 for i := 0; i < workinprogress; i++ {
2786 <-readyalready
2787 }
2788 ilog.Printf("apocalypse")
2789 os.Exit(0)
2790}
2791
2792var preservehooks []func()
2793
2794func bgmonitor() {
2795 for {
2796 when := time.Now().Add(-3 * 24 * time.Hour).UTC().Format(dbtimeformat)
2797 _, err := stmtDeleteOldXonkers.Exec("pubkey", when)
2798 if err != nil {
2799 elog.Printf("error deleting old xonkers: %s", err)
2800 }
2801 zaggies.Flush()
2802 time.Sleep(50 * time.Minute)
2803 }
2804}
2805
2806func emuinit() {
2807 var emunames []string
2808 dir, err := os.Open(dataDir + "/emus")
2809 if err == nil {
2810 emunames, _ = dir.Readdirnames(0)
2811 dir.Close()
2812 }
2813 for _, e := range emunames {
2814 if len(e) <= 4 {
2815 continue
2816 }
2817 ext := e[len(e)-4:]
2818 emu := Emu{
2819 ID: fmt.Sprintf("/emu/%s", e),
2820 Name: e[:len(e)-4],
2821 Type: "image/" + ext[1:],
2822 }
2823 allemus = append(allemus, emu)
2824 }
2825 sort.Slice(allemus, func(i, j int) bool {
2826 return allemus[i].Name < allemus[j].Name
2827 })
2828}
2829
2830func redirectPretty(w http.ResponseWriter, r *http.Request) {
2831 last := path.Base(r.URL.Path)
2832 name := mux.Vars(r)["name"]
2833 aturl := "/@" + name
2834
2835 if last == name {
2836 last = ""
2837 }
2838 http.Redirect(w, r, path.Join(aturl, last), http.StatusMovedPermanently)
2839}
2840
2841func serve() {
2842 db := opendatabase()
2843 login.Init(login.InitArgs{Db: db, Logger: ilog, Insecure: develMode, SameSiteStrict: !develMode})
2844
2845 listener, err := openListener()
2846 if err != nil {
2847 elog.Fatal(err)
2848 }
2849 runBackendServer()
2850 go enditall()
2851 go redeliverator()
2852 go tracker()
2853 go bgmonitor()
2854 go qotd()
2855 loadLingo()
2856 emuinit()
2857
2858 readviews = templates.Load(develMode,
2859 viewDir+"/views/honkpage.html",
2860 viewDir+"/views/honkfrags.html",
2861 viewDir+"/views/honkers.html",
2862 viewDir+"/views/chatter.html",
2863 viewDir+"/views/hfcs.html",
2864 viewDir+"/views/combos.html",
2865 viewDir+"/views/honkform.html",
2866 viewDir+"/views/honk.html",
2867 viewDir+"/views/account.html",
2868 viewDir+"/views/about.html",
2869 viewDir+"/views/funzone.html",
2870 viewDir+"/views/login.html",
2871 viewDir+"/views/xzone.html",
2872 viewDir+"/views/msg.html",
2873 viewDir+"/views/header.html",
2874 viewDir+"/views/onts.html",
2875 viewDir+"/views/emus.html",
2876 viewDir+"/views/oauthlogin.html",
2877 viewDir+"/views/honkpage.js",
2878 )
2879 if !develMode {
2880 assets := []string{
2881 viewDir + "/views/style.css",
2882 dataDir + "/views/local.css",
2883 viewDir + "/views/honkpage.js",
2884 viewDir + "/views/misc.js",
2885 dataDir + "/views/local.js",
2886 viewDir + "/views/manifest.webmanifest",
2887 viewDir + "/views/sw.js",
2888 }
2889 for _, s := range assets {
2890 savedassetparams[s] = getassetparam(s)
2891 }
2892 loadAvatarColors()
2893 }
2894
2895 for _, h := range preservehooks {
2896 h()
2897 }
2898
2899 mux := mux.NewRouter()
2900 mux.Use(login.Checker)
2901
2902 mux.Handle("/api", login.TokenRequired(http.HandlerFunc(apihandler)))
2903
2904 posters := mux.Methods("POST").Subrouter()
2905 getters := mux.Methods("GET").Subrouter()
2906
2907 getters.HandleFunc("/", homepage)
2908 getters.HandleFunc("/home", homepage)
2909 getters.HandleFunc("/front", homepage)
2910 getters.HandleFunc("/events", homepage)
2911 getters.HandleFunc("/robots.txt", nomoroboto)
2912 getters.HandleFunc("/rss", showrss)
2913 getters.HandleFunc("/@{name:[\\pL[:digit:]]+}", showuser)
2914 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}", redirectPretty)
2915 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/"+honkSep+"/{xid:[\\pL[:digit:]]+}", showonehonk)
2916 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/rss", showrss)
2917 posters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/inbox", inbox)
2918 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/outbox", outbox)
2919 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/followers", emptiness)
2920 getters.HandleFunc("/"+userSep+"/{name:[\\pL[:digit:]]+}/following", emptiness)
2921 getters.HandleFunc("/a", avatate)
2922 getters.HandleFunc("/o", thelistingoftheontologies)
2923 getters.HandleFunc("/o/{name:.+}", showontology)
2924 getters.HandleFunc("/d/{xid:[\\pL[:digit:].]+}", servefile)
2925 getters.HandleFunc("/emu/{emu:[^.]*[^/]+}", serveemu)
2926 getters.HandleFunc("/meme/{meme:[^.]*[^/]+}", servememe)
2927 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
2928 getters.HandleFunc("/.well-known/nodeinfo", knowninformation)
2929 getters.HandleFunc("/nodeinfo/2.0", actualinformation)
2930
2931 getters.HandleFunc("/flag/{code:.+}", showflag)
2932
2933 getters.HandleFunc("/server", serveractor)
2934 posters.HandleFunc("/server/inbox", serverinbox)
2935 posters.HandleFunc("/inbox", serverinbox)
2936
2937 posters.HandleFunc("/csp-violation", fiveoh)
2938
2939 getters.HandleFunc("/style.css", serveviewasset)
2940 getters.HandleFunc("/sw.js", serveviewasset)
2941 getters.HandleFunc("/honkpage.js", serveviewasset)
2942 getters.HandleFunc("/misc.js", serveviewasset)
2943 getters.HandleFunc("/local.css", servedataasset)
2944 getters.HandleFunc("/local.js", servedataasset)
2945 getters.HandleFunc("/icon.png", servedataasset)
2946 getters.HandleFunc("/favicon.ico", servedataasset)
2947 getters.HandleFunc("/manifest.webmanifest", serveviewasset)
2948
2949 getters.HandleFunc("/about", servehtml)
2950 getters.HandleFunc("/login", servehtml)
2951 posters.HandleFunc("/dologin", login.LoginFunc)
2952 getters.HandleFunc("/logout", login.LogoutFunc)
2953 getters.HandleFunc("/help/{name:[\\pL[:digit:]_.-]+}", servehelp)
2954
2955 loggedin := mux.NewRoute().Subrouter()
2956 loggedin.Use(login.Required)
2957 loggedin.HandleFunc("/first", homepage)
2958 loggedin.HandleFunc("/chatter", showchatter)
2959 loggedin.Handle("/sendchonk", login.CSRFWrap("sendchonk", http.HandlerFunc(submitchonk)))
2960 loggedin.HandleFunc("/saved", homepage)
2961 loggedin.HandleFunc("/account", accountpage)
2962 loggedin.HandleFunc("/funzone", showfunzone)
2963 loggedin.HandleFunc("/chpass", dochpass)
2964 loggedin.HandleFunc("/atme", homepage)
2965 loggedin.HandleFunc("/longago", homepage)
2966 loggedin.HandleFunc("/hfcs", hfcspage)
2967 loggedin.HandleFunc("/xzone", xzone)
2968 loggedin.HandleFunc("/newhonk", newhonkpage)
2969 loggedin.HandleFunc("/edit", edithonkpage)
2970 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(websubmithonk)))
2971 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(submitbonk)))
2972 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
2973 loggedin.Handle("/savehfcs", login.CSRFWrap("filter", http.HandlerFunc(savehfcs)))
2974 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
2975 loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
2976 loggedin.HandleFunc("/honkers", showhonkers)
2977 loggedin.HandleFunc("/h/{name:[\\pL[:digit:]_.-]+}", showhonker)
2978 loggedin.HandleFunc("/h", showhonker)
2979 loggedin.HandleFunc("/c/{name:[\\pL[:digit:]_.-]+}", showcombo)
2980 loggedin.HandleFunc("/c", showcombos)
2981 loggedin.HandleFunc("/t", showconvoy)
2982 loggedin.HandleFunc("/q", showsearch)
2983 loggedin.HandleFunc("/hydra", webhydra)
2984 loggedin.HandleFunc("/emus", showemus)
2985 loggedin.Handle("/submithonker", login.CSRFWrap("submithonker", http.HandlerFunc(websubmithonker)))
2986
2987 // mastoshit
2988 mastopost := mux.Methods("POST").Subrouter()
2989 mastoget := mux.Methods("GET").Subrouter()
2990
2991 mastoget.HandleFunc("/oauth/authorize", showoauthlogin)
2992 mastopost.HandleFunc("/oauth/authorize", oauthorize)
2993 mastopost.HandleFunc("/oauth/token", oauthtoken)
2994 mastoget.HandleFunc("/api/v1/instance", instance)
2995 mastopost.HandleFunc("/api/v1/apps", apiapps)
2996 mastoget.HandleFunc("/api/v1/apps/verify_credentials", injectapp(verifycreds))
2997
2998 loggedmux := handlers.LoggingHandler(os.Stdout, mux)
2999 err = http.Serve(listener, loggedmux)
3000 if err != nil {
3001 elog.Fatal(err)
3002 }
3003}
3004
3005// Verifies that authtoken is valid and injects the associated
3006// MastoApp in the request context
3007func injectapp(h http.HandlerFunc) http.HandlerFunc {
3008 return func(w http.ResponseWriter, r *http.Request) {
3009 authHeader := r.Header.Get("Authorization")
3010 split := strings.Split(authHeader, "Bearer")
3011 if len(split) != 2 {
3012 elog.Println("masto: bad api token format or lack thereof")
3013 w.WriteHeader(http.StatusBadRequest)
3014 return
3015 }
3016
3017 token := split[1]
3018 app := getMastoAppFromAuthToken(token)
3019 if app == nil {
3020 elog.Println("masto: invalid auth token")
3021 w.WriteHeader(http.StatusUnauthorized)
3022 return
3023 }
3024
3025 ctx := context.WithValue(r.Context(), "app", app)
3026 r = r.WithContext(ctx)
3027 h(w, r)
3028 }
3029}