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 "crypto/rand"
21 "crypto/rsa"
22 "database/sql"
23 "fmt"
24 "html"
25 "html/template"
26 "image"
27 _ "image/gif"
28 _ "image/jpeg"
29 _ "image/png"
30 "io"
31 "log"
32 "net/http"
33 "os"
34 "regexp"
35 "sort"
36 "strings"
37 "sync"
38 "time"
39
40 "github.com/gorilla/mux"
41)
42
43type UserInfo struct {
44 UserID int64
45 Username string
46}
47
48type WhatAbout struct {
49 ID int64
50 Name string
51 Display string
52 About string
53 Key string
54 URL string
55}
56
57var serverName string
58var iconName = "icon.png"
59
60var readviews *Template
61
62func ziggy(user *WhatAbout) (keyname string, key *rsa.PrivateKey) {
63 db := opendatabase()
64 row := db.QueryRow("select seckey from users where userid = ?", user.ID)
65 var data string
66 row.Scan(&data)
67 var err error
68 key, _, err = pez(data)
69 if err != nil {
70 log.Printf("error loading %s seckey: %s", user.Name, err)
71 }
72 keyname = user.URL + "#key"
73 return
74}
75
76func zaggy(keyname string) (key *rsa.PublicKey) {
77 db := opendatabase()
78 row := db.QueryRow("select pubkey from honkers where flavor = 'key' and xid = ?", keyname)
79 var data string
80 err := row.Scan(&data)
81 savekey := false
82 if err != nil {
83 savekey = true
84 j, err := GetJunk(keyname)
85 if err != nil {
86 log.Printf("error getting %s pubkey: %s", keyname, err)
87 return
88 }
89 var ok bool
90 data, ok = jsonfindstring(j, []string{"publicKey", "publicKeyPem"})
91 if !ok {
92 log.Printf("error getting %s pubkey", keyname)
93 return
94 }
95 _, ok = jsonfindstring(j, []string{"publicKey", "owner"})
96 if !ok {
97 log.Printf("error getting %s pubkey owner", keyname)
98 return
99 }
100 }
101 _, key, err = pez(data)
102 if err != nil {
103 log.Printf("error getting %s pubkey: %s", keyname, err)
104 return
105 }
106 if savekey {
107 db.Exec("insert into honkers (name, xid, flavor, pubkey) values (?, ?, ?, ?)",
108 "", keyname, "key", data)
109 }
110 return
111}
112
113func keymatch(keyname string, actor string) bool {
114 return strings.HasPrefix(keyname, actor)
115}
116
117func getInfo(r *http.Request) map[string]interface{} {
118 templinfo := make(map[string]interface{})
119 templinfo["StyleParam"] = getstyleparam()
120 templinfo["ServerName"] = serverName
121 templinfo["IconName"] = iconName
122 templinfo["UserInfo"] = GetUserInfo(r)
123 templinfo["LogoutCSRF"] = GetCSRF("logout", r)
124 return templinfo
125}
126
127var re_unurl = regexp.MustCompile("https://([^/]+).*/([^/]+)")
128
129func honkerhandle(h string) string {
130 m := re_unurl.FindStringSubmatch(h)
131 if len(m) > 2 {
132 return fmt.Sprintf("%s@%s", m[2], m[1])
133 }
134 return ""
135}
136
137func reverbolate(honks []*Honk) {
138 for _, h := range honks {
139 h.What += "ed"
140 if h.Honker == "" {
141 h.Honker = "https://" + serverName + "/u/" + h.Username
142 if strings.IndexByte(h.XID, '/') == -1 {
143 h.URL = h.Honker + "/h/" + h.XID
144 } else {
145 h.URL = h.XID
146 }
147 } else {
148 idx := strings.LastIndexByte(h.Honker, '/')
149 if idx != -1 {
150 h.Username = honkerhandle(h.Honker)
151 } else {
152 h.Username = h.Honker
153 }
154 if h.URL == "" {
155 h.URL = h.XID
156 }
157 }
158 h.HTML = cleanstring(h.Noise)
159 }
160}
161
162func homepage(w http.ResponseWriter, r *http.Request) {
163 templinfo := getInfo(r)
164 honks := gethonks("")
165 u := GetUserInfo(r)
166 if u != nil {
167 morehonks := gethonksforuser(u.UserID)
168 honks = append(honks, morehonks...)
169 templinfo["HonkCSRF"] = GetCSRF("honkhonk", r)
170 }
171 sort.Slice(honks, func(i, j int) bool {
172 return honks[i].Date.After(honks[j].Date)
173 })
174 reverbolate(honks)
175 msg := "Things happen."
176 getconfig("servermsg", &msg)
177 templinfo["Honks"] = honks
178 templinfo["ShowRSS"] = true
179 templinfo["ServerMessage"] = msg
180 err := readviews.ExecuteTemplate(w, "homepage.html", templinfo)
181 if err != nil {
182 log.Print(err)
183 }
184}
185
186func showrss(w http.ResponseWriter, r *http.Request) {
187 name := mux.Vars(r)["name"]
188
189 honks := gethonks(name)
190 sort.Slice(honks, func(i, j int) bool {
191 return honks[i].Date.After(honks[j].Date)
192 })
193 reverbolate(honks)
194
195 home := fmt.Sprintf("https://%s/", serverName)
196 base := home
197 if name != "" {
198 home += "u/" + name
199 name += " "
200 }
201 feed := RssFeed{
202 Title: name + "honk",
203 Link: home,
204 Description: name + "honk rss",
205 FeedImage: &RssFeedImage{
206 URL: base + "icon.png",
207 Title: name + "honk rss",
208 Link: home,
209 },
210 }
211 var modtime time.Time
212 past := time.Now().UTC().Add(-3 * 24 * time.Hour)
213 for _, honk := range honks {
214 if honk.Date.Before(past) {
215 break
216 }
217 if honk.URL[0] == '/' {
218 honk.URL = "https://" + serverName + honk.URL
219 }
220 feed.Items = append(feed.Items, &RssItem{
221 Title: fmt.Sprintf("%s %s %s", honk.Username, honk.What, honk.XID),
222 Description: RssCData{string(honk.HTML)},
223 Link: honk.URL,
224 PubDate: honk.Date.Format(time.RFC1123),
225 })
226 if honk.Date.After(modtime) {
227 modtime = honk.Date
228 }
229 }
230 w.Header().Set("Cache-Control", "max-age=300")
231 w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
232
233 err := feed.Write(w)
234 if err != nil {
235 log.Printf("error writing rss: %s", err)
236 }
237}
238
239func butwhatabout(name string) (*WhatAbout, error) {
240 row := stmtWhatAbout.QueryRow(name)
241 var user WhatAbout
242 err := row.Scan(&user.ID, &user.Name, &user.Display, &user.About, &user.Key)
243 user.URL = fmt.Sprintf("https://%s/u/%s", serverName, user.Name)
244 return &user, err
245}
246
247func crappola(j map[string]interface{}) bool {
248 t, _ := jsongetstring(j, "type")
249 a, _ := jsongetstring(j, "actor")
250 o, _ := jsongetstring(j, "object")
251 if t == "Delete" && a == o {
252 log.Printf("crappola from %s", a)
253 return true
254 }
255 return false
256}
257
258func inbox(w http.ResponseWriter, r *http.Request) {
259 name := mux.Vars(r)["name"]
260 user, err := butwhatabout(name)
261 if err != nil {
262 http.NotFound(w, r)
263 return
264 }
265 var buf bytes.Buffer
266 io.Copy(&buf, r.Body)
267 payload := buf.Bytes()
268 j, err := ReadJunk(bytes.NewReader(payload))
269 if err != nil {
270 log.Printf("bad payload: %s", err)
271 io.WriteString(os.Stdout, "bad payload\n")
272 os.Stdout.Write(payload)
273 io.WriteString(os.Stdout, "\n")
274 return
275 }
276 if crappola(j) {
277 return
278 }
279 keyname, err := zag(r, payload)
280 if err != nil {
281 log.Printf("inbox message failed signature: %s", err)
282 fd, _ := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
283 io.WriteString(fd, "bad signature:\n")
284 WriteJunk(fd, j)
285 io.WriteString(fd, "\n")
286 fd.Close()
287 return
288 }
289 fd, _ := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
290 WriteJunk(fd, j)
291 io.WriteString(fd, "\n")
292 fd.Close()
293 who, _ := jsongetstring(j, "actor")
294 if !keymatch(keyname, who) {
295 log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
296 return
297 }
298 what, _ := jsongetstring(j, "type")
299 switch what {
300 case "Follow":
301 log.Printf("updating honker follow: %s", who)
302 rubadubdub(user, j)
303 case "Accept":
304 db := opendatabase()
305 log.Printf("updating honker accept: %s", who)
306 db.Exec("update honkers set flavor = 'sub' where xid = ? and flavor = 'presub'", who)
307 case "Undo":
308 obj, ok := jsongetmap(j, "object")
309 if !ok {
310 log.Printf("unknown undo no object")
311 } else {
312 what, _ := jsongetstring(obj, "type")
313 if what != "Follow" {
314 log.Printf("unknown undo: %s", what)
315 } else {
316 log.Printf("updating honker undo: %s", who)
317 db := opendatabase()
318 db.Exec("update honkers set flavor = 'undub' where xid = ? and flavor = 'dub'", who)
319 }
320 }
321 default:
322 xonk := xonkxonk(j)
323 if xonk != nil && needxonk(user.ID, xonk) {
324 xonk.UserID = user.ID
325 savexonk(xonk)
326 }
327 }
328}
329
330func outbox(w http.ResponseWriter, r *http.Request) {
331 name := mux.Vars(r)["name"]
332 user, err := butwhatabout(name)
333 if err != nil {
334 http.NotFound(w, r)
335 return
336 }
337 honks := gethonks(name)
338
339 var jonks []map[string]interface{}
340 for _, h := range honks {
341 j, _ := jonkjonk(user, h)
342 jonks = append(jonks, j)
343 }
344
345 j := NewJunk()
346 j["@context"] = itiswhatitis
347 j["id"] = user.URL + "/outbox"
348 j["type"] = "OrderedCollection"
349 j["totalItems"] = len(jonks)
350 j["orderedItems"] = jonks
351
352 w.Header().Set("Content-Type", theonetruename)
353 WriteJunk(w, j)
354}
355
356func viewuser(w http.ResponseWriter, r *http.Request) {
357 name := mux.Vars(r)["name"]
358 user, err := butwhatabout(name)
359 if err != nil {
360 http.NotFound(w, r)
361 return
362 }
363 if friendorfoe(r.Header.Get("Accept")) {
364 j := asjonker(user)
365 w.Header().Set("Content-Type", theonetruename)
366 WriteJunk(w, j)
367 return
368 }
369 honks := gethonks(name)
370 u := GetUserInfo(r)
371 honkpage(w, r, u, user, honks)
372}
373
374func viewhonker(w http.ResponseWriter, r *http.Request) {
375 name := mux.Vars(r)["name"]
376 u := GetUserInfo(r)
377 honks := gethonksbyhonker(u.UserID, name)
378 honkpage(w, r, nil, nil, honks)
379}
380
381func fingerlicker(w http.ResponseWriter, r *http.Request) {
382 orig := r.FormValue("resource")
383
384 log.Printf("finger lick: %s", orig)
385
386 if strings.HasPrefix(orig, "acct:") {
387 orig = orig[5:]
388 }
389
390 name := orig
391 idx := strings.LastIndexByte(name, '/')
392 if idx != -1 {
393 name = name[idx+1:]
394 if "https://"+serverName+"/u/"+name != orig {
395 log.Printf("foreign request rejected")
396 name = ""
397 }
398 } else {
399 idx = strings.IndexByte(name, '@')
400 if idx != -1 {
401 name = name[:idx]
402 if name+"@"+serverName != orig {
403 log.Printf("foreign request rejected")
404 name = ""
405 }
406 }
407 }
408 user, err := butwhatabout(name)
409 if err != nil {
410 http.NotFound(w, r)
411 return
412 }
413
414 j := NewJunk()
415 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
416 j["aliases"] = []string{user.URL}
417 var links []map[string]interface{}
418 l := NewJunk()
419 l["rel"] = "self"
420 l["type"] = `application/activity+json`
421 l["href"] = user.URL
422 links = append(links, l)
423 j["links"] = links
424
425 w.Header().Set("Content-Type", "application/jrd+json")
426 WriteJunk(w, j)
427}
428
429func viewhonk(w http.ResponseWriter, r *http.Request) {
430 name := mux.Vars(r)["name"]
431 xid := mux.Vars(r)["xid"]
432 user, err := butwhatabout(name)
433 if err != nil {
434 http.NotFound(w, r)
435 return
436 }
437 h := getxonk(name, xid)
438 if h == nil {
439 http.NotFound(w, r)
440 return
441 }
442 if friendorfoe(r.Header.Get("Accept")) {
443 _, j := jonkjonk(user, h)
444 j["@context"] = itiswhatitis
445 w.Header().Set("Content-Type", theonetruename)
446 WriteJunk(w, j)
447 return
448 }
449 honkpage(w, r, nil, nil, []*Honk{h})
450}
451
452func honkpage(w http.ResponseWriter, r *http.Request, u *UserInfo, user *WhatAbout, honks []*Honk) {
453 reverbolate(honks)
454 templinfo := getInfo(r)
455 if u != nil && u.Username == user.Name {
456 templinfo["UserCSRF"] = GetCSRF("saveuser", r)
457 templinfo["HonkCSRF"] = GetCSRF("honkhonk", r)
458 }
459 if user != nil {
460 templinfo["Name"] = user.Name
461 whatabout := user.About
462 templinfo["RawWhatAbout"] = whatabout
463 whatabout = obfusbreak(whatabout)
464 templinfo["WhatAbout"] = cleanstring(whatabout)
465 }
466 templinfo["Honks"] = honks
467 err := readviews.ExecuteTemplate(w, "honkpage.html", templinfo)
468 if err != nil {
469 log.Print(err)
470 }
471}
472
473func saveuser(w http.ResponseWriter, r *http.Request) {
474 whatabout := r.FormValue("whatabout")
475 u := GetUserInfo(r)
476 db := opendatabase()
477 _, err := db.Exec("update users set about = ? where username = ?", whatabout, u.Username)
478 if err != nil {
479 log.Printf("error bouting what: %s", err)
480 }
481
482 http.Redirect(w, r, "/u/"+u.Username, http.StatusSeeOther)
483}
484
485type Donk struct {
486 FileID int64
487 XID string
488 Name string
489 URL string
490 Media string
491 Content []byte
492}
493
494type Honk struct {
495 ID int64
496 UserID int64
497 Username string
498 What string
499 Honker string
500 XID string
501 RID string
502 Date time.Time
503 URL string
504 Noise string
505 Audience []string
506 HTML template.HTML
507 Donks []*Donk
508}
509
510type Honker struct {
511 ID int64
512 UserID int64
513 Name string
514 XID string
515 Flavor string
516}
517
518func gethonkers(userid int64) []*Honker {
519 rows, err := stmtHonkers.Query(userid)
520 if err != nil {
521 log.Printf("error querying honkers: %s", err)
522 return nil
523 }
524 defer rows.Close()
525 var honkers []*Honker
526 for rows.Next() {
527 var f Honker
528 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
529 if err != nil {
530 log.Printf("error scanning honker: %s", err)
531 return nil
532 }
533 honkers = append(honkers, &f)
534 }
535 return honkers
536}
537
538func getdubs(userid int64) []*Honker {
539 rows, err := stmtDubbers.Query(userid)
540 if err != nil {
541 log.Printf("error querying dubs: %s", err)
542 return nil
543 }
544 defer rows.Close()
545 var honkers []*Honker
546 for rows.Next() {
547 var f Honker
548 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
549 if err != nil {
550 log.Printf("error scanning honker: %s", err)
551 return nil
552 }
553 honkers = append(honkers, &f)
554 }
555 return honkers
556}
557
558func gethonk(honkid int64) *Honk {
559 var h Honk
560 var dt, aud string
561 row := stmtOneHonk.QueryRow(honkid)
562 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.XID, &h.RID,
563 &dt, &h.URL, &aud, &h.Noise)
564 if err != nil {
565 log.Printf("error scanning honk: %s", err)
566 return nil
567 }
568 h.Date, _ = time.Parse(dbtimeformat, dt)
569 h.Audience = strings.Split(aud, " ")
570 return &h
571}
572
573func getxonk(name, xid string) *Honk {
574 var h Honk
575 var dt, aud string
576 row := stmtOneXonk.QueryRow(xid)
577 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.XID, &h.RID,
578 &dt, &h.URL, &aud, &h.Noise)
579 if err != nil {
580 log.Printf("error scanning xonk: %s", err)
581 return nil
582 }
583 if name != "" && h.Username != name {
584 log.Printf("user xonk mismatch")
585 return nil
586 }
587 h.Date, _ = time.Parse(dbtimeformat, dt)
588 h.Audience = strings.Split(aud, " ")
589 donksforhonks([]*Honk{&h})
590 return &h
591}
592
593func gethonks(username string) []*Honk {
594 return getsomehonks(username, 0, "")
595}
596
597func gethonksforuser(userid int64) []*Honk {
598 return getsomehonks("", userid, "")
599}
600func gethonksbyhonker(userid int64, honker string) []*Honk {
601 return getsomehonks("", userid, honker)
602}
603
604func getsomehonks(username string, userid int64, honkername string) []*Honk {
605 var rows *sql.Rows
606 var err error
607 if username != "" {
608 rows, err = stmtUserHonks.Query(username)
609 } else if honkername != "" {
610 rows, err = stmtHonksByHonker.Query(userid, honkername)
611 } else if userid > 0 {
612 rows, err = stmtHonksForUser.Query(userid)
613 } else {
614 rows, err = stmtHonks.Query()
615 }
616 if err != nil {
617 log.Printf("error querying honks: %s", err)
618 return nil
619 }
620 defer rows.Close()
621 var honks []*Honk
622 for rows.Next() {
623 var h Honk
624 var dt, aud string
625 err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.XID, &h.RID,
626 &dt, &h.URL, &aud, &h.Noise)
627 if err != nil {
628 log.Printf("error scanning honks: %s", err)
629 return nil
630 }
631 h.Date, _ = time.Parse(dbtimeformat, dt)
632 h.Audience = strings.Split(aud, " ")
633 honks = append(honks, &h)
634 }
635 rows.Close()
636 donksforhonks(honks)
637 return honks
638}
639
640func donksforhonks(honks []*Honk) {
641 db := opendatabase()
642 var ids []string
643 for _, h := range honks {
644 ids = append(ids, fmt.Sprintf("%d", h.ID))
645 }
646 q := fmt.Sprintf("select honkid, donks.fileid, xid, name, url, media from donks join files on donks.fileid = files.fileid where honkid in (%s)", strings.Join(ids, ","))
647 rows, err := db.Query(q)
648 if err != nil {
649 log.Printf("error querying donks: %s", err)
650 return
651 }
652 defer rows.Close()
653 for rows.Next() {
654 var hid int64
655 var d Donk
656 err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media)
657 if err != nil {
658 log.Printf("error scanning donk: %s", err)
659 continue
660 }
661 for _, h := range honks {
662 if h.ID == hid {
663 h.Donks = append(h.Donks, &d)
664 }
665 }
666 }
667}
668
669func xfiltrate() string {
670 letters := "BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz1234567891234567891234"
671 db := opendatabase()
672 for {
673 var x int64
674 var b [16]byte
675 rand.Read(b[:])
676 for i, c := range b {
677 b[i] = letters[c&63]
678 }
679 s := string(b[:])
680 r := db.QueryRow("select honkid from honks where xid = ?", s)
681 err := r.Scan(&x)
682 if err == nil {
683 continue
684 }
685 if err != sql.ErrNoRows {
686 log.Printf("err picking xid: %s", err)
687 return ""
688 }
689 r = db.QueryRow("select fileid from files where name = ?", s)
690 err = r.Scan(&x)
691 if err == nil {
692 continue
693 }
694 if err != sql.ErrNoRows {
695 log.Printf("err picking xid: %s", err)
696 return ""
697 }
698 return s
699 }
700}
701
702type Mention struct {
703 who string
704 where string
705}
706
707var re_mentions = regexp.MustCompile(`@[[:alnum:]]+@[[:alnum:].]+`)
708
709func grapevine(s string) []string {
710 m := re_mentions.FindAllString(s, -1)
711 var mentions []string
712 for i := range m {
713 where := gofish(m[i])
714 if where != "" {
715 mentions = append(mentions, where)
716 }
717 }
718 return mentions
719}
720
721func bunchofgrapes(s string) []Mention {
722 m := re_mentions.FindAllString(s, -1)
723 var mentions []Mention
724 for i := range m {
725 where := gofish(m[i])
726 if where != "" {
727 mentions = append(mentions, Mention{who: m[i], where: where})
728 }
729 }
730 return mentions
731}
732
733var re_link = regexp.MustCompile(`https?://[^\s"]+[\w/)]`)
734
735func obfusbreak(s string) string {
736 s = strings.TrimSpace(s)
737 s = strings.Replace(s, "\r", "", -1)
738 s = html.EscapeString(s)
739 linkfn := func(url string) string {
740 addparen := false
741 adddot := false
742 if strings.HasSuffix(url, ")") && strings.IndexByte(url, '(') == -1 {
743 url = url[:len(url)-1]
744 addparen = true
745 }
746 if strings.HasSuffix(url, ".") {
747 url = url[:len(url)-1]
748 adddot = true
749 }
750 url = fmt.Sprintf(`<a href="%s">%s</a>`, url, url)
751 if adddot {
752 url += "."
753 }
754 if addparen {
755 url += ")"
756 }
757 return url
758 }
759 s = re_link.ReplaceAllStringFunc(s, linkfn)
760
761 s = strings.Replace(s, "\n", "<br>", -1)
762 s = re_mentions.ReplaceAllStringFunc(s, func(m string) string {
763 return fmt.Sprintf(`<a href="%s">%s</a>`, html.EscapeString(gofish(m)),
764 html.EscapeString(m))
765 })
766 return s
767}
768
769func prepend(s string, x []string) []string {
770 return append([]string{s}, x...)
771}
772
773func savebonk(w http.ResponseWriter, r *http.Request) {
774 xid := r.FormValue("xid")
775
776 log.Printf("bonking %s", xid)
777
778 xonk := getxonk("", xid)
779 if xonk == nil {
780 return
781 }
782 if xonk.Honker == "" {
783 xonk.XID = fmt.Sprintf("https://%s/u/%s/h/%s", serverName, xonk.Username, xonk.XID)
784 }
785
786 userinfo := GetUserInfo(r)
787
788 dt := time.Now().UTC()
789 bonk := Honk{
790 UserID: userinfo.UserID,
791 Username: userinfo.Username,
792 Honker: xonk.Honker,
793 What: "bonk",
794 XID: xonk.XID,
795 Date: dt,
796 Noise: xonk.Noise,
797 Donks: xonk.Donks,
798 Audience: oneofakind(prepend(thewholeworld, xonk.Audience)),
799 }
800
801 aud := strings.Join(bonk.Audience, " ")
802 res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", "", xid, "",
803 dt.Format(dbtimeformat), "", aud, bonk.Noise)
804 if err != nil {
805 log.Printf("error saving bonk: %s", err)
806 return
807 }
808 bonk.ID, _ = res.LastInsertId()
809 for _, d := range bonk.Donks {
810 _, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
811 if err != nil {
812 log.Printf("err saving donk: %s", err)
813 return
814 }
815 }
816
817 user, _ := butwhatabout(userinfo.Username)
818
819 go honkworldwide(user, &bonk)
820
821}
822
823func savehonk(w http.ResponseWriter, r *http.Request) {
824 rid := r.FormValue("rid")
825 noise := r.FormValue("noise")
826
827 userinfo := GetUserInfo(r)
828
829 dt := time.Now().UTC()
830 xid := xfiltrate()
831 if xid == "" {
832 return
833 }
834 what := "honk"
835 if rid != "" {
836 what = "tonk"
837 }
838 honk := Honk{
839 UserID: userinfo.UserID,
840 Username: userinfo.Username,
841 What: "honk",
842 XID: xid,
843 RID: rid,
844 Date: dt,
845 }
846 if noise[0] == '@' {
847 honk.Audience = append(grapevine(noise), thewholeworld)
848 } else {
849 honk.Audience = append([]string{thewholeworld}, grapevine(noise)...)
850 }
851 if rid != "" {
852 xonk := getxonk("", rid)
853 honk.Audience = append(honk.Audience, xonk.Audience...)
854 }
855 honk.Audience = oneofakind(honk.Audience)
856 noise = obfusbreak(noise)
857 honk.Noise = noise
858
859 file, _, err := r.FormFile("donk")
860 if err == nil {
861 var buf bytes.Buffer
862 io.Copy(&buf, file)
863 file.Close()
864 data := buf.Bytes()
865 img, format, err := image.Decode(&buf)
866 if err != nil {
867 log.Printf("bad image: %s", err)
868 return
869 }
870 data, format, err = vacuumwrap(img, format)
871 if err != nil {
872 log.Printf("can't vacuum image: %s", err)
873 return
874 }
875 name := xfiltrate()
876 media := "image/" + format
877 if format == "jpeg" {
878 format = "jpg"
879 }
880 name = name + "." + format
881 url := fmt.Sprintf("https://%s/d/%s", serverName, name)
882 res, err := stmtSaveFile.Exec(name, name, url, media, data)
883 if err != nil {
884 log.Printf("unable to save image: %s", err)
885 return
886 }
887 var d Donk
888 d.FileID, _ = res.LastInsertId()
889 d.XID = name
890 d.Name = name
891 d.Media = media
892 d.URL = url
893 honk.Donks = append(honk.Donks, &d)
894 }
895
896 aud := strings.Join(honk.Audience, " ")
897 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, "", xid, rid,
898 dt.Format(dbtimeformat), "", aud, noise)
899 if err != nil {
900 log.Printf("error saving honk: %s", err)
901 return
902 }
903 honk.ID, _ = res.LastInsertId()
904 for _, d := range honk.Donks {
905 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
906 if err != nil {
907 log.Printf("err saving donk: %s", err)
908 return
909 }
910 }
911
912 user, _ := butwhatabout(userinfo.Username)
913
914 go honkworldwide(user, &honk)
915
916 http.Redirect(w, r, "/", http.StatusSeeOther)
917}
918
919func showhonkers(w http.ResponseWriter, r *http.Request) {
920 userinfo := GetUserInfo(r)
921 templinfo := getInfo(r)
922 templinfo["Honkers"] = gethonkers(userinfo.UserID)
923 templinfo["HonkerCSRF"] = GetCSRF("savehonker", r)
924 err := readviews.ExecuteTemplate(w, "honkers.html", templinfo)
925 if err != nil {
926 log.Print(err)
927 }
928}
929
930var handfull = make(map[string]string)
931var handlock sync.Mutex
932
933func gofish(name string) string {
934 if name[0] == '@' {
935 name = name[1:]
936 }
937 m := strings.Split(name, "@")
938 if len(m) != 2 {
939 log.Printf("bad far name: %s", name)
940 return ""
941 }
942 handlock.Lock()
943 defer handlock.Unlock()
944 ref, ok := handfull[name]
945 if ok {
946 return ref
947 }
948 j, err := GetJunk(fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s", m[1], name))
949 if err != nil {
950 log.Printf("failed to get far name: %s", err)
951 handfull[name] = ""
952 return ""
953 }
954 links, _ := jsongetarray(j, "links")
955 for _, l := range links {
956 href, _ := jsongetstring(l, "href")
957 rel, _ := jsongetstring(l, "rel")
958 t, _ := jsongetstring(l, "type")
959 if rel == "self" && friendorfoe(t) {
960 handfull[name] = href
961 return href
962 }
963 }
964 handfull[name] = ""
965 return ""
966}
967
968func savehonker(w http.ResponseWriter, r *http.Request) {
969 name := r.FormValue("name")
970 url := r.FormValue("url")
971 peep := r.FormValue("peep")
972 flavor := "presub"
973 if peep == "peep" {
974 flavor = "peep"
975 }
976
977 if url == "" {
978 return
979 }
980 if url[0] == '@' {
981 url = gofish(url)
982 }
983 if url == "" {
984 return
985 }
986
987 u := GetUserInfo(r)
988 db := opendatabase()
989 _, err := db.Exec("insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)",
990 u.UserID, name, url, flavor)
991 if err != nil {
992 log.Print(err)
993 }
994 if flavor == "presub" {
995 user, _ := butwhatabout(u.Username)
996 go subsub(user, url)
997 }
998 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
999}
1000
1001func avatate(w http.ResponseWriter, r *http.Request) {
1002 n := r.FormValue("a")
1003 a := avatar(n)
1004 w.Header().Set("Cache-Control", "max-age=76000")
1005 w.Write(a)
1006}
1007
1008func servecss(w http.ResponseWriter, r *http.Request) {
1009 w.Header().Set("Cache-Control", "max-age=7776000")
1010 http.ServeFile(w, r, "views"+r.URL.Path)
1011}
1012func servehtml(w http.ResponseWriter, r *http.Request) {
1013 templinfo := getInfo(r)
1014 err := readviews.ExecuteTemplate(w, r.URL.Path[1:]+".html", templinfo)
1015 if err != nil {
1016 log.Print(err)
1017 }
1018}
1019
1020func servefile(w http.ResponseWriter, r *http.Request) {
1021 xid := mux.Vars(r)["xid"]
1022 row := stmtFileData.QueryRow(xid)
1023 var data []byte
1024 err := row.Scan(&data)
1025 if err != nil {
1026 log.Printf("error loading file: %s", err)
1027 http.NotFound(w, r)
1028 return
1029 }
1030 w.Header().Set("Cache-Control", "max-age=432000")
1031 w.Write(data)
1032}
1033
1034func serve() {
1035 db := opendatabase()
1036 LoginInit(db)
1037
1038 getconfig("servername", &serverName)
1039 listener, err := openListener()
1040 if err != nil {
1041 log.Fatal(err)
1042 }
1043 debug := false
1044 getconfig("debug", &debug)
1045 readviews = ParseTemplates(debug,
1046 "views/homepage.html",
1047 "views/honkpage.html",
1048 "views/honkers.html",
1049 "views/honkform.html",
1050 "views/honk.html",
1051 "views/login.html",
1052 "views/header.html",
1053 )
1054 if !debug {
1055 savedstyleparam = getstyleparam()
1056 }
1057
1058 mux := mux.NewRouter()
1059 mux.Use(LoginChecker)
1060
1061 posters := mux.Methods("POST").Subrouter()
1062 getters := mux.Methods("GET").Subrouter()
1063
1064 getters.HandleFunc("/", homepage)
1065 getters.HandleFunc("/rss", showrss)
1066 getters.HandleFunc("/u/{name:[[:alnum:]]+}", viewuser)
1067 getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", viewhonk)
1068 getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1069 posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1070 getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1071 getters.HandleFunc("/a", avatate)
1072 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1073 getters.HandleFunc("/h/{name:[[:alnum:]]+}", viewhonker)
1074 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1075
1076 getters.HandleFunc("/style.css", servecss)
1077 getters.HandleFunc("/login", servehtml)
1078 posters.HandleFunc("/dologin", dologin)
1079 getters.HandleFunc("/logout", dologout)
1080
1081 loggedin := mux.NewRoute().Subrouter()
1082 loggedin.Use(LoginRequired)
1083 loggedin.Handle("/honk", CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1084 loggedin.Handle("/bonk", CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1085 loggedin.Handle("/saveuser", CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1086 loggedin.HandleFunc("/honkers", showhonkers)
1087 loggedin.Handle("/savehonker", CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1088
1089 err = http.Serve(listener, mux)
1090 if err != nil {
1091 log.Fatal(err)
1092 }
1093}
1094
1095var stmtHonkers, stmtDubbers, stmtOneHonk, stmtOneXonk, stmtHonks, stmtUserHonks *sql.Stmt
1096var stmtHonksForUser, stmtDeleteHonk, stmtSaveDub *sql.Stmt
1097var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1098var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1099
1100func prepareStatements(db *sql.DB) {
1101 var err error
1102 stmtHonkers, err = db.Prepare("select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'sub' or flavor = 'peep'")
1103 if err != nil {
1104 log.Fatal(err)
1105 }
1106 stmtDubbers, err = db.Prepare("select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1107 if err != nil {
1108 log.Fatal(err)
1109 }
1110 stmtOneHonk, err = db.Prepare("select honkid, honks.userid, users.username, what, honker, xid, rid, dt, url, audience, noise from honks join users on honks.userid = users.userid where honkid = ? limit 50")
1111 if err != nil {
1112 log.Fatal(err)
1113 }
1114 stmtOneXonk, err = db.Prepare("select honkid, honks.userid, users.username, what, honker, xid, rid, dt, url, audience, noise from honks join users on honks.userid = users.userid where xid = ?")
1115 if err != nil {
1116 log.Fatal(err)
1117 }
1118 stmtHonks, err = db.Prepare("select honkid, honks.userid, users.username, what, honker, xid, rid, dt, url, audience, noise from honks join users on honks.userid = users.userid where honker = '' order by honkid desc limit 50")
1119 if err != nil {
1120 log.Fatal(err)
1121 }
1122 stmtUserHonks, err = db.Prepare("select honkid, honks.userid, username, what, honker, xid, rid, dt, url, audience, noise from honks join users on honks.userid = users.userid where honker = '' and username = ? order by honkid desc limit 50")
1123 if err != nil {
1124 log.Fatal(err)
1125 }
1126 stmtHonksForUser, err = db.Prepare("select honkid, honks.userid, users.username, what, honker, xid, rid, dt, url, audience, noise from honks join users on honks.userid = users.userid where honks.userid = ? and honker <> '' and what <> 'zonk' order by honkid desc limit 150")
1127 if err != nil {
1128 log.Fatal(err)
1129 }
1130 stmtHonksByHonker, err = db.Prepare("select honkid, honks.userid, users.username, what, honker, honks.xid, rid, dt, url, audience, noise from honks join users on honks.userid = users.userid join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ? order by honkid desc limit 50")
1131 if err != nil {
1132 log.Fatal(err)
1133 }
1134 stmtSaveHonk, err = db.Prepare("insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise) values (?, ?, ?, ?, ?, ?, ?, ?, ?)")
1135 if err != nil {
1136 log.Fatal(err)
1137 }
1138 stmtFileData, err = db.Prepare("select content from files where xid = ?")
1139 if err != nil {
1140 log.Fatal(err)
1141 }
1142 stmtFindXonk, err = db.Prepare("select honkid from honks where userid = ? and xid = ? and what = ?")
1143 if err != nil {
1144 log.Fatal(err)
1145 }
1146 stmtSaveDonk, err = db.Prepare("insert into donks (honkid, fileid) values (?, ?)")
1147 if err != nil {
1148 log.Fatal(err)
1149 }
1150 stmtDeleteHonk, err = db.Prepare("update honks set what = 'zonk' where xid = ? and honker = ?")
1151 if err != nil {
1152 log.Fatal(err)
1153 }
1154 stmtFindFile, err = db.Prepare("select fileid from files where url = ?")
1155 if err != nil {
1156 log.Fatal(err)
1157 }
1158 stmtSaveFile, err = db.Prepare("insert into files (xid, name, url, media, content) values (?, ?, ?, ?, ?)")
1159 if err != nil {
1160 log.Fatal(err)
1161 }
1162 stmtWhatAbout, err = db.Prepare("select userid, username, displayname, about, pubkey from users where username = ?")
1163 if err != nil {
1164 log.Fatal(err)
1165 }
1166 stmtSaveDub, err = db.Prepare("insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1167 if err != nil {
1168 log.Fatal(err)
1169 }
1170}
1171
1172func ElaborateUnitTests() {
1173}
1174
1175func finishusersetup() error {
1176 db := opendatabase()
1177 k, err := rsa.GenerateKey(rand.Reader, 2048)
1178 if err != nil {
1179 return err
1180 }
1181 pubkey, err := zem(&k.PublicKey)
1182 if err != nil {
1183 return err
1184 }
1185 seckey, err := zem(k)
1186 if err != nil {
1187 return err
1188 }
1189 _, err = db.Exec("update users set displayname = username, about = ?, pubkey = ?, seckey = ? where userid = 1", "what about me?", pubkey, seckey)
1190 if err != nil {
1191 return err
1192 }
1193 return nil
1194}
1195
1196func main() {
1197 cmd := "run"
1198 if len(os.Args) > 1 {
1199 cmd = os.Args[1]
1200 }
1201 if cmd != "init" {
1202 db := opendatabase()
1203 prepareStatements(db)
1204 }
1205 switch cmd {
1206 case "peep":
1207 peeppeep()
1208 case "init":
1209 initdb()
1210 case "run":
1211 serve()
1212 case "test":
1213 ElaborateUnitTests()
1214 default:
1215 log.Fatal("unknown command")
1216 }
1217}