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