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