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