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