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