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