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