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