all repos — honk @ ed28d731aad0057cfa6431f6ca57816f2687305c

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			d.XID = xid
1090			d.Desc = desc
1091			d.URL = url
1092			d.Local = true
1093			honk.Donks = append(honk.Donks, &d)
1094			donkxid = xid
1095		}
1096	} else {
1097		xid := donkxid
1098		url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1099		donk := finddonk(url)
1100		if donk != nil {
1101			honk.Donks = append(honk.Donks, donk)
1102		} else {
1103			log.Printf("can't find file: %s", xid)
1104		}
1105	}
1106	herd := herdofemus(honk.Noise)
1107	for _, e := range herd {
1108		donk := savedonk(e.ID, e.Name, e.Name, "image/png", true)
1109		if donk != nil {
1110			donk.Name = e.Name
1111			honk.Donks = append(honk.Donks, donk)
1112		}
1113	}
1114	memetize(honk)
1115
1116	placename := strings.TrimSpace(r.FormValue("placename"))
1117	placelat := strings.TrimSpace(r.FormValue("placelat"))
1118	placelong := strings.TrimSpace(r.FormValue("placelong"))
1119	placeurl := strings.TrimSpace(r.FormValue("placeurl"))
1120	if placename != "" || placelat != "" || placelong != "" || placeurl != "" {
1121		p := new(Place)
1122		p.Name = placename
1123		p.Latitude, _ = strconv.ParseFloat(placelat, 64)
1124		p.Longitude, _ = strconv.ParseFloat(placelong, 64)
1125		p.Url = placeurl
1126		honk.Place = p
1127	}
1128	timestart := strings.TrimSpace(r.FormValue("timestart"))
1129	if timestart != "" {
1130		t := new(Time)
1131		now := time.Now().Local()
1132		for _, layout := range []string{"2006-01-02 3:04pm", "2006-01-02 15:04", "3:04pm", "15:04"} {
1133			start, err := time.ParseInLocation(layout, timestart, now.Location())
1134			if err == nil {
1135				if start.Year() == 0 {
1136					start = time.Date(now.Year(), now.Month(), now.Day(), start.Hour(), start.Minute(), 0, 0, now.Location())
1137				}
1138				t.StartTime = start
1139				break
1140			}
1141		}
1142		timeend := r.FormValue("timeend")
1143		dur, err := time.ParseDuration(timeend)
1144		if err == nil {
1145			t.Duration = Duration(dur)
1146		}
1147		if !t.StartTime.IsZero() {
1148			honk.What = "event"
1149			honk.Time = t
1150		}
1151	}
1152
1153	if honk.Public {
1154		honk.Whofore = 2
1155	} else {
1156		honk.Whofore = 3
1157	}
1158
1159	// back to markdown
1160	honk.Noise = noise
1161
1162	if r.FormValue("preview") == "preview" {
1163		honks := []*Honk{honk}
1164		reverbolate(userinfo.UserID, honks)
1165		templinfo := getInfo(r)
1166		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1167		templinfo["Honks"] = honks
1168		templinfo["InReplyTo"] = r.FormValue("rid")
1169		templinfo["Noise"] = r.FormValue("noise")
1170		templinfo["SavedFile"] = donkxid
1171		templinfo["ServerMessage"] = "honk preview"
1172		err := readviews.Execute(w, "honkpage.html", templinfo)
1173		if err != nil {
1174			log.Print(err)
1175		}
1176		return
1177	}
1178
1179	if updatexid != "" {
1180		updatehonk(honk)
1181		oldjonks.Clear(honk.XID)
1182	} else {
1183		err := savehonk(honk)
1184		if err != nil {
1185			log.Printf("uh oh")
1186			return
1187		}
1188	}
1189
1190	// reload for consistency
1191	honk.Donks = nil
1192	donksforhonks([]*Honk{honk})
1193
1194	go honkworldwide(user, honk)
1195
1196	http.Redirect(w, r, honk.XID, http.StatusSeeOther)
1197}
1198
1199func showhonkers(w http.ResponseWriter, r *http.Request) {
1200	userinfo := login.GetUserInfo(r)
1201	templinfo := getInfo(r)
1202	templinfo["Honkers"] = gethonkers(userinfo.UserID)
1203	templinfo["HonkerCSRF"] = login.GetCSRF("submithonker", r)
1204	err := readviews.Execute(w, "honkers.html", templinfo)
1205	if err != nil {
1206		log.Print(err)
1207	}
1208}
1209
1210var combocache = cacheNew(cacheOptions{Filler: func(userid int64) ([]string, bool) {
1211	honkers := gethonkers(userid)
1212	var combos []string
1213	for _, h := range honkers {
1214		combos = append(combos, h.Combos...)
1215	}
1216	for i, c := range combos {
1217		if c == "-" {
1218			combos[i] = ""
1219		}
1220	}
1221	combos = oneofakind(combos)
1222	sort.Strings(combos)
1223	return combos, true
1224}})
1225
1226func showcombos(w http.ResponseWriter, r *http.Request) {
1227	userinfo := login.GetUserInfo(r)
1228	var combos []string
1229	combocache.Get(userinfo.UserID, &combos)
1230	templinfo := getInfo(r)
1231	err := readviews.Execute(w, "combos.html", templinfo)
1232	if err != nil {
1233		log.Print(err)
1234	}
1235}
1236
1237func submithonker(w http.ResponseWriter, r *http.Request) {
1238	u := login.GetUserInfo(r)
1239	name := strings.TrimSpace(r.FormValue("name"))
1240	url := strings.TrimSpace(r.FormValue("url"))
1241	peep := r.FormValue("peep")
1242	combos := strings.TrimSpace(r.FormValue("combos"))
1243	combos = " " + combos + " "
1244	honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1245
1246	defer combocache.Clear(u.UserID)
1247
1248	if honkerid > 0 {
1249		goodbye := r.FormValue("goodbye")
1250		if goodbye == "F" {
1251			db := opendatabase()
1252			row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ? and flavor in ('sub')",
1253				honkerid, u.UserID)
1254			err := row.Scan(&url)
1255			if err != nil {
1256				log.Printf("can't get honker xid: %s", err)
1257				return
1258			}
1259			log.Printf("unsubscribing from %s", url)
1260			user, _ := butwhatabout(u.Username)
1261			_, err = stmtUpdateFlavor.Exec("unsub", u.UserID, url, "sub")
1262			if err != nil {
1263				log.Printf("error updating honker: %s", err)
1264				return
1265			}
1266			go itakeitallback(user, url)
1267
1268			http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1269			return
1270		}
1271		if goodbye == "X" {
1272			db := opendatabase()
1273			row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ? and flavor in ('unsub', 'peep')",
1274				honkerid, u.UserID)
1275			err := row.Scan(&url)
1276			if err != nil {
1277				log.Printf("can't get honker xid: %s", err)
1278				return
1279			}
1280			log.Printf("resubscribing to %s", url)
1281			user, _ := butwhatabout(u.Username)
1282			_, err = stmtUpdateFlavor.Exec("presub", u.UserID, url, "unsub")
1283			if err == nil {
1284				_, err = stmtUpdateFlavor.Exec("presub", u.UserID, url, "peep")
1285			}
1286			if err != nil {
1287				log.Printf("error updating honker: %s", err)
1288				return
1289			}
1290			go subsub(user, url)
1291
1292			http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1293			return
1294		}
1295		_, err := stmtUpdateHonker.Exec(name, combos, honkerid, u.UserID)
1296		if err != nil {
1297			log.Printf("update honker err: %s", err)
1298			return
1299		}
1300		http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1301		return
1302	}
1303
1304	flavor := "presub"
1305	if peep == "peep" {
1306		flavor = "peep"
1307	}
1308	p, err := investigate(url)
1309	if err != nil {
1310		http.Error(w, "error investigating: "+err.Error(), http.StatusInternalServerError)
1311		log.Printf("failed to investigate honker: %s", err)
1312		return
1313	}
1314	url = p.XID
1315
1316	db := opendatabase()
1317	row := db.QueryRow("select xid from honkers where xid = ? and userid = ? and flavor in ('sub', 'unsub', 'peep')", url, u.UserID)
1318	var x string
1319	err = row.Scan(&x)
1320	if err != sql.ErrNoRows {
1321		http.Error(w, "it seems you are already subscribed to them", http.StatusInternalServerError)
1322		if err != nil {
1323			log.Printf("honker scan err: %s", err)
1324		}
1325		return
1326	}
1327
1328	if name == "" {
1329		name = p.Handle
1330	}
1331	_, err = stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1332	if err != nil {
1333		log.Print(err)
1334		return
1335	}
1336	if flavor == "presub" {
1337		user, _ := butwhatabout(u.Username)
1338		go subsub(user, url)
1339	}
1340	http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1341}
1342
1343func hfcspage(w http.ResponseWriter, r *http.Request) {
1344	userinfo := login.GetUserInfo(r)
1345
1346	filters := getfilters(userinfo.UserID, filtAny)
1347
1348	templinfo := getInfo(r)
1349	templinfo["Filters"] = filters
1350	templinfo["FilterCSRF"] = login.GetCSRF("filter", r)
1351	err := readviews.Execute(w, "hfcs.html", templinfo)
1352	if err != nil {
1353		log.Print(err)
1354	}
1355}
1356
1357func savehfcs(w http.ResponseWriter, r *http.Request) {
1358	userinfo := login.GetUserInfo(r)
1359	itsok := r.FormValue("itsok")
1360	if itsok == "iforgiveyou" {
1361		hfcsid, _ := strconv.ParseInt(r.FormValue("hfcsid"), 10, 0)
1362		_, err := stmtDeleteFilter.Exec(userinfo.UserID, hfcsid)
1363		if err != nil {
1364			log.Printf("error deleting filter: %s", err)
1365		}
1366		filtcache.Clear(userinfo.UserID)
1367		http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
1368		return
1369	}
1370
1371	filt := new(Filter)
1372	filt.Name = strings.TrimSpace(r.FormValue("name"))
1373	filt.Date = time.Now().UTC()
1374	filt.Actor = strings.TrimSpace(r.FormValue("actor"))
1375	filt.IncludeAudience = r.FormValue("incaud") == "yes"
1376	filt.Text = strings.TrimSpace(r.FormValue("filttext"))
1377	filt.IsAnnounce = r.FormValue("isannounce") == "yes"
1378	filt.AnnounceOf = strings.TrimSpace(r.FormValue("announceof"))
1379	filt.Reject = r.FormValue("doreject") == "yes"
1380	filt.SkipMedia = r.FormValue("doskipmedia") == "yes"
1381	filt.Hide = r.FormValue("dohide") == "yes"
1382	filt.Collapse = r.FormValue("docollapse") == "yes"
1383	filt.Rewrite = strings.TrimSpace(r.FormValue("filtrewrite"))
1384	filt.Replace = strings.TrimSpace(r.FormValue("filtreplace"))
1385
1386	if filt.Actor == "" && filt.Text == "" {
1387		log.Printf("blank filter")
1388		return
1389	}
1390
1391	j, err := jsonify(filt)
1392	if err == nil {
1393		_, err = stmtSaveFilter.Exec(userinfo.UserID, j)
1394	}
1395	if err != nil {
1396		log.Printf("error saving filter: %s", err)
1397	}
1398
1399	filtcache.Clear(userinfo.UserID)
1400	http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
1401}
1402
1403func accountpage(w http.ResponseWriter, r *http.Request) {
1404	u := login.GetUserInfo(r)
1405	user, _ := butwhatabout(u.Username)
1406	templinfo := getInfo(r)
1407	templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1408	templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1409	templinfo["User"] = user
1410	err := readviews.Execute(w, "account.html", templinfo)
1411	if err != nil {
1412		log.Print(err)
1413	}
1414}
1415
1416func dochpass(w http.ResponseWriter, r *http.Request) {
1417	err := login.ChangePassword(w, r)
1418	if err != nil {
1419		log.Printf("error changing password: %s", err)
1420	}
1421	http.Redirect(w, r, "/account", http.StatusSeeOther)
1422}
1423
1424func fingerlicker(w http.ResponseWriter, r *http.Request) {
1425	orig := r.FormValue("resource")
1426
1427	log.Printf("finger lick: %s", orig)
1428
1429	if strings.HasPrefix(orig, "acct:") {
1430		orig = orig[5:]
1431	}
1432
1433	name := orig
1434	idx := strings.LastIndexByte(name, '/')
1435	if idx != -1 {
1436		name = name[idx+1:]
1437		if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
1438			log.Printf("foreign request rejected")
1439			name = ""
1440		}
1441	} else {
1442		idx = strings.IndexByte(name, '@')
1443		if idx != -1 {
1444			name = name[:idx]
1445			if name+"@"+serverName != orig {
1446				log.Printf("foreign request rejected")
1447				name = ""
1448			}
1449		}
1450	}
1451	user, err := butwhatabout(name)
1452	if err != nil {
1453		http.NotFound(w, r)
1454		return
1455	}
1456	if stealthmode(user.ID, r) {
1457		http.NotFound(w, r)
1458		return
1459	}
1460
1461	j := junk.New()
1462	j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1463	j["aliases"] = []string{user.URL}
1464	var links []junk.Junk
1465	l := junk.New()
1466	l["rel"] = "self"
1467	l["type"] = `application/activity+json`
1468	l["href"] = user.URL
1469	links = append(links, l)
1470	j["links"] = links
1471
1472	w.Header().Set("Cache-Control", "max-age=3600")
1473	w.Header().Set("Content-Type", "application/jrd+json")
1474	j.Write(w)
1475}
1476
1477func somedays() string {
1478	secs := 432000 + notrand.Int63n(432000)
1479	return fmt.Sprintf("%d", secs)
1480}
1481
1482func avatate(w http.ResponseWriter, r *http.Request) {
1483	n := r.FormValue("a")
1484	a := avatar(n)
1485	w.Header().Set("Cache-Control", "max-age="+somedays())
1486	w.Write(a)
1487}
1488
1489func servecss(w http.ResponseWriter, r *http.Request) {
1490	fd, err := os.Open("views" + r.URL.Path)
1491	if err != nil {
1492		http.NotFound(w, r)
1493		return
1494	}
1495	w.Header().Set("Cache-Control", "max-age=0")
1496	w.Header().Set("Content-Type", "text/css; charset=utf-8")
1497	err = css.Filter(fd, w)
1498	if err != nil {
1499		log.Printf("error filtering css: %s", err)
1500	}
1501}
1502func serveasset(w http.ResponseWriter, r *http.Request) {
1503	w.Header().Set("Cache-Control", "max-age=7776000")
1504	http.ServeFile(w, r, "views"+r.URL.Path)
1505}
1506func servehelp(w http.ResponseWriter, r *http.Request) {
1507	name := mux.Vars(r)["name"]
1508	w.Header().Set("Cache-Control", "max-age=600")
1509	http.ServeFile(w, r, "docs/"+name)
1510}
1511func servehtml(w http.ResponseWriter, r *http.Request) {
1512	templinfo := getInfo(r)
1513	err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1514	if err != nil {
1515		log.Print(err)
1516	}
1517}
1518func serveemu(w http.ResponseWriter, r *http.Request) {
1519	xid := mux.Vars(r)["xid"]
1520	w.Header().Set("Cache-Control", "max-age="+somedays())
1521	http.ServeFile(w, r, "emus/"+xid)
1522}
1523func servememe(w http.ResponseWriter, r *http.Request) {
1524	xid := mux.Vars(r)["xid"]
1525	w.Header().Set("Cache-Control", "max-age="+somedays())
1526	http.ServeFile(w, r, "memes/"+xid)
1527}
1528
1529func servefile(w http.ResponseWriter, r *http.Request) {
1530	xid := mux.Vars(r)["xid"]
1531	row := stmtGetFileData.QueryRow(xid)
1532	var media string
1533	var data []byte
1534	err := row.Scan(&media, &data)
1535	if err != nil {
1536		log.Printf("error loading file: %s", err)
1537		http.NotFound(w, r)
1538		return
1539	}
1540	w.Header().Set("Content-Type", media)
1541	w.Header().Set("X-Content-Type-Options", "nosniff")
1542	w.Header().Set("Cache-Control", "max-age="+somedays())
1543	w.Write(data)
1544}
1545
1546func nomoroboto(w http.ResponseWriter, r *http.Request) {
1547	io.WriteString(w, "User-agent: *\n")
1548	io.WriteString(w, "Disallow: /a\n")
1549	io.WriteString(w, "Disallow: /d\n")
1550	io.WriteString(w, "Disallow: /meme\n")
1551	for _, u := range allusers() {
1552		fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
1553	}
1554}
1555
1556func webhydra(w http.ResponseWriter, r *http.Request) {
1557	u := login.GetUserInfo(r)
1558	userid := u.UserID
1559	templinfo := getInfo(r)
1560	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1561	page := r.FormValue("page")
1562	var honks []*Honk
1563	switch page {
1564	case "atme":
1565		honks = gethonksforme(userid)
1566		templinfo["ServerMessage"] = "at me!"
1567	case "home":
1568		honks = gethonksforuser(userid)
1569		honks = osmosis(honks, userid)
1570		templinfo["ServerMessage"] = serverMsg
1571	case "first":
1572		honks = gethonksforuserfirstclass(userid)
1573		honks = osmosis(honks, userid)
1574		templinfo["ServerMessage"] = "first class only"
1575	case "combo":
1576		c := r.FormValue("c")
1577		honks = gethonksbycombo(userid, c)
1578		honks = osmosis(honks, userid)
1579		templinfo["ServerMessage"] = "honks by combo: " + c
1580	case "convoy":
1581		c := r.FormValue("c")
1582		honks = gethonksbyconvoy(userid, c)
1583		templinfo["ServerMessage"] = "honks in convoy: " + c
1584	case "honker":
1585		xid := r.FormValue("xid")
1586		if strings.IndexByte(xid, '@') != -1 {
1587			xid = gofish(xid)
1588		}
1589		honks = gethonksbyxonker(userid, xid)
1590		xid = html.EscapeString(xid)
1591		msg := fmt.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>`, xid, xid)
1592		templinfo["ServerMessage"] = template.HTML(msg)
1593	default:
1594		http.NotFound(w, r)
1595	}
1596	if len(honks) > 0 {
1597		templinfo["TopXID"] = honks[0].XID
1598	}
1599	if topxid := r.FormValue("topxid"); topxid != "" {
1600		for i, h := range honks {
1601			if h.XID == topxid {
1602				honks = honks[0:i]
1603				break
1604			}
1605		}
1606		log.Printf("topxid %d frags", len(honks))
1607	}
1608	reverbolate(userid, honks)
1609	templinfo["Honks"] = honks
1610	w.Header().Set("Content-Type", "text/html; charset=utf-8")
1611	err := readviews.Execute(w, "honkfrags.html", templinfo)
1612	if err != nil {
1613		log.Printf("frag error: %s", err)
1614	}
1615}
1616
1617func serve() {
1618	db := opendatabase()
1619	login.Init(db)
1620
1621	listener, err := openListener()
1622	if err != nil {
1623		log.Fatal(err)
1624	}
1625	go redeliverator()
1626
1627	debug := false
1628	getconfig("debug", &debug)
1629	readviews = templates.Load(debug,
1630		"views/honkpage.html",
1631		"views/honkfrags.html",
1632		"views/honkers.html",
1633		"views/hfcs.html",
1634		"views/combos.html",
1635		"views/honkform.html",
1636		"views/honk.html",
1637		"views/account.html",
1638		"views/about.html",
1639		"views/funzone.html",
1640		"views/login.html",
1641		"views/xzone.html",
1642		"views/header.html",
1643		"views/onts.html",
1644		"views/honkpage.js",
1645	)
1646	if !debug {
1647		assets := []string{"views/style.css", "views/local.css", "views/honkpage.js"}
1648		for _, s := range assets {
1649			savedassetparams[s] = getassetparam(s)
1650		}
1651	}
1652
1653	mux := mux.NewRouter()
1654	mux.Use(login.Checker)
1655
1656	posters := mux.Methods("POST").Subrouter()
1657	getters := mux.Methods("GET").Subrouter()
1658
1659	getters.HandleFunc("/", homepage)
1660	getters.HandleFunc("/home", homepage)
1661	getters.HandleFunc("/front", homepage)
1662	getters.HandleFunc("/events", homepage)
1663	getters.HandleFunc("/robots.txt", nomoroboto)
1664	getters.HandleFunc("/rss", showrss)
1665	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}", showuser)
1666	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/"+honkSep+"/{xid:[[:alnum:]]+}", showhonk)
1667	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/rss", showrss)
1668	posters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/inbox", inbox)
1669	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/outbox", outbox)
1670	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/followers", emptiness)
1671	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/following", emptiness)
1672	getters.HandleFunc("/a", avatate)
1673	getters.HandleFunc("/o", thelistingoftheontologies)
1674	getters.HandleFunc("/o/{name:.+}", showontology)
1675	getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1676	getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
1677	getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
1678	getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1679
1680	getters.HandleFunc("/style.css", servecss)
1681	getters.HandleFunc("/local.css", servecss)
1682	getters.HandleFunc("/honkpage.js", serveasset)
1683	getters.HandleFunc("/about", servehtml)
1684	getters.HandleFunc("/login", servehtml)
1685	posters.HandleFunc("/dologin", login.LoginFunc)
1686	getters.HandleFunc("/logout", login.LogoutFunc)
1687	getters.HandleFunc("/help/{name:[[:alnum:]_.-]+}", servehelp)
1688
1689	loggedin := mux.NewRoute().Subrouter()
1690	loggedin.Use(login.Required)
1691	loggedin.HandleFunc("/first", homepage)
1692	loggedin.HandleFunc("/account", accountpage)
1693	loggedin.HandleFunc("/funzone", showfunzone)
1694	loggedin.HandleFunc("/chpass", dochpass)
1695	loggedin.HandleFunc("/atme", homepage)
1696	loggedin.HandleFunc("/hfcs", hfcspage)
1697	loggedin.HandleFunc("/xzone", xzone)
1698	loggedin.HandleFunc("/edit", edithonkpage)
1699	loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(submithonk)))
1700	loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(submitbonk)))
1701	loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1702	loggedin.Handle("/savehfcs", login.CSRFWrap("filter", http.HandlerFunc(savehfcs)))
1703	loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1704	loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
1705	loggedin.HandleFunc("/honkers", showhonkers)
1706	loggedin.HandleFunc("/h/{name:[[:alnum:]_.-]+}", showhonker)
1707	loggedin.HandleFunc("/h", showhonker)
1708	loggedin.HandleFunc("/c/{name:[[:alnum:]_.-]+}", showcombo)
1709	loggedin.HandleFunc("/c", showcombos)
1710	loggedin.HandleFunc("/t", showconvoy)
1711	loggedin.HandleFunc("/q", showsearch)
1712	loggedin.HandleFunc("/hydra", webhydra)
1713	loggedin.Handle("/submithonker", login.CSRFWrap("submithonker", http.HandlerFunc(submithonker)))
1714
1715	err = http.Serve(listener, mux)
1716	if err != nil {
1717		log.Fatal(err)
1718	}
1719}