all repos — honk @ 2d48a59984723cb4480a013b05eef3a79f3cd428

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