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