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