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["InReplyTo"] = r.FormValue("rid")
1093 templinfo["Noise"] = r.FormValue("noise")
1094 templinfo["ServerMessage"] = "honk preview"
1095 err := readviews.Execute(w, "honkpage.html", templinfo)
1096 if err != nil {
1097 log.Print(err)
1098 }
1099 return
1100 }
1101 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, honk.Honker, xid, rid,
1102 dt.Format(dbtimeformat), "", aud, honk.Noise, convoy, whofore, "html", honk.Precis, honk.Oonker)
1103 if err != nil {
1104 log.Printf("error saving honk: %s", err)
1105 http.Error(w, "something bad happened while saving", http.StatusInternalServerError)
1106 return
1107 }
1108 honk.ID, _ = res.LastInsertId()
1109 for _, d := range honk.Donks {
1110 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
1111 if err != nil {
1112 log.Printf("err saving donk: %s", err)
1113 http.Error(w, "something bad happened while saving", http.StatusInternalServerError)
1114 return
1115 }
1116 }
1117
1118 go honkworldwide(user, &honk)
1119
1120 http.Redirect(w, r, xid, http.StatusSeeOther)
1121}
1122
1123func showhonkers(w http.ResponseWriter, r *http.Request) {
1124 userinfo := login.GetUserInfo(r)
1125 templinfo := getInfo(r)
1126 templinfo["Honkers"] = gethonkers(userinfo.UserID)
1127 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
1128 err := readviews.Execute(w, "honkers.html", templinfo)
1129 if err != nil {
1130 log.Print(err)
1131 }
1132}
1133
1134func showcombos(w http.ResponseWriter, r *http.Request) {
1135 userinfo := login.GetUserInfo(r)
1136 templinfo := getInfo(r)
1137 honkers := gethonkers(userinfo.UserID)
1138 var combos []string
1139 for _, h := range honkers {
1140 combos = append(combos, h.Combos...)
1141 }
1142 for i, c := range combos {
1143 if c == "-" {
1144 combos[i] = ""
1145 }
1146 }
1147 combos = oneofakind(combos)
1148 sort.Strings(combos)
1149 templinfo["Combos"] = combos
1150 err := readviews.Execute(w, "combos.html", templinfo)
1151 if err != nil {
1152 log.Print(err)
1153 }
1154}
1155
1156func savehonker(w http.ResponseWriter, r *http.Request) {
1157 u := login.GetUserInfo(r)
1158 name := r.FormValue("name")
1159 url := r.FormValue("url")
1160 peep := r.FormValue("peep")
1161 combos := r.FormValue("combos")
1162 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1163
1164 if honkerid > 0 {
1165 goodbye := r.FormValue("goodbye")
1166 if goodbye == "F" {
1167 db := opendatabase()
1168 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1169 honkerid, u.UserID)
1170 var xid string
1171 err := row.Scan(&xid)
1172 if err != nil {
1173 log.Printf("can't get honker xid: %s", err)
1174 return
1175 }
1176 log.Printf("unsubscribing from %s", xid)
1177 user, _ := butwhatabout(u.Username)
1178 go itakeitallback(user, xid)
1179 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1180 if err != nil {
1181 log.Printf("error updating honker: %s", err)
1182 return
1183 }
1184
1185 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1186 return
1187 }
1188 combos = " " + strings.TrimSpace(combos) + " "
1189 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1190 if err != nil {
1191 log.Printf("update honker err: %s", err)
1192 return
1193 }
1194 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1195 }
1196
1197 flavor := "presub"
1198 if peep == "peep" {
1199 flavor = "peep"
1200 }
1201 url = investigate(url)
1202 if url == "" {
1203 return
1204 }
1205 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1206 if err != nil {
1207 log.Print(err)
1208 return
1209 }
1210 if flavor == "presub" {
1211 user, _ := butwhatabout(u.Username)
1212 go subsub(user, url)
1213 }
1214 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1215}
1216
1217type Zonker struct {
1218 ID int64
1219 Name string
1220 Wherefore string
1221}
1222
1223func zonkzone(w http.ResponseWriter, r *http.Request) {
1224 userinfo := login.GetUserInfo(r)
1225 rows, err := stmtGetZonkers.Query(userinfo.UserID)
1226 if err != nil {
1227 log.Printf("err: %s", err)
1228 return
1229 }
1230 defer rows.Close()
1231 var zonkers []Zonker
1232 for rows.Next() {
1233 var z Zonker
1234 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1235 zonkers = append(zonkers, z)
1236 }
1237 sort.Slice(zonkers, func(i, j int) bool {
1238 w1 := zonkers[i].Wherefore
1239 w2 := zonkers[j].Wherefore
1240 if w1 == w2 {
1241 return zonkers[i].Name < zonkers[j].Name
1242 }
1243 if w1 == "zonvoy" {
1244 w1 = "zzzzzzz"
1245 }
1246 if w2 == "zonvoy" {
1247 w2 = "zzzzzzz"
1248 }
1249 return w1 < w2
1250 })
1251
1252 templinfo := getInfo(r)
1253 templinfo["Zonkers"] = zonkers
1254 templinfo["ZonkCSRF"] = login.GetCSRF("zonkzonk", r)
1255 err = readviews.Execute(w, "zonkers.html", templinfo)
1256 if err != nil {
1257 log.Print(err)
1258 }
1259}
1260
1261func zonkzonk(w http.ResponseWriter, r *http.Request) {
1262 userinfo := login.GetUserInfo(r)
1263 itsok := r.FormValue("itsok")
1264 if itsok == "iforgiveyou" {
1265 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1266 db := opendatabase()
1267 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1268 userinfo.UserID, zonkerid)
1269 bitethethumbs()
1270 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1271 return
1272 }
1273 wherefore := r.FormValue("wherefore")
1274 name := r.FormValue("name")
1275 if name == "" {
1276 return
1277 }
1278 switch wherefore {
1279 case "zonker":
1280 case "zomain":
1281 case "zonvoy":
1282 case "zord":
1283 case "zilence":
1284 default:
1285 return
1286 }
1287 db := opendatabase()
1288 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1289 userinfo.UserID, name, wherefore)
1290 if wherefore == "zonker" || wherefore == "zomain" || wherefore == "zord" || wherefore == "zilence" {
1291 bitethethumbs()
1292 }
1293
1294 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1295}
1296
1297func accountpage(w http.ResponseWriter, r *http.Request) {
1298 u := login.GetUserInfo(r)
1299 user, _ := butwhatabout(u.Username)
1300 templinfo := getInfo(r)
1301 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1302 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1303 templinfo["User"] = user
1304 err := readviews.Execute(w, "account.html", templinfo)
1305 if err != nil {
1306 log.Print(err)
1307 }
1308}
1309
1310func dochpass(w http.ResponseWriter, r *http.Request) {
1311 err := login.ChangePassword(w, r)
1312 if err != nil {
1313 log.Printf("error changing password: %s", err)
1314 }
1315 http.Redirect(w, r, "/account", http.StatusSeeOther)
1316}
1317
1318func fingerlicker(w http.ResponseWriter, r *http.Request) {
1319 orig := r.FormValue("resource")
1320
1321 log.Printf("finger lick: %s", orig)
1322
1323 if strings.HasPrefix(orig, "acct:") {
1324 orig = orig[5:]
1325 }
1326
1327 name := orig
1328 idx := strings.LastIndexByte(name, '/')
1329 if idx != -1 {
1330 name = name[idx+1:]
1331 if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
1332 log.Printf("foreign request rejected")
1333 name = ""
1334 }
1335 } else {
1336 idx = strings.IndexByte(name, '@')
1337 if idx != -1 {
1338 name = name[:idx]
1339 if name+"@"+serverName != orig {
1340 log.Printf("foreign request rejected")
1341 name = ""
1342 }
1343 }
1344 }
1345 user, err := butwhatabout(name)
1346 if err != nil {
1347 http.NotFound(w, r)
1348 return
1349 }
1350
1351 j := junk.New()
1352 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1353 j["aliases"] = []string{user.URL}
1354 var links []junk.Junk
1355 l := junk.New()
1356 l["rel"] = "self"
1357 l["type"] = `application/activity+json`
1358 l["href"] = user.URL
1359 links = append(links, l)
1360 j["links"] = links
1361
1362 w.Header().Set("Cache-Control", "max-age=3600")
1363 w.Header().Set("Content-Type", "application/jrd+json")
1364 j.Write(w)
1365}
1366
1367func somedays() string {
1368 secs := 432000 + notrand.Int63n(432000)
1369 return fmt.Sprintf("%d", secs)
1370}
1371
1372func avatate(w http.ResponseWriter, r *http.Request) {
1373 n := r.FormValue("a")
1374 a := avatar(n)
1375 w.Header().Set("Cache-Control", "max-age="+somedays())
1376 w.Write(a)
1377}
1378
1379func servecss(w http.ResponseWriter, r *http.Request) {
1380 w.Header().Set("Cache-Control", "max-age=7776000")
1381 http.ServeFile(w, r, "views"+r.URL.Path)
1382}
1383func servehtml(w http.ResponseWriter, r *http.Request) {
1384 templinfo := getInfo(r)
1385 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1386 if err != nil {
1387 log.Print(err)
1388 }
1389}
1390func serveemu(w http.ResponseWriter, r *http.Request) {
1391 xid := mux.Vars(r)["xid"]
1392 w.Header().Set("Cache-Control", "max-age="+somedays())
1393 http.ServeFile(w, r, "emus/"+xid)
1394}
1395func servememe(w http.ResponseWriter, r *http.Request) {
1396 xid := mux.Vars(r)["xid"]
1397 w.Header().Set("Cache-Control", "max-age="+somedays())
1398 http.ServeFile(w, r, "memes/"+xid)
1399}
1400
1401func servefile(w http.ResponseWriter, r *http.Request) {
1402 xid := mux.Vars(r)["xid"]
1403 row := stmtFileData.QueryRow(xid)
1404 var media string
1405 var data []byte
1406 err := row.Scan(&media, &data)
1407 if err != nil {
1408 log.Printf("error loading file: %s", err)
1409 http.NotFound(w, r)
1410 return
1411 }
1412 w.Header().Set("Content-Type", media)
1413 w.Header().Set("X-Content-Type-Options", "nosniff")
1414 w.Header().Set("Cache-Control", "max-age="+somedays())
1415 w.Write(data)
1416}
1417
1418func nomoroboto(w http.ResponseWriter, r *http.Request) {
1419 io.WriteString(w, "User-agent: *\n")
1420 io.WriteString(w, "Disallow: /a\n")
1421 io.WriteString(w, "Disallow: /d\n")
1422 io.WriteString(w, "Disallow: /meme\n")
1423 for _, u := range allusers() {
1424 fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
1425 }
1426}
1427
1428func serve() {
1429 db := opendatabase()
1430 login.Init(db)
1431
1432 listener, err := openListener()
1433 if err != nil {
1434 log.Fatal(err)
1435 }
1436 go redeliverator()
1437
1438 debug := false
1439 getconfig("debug", &debug)
1440 readviews = templates.Load(debug,
1441 "views/honkpage.html",
1442 "views/honkers.html",
1443 "views/zonkers.html",
1444 "views/combos.html",
1445 "views/honkform.html",
1446 "views/honk.html",
1447 "views/account.html",
1448 "views/about.html",
1449 "views/funzone.html",
1450 "views/login.html",
1451 "views/xzone.html",
1452 "views/header.html",
1453 )
1454 if !debug {
1455 s := "views/style.css"
1456 savedstyleparams[s] = getstyleparam(s)
1457 s = "views/local.css"
1458 savedstyleparams[s] = getstyleparam(s)
1459 }
1460
1461 bitethethumbs()
1462
1463 mux := mux.NewRouter()
1464 mux.Use(login.Checker)
1465
1466 posters := mux.Methods("POST").Subrouter()
1467 getters := mux.Methods("GET").Subrouter()
1468
1469 getters.HandleFunc("/", homepage)
1470 getters.HandleFunc("/front", homepage)
1471 getters.HandleFunc("/robots.txt", nomoroboto)
1472 getters.HandleFunc("/rss", showrss)
1473 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}", showuser)
1474 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/"+honkSep+"/{xid:[[:alnum:]]+}", showhonk)
1475 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/rss", showrss)
1476 posters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/inbox", inbox)
1477 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/outbox", outbox)
1478 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/followers", emptiness)
1479 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/following", emptiness)
1480 getters.HandleFunc("/a", avatate)
1481 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1482 getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
1483 getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
1484 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1485
1486 getters.HandleFunc("/style.css", servecss)
1487 getters.HandleFunc("/local.css", servecss)
1488 getters.HandleFunc("/about", servehtml)
1489 getters.HandleFunc("/login", servehtml)
1490 posters.HandleFunc("/dologin", login.LoginFunc)
1491 getters.HandleFunc("/logout", login.LogoutFunc)
1492
1493 loggedin := mux.NewRoute().Subrouter()
1494 loggedin.Use(login.Required)
1495 loggedin.HandleFunc("/account", accountpage)
1496 loggedin.HandleFunc("/funzone", showfunzone)
1497 loggedin.HandleFunc("/chpass", dochpass)
1498 loggedin.HandleFunc("/atme", homepage)
1499 loggedin.HandleFunc("/zonkzone", zonkzone)
1500 loggedin.HandleFunc("/xzone", xzone)
1501 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1502 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1503 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1504 loggedin.Handle("/zonkzonk", login.CSRFWrap("zonkzonk", http.HandlerFunc(zonkzonk)))
1505 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1506 loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
1507 loggedin.HandleFunc("/honkers", showhonkers)
1508 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1509 loggedin.HandleFunc("/h", showhonker)
1510 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1511 loggedin.HandleFunc("/c", showcombos)
1512 loggedin.HandleFunc("/t", showconvoy)
1513 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1514
1515 err = http.Serve(listener, mux)
1516 if err != nil {
1517 log.Fatal(err)
1518 }
1519}
1520
1521func cleanupdb(arg string) {
1522 db := opendatabase()
1523 days, err := strconv.Atoi(arg)
1524 if err != nil {
1525 honker := arg
1526 expdate := time.Now().UTC().Add(-3 * 24 * time.Hour).Format(dbtimeformat)
1527 doordie(db, "delete from donks where honkid in (select honkid from honks where dt < ? and whofore = 0 and honker = ?)", expdate, honker)
1528 doordie(db, "delete from honks where dt < ? and whofore = 0 and honker = ?", expdate, honker)
1529 } else {
1530 expdate := time.Now().UTC().Add(-time.Duration(days) * 24 * time.Hour).Format(dbtimeformat)
1531 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)
1532 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)
1533 }
1534 doordie(db, "delete from files where fileid not in (select fileid from donks)")
1535 for _, u := range allusers() {
1536 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)
1537 }
1538}
1539
1540var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1541var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1542var stmtHonksForUser, stmtHonksForMe, stmtSaveDub, stmtHonksByXonker *sql.Stmt
1543var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1544var stmtFindZonk, stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1545var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1546var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1547var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker *sql.Stmt
1548
1549func preparetodie(db *sql.DB, s string) *sql.Stmt {
1550 stmt, err := db.Prepare(s)
1551 if err != nil {
1552 log.Fatalf("error %s: %s", err, s)
1553 }
1554 return stmt
1555}
1556
1557func prepareStatements(db *sql.DB) {
1558 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")
1559 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1560 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1561 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1562 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1563 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1564
1565 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 "
1566 limit := " order by honkid desc limit 250"
1567 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1568 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1569 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
1570 stmtUserHonks = preparetodie(db, selecthonks+"where (whofore = 2 or whofore = ?) and username = ? and dt > ?"+limit)
1571 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)
1572 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1573 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1574 stmtHonksByXonker = preparetodie(db, selecthonks+" where honks.userid = ? and (honker = ? or oonker = ?)"+butnotthose+limit)
1575 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1576 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or (? = -1 and whofore = 2)) and convoy = ?"+limit)
1577
1578 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1579 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1580 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1581 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1582 stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1583 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1584 stmtFindFile = preparetodie(db, "select fileid from files where url = ? and local = 1")
1585 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, local, content) values (?, ?, ?, ?, ?, ?)")
1586 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey, options from users where username = ?")
1587 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1588 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1589 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1590 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1591 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1592 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zomain' or wherefore = 'zord' or wherefore = 'zilence')")
1593 stmtFindZonk = preparetodie(db, "select zonkerid from zonkers where userid = ? and name = ? and wherefore = 'zonk'")
1594 stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'")
1595 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1596 stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
1597 stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor) values (?, ?, ?)")
1598 stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ?")
1599 stmtRecentHonkers = preparetodie(db, "select distinct(honker) from honks where userid = ? order by honkid desc limit 100")
1600}
1601
1602func ElaborateUnitTests() {
1603}
1604
1605func main() {
1606 cmd := "run"
1607 if len(os.Args) > 1 {
1608 cmd = os.Args[1]
1609 }
1610 switch cmd {
1611 case "init":
1612 initdb()
1613 case "upgrade":
1614 upgradedb()
1615 }
1616 db := opendatabase()
1617 dbversion := 0
1618 getconfig("dbversion", &dbversion)
1619 if dbversion != myVersion {
1620 log.Fatal("incorrect database version. run upgrade.")
1621 }
1622 getconfig("servermsg", &serverMsg)
1623 getconfig("servername", &serverName)
1624 getconfig("usersep", &userSep)
1625 getconfig("honksep", &honkSep)
1626 getconfig("dnf", &donotfedafterdark)
1627 prepareStatements(db)
1628 switch cmd {
1629 case "adduser":
1630 adduser()
1631 case "cleanup":
1632 arg := "30"
1633 if len(os.Args) > 2 {
1634 arg = os.Args[2]
1635 }
1636 cleanupdb(arg)
1637 case "ping":
1638 if len(os.Args) < 4 {
1639 fmt.Printf("usage: honk ping from to\n")
1640 return
1641 }
1642 name := os.Args[2]
1643 targ := os.Args[3]
1644 user, err := butwhatabout(name)
1645 if err != nil {
1646 log.Printf("unknown user")
1647 return
1648 }
1649 ping(user, targ)
1650 case "peep":
1651 peeppeep()
1652 case "run":
1653 serve()
1654 case "test":
1655 ElaborateUnitTests()
1656 default:
1657 log.Fatal("unknown command")
1658 }
1659}