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