all repos — honk @ 07fc8d94e48b0f6a64b3868eab0fa382791ce7ff

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