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