all repos — honk @ 39eb566738fc186d2a68dcb88f9c1a43bb5b1625

my fork of honk

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