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