all repos — honk @ 9c7e310ff2630d282c912c04e03be6004b76dae9

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