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 if xonk.Honker == "" {
835 xonk.Honker = "https://" + serverName + "/u/" + xonk.Username
836 rid = xonk.Honker + "/h/" + rid
837 }
838 honk.Audience = append(honk.Audience, xonk.Audience...)
839 convoy = xonk.Convoy
840 } else {
841 xonkaud, c := whosthere(rid)
842 honk.Audience = append(honk.Audience, xonkaud...)
843 convoy = c
844 }
845 honk.RID = rid
846 }
847 if convoy == "" {
848 convoy = "data:,electrichonkytonk-" + xfiltrate()
849 }
850 butnottooloud(honk.Audience)
851 honk.Audience = oneofakind(honk.Audience)
852 noise = obfusbreak(noise)
853 honk.Noise = noise
854 honk.Convoy = convoy
855
856 file, filehdr, err := r.FormFile("donk")
857 if err == nil {
858 var buf bytes.Buffer
859 io.Copy(&buf, file)
860 file.Close()
861 data := buf.Bytes()
862 xid := xfiltrate()
863 var media, name string
864 img, err := image.Vacuum(&buf)
865 if err == nil {
866 data = img.Data
867 format := img.Format
868 media = "image/" + format
869 if format == "jpeg" {
870 format = "jpg"
871 }
872 name = xid + "." + format
873 xid = name
874 } else {
875 maxsize := 100000
876 if len(data) > maxsize {
877 log.Printf("bad image: %s too much text: %d", err, len(data))
878 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
879 return
880 }
881 for i := 0; i < len(data); i++ {
882 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
883 log.Printf("bad image: %s not text: %d", err, data[i])
884 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
885 return
886 }
887 }
888 media = "text/plain"
889 name = filehdr.Filename
890 if name == "" {
891 name = xid + ".txt"
892 }
893 xid += ".txt"
894 }
895 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
896 res, err := stmtSaveFile.Exec(xid, name, url, media, data)
897 if err != nil {
898 log.Printf("unable to save image: %s", err)
899 return
900 }
901 var d Donk
902 d.FileID, _ = res.LastInsertId()
903 d.XID = name
904 d.Name = name
905 d.Media = media
906 d.URL = url
907 honk.Donks = append(honk.Donks, &d)
908 }
909 herd := herdofemus(honk.Noise)
910 for _, e := range herd {
911 donk := savedonk(e.ID, e.Name, "image/png")
912 if donk != nil {
913 donk.Name = e.Name
914 honk.Donks = append(honk.Donks, donk)
915 }
916 }
917
918 aud := strings.Join(honk.Audience, " ")
919 whofore := 2
920 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, honk.Honker, xid, rid,
921 dt.Format(dbtimeformat), "", aud, noise, convoy, whofore, "html", honk.Precis, honk.Oonker)
922 if err != nil {
923 log.Printf("error saving honk: %s", err)
924 return
925 }
926 honk.ID, _ = res.LastInsertId()
927 for _, d := range honk.Donks {
928 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
929 if err != nil {
930 log.Printf("err saving donk: %s", err)
931 return
932 }
933 }
934
935 go honkworldwide(user, &honk)
936
937 http.Redirect(w, r, "/", http.StatusSeeOther)
938}
939
940func showhonkers(w http.ResponseWriter, r *http.Request) {
941 userinfo := login.GetUserInfo(r)
942 templinfo := getInfo(r)
943 templinfo["Honkers"] = gethonkers(userinfo.UserID)
944 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
945 err := readviews.Execute(w, "honkers.html", templinfo)
946 if err != nil {
947 log.Print(err)
948 }
949}
950
951func showcombos(w http.ResponseWriter, r *http.Request) {
952 userinfo := login.GetUserInfo(r)
953 templinfo := getInfo(r)
954 honkers := gethonkers(userinfo.UserID)
955 var combos []string
956 for _, h := range honkers {
957 combos = append(combos, h.Combos...)
958 }
959 for i, c := range combos {
960 if c == "-" {
961 combos[i] = ""
962 }
963 }
964 combos = oneofakind(combos)
965 sort.Strings(combos)
966 templinfo["Combos"] = combos
967 err := readviews.Execute(w, "combos.html", templinfo)
968 if err != nil {
969 log.Print(err)
970 }
971}
972
973func savehonker(w http.ResponseWriter, r *http.Request) {
974 u := login.GetUserInfo(r)
975 name := r.FormValue("name")
976 url := r.FormValue("url")
977 peep := r.FormValue("peep")
978 combos := r.FormValue("combos")
979 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
980
981 if honkerid > 0 {
982 goodbye := r.FormValue("goodbye")
983 if goodbye == "goodbye" {
984 db := opendatabase()
985 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
986 honkerid, u.UserID)
987 var xid string
988 err := row.Scan(&xid)
989 if err != nil {
990 log.Printf("can't get honker xid: %s", err)
991 return
992 }
993 log.Printf("unsubscribing from %s", xid)
994 user, _ := butwhatabout(u.Username)
995 err = itakeitallback(user, xid)
996 if err != nil {
997 log.Printf("can't take it back: %s", err)
998 } else {
999 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1000 if err != nil {
1001 log.Printf("error updating honker: %s", err)
1002 return
1003 }
1004 }
1005
1006 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1007 return
1008 }
1009 combos = " " + strings.TrimSpace(combos) + " "
1010 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1011 if err != nil {
1012 log.Printf("update honker err: %s", err)
1013 return
1014 }
1015 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1016 }
1017
1018 flavor := "presub"
1019 if peep == "peep" {
1020 flavor = "peep"
1021 }
1022 if url == "" {
1023 return
1024 }
1025 if url[0] == '@' {
1026 url = gofish(url)
1027 }
1028 if url == "" {
1029 return
1030 }
1031 _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1032 if err != nil {
1033 log.Print(err)
1034 return
1035 }
1036 if flavor == "presub" {
1037 user, _ := butwhatabout(u.Username)
1038 go subsub(user, url)
1039 }
1040 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1041}
1042
1043type Zonker struct {
1044 ID int64
1045 Name string
1046 Wherefore string
1047}
1048
1049func killzone(w http.ResponseWriter, r *http.Request) {
1050 db := opendatabase()
1051 userinfo := login.GetUserInfo(r)
1052 rows, err := db.Query("select zonkerid, name, wherefore from zonkers where userid = ?", userinfo.UserID)
1053 if err != nil {
1054 log.Printf("err: %s", err)
1055 return
1056 }
1057 var zonkers []Zonker
1058 for rows.Next() {
1059 var z Zonker
1060 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1061 zonkers = append(zonkers, z)
1062 }
1063 templinfo := getInfo(r)
1064 templinfo["Zonkers"] = zonkers
1065 templinfo["KillCSRF"] = login.GetCSRF("killitwithfire", r)
1066 err = readviews.Execute(w, "zonkers.html", templinfo)
1067 if err != nil {
1068 log.Print(err)
1069 }
1070}
1071
1072func killitwithfire(w http.ResponseWriter, r *http.Request) {
1073 userinfo := login.GetUserInfo(r)
1074 itsok := r.FormValue("itsok")
1075 if itsok == "iforgiveyou" {
1076 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1077 db := opendatabase()
1078 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1079 userinfo.UserID, zonkerid)
1080 bitethethumbs()
1081 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1082 return
1083 }
1084 wherefore := r.FormValue("wherefore")
1085 name := r.FormValue("name")
1086 if name == "" {
1087 return
1088 }
1089 switch wherefore {
1090 case "zonker":
1091 case "zurl":
1092 case "zonvoy":
1093 default:
1094 return
1095 }
1096 db := opendatabase()
1097 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1098 userinfo.UserID, name, wherefore)
1099 if wherefore == "zonker" || wherefore == "zurl" {
1100 bitethethumbs()
1101 }
1102
1103 http.Redirect(w, r, "/killzone", http.StatusSeeOther)
1104}
1105
1106func accountpage(w http.ResponseWriter, r *http.Request) {
1107 u := login.GetUserInfo(r)
1108 user, _ := butwhatabout(u.Username)
1109 templinfo := getInfo(r)
1110 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1111 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1112 templinfo["WhatAbout"] = user.About
1113 err := readviews.Execute(w, "account.html", templinfo)
1114 if err != nil {
1115 log.Print(err)
1116 }
1117}
1118
1119func dochpass(w http.ResponseWriter, r *http.Request) {
1120 err := login.ChangePassword(w, r)
1121 if err != nil {
1122 log.Printf("error changing password: %s", err)
1123 }
1124 http.Redirect(w, r, "/account", http.StatusSeeOther)
1125}
1126
1127func fingerlicker(w http.ResponseWriter, r *http.Request) {
1128 orig := r.FormValue("resource")
1129
1130 log.Printf("finger lick: %s", orig)
1131
1132 if strings.HasPrefix(orig, "acct:") {
1133 orig = orig[5:]
1134 }
1135
1136 name := orig
1137 idx := strings.LastIndexByte(name, '/')
1138 if idx != -1 {
1139 name = name[idx+1:]
1140 if "https://"+serverName+"/u/"+name != orig {
1141 log.Printf("foreign request rejected")
1142 name = ""
1143 }
1144 } else {
1145 idx = strings.IndexByte(name, '@')
1146 if idx != -1 {
1147 name = name[:idx]
1148 if name+"@"+serverName != orig {
1149 log.Printf("foreign request rejected")
1150 name = ""
1151 }
1152 }
1153 }
1154 user, err := butwhatabout(name)
1155 if err != nil {
1156 http.NotFound(w, r)
1157 return
1158 }
1159
1160 j := NewJunk()
1161 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1162 j["aliases"] = []string{user.URL}
1163 var links []map[string]interface{}
1164 l := NewJunk()
1165 l["rel"] = "self"
1166 l["type"] = `application/activity+json`
1167 l["href"] = user.URL
1168 links = append(links, l)
1169 j["links"] = links
1170
1171 w.Header().Set("Cache-Control", "max-age=3600")
1172 w.Header().Set("Content-Type", "application/jrd+json")
1173 WriteJunk(w, j)
1174}
1175
1176func somedays() string {
1177 secs := 432000 + notrand.Int63n(432000)
1178 return fmt.Sprintf("%d", secs)
1179}
1180
1181func avatate(w http.ResponseWriter, r *http.Request) {
1182 n := r.FormValue("a")
1183 a := avatar(n)
1184 w.Header().Set("Cache-Control", "max-age="+somedays())
1185 w.Write(a)
1186}
1187
1188func servecss(w http.ResponseWriter, r *http.Request) {
1189 w.Header().Set("Cache-Control", "max-age=7776000")
1190 http.ServeFile(w, r, "views"+r.URL.Path)
1191}
1192func servehtml(w http.ResponseWriter, r *http.Request) {
1193 templinfo := getInfo(r)
1194 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1195 if err != nil {
1196 log.Print(err)
1197 }
1198}
1199func serveemu(w http.ResponseWriter, r *http.Request) {
1200 xid := mux.Vars(r)["xid"]
1201 w.Header().Set("Cache-Control", "max-age="+somedays())
1202 http.ServeFile(w, r, "emus/"+xid)
1203}
1204
1205func servefile(w http.ResponseWriter, r *http.Request) {
1206 xid := mux.Vars(r)["xid"]
1207 row := stmtFileData.QueryRow(xid)
1208 var media string
1209 var data []byte
1210 err := row.Scan(&media, &data)
1211 if err != nil {
1212 log.Printf("error loading file: %s", err)
1213 http.NotFound(w, r)
1214 return
1215 }
1216 w.Header().Set("Content-Type", media)
1217 w.Header().Set("X-Content-Type-Options", "nosniff")
1218 w.Header().Set("Cache-Control", "max-age="+somedays())
1219 w.Write(data)
1220}
1221
1222func serve() {
1223 db := opendatabase()
1224 login.Init(db)
1225
1226 listener, err := openListener()
1227 if err != nil {
1228 log.Fatal(err)
1229 }
1230 go redeliverator()
1231
1232 debug := false
1233 getconfig("debug", &debug)
1234 readviews = templates.Load(debug,
1235 "views/honkpage.html",
1236 "views/honkers.html",
1237 "views/zonkers.html",
1238 "views/combos.html",
1239 "views/honkform.html",
1240 "views/honk.html",
1241 "views/account.html",
1242 "views/login.html",
1243 "views/header.html",
1244 )
1245 if !debug {
1246 s := "views/style.css"
1247 savedstyleparams[s] = getstyleparam(s)
1248 s = "views/local.css"
1249 savedstyleparams[s] = getstyleparam(s)
1250 }
1251
1252 bitethethumbs()
1253
1254 mux := mux.NewRouter()
1255 mux.Use(login.Checker)
1256
1257 posters := mux.Methods("POST").Subrouter()
1258 getters := mux.Methods("GET").Subrouter()
1259
1260 getters.HandleFunc("/", homepage)
1261 getters.HandleFunc("/rss", showrss)
1262 getters.HandleFunc("/u/{name:[[:alnum:]]+}", showuser)
1263 getters.HandleFunc("/u/{name:[[:alnum:]]+}/h/{xid:[[:alnum:]]+}", showhonk)
1264 getters.HandleFunc("/u/{name:[[:alnum:]]+}/rss", showrss)
1265 posters.HandleFunc("/u/{name:[[:alnum:]]+}/inbox", inbox)
1266 getters.HandleFunc("/u/{name:[[:alnum:]]+}/outbox", outbox)
1267 getters.HandleFunc("/u/{name:[[:alnum:]]+}/followers", emptiness)
1268 getters.HandleFunc("/u/{name:[[:alnum:]]+}/following", emptiness)
1269 getters.HandleFunc("/a", avatate)
1270 getters.HandleFunc("/t", showconvoy)
1271 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1272 getters.HandleFunc("/emu/{xid:[[:alnum:]_.]+}", serveemu)
1273 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1274
1275 getters.HandleFunc("/style.css", servecss)
1276 getters.HandleFunc("/local.css", servecss)
1277 getters.HandleFunc("/login", servehtml)
1278 posters.HandleFunc("/dologin", login.LoginFunc)
1279 getters.HandleFunc("/logout", login.LogoutFunc)
1280
1281 loggedin := mux.NewRoute().Subrouter()
1282 loggedin.Use(login.Required)
1283 loggedin.HandleFunc("/account", accountpage)
1284 loggedin.HandleFunc("/chpass", dochpass)
1285 loggedin.HandleFunc("/atme", homepage)
1286 loggedin.HandleFunc("/killzone", killzone)
1287 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1288 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1289 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1290 loggedin.Handle("/killitwithfire", login.CSRFWrap("killitwithfire", http.HandlerFunc(killitwithfire)))
1291 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1292 loggedin.HandleFunc("/honkers", showhonkers)
1293 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1294 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1295 loggedin.HandleFunc("/c", showcombos)
1296 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1297
1298 err = http.Serve(listener, mux)
1299 if err != nil {
1300 log.Fatal(err)
1301 }
1302}
1303
1304var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1305var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1306var stmtHonksForUser, stmtHonksForMe, stmtSaveDub *sql.Stmt
1307var stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1308var stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1309var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1310var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1311var stmtGetBoxes, stmtSaveBoxes *sql.Stmt
1312
1313func preparetodie(db *sql.DB, s string) *sql.Stmt {
1314 stmt, err := db.Prepare(s)
1315 if err != nil {
1316 log.Fatalf("error %s: %s", err, s)
1317 }
1318 return stmt
1319}
1320
1321func prepareStatements(db *sql.DB) {
1322 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")
1323 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1324 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1325 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1326 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1327 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1328
1329 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 "
1330 limit := " order by honkid desc limit 250"
1331 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1332 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1333 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
1334 stmtUserHonks = preparetodie(db, selecthonks+"where whofore = 2 and username = ? and dt > ?"+limit)
1335 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)
1336 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1337 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1338 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1339 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or whofore = 2) and convoy = ?"+limit)
1340
1341 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1342 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1343 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1344 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1345 stmtZonkIt = preparetodie(db, "delete from honks where userid = ? and xid = ?")
1346 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1347 stmtFindFile = preparetodie(db, "select fileid from files where url = ?")
1348 stmtSaveFile = preparetodie(db, "insert into files (xid, name, url, media, content) values (?, ?, ?, ?, ?)")
1349 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey from users where username = ?")
1350 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1351 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1352 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1353 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1354 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1355 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zurl')")
1356 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1357 stmtGetBoxes = preparetodie(db, "select ibox, obox, sbox from xonkers where xid = ?")
1358 stmtSaveBoxes = preparetodie(db, "insert into xonkers (xid, ibox, obox, sbox, pubkey) values (?, ?, ?, ?, ?)")
1359}
1360
1361func ElaborateUnitTests() {
1362}
1363
1364func finishusersetup() error {
1365 db := opendatabase()
1366 k, err := rsa.GenerateKey(rand.Reader, 2048)
1367 if err != nil {
1368 return err
1369 }
1370 pubkey, err := zem(&k.PublicKey)
1371 if err != nil {
1372 return err
1373 }
1374 seckey, err := zem(k)
1375 if err != nil {
1376 return err
1377 }
1378 _, err = db.Exec("update users set displayname = username, about = ?, pubkey = ?, seckey = ? where userid = 1", "what about me?", pubkey, seckey)
1379 if err != nil {
1380 return err
1381 }
1382 return nil
1383}
1384
1385func main() {
1386 cmd := "run"
1387 if len(os.Args) > 1 {
1388 cmd = os.Args[1]
1389 }
1390 switch cmd {
1391 case "init":
1392 initdb()
1393 case "upgrade":
1394 upgradedb()
1395 }
1396 db := opendatabase()
1397 dbversion := 0
1398 getconfig("dbversion", &dbversion)
1399 if dbversion != myVersion {
1400 log.Fatal("incorrect database version. run upgrade.")
1401 }
1402 getconfig("servername", &serverName)
1403 prepareStatements(db)
1404 switch cmd {
1405 case "ping":
1406 if len(os.Args) < 4 {
1407 fmt.Printf("usage: honk ping from to\n")
1408 return
1409 }
1410 name := os.Args[2]
1411 targ := os.Args[3]
1412 user, err := butwhatabout(name)
1413 if err != nil {
1414 log.Printf("unknown user")
1415 return
1416 }
1417 ping(user, targ)
1418 case "peep":
1419 peeppeep()
1420 case "run":
1421 serve()
1422 case "test":
1423 ElaborateUnitTests()
1424 default:
1425 log.Fatal("unknown command")
1426 }
1427}