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