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 "io"
27 "log"
28 notrand "math/rand"
29 "net/http"
30 "os"
31 "sort"
32 "strconv"
33 "strings"
34 "time"
35
36 "github.com/gorilla/mux"
37 "humungus.tedunangst.com/r/webs/htfilter"
38 "humungus.tedunangst.com/r/webs/image"
39 "humungus.tedunangst.com/r/webs/login"
40 "humungus.tedunangst.com/r/webs/rss"
41 "humungus.tedunangst.com/r/webs/templates"
42)
43
44type WhatAbout struct {
45 ID int64
46 Name string
47 Display string
48 About string
49 Key string
50 URL string
51}
52
53type Honk struct {
54 ID int64
55 UserID int64
56 Username string
57 What string
58 Honker string
59 Oonker string
60 XID string
61 RID string
62 Date time.Time
63 URL string
64 Noise string
65 Precis string
66 Convoy string
67 Audience []string
68 Privacy string
69 Whofore int64
70 HTML template.HTML
71 Donks []*Donk
72}
73
74type Donk struct {
75 FileID int64
76 XID string
77 Name string
78 URL string
79 Media string
80 Content []byte
81}
82
83type Honker struct {
84 ID int64
85 UserID int64
86 Name string
87 XID string
88 Flavor string
89 Combos []string
90}
91
92var serverName string
93var iconName = "icon.png"
94
95var readviews *templates.Template
96
97func getInfo(r *http.Request) map[string]interface{} {
98 templinfo := make(map[string]interface{})
99 templinfo["StyleParam"] = getstyleparam("views/style.css")
100 templinfo["LocalStyleParam"] = getstyleparam("views/local.css")
101 templinfo["ServerName"] = serverName
102 templinfo["IconName"] = iconName
103 templinfo["UserInfo"] = login.GetUserInfo(r)
104 return templinfo
105}
106
107func homepage(w http.ResponseWriter, r *http.Request) {
108 templinfo := getInfo(r)
109 u := login.GetUserInfo(r)
110 var honks []*Honk
111 if u != nil {
112 if r.URL.Path == "/atme" {
113 honks = gethonksforme(u.UserID)
114 } else {
115 honks = gethonksforuser(u.UserID)
116 }
117 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
118 } else {
119 honks = getpublichonks()
120 }
121
122 var modtime time.Time
123 if len(honks) > 0 {
124 modtime = honks[0].Date
125 }
126 debug := false
127 getconfig("debug", &debug)
128 imh := r.Header.Get("If-Modified-Since")
129 if !debug && imh != "" && !modtime.IsZero() {
130 ifmod, err := time.Parse(http.TimeFormat, imh)
131 if err == nil && !modtime.After(ifmod) {
132 w.WriteHeader(http.StatusNotModified)
133 return
134 }
135 }
136 reverbolate(honks)
137
138 msg := "Things happen."
139 getconfig("servermsg", &msg)
140 templinfo["Honks"] = honks
141 templinfo["ShowRSS"] = true
142 templinfo["ServerMessage"] = msg
143 if u == nil {
144 w.Header().Set("Cache-Control", "max-age=60")
145 } else {
146 w.Header().Set("Cache-Control", "max-age=0")
147 }
148 w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
149 err := readviews.Execute(w, "honkpage.html", templinfo)
150 if err != nil {
151 log.Print(err)
152 }
153}
154
155func showrss(w http.ResponseWriter, r *http.Request) {
156 name := mux.Vars(r)["name"]
157
158 var honks []*Honk
159 if name != "" {
160 honks = gethonksbyuser(name)
161 } else {
162 honks = getpublichonks()
163 }
164 reverbolate(honks)
165
166 home := fmt.Sprintf("https://%s/", serverName)
167 base := home
168 if name != "" {
169 home += "u/" + name
170 name += " "
171 }
172 feed := rss.Feed{
173 Title: name + "honk",
174 Link: home,
175 Description: name + "honk rss",
176 Image: &rss.Image{
177 URL: base + "icon.png",
178 Title: name + "honk rss",
179 Link: home,
180 },
181 }
182 var modtime time.Time
183 for _, honk := range honks {
184 desc := string(honk.HTML)
185 for _, d := range honk.Donks {
186 desc += fmt.Sprintf(`<p><a href="%sd/%s">Attachment: %s</a>`,
187 base, d.XID, html.EscapeString(d.Name))
188 }
189
190 feed.Items = append(feed.Items, &rss.Item{
191 Title: fmt.Sprintf("%s %s %s", honk.Username, honk.What, honk.XID),
192 Description: rss.CData{desc},
193 Link: honk.URL,
194 PubDate: honk.Date.Format(time.RFC1123),
195 Guid: &rss.Guid{IsPermaLink: true, Value: honk.URL},
196 })
197 if honk.Date.After(modtime) {
198 modtime = honk.Date
199 }
200 }
201 w.Header().Set("Cache-Control", "max-age=300")
202 w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
203
204 err := feed.Write(w)
205 if err != nil {
206 log.Printf("error writing rss: %s", err)
207 }
208}
209
210func butwhatabout(name string) (*WhatAbout, error) {
211 row := stmtWhatAbout.QueryRow(name)
212 var user WhatAbout
213 err := row.Scan(&user.ID, &user.Name, &user.Display, &user.About, &user.Key)
214 user.URL = fmt.Sprintf("https://%s/u/%s", serverName, user.Name)
215 return &user, err
216}
217
218func crappola(j map[string]interface{}) bool {
219 t, _ := jsongetstring(j, "type")
220 a, _ := jsongetstring(j, "actor")
221 o, _ := jsongetstring(j, "object")
222 if t == "Delete" && a == o {
223 log.Printf("crappola from %s", a)
224 return true
225 }
226 return false
227}
228
229func ping(user *WhatAbout, who string) {
230 box, err := getboxes(who)
231 if err != nil {
232 log.Printf("no inbox for ping: %s", err)
233 return
234 }
235 j := NewJunk()
236 j["@context"] = itiswhatitis
237 j["type"] = "Ping"
238 j["id"] = user.URL + "/ping/" + xfiltrate()
239 j["actor"] = user.URL
240 j["to"] = who
241 keyname, key := ziggy(user.Name)
242 err = PostJunk(keyname, key, box.In, j)
243 if err != nil {
244 log.Printf("can't send ping: %s", err)
245 return
246 }
247 log.Printf("sent ping to %s: %s", who, j["id"])
248}
249
250func pong(user *WhatAbout, who string, obj string) {
251 box, err := getboxes(who)
252 if err != nil {
253 log.Printf("no inbox for pong %s : %s", who, err)
254 return
255 }
256 j := NewJunk()
257 j["@context"] = itiswhatitis
258 j["type"] = "Pong"
259 j["id"] = user.URL + "/pong/" + xfiltrate()
260 j["actor"] = user.URL
261 j["to"] = who
262 j["object"] = obj
263 keyname, key := ziggy(user.Name)
264 err = PostJunk(keyname, key, box.In, j)
265 if err != nil {
266 log.Printf("can't send pong: %s", err)
267 return
268 }
269}
270
271func inbox(w http.ResponseWriter, r *http.Request) {
272 name := mux.Vars(r)["name"]
273 user, err := butwhatabout(name)
274 if err != nil {
275 http.NotFound(w, r)
276 return
277 }
278 var buf bytes.Buffer
279 io.Copy(&buf, r.Body)
280 payload := buf.Bytes()
281 j, err := ReadJunk(bytes.NewReader(payload))
282 if err != nil {
283 log.Printf("bad payload: %s", err)
284 io.WriteString(os.Stdout, "bad payload\n")
285 os.Stdout.Write(payload)
286 io.WriteString(os.Stdout, "\n")
287 return
288 }
289 if crappola(j) {
290 return
291 }
292 keyname, err := zag(r, payload)
293 if err != nil {
294 log.Printf("inbox message failed signature: %s", err)
295 if keyname != "" {
296 keyname, err = makeitworksomehowwithoutregardforkeycontinuity(keyname, r, payload)
297 }
298 if err != nil {
299 return
300 }
301 }
302 what, _ := jsongetstring(j, "type")
303 if what == "Like" {
304 return
305 }
306 who, _ := jsongetstring(j, "actor")
307 if !keymatch(keyname, who, what, user.ID) {
308 log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
309 return
310 }
311 objid, _ := jsongetstring(j, "id")
312 if thoudostbitethythumb(user.ID, []string{who}, objid) {
313 log.Printf("ignoring thumb sucker %s", who)
314 return
315 }
316 switch what {
317 case "Ping":
318 obj, _ := jsongetstring(j, "id")
319 log.Printf("ping from %s: %s", who, obj)
320 pong(user, who, obj)
321 case "Pong":
322 obj, _ := jsongetstring(j, "object")
323 log.Printf("pong from %s: %s", who, obj)
324 case "Follow":
325 obj, _ := jsongetstring(j, "object")
326 if obj == user.URL {
327 log.Printf("updating honker follow: %s", who)
328 rubadubdub(user, j)
329 } else {
330 log.Printf("can't follow %s", obj)
331 }
332 case "Accept":
333 log.Printf("updating honker accept: %s", who)
334 _, err = stmtUpdateFlavor.Exec("sub", user.ID, who, "presub")
335 if err != nil {
336 log.Printf("error updating honker: %s", err)
337 return
338 }
339 case "Undo":
340 obj, ok := jsongetmap(j, "object")
341 if !ok {
342 log.Printf("unknown undo no object")
343 } else {
344 what, _ := jsongetstring(obj, "type")
345 switch what {
346 case "Follow":
347 log.Printf("updating honker undo: %s", who)
348 _, err = stmtUpdateFlavor.Exec("undub", user.ID, who, "dub")
349 if err != nil {
350 log.Printf("error updating honker: %s", err)
351 return
352 }
353 case "Like":
354 case "Announce":
355 default:
356 log.Printf("unknown undo: %s", what)
357 }
358 }
359 default:
360 xonk := xonkxonk(user, j)
361 if xonk != nil {
362 savexonk(user, xonk)
363 }
364 }
365}
366
367func outbox(w http.ResponseWriter, r *http.Request) {
368 name := mux.Vars(r)["name"]
369 user, err := butwhatabout(name)
370 if err != nil {
371 http.NotFound(w, r)
372 return
373 }
374 honks := gethonksbyuser(name)
375
376 var jonks []map[string]interface{}
377 for _, h := range honks {
378 j, _ := jonkjonk(user, h)
379 jonks = append(jonks, j)
380 }
381
382 j := NewJunk()
383 j["@context"] = itiswhatitis
384 j["id"] = user.URL + "/outbox"
385 j["type"] = "OrderedCollection"
386 j["totalItems"] = len(jonks)
387 j["orderedItems"] = jonks
388
389 w.Header().Set("Cache-Control", "max-age=60")
390 w.Header().Set("Content-Type", theonetruename)
391 WriteJunk(w, j)
392}
393
394func emptiness(w http.ResponseWriter, r *http.Request) {
395 name := mux.Vars(r)["name"]
396 user, err := butwhatabout(name)
397 if err != nil {
398 http.NotFound(w, r)
399 return
400 }
401 colname := "/followers"
402 if strings.HasSuffix(r.URL.Path, "/following") {
403 colname = "/following"
404 }
405 j := NewJunk()
406 j["@context"] = itiswhatitis
407 j["id"] = user.URL + colname
408 j["type"] = "OrderedCollection"
409 j["totalItems"] = 0
410 j["orderedItems"] = []interface{}{}
411
412 w.Header().Set("Cache-Control", "max-age=60")
413 w.Header().Set("Content-Type", theonetruename)
414 WriteJunk(w, j)
415}
416
417func showuser(w http.ResponseWriter, r *http.Request) {
418 name := mux.Vars(r)["name"]
419 user, err := butwhatabout(name)
420 if err != nil {
421 http.NotFound(w, r)
422 return
423 }
424 if friendorfoe(r.Header.Get("Accept")) {
425 j := asjonker(user)
426 w.Header().Set("Cache-Control", "max-age=600")
427 w.Header().Set("Content-Type", theonetruename)
428 WriteJunk(w, j)
429 return
430 }
431 honks := gethonksbyuser(name)
432 u := login.GetUserInfo(r)
433 honkpage(w, r, u, user, honks, "")
434}
435
436func showhonker(w http.ResponseWriter, r *http.Request) {
437 name := mux.Vars(r)["name"]
438 u := login.GetUserInfo(r)
439 honks := gethonksbyhonker(u.UserID, name)
440 honkpage(w, r, u, nil, honks, "honks by honker: "+name)
441}
442
443func showcombo(w http.ResponseWriter, r *http.Request) {
444 name := mux.Vars(r)["name"]
445 u := login.GetUserInfo(r)
446 honks := gethonksbycombo(u.UserID, name)
447 honkpage(w, r, u, nil, honks, "honks by combo: "+name)
448}
449func showconvoy(w http.ResponseWriter, r *http.Request) {
450 c := r.FormValue("c")
451 var userid int64 = -1
452 u := login.GetUserInfo(r)
453 if u != nil {
454 userid = u.UserID
455 }
456 honks := gethonksbyconvoy(userid, c)
457 honkpage(w, r, u, nil, honks, "honks in convoy: "+c)
458}
459
460func showhonk(w http.ResponseWriter, r *http.Request) {
461 name := mux.Vars(r)["name"]
462 user, err := butwhatabout(name)
463 if err != nil {
464 http.NotFound(w, r)
465 return
466 }
467 xid := fmt.Sprintf("https://%s%s", serverName, r.URL.Path)
468 h := getxonk(user.ID, xid)
469 if h == nil {
470 http.NotFound(w, r)
471 return
472 }
473 if friendorfoe(r.Header.Get("Accept")) {
474 donksforhonks([]*Honk{h})
475 _, j := jonkjonk(user, h)
476 j["@context"] = itiswhatitis
477 w.Header().Set("Cache-Control", "max-age=3600")
478 w.Header().Set("Content-Type", theonetruename)
479 WriteJunk(w, j)
480 return
481 }
482 honks := gethonksbyconvoy(-1, h.Convoy)
483 for _, hh := range honks {
484 if hh.XID != h.XID {
485 hh.Privacy = "limited"
486 }
487 }
488 u := login.GetUserInfo(r)
489 honkpage(w, r, u, nil, honks, "one honk maybe more")
490}
491
492func honkpage(w http.ResponseWriter, r *http.Request, u *login.UserInfo, user *WhatAbout,
493 honks []*Honk, infomsg string) {
494 reverbolate(honks)
495 templinfo := getInfo(r)
496 if u != nil {
497 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
498 }
499 if u == nil {
500 w.Header().Set("Cache-Control", "max-age=60")
501 }
502 if user != nil {
503 filt := htfilter.New()
504 templinfo["Name"] = user.Name
505 whatabout := user.About
506 whatabout = obfusbreak(user.About)
507 templinfo["WhatAbout"], _ = filt.String(whatabout)
508 }
509 templinfo["Honks"] = honks
510 templinfo["ServerMessage"] = infomsg
511 err := readviews.Execute(w, "honkpage.html", templinfo)
512 if err != nil {
513 log.Print(err)
514 }
515}
516
517func saveuser(w http.ResponseWriter, r *http.Request) {
518 whatabout := r.FormValue("whatabout")
519 u := login.GetUserInfo(r)
520 db := opendatabase()
521 _, err := db.Exec("update users set about = ? where username = ?", whatabout, u.Username)
522 if err != nil {
523 log.Printf("error bouting what: %s", err)
524 }
525
526 http.Redirect(w, r, "/account", http.StatusSeeOther)
527}
528
529func gethonkers(userid int64) []*Honker {
530 rows, err := stmtHonkers.Query(userid)
531 if err != nil {
532 log.Printf("error querying honkers: %s", err)
533 return nil
534 }
535 defer rows.Close()
536 var honkers []*Honker
537 for rows.Next() {
538 var f Honker
539 var combos string
540 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor, &combos)
541 f.Combos = strings.Split(strings.TrimSpace(combos), " ")
542 if err != nil {
543 log.Printf("error scanning honker: %s", err)
544 return nil
545 }
546 honkers = append(honkers, &f)
547 }
548 return honkers
549}
550
551func getdubs(userid int64) []*Honker {
552 rows, err := stmtDubbers.Query(userid)
553 if err != nil {
554 log.Printf("error querying dubs: %s", err)
555 return nil
556 }
557 defer rows.Close()
558 var honkers []*Honker
559 for rows.Next() {
560 var f Honker
561 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
562 if err != nil {
563 log.Printf("error scanning honker: %s", err)
564 return nil
565 }
566 honkers = append(honkers, &f)
567 }
568 return honkers
569}
570
571func allusers() []login.UserInfo {
572 var users []login.UserInfo
573 rows, _ := opendatabase().Query("select userid, username from users")
574 defer rows.Close()
575 for rows.Next() {
576 var u login.UserInfo
577 rows.Scan(&u.UserID, &u.Username)
578 users = append(users, u)
579 }
580 return users
581}
582
583func getxonk(userid int64, xid string) *Honk {
584 h := new(Honk)
585 var dt, aud string
586 row := stmtOneXonk.QueryRow(userid, xid)
587 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
588 &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore)
589 if err != nil {
590 if err != sql.ErrNoRows {
591 log.Printf("error scanning xonk: %s", err)
592 }
593 return nil
594 }
595 h.Date, _ = time.Parse(dbtimeformat, dt)
596 h.Audience = strings.Split(aud, " ")
597 return h
598}
599
600func getpublichonks() []*Honk {
601 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
602 rows, err := stmtPublicHonks.Query(dt)
603 return getsomehonks(rows, err)
604}
605func gethonksbyuser(name string) []*Honk {
606 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
607 rows, err := stmtUserHonks.Query(name, dt)
608 return getsomehonks(rows, err)
609}
610func gethonksforuser(userid int64) []*Honk {
611 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
612 rows, err := stmtHonksForUser.Query(userid, dt, userid, userid)
613 return getsomehonks(rows, err)
614}
615func gethonksforme(userid int64) []*Honk {
616 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
617 rows, err := stmtHonksForMe.Query(userid, dt, userid)
618 return getsomehonks(rows, err)
619}
620func gethonksbyhonker(userid int64, honker string) []*Honk {
621 rows, err := stmtHonksByHonker.Query(userid, honker, userid)
622 return getsomehonks(rows, err)
623}
624func gethonksbycombo(userid int64, combo string) []*Honk {
625 combo = "% " + combo + " %"
626 rows, err := stmtHonksByCombo.Query(userid, combo, userid)
627 return getsomehonks(rows, err)
628}
629func gethonksbyconvoy(userid int64, convoy string) []*Honk {
630 rows, err := stmtHonksByConvoy.Query(userid, convoy)
631 honks := getsomehonks(rows, err)
632 for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
633 honks[i], honks[j] = honks[j], honks[i]
634 }
635 return honks
636}
637
638func getsomehonks(rows *sql.Rows, err error) []*Honk {
639 if err != nil {
640 log.Printf("error querying honks: %s", err)
641 return nil
642 }
643 defer rows.Close()
644 var honks []*Honk
645 for rows.Next() {
646 var h Honk
647 var dt, aud string
648 err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker,
649 &h.XID, &h.RID, &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore)
650 if err != nil {
651 log.Printf("error scanning honks: %s", err)
652 return nil
653 }
654 h.Date, _ = time.Parse(dbtimeformat, dt)
655 h.Audience = strings.Split(aud, " ")
656 h.Privacy = "limited"
657 for _, a := range h.Audience {
658 if a == thewholeworld {
659 h.Privacy = ""
660 break
661 }
662 }
663 honks = append(honks, &h)
664 }
665 rows.Close()
666 donksforhonks(honks)
667 return honks
668}
669
670func donksforhonks(honks []*Honk) {
671 db := opendatabase()
672 var ids []string
673 hmap := make(map[int64]*Honk)
674 for _, h := range honks {
675 ids = append(ids, fmt.Sprintf("%d", h.ID))
676 hmap[h.ID] = h
677 }
678 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, ","))
679 rows, err := db.Query(q)
680 if err != nil {
681 log.Printf("error querying donks: %s", err)
682 return
683 }
684 defer rows.Close()
685 for rows.Next() {
686 var hid int64
687 var d Donk
688 err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.URL, &d.Media)
689 if err != nil {
690 log.Printf("error scanning donk: %s", err)
691 continue
692 }
693 h := hmap[hid]
694 h.Donks = append(h.Donks, &d)
695 }
696}
697
698func savebonk(w http.ResponseWriter, r *http.Request) {
699 xid := r.FormValue("xid")
700 userinfo := login.GetUserInfo(r)
701 user, _ := butwhatabout(userinfo.Username)
702
703 log.Printf("bonking %s", xid)
704
705 xonk := getxonk(userinfo.UserID, xid)
706 if xonk == nil {
707 return
708 }
709 donksforhonks([]*Honk{xonk})
710
711 dt := time.Now().UTC()
712 bonk := Honk{
713 UserID: userinfo.UserID,
714 Username: userinfo.Username,
715 What: "bonk",
716 Honker: user.URL,
717 XID: xonk.XID,
718 Date: dt,
719 Donks: xonk.Donks,
720 Audience: []string{thewholeworld},
721 }
722
723 aud := strings.Join(bonk.Audience, " ")
724 whofore := 2
725 res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", bonk.Honker, xid, "",
726 dt.Format(dbtimeformat), "", aud, xonk.Noise, xonk.Convoy, whofore, "html",
727 xonk.Precis, xonk.Honker)
728 if err != nil {
729 log.Printf("error saving bonk: %s", err)
730 return
731 }
732 bonk.ID, _ = res.LastInsertId()
733 for _, d := range bonk.Donks {
734 _, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
735 if err != nil {
736 log.Printf("err saving donk: %s", err)
737 return
738 }
739 }
740
741 go honkworldwide(user, &bonk)
742}
743
744func zonkit(w http.ResponseWriter, r *http.Request) {
745 wherefore := r.FormValue("wherefore")
746 var what string
747 switch wherefore {
748 case "this honk":
749 what = r.FormValue("honk")
750 wherefore = "zonk"
751 case "this honker":
752 what = r.FormValue("honker")
753 wherefore = "zonker"
754 case "this convoy":
755 what = r.FormValue("convoy")
756 wherefore = "zonvoy"
757 }
758 if what == "" {
759 return
760 }
761
762 log.Printf("zonking %s %s", wherefore, what)
763 userinfo := login.GetUserInfo(r)
764 if wherefore == "zonk" {
765 xonk := getxonk(userinfo.UserID, what)
766 if xonk != nil {
767 stmtZonkDonks.Exec(xonk.ID)
768 stmtZonkIt.Exec(userinfo.UserID, what)
769 if xonk.Whofore == 2 {
770 zonk := Honk{
771 What: "zonk",
772 XID: xonk.XID,
773 Date: time.Now().UTC(),
774 Audience: oneofakind(xonk.Audience),
775 }
776
777 user, _ := butwhatabout(userinfo.Username)
778 log.Printf("announcing deleted honk: %s", what)
779 go honkworldwide(user, &zonk)
780 }
781 }
782 } else {
783 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
784 if err != nil {
785 log.Printf("error saving zonker: %s", err)
786 return
787 }
788 }
789}
790
791func savehonk(w http.ResponseWriter, r *http.Request) {
792 rid := r.FormValue("rid")
793 noise := r.FormValue("noise")
794
795 userinfo := login.GetUserInfo(r)
796 user, _ := butwhatabout(userinfo.Username)
797
798 dt := time.Now().UTC()
799 xid := fmt.Sprintf("https://%s/u/%s/h/%s", serverName, userinfo.Username, xfiltrate())
800 what := "honk"
801 if rid != "" {
802 what = "tonk"
803 }
804 honk := Honk{
805 UserID: userinfo.UserID,
806 Username: userinfo.Username,
807 What: "honk",
808 Honker: user.URL,
809 XID: xid,
810 Date: dt,
811 }
812 if strings.HasPrefix(noise, "DZ:") {
813 idx := strings.Index(noise, "\n")
814 if idx == -1 {
815 honk.Precis = noise
816 noise = ""
817 } else {
818 honk.Precis = noise[:idx]
819 noise = noise[idx+1:]
820 }
821 }
822 noise = strings.TrimSpace(noise)
823 honk.Precis = strings.TrimSpace(honk.Precis)
824
825 if noise != "" && noise[0] == '@' {
826 honk.Audience = append(grapevine(noise), thewholeworld)
827 } else {
828 honk.Audience = prepend(thewholeworld, grapevine(noise))
829 }
830 var convoy string
831 if rid != "" {
832 xonk := getxonk(userinfo.UserID, rid)
833 if xonk != nil {
834 honk.Audience = append(honk.Audience, xonk.Audience...)
835 convoy = xonk.Convoy
836 } else {
837 xonkaud, c := whosthere(rid)
838 honk.Audience = append(honk.Audience, xonkaud...)
839 convoy = c
840 }
841 honk.RID = rid
842 }
843 if convoy == "" {
844 convoy = "data:,electrichonkytonk-" + xfiltrate()
845 }
846 butnottooloud(honk.Audience)
847 honk.Audience = oneofakind(honk.Audience)
848 noise = obfusbreak(noise)
849 honk.Noise = noise
850 honk.Convoy = convoy
851
852 file, filehdr, err := r.FormFile("donk")
853 if err == nil {
854 var buf bytes.Buffer
855 io.Copy(&buf, file)
856 file.Close()
857 data := buf.Bytes()
858 xid := xfiltrate()
859 var media, name string
860 img, err := image.Vacuum(&buf)
861 if err == nil {
862 data = img.Data
863 format := img.Format
864 media = "image/" + format
865 if format == "jpeg" {
866 format = "jpg"
867 }
868 name = xid + "." + format
869 xid = name
870 } else {
871 maxsize := 100000
872 if len(data) > maxsize {
873 log.Printf("bad image: %s too much text: %d", err, len(data))
874 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
875 return
876 }
877 for i := 0; i < len(data); i++ {
878 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
879 log.Printf("bad image: %s not text: %d", err, data[i])
880 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
881 return
882 }
883 }
884 media = "text/plain"
885 name = filehdr.Filename
886 if name == "" {
887 name = xid + ".txt"
888 }
889 xid += ".txt"
890 }
891 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
892 res, err := stmtSaveFile.Exec(xid, name, url, media, data)
893 if err != nil {
894 log.Printf("unable to save image: %s", err)
895 return
896 }
897 var d Donk
898 d.FileID, _ = res.LastInsertId()
899 d.XID = name
900 d.Name = name
901 d.Media = media
902 d.URL = url
903 honk.Donks = append(honk.Donks, &d)
904 }
905 herd := herdofemus(honk.Noise)
906 for _, e := range herd {
907 donk := savedonk(e.ID, e.Name, "image/png")
908 if donk != nil {
909 donk.Name = e.Name
910 honk.Donks = append(honk.Donks, donk)
911 }
912 }
913
914 aud := strings.Join(honk.Audience, " ")
915 whofore := 2
916 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, honk.Honker, xid, rid,
917 dt.Format(dbtimeformat), "", aud, noise, convoy, whofore, "html", honk.Precis, honk.Oonker)
918 if err != nil {
919 log.Printf("error saving honk: %s", err)
920 return
921 }
922 honk.ID, _ = res.LastInsertId()
923 for _, d := range honk.Donks {
924 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
925 if err != nil {
926 log.Printf("err saving donk: %s", err)
927 return
928 }
929 }
930
931 go honkworldwide(user, &honk)
932
933 http.Redirect(w, r, "/", http.StatusSeeOther)
934}
935
936func showhonkers(w http.ResponseWriter, r *http.Request) {
937 userinfo := login.GetUserInfo(r)
938 templinfo := getInfo(r)
939 templinfo["Honkers"] = gethonkers(userinfo.UserID)
940 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
941 err := readviews.Execute(w, "honkers.html", templinfo)
942 if err != nil {
943 log.Print(err)
944 }
945}
946
947func showcombos(w http.ResponseWriter, r *http.Request) {
948 userinfo := login.GetUserInfo(r)
949 templinfo := getInfo(r)
950 honkers := gethonkers(userinfo.UserID)
951 var combos []string
952 for _, h := range honkers {
953 combos = append(combos, h.Combos...)
954 }
955 for i, c := range combos {
956 if c == "-" {
957 combos[i] = ""
958 }
959 }
960 combos = oneofakind(combos)
961 sort.Strings(combos)
962 templinfo["Combos"] = combos
963 err := readviews.Execute(w, "combos.html", templinfo)
964 if err != nil {
965 log.Print(err)
966 }
967}
968
969func savehonker(w http.ResponseWriter, r *http.Request) {
970 u := login.GetUserInfo(r)
971 name := r.FormValue("name")
972 url := r.FormValue("url")
973 peep := r.FormValue("peep")
974 combos := r.FormValue("combos")
975 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
976
977 if honkerid > 0 {
978 goodbye := r.FormValue("goodbye")
979 if goodbye == "goodbye" {
980 db := opendatabase()
981 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
982 honkerid, u.UserID)
983 var xid string
984 err := row.Scan(&xid)
985 if err != nil {
986 log.Printf("can't get honker xid: %s", err)
987 return
988 }
989 log.Printf("unsubscribing from %s", xid)
990 user, _ := butwhatabout(u.Username)
991 err = itakeitallback(user, xid)
992 if err != nil {
993 log.Printf("can't take it back: %s", err)
994 } else {
995 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
996 if err != nil {
997 log.Printf("error updating honker: %s", err)
998 return
999 }
1000 }
1001
1002 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1003 return
1004 }
1005 combos = " " + strings.TrimSpace(combos) + " "
1006 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1007 if err != nil {
1008 log.Printf("update honker err: %s", err)
1009 return
1010 }
1011 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1012 }
1013
1014 flavor := "presub"
1015 if peep == "peep" {
1016 flavor = "peep"
1017 }
1018 if url == "" {
1019 return
1020 }
1021 if url[0] == '@' {
1022 url = gofish(url)
1023 }
1024 if url == "" {
1025 return
1026 }
1027 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1028 if err != nil {
1029 log.Print(err)
1030 return
1031 }
1032 if flavor == "presub" {
1033 user, _ := butwhatabout(u.Username)
1034 go subsub(user, url)
1035 }
1036 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1037}
1038
1039type Zonker struct {
1040 ID int64
1041 Name string
1042 Wherefore string
1043}
1044
1045func killzone(w http.ResponseWriter, r *http.Request) {
1046 db := opendatabase()
1047 userinfo := login.GetUserInfo(r)
1048 rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1049 if err != nil {
1050 log.Printf("err: %s", err)
1051 return
1052 }
1053 var zonkers []Zonker
1054 for rows.Next() {
1055 var z Zonker
1056 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1057 zonkers = append(zonkers, z)
1058 }
1059 templinfo := getInfo(r)
1060 templinfo["Zonkers"] = zonkers
1061 templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
1062 err = readviews.Execute(w, "zonkers.html", templinfo)
1063 if err != nil {
1064 log.Print(err)
1065 }
1066}
1067
1068func killitwithfire(w http.ResponseWriter, r *http.Request) {
1069 userinfo := login.GetUserInfo(r)
1070 itsok := r.FormValue("itsok")
1071 if itsok == "iforgiveyou" {
1072 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1073 db := opendatabase()
1074 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1075 userinfo.UserID, zonkerid)
1076 bitethethumbs()
1077 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1078 return
1079 }
1080 wherefore := r.FormValue("wherefore")
1081 name := r.FormValue("name")
1082 if name == "" {
1083 return
1084 }
1085 switch wherefore {
1086 case "zonker":
1087 case "zurl":
1088 case "zonvoy":
1089 default:
1090 return
1091 }
1092 db := opendatabase()
1093 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1094 userinfo.UserID, name, wherefore)
1095 if wherefore == "zonker" || wherefore == "zurl" {
1096 bitethethumbs()
1097 }
1098
1099 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1100}
1101
1102func accountpage(w http.ResponseWriter, r *http.Request) {
1103 u := login.GetUserInfo(r)
1104 user, _ := butwhatabout(u.Username)
1105 templinfo := getInfo(r)
1106 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1107 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1108 templinfo["WhatAbout"] = user.About
1109 err := readviews.Execute(w, "account.html", templinfo)
1110 if err != nil {
1111 log.Print(err)
1112 }
1113}
1114
1115func dochpass(w http.ResponseWriter, r *http.Request) {
1116 err := login.ChangePassword(w, r)
1117 if err != nil {
1118 log.Printf("error changing password: %s", err)
1119 }
1120 http.Redirect(w, r, "/account", http.StatusSeeOther)
1121}
1122
1123func fingerlicker(w http.ResponseWriter, r *http.Request) {
1124 orig := r.FormValue("resource")
1125
1126 log.Printf("finger lick: %s", orig)
1127
1128 if strings.HasPrefix(orig, "acct:") {
1129 orig = orig[5:]
1130 }
1131
1132 name := orig
1133 idx := strings.LastIndexByte(name, '/')
1134 if idx != -1 {
1135 name = name[idx+1:]
1136 if "https://"+serverName+"/u/"+name != orig {
1137 log.Printf("foreign request rejected")
1138 name = ""
1139 }
1140 } else {
1141 idx = strings.IndexByte(name, '@')
1142 if idx != -1 {
1143 name = name[:idx]
1144 if name+"@"+serverName != orig {
1145 log.Printf("foreign request rejected")
1146 name = ""
1147 }
1148 }
1149 }
1150 user, err := butwhatabout(name)
1151 if err != nil {
1152 http.NotFound(w, r)
1153 return
1154 }
1155
1156 j := NewJunk()
1157 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1158 j["aliases"] = []string{user.URL}
1159 var links []map[string]interface{}
1160 l := NewJunk()
1161 l["rel"] = "self"
1162 l["type"] = `application/activity+json`
1163 l["href"] = user.URL
1164 links = append(links, l)
1165 j["links"] = links
1166
1167 w.Header().Set("Cache-Control", "max-age=3600")
1168 w.Header().Set("Content-Type", "application/jrd+json")
1169 WriteJunk(w, j)
1170}
1171
1172func somedays() string {
1173 secs := 432000 + notrand.Int63n(432000)
1174 return fmt.Sprintf("%d", secs)
1175}
1176
1177func avatate(w http.ResponseWriter, r *http.Request) {
1178 n := r.FormValue("a")
1179 a := avatar(n)
1180 w.Header().Set("Cache-Control", "max-age="+somedays())
1181 w.Write(a)
1182}
1183
1184func servecss(w http.ResponseWriter, r *http.Request) {
1185 w.Header().Set("Cache-Control", "max-age=7776000")
1186 http.ServeFile(w, r, "views"+r.URL.Path)
1187}
1188func servehtml(w http.ResponseWriter, r *http.Request) {
1189 templinfo := getInfo(r)
1190 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1191 if err != nil {
1192 log.Print(err)
1193 }
1194}
1195func serveemu(w http.ResponseWriter, r *http.Request) {
1196 xid := mux.Vars(r)["xid"]
1197 w.Header().Set("Cache-Control", "max-age="+somedays())
1198 http.ServeFile(w, r, "emus/"+xid)
1199}
1200
1201func servefile(w http.ResponseWriter, r *http.Request) {
1202 xid := mux.Vars(r)["xid"]
1203 row := stmtFileData.QueryRow(xid)
1204 var media string
1205 var data []byte
1206 err := row.Scan(&media, &data)
1207 if err != nil {
1208 log.Printf("error loading file: %s", err)
1209 http.NotFound(w, r)
1210 return
1211 }
1212 w.Header().Set("Content-Type", media)
1213 w.Header().Set("X-Content-Type-Options", "nosniff")
1214 w.Header().Set("Cache-Control", "max-age="+somedays())
1215 w.Write(data)
1216}
1217
1218func serve() {
1219 db := opendatabase()
1220 login.Init(db)
1221
1222 listener, err := openListener()
1223 if err != nil {
1224 log.Fatal(err)
1225 }
1226 go redeliverator()
1227
1228 debug := false
1229 getconfig("debug", &debug)
1230 readviews = templates.Load(debug,
1231 "views/honkpage.html",
1232 "views/honkers.html",
1233 "views/zonkers.html",
1234 "views/combos.html",
1235 "views/honkform.html",
1236 "views/honk.html",
1237 "views/account.html",
1238 "views/login.html",
1239 "views/header.html",
1240 )
1241 if !debug {
1242 s := "views/style.css"
1243 savedstyleparams[s] = getstyleparam(s)
1244 s = "views/local.css"
1245 savedstyleparams[s] = getstyleparam(s)
1246 }
1247
1248 bitethethumbs()
1249
1250 mux := mux.NewRouter()
1251 mux.Use(login.Checker)
1252
1253 posters := mux.Methods("POST").Subrouter()
1254 getters := mux.Methods("GET").Subrouter()
1255
1256 getters.HandleFunc("/", homepage)
1257 getters.HandleFunc("/rss", showrss)
1258 getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1259 getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1260 getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1261 posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1262 getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1263 getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1264 getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1265 getters.HandleFunc("/a", avatate)
1266 getters.HandleFunc("/t", showconvoy)
1267 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1268 getters.HandleFunc("/emu/{xid:[[:alnum:]_.]+}", serveemu)
1269 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1270
1271 getters.HandleFunc("/style.css", servecss)
1272 getters.HandleFunc("/local.css", servecss)
1273 getters.HandleFunc("/login", servehtml)
1274 posters.HandleFunc("/dologin", login.LoginFunc)
1275 getters.HandleFunc("/logout", login.LogoutFunc)
1276
1277 loggedin := mux.NewRoute().Subrouter()
1278 loggedin.Use(login.Required)
1279 loggedin.HandleFunc("/account", accountpage)
1280 loggedin.HandleFunc("/chpass", dochpass)
1281 loggedin.HandleFunc("/atme", homepage)
1282 loggedin.HandleFunc("/killzone", killzone)
1283 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1284 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1285 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1286 loggedin.Handle("/killitwithfire", login.CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
1287 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1288 loggedin.HandleFunc("/honkers", showhonkers)
1289 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1290 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1291 loggedin.HandleFunc("/c", showcombos)
1292 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1293
1294 err = http.Serve(listener, mux)
1295 if err != nil {
1296 log.Fatal(err)
1297 }
1298}
1299
1300var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1301var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1302var stmtHonksForUser, stmtHonksForMe, stmtSaveDub *sql.Stmt
1303var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1304var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1305var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1306var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1307var stmtGetBoxes, stmtSaveBoxes *sql.Stmt
1308
1309func preparetodie(db *sql.DB, s string) *sql.Stmt {
1310 stmt, err := db.Prepare(s)
1311 if err != nil {
1312 log.Fatalf("error %s: %s", err, s)
1313 }
1314 return stmt
1315}
1316
1317func prepareStatements(db *sql.DB) {
1318 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")
1319 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1320 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1321 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1322 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1323 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1324
1325 selecthonks := "select honkid, honks.userid, username, what, honker, oonker, honks.xid, rid, dt, url, audience, noise, precis, convoy, whofore from honks join users on honks.userid = users.userid "
1326 limit := " order by honkid desc limit 250"
1327 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1328 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1329 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
1330 stmtUserHonks = preparetodie(db, selecthonks+"where whofore = 2 and username = ? and dt > ?"+limit)
1331 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)
1332 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1333 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1334 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1335 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or whofore = 2) and convoy = ?"+limit)
1336
1337 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1338 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1339 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1340 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1341 stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1342 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1343 stmtFindFile = preparetodie(db, "select fileid from files where url = ?")
1344 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, content) values (?, ?, ?, ?, ?)")
1345 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1346 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1347 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1348 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1349 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1350 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1351 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zurl')")
1352 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1353 stmtGetBoxes = preparetodie(db, "select ibox, obox, sbox from xonkers where xid = ?")
1354 stmtSaveBoxes = preparetodie(db, "insert into xonkers (xid, ibox, obox, sbox, pubkey) values (?, ?, ?, ?, ?)")
1355}
1356
1357func ElaborateUnitTests() {
1358}
1359
1360func finishusersetup() error {
1361 db := opendatabase()
1362 k, err := rsa.GenerateKey(rand.Reader, 2048)
1363 if err != nil {
1364 return err
1365 }
1366 pubkey, err := zem(&k.PublicKey)
1367 if err != nil {
1368 return err
1369 }
1370 seckey, err := zem(k)
1371 if err != nil {
1372 return err
1373 }
1374 _, err = db.Exec("update users set displayname = username, about = ?, pubkey = ?, seckey = ? where userid = 1", "what about me?", pubkey, seckey)
1375 if err != nil {
1376 return err
1377 }
1378 return nil
1379}
1380
1381func main() {
1382 cmd := "run"
1383 if len(os.Args) > 1 {
1384 cmd = os.Args[1]
1385 }
1386 switch cmd {
1387 case "init":
1388 initdb()
1389 case "upgrade":
1390 upgradedb()
1391 }
1392 db := opendatabase()
1393 dbversion := 0
1394 getconfig("dbversion", &dbversion)
1395 if dbversion != myVersion {
1396 log.Fatal("incorrect database version. run upgrade.")
1397 }
1398 getconfig("servername", &serverName)
1399 prepareStatements(db)
1400 switch cmd {
1401 case "ping":
1402 if len(os.Args) < 4 {
1403 fmt.Printf("usage: honk ping from to\n")
1404 return
1405 }
1406 name := os.Args[2]
1407 targ := os.Args[3]
1408 user, err := butwhatabout(name)
1409 if err != nil {
1410 log.Printf("unknown user")
1411 return
1412 }
1413 ping(user, targ)
1414 case "peep":
1415 peeppeep()
1416 case "run":
1417 serve()
1418 case "test":
1419 ElaborateUnitTests()
1420 default:
1421 log.Fatal("unknown command")
1422 }
1423}