all repos — honk @ b82840f8cbf6960766aa4eefd945bb65e70037f1

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