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