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; charset=utf-8")
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 sendzonkofsorts(xonk *Honk, user *WhatAbout, what string) {
951 zonk := Honk{
952 What: what,
953 XID: xonk.XID,
954 Date: time.Now().UTC(),
955 Audience: oneofakind(xonk.Audience),
956 }
957 zonk.Public = !keepitquiet(zonk.Audience)
958
959 log.Printf("announcing %sed honk: %s", what, xonk.XID)
960 go honkworldwide(user, &zonk)
961}
962
963func zonkit(w http.ResponseWriter, r *http.Request) {
964 wherefore := r.FormValue("wherefore")
965 what := r.FormValue("what")
966 userinfo := login.GetUserInfo(r)
967 user, _ := butwhatabout(userinfo.Username)
968
969 if wherefore == "ack" {
970 _, err := stmtUpdateFlags.Exec(flagIsAcked, what)
971 if err != nil {
972 log.Printf("error acking: %s", err)
973 }
974 xonk := getxonk(userinfo.UserID, what)
975 if xonk != nil {
976 sendzonkofsorts(xonk, user, "ack")
977 }
978 return
979 }
980
981 if wherefore == "deack" {
982 _, err := stmtClearFlags.Exec(flagIsAcked, what)
983 if err != nil {
984 log.Printf("error deacking: %s", err)
985 }
986 xonk := getxonk(userinfo.UserID, what)
987 if xonk != nil {
988 sendzonkofsorts(xonk, user, "deack")
989 }
990 return
991 }
992
993 log.Printf("zonking %s %s", wherefore, what)
994 if wherefore == "zonk" {
995 xonk := getxonk(userinfo.UserID, what)
996 if xonk != nil {
997 _, err := stmtZonkDonks.Exec(xonk.ID)
998 if err != nil {
999 log.Printf("error zonking: %s", err)
1000 }
1001 _, err = stmtZonkIt.Exec(userinfo.UserID, what)
1002 if err != nil {
1003 log.Printf("error zonking: %s", err)
1004 }
1005 if xonk.Whofore == 2 || xonk.Whofore == 3 {
1006 sendzonkofsorts(xonk, user, "zonk")
1007 }
1008 }
1009 }
1010 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
1011 if err != nil {
1012 log.Printf("error saving zonker: %s", err)
1013 return
1014 }
1015}
1016
1017func savehonk(w http.ResponseWriter, r *http.Request) {
1018 rid := r.FormValue("rid")
1019 noise := r.FormValue("noise")
1020
1021 userinfo := login.GetUserInfo(r)
1022 user, _ := butwhatabout(userinfo.Username)
1023
1024 dt := time.Now().UTC()
1025 xid := fmt.Sprintf("%s/%s/%s", user.URL, honkSep, xfiltrate())
1026 what := "honk"
1027 if rid != "" {
1028 what = "tonk"
1029 }
1030 honk := Honk{
1031 UserID: userinfo.UserID,
1032 Username: userinfo.Username,
1033 What: "honk",
1034 Honker: user.URL,
1035 XID: xid,
1036 Date: dt,
1037 }
1038 if strings.HasPrefix(noise, "DZ:") {
1039 idx := strings.Index(noise, "\n")
1040 if idx == -1 {
1041 honk.Precis = noise
1042 noise = ""
1043 } else {
1044 honk.Precis = noise[:idx]
1045 noise = noise[idx+1:]
1046 }
1047 }
1048 noise = hooterize(noise)
1049 noise = strings.TrimSpace(noise)
1050 honk.Precis = strings.TrimSpace(honk.Precis)
1051
1052 var convoy string
1053 if rid != "" {
1054 xonk := getxonk(userinfo.UserID, rid)
1055 if xonk != nil {
1056 if xonk.Public {
1057 honk.Audience = append(honk.Audience, xonk.Audience...)
1058 }
1059 convoy = xonk.Convoy
1060 } else {
1061 xonkaud, c := whosthere(rid)
1062 honk.Audience = append(honk.Audience, xonkaud...)
1063 convoy = c
1064 }
1065 for i, a := range honk.Audience {
1066 if a == thewholeworld {
1067 honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
1068 break
1069 }
1070 }
1071 honk.RID = rid
1072 } else {
1073 honk.Audience = []string{thewholeworld}
1074 }
1075 if noise != "" && noise[0] == '@' {
1076 honk.Audience = append(grapevine(noise), honk.Audience...)
1077 } else {
1078 honk.Audience = append(honk.Audience, grapevine(noise)...)
1079 }
1080 if convoy == "" {
1081 convoy = "data:,electrichonkytonk-" + xfiltrate()
1082 }
1083 butnottooloud(honk.Audience)
1084 honk.Audience = oneofakind(honk.Audience)
1085 if len(honk.Audience) == 0 {
1086 log.Printf("honk to nowhere")
1087 http.Error(w, "honk to nowhere...", http.StatusNotFound)
1088 return
1089 }
1090 honk.Public = !keepitquiet(honk.Audience)
1091 noise = obfusbreak(noise)
1092 honk.Noise = noise
1093 honk.Convoy = convoy
1094
1095 donkxid := r.FormValue("donkxid")
1096 if donkxid == "" {
1097 file, filehdr, err := r.FormFile("donk")
1098 if err == nil {
1099 var buf bytes.Buffer
1100 io.Copy(&buf, file)
1101 file.Close()
1102 data := buf.Bytes()
1103 xid := xfiltrate()
1104 var media, name string
1105 img, err := image.Vacuum(&buf, image.Params{MaxWidth: 2048, MaxHeight: 2048})
1106 if err == nil {
1107 data = img.Data
1108 format := img.Format
1109 media = "image/" + format
1110 if format == "jpeg" {
1111 format = "jpg"
1112 }
1113 name = xid + "." + format
1114 xid = name
1115 } else {
1116 maxsize := 100000
1117 if len(data) > maxsize {
1118 log.Printf("bad image: %s too much text: %d", err, len(data))
1119 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1120 return
1121 }
1122 for i := 0; i < len(data); i++ {
1123 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
1124 log.Printf("bad image: %s not text: %d", err, data[i])
1125 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1126 return
1127 }
1128 }
1129 media = "text/plain"
1130 name = filehdr.Filename
1131 if name == "" {
1132 name = xid + ".txt"
1133 }
1134 xid += ".txt"
1135 }
1136 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1137 res, err := stmtSaveFile.Exec(xid, name, url, media, 1, data)
1138 if err != nil {
1139 log.Printf("unable to save image: %s", err)
1140 return
1141 }
1142 var d Donk
1143 d.FileID, _ = res.LastInsertId()
1144 d.XID = name
1145 d.Name = name
1146 d.Media = media
1147 d.URL = url
1148 d.Local = true
1149 honk.Donks = append(honk.Donks, &d)
1150 donkxid = d.XID
1151 }
1152 } else {
1153 xid := donkxid
1154 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1155 var donk Donk
1156 row := stmtFindFile.QueryRow(url)
1157 err := row.Scan(&donk.FileID)
1158 if err == nil {
1159 donk.XID = xid
1160 donk.Local = true
1161 donk.URL = url
1162 honk.Donks = append(honk.Donks, &donk)
1163 } else {
1164 log.Printf("can't find file: %s", xid)
1165 }
1166 }
1167 herd := herdofemus(honk.Noise)
1168 for _, e := range herd {
1169 donk := savedonk(e.ID, e.Name, "image/png", true)
1170 if donk != nil {
1171 donk.Name = e.Name
1172 honk.Donks = append(honk.Donks, donk)
1173 }
1174 }
1175 memetize(&honk)
1176
1177 aud := strings.Join(honk.Audience, " ")
1178 whofore := 2
1179 if !honk.Public {
1180 whofore = 3
1181 }
1182 if r.FormValue("preview") == "preview" {
1183 honks := []*Honk{&honk}
1184 reverbolate(userinfo.UserID, honks)
1185 templinfo := getInfo(r)
1186 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1187 templinfo["Honks"] = honks
1188 templinfo["InReplyTo"] = r.FormValue("rid")
1189 templinfo["Noise"] = r.FormValue("noise")
1190 templinfo["SavedFile"] = donkxid
1191 templinfo["ServerMessage"] = "honk preview"
1192 err := readviews.Execute(w, "honkpage.html", templinfo)
1193 if err != nil {
1194 log.Print(err)
1195 }
1196 return
1197 }
1198 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, honk.Honker, xid, rid,
1199 dt.Format(dbtimeformat), "", aud, honk.Noise, convoy, whofore, "html",
1200 honk.Precis, honk.Oonker, 0)
1201 if err != nil {
1202 log.Printf("error saving honk: %s", err)
1203 http.Error(w, "something bad happened while saving", http.StatusInternalServerError)
1204 return
1205 }
1206 honk.ID, _ = res.LastInsertId()
1207 for _, d := range honk.Donks {
1208 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
1209 if err != nil {
1210 log.Printf("err saving donk: %s", err)
1211 http.Error(w, "something bad happened while saving", http.StatusInternalServerError)
1212 return
1213 }
1214 }
1215
1216 go honkworldwide(user, &honk)
1217
1218 http.Redirect(w, r, xid, http.StatusSeeOther)
1219}
1220
1221func showhonkers(w http.ResponseWriter, r *http.Request) {
1222 userinfo := login.GetUserInfo(r)
1223 templinfo := getInfo(r)
1224 templinfo["Honkers"] = gethonkers(userinfo.UserID)
1225 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
1226 err := readviews.Execute(w, "honkers.html", templinfo)
1227 if err != nil {
1228 log.Print(err)
1229 }
1230}
1231
1232func showcombos(w http.ResponseWriter, r *http.Request) {
1233 userinfo := login.GetUserInfo(r)
1234 templinfo := getInfo(r)
1235 honkers := gethonkers(userinfo.UserID)
1236 var combos []string
1237 for _, h := range honkers {
1238 combos = append(combos, h.Combos...)
1239 }
1240 for i, c := range combos {
1241 if c == "-" {
1242 combos[i] = ""
1243 }
1244 }
1245 combos = oneofakind(combos)
1246 sort.Strings(combos)
1247 templinfo["Combos"] = combos
1248 err := readviews.Execute(w, "combos.html", templinfo)
1249 if err != nil {
1250 log.Print(err)
1251 }
1252}
1253
1254func savehonker(w http.ResponseWriter, r *http.Request) {
1255 u := login.GetUserInfo(r)
1256 name := r.FormValue("name")
1257 url := r.FormValue("url")
1258 peep := r.FormValue("peep")
1259 combos := r.FormValue("combos")
1260 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1261
1262 if honkerid > 0 {
1263 goodbye := r.FormValue("goodbye")
1264 if goodbye == "F" {
1265 db := opendatabase()
1266 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1267 honkerid, u.UserID)
1268 var xid string
1269 err := row.Scan(&xid)
1270 if err != nil {
1271 log.Printf("can't get honker xid: %s", err)
1272 return
1273 }
1274 log.Printf("unsubscribing from %s", xid)
1275 user, _ := butwhatabout(u.Username)
1276 go itakeitallback(user, xid)
1277 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1278 if err != nil {
1279 log.Printf("error updating honker: %s", err)
1280 return
1281 }
1282
1283 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1284 return
1285 }
1286 combos = " " + strings.TrimSpace(combos) + " "
1287 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1288 if err != nil {
1289 log.Printf("update honker err: %s", err)
1290 return
1291 }
1292 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1293 }
1294
1295 flavor := "presub"
1296 if peep == "peep" {
1297 flavor = "peep"
1298 }
1299 p := investigate(url)
1300 if p == nil {
1301 log.Printf("failed to investigate honker")
1302 return
1303 }
1304 url = p.XID
1305 if name == "" {
1306 name = p.Handle
1307 }
1308 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1309 if err != nil {
1310 log.Print(err)
1311 return
1312 }
1313 if flavor == "presub" {
1314 user, _ := butwhatabout(u.Username)
1315 go subsub(user, url)
1316 }
1317 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1318}
1319
1320type Zonker struct {
1321 ID int64
1322 Name string
1323 Wherefore string
1324}
1325
1326func zonkzone(w http.ResponseWriter, r *http.Request) {
1327 userinfo := login.GetUserInfo(r)
1328 rows, err := stmtGetZonkers.Query(userinfo.UserID)
1329 if err != nil {
1330 log.Printf("err: %s", err)
1331 return
1332 }
1333 defer rows.Close()
1334 var zonkers []Zonker
1335 for rows.Next() {
1336 var z Zonker
1337 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1338 zonkers = append(zonkers, z)
1339 }
1340 sort.Slice(zonkers, func(i, j int) bool {
1341 w1 := zonkers[i].Wherefore
1342 w2 := zonkers[j].Wherefore
1343 if w1 == w2 {
1344 return zonkers[i].Name < zonkers[j].Name
1345 }
1346 if w1 == "zonvoy" {
1347 w1 = "zzzzzzz"
1348 }
1349 if w2 == "zonvoy" {
1350 w2 = "zzzzzzz"
1351 }
1352 return w1 < w2
1353 })
1354
1355 templinfo := getInfo(r)
1356 templinfo["Zonkers"] = zonkers
1357 templinfo["ZonkCSRF"] = login.GetCSRF("zonkzonk", r)
1358 err = readviews.Execute(w, "zonkers.html", templinfo)
1359 if err != nil {
1360 log.Print(err)
1361 }
1362}
1363
1364func zonkzonk(w http.ResponseWriter, r *http.Request) {
1365 userinfo := login.GetUserInfo(r)
1366 itsok := r.FormValue("itsok")
1367 if itsok == "iforgiveyou" {
1368 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1369 db := opendatabase()
1370 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1371 userinfo.UserID, zonkerid)
1372 bitethethumbs()
1373 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1374 return
1375 }
1376 wherefore := r.FormValue("wherefore")
1377 name := r.FormValue("name")
1378 if name == "" {
1379 return
1380 }
1381 switch wherefore {
1382 case "zonker":
1383 case "zomain":
1384 case "zonvoy":
1385 case "zord":
1386 case "zilence":
1387 default:
1388 return
1389 }
1390 db := opendatabase()
1391 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1392 userinfo.UserID, name, wherefore)
1393 if wherefore == "zonker" || wherefore == "zomain" || wherefore == "zord" || wherefore == "zilence" {
1394 bitethethumbs()
1395 }
1396
1397 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1398}
1399
1400func accountpage(w http.ResponseWriter, r *http.Request) {
1401 u := login.GetUserInfo(r)
1402 user, _ := butwhatabout(u.Username)
1403 templinfo := getInfo(r)
1404 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1405 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1406 templinfo["User"] = user
1407 err := readviews.Execute(w, "account.html", templinfo)
1408 if err != nil {
1409 log.Print(err)
1410 }
1411}
1412
1413func dochpass(w http.ResponseWriter, r *http.Request) {
1414 err := login.ChangePassword(w, r)
1415 if err != nil {
1416 log.Printf("error changing password: %s", err)
1417 }
1418 http.Redirect(w, r, "/account", http.StatusSeeOther)
1419}
1420
1421func fingerlicker(w http.ResponseWriter, r *http.Request) {
1422 orig := r.FormValue("resource")
1423
1424 log.Printf("finger lick: %s", orig)
1425
1426 if strings.HasPrefix(orig, "acct:") {
1427 orig = orig[5:]
1428 }
1429
1430 name := orig
1431 idx := strings.LastIndexByte(name, '/')
1432 if idx != -1 {
1433 name = name[idx+1:]
1434 if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
1435 log.Printf("foreign request rejected")
1436 name = ""
1437 }
1438 } else {
1439 idx = strings.IndexByte(name, '@')
1440 if idx != -1 {
1441 name = name[:idx]
1442 if name+"@"+serverName != orig {
1443 log.Printf("foreign request rejected")
1444 name = ""
1445 }
1446 }
1447 }
1448 user, err := butwhatabout(name)
1449 if err != nil {
1450 http.NotFound(w, r)
1451 return
1452 }
1453
1454 j := junk.New()
1455 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1456 j["aliases"] = []string{user.URL}
1457 var links []junk.Junk
1458 l := junk.New()
1459 l["rel"] = "self"
1460 l["type"] = `application/activity+json`
1461 l["href"] = user.URL
1462 links = append(links, l)
1463 j["links"] = links
1464
1465 w.Header().Set("Cache-Control", "max-age=3600")
1466 w.Header().Set("Content-Type", "application/jrd+json")
1467 j.Write(w)
1468}
1469
1470func somedays() string {
1471 secs := 432000 + notrand.Int63n(432000)
1472 return fmt.Sprintf("%d", secs)
1473}
1474
1475func avatate(w http.ResponseWriter, r *http.Request) {
1476 n := r.FormValue("a")
1477 a := avatar(n)
1478 w.Header().Set("Cache-Control", "max-age="+somedays())
1479 w.Write(a)
1480}
1481
1482func servecss(w http.ResponseWriter, r *http.Request) {
1483 w.Header().Set("Cache-Control", "max-age=7776000")
1484 http.ServeFile(w, r, "views"+r.URL.Path)
1485}
1486func servehtml(w http.ResponseWriter, r *http.Request) {
1487 templinfo := getInfo(r)
1488 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1489 if err != nil {
1490 log.Print(err)
1491 }
1492}
1493func serveemu(w http.ResponseWriter, r *http.Request) {
1494 xid := mux.Vars(r)["xid"]
1495 w.Header().Set("Cache-Control", "max-age="+somedays())
1496 http.ServeFile(w, r, "emus/"+xid)
1497}
1498func servememe(w http.ResponseWriter, r *http.Request) {
1499 xid := mux.Vars(r)["xid"]
1500 w.Header().Set("Cache-Control", "max-age="+somedays())
1501 http.ServeFile(w, r, "memes/"+xid)
1502}
1503
1504func servefile(w http.ResponseWriter, r *http.Request) {
1505 xid := mux.Vars(r)["xid"]
1506 row := stmtFileData.QueryRow(xid)
1507 var media string
1508 var data []byte
1509 err := row.Scan(&media, &data)
1510 if err != nil {
1511 log.Printf("error loading file: %s", err)
1512 http.NotFound(w, r)
1513 return
1514 }
1515 w.Header().Set("Content-Type", media)
1516 w.Header().Set("X-Content-Type-Options", "nosniff")
1517 w.Header().Set("Cache-Control", "max-age="+somedays())
1518 w.Write(data)
1519}
1520
1521func nomoroboto(w http.ResponseWriter, r *http.Request) {
1522 io.WriteString(w, "User-agent: *\n")
1523 io.WriteString(w, "Disallow: /a\n")
1524 io.WriteString(w, "Disallow: /d\n")
1525 io.WriteString(w, "Disallow: /meme\n")
1526 for _, u := range allusers() {
1527 fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
1528 }
1529}
1530
1531func serve() {
1532 db := opendatabase()
1533 login.Init(db)
1534
1535 listener, err := openListener()
1536 if err != nil {
1537 log.Fatal(err)
1538 }
1539 go redeliverator()
1540
1541 debug := false
1542 getconfig("debug", &debug)
1543 readviews = templates.Load(debug,
1544 "views/honkpage.html",
1545 "views/honkfrags.html",
1546 "views/honkers.html",
1547 "views/zonkers.html",
1548 "views/combos.html",
1549 "views/honkform.html",
1550 "views/honk.html",
1551 "views/account.html",
1552 "views/about.html",
1553 "views/funzone.html",
1554 "views/login.html",
1555 "views/xzone.html",
1556 "views/header.html",
1557 )
1558 if !debug {
1559 s := "views/style.css"
1560 savedstyleparams[s] = getstyleparam(s)
1561 s = "views/local.css"
1562 savedstyleparams[s] = getstyleparam(s)
1563 }
1564
1565 bitethethumbs()
1566
1567 mux := mux.NewRouter()
1568 mux.Use(login.Checker)
1569
1570 posters := mux.Methods("POST").Subrouter()
1571 getters := mux.Methods("GET").Subrouter()
1572
1573 getters.HandleFunc("/", homepage)
1574 getters.HandleFunc("/front", homepage)
1575 getters.HandleFunc("/robots.txt", nomoroboto)
1576 getters.HandleFunc("/rss", showrss)
1577 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}", showuser)
1578 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/"+honkSep+"/{xid:[[:alnum:]]+}", showhonk)
1579 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/rss", showrss)
1580 posters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/inbox", inbox)
1581 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/outbox", outbox)
1582 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/followers", emptiness)
1583 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/following", emptiness)
1584 getters.HandleFunc("/a", avatate)
1585 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1586 getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
1587 getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
1588 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1589
1590 getters.HandleFunc("/style.css", servecss)
1591 getters.HandleFunc("/local.css", servecss)
1592 getters.HandleFunc("/about", servehtml)
1593 getters.HandleFunc("/login", servehtml)
1594 posters.HandleFunc("/dologin", login.LoginFunc)
1595 getters.HandleFunc("/logout", login.LogoutFunc)
1596
1597 loggedin := mux.NewRoute().Subrouter()
1598 loggedin.Use(login.Required)
1599 loggedin.HandleFunc("/account", accountpage)
1600 loggedin.HandleFunc("/funzone", showfunzone)
1601 loggedin.HandleFunc("/chpass", dochpass)
1602 loggedin.HandleFunc("/atme", homepage)
1603 loggedin.HandleFunc("/zonkzone", zonkzone)
1604 loggedin.HandleFunc("/xzone", xzone)
1605 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1606 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1607 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1608 loggedin.Handle("/zonkzonk", login.CSRFWrap("zonkzonk", http.HandlerFunc(zonkzonk)))
1609 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1610 loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
1611 loggedin.HandleFunc("/honkers", showhonkers)
1612 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1613 loggedin.HandleFunc("/h", showhonker)
1614 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1615 loggedin.HandleFunc("/c", showcombos)
1616 loggedin.HandleFunc("/t", showconvoy)
1617 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1618
1619 err = http.Serve(listener, mux)
1620 if err != nil {
1621 log.Fatal(err)
1622 }
1623}
1624
1625func cleanupdb(arg string) {
1626 db := opendatabase()
1627 days, err := strconv.Atoi(arg)
1628 if err != nil {
1629 honker := arg
1630 expdate := time.Now().UTC().Add(-3 * 24 * time.Hour).Format(dbtimeformat)
1631 doordie(db, "delete from donks where honkid in (select honkid from honks where dt < ? and whofore = 0 and honker = ?)", expdate, honker)
1632 doordie(db, "delete from honks where dt < ? and whofore = 0 and honker = ?", expdate, honker)
1633 } else {
1634 expdate := time.Now().UTC().Add(-time.Duration(days) * 24 * time.Hour).Format(dbtimeformat)
1635 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)
1636 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)
1637 }
1638 doordie(db, "delete from files where fileid not in (select fileid from donks)")
1639 for _, u := range allusers() {
1640 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)
1641 }
1642}
1643
1644var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1645var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1646var stmtHonksForUser, stmtHonksForMe, stmtSaveDub, stmtHonksByXonker *sql.Stmt
1647var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1648var stmtFindZonk, stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1649var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1650var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1651var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker *sql.Stmt
1652var stmtUpdateFlags, stmtClearFlags *sql.Stmt
1653
1654func preparetodie(db *sql.DB, s string) *sql.Stmt {
1655 stmt, err := db.Prepare(s)
1656 if err != nil {
1657 log.Fatalf("error %s: %s", err, s)
1658 }
1659 return stmt
1660}
1661
1662func prepareStatements(db *sql.DB) {
1663 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")
1664 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1665 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1666 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1667 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1668 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1669
1670 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 "
1671 limit := " order by honkid desc limit 250"
1672 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1673 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1674 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
1675 stmtUserHonks = preparetodie(db, selecthonks+"where (whofore = 2 or whofore = ?) and username = ? and dt > ?"+limit)
1676 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)
1677 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1678 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on (honkers.xid = honks.honker or honkers.xid = honks.oonker) where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1679 stmtHonksByXonker = preparetodie(db, selecthonks+" where honks.userid = ? and (honker = ? or oonker = ?)"+butnotthose+limit)
1680 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1681 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or (? = -1 and whofore = 2)) and convoy = ?"+limit)
1682
1683 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1684 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1685 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1686 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1687 stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1688 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1689 stmtFindFile = preparetodie(db, "select fileid from files where url = ? and local = 1")
1690 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, local, content) values (?, ?, ?, ?, ?, ?)")
1691 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey, options from users where username = ?")
1692 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1693 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1694 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1695 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1696 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1697 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zomain' or wherefore = 'zord' or wherefore = 'zilence')")
1698 stmtFindZonk = preparetodie(db, "select zonkerid from zonkers where userid = ? and name = ? and wherefore = 'zonk'")
1699 stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'")
1700 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1701 stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
1702 stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor) values (?, ?, ?)")
1703 stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ?")
1704 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")
1705 stmtUpdateFlags = preparetodie(db, "update honks set flags = flags | ? where xid = ?")
1706 stmtClearFlags = preparetodie(db, "update honks set flags = flags & ~ ? where xid = ?")
1707}
1708
1709func ElaborateUnitTests() {
1710}
1711
1712func main() {
1713 cmd := "run"
1714 if len(os.Args) > 1 {
1715 cmd = os.Args[1]
1716 }
1717 switch cmd {
1718 case "init":
1719 initdb()
1720 case "upgrade":
1721 upgradedb()
1722 }
1723 db := opendatabase()
1724 dbversion := 0
1725 getconfig("dbversion", &dbversion)
1726 if dbversion != myVersion {
1727 log.Fatal("incorrect database version. run upgrade.")
1728 }
1729 getconfig("servermsg", &serverMsg)
1730 getconfig("servername", &serverName)
1731 getconfig("usersep", &userSep)
1732 getconfig("honksep", &honkSep)
1733 getconfig("dnf", &donotfedafterdark)
1734 prepareStatements(db)
1735 switch cmd {
1736 case "adduser":
1737 adduser()
1738 case "cleanup":
1739 arg := "30"
1740 if len(os.Args) > 2 {
1741 arg = os.Args[2]
1742 }
1743 cleanupdb(arg)
1744 case "ping":
1745 if len(os.Args) < 4 {
1746 fmt.Printf("usage: honk ping from to\n")
1747 return
1748 }
1749 name := os.Args[2]
1750 targ := os.Args[3]
1751 user, err := butwhatabout(name)
1752 if err != nil {
1753 log.Printf("unknown user")
1754 return
1755 }
1756 ping(user, targ)
1757 case "peep":
1758 peeppeep()
1759 case "run":
1760 serve()
1761 case "test":
1762 ElaborateUnitTests()
1763 default:
1764 log.Fatal("unknown command")
1765 }
1766}