all repos — honk @ 3e21bb457f8955800b9d4f612412d9b567f0aaf6

my fork of honk

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