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