all repos — honk @ 2e3e9b28c3dea4ab9da71237ef83c6a63852a0ff

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