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