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