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 "fmt"
21 "html"
22 "html/template"
23 "io"
24 "log"
25 notrand "math/rand"
26 "net/http"
27 "net/url"
28 "os"
29 "sort"
30 "strconv"
31 "strings"
32 "time"
33
34 "github.com/gorilla/mux"
35 "humungus.tedunangst.com/r/webs/css"
36 "humungus.tedunangst.com/r/webs/htfilter"
37 "humungus.tedunangst.com/r/webs/httpsig"
38 "humungus.tedunangst.com/r/webs/image"
39 "humungus.tedunangst.com/r/webs/junk"
40 "humungus.tedunangst.com/r/webs/login"
41 "humungus.tedunangst.com/r/webs/rss"
42 "humungus.tedunangst.com/r/webs/templates"
43)
44
45var readviews *templates.Template
46
47var userSep = "u"
48var honkSep = "h"
49
50func getuserstyle(u *login.UserInfo) template.CSS {
51 if u == nil {
52 return ""
53 }
54 user, _ := butwhatabout(u.Username)
55 if user.SkinnyCSS {
56 return "main { max-width: 700px; }"
57 }
58 return ""
59}
60
61func getInfo(r *http.Request) map[string]interface{} {
62 u := login.GetUserInfo(r)
63 templinfo := make(map[string]interface{})
64 templinfo["StyleParam"] = getassetparam("views/style.css")
65 templinfo["LocalStyleParam"] = getassetparam("views/local.css")
66 templinfo["JSParam"] = getassetparam("views/honkpage.js")
67 templinfo["UserStyle"] = getuserstyle(u)
68 templinfo["ServerName"] = serverName
69 templinfo["IconName"] = iconName
70 templinfo["UserInfo"] = u
71 templinfo["UserSep"] = userSep
72 if u != nil {
73 var combos []string
74 combocache.Get(u.UserID, &combos)
75 templinfo["Combos"] = combos
76 }
77 return templinfo
78}
79
80func homepage(w http.ResponseWriter, r *http.Request) {
81 templinfo := getInfo(r)
82 u := login.GetUserInfo(r)
83 var honks []*Honk
84 var userid int64 = -1
85 if r.URL.Path == "/front" || u == nil {
86 honks = getpublichonks()
87 } else {
88 userid = u.UserID
89 if r.URL.Path == "/atme" {
90 templinfo["PageName"] = "atme"
91 honks = gethonksforme(userid)
92 } else if r.URL.Path == "/first" {
93 templinfo["PageName"] = "first"
94 honks = gethonksforuser(userid)
95 honks = osmosis(honks, userid)
96 } else {
97 templinfo["PageName"] = "home"
98 honks = gethonksforuser(userid)
99 honks = osmosis(honks, userid)
100 }
101 if len(honks) > 0 {
102 templinfo["TopXID"] = honks[0].XID
103 }
104 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
105 }
106
107 templinfo["ShowRSS"] = true
108 templinfo["ServerMessage"] = serverMsg
109 honkpage(w, u, honks, templinfo)
110}
111
112func showfunzone(w http.ResponseWriter, r *http.Request) {
113 var emunames, memenames []string
114 dir, err := os.Open("emus")
115 if err == nil {
116 emunames, _ = dir.Readdirnames(0)
117 dir.Close()
118 }
119 for i, e := range emunames {
120 if len(e) > 4 {
121 emunames[i] = e[:len(e)-4]
122 }
123 }
124 dir, err = os.Open("memes")
125 if err == nil {
126 memenames, _ = dir.Readdirnames(0)
127 dir.Close()
128 }
129 templinfo := getInfo(r)
130 templinfo["Emus"] = emunames
131 templinfo["Memes"] = memenames
132 err = readviews.Execute(w, "funzone.html", templinfo)
133 if err != nil {
134 log.Print(err)
135 }
136}
137
138func showrss(w http.ResponseWriter, r *http.Request) {
139 name := mux.Vars(r)["name"]
140
141 var honks []*Honk
142 if name != "" {
143 honks = gethonksbyuser(name, false)
144 } else {
145 honks = getpublichonks()
146 }
147 if len(honks) > 20 {
148 honks = honks[0:20]
149 }
150 reverbolate(-1, honks)
151
152 home := fmt.Sprintf("https://%s/", serverName)
153 base := home
154 if name != "" {
155 home += "u/" + name
156 name += " "
157 }
158 feed := rss.Feed{
159 Title: name + "honk",
160 Link: home,
161 Description: name + "honk rss",
162 Image: &rss.Image{
163 URL: base + "icon.png",
164 Title: name + "honk rss",
165 Link: home,
166 },
167 }
168 var modtime time.Time
169 for _, honk := range honks {
170 if !firstclass(honk) {
171 continue
172 }
173 desc := string(honk.HTML)
174 if t := honk.Time; t != nil {
175 desc += fmt.Sprintf(`<p>Time: %s`, t.StartTime.Local().Format("03:04PM EDT Mon Jan 02"))
176 if t.Duration != 0 {
177 desc += fmt.Sprintf(`<br>Duration: %s`, t.Duration)
178 }
179 }
180 if p := honk.Place; p != nil {
181 desc += fmt.Sprintf(`<p>Location: <a href="%s">%s</a> %f %f`,
182 html.EscapeString(p.Url), html.EscapeString(p.Name), p.Latitude, p.Longitude)
183 }
184 for _, d := range honk.Donks {
185 desc += fmt.Sprintf(`<p><a href="%s">Attachment: %s</a>`,
186 d.URL, html.EscapeString(d.Name))
187 }
188
189 feed.Items = append(feed.Items, &rss.Item{
190 Title: fmt.Sprintf("%s %s %s", honk.Username, honk.What, honk.XID),
191 Description: rss.CData{desc},
192 Link: honk.URL,
193 PubDate: honk.Date.Format(time.RFC1123),
194 Guid: &rss.Guid{IsPermaLink: true, Value: honk.URL},
195 })
196 if honk.Date.After(modtime) {
197 modtime = honk.Date
198 }
199 }
200 w.Header().Set("Cache-Control", "max-age=300")
201 w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
202
203 err := feed.Write(w)
204 if err != nil {
205 log.Printf("error writing rss: %s", err)
206 }
207}
208
209func crappola(j junk.Junk) bool {
210 t, _ := j.GetString("type")
211 a, _ := j.GetString("actor")
212 o, _ := j.GetString("object")
213 if t == "Delete" && a == o {
214 log.Printf("crappola from %s", a)
215 return true
216 }
217 return false
218}
219
220func ping(user *WhatAbout, who string) {
221 box, err := getboxes(who)
222 if err != nil {
223 log.Printf("no inbox for ping: %s", err)
224 return
225 }
226 j := junk.New()
227 j["@context"] = itiswhatitis
228 j["type"] = "Ping"
229 j["id"] = user.URL + "/ping/" + xfiltrate()
230 j["actor"] = user.URL
231 j["to"] = who
232 keyname, key := ziggy(user.Name)
233 err = PostJunk(keyname, key, box.In, j)
234 if err != nil {
235 log.Printf("can't send ping: %s", err)
236 return
237 }
238 log.Printf("sent ping to %s: %s", who, j["id"])
239}
240
241func pong(user *WhatAbout, who string, obj string) {
242 box, err := getboxes(who)
243 if err != nil {
244 log.Printf("no inbox for pong %s : %s", who, err)
245 return
246 }
247 j := junk.New()
248 j["@context"] = itiswhatitis
249 j["type"] = "Pong"
250 j["id"] = user.URL + "/pong/" + xfiltrate()
251 j["actor"] = user.URL
252 j["to"] = who
253 j["object"] = obj
254 keyname, key := ziggy(user.Name)
255 err = PostJunk(keyname, key, box.In, j)
256 if err != nil {
257 log.Printf("can't send pong: %s", err)
258 return
259 }
260}
261
262func inbox(w http.ResponseWriter, r *http.Request) {
263 name := mux.Vars(r)["name"]
264 user, err := butwhatabout(name)
265 if err != nil {
266 http.NotFound(w, r)
267 return
268 }
269 if stealthmode(user.ID, r) {
270 http.NotFound(w, r)
271 return
272 }
273 var buf bytes.Buffer
274 io.Copy(&buf, r.Body)
275 payload := buf.Bytes()
276 j, err := junk.Read(bytes.NewReader(payload))
277 if err != nil {
278 log.Printf("bad payload: %s", err)
279 io.WriteString(os.Stdout, "bad payload\n")
280 os.Stdout.Write(payload)
281 io.WriteString(os.Stdout, "\n")
282 return
283 }
284 if crappola(j) {
285 return
286 }
287 keyname, err := httpsig.VerifyRequest(r, payload, zaggy)
288 if err != nil {
289 log.Printf("inbox message failed signature: %s", err)
290 if keyname != "" {
291 keyname, err = makeitworksomehowwithoutregardforkeycontinuity(keyname, r, payload)
292 if err != nil {
293 log.Printf("still failed: %s", err)
294 }
295 }
296 if err != nil {
297 return
298 }
299 }
300 what, _ := j.GetString("type")
301 if what == "Like" {
302 return
303 }
304 who, _ := j.GetString("actor")
305 origin := keymatch(keyname, who)
306 if origin == "" {
307 log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
308 return
309 }
310 if rejectactor(user.ID, who) {
311 log.Printf("ignoring thumb sucker %s", who)
312 return
313 }
314 switch what {
315 case "Ping":
316 obj, _ := j.GetString("id")
317 log.Printf("ping from %s: %s", who, obj)
318 pong(user, who, obj)
319 case "Pong":
320 obj, _ := j.GetString("object")
321 log.Printf("pong from %s: %s", who, obj)
322 case "Follow":
323 obj, _ := j.GetString("object")
324 if obj == user.URL {
325 log.Printf("updating honker follow: %s", who)
326 stmtSaveDub.Exec(user.ID, who, who, "dub")
327 go rubadubdub(user, j)
328 } else {
329 log.Printf("can't follow %s", obj)
330 }
331 case "Accept":
332 log.Printf("updating honker accept: %s", who)
333 _, err = stmtUpdateFlavor.Exec("sub", user.ID, who, "presub")
334 if err != nil {
335 log.Printf("error updating honker: %s", err)
336 return
337 }
338 case "Update":
339 obj, ok := j.GetMap("object")
340 if ok {
341 what, _ := obj.GetString("type")
342 switch what {
343 case "Person":
344 return
345 case "Question":
346 return
347 case "Note":
348 go xonksaver(user, j, origin)
349 return
350 }
351 }
352 log.Printf("unknown Update activity")
353 fd, _ := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
354 j.Write(fd)
355 io.WriteString(fd, "\n")
356 fd.Close()
357
358 case "Undo":
359 obj, ok := j.GetMap("object")
360 if !ok {
361 log.Printf("unknown undo no object")
362 } else {
363 what, _ := obj.GetString("type")
364 switch what {
365 case "Follow":
366 log.Printf("updating honker undo: %s", who)
367 _, err = stmtUpdateFlavor.Exec("undub", user.ID, who, "dub")
368 if err != nil {
369 log.Printf("error updating honker: %s", err)
370 return
371 }
372 case "Announce":
373 xid, _ := obj.GetString("object")
374 log.Printf("undo announce: %s", xid)
375 case "Like":
376 default:
377 log.Printf("unknown undo: %s", what)
378 }
379 }
380 default:
381 go xonksaver(user, j, origin)
382 }
383}
384
385func ximport(w http.ResponseWriter, r *http.Request) {
386 u := login.GetUserInfo(r)
387 xid := r.FormValue("xid")
388 xonk := getxonk(u.UserID, xid)
389 if xonk == nil {
390 p, _ := investigate(xid)
391 if p != nil {
392 xid = p.XID
393 }
394 j, err := GetJunk(xid)
395 if err != nil {
396 http.Error(w, "error getting external object", http.StatusInternalServerError)
397 log.Printf("error getting external object: %s", err)
398 return
399 }
400 log.Printf("importing %s", xid)
401 user, _ := butwhatabout(u.Username)
402
403 what, _ := j.GetString("type")
404 if isactor(what) {
405 outbox, _ := j.GetString("outbox")
406 gimmexonks(user, outbox)
407 http.Redirect(w, r, "/h?xid="+url.QueryEscape(xid), http.StatusSeeOther)
408 return
409 }
410 xonk = xonksaver(user, j, originate(xid))
411 }
412 convoy := ""
413 if xonk != nil {
414 convoy = xonk.Convoy
415 }
416 http.Redirect(w, r, "/t?c="+url.QueryEscape(convoy), http.StatusSeeOther)
417}
418
419func xzone(w http.ResponseWriter, r *http.Request) {
420 u := login.GetUserInfo(r)
421 rows, err := stmtRecentHonkers.Query(u.UserID, u.UserID)
422 if err != nil {
423 log.Printf("query err: %s", err)
424 return
425 }
426 defer rows.Close()
427 var honkers []Honker
428 for rows.Next() {
429 var xid string
430 rows.Scan(&xid)
431 honkers = append(honkers, Honker{XID: xid})
432 }
433 rows.Close()
434 for i, _ := range honkers {
435 _, honkers[i].Handle = handles(honkers[i].XID)
436 }
437 templinfo := getInfo(r)
438 templinfo["XCSRF"] = login.GetCSRF("ximport", r)
439 templinfo["Honkers"] = honkers
440 err = readviews.Execute(w, "xzone.html", templinfo)
441 if err != nil {
442 log.Print(err)
443 }
444}
445
446func outbox(w http.ResponseWriter, r *http.Request) {
447 name := mux.Vars(r)["name"]
448 user, err := butwhatabout(name)
449 if err != nil {
450 http.NotFound(w, r)
451 return
452 }
453 if stealthmode(user.ID, r) {
454 http.NotFound(w, r)
455 return
456 }
457
458 honks := gethonksbyuser(name, false)
459 if len(honks) > 20 {
460 honks = honks[0:20]
461 }
462
463 var jonks []junk.Junk
464 for _, h := range honks {
465 j, _ := jonkjonk(user, h)
466 jonks = append(jonks, j)
467 }
468
469 j := junk.New()
470 j["@context"] = itiswhatitis
471 j["id"] = user.URL + "/outbox"
472 j["type"] = "OrderedCollection"
473 j["totalItems"] = len(jonks)
474 j["orderedItems"] = jonks
475
476 w.Header().Set("Content-Type", theonetruename)
477 j.Write(w)
478}
479
480func emptiness(w http.ResponseWriter, r *http.Request) {
481 name := mux.Vars(r)["name"]
482 user, err := butwhatabout(name)
483 if err != nil {
484 http.NotFound(w, r)
485 return
486 }
487 if stealthmode(user.ID, r) {
488 http.NotFound(w, r)
489 return
490 }
491 colname := "/followers"
492 if strings.HasSuffix(r.URL.Path, "/following") {
493 colname = "/following"
494 }
495 j := junk.New()
496 j["@context"] = itiswhatitis
497 j["id"] = user.URL + colname
498 j["type"] = "OrderedCollection"
499 j["totalItems"] = 0
500 j["orderedItems"] = []junk.Junk{}
501
502 w.Header().Set("Content-Type", theonetruename)
503 j.Write(w)
504}
505
506func showuser(w http.ResponseWriter, r *http.Request) {
507 name := mux.Vars(r)["name"]
508 user, err := butwhatabout(name)
509 if err != nil {
510 log.Printf("user not found %s: %s", name, err)
511 http.NotFound(w, r)
512 return
513 }
514 if stealthmode(user.ID, r) {
515 http.NotFound(w, r)
516 return
517 }
518 if friendorfoe(r.Header.Get("Accept")) {
519 j := asjonker(user)
520 w.Header().Set("Content-Type", theonetruename)
521 j.Write(w)
522 return
523 }
524 u := login.GetUserInfo(r)
525 honks := gethonksbyuser(name, u != nil && u.Username == name)
526 templinfo := getInfo(r)
527 filt := htfilter.New()
528 templinfo["Name"] = user.Name
529 whatabout := user.About
530 whatabout = markitzero(user.About)
531 templinfo["WhatAbout"], _ = filt.String(whatabout)
532 templinfo["ServerMessage"] = ""
533 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
534 honkpage(w, u, honks, templinfo)
535}
536
537func showhonker(w http.ResponseWriter, r *http.Request) {
538 u := login.GetUserInfo(r)
539 name := mux.Vars(r)["name"]
540 var honks []*Honk
541 if name == "" {
542 name = r.FormValue("xid")
543 honks = gethonksbyxonker(u.UserID, name)
544 } else {
545 honks = gethonksbyhonker(u.UserID, name)
546 }
547 name = html.EscapeString(name)
548 msg := fmt.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>`, name, name)
549 templinfo := getInfo(r)
550 templinfo["PageName"] = "honker"
551 templinfo["ServerMessage"] = template.HTML(msg)
552 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
553 honkpage(w, u, honks, templinfo)
554}
555
556func showcombo(w http.ResponseWriter, r *http.Request) {
557 name := mux.Vars(r)["name"]
558 u := login.GetUserInfo(r)
559 honks := gethonksbycombo(u.UserID, name)
560 honks = osmosis(honks, u.UserID)
561 templinfo := getInfo(r)
562 templinfo["PageName"] = "combo"
563 templinfo["PageArg"] = "name"
564 templinfo["ServerMessage"] = "honks by combo: " + name
565 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
566 if len(honks) > 0 {
567 templinfo["TopXID"] = honks[0].XID
568 }
569 honkpage(w, u, honks, templinfo)
570}
571func showconvoy(w http.ResponseWriter, r *http.Request) {
572 c := r.FormValue("c")
573 u := login.GetUserInfo(r)
574 honks := gethonksbyconvoy(u.UserID, c)
575 templinfo := getInfo(r)
576 templinfo["ServerMessage"] = "honks in convoy: " + c
577 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
578 honkpage(w, u, honks, templinfo)
579}
580func showsearch(w http.ResponseWriter, r *http.Request) {
581 q := r.FormValue("q")
582 u := login.GetUserInfo(r)
583 honks := gethonksbysearch(u.UserID, q)
584 templinfo := getInfo(r)
585 templinfo["ServerMessage"] = "honks for search: " + q
586 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
587 honkpage(w, u, honks, templinfo)
588}
589func showontology(w http.ResponseWriter, r *http.Request) {
590 name := mux.Vars(r)["name"]
591 u := login.GetUserInfo(r)
592 var userid int64 = -1
593 if u != nil {
594 userid = u.UserID
595 }
596 honks := gethonksbyontology(userid, "#"+name)
597 templinfo := getInfo(r)
598 templinfo["ServerMessage"] = "honks by ontology: " + name
599 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
600 honkpage(w, u, honks, templinfo)
601}
602
603func thelistingoftheontologies(w http.ResponseWriter, r *http.Request) {
604 u := login.GetUserInfo(r)
605 var userid int64 = -1
606 if u != nil {
607 userid = u.UserID
608 }
609 rows, err := stmtSelectOnts.Query(userid)
610 if err != nil {
611 log.Printf("selection error: %s", err)
612 return
613 }
614 defer rows.Close()
615 var onts [][]string
616 for rows.Next() {
617 var o string
618 err := rows.Scan(&o)
619 if err != nil {
620 log.Printf("error scanning ont: %s", err)
621 continue
622 }
623 onts = append(onts, []string{o, o[1:]})
624 }
625 if u == nil {
626 w.Header().Set("Cache-Control", "max-age=300")
627 }
628 templinfo := getInfo(r)
629 templinfo["Onts"] = onts
630 err = readviews.Execute(w, "onts.html", templinfo)
631 if err != nil {
632 log.Print(err)
633 }
634}
635
636func showhonk(w http.ResponseWriter, r *http.Request) {
637 name := mux.Vars(r)["name"]
638 user, err := butwhatabout(name)
639 if err != nil {
640 http.NotFound(w, r)
641 return
642 }
643 if stealthmode(user.ID, r) {
644 http.NotFound(w, r)
645 return
646 }
647
648 xid := fmt.Sprintf("https://%s%s", serverName, r.URL.Path)
649 honk := getxonk(user.ID, xid)
650 if honk == nil {
651 http.NotFound(w, r)
652 return
653 }
654 u := login.GetUserInfo(r)
655 if u != nil && u.UserID != user.ID {
656 u = nil
657 }
658 if !honk.Public {
659 if u == nil {
660 http.NotFound(w, r)
661 return
662
663 }
664 templinfo := getInfo(r)
665 templinfo["ServerMessage"] = "one honk maybe more"
666 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
667 honkpage(w, u, []*Honk{honk}, templinfo)
668 return
669 }
670 rawhonks := gethonksbyconvoy(honk.UserID, honk.Convoy)
671 if friendorfoe(r.Header.Get("Accept")) {
672 for _, h := range rawhonks {
673 if h.RID == honk.XID && h.Public && (h.Whofore == 2 || h.IsAcked()) {
674 honk.Replies = append(honk.Replies, h)
675 }
676 }
677 donksforhonks([]*Honk{honk})
678 _, j := jonkjonk(user, honk)
679 j["@context"] = itiswhatitis
680 w.Header().Set("Content-Type", theonetruename)
681 j.Write(w)
682 return
683 }
684 var honks []*Honk
685 for _, h := range rawhonks {
686 if h.Public && (h.Whofore == 2 || h.IsAcked()) {
687 honks = append(honks, h)
688 }
689 }
690
691 templinfo := getInfo(r)
692 templinfo["ServerMessage"] = "one honk maybe more"
693 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
694 honkpage(w, u, honks, templinfo)
695}
696
697func honkpage(w http.ResponseWriter, u *login.UserInfo, honks []*Honk, templinfo map[string]interface{}) {
698 var userid int64 = -1
699 if u != nil {
700 userid = u.UserID
701 }
702 if u == nil {
703 w.Header().Set("Cache-Control", "max-age=60")
704 }
705 reverbolate(userid, honks)
706 templinfo["Honks"] = honks
707 err := readviews.Execute(w, "honkpage.html", templinfo)
708 if err != nil {
709 log.Print(err)
710 }
711}
712
713func saveuser(w http.ResponseWriter, r *http.Request) {
714 whatabout := r.FormValue("whatabout")
715 u := login.GetUserInfo(r)
716 db := opendatabase()
717 options := ""
718 if r.FormValue("skinny") == "skinny" {
719 options += " skinny "
720 }
721 _, err := db.Exec("update users set about = ?, options = ? where username = ?", whatabout, options, u.Username)
722 if err != nil {
723 log.Printf("error bouting what: %s", err)
724 }
725
726 http.Redirect(w, r, "/account", http.StatusSeeOther)
727}
728
729func submitbonk(w http.ResponseWriter, r *http.Request) {
730 xid := r.FormValue("xid")
731 userinfo := login.GetUserInfo(r)
732 user, _ := butwhatabout(userinfo.Username)
733
734 log.Printf("bonking %s", xid)
735
736 xonk := getxonk(userinfo.UserID, xid)
737 if xonk == nil {
738 return
739 }
740 if !xonk.Public {
741 return
742 }
743 donksforhonks([]*Honk{xonk})
744
745 _, err := stmtUpdateFlags.Exec(flagIsBonked, xonk.ID)
746 if err != nil {
747 log.Printf("error acking bonk: %s", err)
748 }
749
750 oonker := xonk.Oonker
751 if oonker == "" {
752 oonker = xonk.Honker
753 }
754 dt := time.Now().UTC()
755 bonk := Honk{
756 UserID: userinfo.UserID,
757 Username: userinfo.Username,
758 What: "bonk",
759 Honker: user.URL,
760 Oonker: oonker,
761 XID: xonk.XID,
762 RID: xonk.RID,
763 Noise: xonk.Noise,
764 Precis: xonk.Precis,
765 URL: xonk.URL,
766 Date: dt,
767 Donks: xonk.Donks,
768 Whofore: 2,
769 Convoy: xonk.Convoy,
770 Audience: []string{thewholeworld, oonker},
771 Public: true,
772 }
773
774 bonk.Format = "html"
775
776 err = savehonk(&bonk)
777 if err != nil {
778 log.Printf("uh oh")
779 return
780 }
781
782 go honkworldwide(user, &bonk)
783}
784
785func sendzonkofsorts(xonk *Honk, user *WhatAbout, what string) {
786 zonk := Honk{
787 What: what,
788 XID: xonk.XID,
789 Date: time.Now().UTC(),
790 Audience: oneofakind(xonk.Audience),
791 }
792 zonk.Public = !keepitquiet(zonk.Audience)
793
794 log.Printf("announcing %sed honk: %s", what, xonk.XID)
795 go honkworldwide(user, &zonk)
796}
797
798func zonkit(w http.ResponseWriter, r *http.Request) {
799 wherefore := r.FormValue("wherefore")
800 what := r.FormValue("what")
801 userinfo := login.GetUserInfo(r)
802 user, _ := butwhatabout(userinfo.Username)
803
804 if wherefore == "ack" {
805 xonk := getxonk(userinfo.UserID, what)
806 if xonk != nil {
807 _, err := stmtUpdateFlags.Exec(flagIsAcked, xonk.ID)
808 if err != nil {
809 log.Printf("error acking: %s", err)
810 }
811 sendzonkofsorts(xonk, user, "ack")
812 }
813 return
814 }
815
816 if wherefore == "deack" {
817 xonk := getxonk(userinfo.UserID, what)
818 if xonk != nil {
819 _, err := stmtClearFlags.Exec(flagIsAcked, xonk.ID)
820 if err != nil {
821 log.Printf("error deacking: %s", err)
822 }
823 sendzonkofsorts(xonk, user, "deack")
824 }
825 return
826 }
827
828 if wherefore == "unbonk" {
829 xonk := getbonk(userinfo.UserID, what)
830 if xonk != nil {
831 deletehonk(xonk.ID)
832 xonk = getxonk(userinfo.UserID, what)
833 _, err := stmtClearFlags.Exec(flagIsBonked, xonk.ID)
834 if err != nil {
835 log.Printf("error unbonking: %s", err)
836 }
837 sendzonkofsorts(xonk, user, "unbonk")
838 }
839 return
840 }
841
842 log.Printf("zonking %s %s", wherefore, what)
843 if wherefore == "zonk" {
844 xonk := getxonk(userinfo.UserID, what)
845 if xonk != nil {
846 deletehonk(xonk.ID)
847 if xonk.Whofore == 2 || xonk.Whofore == 3 {
848 sendzonkofsorts(xonk, user, "zonk")
849 }
850 }
851 }
852 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
853 if err != nil {
854 log.Printf("error saving zonker: %s", err)
855 return
856 }
857}
858
859func edithonkpage(w http.ResponseWriter, r *http.Request) {
860 u := login.GetUserInfo(r)
861 user, _ := butwhatabout(u.Username)
862 xid := r.FormValue("xid")
863 honk := getxonk(u.UserID, xid)
864 if honk == nil || honk.Honker != user.URL || honk.What != "honk" {
865 log.Printf("no edit")
866 return
867 }
868
869 noise := honk.Noise
870 if honk.Precis != "" {
871 noise = honk.Precis + "\n\n" + noise
872 }
873
874 honks := []*Honk{honk}
875 donksforhonks(honks)
876 reverbolate(u.UserID, honks)
877 templinfo := getInfo(r)
878 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
879 templinfo["Honks"] = honks
880 templinfo["Noise"] = noise
881 templinfo["ServerMessage"] = "honk edit"
882 templinfo["UpdateXID"] = honk.XID
883 if len(honk.Donks) > 0 {
884 templinfo["SavedFile"] = honk.Donks[0].XID
885 }
886 err := readviews.Execute(w, "honkpage.html", templinfo)
887 if err != nil {
888 log.Print(err)
889 }
890}
891
892// what a hot mess this function is
893func submithonk(w http.ResponseWriter, r *http.Request) {
894 rid := r.FormValue("rid")
895 noise := r.FormValue("noise")
896
897 userinfo := login.GetUserInfo(r)
898 user, _ := butwhatabout(userinfo.Username)
899
900 dt := time.Now().UTC()
901 updatexid := r.FormValue("updatexid")
902 var honk *Honk
903 if updatexid != "" {
904 honk = getxonk(userinfo.UserID, updatexid)
905 if honk == nil || honk.Honker != user.URL || honk.What != "honk" {
906 log.Printf("not saving edit")
907 return
908 }
909 honk.Date = dt
910 honk.What = "update"
911 honk.Format = "markdown"
912 } else {
913 xid := fmt.Sprintf("%s/%s/%s", user.URL, honkSep, xfiltrate())
914 what := "honk"
915 if rid != "" {
916 what = "tonk"
917 }
918 honk = &Honk{
919 UserID: userinfo.UserID,
920 Username: userinfo.Username,
921 What: what,
922 Honker: user.URL,
923 XID: xid,
924 Date: dt,
925 Format: "markdown",
926 }
927 }
928
929 noise = hooterize(noise)
930 honk.Noise = noise
931 translate(honk)
932
933 var convoy string
934 if rid != "" {
935 xonk := getxonk(userinfo.UserID, rid)
936 if xonk != nil {
937 if xonk.Public {
938 honk.Audience = append(honk.Audience, xonk.Audience...)
939 }
940 convoy = xonk.Convoy
941 } else {
942 xonkaud, c := whosthere(rid)
943 honk.Audience = append(honk.Audience, xonkaud...)
944 convoy = c
945 }
946 for i, a := range honk.Audience {
947 if a == thewholeworld {
948 honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
949 break
950 }
951 }
952 honk.RID = rid
953 } else {
954 honk.Audience = []string{thewholeworld}
955 }
956 if honk.Noise != "" && honk.Noise[0] == '@' {
957 honk.Audience = append(grapevine(honk.Noise), honk.Audience...)
958 } else {
959 honk.Audience = append(honk.Audience, grapevine(honk.Noise)...)
960 }
961
962 if convoy == "" {
963 convoy = "data:,electrichonkytonk-" + xfiltrate()
964 }
965 butnottooloud(honk.Audience)
966 honk.Audience = oneofakind(honk.Audience)
967 if len(honk.Audience) == 0 {
968 log.Printf("honk to nowhere")
969 http.Error(w, "honk to nowhere...", http.StatusNotFound)
970 return
971 }
972 honk.Public = !keepitquiet(honk.Audience)
973 honk.Convoy = convoy
974
975 donkxid := r.FormValue("donkxid")
976 if donkxid == "" {
977 file, filehdr, err := r.FormFile("donk")
978 if err == nil {
979 var buf bytes.Buffer
980 io.Copy(&buf, file)
981 file.Close()
982 data := buf.Bytes()
983 xid := xfiltrate()
984 var media, name string
985 img, err := image.Vacuum(&buf, image.Params{MaxWidth: 2048, MaxHeight: 2048})
986 if err == nil {
987 data = img.Data
988 format := img.Format
989 media = "image/" + format
990 if format == "jpeg" {
991 format = "jpg"
992 }
993 name = xid + "." + format
994 xid = name
995 } else {
996 maxsize := 100000
997 if len(data) > maxsize {
998 log.Printf("bad image: %s too much text: %d", err, len(data))
999 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1000 return
1001 }
1002 for i := 0; i < len(data); i++ {
1003 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
1004 log.Printf("bad image: %s not text: %d", err, data[i])
1005 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1006 return
1007 }
1008 }
1009 media = "text/plain"
1010 name = filehdr.Filename
1011 if name == "" {
1012 name = xid + ".txt"
1013 }
1014 xid += ".txt"
1015 }
1016 desc := r.FormValue("donkdesc")
1017 if desc == "" {
1018 desc = name
1019 }
1020 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1021 fileid, err := savefile(xid, name, desc, url, media, true, data)
1022 if err != nil {
1023 log.Printf("unable to save image: %s", err)
1024 return
1025 }
1026 var d Donk
1027 d.FileID = fileid
1028 honk.Donks = append(honk.Donks, &d)
1029 donkxid = d.XID
1030 }
1031 } else {
1032 xid := donkxid
1033 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1034 donk := finddonk(url)
1035 if donk != nil {
1036 honk.Donks = append(honk.Donks, donk)
1037 } else {
1038 log.Printf("can't find file: %s", xid)
1039 }
1040 }
1041 herd := herdofemus(honk.Noise)
1042 for _, e := range herd {
1043 donk := savedonk(e.ID, e.Name, e.Name, "image/png", true)
1044 if donk != nil {
1045 donk.Name = e.Name
1046 honk.Donks = append(honk.Donks, donk)
1047 }
1048 }
1049 memetize(honk)
1050
1051 placename := r.FormValue("placename")
1052 placelat := r.FormValue("placelat")
1053 placelong := r.FormValue("placelong")
1054 if placename != "" || placelat != "" || placelong != "" {
1055 p := new(Place)
1056 p.Name = placename
1057 p.Latitude, _ = strconv.ParseFloat(placelat, 64)
1058 p.Longitude, _ = strconv.ParseFloat(placelong, 64)
1059 p.Url = r.FormValue("placeurl")
1060 honk.Place = p
1061 }
1062 timestart := r.FormValue("timestart")
1063 if timestart != "" {
1064 t := new(Time)
1065 now := time.Now().Local()
1066 for _, layout := range []string{"2006-01-02 3:04pm", "2006-01-02 15:04", "3:04pm", "15:04"} {
1067 start, err := time.ParseInLocation(layout, timestart, now.Location())
1068 if err == nil {
1069 if start.Year() == 0 {
1070 start = time.Date(now.Year(), now.Month(), now.Day(), start.Hour(), start.Minute(), 0, 0, now.Location())
1071 }
1072 t.StartTime = start
1073 break
1074 }
1075 }
1076 timeend := r.FormValue("timeend")
1077 dur, err := time.ParseDuration(timeend)
1078 if err == nil {
1079 t.Duration = Duration(dur)
1080 }
1081 if !t.StartTime.IsZero() {
1082 honk.What = "event"
1083 honk.Time = t
1084 }
1085 }
1086
1087 if honk.Public {
1088 honk.Whofore = 2
1089 } else {
1090 honk.Whofore = 3
1091 }
1092
1093 // back to markdown
1094 honk.Noise = noise
1095
1096 if r.FormValue("preview") == "preview" {
1097 honks := []*Honk{honk}
1098 reverbolate(userinfo.UserID, honks)
1099 templinfo := getInfo(r)
1100 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1101 templinfo["Honks"] = honks
1102 templinfo["InReplyTo"] = r.FormValue("rid")
1103 templinfo["Noise"] = r.FormValue("noise")
1104 templinfo["SavedFile"] = donkxid
1105 templinfo["ServerMessage"] = "honk preview"
1106 err := readviews.Execute(w, "honkpage.html", templinfo)
1107 if err != nil {
1108 log.Print(err)
1109 }
1110 return
1111 }
1112
1113 if updatexid != "" {
1114 updatehonk(honk)
1115 } else {
1116 err := savehonk(honk)
1117 if err != nil {
1118 log.Printf("uh oh")
1119 return
1120 }
1121 }
1122
1123 // reload for consistency
1124 honk.Donks = nil
1125 donksforhonks([]*Honk{honk})
1126
1127 go honkworldwide(user, honk)
1128
1129 http.Redirect(w, r, honk.XID, http.StatusSeeOther)
1130}
1131
1132func showhonkers(w http.ResponseWriter, r *http.Request) {
1133 userinfo := login.GetUserInfo(r)
1134 templinfo := getInfo(r)
1135 templinfo["Honkers"] = gethonkers(userinfo.UserID)
1136 templinfo["HonkerCSRF"] = login.GetCSRF("submithonker", r)
1137 err := readviews.Execute(w, "honkers.html", templinfo)
1138 if err != nil {
1139 log.Print(err)
1140 }
1141}
1142
1143var combocache = cacheNew(func(userid int64) ([]string, bool) {
1144 honkers := gethonkers(userid)
1145 var combos []string
1146 for _, h := range honkers {
1147 combos = append(combos, h.Combos...)
1148 }
1149 for i, c := range combos {
1150 if c == "-" {
1151 combos[i] = ""
1152 }
1153 }
1154 combos = oneofakind(combos)
1155 sort.Strings(combos)
1156 return combos, true
1157})
1158
1159func showcombos(w http.ResponseWriter, r *http.Request) {
1160 userinfo := login.GetUserInfo(r)
1161 var combos []string
1162 combocache.Get(userinfo.UserID, &combos)
1163 templinfo := getInfo(r)
1164 err := readviews.Execute(w, "combos.html", templinfo)
1165 if err != nil {
1166 log.Print(err)
1167 }
1168}
1169
1170func submithonker(w http.ResponseWriter, r *http.Request) {
1171 u := login.GetUserInfo(r)
1172 name := r.FormValue("name")
1173 url := r.FormValue("url")
1174 peep := r.FormValue("peep")
1175 combos := r.FormValue("combos")
1176 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1177
1178 defer combocache.Clear(u.UserID)
1179
1180 if honkerid > 0 {
1181 goodbye := r.FormValue("goodbye")
1182 if goodbye == "F" {
1183 db := opendatabase()
1184 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1185 honkerid, u.UserID)
1186 var xid string
1187 err := row.Scan(&xid)
1188 if err != nil {
1189 log.Printf("can't get honker xid: %s", err)
1190 return
1191 }
1192 log.Printf("unsubscribing from %s", xid)
1193 user, _ := butwhatabout(u.Username)
1194 go itakeitallback(user, xid)
1195 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1196 if err != nil {
1197 log.Printf("error updating honker: %s", err)
1198 return
1199 }
1200
1201 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1202 return
1203 }
1204 combos = " " + strings.TrimSpace(combos) + " "
1205 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1206 if err != nil {
1207 log.Printf("update honker err: %s", err)
1208 return
1209 }
1210 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1211 return
1212 }
1213
1214 flavor := "presub"
1215 if peep == "peep" {
1216 flavor = "peep"
1217 }
1218 p, err := investigate(url)
1219 if err != nil {
1220 http.Error(w, "error investigating: "+err.Error(), http.StatusInternalServerError)
1221 log.Printf("failed to investigate honker")
1222 return
1223 }
1224 url = p.XID
1225 if name == "" {
1226 name = p.Handle
1227 }
1228 _, err = stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1229 if err != nil {
1230 log.Print(err)
1231 return
1232 }
1233 if flavor == "presub" {
1234 user, _ := butwhatabout(u.Username)
1235 go subsub(user, url)
1236 }
1237 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1238}
1239
1240func zonkzone(w http.ResponseWriter, r *http.Request) {
1241 userinfo := login.GetUserInfo(r)
1242
1243 var filters afiltermap
1244 filtcache.Get(userinfo.UserID, &filters)
1245
1246 templinfo := getInfo(r)
1247 templinfo["Filters"] = filters
1248 templinfo["ZonkCSRF"] = login.GetCSRF("zonkzonk", r)
1249 err := readviews.Execute(w, "zonkers.html", templinfo)
1250 if err != nil {
1251 log.Print(err)
1252 }
1253}
1254
1255// todo
1256func zonkzonk(w http.ResponseWriter, r *http.Request) {
1257 userinfo := login.GetUserInfo(r)
1258 itsok := r.FormValue("itsok")
1259 if itsok == "iforgiveyou" {
1260 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1261 db := opendatabase()
1262 db.Exec("delete from hfcs where userid = ? and hfcsid = ?",
1263 userinfo.UserID, zonkerid)
1264 filtcache.Clear(userinfo.UserID)
1265 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1266 return
1267 }
1268
1269 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1270}
1271
1272func accountpage(w http.ResponseWriter, r *http.Request) {
1273 u := login.GetUserInfo(r)
1274 user, _ := butwhatabout(u.Username)
1275 templinfo := getInfo(r)
1276 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1277 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1278 templinfo["User"] = user
1279 err := readviews.Execute(w, "account.html", templinfo)
1280 if err != nil {
1281 log.Print(err)
1282 }
1283}
1284
1285func dochpass(w http.ResponseWriter, r *http.Request) {
1286 err := login.ChangePassword(w, r)
1287 if err != nil {
1288 log.Printf("error changing password: %s", err)
1289 }
1290 http.Redirect(w, r, "/account", http.StatusSeeOther)
1291}
1292
1293func fingerlicker(w http.ResponseWriter, r *http.Request) {
1294 orig := r.FormValue("resource")
1295
1296 log.Printf("finger lick: %s", orig)
1297
1298 if strings.HasPrefix(orig, "acct:") {
1299 orig = orig[5:]
1300 }
1301
1302 name := orig
1303 idx := strings.LastIndexByte(name, '/')
1304 if idx != -1 {
1305 name = name[idx+1:]
1306 if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
1307 log.Printf("foreign request rejected")
1308 name = ""
1309 }
1310 } else {
1311 idx = strings.IndexByte(name, '@')
1312 if idx != -1 {
1313 name = name[:idx]
1314 if name+"@"+serverName != orig {
1315 log.Printf("foreign request rejected")
1316 name = ""
1317 }
1318 }
1319 }
1320 user, err := butwhatabout(name)
1321 if err != nil {
1322 http.NotFound(w, r)
1323 return
1324 }
1325 if stealthmode(user.ID, r) {
1326 http.NotFound(w, r)
1327 return
1328 }
1329
1330 j := junk.New()
1331 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1332 j["aliases"] = []string{user.URL}
1333 var links []junk.Junk
1334 l := junk.New()
1335 l["rel"] = "self"
1336 l["type"] = `application/activity+json`
1337 l["href"] = user.URL
1338 links = append(links, l)
1339 j["links"] = links
1340
1341 w.Header().Set("Cache-Control", "max-age=3600")
1342 w.Header().Set("Content-Type", "application/jrd+json")
1343 j.Write(w)
1344}
1345
1346func somedays() string {
1347 secs := 432000 + notrand.Int63n(432000)
1348 return fmt.Sprintf("%d", secs)
1349}
1350
1351func avatate(w http.ResponseWriter, r *http.Request) {
1352 n := r.FormValue("a")
1353 a := avatar(n)
1354 w.Header().Set("Cache-Control", "max-age="+somedays())
1355 w.Write(a)
1356}
1357
1358func servecss(w http.ResponseWriter, r *http.Request) {
1359 fd, err := os.Open("views" + r.URL.Path)
1360 if err != nil {
1361 http.NotFound(w, r)
1362 return
1363 }
1364 w.Header().Set("Cache-Control", "max-age=0")
1365 w.Header().Set("Content-Type", "text/css; charset=utf-8")
1366 err = css.Filter(fd, w)
1367 if err != nil {
1368 log.Printf("error filtering css: %s", err)
1369 }
1370}
1371func serveasset(w http.ResponseWriter, r *http.Request) {
1372 w.Header().Set("Cache-Control", "max-age=7776000")
1373 http.ServeFile(w, r, "views"+r.URL.Path)
1374}
1375func servehtml(w http.ResponseWriter, r *http.Request) {
1376 templinfo := getInfo(r)
1377 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1378 if err != nil {
1379 log.Print(err)
1380 }
1381}
1382func serveemu(w http.ResponseWriter, r *http.Request) {
1383 xid := mux.Vars(r)["xid"]
1384 w.Header().Set("Cache-Control", "max-age="+somedays())
1385 http.ServeFile(w, r, "emus/"+xid)
1386}
1387func servememe(w http.ResponseWriter, r *http.Request) {
1388 xid := mux.Vars(r)["xid"]
1389 w.Header().Set("Cache-Control", "max-age="+somedays())
1390 http.ServeFile(w, r, "memes/"+xid)
1391}
1392
1393func servefile(w http.ResponseWriter, r *http.Request) {
1394 xid := mux.Vars(r)["xid"]
1395 row := stmtGetFileData.QueryRow(xid)
1396 var media string
1397 var data []byte
1398 err := row.Scan(&media, &data)
1399 if err != nil {
1400 log.Printf("error loading file: %s", err)
1401 http.NotFound(w, r)
1402 return
1403 }
1404 w.Header().Set("Content-Type", media)
1405 w.Header().Set("X-Content-Type-Options", "nosniff")
1406 w.Header().Set("Cache-Control", "max-age="+somedays())
1407 w.Write(data)
1408}
1409
1410func nomoroboto(w http.ResponseWriter, r *http.Request) {
1411 io.WriteString(w, "User-agent: *\n")
1412 io.WriteString(w, "Disallow: /a\n")
1413 io.WriteString(w, "Disallow: /d\n")
1414 io.WriteString(w, "Disallow: /meme\n")
1415 for _, u := range allusers() {
1416 fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
1417 }
1418}
1419
1420func webhydra(w http.ResponseWriter, r *http.Request) {
1421 u := login.GetUserInfo(r)
1422 userid := u.UserID
1423 templinfo := getInfo(r)
1424 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1425 page := r.FormValue("page")
1426 var honks []*Honk
1427 switch page {
1428 case "atme":
1429 honks = gethonksforme(userid)
1430 case "home":
1431 honks = gethonksforuser(userid)
1432 honks = osmosis(honks, userid)
1433 case "first":
1434 honks = gethonksforuserfirstclass(userid)
1435 honks = osmosis(honks, userid)
1436 case "combo":
1437 c := r.FormValue("c")
1438 honks = gethonksbycombo(userid, c)
1439 case "convoy":
1440 c := r.FormValue("c")
1441 honks = gethonksbyconvoy(userid, c)
1442 case "honker":
1443 xid := r.FormValue("xid")
1444 if strings.IndexByte(xid, '@') != -1 {
1445 xid = gofish(xid)
1446 }
1447 honks = gethonksbyxonker(userid, xid)
1448 default:
1449 http.NotFound(w, r)
1450 }
1451 if len(honks) > 0 {
1452 templinfo["TopXID"] = honks[0].XID
1453 }
1454 if topxid := r.FormValue("topxid"); topxid != "" {
1455 for i, h := range honks {
1456 if h.XID == topxid {
1457 honks = honks[0:i]
1458 break
1459 }
1460 }
1461 log.Printf("topxid %d frags", len(honks))
1462 }
1463 reverbolate(userid, honks)
1464 templinfo["Honks"] = honks
1465 w.Header().Set("Content-Type", "text/html; charset=utf-8")
1466 err := readviews.Execute(w, "honkfrags.html", templinfo)
1467 if err != nil {
1468 log.Printf("frag error: %s", err)
1469 }
1470}
1471
1472func serve() {
1473 db := opendatabase()
1474 login.Init(db)
1475
1476 listener, err := openListener()
1477 if err != nil {
1478 log.Fatal(err)
1479 }
1480 go redeliverator()
1481
1482 debug := false
1483 getconfig("debug", &debug)
1484 readviews = templates.Load(debug,
1485 "views/honkpage.html",
1486 "views/honkfrags.html",
1487 "views/honkers.html",
1488 "views/zonkers.html",
1489 "views/combos.html",
1490 "views/honkform.html",
1491 "views/honk.html",
1492 "views/account.html",
1493 "views/about.html",
1494 "views/funzone.html",
1495 "views/login.html",
1496 "views/xzone.html",
1497 "views/header.html",
1498 "views/onts.html",
1499 "views/honkpage.js",
1500 )
1501 if !debug {
1502 assets := []string{"views/style.css", "views/local.css", "views/honkpage.js"}
1503 for _, s := range assets {
1504 savedassetparams[s] = getassetparam(s)
1505 }
1506 }
1507
1508 mux := mux.NewRouter()
1509 mux.Use(login.Checker)
1510
1511 posters := mux.Methods("POST").Subrouter()
1512 getters := mux.Methods("GET").Subrouter()
1513
1514 getters.HandleFunc("/", homepage)
1515 getters.HandleFunc("/home", homepage)
1516 getters.HandleFunc("/front", homepage)
1517 getters.HandleFunc("/robots.txt", nomoroboto)
1518 getters.HandleFunc("/rss", showrss)
1519 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}", showuser)
1520 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/"+honkSep+"/{xid:[[:alnum:]]+}", showhonk)
1521 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/rss", showrss)
1522 posters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/inbox", inbox)
1523 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/outbox", outbox)
1524 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/followers", emptiness)
1525 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/following", emptiness)
1526 getters.HandleFunc("/a", avatate)
1527 getters.HandleFunc("/o", thelistingoftheontologies)
1528 getters.HandleFunc("/o/{name:.+}", showontology)
1529 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1530 getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
1531 getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
1532 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1533
1534 getters.HandleFunc("/style.css", servecss)
1535 getters.HandleFunc("/local.css", servecss)
1536 getters.HandleFunc("/honkpage.js", serveasset)
1537 getters.HandleFunc("/about", servehtml)
1538 getters.HandleFunc("/login", servehtml)
1539 posters.HandleFunc("/dologin", login.LoginFunc)
1540 getters.HandleFunc("/logout", login.LogoutFunc)
1541
1542 loggedin := mux.NewRoute().Subrouter()
1543 loggedin.Use(login.Required)
1544 loggedin.HandleFunc("/account", accountpage)
1545 loggedin.HandleFunc("/funzone", showfunzone)
1546 loggedin.HandleFunc("/chpass", dochpass)
1547 loggedin.HandleFunc("/atme", homepage)
1548 loggedin.HandleFunc("/zonkzone", zonkzone)
1549 loggedin.HandleFunc("/xzone", xzone)
1550 loggedin.HandleFunc("/edit", edithonkpage)
1551 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(submithonk)))
1552 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(submitbonk)))
1553 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1554 loggedin.Handle("/zonkzonk", login.CSRFWrap("zonkzonk", http.HandlerFunc(zonkzonk)))
1555 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1556 loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
1557 loggedin.HandleFunc("/honkers", showhonkers)
1558 loggedin.HandleFunc("/h/{name:[[:alnum:]_.-]+}", showhonker)
1559 loggedin.HandleFunc("/h", showhonker)
1560 loggedin.HandleFunc("/c/{name:[[:alnum:]_.-]+}", showcombo)
1561 loggedin.HandleFunc("/c", showcombos)
1562 loggedin.HandleFunc("/t", showconvoy)
1563 loggedin.HandleFunc("/q", showsearch)
1564 loggedin.HandleFunc("/hydra", webhydra)
1565 loggedin.Handle("/submithonker", login.CSRFWrap("submithonker", http.HandlerFunc(submithonker)))
1566
1567 err = http.Serve(listener, mux)
1568 if err != nil {
1569 log.Fatal(err)
1570 }
1571}