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 "database/sql"
21 "fmt"
22 "html"
23 "html/template"
24 "io"
25 "io/ioutil"
26 "log"
27 notrand "math/rand"
28 "net/http"
29 "net/url"
30 "os"
31 "sort"
32 "strconv"
33 "strings"
34 "time"
35
36 "github.com/gorilla/mux"
37 "humungus.tedunangst.com/r/webs/css"
38 "humungus.tedunangst.com/r/webs/htfilter"
39 "humungus.tedunangst.com/r/webs/httpsig"
40 "humungus.tedunangst.com/r/webs/image"
41 "humungus.tedunangst.com/r/webs/junk"
42 "humungus.tedunangst.com/r/webs/login"
43 "humungus.tedunangst.com/r/webs/rss"
44 "humungus.tedunangst.com/r/webs/templates"
45)
46
47type WhatAbout struct {
48 ID int64
49 Name string
50 Display string
51 About string
52 Key string
53 URL string
54 SkinnyCSS bool
55}
56
57type Honk struct {
58 ID int64
59 UserID int64
60 Username string
61 What string
62 Honker string
63 Handle string
64 Oonker string
65 Oondle string
66 XID string
67 RID string
68 Date time.Time
69 URL string
70 Noise string
71 Precis string
72 Convoy string
73 Audience []string
74 Public bool
75 Whofore int64
76 Replies []*Honk
77 Flags int64
78 HTML template.HTML
79 Style string
80 Open string
81 Donks []*Donk
82 Onts []string
83}
84
85const (
86 flagIsAcked = 1
87 flagIsBonked = 2
88)
89
90func (honk *Honk) IsAcked() bool {
91 return honk.Flags&flagIsAcked != 0
92}
93
94func (honk *Honk) IsBonked() bool {
95 return honk.Flags&flagIsBonked != 0
96}
97
98type Donk struct {
99 FileID int64
100 XID string
101 Name string
102 Desc string
103 URL string
104 Media string
105 Local bool
106 Content []byte
107}
108
109type Honker struct {
110 ID int64
111 UserID int64
112 Name string
113 XID string
114 Handle string
115 Flavor string
116 Combos []string
117}
118
119var serverName string
120var iconName = "icon.png"
121var serverMsg = "Things happen."
122
123var userSep = "u"
124var honkSep = "h"
125
126var readviews *templates.Template
127
128func getuserstyle(u *login.UserInfo) template.CSS {
129 if u == nil {
130 return ""
131 }
132 user, _ := butwhatabout(u.Username)
133 if user.SkinnyCSS {
134 return "main { max-width: 700px; }"
135 }
136 return ""
137}
138
139func getInfo(r *http.Request) map[string]interface{} {
140 u := login.GetUserInfo(r)
141 templinfo := make(map[string]interface{})
142 templinfo["StyleParam"] = getstyleparam("views/style.css")
143 templinfo["LocalStyleParam"] = getstyleparam("views/local.css")
144 templinfo["UserStyle"] = getuserstyle(u)
145 templinfo["ServerName"] = serverName
146 templinfo["IconName"] = iconName
147 templinfo["UserInfo"] = u
148 templinfo["UserSep"] = userSep
149 return templinfo
150}
151
152var donotfedafterdark = make(map[string]bool)
153
154func stealthed(r *http.Request) bool {
155 addr := r.Header.Get("X-Forwarded-For")
156 fake := donotfedafterdark[addr]
157 if fake {
158 log.Printf("faking 404 for %s", addr)
159 }
160 return fake
161}
162
163func homepage(w http.ResponseWriter, r *http.Request) {
164 templinfo := getInfo(r)
165 u := login.GetUserInfo(r)
166 var honks []*Honk
167 var userid int64 = -1
168 if r.URL.Path == "/front" || u == nil {
169 honks = getpublichonks()
170 } else {
171 userid = u.UserID
172 if r.URL.Path == "/atme" {
173 templinfo["PageName"] = "atme"
174 honks = gethonksforme(userid)
175 } else {
176 templinfo["PageName"] = "home"
177 honks = gethonksforuser(userid)
178 honks = osmosis(honks, userid)
179 }
180 if len(honks) > 0 {
181 templinfo["TopXID"] = honks[0].XID
182 }
183 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
184 }
185
186 tname := "honkpage.html"
187 if topxid := r.FormValue("topxid"); topxid != "" {
188 for i, h := range honks {
189 if h.XID == topxid {
190 honks = honks[0:i]
191 break
192 }
193 }
194 log.Printf("topxid %d frags", len(honks))
195 tname = "honkfrags.html"
196 }
197
198 reverbolate(userid, honks)
199
200 templinfo["Honks"] = honks
201 templinfo["ShowRSS"] = true
202 templinfo["ServerMessage"] = serverMsg
203 if u == nil {
204 w.Header().Set("Cache-Control", "max-age=60")
205 } else {
206 w.Header().Set("Cache-Control", "max-age=0")
207 }
208 w.Header().Set("Content-Type", "text/html; charset=utf-8")
209
210 err := readviews.Execute(w, tname, templinfo)
211 if err != nil {
212 log.Print(err)
213 }
214}
215
216func showfunzone(w http.ResponseWriter, r *http.Request) {
217 var emunames, memenames []string
218 dir, err := os.Open("emus")
219 if err == nil {
220 emunames, _ = dir.Readdirnames(0)
221 dir.Close()
222 }
223 for i, e := range emunames {
224 if len(e) > 4 {
225 emunames[i] = e[:len(e)-4]
226 }
227 }
228 dir, err = os.Open("memes")
229 if err == nil {
230 memenames, _ = dir.Readdirnames(0)
231 dir.Close()
232 }
233 templinfo := getInfo(r)
234 templinfo["Emus"] = emunames
235 templinfo["Memes"] = memenames
236 err = readviews.Execute(w, "funzone.html", templinfo)
237 if err != nil {
238 log.Print(err)
239 }
240
241}
242
243func showrss(w http.ResponseWriter, r *http.Request) {
244 name := mux.Vars(r)["name"]
245
246 var honks []*Honk
247 if name != "" {
248 honks = gethonksbyuser(name, false)
249 } else {
250 honks = getpublichonks()
251 }
252 if len(honks) > 20 {
253 honks = honks[0:20]
254 }
255 reverbolate(-1, honks)
256
257 home := fmt.Sprintf("https://%s/", serverName)
258 base := home
259 if name != "" {
260 home += "u/" + name
261 name += " "
262 }
263 feed := rss.Feed{
264 Title: name + "honk",
265 Link: home,
266 Description: name + "honk rss",
267 Image: &rss.Image{
268 URL: base + "icon.png",
269 Title: name + "honk rss",
270 Link: home,
271 },
272 }
273 var modtime time.Time
274 for _, honk := range honks {
275 if !firstclass(honk) {
276 continue
277 }
278 desc := string(honk.HTML)
279 for _, d := range honk.Donks {
280 desc += fmt.Sprintf(`<p><a href="%s">Attachment: %s</a>`,
281 d.URL, html.EscapeString(d.Name))
282 }
283
284 feed.Items = append(feed.Items, &rss.Item{
285 Title: fmt.Sprintf("%s %s %s", honk.Username, honk.What, honk.XID),
286 Description: rss.CData{desc},
287 Link: honk.URL,
288 PubDate: honk.Date.Format(time.RFC1123),
289 Guid: &rss.Guid{IsPermaLink: true, Value: honk.URL},
290 })
291 if honk.Date.After(modtime) {
292 modtime = honk.Date
293 }
294 }
295 w.Header().Set("Cache-Control", "max-age=300")
296 w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
297
298 err := feed.Write(w)
299 if err != nil {
300 log.Printf("error writing rss: %s", err)
301 }
302}
303
304func butwhatabout(name string) (*WhatAbout, error) {
305 row := stmtWhatAbout.QueryRow(name)
306 var user WhatAbout
307 var options string
308 err := row.Scan(&user.ID, &user.Name, &user.Display, &user.About, &user.Key, &options)
309 user.URL = fmt.Sprintf("https://%s/%s/%s", serverName, userSep, user.Name)
310 user.SkinnyCSS = strings.Contains(options, " skinny ")
311 return &user, err
312}
313
314func crappola(j junk.Junk) bool {
315 t, _ := j.GetString("type")
316 a, _ := j.GetString("actor")
317 o, _ := j.GetString("object")
318 if t == "Delete" && a == o {
319 log.Printf("crappola from %s", a)
320 return true
321 }
322 return false
323}
324
325func ping(user *WhatAbout, who string) {
326 box, err := getboxes(who)
327 if err != nil {
328 log.Printf("no inbox for ping: %s", err)
329 return
330 }
331 j := junk.New()
332 j["@context"] = itiswhatitis
333 j["type"] = "Ping"
334 j["id"] = user.URL + "/ping/" + xfiltrate()
335 j["actor"] = user.URL
336 j["to"] = who
337 keyname, key := ziggy(user.Name)
338 err = PostJunk(keyname, key, box.In, j)
339 if err != nil {
340 log.Printf("can't send ping: %s", err)
341 return
342 }
343 log.Printf("sent ping to %s: %s", who, j["id"])
344}
345
346func pong(user *WhatAbout, who string, obj string) {
347 box, err := getboxes(who)
348 if err != nil {
349 log.Printf("no inbox for pong %s : %s", who, err)
350 return
351 }
352 j := junk.New()
353 j["@context"] = itiswhatitis
354 j["type"] = "Pong"
355 j["id"] = user.URL + "/pong/" + xfiltrate()
356 j["actor"] = user.URL
357 j["to"] = who
358 j["object"] = obj
359 keyname, key := ziggy(user.Name)
360 err = PostJunk(keyname, key, box.In, j)
361 if err != nil {
362 log.Printf("can't send pong: %s", err)
363 return
364 }
365}
366
367func inbox(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 var buf bytes.Buffer
375 io.Copy(&buf, r.Body)
376 payload := buf.Bytes()
377 j, err := junk.Read(bytes.NewReader(payload))
378 if err != nil {
379 log.Printf("bad payload: %s", err)
380 io.WriteString(os.Stdout, "bad payload\n")
381 os.Stdout.Write(payload)
382 io.WriteString(os.Stdout, "\n")
383 return
384 }
385 if crappola(j) {
386 return
387 }
388 keyname, err := httpsig.VerifyRequest(r, payload, zaggy)
389 if err != nil {
390 log.Printf("inbox message failed signature: %s", err)
391 if keyname != "" {
392 keyname, err = makeitworksomehowwithoutregardforkeycontinuity(keyname, r, payload)
393 if err != nil {
394 log.Printf("still failed: %s", err)
395 }
396 }
397 if err != nil {
398 return
399 }
400 }
401 what, _ := j.GetString("type")
402 if what == "Like" {
403 return
404 }
405 who, _ := j.GetString("actor")
406 origin := keymatch(keyname, who)
407 if origin == "" {
408 log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
409 return
410 }
411 objid, _ := j.GetString("id")
412 if thoudostbitethythumb(user.ID, []string{who}, objid) {
413 log.Printf("ignoring thumb sucker %s", who)
414 return
415 }
416 switch what {
417 case "Ping":
418 obj, _ := j.GetString("id")
419 log.Printf("ping from %s: %s", who, obj)
420 pong(user, who, obj)
421 case "Pong":
422 obj, _ := j.GetString("object")
423 log.Printf("pong from %s: %s", who, obj)
424 case "Follow":
425 obj, _ := j.GetString("object")
426 if obj == user.URL {
427 log.Printf("updating honker follow: %s", who)
428 stmtSaveDub.Exec(user.ID, who, who, "dub")
429 go rubadubdub(user, j)
430 } else {
431 log.Printf("can't follow %s", obj)
432 }
433 case "Accept":
434 log.Printf("updating honker accept: %s", who)
435 _, err = stmtUpdateFlavor.Exec("sub", user.ID, who, "presub")
436 if err != nil {
437 log.Printf("error updating honker: %s", err)
438 return
439 }
440 case "Update":
441 obj, ok := j.GetMap("object")
442 if ok {
443 what, _ := obj.GetString("type")
444 switch what {
445 case "Person":
446 return
447 case "Question":
448 return
449 }
450 }
451 log.Printf("unknown Update activity")
452 fd, _ := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
453 j.Write(fd)
454 io.WriteString(fd, "\n")
455 fd.Close()
456
457 case "Undo":
458 obj, ok := j.GetMap("object")
459 if !ok {
460 log.Printf("unknown undo no object")
461 } else {
462 what, _ := obj.GetString("type")
463 switch what {
464 case "Follow":
465 log.Printf("updating honker undo: %s", who)
466 _, err = stmtUpdateFlavor.Exec("undub", user.ID, who, "dub")
467 if err != nil {
468 log.Printf("error updating honker: %s", err)
469 return
470 }
471 case "Announce":
472 xid, _ := obj.GetString("object")
473 log.Printf("undo announce: %s", xid)
474 case "Like":
475 default:
476 log.Printf("unknown undo: %s", what)
477 }
478 }
479 default:
480 go consumeactivity(user, j, origin)
481 }
482}
483
484func ximport(w http.ResponseWriter, r *http.Request) {
485 xid := r.FormValue("xid")
486 p, _ := investigate(xid)
487 if p != nil {
488 xid = p.XID
489 }
490 j, err := GetJunk(xid)
491 if err != nil {
492 http.Error(w, "error getting external object", http.StatusInternalServerError)
493 log.Printf("error getting external object: %s", err)
494 return
495 }
496 log.Printf("importing %s", xid)
497 u := login.GetUserInfo(r)
498 user, _ := butwhatabout(u.Username)
499
500 what, _ := j.GetString("type")
501 if isactor(what) {
502 outbox, _ := j.GetString("outbox")
503 gimmexonks(user, outbox)
504 http.Redirect(w, r, "/h?xid="+url.QueryEscape(xid), http.StatusSeeOther)
505 return
506 }
507 xonk := xonkxonk(user, j, originate(xid))
508 convoy := ""
509 if xonk != nil {
510 convoy = xonk.Convoy
511 savexonk(user, xonk)
512 }
513 http.Redirect(w, r, "/t?c="+url.QueryEscape(convoy), http.StatusSeeOther)
514}
515
516func xzone(w http.ResponseWriter, r *http.Request) {
517 u := login.GetUserInfo(r)
518 rows, err := stmtRecentHonkers.Query(u.UserID, u.UserID)
519 if err != nil {
520 log.Printf("query err: %s", err)
521 return
522 }
523 defer rows.Close()
524 var honkers []Honker
525 for rows.Next() {
526 var xid string
527 rows.Scan(&xid)
528 honkers = append(honkers, Honker{XID: xid})
529 }
530 rows.Close()
531 for i, _ := range honkers {
532 _, honkers[i].Handle = handles(honkers[i].XID)
533 }
534 templinfo := getInfo(r)
535 templinfo["XCSRF"] = login.GetCSRF("ximport", r)
536 templinfo["Honkers"] = honkers
537 err = readviews.Execute(w, "xzone.html", templinfo)
538 if err != nil {
539 log.Print(err)
540 }
541}
542
543func outbox(w http.ResponseWriter, r *http.Request) {
544 name := mux.Vars(r)["name"]
545 user, err := butwhatabout(name)
546 if err != nil {
547 http.NotFound(w, r)
548 return
549 }
550 if stealthed(r) {
551 http.NotFound(w, r)
552 return
553 }
554
555 honks := gethonksbyuser(name, false)
556 if len(honks) > 20 {
557 honks = honks[0:20]
558 }
559
560 var jonks []junk.Junk
561 for _, h := range honks {
562 j, _ := jonkjonk(user, h)
563 jonks = append(jonks, j)
564 }
565
566 j := junk.New()
567 j["@context"] = itiswhatitis
568 j["id"] = user.URL + "/outbox"
569 j["type"] = "OrderedCollection"
570 j["totalItems"] = len(jonks)
571 j["orderedItems"] = jonks
572
573 w.Header().Set("Content-Type", theonetruename)
574 j.Write(w)
575}
576
577func emptiness(w http.ResponseWriter, r *http.Request) {
578 name := mux.Vars(r)["name"]
579 user, err := butwhatabout(name)
580 if err != nil {
581 http.NotFound(w, r)
582 return
583 }
584 colname := "/followers"
585 if strings.HasSuffix(r.URL.Path, "/following") {
586 colname = "/following"
587 }
588 j := junk.New()
589 j["@context"] = itiswhatitis
590 j["id"] = user.URL + colname
591 j["type"] = "OrderedCollection"
592 j["totalItems"] = 0
593 j["orderedItems"] = []junk.Junk{}
594
595 w.Header().Set("Content-Type", theonetruename)
596 j.Write(w)
597}
598
599func showuser(w http.ResponseWriter, r *http.Request) {
600 name := mux.Vars(r)["name"]
601 user, err := butwhatabout(name)
602 if err != nil {
603 log.Printf("user not found %s: %s", name, err)
604 http.NotFound(w, r)
605 return
606 }
607 if friendorfoe(r.Header.Get("Accept")) {
608 j := asjonker(user)
609 w.Header().Set("Content-Type", theonetruename)
610 j.Write(w)
611 return
612 }
613 u := login.GetUserInfo(r)
614 honks := gethonksbyuser(name, u != nil && u.Username == name)
615 honkpage(w, r, u, user, honks, "")
616}
617
618func showhonker(w http.ResponseWriter, r *http.Request) {
619 u := login.GetUserInfo(r)
620 name := mux.Vars(r)["name"]
621 var honks []*Honk
622 if name == "" {
623 name = r.FormValue("xid")
624 honks = gethonksbyxonker(u.UserID, name)
625 } else {
626 honks = gethonksbyhonker(u.UserID, name)
627 }
628 name = html.EscapeString(name)
629 msg := fmt.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>`, name, name)
630 honkpage(w, r, u, nil, honks, template.HTML(msg))
631}
632
633func showcombo(w http.ResponseWriter, r *http.Request) {
634 name := mux.Vars(r)["name"]
635 u := login.GetUserInfo(r)
636 honks := gethonksbycombo(u.UserID, name)
637 honks = osmosis(honks, u.UserID)
638 honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks by combo: "+name)))
639}
640func showconvoy(w http.ResponseWriter, r *http.Request) {
641 c := r.FormValue("c")
642 u := login.GetUserInfo(r)
643 honks := gethonksbyconvoy(u.UserID, c)
644 honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks in convoy: "+c)))
645}
646func showsearch(w http.ResponseWriter, r *http.Request) {
647 q := r.FormValue("q")
648 u := login.GetUserInfo(r)
649 honks := gethonksbysearch(u.UserID, q)
650 honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks for search: "+q)))
651}
652func showontology(w http.ResponseWriter, r *http.Request) {
653 name := mux.Vars(r)["name"]
654 u := login.GetUserInfo(r)
655 var userid int64 = -1
656 if u != nil {
657 userid = u.UserID
658 }
659 honks := gethonksbyontology(userid, "#"+name)
660 honkpage(w, r, u, nil, honks, template.HTML(html.EscapeString("honks by ontology: "+name)))
661}
662
663func thelistingoftheontologies(w http.ResponseWriter, r *http.Request) {
664 u := login.GetUserInfo(r)
665 var userid int64 = -1
666 if u != nil {
667 userid = u.UserID
668 }
669 rows, err := stmtSelectOnts.Query(userid)
670 if err != nil {
671 log.Printf("selection error: %s", err)
672 return
673 }
674 var onts [][]string
675 for rows.Next() {
676 var o string
677 err := rows.Scan(&o)
678 if err != nil {
679 log.Printf("error scanning ont: %s", err)
680 continue
681 }
682 onts = append(onts, []string{o, o[1:]})
683 }
684 if u == nil {
685 w.Header().Set("Cache-Control", "max-age=300")
686 }
687 templinfo := getInfo(r)
688 templinfo["Onts"] = onts
689 err = readviews.Execute(w, "onts.html", templinfo)
690 if err != nil {
691 log.Print(err)
692 }
693}
694
695func showhonk(w http.ResponseWriter, r *http.Request) {
696 name := mux.Vars(r)["name"]
697 user, err := butwhatabout(name)
698 if err != nil {
699 http.NotFound(w, r)
700 return
701 }
702 if stealthed(r) {
703 http.NotFound(w, r)
704 return
705 }
706
707 xid := fmt.Sprintf("https://%s%s", serverName, r.URL.Path)
708 honk := getxonk(user.ID, xid)
709 if honk == nil {
710 http.NotFound(w, r)
711 return
712 }
713 u := login.GetUserInfo(r)
714 if u != nil && u.UserID != user.ID {
715 u = nil
716 }
717 if !honk.Public {
718 if u == nil {
719 http.NotFound(w, r)
720 return
721
722 }
723 honkpage(w, r, u, nil, []*Honk{honk}, "one honk maybe more")
724 return
725 }
726 rawhonks := gethonksbyconvoy(honk.UserID, honk.Convoy)
727 if friendorfoe(r.Header.Get("Accept")) {
728 for _, h := range rawhonks {
729 if h.RID == honk.XID && h.Public && (h.Whofore == 2 || h.IsAcked()) {
730 honk.Replies = append(honk.Replies, h)
731 }
732 }
733 donksforhonks([]*Honk{honk})
734 _, j := jonkjonk(user, honk)
735 j["@context"] = itiswhatitis
736 w.Header().Set("Content-Type", theonetruename)
737 j.Write(w)
738 return
739 }
740 var honks []*Honk
741 for _, h := range rawhonks {
742 if h.Public && (h.Whofore == 2 || h.IsAcked()) {
743 honks = append(honks, h)
744 }
745 }
746
747 honkpage(w, r, u, nil, honks, "one honk maybe more")
748}
749
750func honkpage(w http.ResponseWriter, r *http.Request, u *login.UserInfo, user *WhatAbout,
751 honks []*Honk, infomsg template.HTML) {
752 templinfo := getInfo(r)
753 var userid int64 = -1
754 if u != nil {
755 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
756 userid = u.UserID
757 }
758 if u == nil {
759 w.Header().Set("Cache-Control", "max-age=60")
760 }
761 reverbolate(userid, honks)
762 if user != nil {
763 filt := htfilter.New()
764 templinfo["Name"] = user.Name
765 whatabout := user.About
766 whatabout = obfusbreak(user.About)
767 templinfo["WhatAbout"], _ = filt.String(whatabout)
768 }
769 templinfo["Honks"] = honks
770 templinfo["ServerMessage"] = infomsg
771 err := readviews.Execute(w, "honkpage.html", templinfo)
772 if err != nil {
773 log.Print(err)
774 }
775}
776
777func saveuser(w http.ResponseWriter, r *http.Request) {
778 whatabout := r.FormValue("whatabout")
779 u := login.GetUserInfo(r)
780 db := opendatabase()
781 options := ""
782 if r.FormValue("skinny") == "skinny" {
783 options += " skinny "
784 }
785 _, err := db.Exec("update users set about = ?, options = ? where username = ?", whatabout, options, u.Username)
786 if err != nil {
787 log.Printf("error bouting what: %s", err)
788 }
789
790 http.Redirect(w, r, "/account", http.StatusSeeOther)
791}
792
793func gethonkers(userid int64) []*Honker {
794 rows, err := stmtHonkers.Query(userid)
795 if err != nil {
796 log.Printf("error querying honkers: %s", err)
797 return nil
798 }
799 defer rows.Close()
800 var honkers []*Honker
801 for rows.Next() {
802 var f Honker
803 var combos string
804 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor, &combos)
805 f.Combos = strings.Split(strings.TrimSpace(combos), " ")
806 if err != nil {
807 log.Printf("error scanning honker: %s", err)
808 return nil
809 }
810 honkers = append(honkers, &f)
811 }
812 return honkers
813}
814
815func getdubs(userid int64) []*Honker {
816 rows, err := stmtDubbers.Query(userid)
817 if err != nil {
818 log.Printf("error querying dubs: %s", err)
819 return nil
820 }
821 defer rows.Close()
822 var honkers []*Honker
823 for rows.Next() {
824 var f Honker
825 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
826 if err != nil {
827 log.Printf("error scanning honker: %s", err)
828 return nil
829 }
830 honkers = append(honkers, &f)
831 }
832 return honkers
833}
834
835func allusers() []login.UserInfo {
836 var users []login.UserInfo
837 rows, _ := opendatabase().Query("select userid, username from users")
838 defer rows.Close()
839 for rows.Next() {
840 var u login.UserInfo
841 rows.Scan(&u.UserID, &u.Username)
842 users = append(users, u)
843 }
844 return users
845}
846
847func getxonk(userid int64, xid string) *Honk {
848 h := new(Honk)
849 var dt, aud, onts string
850 row := stmtOneXonk.QueryRow(userid, xid)
851 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
852 &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags, &onts)
853 if err != nil {
854 if err != sql.ErrNoRows {
855 log.Printf("error scanning xonk: %s", err)
856 }
857 return nil
858 }
859 h.Date, _ = time.Parse(dbtimeformat, dt)
860 h.Audience = strings.Split(aud, " ")
861 h.Public = !keepitquiet(h.Audience)
862 if len(onts) > 0 {
863 h.Onts = strings.Split(onts, " ")
864 }
865 return h
866}
867
868func getbonk(userid int64, xid string) *Honk {
869 h := new(Honk)
870 var dt, aud, onts string
871 row := stmtOneBonk.QueryRow(userid, xid)
872 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
873 &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags, &onts)
874 if err != nil {
875 if err != sql.ErrNoRows {
876 log.Printf("error scanning xonk: %s", err)
877 }
878 return nil
879 }
880 h.Date, _ = time.Parse(dbtimeformat, dt)
881 h.Audience = strings.Split(aud, " ")
882 h.Public = !keepitquiet(h.Audience)
883 if len(onts) > 0 {
884 h.Onts = strings.Split(onts, " ")
885 }
886 return h
887}
888
889func getpublichonks() []*Honk {
890 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
891 rows, err := stmtPublicHonks.Query(dt)
892 return getsomehonks(rows, err)
893}
894func gethonksbyuser(name string, includeprivate bool) []*Honk {
895 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
896 whofore := 2
897 if includeprivate {
898 whofore = 3
899 }
900 rows, err := stmtUserHonks.Query(whofore, name, dt)
901 return getsomehonks(rows, err)
902}
903func gethonksforuser(userid int64) []*Honk {
904 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
905 rows, err := stmtHonksForUser.Query(userid, dt, userid, userid)
906 return getsomehonks(rows, err)
907}
908func gethonksforme(userid int64) []*Honk {
909 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
910 rows, err := stmtHonksForMe.Query(userid, dt, userid)
911 return getsomehonks(rows, err)
912}
913func gethonksbyhonker(userid int64, honker string) []*Honk {
914 rows, err := stmtHonksByHonker.Query(userid, honker, userid)
915 return getsomehonks(rows, err)
916}
917func gethonksbyxonker(userid int64, xonker string) []*Honk {
918 rows, err := stmtHonksByXonker.Query(userid, xonker, xonker, userid)
919 return getsomehonks(rows, err)
920}
921func gethonksbycombo(userid int64, combo string) []*Honk {
922 combo = "% " + combo + " %"
923 rows, err := stmtHonksByCombo.Query(userid, combo, userid)
924 return getsomehonks(rows, err)
925}
926func gethonksbyconvoy(userid int64, convoy string) []*Honk {
927 rows, err := stmtHonksByConvoy.Query(userid, userid, convoy)
928 honks := getsomehonks(rows, err)
929 for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
930 honks[i], honks[j] = honks[j], honks[i]
931 }
932 return honks
933}
934func gethonksbysearch(userid int64, q string) []*Honk {
935 q = "%" + q + "%"
936 rows, err := stmtHonksBySearch.Query(userid, q)
937 honks := getsomehonks(rows, err)
938 return honks
939}
940func gethonksbyontology(userid int64, name string) []*Honk {
941 rows, err := stmtHonksByOntology.Query(name, userid, userid)
942 honks := getsomehonks(rows, err)
943 return honks
944}
945
946func getsomehonks(rows *sql.Rows, err error) []*Honk {
947 if err != nil {
948 log.Printf("error querying honks: %s", err)
949 return nil
950 }
951 defer rows.Close()
952 var honks []*Honk
953 for rows.Next() {
954 var h Honk
955 var dt, aud, onts string
956 err = rows.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID,
957 &h.RID, &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Convoy, &h.Whofore, &h.Flags, &onts)
958 if err != nil {
959 log.Printf("error scanning honks: %s", err)
960 return nil
961 }
962 h.Date, _ = time.Parse(dbtimeformat, dt)
963 h.Audience = strings.Split(aud, " ")
964 h.Public = !keepitquiet(h.Audience)
965 if len(onts) > 0 {
966 h.Onts = strings.Split(onts, " ")
967 }
968 honks = append(honks, &h)
969 }
970 rows.Close()
971 donksforhonks(honks)
972 return honks
973}
974
975func donksforhonks(honks []*Honk) {
976 db := opendatabase()
977 var ids []string
978 hmap := make(map[int64]*Honk)
979 for _, h := range honks {
980 ids = append(ids, fmt.Sprintf("%d", h.ID))
981 hmap[h.ID] = h
982 }
983 q := fmt.Sprintf("select honkid, donks.fileid, xid, name, description, url, media, local from donks join files on donks.fileid = files.fileid where honkid in (%s)", strings.Join(ids, ","))
984 rows, err := db.Query(q)
985 if err != nil {
986 log.Printf("error querying donks: %s", err)
987 return
988 }
989 defer rows.Close()
990 for rows.Next() {
991 var hid int64
992 var d Donk
993 err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.Desc, &d.URL, &d.Media, &d.Local)
994 if err != nil {
995 log.Printf("error scanning donk: %s", err)
996 continue
997 }
998 h := hmap[hid]
999 h.Donks = append(h.Donks, &d)
1000 }
1001}
1002
1003func savebonk(w http.ResponseWriter, r *http.Request) {
1004 xid := r.FormValue("xid")
1005 userinfo := login.GetUserInfo(r)
1006 user, _ := butwhatabout(userinfo.Username)
1007
1008 log.Printf("bonking %s", xid)
1009
1010 xonk := getxonk(userinfo.UserID, xid)
1011 if xonk == nil {
1012 return
1013 }
1014 if !xonk.Public {
1015 return
1016 }
1017 donksforhonks([]*Honk{xonk})
1018
1019 _, err := stmtUpdateFlags.Exec(flagIsBonked, xonk.ID)
1020 if err != nil {
1021 log.Printf("error acking bonk: %s", err)
1022 }
1023
1024 oonker := xonk.Oonker
1025 if oonker == "" {
1026 oonker = xonk.Honker
1027 }
1028 dt := time.Now().UTC()
1029 bonk := Honk{
1030 UserID: userinfo.UserID,
1031 Username: userinfo.Username,
1032 What: "bonk",
1033 Honker: user.URL,
1034 XID: xonk.XID,
1035 Date: dt,
1036 Donks: xonk.Donks,
1037 Convoy: xonk.Convoy,
1038 Audience: []string{thewholeworld, oonker},
1039 Public: true,
1040 }
1041
1042 aud := strings.Join(bonk.Audience, " ")
1043 whofore := 2
1044 onts := xonk.Onts
1045 res, err := stmtSaveHonk.Exec(userinfo.UserID, "bonk", bonk.Honker, xid, "",
1046 dt.Format(dbtimeformat), "", aud, xonk.Noise, xonk.Convoy, whofore, "html",
1047 xonk.Precis, oonker, 0, strings.Join(onts, " "))
1048 if err != nil {
1049 log.Printf("error saving bonk: %s", err)
1050 return
1051 }
1052 bonk.ID, _ = res.LastInsertId()
1053 for _, d := range bonk.Donks {
1054 _, err = stmtSaveDonk.Exec(bonk.ID, d.FileID)
1055 if err != nil {
1056 log.Printf("err saving donk: %s", err)
1057 return
1058 }
1059 }
1060 for _, o := range onts {
1061 _, err = stmtSaveOnts.Exec(strings.ToLower(o), bonk.ID)
1062 if err != nil {
1063 log.Printf("error saving ont: %s", err)
1064 }
1065 }
1066
1067 go honkworldwide(user, &bonk)
1068}
1069
1070func sendzonkofsorts(xonk *Honk, user *WhatAbout, what string) {
1071 zonk := Honk{
1072 What: what,
1073 XID: xonk.XID,
1074 Date: time.Now().UTC(),
1075 Audience: oneofakind(xonk.Audience),
1076 }
1077 zonk.Public = !keepitquiet(zonk.Audience)
1078
1079 log.Printf("announcing %sed honk: %s", what, xonk.XID)
1080 go honkworldwide(user, &zonk)
1081}
1082
1083func zonkit(w http.ResponseWriter, r *http.Request) {
1084 wherefore := r.FormValue("wherefore")
1085 what := r.FormValue("what")
1086 userinfo := login.GetUserInfo(r)
1087 user, _ := butwhatabout(userinfo.Username)
1088
1089 if wherefore == "ack" {
1090 xonk := getxonk(userinfo.UserID, what)
1091 if xonk != nil {
1092 _, err := stmtUpdateFlags.Exec(flagIsAcked, xonk.ID)
1093 if err != nil {
1094 log.Printf("error acking: %s", err)
1095 }
1096 sendzonkofsorts(xonk, user, "ack")
1097 }
1098 return
1099 }
1100
1101 if wherefore == "deack" {
1102 xonk := getxonk(userinfo.UserID, what)
1103 if xonk != nil {
1104 _, err := stmtClearFlags.Exec(flagIsAcked, xonk.ID)
1105 if err != nil {
1106 log.Printf("error deacking: %s", err)
1107 }
1108 sendzonkofsorts(xonk, user, "deack")
1109 }
1110 return
1111 }
1112
1113 if wherefore == "unbonk" {
1114 xonk := getbonk(userinfo.UserID, what)
1115 if xonk != nil {
1116 _, err := stmtZonkDonks.Exec(xonk.ID)
1117 if err != nil {
1118 log.Printf("error zonking: %s", err)
1119 }
1120 _, err = stmtZonkIt.Exec(xonk.ID)
1121 if err != nil {
1122 log.Printf("error zonking: %s", err)
1123 }
1124 xonk = getxonk(userinfo.UserID, what)
1125 _, err = stmtClearFlags.Exec(flagIsBonked, xonk.ID)
1126 if err != nil {
1127 log.Printf("error unbonking: %s", err)
1128 }
1129 sendzonkofsorts(xonk, user, "unbonk")
1130 }
1131 return
1132 }
1133
1134 log.Printf("zonking %s %s", wherefore, what)
1135 if wherefore == "zonk" {
1136 xonk := getxonk(userinfo.UserID, what)
1137 if xonk != nil {
1138 _, err := stmtZonkDonks.Exec(xonk.ID)
1139 if err != nil {
1140 log.Printf("error zonking: %s", err)
1141 }
1142 _, err = stmtZonkIt.Exec(xonk.ID)
1143 if err != nil {
1144 log.Printf("error zonking: %s", err)
1145 }
1146 if xonk.Whofore == 2 || xonk.Whofore == 3 {
1147 sendzonkofsorts(xonk, user, "zonk")
1148 }
1149 }
1150 }
1151 _, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
1152 if err != nil {
1153 log.Printf("error saving zonker: %s", err)
1154 return
1155 }
1156}
1157
1158func savehonk(w http.ResponseWriter, r *http.Request) {
1159 rid := r.FormValue("rid")
1160 noise := r.FormValue("noise")
1161
1162 userinfo := login.GetUserInfo(r)
1163 user, _ := butwhatabout(userinfo.Username)
1164
1165 dt := time.Now().UTC()
1166 xid := fmt.Sprintf("%s/%s/%s", user.URL, honkSep, xfiltrate())
1167 what := "honk"
1168 if rid != "" {
1169 what = "tonk"
1170 }
1171 honk := Honk{
1172 UserID: userinfo.UserID,
1173 Username: userinfo.Username,
1174 What: "honk",
1175 Honker: user.URL,
1176 XID: xid,
1177 Date: dt,
1178 }
1179 if strings.HasPrefix(noise, "DZ:") {
1180 idx := strings.Index(noise, "\n")
1181 if idx == -1 {
1182 honk.Precis = noise
1183 noise = ""
1184 } else {
1185 honk.Precis = noise[:idx]
1186 noise = noise[idx+1:]
1187 }
1188 }
1189 noise = hooterize(noise)
1190 noise = strings.TrimSpace(noise)
1191 honk.Precis = strings.TrimSpace(honk.Precis)
1192
1193 var convoy string
1194 if rid != "" {
1195 xonk := getxonk(userinfo.UserID, rid)
1196 if xonk != nil {
1197 if xonk.Public {
1198 honk.Audience = append(honk.Audience, xonk.Audience...)
1199 }
1200 convoy = xonk.Convoy
1201 } else {
1202 xonkaud, c := whosthere(rid)
1203 honk.Audience = append(honk.Audience, xonkaud...)
1204 convoy = c
1205 }
1206 for i, a := range honk.Audience {
1207 if a == thewholeworld {
1208 honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
1209 break
1210 }
1211 }
1212 honk.RID = rid
1213 } else {
1214 honk.Audience = []string{thewholeworld}
1215 }
1216 if noise != "" && noise[0] == '@' {
1217 honk.Audience = append(grapevine(noise), honk.Audience...)
1218 } else {
1219 honk.Audience = append(honk.Audience, grapevine(noise)...)
1220 }
1221 if convoy == "" {
1222 convoy = "data:,electrichonkytonk-" + xfiltrate()
1223 }
1224 butnottooloud(honk.Audience)
1225 honk.Audience = oneofakind(honk.Audience)
1226 if len(honk.Audience) == 0 {
1227 log.Printf("honk to nowhere")
1228 http.Error(w, "honk to nowhere...", http.StatusNotFound)
1229 return
1230 }
1231 honk.Public = !keepitquiet(honk.Audience)
1232 noise = obfusbreak(noise)
1233 honk.Noise = noise
1234 honk.Convoy = convoy
1235
1236 donkxid := r.FormValue("donkxid")
1237 if donkxid == "" {
1238 file, filehdr, err := r.FormFile("donk")
1239 if err == nil {
1240 var buf bytes.Buffer
1241 io.Copy(&buf, file)
1242 file.Close()
1243 data := buf.Bytes()
1244 xid := xfiltrate()
1245 var media, name string
1246 img, err := image.Vacuum(&buf, image.Params{MaxWidth: 2048, MaxHeight: 2048})
1247 if err == nil {
1248 data = img.Data
1249 format := img.Format
1250 media = "image/" + format
1251 if format == "jpeg" {
1252 format = "jpg"
1253 }
1254 name = xid + "." + format
1255 xid = name
1256 } else {
1257 maxsize := 100000
1258 if len(data) > maxsize {
1259 log.Printf("bad image: %s too much text: %d", err, len(data))
1260 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1261 return
1262 }
1263 for i := 0; i < len(data); i++ {
1264 if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
1265 log.Printf("bad image: %s not text: %d", err, data[i])
1266 http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1267 return
1268 }
1269 }
1270 media = "text/plain"
1271 name = filehdr.Filename
1272 if name == "" {
1273 name = xid + ".txt"
1274 }
1275 xid += ".txt"
1276 }
1277 desc := r.FormValue("donkdesc")
1278 if desc == "" {
1279 desc = name
1280 }
1281 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1282 res, err := stmtSaveFile.Exec(xid, name, desc, url, media, 1, data)
1283 if err != nil {
1284 log.Printf("unable to save image: %s", err)
1285 return
1286 }
1287 var d Donk
1288 d.FileID, _ = res.LastInsertId()
1289 honk.Donks = append(honk.Donks, &d)
1290 donkxid = d.XID
1291 }
1292 } else {
1293 xid := donkxid
1294 url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1295 var donk Donk
1296 row := stmtFindFile.QueryRow(url)
1297 err := row.Scan(&donk.FileID)
1298 if err == nil {
1299 honk.Donks = append(honk.Donks, &donk)
1300 } else {
1301 log.Printf("can't find file: %s", xid)
1302 }
1303 }
1304 herd := herdofemus(honk.Noise)
1305 for _, e := range herd {
1306 donk := savedonk(e.ID, e.Name, e.Name, "image/png", true)
1307 if donk != nil {
1308 donk.Name = e.Name
1309 honk.Donks = append(honk.Donks, donk)
1310 }
1311 }
1312 memetize(&honk)
1313
1314 aud := strings.Join(honk.Audience, " ")
1315 whofore := 2
1316 if !honk.Public {
1317 whofore = 3
1318 }
1319 if r.FormValue("preview") == "preview" {
1320 honks := []*Honk{&honk}
1321 reverbolate(userinfo.UserID, honks)
1322 templinfo := getInfo(r)
1323 templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1324 templinfo["Honks"] = honks
1325 templinfo["InReplyTo"] = r.FormValue("rid")
1326 templinfo["Noise"] = r.FormValue("noise")
1327 templinfo["SavedFile"] = donkxid
1328 templinfo["ServerMessage"] = "honk preview"
1329 err := readviews.Execute(w, "honkpage.html", templinfo)
1330 if err != nil {
1331 log.Print(err)
1332 }
1333 return
1334 }
1335 honk.Onts = oneofakind(ontologies(honk.Noise))
1336 res, err := stmtSaveHonk.Exec(userinfo.UserID, what, honk.Honker, xid, rid,
1337 dt.Format(dbtimeformat), "", aud, honk.Noise, convoy, whofore, "html",
1338 honk.Precis, honk.Oonker, 0, strings.Join(honk.Onts, " "))
1339 if err != nil {
1340 log.Printf("error saving honk: %s", err)
1341 http.Error(w, "something bad happened while saving", http.StatusInternalServerError)
1342 return
1343 }
1344 honk.ID, _ = res.LastInsertId()
1345 for _, d := range honk.Donks {
1346 _, err = stmtSaveDonk.Exec(honk.ID, d.FileID)
1347 if err != nil {
1348 log.Printf("err saving donk: %s", err)
1349 http.Error(w, "something bad happened while saving", http.StatusInternalServerError)
1350 return
1351 }
1352 }
1353 for _, o := range honk.Onts {
1354 _, err = stmtSaveOnts.Exec(strings.ToLower(o), honk.ID)
1355 if err != nil {
1356 log.Printf("error saving ont: %s", err)
1357 }
1358 }
1359 honk.Donks = nil
1360 donksforhonks([]*Honk{&honk})
1361
1362 go honkworldwide(user, &honk)
1363
1364 http.Redirect(w, r, xid, http.StatusSeeOther)
1365}
1366
1367func showhonkers(w http.ResponseWriter, r *http.Request) {
1368 userinfo := login.GetUserInfo(r)
1369 templinfo := getInfo(r)
1370 templinfo["Honkers"] = gethonkers(userinfo.UserID)
1371 templinfo["HonkerCSRF"] = login.GetCSRF("savehonker", r)
1372 err := readviews.Execute(w, "honkers.html", templinfo)
1373 if err != nil {
1374 log.Print(err)
1375 }
1376}
1377
1378func showcombos(w http.ResponseWriter, r *http.Request) {
1379 userinfo := login.GetUserInfo(r)
1380 templinfo := getInfo(r)
1381 honkers := gethonkers(userinfo.UserID)
1382 var combos []string
1383 for _, h := range honkers {
1384 combos = append(combos, h.Combos...)
1385 }
1386 for i, c := range combos {
1387 if c == "-" {
1388 combos[i] = ""
1389 }
1390 }
1391 combos = oneofakind(combos)
1392 sort.Strings(combos)
1393 templinfo["Combos"] = combos
1394 err := readviews.Execute(w, "combos.html", templinfo)
1395 if err != nil {
1396 log.Print(err)
1397 }
1398}
1399
1400func savehonker(w http.ResponseWriter, r *http.Request) {
1401 u := login.GetUserInfo(r)
1402 name := r.FormValue("name")
1403 url := r.FormValue("url")
1404 peep := r.FormValue("peep")
1405 combos := r.FormValue("combos")
1406 honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1407
1408 if honkerid > 0 {
1409 goodbye := r.FormValue("goodbye")
1410 if goodbye == "F" {
1411 db := opendatabase()
1412 row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ?",
1413 honkerid, u.UserID)
1414 var xid string
1415 err := row.Scan(&xid)
1416 if err != nil {
1417 log.Printf("can't get honker xid: %s", err)
1418 return
1419 }
1420 log.Printf("unsubscribing from %s", xid)
1421 user, _ := butwhatabout(u.Username)
1422 go itakeitallback(user, xid)
1423 _, err = stmtUpdateFlavor.Exec("unsub", u.UserID, xid, "sub")
1424 if err != nil {
1425 log.Printf("error updating honker: %s", err)
1426 return
1427 }
1428
1429 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1430 return
1431 }
1432 combos = " " + strings.TrimSpace(combos) + " "
1433 _, err := stmtUpdateCombos.Exec(combos, honkerid, u.UserID)
1434 if err != nil {
1435 log.Printf("update honker err: %s", err)
1436 return
1437 }
1438 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1439 }
1440
1441 flavor := "presub"
1442 if peep == "peep" {
1443 flavor = "peep"
1444 }
1445 p, err := investigate(url)
1446 if err != nil {
1447 http.Error(w, "error investigating: "+err.Error(), http.StatusInternalServerError)
1448 log.Printf("failed to investigate honker")
1449 return
1450 }
1451 url = p.XID
1452 if name == "" {
1453 name = p.Handle
1454 }
1455 _, err = stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1456 if err != nil {
1457 log.Print(err)
1458 return
1459 }
1460 if flavor == "presub" {
1461 user, _ := butwhatabout(u.Username)
1462 go subsub(user, url)
1463 }
1464 http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1465}
1466
1467type Zonker struct {
1468 ID int64
1469 Name string
1470 Wherefore string
1471}
1472
1473func zonkzone(w http.ResponseWriter, r *http.Request) {
1474 userinfo := login.GetUserInfo(r)
1475 rows, err := stmtGetZonkers.Query(userinfo.UserID)
1476 if err != nil {
1477 log.Printf("err: %s", err)
1478 return
1479 }
1480 defer rows.Close()
1481 var zonkers []Zonker
1482 for rows.Next() {
1483 var z Zonker
1484 rows.Scan(&z.ID, &z.Name, &z.Wherefore)
1485 zonkers = append(zonkers, z)
1486 }
1487 sort.Slice(zonkers, func(i, j int) bool {
1488 w1 := zonkers[i].Wherefore
1489 w2 := zonkers[j].Wherefore
1490 if w1 == w2 {
1491 return zonkers[i].Name < zonkers[j].Name
1492 }
1493 if w1 == "zonvoy" {
1494 w1 = "zzzzzzz"
1495 }
1496 if w2 == "zonvoy" {
1497 w2 = "zzzzzzz"
1498 }
1499 return w1 < w2
1500 })
1501
1502 templinfo := getInfo(r)
1503 templinfo["Zonkers"] = zonkers
1504 templinfo["ZonkCSRF"] = login.GetCSRF("zonkzonk", r)
1505 err = readviews.Execute(w, "zonkers.html", templinfo)
1506 if err != nil {
1507 log.Print(err)
1508 }
1509}
1510
1511func zonkzonk(w http.ResponseWriter, r *http.Request) {
1512 userinfo := login.GetUserInfo(r)
1513 itsok := r.FormValue("itsok")
1514 if itsok == "iforgiveyou" {
1515 zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
1516 db := opendatabase()
1517 db.Exec("delete from zonkers where userid = ? and zonkerid = ?",
1518 userinfo.UserID, zonkerid)
1519 bitethethumbs()
1520 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1521 return
1522 }
1523 wherefore := r.FormValue("wherefore")
1524 name := r.FormValue("name")
1525 if name == "" {
1526 return
1527 }
1528 switch wherefore {
1529 case "zonker":
1530 case "zomain":
1531 case "zonvoy":
1532 case "zord":
1533 case "zilence":
1534 default:
1535 return
1536 }
1537 db := opendatabase()
1538 db.Exec("insert into zonkers (userid, name, wherefore) values (?, ?, ?)",
1539 userinfo.UserID, name, wherefore)
1540 if wherefore == "zonker" || wherefore == "zomain" || wherefore == "zord" || wherefore == "zilence" {
1541 bitethethumbs()
1542 }
1543
1544 http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
1545}
1546
1547func accountpage(w http.ResponseWriter, r *http.Request) {
1548 u := login.GetUserInfo(r)
1549 user, _ := butwhatabout(u.Username)
1550 templinfo := getInfo(r)
1551 templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1552 templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1553 templinfo["User"] = user
1554 err := readviews.Execute(w, "account.html", templinfo)
1555 if err != nil {
1556 log.Print(err)
1557 }
1558}
1559
1560func dochpass(w http.ResponseWriter, r *http.Request) {
1561 err := login.ChangePassword(w, r)
1562 if err != nil {
1563 log.Printf("error changing password: %s", err)
1564 }
1565 http.Redirect(w, r, "/account", http.StatusSeeOther)
1566}
1567
1568func fingerlicker(w http.ResponseWriter, r *http.Request) {
1569 orig := r.FormValue("resource")
1570
1571 log.Printf("finger lick: %s", orig)
1572
1573 if strings.HasPrefix(orig, "acct:") {
1574 orig = orig[5:]
1575 }
1576
1577 name := orig
1578 idx := strings.LastIndexByte(name, '/')
1579 if idx != -1 {
1580 name = name[idx+1:]
1581 if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
1582 log.Printf("foreign request rejected")
1583 name = ""
1584 }
1585 } else {
1586 idx = strings.IndexByte(name, '@')
1587 if idx != -1 {
1588 name = name[:idx]
1589 if name+"@"+serverName != orig {
1590 log.Printf("foreign request rejected")
1591 name = ""
1592 }
1593 }
1594 }
1595 user, err := butwhatabout(name)
1596 if err != nil {
1597 http.NotFound(w, r)
1598 return
1599 }
1600
1601 j := junk.New()
1602 j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1603 j["aliases"] = []string{user.URL}
1604 var links []junk.Junk
1605 l := junk.New()
1606 l["rel"] = "self"
1607 l["type"] = `application/activity+json`
1608 l["href"] = user.URL
1609 links = append(links, l)
1610 j["links"] = links
1611
1612 w.Header().Set("Cache-Control", "max-age=3600")
1613 w.Header().Set("Content-Type", "application/jrd+json")
1614 j.Write(w)
1615}
1616
1617func somedays() string {
1618 secs := 432000 + notrand.Int63n(432000)
1619 return fmt.Sprintf("%d", secs)
1620}
1621
1622func avatate(w http.ResponseWriter, r *http.Request) {
1623 n := r.FormValue("a")
1624 a := avatar(n)
1625 w.Header().Set("Cache-Control", "max-age="+somedays())
1626 w.Write(a)
1627}
1628
1629func servecss(w http.ResponseWriter, r *http.Request) {
1630 data, _ := ioutil.ReadFile("views" + r.URL.Path)
1631 s := css.Process(string(data))
1632 w.Header().Set("Cache-Control", "max-age=7776000")
1633 w.Header().Set("Content-Type", "text/css; charset=utf-8")
1634 w.Write([]byte(s))
1635}
1636func servehtml(w http.ResponseWriter, r *http.Request) {
1637 templinfo := getInfo(r)
1638 err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1639 if err != nil {
1640 log.Print(err)
1641 }
1642}
1643func serveemu(w http.ResponseWriter, r *http.Request) {
1644 xid := mux.Vars(r)["xid"]
1645 w.Header().Set("Cache-Control", "max-age="+somedays())
1646 http.ServeFile(w, r, "emus/"+xid)
1647}
1648func servememe(w http.ResponseWriter, r *http.Request) {
1649 xid := mux.Vars(r)["xid"]
1650 w.Header().Set("Cache-Control", "max-age="+somedays())
1651 http.ServeFile(w, r, "memes/"+xid)
1652}
1653
1654func servefile(w http.ResponseWriter, r *http.Request) {
1655 xid := mux.Vars(r)["xid"]
1656 row := stmtFileData.QueryRow(xid)
1657 var media string
1658 var data []byte
1659 err := row.Scan(&media, &data)
1660 if err != nil {
1661 log.Printf("error loading file: %s", err)
1662 http.NotFound(w, r)
1663 return
1664 }
1665 w.Header().Set("Content-Type", media)
1666 w.Header().Set("X-Content-Type-Options", "nosniff")
1667 w.Header().Set("Cache-Control", "max-age="+somedays())
1668 w.Write(data)
1669}
1670
1671func nomoroboto(w http.ResponseWriter, r *http.Request) {
1672 io.WriteString(w, "User-agent: *\n")
1673 io.WriteString(w, "Disallow: /a\n")
1674 io.WriteString(w, "Disallow: /d\n")
1675 io.WriteString(w, "Disallow: /meme\n")
1676 for _, u := range allusers() {
1677 fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
1678 }
1679}
1680
1681func serve() {
1682 db := opendatabase()
1683 login.Init(db)
1684
1685 listener, err := openListener()
1686 if err != nil {
1687 log.Fatal(err)
1688 }
1689 go redeliverator()
1690
1691 debug := false
1692 getconfig("debug", &debug)
1693 readviews = templates.Load(debug,
1694 "views/honkpage.html",
1695 "views/honkfrags.html",
1696 "views/honkers.html",
1697 "views/zonkers.html",
1698 "views/combos.html",
1699 "views/honkform.html",
1700 "views/honk.html",
1701 "views/account.html",
1702 "views/about.html",
1703 "views/funzone.html",
1704 "views/login.html",
1705 "views/xzone.html",
1706 "views/header.html",
1707 "views/onts.html",
1708 )
1709 if !debug {
1710 s := "views/style.css"
1711 savedstyleparams[s] = getstyleparam(s)
1712 s = "views/local.css"
1713 savedstyleparams[s] = getstyleparam(s)
1714 }
1715
1716 bitethethumbs()
1717
1718 mux := mux.NewRouter()
1719 mux.Use(login.Checker)
1720
1721 posters := mux.Methods("POST").Subrouter()
1722 getters := mux.Methods("GET").Subrouter()
1723
1724 getters.HandleFunc("/", homepage)
1725 getters.HandleFunc("/home", homepage)
1726 getters.HandleFunc("/front", homepage)
1727 getters.HandleFunc("/robots.txt", nomoroboto)
1728 getters.HandleFunc("/rss", showrss)
1729 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}", showuser)
1730 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/"+honkSep+"/{xid:[[:alnum:]]+}", showhonk)
1731 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/rss", showrss)
1732 posters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/inbox", inbox)
1733 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/outbox", outbox)
1734 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/followers", emptiness)
1735 getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/following", emptiness)
1736 getters.HandleFunc("/a", avatate)
1737 getters.HandleFunc("/o", thelistingoftheontologies)
1738 getters.HandleFunc("/o/{name:[a-z0-9-]+}", showontology)
1739 getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1740 getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
1741 getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
1742 getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1743
1744 getters.HandleFunc("/style.css", servecss)
1745 getters.HandleFunc("/local.css", servecss)
1746 getters.HandleFunc("/about", servehtml)
1747 getters.HandleFunc("/login", servehtml)
1748 posters.HandleFunc("/dologin", login.LoginFunc)
1749 getters.HandleFunc("/logout", login.LogoutFunc)
1750
1751 loggedin := mux.NewRoute().Subrouter()
1752 loggedin.Use(login.Required)
1753 loggedin.HandleFunc("/account", accountpage)
1754 loggedin.HandleFunc("/funzone", showfunzone)
1755 loggedin.HandleFunc("/chpass", dochpass)
1756 loggedin.HandleFunc("/atme", homepage)
1757 loggedin.HandleFunc("/zonkzone", zonkzone)
1758 loggedin.HandleFunc("/xzone", xzone)
1759 loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(savehonk)))
1760 loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(savebonk)))
1761 loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1762 loggedin.Handle("/zonkzonk", login.CSRFWrap("zonkzonk", http.HandlerFunc(zonkzonk)))
1763 loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1764 loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
1765 loggedin.HandleFunc("/honkers", showhonkers)
1766 loggedin.HandleFunc("/h/{name:[[:alnum:]]+}", showhonker)
1767 loggedin.HandleFunc("/h", showhonker)
1768 loggedin.HandleFunc("/c/{name:[[:alnum:]]+}", showcombo)
1769 loggedin.HandleFunc("/c", showcombos)
1770 loggedin.HandleFunc("/t", showconvoy)
1771 loggedin.HandleFunc("/q", showsearch)
1772 loggedin.Handle("/savehonker", login.CSRFWrap("savehonker", http.HandlerFunc(savehonker)))
1773
1774 err = http.Serve(listener, mux)
1775 if err != nil {
1776 log.Fatal(err)
1777 }
1778}
1779
1780func cleanupdb(arg string) {
1781 db := opendatabase()
1782 days, err := strconv.Atoi(arg)
1783 if err != nil {
1784 honker := arg
1785 expdate := time.Now().UTC().Add(-3 * 24 * time.Hour).Format(dbtimeformat)
1786 doordie(db, "delete from donks where honkid in (select honkid from honks where dt < ? and whofore = 0 and honker = ?)", expdate, honker)
1787 doordie(db, "delete from honks where dt < ? and whofore = 0 and honker = ?", expdate, honker)
1788 } else {
1789 expdate := time.Now().UTC().Add(-time.Duration(days) * 24 * time.Hour).Format(dbtimeformat)
1790 doordie(db, "delete from donks where honkid in (select honkid from honks where dt < ? and whofore = 0 and convoy not in (select convoy from honks where whofore = 2 or whofore = 3))", expdate)
1791 doordie(db, "delete from honks where dt < ? and whofore = 0 and convoy not in (select convoy from honks where whofore = 2 or whofore = 3)", expdate)
1792 }
1793 doordie(db, "delete from files where fileid not in (select fileid from donks)")
1794 for _, u := range allusers() {
1795 doordie(db, "delete from zonkers where userid = ? and wherefore = 'zonvoy' and zonkerid < (select zonkerid from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 1 offset 200)", u.UserID, u.UserID)
1796 }
1797}
1798
1799var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
1800var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1801var stmtHonksByOntology, stmtHonksForUser, stmtHonksForMe, stmtSaveDub, stmtHonksByXonker *sql.Stmt
1802var stmtHonksBySearch, stmtHonksByHonker, stmtSaveHonk, stmtFileData, stmtWhatAbout *sql.Stmt
1803var stmtOneBonk, stmtFindZonk, stmtFindXonk, stmtSaveDonk, stmtFindFile, stmtSaveFile *sql.Stmt
1804var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover *sql.Stmt
1805var stmtHasHonker, stmtThumbBiters, stmtZonkIt, stmtZonkDonks, stmtSaveZonker *sql.Stmt
1806var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker *sql.Stmt
1807var stmtSelectOnts, stmtSaveOnts, stmtUpdateFlags, stmtClearFlags *sql.Stmt
1808
1809func preparetodie(db *sql.DB, s string) *sql.Stmt {
1810 stmt, err := db.Prepare(s)
1811 if err != nil {
1812 log.Fatalf("error %s: %s", err, s)
1813 }
1814 return stmt
1815}
1816
1817func prepareStatements(db *sql.DB) {
1818 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")
1819 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
1820 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
1821 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
1822 stmtHasHonker = preparetodie(db, "select honkerid from honkers where xid = ? and userid = ?")
1823 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1824
1825 selecthonks := "select honks.honkid, honks.userid, username, what, honker, oonker, honks.xid, rid, dt, url, audience, noise, precis, convoy, whofore, flags, onts from honks join users on honks.userid = users.userid "
1826 limit := " order by honks.honkid desc limit 250"
1827 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1828 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1829 stmtOneBonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ? and what = 'bonk' and whofore = 2")
1830 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
1831 stmtUserHonks = preparetodie(db, selecthonks+"where (whofore = 2 or whofore = ?) and username = ? and dt > ?"+limit)
1832 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)
1833 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1834 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on (honkers.xid = honks.honker or honkers.xid = honks.oonker) where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1835 stmtHonksByXonker = preparetodie(db, selecthonks+" where honks.userid = ? and (honker = ? or oonker = ?)"+butnotthose+limit)
1836 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
1837 stmtHonksBySearch = preparetodie(db, selecthonks+"where honks.userid = ? and noise like ?"+limit)
1838 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or (? = -1 and whofore = 2)) and convoy = ?"+limit)
1839 stmtHonksByOntology = preparetodie(db, selecthonks+"join onts on honks.honkid = onts.honkid where onts.ontology = ? and (honks.userid = ? or (? = -1 and honks.whofore = 2))"+limit)
1840
1841 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags, onts) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1842 stmtSaveOnts = preparetodie(db, "insert into onts (ontology, honkid) values (?, ?)")
1843 stmtFileData = preparetodie(db, "select media, content from files where xid = ?")
1844 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1845 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
1846 stmtZonkIt = preparetodie(db, "delete from honks where honkid = ?")
1847 stmtZonkDonks = preparetodie(db, "delete from donks where honkid = ?")
1848 stmtFindFile = preparetodie(db, "select fileid from files where url = ? and local = 1")
1849 stmtSaveFile = preparetodie(db, "insert into files (xid, name, description, url, media, local, content) values (?, ?, ?, ?, ?, ?, ?)")
1850 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey, options from users where username = ?")
1851 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
1852 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
1853 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1854 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
1855 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1856 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers where (wherefore = 'zonker' or wherefore = 'zomain' or wherefore = 'zord' or wherefore = 'zilence')")
1857 stmtFindZonk = preparetodie(db, "select zonkerid from zonkers where userid = ? and name = ? and wherefore = 'zonk'")
1858 stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'")
1859 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1860 stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
1861 stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor) values (?, ?, ?)")
1862 stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ?")
1863 stmtRecentHonkers = preparetodie(db, "select distinct(honker) from honks where userid = ? and honker not in (select xid from honkers where userid = ? and flavor = 'sub') order by honkid desc limit 100")
1864 stmtUpdateFlags = preparetodie(db, "update honks set flags = flags | ? where honkid = ?")
1865 stmtClearFlags = preparetodie(db, "update honks set flags = flags & ~ ? where honkid = ?")
1866 stmtSelectOnts = preparetodie(db, "select distinct(ontology) from onts join honks on onts.honkid = honks.honkid where (honks.userid = ? or honks.whofore = 2)")
1867}
1868
1869func ElaborateUnitTests() {
1870}
1871
1872func main() {
1873 cmd := "run"
1874 if len(os.Args) > 1 {
1875 cmd = os.Args[1]
1876 }
1877 switch cmd {
1878 case "init":
1879 initdb()
1880 case "upgrade":
1881 upgradedb()
1882 }
1883 db := opendatabase()
1884 dbversion := 0
1885 getconfig("dbversion", &dbversion)
1886 if dbversion != myVersion {
1887 log.Fatal("incorrect database version. run upgrade.")
1888 }
1889 getconfig("servermsg", &serverMsg)
1890 getconfig("servername", &serverName)
1891 getconfig("usersep", &userSep)
1892 getconfig("honksep", &honkSep)
1893 getconfig("dnf", &donotfedafterdark)
1894 prepareStatements(db)
1895 switch cmd {
1896 case "adduser":
1897 adduser()
1898 case "cleanup":
1899 arg := "30"
1900 if len(os.Args) > 2 {
1901 arg = os.Args[2]
1902 }
1903 cleanupdb(arg)
1904 case "ping":
1905 if len(os.Args) < 4 {
1906 fmt.Printf("usage: honk ping from to\n")
1907 return
1908 }
1909 name := os.Args[2]
1910 targ := os.Args[3]
1911 user, err := butwhatabout(name)
1912 if err != nil {
1913 log.Printf("unknown user")
1914 return
1915 }
1916 ping(user, targ)
1917 case "peep":
1918 peeppeep()
1919 case "run":
1920 serve()
1921 case "test":
1922 ElaborateUnitTests()
1923 default:
1924 log.Fatal("unknown command")
1925 }
1926}