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