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"
23 "html/template"
24 "io"
25 "log"
26 notrand "math/rand"
27 "net/http"
28 "net/url"
29 "os"
30 "sort"
31 "strconv"
32 "strings"
33 "time"
34
35 "github.com/gorilla/mux"
36 "humungus.tedunangst.com/r/webs/css"
37 "humungus.tedunangst.com/r/webs/htfilter"
38 "humungus.tedunangst.com/r/webs/httpsig"
39 "humungus.tedunangst.com/r/webs/image"
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
51func getuserstyle(u *login.UserInfo) template.CSS {
52 if u == nil {
53 return ""
54 }
55 user, _ := butwhatabout(u.Username)
56 if user.SkinnyCSS {
57 return "main { max-width: 700px; }"
58 }
59 return ""
60}
61
62func getInfo(r *http.Request) map[string]interface{} {
63 u := login.GetUserInfo(r)
64 templinfo := make(map[string]interface{})
65 templinfo["StyleParam"] = getassetparam("views/style.css")
66 templinfo["LocalStyleParam"] = getassetparam("views/local.css")
67 templinfo["JSParam"] = getassetparam("views/honkpage.js")
68 templinfo["UserStyle"] = getuserstyle(u)
69 templinfo["ServerName"] = serverName
70 templinfo["IconName"] = iconName
71 templinfo["UserInfo"] = u
72 templinfo["UserSep"] = userSep
73 if u != nil {
74 var combos []string
75 combocache.Get(u.UserID, &combos)
76 templinfo["Combos"] = combos
77 }
78 return templinfo
79}
80
81func homepage(w http.ResponseWriter, r *http.Request) {
82 templinfo := getInfo(r)
83 u := login.GetUserInfo(r)
84 var honks []*Honk
85 var userid int64 = -1
86 if r.URL.Path == "/front" || u == nil {
87 honks = getpublichonks()
88 } else {
89 userid = u.UserID
90 if r.URL.Path == "/atme" {
91 templinfo["PageName"] = "atme"
92 honks = gethonksforme(userid)
93 } else if r.URL.Path == "/first" {
94 templinfo["PageName"] = "first"
95 honks = gethonksforuser(userid)
96 honks = osmosis(honks, userid)
97 } else {
98 templinfo["PageName"] = "home"
99 honks = gethonksforuser(userid)
100 honks = osmosis(honks, userid)
101 }
102 if len(honks) > 0 {
103 templinfo["TopXID"] = honks[0].XID
104 }
105 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
106 }
107
108 templinfo["ShowRSS"] = true
109 templinfo["ServerMessage"] = serverMsg
110 honkpage(w, u, honks, templinfo)
111}
112
113func showfunzone(w http.ResponseWriter, r *http.Request) {
114 var emunames, memenames []string
115 dir, err := os.Open("emus")
116 if err == nil {
117 emunames, _ = dir.Readdirnames(0)
118 dir.Close()
119 }
120 for i, e := range emunames {
121 if len(e) > 4 {
122 emunames[i] = e[:len(e)-4]
123 }
124 }
125 dir, err = os.Open("memes")
126 if err == nil {
127 memenames, _ = dir.Readdirnames(0)
128 dir.Close()
129 }
130 templinfo := getInfo(r)
131 templinfo["Emus"] = emunames
132 templinfo["Memes"] = memenames
133 err = readviews.Execute(w, "funzone.html", templinfo)
134 if err != nil {
135 log.Print(err)
136 }
137}
138
139func showrss(w http.ResponseWriter, r *http.Request) {
140 name := mux.Vars(r)["name"]
141
142 var honks []*Honk
143 if name != "" {
144 honks = gethonksbyuser(name, false)
145 } else {
146 honks = getpublichonks()
147 }
148 if len(honks) > 20 {
149 honks = honks[0:20]
150 }
151 reverbolate(-1, honks)
152
153 home := fmt.Sprintf("https://%s/", serverName)
154 base := home
155 if name != "" {
156 home += "u/" + name
157 name += " "
158 }
159 feed := rss.Feed{
160 Title: name + "honk",
161 Link: home,
162 Description: name + "honk rss",
163 Image: &rss.Image{
164 URL: base + "icon.png",
165 Title: name + "honk rss",
166 Link: home,
167 },
168 }
169 var modtime time.Time
170 for _, honk := range honks {
171 if !firstclass(honk) {
172 continue
173 }
174 desc := string(honk.HTML)
175 if t := honk.Time; t != nil {
176 desc += fmt.Sprintf(`<p>Time: %s`, t.StartTime.Local().Format("03:04PM EDT Mon Jan 02"))
177 if t.Duration != 0 {
178 desc += fmt.Sprintf(`<br>Duration: %s`, t.Duration)
179 }
180 }
181 if p := honk.Place; p != nil {
182 desc += fmt.Sprintf(`<p>Location: <a href="%s">%s</a> %f %f`,
183 html.EscapeString(p.Url), html.EscapeString(p.Name), p.Latitude, p.Longitude)
184 }
185 for _, d := range honk.Donks {
186 desc += fmt.Sprintf(`<p><a href="%s">Attachment: %s</a>`,
187 d.URL, html.EscapeString(d.Name))
188 }
189
190 feed.Items = append(feed.Items, &rss.Item{
191 Title: fmt.Sprintf("%s %s %s", honk.Username, honk.What, honk.XID),
192 Description: rss.CData{Data: desc},
193 Link: honk.URL,
194 PubDate: honk.Date.Format(time.RFC1123),
195 Guid: &rss.Guid{IsPermaLink: true, Value: honk.URL},
196 })
197 if honk.Date.After(modtime) {
198 modtime = honk.Date
199 }
200 }
201 w.Header().Set("Cache-Control", "max-age=300")
202 w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
203
204 err := feed.Write(w)
205 if err != nil {
206 log.Printf("error writing rss: %s", err)
207 }
208}
209
210func crappola(j junk.Junk) bool {
211 t, _ := j.GetString("type")
212 a, _ := j.GetString("actor")
213 o, _ := j.GetString("object")
214 if t == "Delete" && a == o {
215 log.Printf("crappola from %s", a)
216 return true
217 }
218 return false
219}
220
221func ping(user *WhatAbout, who string) {
222 box, err := getboxes(who)
223 if err != nil {
224 log.Printf("no inbox for ping: %s", err)
225 return
226 }
227 j := junk.New()
228 j["@context"] = itiswhatitis
229 j["type"] = "Ping"
230 j["id"] = user.URL + "/ping/" + xfiltrate()
231 j["actor"] = user.URL
232 j["to"] = who
233 keyname, key := ziggy(user.Name)
234 err = PostJunk(keyname, key, box.In, j)
235 if err != nil {
236 log.Printf("can't send ping: %s", err)
237 return
238 }
239 log.Printf("sent ping to %s: %s", who, j["id"])
240}
241
242func pong(user *WhatAbout, who string, obj string) {
243 box, err := getboxes(who)
244 if err != nil {
245 log.Printf("no inbox for pong %s : %s", who, err)
246 return
247 }
248 j := junk.New()
249 j["@context"] = itiswhatitis
250 j["type"] = "Pong"
251 j["id"] = user.URL + "/pong/" + xfiltrate()
252 j["actor"] = user.URL
253 j["to"] = who
254 j["object"] = obj
255 keyname, key := ziggy(user.Name)
256 err = PostJunk(keyname, key, box.In, j)
257 if err != nil {
258 log.Printf("can't send pong: %s", err)
259 return
260 }
261}
262
263func inbox(w http.ResponseWriter, r *http.Request) {
264 name := mux.Vars(r)["name"]
265 user, err := butwhatabout(name)
266 if err != nil {
267 http.NotFound(w, r)
268 return
269 }
270 if stealthmode(user.ID, r) {
271 http.NotFound(w, r)
272 return
273 }
274 var buf bytes.Buffer
275 io.Copy(&buf, r.Body)
276 payload := buf.Bytes()
277 j, err := junk.Read(bytes.NewReader(payload))
278 if err != nil {
279 log.Printf("bad payload: %s", err)
280 io.WriteString(os.Stdout, "bad payload\n")
281 os.Stdout.Write(payload)
282 io.WriteString(os.Stdout, "\n")
283 return
284 }
285 if crappola(j) {
286 return
287 }
288 keyname, err := httpsig.VerifyRequest(r, payload, zaggy)
289 if err != nil {
290 log.Printf("inbox message failed signature: %s", err)
291 if keyname != "" {
292 keyname, err = makeitworksomehowwithoutregardforkeycontinuity(keyname, r, payload)
293 if err != nil {
294 log.Printf("still failed: %s", err)
295 }
296 }
297 if err != nil {
298 return
299 }
300 }
301 what, _ := j.GetString("type")
302 if what == "Like" {
303 return
304 }
305 who, _ := j.GetString("actor")
306 origin := keymatch(keyname, who)
307 if origin == "" {
308 log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
309 return
310 }
311 if rejectactor(user.ID, who) {
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 = markitzero(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 = ? and flavor in ('dub')",
1186 honkerid, u.UserID)
1187 err := row.Scan(&url)
1188 if err != nil {
1189 log.Printf("can't get honker xid: %s", err)
1190 return
1191 }
1192 log.Printf("unsubscribing from %s", url)
1193 user, _ := butwhatabout(u.Username)
1194 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, url, "sub")
1195 if err != nil {
1196 log.Printf("error updating honker: %s", err)
1197 return
1198 }
1199 go itakeitallback(user, url)
1200
1201 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1202 return
1203 }
1204 if goodbye == "X" {
1205 db := opendatabase()
1206 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ? and flavor in ('unsub', 'peep')",
1207 honkerid, u.UserID)
1208 err := row.Scan(&url)
1209 if err != nil {
1210 log.Printf("can't get honker xid: %s", err)
1211 return
1212 }
1213 log.Printf("resubscribing to %s", url)
1214 user, _ := butwhatabout(u.Username)
1215 _, err = stmtUpdateFlavor.Exec("presub", u.UserID, url, "unsub")
1216 if err == nil {
1217 _, err = stmtUpdateFlavor.Exec("presub", u.UserID, url, "peep")
1218 }
1219 if err != nil {
1220 log.Printf("error updating honker: %s", err)
1221 return
1222 }
1223 go subsub(user, url)
1224
1225 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1226 return
1227 }
1228 combos = " " + strings.TrimSpace(combos) + " "
1229 _, err := stmtUpdateHonker.Exec(name, combos, honkerid, u.UserID)
1230 if err != nil {
1231 log.Printf("update honker err: %s", err)
1232 return
1233 }
1234 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1235 return
1236 }
1237
1238 flavor := "presub"
1239 if peep == "peep" {
1240 flavor = "peep"
1241 }
1242 p, err := investigate(url)
1243 if err != nil {
1244 http.Error(w, "error investigating: "+err.Error(), http.StatusInternalServerError)
1245 log.Printf("failed to investigate honker: %s", err)
1246 return
1247 }
1248 url = p.XID
1249
1250 db := opendatabase()
1251 row := db.QueryRow("select xid from honkers where xid = ? and userid = ? and flavor in ('sub', 'unsub', 'peep')", url, u.UserID)
1252 var x string
1253 err = row.Scan(&x)
1254 if err != sql.ErrNoRows {
1255 http.Error(w, "it seems you are already subscribed to them", http.StatusInternalServerError)
1256 if err != nil {
1257 log.Printf("honker scan err: %s", err)
1258 }
1259 return
1260 }
1261
1262 if name == "" {
1263 name = p.Handle
1264 }
1265 _, err = stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1266 if err != nil {
1267 log.Print(err)
1268 return
1269 }
1270 if flavor == "presub" {
1271 user, _ := butwhatabout(u.Username)
1272 go subsub(user, url)
1273 }
1274 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1275}
1276
1277func hfcspage(w http.ResponseWriter, r *http.Request) {
1278 userinfo := login.GetUserInfo(r)
1279
1280 filters := getfilters(userinfo.UserID, filtAny)
1281
1282 templinfo := getInfo(r)
1283 templinfo["Filters"] = filters
1284 templinfo["FilterCSRF"] = login.GetCSRF("filter", r)
1285 err := readviews.Execute(w, "hfcs.html", templinfo)
1286 if err != nil {
1287 log.Print(err)
1288 }
1289}
1290
1291func savehfcs(w http.ResponseWriter, r *http.Request) {
1292 userinfo := login.GetUserInfo(r)
1293 itsok := r.FormValue("itsok")
1294 if itsok == "iforgiveyou" {
1295 hfcsid, _ := strconv.ParseInt(r.FormValue("hfcsid"), 10, 0)
1296 _, err := stmtDeleteFilter.Exec(userinfo.UserID, hfcsid)
1297 if err != nil {
1298 log.Printf("error deleting filter: %s", err)
1299 }
1300 filtcache.Clear(userinfo.UserID)
1301 http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
1302 return
1303 }
1304
1305 filt := new(Filter)
1306 filt.Name = r.FormValue("name")
1307 filt.Date = time.Now().UTC()
1308 filt.Actor = r.FormValue("actor")
1309 filt.IncludeAudience = r.FormValue("incaud") == "yes"
1310 filt.Text = r.FormValue("filttext")
1311 filt.IsAnnounce = r.FormValue("isannounce") == "yes"
1312 filt.AnnounceOf = r.FormValue("announceof")
1313 filt.Reject = r.FormValue("doreject") == "yes"
1314 filt.SkipMedia = r.FormValue("doskipmedia") == "yes"
1315 filt.Hide = r.FormValue("dohide") == "yes"
1316 filt.Collapse = r.FormValue("docollapse") == "yes"
1317 filt.Rewrite = r.FormValue("filtrewrite")
1318 filt.Replace = r.FormValue("filtreplace")
1319
1320 if filt.Actor == "" && filt.Text == "" {
1321 log.Printf("blank filter")
1322 return
1323 }
1324
1325 j, err := jsonify(filt)
1326 if err == nil {
1327 _, err = stmtSaveFilter.Exec(userinfo.UserID, j)
1328 }
1329 if err != nil {
1330 log.Printf("error saving filter: %s", err)
1331 }
1332
1333 filtcache.Clear(userinfo.UserID)
1334 http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
1335}
1336
1337func accountpage(w http.ResponseWriter, r *http.Request) {
1338 u := login.GetUserInfo(r)
1339 user, _ := butwhatabout(u.Username)
1340 templinfo := getInfo(r)
1341 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1342 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1343 templinfo["User"] = user
1344 err := readviews.Execute(w, "account.html", templinfo)
1345 if err != nil {
1346 log.Print(err)
1347 }
1348}
1349
1350func dochpass(w http.ResponseWriter, r *http.Request) {
1351 err := login.ChangePassword(w, r)
1352 if err != nil {
1353 log.Printf("error changing password: %s", err)
1354 }
1355 http.Redirect(w, r, "/account", http.StatusSeeOther)
1356}
1357
1358func fingerlicker(w http.ResponseWriter, r *http.Request) {
1359 orig := r.FormValue("resource")
1360
1361 log.Printf("finger lick: %s", orig)
1362
1363 if strings.HasPrefix(orig, "acct:") {
1364 orig = orig[5:]
1365 }
1366
1367 name := orig
1368 idx := strings.LastIndexByte(name, '/')
1369 if idx != -1 {
1370 name = name[idx+1:]
1371 if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
1372 log.Printf("foreign request rejected")
1373 name = ""
1374 }
1375 } else {
1376 idx = strings.IndexByte(name, '@')
1377 if idx != -1 {
1378 name = name[:idx]
1379 if name+"@"+serverName != orig {
1380 log.Printf("foreign request rejected")
1381 name = ""
1382 }
1383 }
1384 }
1385 user, err := butwhatabout(name)
1386 if err != nil {
1387 http.NotFound(w, r)
1388 return
1389 }
1390 if stealthmode(user.ID, r) {
1391 http.NotFound(w, r)
1392 return
1393 }
1394
1395 j := junk.New()
1396 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1397 j["aliases"] = []string{user.URL}
1398 var links []junk.Junk
1399 l := junk.New()
1400 l["rel"] = "self"
1401 l["type"] = `application/activity+json`
1402 l["href"] = user.URL
1403 links = append(links, l)
1404 j["links"] = links
1405
1406 w.Header().Set("Cache-Control", "max-age=3600")
1407 w.Header().Set("Content-Type", "application/jrd+json")
1408 j.Write(w)
1409}
1410
1411func somedays() string {
1412 secs := 432000 + notrand.Int63n(432000)
1413 return fmt.Sprintf("%d", secs)
1414}
1415
1416func avatate(w http.ResponseWriter, r *http.Request) {
1417 n := r.FormValue("a")
1418 a := avatar(n)
1419 w.Header().Set("Cache-Control", "max-age="+somedays())
1420 w.Write(a)
1421}
1422
1423func servecss(w http.ResponseWriter, r *http.Request) {
1424 fd, err := os.Open("views" + r.URL.Path)
1425 if err != nil {
1426 http.NotFound(w, r)
1427 return
1428 }
1429 w.Header().Set("Cache-Control", "max-age=0")
1430 w.Header().Set("Content-Type", "text/css; charset=utf-8")
1431 err = css.Filter(fd, w)
1432 if err != nil {
1433 log.Printf("error filtering css: %s", err)
1434 }
1435}
1436func serveasset(w http.ResponseWriter, r *http.Request) {
1437 w.Header().Set("Cache-Control", "max-age=7776000")
1438 http.ServeFile(w, r, "views"+r.URL.Path)
1439}
1440func servehtml(w http.ResponseWriter, r *http.Request) {
1441 templinfo := getInfo(r)
1442 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1443 if err != nil {
1444 log.Print(err)
1445 }
1446}
1447func serveemu(w http.ResponseWriter, r *http.Request) {
1448 xid := mux.Vars(r)["xid"]
1449 w.Header().Set("Cache-Control", "max-age="+somedays())
1450 http.ServeFile(w, r, "emus/"+xid)
1451}
1452func servememe(w http.ResponseWriter, r *http.Request) {
1453 xid := mux.Vars(r)["xid"]
1454 w.Header().Set("Cache-Control", "max-age="+somedays())
1455 http.ServeFile(w, r, "memes/"+xid)
1456}
1457
1458func servefile(w http.ResponseWriter, r *http.Request) {
1459 xid := mux.Vars(r)["xid"]
1460 row := stmtGetFileData.QueryRow(xid)
1461 var media string
1462 var data []byte
1463 err := row.Scan(&media, &data)
1464 if err != nil {
1465 log.Printf("error loading file: %s", err)
1466 http.NotFound(w, r)
1467 return
1468 }
1469 w.Header().Set("Content-Type", media)
1470 w.Header().Set("X-Content-Type-Options", "nosniff")
1471 w.Header().Set("Cache-Control", "max-age="+somedays())
1472 w.Write(data)
1473}
1474
1475func nomoroboto(w http.ResponseWriter, r *http.Request) {
1476 io.WriteString(w, "User-agent: *\n")
1477 io.WriteString(w, "Disallow: /a\n")
1478 io.WriteString(w, "Disallow: /d\n")
1479 io.WriteString(w, "Disallow: /meme\n")
1480 for _, u := range allusers() {
1481 fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
1482 }
1483}
1484
1485func webhydra(w http.ResponseWriter, r *http.Request) {
1486 u := login.GetUserInfo(r)
1487 userid := u.UserID
1488 templinfo := getInfo(r)
1489 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1490 page := r.FormValue("page")
1491 var honks []*Honk
1492 switch page {
1493 case "atme":
1494 honks = gethonksforme(userid)
1495 case "home":
1496 honks = gethonksforuser(userid)
1497 honks = osmosis(honks, userid)
1498 case "first":
1499 honks = gethonksforuserfirstclass(userid)
1500 honks = osmosis(honks, userid)
1501 case "combo":
1502 c := r.FormValue("c")
1503 honks = gethonksbycombo(userid, c)
1504 case "convoy":
1505 c := r.FormValue("c")
1506 honks = gethonksbyconvoy(userid, c)
1507 case "honker":
1508 xid := r.FormValue("xid")
1509 if strings.IndexByte(xid, '@') != -1 {
1510 xid = gofish(xid)
1511 }
1512 honks = gethonksbyxonker(userid, xid)
1513 default:
1514 http.NotFound(w, r)
1515 }
1516 if len(honks) > 0 {
1517 templinfo["TopXID"] = honks[0].XID
1518 }
1519 if topxid := r.FormValue("topxid"); topxid != "" {
1520 for i, h := range honks {
1521 if h.XID == topxid {
1522 honks = honks[0:i]
1523 break
1524 }
1525 }
1526 log.Printf("topxid %d frags", len(honks))
1527 }
1528 reverbolate(userid, honks)
1529 templinfo["Honks"] = honks
1530 w.Header().Set("Content-Type", "text/html; charset=utf-8")
1531 err := readviews.Execute(w, "honkfrags.html", templinfo)
1532 if err != nil {
1533 log.Printf("frag error: %s", err)
1534 }
1535}
1536
1537func serve() {
1538 db := opendatabase()
1539 login.Init(db)
1540
1541 listener, err := openListener()
1542 if err != nil {
1543 log.Fatal(err)
1544 }
1545 go redeliverator()
1546
1547 debug := false
1548 getconfig("debug", &debug)
1549 readviews = templates.Load(debug,
1550 "views/honkpage.html",
1551 "views/honkfrags.html",
1552 "views/honkers.html",
1553 "views/hfcs.html",
1554 "views/combos.html",
1555 "views/honkform.html",
1556 "views/honk.html",
1557 "views/account.html",
1558 "views/about.html",
1559 "views/funzone.html",
1560 "views/login.html",
1561 "views/xzone.html",
1562 "views/header.html",
1563 "views/onts.html",
1564 "views/honkpage.js",
1565 )
1566 if !debug {
1567 assets := []string{"views/style.css", "views/local.css", "views/honkpage.js"}
1568 for _, s := range assets {
1569 savedassetparams[s] = getassetparam(s)
1570 }
1571 }
1572
1573 mux := mux.NewRouter()
1574 mux.Use(login.Checker)
1575
1576 posters := mux.Methods("POST").Subrouter()
1577 getters := mux.Methods("GET").Subrouter()
1578
1579 getters.HandleFunc("/", homepage)
1580 getters.HandleFunc("/home", homepage)
1581 getters.HandleFunc("/front", homepage)
1582 getters.HandleFunc("/robots.txt", nomoroboto)
1583 getters.HandleFunc("/rss", showrss)
1584 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}", showuser)
1585 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/"+honkSep+"/{xid:[[:alnum:]]+}", showhonk)
1586 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/rss", showrss)
1587 posters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/inbox", inbox)
1588 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/outbox", outbox)
1589 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/followers", emptiness)
1590 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/following", emptiness)
1591 getters.HandleFunc("/a", avatate)
1592 getters.HandleFunc("/o", thelistingoftheontologies)
1593 getters.HandleFunc("/o/{name:.+}", showontology)
1594 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1595 getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
1596 getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
1597 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1598
1599 getters.HandleFunc("/style.css", servecss)
1600 getters.HandleFunc("/local.css", servecss)
1601 getters.HandleFunc("/honkpage.js", serveasset)
1602 getters.HandleFunc("/about", servehtml)
1603 getters.HandleFunc("/login", servehtml)
1604 posters.HandleFunc("/dologin", login.LoginFunc)
1605 getters.HandleFunc("/logout", login.LogoutFunc)
1606
1607 loggedin := mux.NewRoute().Subrouter()
1608 loggedin.Use(login.Required)
1609 loggedin.HandleFunc("/account", accountpage)
1610 loggedin.HandleFunc("/funzone", showfunzone)
1611 loggedin.HandleFunc("/chpass", dochpass)
1612 loggedin.HandleFunc("/atme", homepage)
1613 loggedin.HandleFunc("/hfcs", hfcspage)
1614 loggedin.HandleFunc("/xzone", xzone)
1615 loggedin.HandleFunc("/edit", edithonkpage)
1616 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(submithonk)))
1617 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(submitbonk)))
1618 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1619 loggedin.Handle("/savehfcs", login.CSRFWrap("filter", http.HandlerFunc(savehfcs)))
1620 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1621 loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
1622 loggedin.HandleFunc("/honkers", showhonkers)
1623 loggedin.HandleFunc("/h/{name:[[:alnum:]_.-]+}", showhonker)
1624 loggedin.HandleFunc("/h", showhonker)
1625 loggedin.HandleFunc("/c/{name:[[:alnum:]_.-]+}", showcombo)
1626 loggedin.HandleFunc("/c", showcombos)
1627 loggedin.HandleFunc("/t", showconvoy)
1628 loggedin.HandleFunc("/q", showsearch)
1629 loggedin.HandleFunc("/hydra", webhydra)
1630 loggedin.Handle("/submithonker", login.CSRFWrap("submithonker", http.HandlerFunc(submithonker)))
1631
1632 err = http.Serve(listener, mux)
1633 if err != nil {
1634 log.Fatal(err)
1635 }
1636}