all repos — honk @ 2b775df63f72aab06a9ac46b6cd3ef6b6629f5da

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