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