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 "Announce":
464 fd, _ := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
465 j.Write(fd)
466 io.WriteString(fd, "\n")
467 fd.Close()
468 log.Printf("an announcement has been undone")
469 xid, _ := obj.GetString("object")
470 log.Printf("undo announce: %s", xid)
471 case "Like":
472 default:
473 log.Printf("unknown undo: %s", what)
474 }
475 }
476 default:
477 go consumeactivity(user, j, origin)
478 }
479}
480
481func ximport(w http.ResponseWriter, r *http.Request) {
482 xid := r.FormValue("xid")
483 p := investigate(xid)
484 if p != nil {
485 xid = p.XID
486 }
487 j, err := GetJunk(xid)
488 if err != nil {
489 http.Error(w, "error getting external object", http.StatusInternalServerError)
490 log.Printf("error getting external object: %s", err)
491 return
492 }
493 log.Printf("importing %s", xid)
494 u := login.GetUserInfo(r)
495 user, _ := butwhatabout(u.Username)
496
497 what, _ := j.GetString("type")
498 if isactor(what) {
499 outbox, _ := j.GetString("outbox")
500 gimmexonks(user, outbox)
501 http.Redirect(w, r, "/h?xid="+url.QueryEscape(xid), http.StatusSeeOther)
502 return
503 }
504 xonk := xonkxonk(user, j, originate(xid))
505 convoy := ""
506 if xonk != nil {
507 convoy = xonk.Convoy
508 savexonk(user, xonk)
509 }
510 http.Redirect(w, r, "/t?c="+url.QueryEscape(convoy), http.StatusSeeOther)
511}
512
513func xzone(w http.ResponseWriter, r *http.Request) {
514 u := login.GetUserInfo(r)
515 rows, err := stmtRecentHonkers.Query(u.UserID, u.UserID)
516 if err != nil {
517 log.Printf("query err: %s", err)
518 return
519 }
520 defer rows.Close()
521 var honkers []Honker
522 for rows.Next() {
523 var xid string
524 rows.Scan(&xid)
525 honkers = append(honkers, Honker{XID: xid})
526 }
527 rows.Close()
528 for i, _ := range honkers {
529 _, honkers[i].Handle = handles(honkers[i].XID)
530 }
531 templinfo := getInfo(r)
532 templinfo["XCSRF"] = login.GetCSRF("ximport", r)
533 templinfo["Honkers"] = honkers
534 err = readviews.Execute(w, "xzone.html", templinfo)
535 if err != nil {
536 log.Print(err)
537 }
538}
539
540func outbox(w http.ResponseWriter, r *http.Request) {
541 name := mux.Vars(r)["name"]
542 user, err := butwhatabout(name)
543 if err != nil {
544 http.NotFound(w, r)
545 return
546 }
547 if stealthed(r) {
548 http.NotFound(w, r)
549 return
550 }
551
552 honks := gethonksbyuser(name, false)
553
554 var jonks []junk.Junk
555 for _, h := range honks {
556 j, _ := jonkjonk(user, h)
557 jonks = append(jonks, j)
558 }
559
560 j := junk.New()
561 j["@context"] = itiswhatitis
562 j["id"] = user.URL + "/outbox"
563 j["type"] = "OrderedCollection"
564 j["totalItems"] = len(jonks)
565 j["orderedItems"] = jonks
566
567 w.Header().Set("Content-Type", theonetruename)
568 j.Write(w)
569}
570
571func emptiness(w http.ResponseWriter, r *http.Request) {
572 name := mux.Vars(r)["name"]
573 user, err := butwhatabout(name)
574 if err != nil {
575 http.NotFound(w, r)
576 return
577 }
578 colname := "/followers"
579 if strings.HasSuffix(r.URL.Path, "/following") {
580 colname = "/following"
581 }
582 j := junk.New()
583 j["@context"] = itiswhatitis
584 j["id"] = user.URL + colname
585 j["type"] = "OrderedCollection"
586 j["totalItems"] = 0
587 j["orderedItems"] = []junk.Junk{}
588
589 w.Header().Set("Content-Type", theonetruename)
590 j.Write(w)
591}
592
593func showuser(w http.ResponseWriter, r *http.Request) {
594 name := mux.Vars(r)["name"]
595 user, err := butwhatabout(name)
596 if err != nil {
597 log.Printf("user not found %s: %s", name, err)
598 http.NotFound(w, r)
599 return
600 }
601 if friendorfoe(r.Header.Get("Accept")) {
602 j := asjonker(user)
603 w.Header().Set("Content-Type", theonetruename)
604 j.Write(w)
605 return
606 }
607 u := login.GetUserInfo(r)
608 honks := gethonksbyuser(name, u != nil && u.Username == name)
609 honkpage(w, r, u, user, honks, "")
610}
611
612func showhonker(w http.ResponseWriter, r *http.Request) {
613 u := login.GetUserInfo(r)
614 name := mux.Vars(r)["name"]
615 var honks []*Honk
616 if name == "" {
617 name = r.FormValue("xid")
618 honks = gethonksbyxonker(u.UserID, name)
619 } else {
620 honks = gethonksbyhonker(u.UserID, name)
621 }
622 name = html.EscapeString(name)
623 msg := fmt.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>`, name, name)
624 honkpage(w, r, u, nil, honks, template.HTML(msg))
625}
626
627func showcombo(w http.ResponseWriter, r *http.Request) {
628 name := mux.Vars(r)["name"]
629 u := login.GetUserInfo(r)
630 honks := gethonksbycombo(u.UserID, name)
631 honks = osmosis(honks, u.UserID)
632 honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks by combo: "+name)))
633}
634func showconvoy(w http.ResponseWriter, r *http.Request) {
635 c := r.FormValue("c")
636 u := login.GetUserInfo(r)
637 honks := gethonksbyconvoy(u.UserID, c)
638 honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks in convoy: "+c)))
639}
640func showontology(w http.ResponseWriter, r *http.Request) {
641 name := mux.Vars(r)["name"]
642 u := login.GetUserInfo(r)
643 var userid int64 = -1
644 if u != nil {
645 userid = u.UserID
646 }
647 honks := gethonksbyontology(userid, "#"+name)
648 honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks by ontology: "+name)))
649}
650
651func showhonk(w http.ResponseWriter, r *http.Request) {
652 name := mux.Vars(r)["name"]
653 user, err := butwhatabout(name)
654 if err != nil {
655 http.NotFound(w, r)
656 return
657 }
658 if stealthed(r) {
659 http.NotFound(w, r)
660 return
661 }
662
663 xid := fmt.Sprintf("https://%s%s", serverName, r.URL.Path)
664 honk := getxonk(user.ID, xid)
665 if honk == nil {
666 http.NotFound(w, r)
667 return
668 }
669 u := login.GetUserInfo(r)
670 if u != nil && u.UserID != user.ID {
671 u = nil
672 }
673 if !honk.Public {
674 if u == nil {
675 http.NotFound(w, r)
676 return
677
678 }
679 honkpage(w, r, u, nil, []*Honk{honk}, "one honk maybe more")
680 return
681 }
682 rawhonks := gethonksbyconvoy(honk.UserID, honk.Convoy)
683 if friendorfoe(r.Header.Get("Accept")) {
684 for _, h := range rawhonks {
685 if h.RID == honk.XID && h.Public && (h.Whofore == 2 || h.IsAcked()) {
686 honk.Replies = append(honk.Replies, h)
687 }
688 }
689 donksforhonks([]*Honk{honk})
690 _, j := jonkjonk(user, honk)
691 j["@context"] = itiswhatitis
692 w.Header().Set("Content-Type", theonetruename)
693 j.Write(w)
694 return
695 }
696 var honks []*Honk
697 for _, h := range rawhonks {
698 if h.Public && (h.Whofore == 2 || h.IsAcked()) {
699 honks = append(honks, h)
700 }
701 }
702
703 honkpage(w, r, u, nil, honks, "one honk maybe more")
704}
705
706func honkpage(w http.ResponseWriter, r *http.Request, u *login.UserInfo, user *WhatAbout,
707 honks []*Honk, infomsg template.HTML) {
708 templinfo := getInfo(r)
709 var userid int64 = -1
710 if u != nil {
711 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
712 userid = u.UserID
713 }
714 if u == nil {
715 w.Header().Set("Cache-Control", "max-age=60")
716 }
717 reverbolate(userid, honks)
718 if user != nil {
719 filt := htfilter.New()
720 templinfo["Name"] = user.Name
721 whatabout := user.About
722 whatabout = obfusbreak(user.About)
723 templinfo["WhatAbout"], _ = filt.String(whatabout)
724 }
725 templinfo["Honks"] = honks
726 templinfo["ServerMessage"] = infomsg
727 err := readviews.Execute(w, "honkpage.html", templinfo)
728 if err != nil {
729 log.Print(err)
730 }
731}
732
733func saveuser(w http.ResponseWriter, r *http.Request) {
734 whatabout := r.FormValue("whatabout")
735 u := login.GetUserInfo(r)
736 db := opendatabase()
737 options := ""
738 if r.FormValue("skinny") == "skinny" {
739 options += " skinny "
740 }
741 _, err := db.Exec("update users set about = ?, options = ? where username = ?", whatabout, options, u.Username)
742 if err != nil {
743 log.Printf("error bouting what: %s", err)
744 }
745
746 http.Redirect(w, r, "/account", http.StatusSeeOther)
747}
748
749func gethonkers(userid int64) []*Honker {
750 rows, err := stmtHonkers.Query(userid)
751 if err != nil {
752 log.Printf("error querying honkers: %s", err)
753 return nil
754 }
755 defer rows.Close()
756 var honkers []*Honker
757 for rows.Next() {
758 var f Honker
759 var combos string
760 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor, &combos)
761 f.Combos = strings.Split(strings.TrimSpace(combos), " ")
762 if err != nil {
763 log.Printf("error scanning honker: %s", err)
764 return nil
765 }
766 honkers = append(honkers, &f)
767 }
768 return honkers
769}
770
771func getdubs(userid int64) []*Honker {
772 rows, err := stmtDubbers.Query(userid)
773 if err != nil {
774 log.Printf("error querying dubs: %s", err)
775 return nil
776 }
777 defer rows.Close()
778 var honkers []*Honker
779 for rows.Next() {
780 var f Honker
781 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
782 if err != nil {
783 log.Printf("error scanning honker: %s", err)
784 return nil
785 }
786 honkers = append(honkers, &f)
787 }
788 return honkers
789}
790
791func allusers() []login.UserInfo {
792 var users []login.UserInfo
793 rows, _ := opendatabase().Query("select userid, username from users")
794 defer rows.Close()
795 for rows.Next() {
796 var u login.UserInfo
797 rows.Scan(&u.UserID, &u.Username)
798 users = append(users, u)
799 }
800 return users
801}
802
803func getxonk(userid int64, xid string) *Honk {
804 h := new(Honk)
805 var dt, aud string
806 row := stmtOneXonk.QueryRow(userid, xid)
807 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
808 &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags)
809 if err != nil {
810 if err != sql.ErrNoRows {
811 log.Printf("error scanning xonk: %s", err)
812 }
813 return nil
814 }
815 h.Date, _ = time.Parse(dbtimeformat, dt)
816 h.Audience = strings.Split(aud, " ")
817 h.Public = !keepitquiet(h.Audience)
818 return h
819}
820
821func getbonk(userid int64, xid string) *Honk {
822 h := new(Honk)
823 var dt, aud string
824 row := stmtOneXonk.QueryRow(userid, xid)
825 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
826 &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags)
827 if err != nil {
828 if err != sql.ErrNoRows {
829 log.Printf("error scanning xonk: %s", err)
830 }
831 return nil
832 }
833 h.Date, _ = time.Parse(dbtimeformat, dt)
834 h.Audience = strings.Split(aud, " ")
835 h.Public = !keepitquiet(h.Audience)
836 return h
837}
838
839func getpublichonks() []*Honk {
840 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
841 rows, err := stmtPublicHonks.Query(dt)
842 return getsomehonks(rows, err)
843}
844func gethonksbyuser(name string, includeprivate bool) []*Honk {
845 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
846 whofore := 2
847 if includeprivate {
848 whofore = 3
849 }
850 rows, err := stmtUserHonks.Query(whofore, name, dt)
851 return getsomehonks(rows, err)
852}
853func gethonksforuser(userid int64) []*Honk {
854 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
855 rows, err := stmtHonksForUser.Query(userid, dt, userid, userid)
856 return getsomehonks(rows, err)
857}
858func gethonksforme(userid int64) []*Honk {
859 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
860 rows, err := stmtHonksForMe.Query(userid, dt, userid)
861 return getsomehonks(rows, err)
862}
863func gethonksbyhonker(userid int64, honker string) []*Honk {
864 rows, err := stmtHonksByHonker.Query(userid, honker, userid)
865 return getsomehonks(rows, err)
866}
867func gethonksbyxonker(userid int64, xonker string) []*Honk {
868 rows, err := stmtHonksByXonker.Query(userid, xonker, xonker, userid)
869 return getsomehonks(rows, err)
870}
871func gethonksbycombo(userid int64, combo string) []*Honk {
872 combo = "% " + combo + " %"
873 rows, err := stmtHonksByCombo.Query(userid, combo, userid)
874 return getsomehonks(rows, err)
875}
876func gethonksbyconvoy(userid int64, convoy string) []*Honk {
877 rows, err := stmtHonksByConvoy.Query(userid, userid, convoy)
878 honks := getsomehonks(rows, err)
879 for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
880 honks[i], honks[j] = honks[j], honks[i]
881 }
882 return honks
883}
884func gethonksbyontology(userid int64, name string) []*Honk {
885 rows, err := stmtHonksByOntology.Query(name, userid, userid)
886 honks := getsomehonks(rows, err)
887 return honks
888}
889
890func getsomehonks(rows *sql.Rows, err error) []*Honk {
891 if err != nil {
892 log.Printf("error querying honks: %s", err)
893 return nil
894 }
895 defer rows.Close()
896 var honks []*Honk
897 for rows.Next() {
898 var h Honk
899 var dt, aud string
900 err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker,
901 &h.XID, &h.RID, &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags)
902 if err != nil {
903 log.Printf("error scanning honks: %s", err)
904 return nil
905 }
906 h.Date, _ = time.Parse(dbtimeformat, dt)
907 h.Audience = strings.Split(aud, " ")
908 h.Public = !keepitquiet(h.Audience)
909 honks = append(honks, &h)
910 }
911 rows.Close()
912 donksforhonks(honks)
913 return honks
914}
915
916func donksforhonks(honks []*Honk) {
917 db := opendatabase()
918 var ids []string
919 hmap := make(map[int64]*Honk)
920 for _, h := range honks {
921 ids = append(ids, fmt.Sprintf("%d", h.ID))
922 hmap[h.ID] = h
923 }
924 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, ","))
925 rows, err := db.Query(q)
926 if err != nil {
927 log.Printf("error querying donks: %s", err)
928 return
929 }
930 defer rows.Close()
931 for rows.Next() {
932 var hid int64
933 var d Donk
934 err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media, &d.Local)
935 if err != nil {
936 log.Printf("error scanning donk: %s", err)
937 continue
938 }
939 h := hmap[hid]
940 h.Donks = append(h.Donks, &d)
941 }
942}
943
944func savebonk(w http.ResponseWriter, r *http.Request) {
945 xid := r.FormValue("xid")
946 userinfo := login.GetUserInfo(r)
947 user, _ := butwhatabout(userinfo.Username)
948
949 log.Printf("bonking %s", xid)
950
951 xonk := getxonk(userinfo.UserID, xid)
952 if xonk == nil {
953 return
954 }
955 if !xonk.Public {
956 return
957 }
958 donksforhonks([]*Honk{xonk})
959
960 _, err := stmtUpdateFlags.Exec(flagIsBonked, xonk.ID)
961 if err != nil {
962 log.Printf("error acking bonk: %s", err)
963 }
964
965 oonker := xonk.Oonker
966 if oonker == "" {
967 oonker = xonk.Honker
968 }
969 dt := time.Now().UTC()
970 bonk := Honk{
971 UserID: userinfo.UserID,
972 Username: userinfo.Username,
973 What: "bonk",
974 Honker: user.URL,
975 XID: xonk.XID,
976 Date: dt,
977 Donks: xonk.Donks,
978 Convoy: xonk.Convoy,
979 Audience: []string{thewholeworld, oonker},
980 Public: true,
981 }
982
983 aud := strings.Join(bonk.Audience, " ")
984 whofore := 2
985 onts := ontologies(xonk.Noise)
986 res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", bonk.Honker, xid, "",
987 dt.Format(dbtimeformat), "", aud, xonk.Noise, xonk.Convoy, whofore, "html",
988 xonk.Precis, oonker, 0, strings.Join(onts, " "))
989 if err != nil {
990 log.Printf("error saving bonk: %s", err)
991 return
992 }
993 bonk.ID, _ = res.LastInsertId()
994 for _, d := range bonk.Donks {
995 _, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
996 if err != nil {
997 log.Printf("err saving donk: %s", err)
998 return
999 }
1000 }
1001 for _, o := range onts {
1002 _, err = stmtSaveOnts.Exec(strings.ToLower(o), bonk.ID)
1003 if err != nil {
1004 log.Printf("error saving ont: %s", err)
1005 }
1006 }
1007
1008 go honkworldwide(user, &bonk)
1009}
1010
1011func sendzonkofsorts(xonk *Honk, user *WhatAbout, what string) {
1012 zonk := Honk{
1013 What: what,
1014 XID: xonk.XID,
1015 Date: time.Now().UTC(),
1016 Audience: oneofakind(xonk.Audience),
1017 }
1018 zonk.Public = !keepitquiet(zonk.Audience)
1019
1020 log.Printf("announcing %sed honk: %s", what, xonk.XID)
1021 go honkworldwide(user, &zonk)
1022}
1023
1024func zonkit(w http.ResponseWriter, r *http.Request) {
1025 wherefore := r.FormValue("wherefore")
1026 what := r.FormValue("what")
1027 userinfo := login.GetUserInfo(r)
1028 user, _ := butwhatabout(userinfo.Username)
1029
1030 if wherefore == "ack" {
1031 xonk := getxonk(userinfo.UserID, what)
1032 if xonk != nil {
1033 _, err := stmtUpdateFlags.Exec(flagIsAcked, xonk.ID)
1034 if err != nil {
1035 log.Printf("error acking: %s", err)
1036 }
1037 sendzonkofsorts(xonk, user, "ack")
1038 }
1039 return
1040 }
1041
1042 if wherefore == "deack" {
1043 xonk := getxonk(userinfo.UserID, what)
1044 if xonk != nil {
1045 _, err := stmtClearFlags.Exec(flagIsAcked, xonk.ID)
1046 if err != nil {
1047 log.Printf("error deacking: %s", err)
1048 }
1049 sendzonkofsorts(xonk, user, "deack")
1050 }
1051 return
1052 }
1053
1054 if wherefore == "unbonk" {
1055 xonk := getbonk(userinfo.UserID, what)
1056 if xonk != nil {
1057 _, err := stmtClearFlags.Exec(flagIsBonked, xonk.ID)
1058 if err != nil {
1059 log.Printf("error unbonking: %s", err)
1060 }
1061 sendzonkofsorts(xonk, user, "unbonk")
1062 }
1063 return
1064 }
1065
1066 log.Printf("zonking %s %s", wherefore, what)
1067 if wherefore == "zonk" {
1068 xonk := getxonk(userinfo.UserID, what)
1069 if xonk != nil {
1070 _, err := stmtZonkDonks.Exec(xonk.ID)
1071 if err != nil {
1072 log.Printf("error zonking: %s", err)
1073 }
1074 _, err = stmtZonkIt.Exec(userinfo.UserID, what)
1075 if err != nil {
1076 log.Printf("error zonking: %s", err)
1077 }
1078 if xonk.Whofore == 2 || xonk.Whofore == 3 {
1079 sendzonkofsorts(xonk, user, "zonk")
1080 }
1081 }
1082 }
1083 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
1084 if err != nil {
1085 log.Printf("error saving zonker: %s", err)
1086 return
1087 }
1088}
1089
1090func savehonk(w http.ResponseWriter, r *http.Request) {
1091 rid := r.FormValue("rid")
1092 noise := r.FormValue("noise")
1093
1094 userinfo := login.GetUserInfo(r)
1095 user, _ := butwhatabout(userinfo.Username)
1096
1097 dt := time.Now().UTC()
1098 xid := fmt.Sprintf("%s/%s/%s", user.URL, honkSep, xfiltrate())
1099 what := "honk"
1100 if rid != "" {
1101 what = "tonk"
1102 }
1103 honk := Honk{
1104 UserID: userinfo.UserID,
1105 Username: userinfo.Username,
1106 What: "honk",
1107 Honker: user.URL,
1108 XID: xid,
1109 Date: dt,
1110 }
1111 if strings.HasPrefix(noise, "DZ:") {
1112 idx := strings.Index(noise, "\n")
1113 if idx == -1 {
1114 honk.Precis = noise
1115 noise = ""
1116 } else {
1117 honk.Precis = noise[:idx]
1118 noise = noise[idx+1:]
1119 }
1120 }
1121 noise = hooterize(noise)
1122 noise = strings.TrimSpace(noise)
1123 honk.Precis = strings.TrimSpace(honk.Precis)
1124
1125 var convoy string
1126 if rid != "" {
1127 xonk := getxonk(userinfo.UserID, rid)
1128 if xonk != nil {
1129 if xonk.Public {
1130 honk.Audience = append(honk.Audience, xonk.Audience...)
1131 }
1132 convoy = xonk.Convoy
1133 } else {
1134 xonkaud, c := whosthere(rid)
1135 honk.Audience = append(honk.Audience, xonkaud...)
1136 convoy = c
1137 }
1138 for i, a := range honk.Audience {
1139 if a == thewholeworld {
1140 honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
1141 break
1142 }
1143 }
1144 honk.RID = rid
1145 } else {
1146 honk.Audience = []string{thewholeworld}
1147 }
1148 if noise != "" && noise[0] == '@' {
1149 honk.Audience = append(grapevine(noise), honk.Audience...)
1150 } else {
1151 honk.Audience = append(honk.Audience, grapevine(noise)...)
1152 }
1153 if convoy == "" {
1154 convoy = "data:,electrichonkytonk-" + xfiltrate()
1155 }
1156 butnottooloud(honk.Audience)
1157 honk.Audience = oneofakind(honk.Audience)
1158 if len(honk.Audience) == 0 {
1159 log.Printf("honk to nowhere")
1160 http.Error(w, "honk to nowhere...", http.StatusNotFound)
1161 return
1162 }
1163 honk.Public = !keepitquiet(honk.Audience)
1164 noise = obfusbreak(noise)
1165 honk.Noise = noise
1166 honk.Convoy = convoy
1167
1168 donkxid := r.FormValue("donkxid")
1169 if donkxid == "" {
1170 file, filehdr, err := r.FormFile("donk")
1171 if err == nil {
1172 var buf bytes.Buffer
1173 io.Copy(&buf, file)
1174 file.Close()
1175 data := buf.Bytes()
1176 xid := xfiltrate()
1177 var media, name string
1178 img, err := image.Vacuum(&buf, image.Params{MaxWidth: 2048, MaxHeight: 2048})
1179 if err == nil {
1180 data = img.Data
1181 format := img.Format
1182 media = "image/" + format
1183 if format == "jpeg" {
1184 format = "jpg"
1185 }
1186 name = xid + "." + format
1187 xid = name
1188 } else {
1189 maxsize := 100000
1190 if len(data) > maxsize {
1191 log.Printf("bad image: %s too much text: %d", err, len(data))
1192 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1193 return
1194 }
1195 for i := 0; i < len(data); i++ {
1196 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
1197 log.Printf("bad image: %s not text: %d", err, data[i])
1198 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1199 return
1200 }
1201 }
1202 media = "text/plain"
1203 name = filehdr.Filename
1204 if name == "" {
1205 name = xid + ".txt"
1206 }
1207 xid += ".txt"
1208 }
1209 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1210 res, err := stmtSaveFile.Exec(xid, name, url, media, 1, data)
1211 if err != nil {
1212 log.Printf("unable to save image: %s", err)
1213 return
1214 }
1215 var d Donk
1216 d.FileID, _ = res.LastInsertId()
1217 d.XID = name
1218 d.Name = name
1219 d.Media = media
1220 d.URL = url
1221 d.Local = true
1222 honk.Donks = append(honk.Donks, &d)
1223 donkxid = d.XID
1224 }
1225 } else {
1226 xid := donkxid
1227 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1228 var donk Donk
1229 row := stmtFindFile.QueryRow(url)
1230 err := row.Scan(&donk.FileID)
1231 if err == nil {
1232 donk.XID = xid
1233 donk.Local = true
1234 donk.URL = url
1235 honk.Donks = append(honk.Donks, &donk)
1236 } else {
1237 log.Printf("can't find file: %s", xid)
1238 }
1239 }
1240 herd := herdofemus(honk.Noise)
1241 for _, e := range herd {
1242 donk := savedonk(e.ID, e.Name, "image/png", true)
1243 if donk != nil {
1244 donk.Name = e.Name
1245 honk.Donks = append(honk.Donks, donk)
1246 }
1247 }
1248 memetize(&honk)
1249
1250 aud := strings.Join(honk.Audience, " ")
1251 whofore := 2
1252 if !honk.Public {
1253 whofore = 3
1254 }
1255 if r.FormValue("preview") == "preview" {
1256 honks := []*Honk{&honk}
1257 reverbolate(userinfo.UserID, honks)
1258 templinfo := getInfo(r)
1259 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1260 templinfo["Honks"] = honks
1261 templinfo["InReplyTo"] = r.FormValue("rid")
1262 templinfo["Noise"] = r.FormValue("noise")
1263 templinfo["SavedFile"] = donkxid
1264 templinfo["ServerMessage"] = "honk preview"
1265 err := readviews.Execute(w, "honkpage.html", templinfo)
1266 if err != nil {
1267 log.Print(err)
1268 }
1269 return
1270 }
1271 onts := ontologies(honk.Noise)
1272 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, honk.Honker, xid, rid,
1273 dt.Format(dbtimeformat), "", aud, honk.Noise, convoy, whofore, "html",
1274 honk.Precis, honk.Oonker, 0, strings.Join(onts, " "))
1275 if err != nil {
1276 log.Printf("error saving honk: %s", err)
1277 http.Error(w, "something bad happened while saving", http.StatusInternalServerError)
1278 return
1279 }
1280 honk.ID, _ = res.LastInsertId()
1281 for _, d := range honk.Donks {
1282 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
1283 if err != nil {
1284 log.Printf("err saving donk: %s", err)
1285 http.Error(w, "something bad happened while saving", http.StatusInternalServerError)
1286 return
1287 }
1288 }
1289 for _, o := range onts {
1290 _, err = stmtSaveOnts.Exec(strings.ToLower(o), honk.ID)
1291 if err != nil {
1292 log.Printf("error saving ont: %s", err)
1293 }
1294 }
1295
1296 go honkworldwide(user, &honk)
1297
1298 http.Redirect(w, r, xid, http.StatusSeeOther)
1299}
1300
1301func showhonkers(w http.ResponseWriter, r *http.Request) {
1302 userinfo := login.GetUserInfo(r)
1303 templinfo := getInfo(r)
1304 templinfo["Honkers"] = gethonkers(userinfo.UserID)
1305 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
1306 err := readviews.Execute(w, "honkers.html", templinfo)
1307 if err != nil {
1308 log.Print(err)
1309 }
1310}
1311
1312func showcombos(w http.ResponseWriter, r *http.Request) {
1313 userinfo := login.GetUserInfo(r)
1314 templinfo := getInfo(r)
1315 honkers := gethonkers(userinfo.UserID)
1316 var combos []string
1317 for _, h := range honkers {
1318 combos = append(combos, h.Combos...)
1319 }
1320 for i, c := range combos {
1321 if c == "-" {
1322 combos[i] = ""
1323 }
1324 }
1325 combos = oneofakind(combos)
1326 sort.Strings(combos)
1327 templinfo["Combos"] = combos
1328 err := readviews.Execute(w, "combos.html", templinfo)
1329 if err != nil {
1330 log.Print(err)
1331 }
1332}
1333
1334func savehonker(w http.ResponseWriter, r *http.Request) {
1335 u := login.GetUserInfo(r)
1336 name := r.FormValue("name")
1337 url := r.FormValue("url")
1338 peep := r.FormValue("peep")
1339 combos := r.FormValue("combos")
1340 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1341
1342 if honkerid > 0 {
1343 goodbye := r.FormValue("goodbye")
1344 if goodbye == "F" {
1345 db := opendatabase()
1346 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1347 honkerid, u.UserID)
1348 var xid string
1349 err := row.Scan(&xid)
1350 if err != nil {
1351 log.Printf("can't get honker xid: %s", err)
1352 return
1353 }
1354 log.Printf("unsubscribing from %s", xid)
1355 user, _ := butwhatabout(u.Username)
1356 go itakeitallback(user, xid)
1357 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1358 if err != nil {
1359 log.Printf("error updating honker: %s", err)
1360 return
1361 }
1362
1363 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1364 return
1365 }
1366 combos = " " + strings.TrimSpace(combos) + " "
1367 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1368 if err != nil {
1369 log.Printf("update honker err: %s", err)
1370 return
1371 }
1372 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1373 }
1374
1375 flavor := "presub"
1376 if peep == "peep" {
1377 flavor = "peep"
1378 }
1379 p := investigate(url)
1380 if p == nil {
1381 log.Printf("failed to investigate honker")
1382 return
1383 }
1384 url = p.XID
1385 if name == "" {
1386 name = p.Handle
1387 }
1388 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1389 if err != nil {
1390 log.Print(err)
1391 return
1392 }
1393 if flavor == "presub" {
1394 user, _ := butwhatabout(u.Username)
1395 go subsub(user, url)
1396 }
1397 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1398}
1399
1400type Zonker struct {
1401 ID int64
1402 Name string
1403 Wherefore string
1404}
1405
1406func zonkzone(w http.ResponseWriter, r *http.Request) {
1407 userinfo := login.GetUserInfo(r)
1408 rows, err := stmtGetZonkers.Query(userinfo.UserID)
1409 if err != nil {
1410 log.Printf("err: %s", err)
1411 return
1412 }
1413 defer rows.Close()
1414 var zonkers []Zonker
1415 for rows.Next() {
1416 var z Zonker
1417 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1418 zonkers = append(zonkers, z)
1419 }
1420 sort.Slice(zonkers, func(i, j int) bool {
1421 w1 := zonkers[i].Wherefore
1422 w2 := zonkers[j].Wherefore
1423 if w1 == w2 {
1424 return zonkers[i].Name < zonkers[j].Name
1425 }
1426 if w1 == "zonvoy" {
1427 w1 = "zzzzzzz"
1428 }
1429 if w2 == "zonvoy" {
1430 w2 = "zzzzzzz"
1431 }
1432 return w1 < w2
1433 })
1434
1435 templinfo := getInfo(r)
1436 templinfo["Zonkers"] = zonkers
1437 templinfo["ZonkCSRF"] = login.GetCSRF("zonkzonk", r)
1438 err = readviews.Execute(w, "zonkers.html", templinfo)
1439 if err != nil {
1440 log.Print(err)
1441 }
1442}
1443
1444func zonkzonk(w http.ResponseWriter, r *http.Request) {
1445 userinfo := login.GetUserInfo(r)
1446 itsok := r.FormValue("itsok")
1447 if itsok == "iforgiveyou" {
1448 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1449 db := opendatabase()
1450 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1451 userinfo.UserID, zonkerid)
1452 bitethethumbs()
1453 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1454 return
1455 }
1456 wherefore := r.FormValue("wherefore")
1457 name := r.FormValue("name")
1458 if name == "" {
1459 return
1460 }
1461 switch wherefore {
1462 case "zonker":
1463 case "zomain":
1464 case "zonvoy":
1465 case "zord":
1466 case "zilence":
1467 default:
1468 return
1469 }
1470 db := opendatabase()
1471 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1472 userinfo.UserID, name, wherefore)
1473 if wherefore == "zonker" || wherefore == "zomain" || wherefore == "zord" || wherefore == "zilence" {
1474 bitethethumbs()
1475 }
1476
1477 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1478}
1479
1480func accountpage(w http.ResponseWriter, r *http.Request) {
1481 u := login.GetUserInfo(r)
1482 user, _ := butwhatabout(u.Username)
1483 templinfo := getInfo(r)
1484 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1485 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1486 templinfo["User"] = user
1487 err := readviews.Execute(w, "account.html", templinfo)
1488 if err != nil {
1489 log.Print(err)
1490 }
1491}
1492
1493func dochpass(w http.ResponseWriter, r *http.Request) {
1494 err := login.ChangePassword(w, r)
1495 if err != nil {
1496 log.Printf("error changing password: %s", err)
1497 }
1498 http.Redirect(w, r, "/account", http.StatusSeeOther)
1499}
1500
1501func fingerlicker(w http.ResponseWriter, r *http.Request) {
1502 orig := r.FormValue("resource")
1503
1504 log.Printf("finger lick: %s", orig)
1505
1506 if strings.HasPrefix(orig, "acct:") {
1507 orig = orig[5:]
1508 }
1509
1510 name := orig
1511 idx := strings.LastIndexByte(name, '/')
1512 if idx != -1 {
1513 name = name[idx+1:]
1514 if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
1515 log.Printf("foreign request rejected")
1516 name = ""
1517 }
1518 } else {
1519 idx = strings.IndexByte(name, '@')
1520 if idx != -1 {
1521 name = name[:idx]
1522 if name+"@"+serverName != orig {
1523 log.Printf("foreign request rejected")
1524 name = ""
1525 }
1526 }
1527 }
1528 user, err := butwhatabout(name)
1529 if err != nil {
1530 http.NotFound(w, r)
1531 return
1532 }
1533
1534 j := junk.New()
1535 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1536 j["aliases"] = []string{user.URL}
1537 var links []junk.Junk
1538 l := junk.New()
1539 l["rel"] = "self"
1540 l["type"] = `application/activity+json`
1541 l["href"] = user.URL
1542 links = append(links, l)
1543 j["links"] = links
1544
1545 w.Header().Set("Cache-Control", "max-age=3600")
1546 w.Header().Set("Content-Type", "application/jrd+json")
1547 j.Write(w)
1548}
1549
1550func somedays() string {
1551 secs := 432000 + notrand.Int63n(432000)
1552 return fmt.Sprintf("%d", secs)
1553}
1554
1555func avatate(w http.ResponseWriter, r *http.Request) {
1556 n := r.FormValue("a")
1557 a := avatar(n)
1558 w.Header().Set("Cache-Control", "max-age="+somedays())
1559 w.Write(a)
1560}
1561
1562func servecss(w http.ResponseWriter, r *http.Request) {
1563 w.Header().Set("Cache-Control", "max-age=7776000")
1564 http.ServeFile(w, r, "views"+r.URL.Path)
1565}
1566func servehtml(w http.ResponseWriter, r *http.Request) {
1567 templinfo := getInfo(r)
1568 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1569 if err != nil {
1570 log.Print(err)
1571 }
1572}
1573func serveemu(w http.ResponseWriter, r *http.Request) {
1574 xid := mux.Vars(r)["xid"]
1575 w.Header().Set("Cache-Control", "max-age="+somedays())
1576 http.ServeFile(w, r, "emus/"+xid)
1577}
1578func servememe(w http.ResponseWriter, r *http.Request) {
1579 xid := mux.Vars(r)["xid"]
1580 w.Header().Set("Cache-Control", "max-age="+somedays())
1581 http.ServeFile(w, r, "memes/"+xid)
1582}
1583
1584func servefile(w http.ResponseWriter, r *http.Request) {
1585 xid := mux.Vars(r)["xid"]
1586 row := stmtFileData.QueryRow(xid)
1587 var media string
1588 var data []byte
1589 err := row.Scan(&media, &data)
1590 if err != nil {
1591 log.Printf("error loading file: %s", err)
1592 http.NotFound(w, r)
1593 return
1594 }
1595 w.Header().Set("Content-Type", media)
1596 w.Header().Set("X-Content-Type-Options", "nosniff")
1597 w.Header().Set("Cache-Control", "max-age="+somedays())
1598 w.Write(data)
1599}
1600
1601func nomoroboto(w http.ResponseWriter, r *http.Request) {
1602 io.WriteString(w, "User-agent: *\n")
1603 io.WriteString(w, "Disallow: /a\n")
1604 io.WriteString(w, "Disallow: /d\n")
1605 io.WriteString(w, "Disallow: /meme\n")
1606 for _, u := range allusers() {
1607 fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
1608 }
1609}
1610
1611func serve() {
1612 db := opendatabase()
1613 login.Init(db)
1614
1615 listener, err := openListener()
1616 if err != nil {
1617 log.Fatal(err)
1618 }
1619 go redeliverator()
1620
1621 debug := false
1622 getconfig("debug", &debug)
1623 readviews = templates.Load(debug,
1624 "views/honkpage.html",
1625 "views/honkfrags.html",
1626 "views/honkers.html",
1627 "views/zonkers.html",
1628 "views/combos.html",
1629 "views/honkform.html",
1630 "views/honk.html",
1631 "views/account.html",
1632 "views/about.html",
1633 "views/funzone.html",
1634 "views/login.html",
1635 "views/xzone.html",
1636 "views/header.html",
1637 )
1638 if !debug {
1639 s := "views/style.css"
1640 savedstyleparams[s] = getstyleparam(s)
1641 s = "views/local.css"
1642 savedstyleparams[s] = getstyleparam(s)
1643 }
1644
1645 bitethethumbs()
1646
1647 mux := mux.NewRouter()
1648 mux.Use(login.Checker)
1649
1650 posters := mux.Methods("POST").Subrouter()
1651 getters := mux.Methods("GET").Subrouter()
1652
1653 getters.HandleFunc("/", homepage)
1654 getters.HandleFunc("/front", homepage)
1655 getters.HandleFunc("/robots.txt", nomoroboto)
1656 getters.HandleFunc("/rss", showrss)
1657 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}", showuser)
1658 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/"+honkSep+"/{xid:[[:alnum:]]+}", showhonk)
1659 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/rss", showrss)
1660 posters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/inbox", inbox)
1661 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/outbox", outbox)
1662 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/followers", emptiness)
1663 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/following", emptiness)
1664 getters.HandleFunc("/a", avatate)
1665 getters.HandleFunc("/o/{name:[a-z0-9-]+}", showontology)
1666 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1667 getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
1668 getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
1669 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1670
1671 getters.HandleFunc("/style.css", servecss)
1672 getters.HandleFunc("/local.css", servecss)
1673 getters.HandleFunc("/about", servehtml)
1674 getters.HandleFunc("/login", servehtml)
1675 posters.HandleFunc("/dologin", login.LoginFunc)
1676 getters.HandleFunc("/logout", login.LogoutFunc)
1677
1678 loggedin := mux.NewRoute().Subrouter()
1679 loggedin.Use(login.Required)
1680 loggedin.HandleFunc("/account", accountpage)
1681 loggedin.HandleFunc("/funzone", showfunzone)
1682 loggedin.HandleFunc("/chpass", dochpass)
1683 loggedin.HandleFunc("/atme", homepage)
1684 loggedin.HandleFunc("/zonkzone", zonkzone)
1685 loggedin.HandleFunc("/xzone", xzone)
1686 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1687 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1688 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1689 loggedin.Handle("/zonkzonk", login.CSRFWrap("zonkzonk", http.HandlerFunc(zonkzonk)))
1690 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1691 loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
1692 loggedin.HandleFunc("/honkers", showhonkers)
1693 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1694 loggedin.HandleFunc("/h", showhonker)
1695 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1696 loggedin.HandleFunc("/c", showcombos)
1697 loggedin.HandleFunc("/t", showconvoy)
1698 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1699
1700 err = http.Serve(listener, mux)
1701 if err != nil {
1702 log.Fatal(err)
1703 }
1704}
1705
1706func cleanupdb(arg string) {
1707 db := opendatabase()
1708 days, err := strconv.Atoi(arg)
1709 if err != nil {
1710 honker := arg
1711 expdate := time.Now().UTC().Add(-3 * 24 * time.Hour).Format(dbtimeformat)
1712 doordie(db, "delete from donks where honkid in (select honkid from honks where dt < ? and whofore = 0 and honker = ?)", expdate, honker)
1713 doordie(db, "delete from honks where dt < ? and whofore = 0 and honker = ?", expdate, honker)
1714 } else {
1715 expdate := time.Now().UTC().Add(-time.Duration(days) * 24 * time.Hour).Format(dbtimeformat)
1716 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)
1717 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)
1718 }
1719 doordie(db, "delete from files where fileid not in (select fileid from donks)")
1720 for _, u := range allusers() {
1721 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)
1722 }
1723}
1724
1725var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1726var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1727var stmtHonksByOntology, stmtHonksForUser, stmtHonksForMe, stmtSaveDub, stmtHonksByXonker *sql.Stmt
1728var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1729var stmtOneBonk, stmtFindZonk, stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1730var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1731var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1732var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker *sql.Stmt
1733var stmtSaveOnts, stmtUpdateFlags, stmtClearFlags *sql.Stmt
1734
1735func preparetodie(db *sql.DB, s string) *sql.Stmt {
1736 stmt, err := db.Prepare(s)
1737 if err != nil {
1738 log.Fatalf("error %s: %s", err, s)
1739 }
1740 return stmt
1741}
1742
1743func prepareStatements(db *sql.DB) {
1744 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")
1745 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1746 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1747 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1748 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1749 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1750
1751 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 "
1752 limit := " order by honks.honkid desc limit 250"
1753 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1754 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1755 stmtOneBonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ? and what = 'bonk' and whofore = 2")
1756 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
1757 stmtUserHonks = preparetodie(db, selecthonks+"where (whofore = 2 or whofore = ?) and username = ? and dt > ?"+limit)
1758 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)
1759 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1760 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on (honkers.xid = honks.honker or honkers.xid = honks.oonker) where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1761 stmtHonksByXonker = preparetodie(db, selecthonks+" where honks.userid = ? and (honker = ? or oonker = ?)"+butnotthose+limit)
1762 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1763 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or (? = -1 and whofore = 2)) and convoy = ?"+limit)
1764 stmtHonksByOntology = preparetodie(db, selecthonks+"join onts on honks.honkid = onts.honkid where onts.ontology = ? and (honks.userid = ? or (? = -1 and honks.whofore = 2))"+limit)
1765
1766 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags, onts) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1767 stmtSaveOnts = preparetodie(db, "insert into onts (ontology, honkid) values (?, ?)")
1768 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1769 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1770 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1771 stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1772 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1773 stmtFindFile = preparetodie(db, "select fileid from files where url = ? and local = 1")
1774 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, local, content) values (?, ?, ?, ?, ?, ?)")
1775 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey, options from users where username = ?")
1776 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1777 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1778 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1779 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1780 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1781 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zomain' or wherefore = 'zord' or wherefore = 'zilence')")
1782 stmtFindZonk = preparetodie(db, "select zonkerid from zonkers where userid = ? and name = ? and wherefore = 'zonk'")
1783 stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'")
1784 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1785 stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
1786 stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor) values (?, ?, ?)")
1787 stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ?")
1788 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")
1789 stmtUpdateFlags = preparetodie(db, "update honks set flags = flags | ? where honkid = ?")
1790 stmtClearFlags = preparetodie(db, "update honks set flags = flags & ~ ? where honkid = ?")
1791}
1792
1793func ElaborateUnitTests() {
1794}
1795
1796func main() {
1797 cmd := "run"
1798 if len(os.Args) > 1 {
1799 cmd = os.Args[1]
1800 }
1801 switch cmd {
1802 case "init":
1803 initdb()
1804 case "upgrade":
1805 upgradedb()
1806 }
1807 db := opendatabase()
1808 dbversion := 0
1809 getconfig("dbversion", &dbversion)
1810 if dbversion != myVersion {
1811 log.Fatal("incorrect database version. run upgrade.")
1812 }
1813 getconfig("servermsg", &serverMsg)
1814 getconfig("servername", &serverName)
1815 getconfig("usersep", &userSep)
1816 getconfig("honksep", &honkSep)
1817 getconfig("dnf", &donotfedafterdark)
1818 prepareStatements(db)
1819 switch cmd {
1820 case "adduser":
1821 adduser()
1822 case "cleanup":
1823 arg := "30"
1824 if len(os.Args) > 2 {
1825 arg = os.Args[2]
1826 }
1827 cleanupdb(arg)
1828 case "ping":
1829 if len(os.Args) < 4 {
1830 fmt.Printf("usage: honk ping from to\n")
1831 return
1832 }
1833 name := os.Args[2]
1834 targ := os.Args[3]
1835 user, err := butwhatabout(name)
1836 if err != nil {
1837 log.Printf("unknown user")
1838 return
1839 }
1840 ping(user, targ)
1841 case "peep":
1842 peeppeep()
1843 case "run":
1844 serve()
1845 case "test":
1846 ElaborateUnitTests()
1847 default:
1848 log.Fatal("unknown command")
1849 }
1850}