all repos — honk @ 9c7f3569ee2cfcd7848912430f150f30c97f375c

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