all repos — honk @ 0ec8afa227281c1fd4f35bf331aa0a16582fff89

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/template"
  23	"io"
  24	"log"
  25	notrand "math/rand"
  26	"net/http"
  27	"net/url"
  28	"os"
  29	"os/signal"
  30	"regexp"
  31	"sort"
  32	"strconv"
  33	"strings"
  34	"syscall"
  35	"time"
  36
  37	"github.com/gorilla/mux"
  38	"humungus.tedunangst.com/r/webs/cache"
  39	"humungus.tedunangst.com/r/webs/httpsig"
  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.Options.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(viewDir + "/views/style.css")
  66	templinfo["LocalStyleParam"] = getassetparam(viewDir + "/views/local.css")
  67	templinfo["JSParam"] = getassetparam(viewDir + "/views/honkpage.js")
  68	templinfo["UserStyle"] = getuserstyle(u)
  69	templinfo["ServerName"] = serverName
  70	templinfo["IconName"] = iconName
  71	templinfo["UserInfo"] = u
  72	templinfo["UserSep"] = userSep
  73	if u != nil {
  74		var combos []string
  75		combocache.Get(u.UserID, &combos)
  76		templinfo["Combos"] = combos
  77	}
  78	return templinfo
  79}
  80
  81func homepage(w http.ResponseWriter, r *http.Request) {
  82	templinfo := getInfo(r)
  83	u := login.GetUserInfo(r)
  84	var honks []*Honk
  85	var userid int64 = -1
  86
  87	templinfo["ServerMessage"] = serverMsg
  88	if u == nil || r.URL.Path == "/front" {
  89		switch r.URL.Path {
  90		case "/events":
  91			honks = geteventhonks(userid)
  92			templinfo["ServerMessage"] = "some recent and upcoming events"
  93		default:
  94			templinfo["ShowRSS"] = true
  95			honks = getpublichonks()
  96		}
  97	} else {
  98		userid = u.UserID
  99		switch r.URL.Path {
 100		case "/atme":
 101			templinfo["ServerMessage"] = "at me!"
 102			templinfo["PageName"] = "atme"
 103			honks = gethonksforme(userid, 0)
 104			honks = osmosis(honks, userid, false)
 105		case "/events":
 106			templinfo["ServerMessage"] = "some recent and upcoming events"
 107			templinfo["PageName"] = "events"
 108			honks = geteventhonks(userid)
 109			honks = osmosis(honks, userid, true)
 110		case "/first":
 111			templinfo["PageName"] = "first"
 112			honks = gethonksforuserfirstclass(userid, 0)
 113			honks = osmosis(honks, userid, true)
 114		case "/saved":
 115			templinfo["ServerMessage"] = "saved honks"
 116			templinfo["PageName"] = "saved"
 117			honks = getsavedhonks(userid, 0)
 118		default:
 119			templinfo["PageName"] = "home"
 120			honks = gethonksforuser(userid, 0)
 121			honks = osmosis(honks, userid, true)
 122		}
 123		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 124	}
 125
 126	honkpage(w, u, honks, templinfo)
 127}
 128
 129func showfunzone(w http.ResponseWriter, r *http.Request) {
 130	var emunames, memenames []string
 131	dir, err := os.Open(dataDir + "/emus")
 132	if err == nil {
 133		emunames, _ = dir.Readdirnames(0)
 134		dir.Close()
 135	}
 136	for i, e := range emunames {
 137		if len(e) > 4 {
 138			emunames[i] = e[:len(e)-4]
 139		}
 140	}
 141	dir, err = os.Open(dataDir + "/memes")
 142	if err == nil {
 143		memenames, _ = dir.Readdirnames(0)
 144		dir.Close()
 145	}
 146	templinfo := getInfo(r)
 147	templinfo["Emus"] = emunames
 148	templinfo["Memes"] = memenames
 149	err = readviews.Execute(w, "funzone.html", templinfo)
 150	if err != nil {
 151		log.Print(err)
 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, 0)
 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		if t := honk.Time; t != nil {
 192			desc += fmt.Sprintf(`<p>Time: %s`, t.StartTime.Local().Format("03:04PM EDT Mon Jan 02"))
 193			if t.Duration != 0 {
 194				desc += fmt.Sprintf(`<br>Duration: %s`, t.Duration)
 195			}
 196		}
 197		if p := honk.Place; p != nil {
 198			desc += string(templates.Sprintf(`<p>Location: <a href="%s">%s</a> %f %f`,
 199				p.Url, p.Name, p.Latitude, p.Longitude))
 200		}
 201		for _, d := range honk.Donks {
 202			desc += string(templates.Sprintf(`<p><a href="%s">Attachment: %s</a>`,
 203				d.URL, d.Name))
 204		}
 205
 206		feed.Items = append(feed.Items, &rss.Item{
 207			Title:       fmt.Sprintf("%s %s %s", honk.Username, honk.What, honk.XID),
 208			Description: rss.CData{Data: desc},
 209			Link:        honk.URL,
 210			PubDate:     honk.Date.Format(time.RFC1123),
 211			Guid:        &rss.Guid{IsPermaLink: true, Value: honk.URL},
 212		})
 213		if honk.Date.After(modtime) {
 214			modtime = honk.Date
 215		}
 216	}
 217	w.Header().Set("Cache-Control", "max-age=300")
 218	w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
 219
 220	err := feed.Write(w)
 221	if err != nil {
 222		log.Printf("error writing rss: %s", err)
 223	}
 224}
 225
 226func crappola(j junk.Junk) bool {
 227	t, _ := j.GetString("type")
 228	a, _ := j.GetString("actor")
 229	o, _ := j.GetString("object")
 230	if t == "Delete" && a == o {
 231		log.Printf("crappola from %s", a)
 232		return true
 233	}
 234	return false
 235}
 236
 237func ping(user *WhatAbout, who string) {
 238	var box *Box
 239	ok := boxofboxes.Get(who, &box)
 240	if !ok {
 241		log.Printf("no inbox to ping %s", who)
 242		return
 243	}
 244	j := junk.New()
 245	j["@context"] = itiswhatitis
 246	j["type"] = "Ping"
 247	j["id"] = user.URL + "/ping/" + xfiltrate()
 248	j["actor"] = user.URL
 249	j["to"] = who
 250	ki := ziggy(user.ID)
 251	if ki == nil {
 252		return
 253	}
 254	err := PostJunk(ki.keyname, ki.seckey, box.In, j)
 255	if err != nil {
 256		log.Printf("can't send ping: %s", err)
 257		return
 258	}
 259	log.Printf("sent ping to %s: %s", who, j["id"])
 260}
 261
 262func pong(user *WhatAbout, who string, obj string) {
 263	var box *Box
 264	ok := boxofboxes.Get(who, &box)
 265	if !ok {
 266		log.Printf("no inbox to pong %s", who)
 267		return
 268	}
 269	j := junk.New()
 270	j["@context"] = itiswhatitis
 271	j["type"] = "Pong"
 272	j["id"] = user.URL + "/pong/" + xfiltrate()
 273	j["actor"] = user.URL
 274	j["to"] = who
 275	j["object"] = obj
 276	ki := ziggy(user.ID)
 277	if ki == nil {
 278		return
 279	}
 280	err := PostJunk(ki.keyname, ki.seckey, box.In, j)
 281	if err != nil {
 282		log.Printf("can't send pong: %s", err)
 283		return
 284	}
 285}
 286
 287func inbox(w http.ResponseWriter, r *http.Request) {
 288	name := mux.Vars(r)["name"]
 289	user, err := butwhatabout(name)
 290	if err != nil {
 291		http.NotFound(w, r)
 292		return
 293	}
 294	if stealthmode(user.ID, r) {
 295		http.NotFound(w, r)
 296		return
 297	}
 298	var buf bytes.Buffer
 299	limiter := io.LimitReader(r.Body, 1*1024*1024)
 300	io.Copy(&buf, limiter)
 301	payload := buf.Bytes()
 302	j, err := junk.FromBytes(payload)
 303	if err != nil {
 304		log.Printf("bad payload: %s", err)
 305		io.WriteString(os.Stdout, "bad payload\n")
 306		os.Stdout.Write(payload)
 307		io.WriteString(os.Stdout, "\n")
 308		return
 309	}
 310
 311	if crappola(j) {
 312		return
 313	}
 314	what, _ := j.GetString("type")
 315	if what == "Like" {
 316		return
 317	}
 318	who, _ := j.GetString("actor")
 319	if rejectactor(user.ID, who) {
 320		return
 321	}
 322
 323	keyname, err := httpsig.VerifyRequest(r, payload, zaggy)
 324	if err != nil {
 325		log.Printf("inbox message failed signature for %s from %s", keyname, r.Header.Get("X-Forwarded-For"))
 326		if keyname != "" {
 327			log.Printf("bad signature from %s", keyname)
 328			io.WriteString(os.Stdout, "bad payload\n")
 329			os.Stdout.Write(payload)
 330			io.WriteString(os.Stdout, "\n")
 331		}
 332		http.Error(w, "what did you call me?", http.StatusTeapot)
 333		return
 334	}
 335	origin := keymatch(keyname, who)
 336	if origin == "" {
 337		log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
 338		return
 339	}
 340
 341	switch what {
 342	case "Ping":
 343		obj, _ := j.GetString("id")
 344		log.Printf("ping from %s: %s", who, obj)
 345		pong(user, who, obj)
 346	case "Pong":
 347		obj, _ := j.GetString("object")
 348		log.Printf("pong from %s: %s", who, obj)
 349	case "Follow":
 350		obj, _ := j.GetString("object")
 351		if obj != user.URL {
 352			log.Printf("can't follow %s", obj)
 353			return
 354		}
 355		log.Printf("updating honker follow: %s", who)
 356
 357		db := opendatabase()
 358		row := db.QueryRow("select xid from honkers where xid = ? and userid = ? and flavor in ('dub', 'undub')", who, user.ID)
 359		var x string
 360		err = row.Scan(&x)
 361		if err != sql.ErrNoRows {
 362			log.Printf("duplicate follow request: %s", who)
 363			_, err = stmtUpdateFlavor.Exec("dub", user.ID, who, who, "undub")
 364			if err != nil {
 365				log.Printf("error updating honker: %s", err)
 366			}
 367		} else {
 368			stmtSaveDub.Exec(user.ID, who, who, "dub")
 369		}
 370		go rubadubdub(user, j)
 371	case "Accept":
 372		log.Printf("updating honker accept: %s", who)
 373		db := opendatabase()
 374		row := db.QueryRow("select name from honkers where userid = ? and xid = ? and flavor in ('presub')",
 375			user.ID, who)
 376		var name string
 377		err := row.Scan(&name)
 378		if err != nil {
 379			log.Printf("can't get honker name: %s", err)
 380			return
 381		}
 382		_, err = stmtUpdateFlavor.Exec("sub", user.ID, who, name, "presub")
 383		if err != nil {
 384			log.Printf("error updating honker: %s", err)
 385			return
 386		}
 387	case "Update":
 388		obj, ok := j.GetMap("object")
 389		if ok {
 390			what, _ := obj.GetString("type")
 391			switch what {
 392			case "Person":
 393				return
 394			case "Question":
 395				return
 396			case "Note":
 397				go xonksaver(user, j, origin)
 398				return
 399			}
 400		}
 401		log.Printf("unknown Update activity")
 402		fd, _ := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
 403		j.Write(fd)
 404		io.WriteString(fd, "\n")
 405		fd.Close()
 406
 407	case "Undo":
 408		obj, ok := j.GetMap("object")
 409		if !ok {
 410			log.Printf("unknown undo no object")
 411			return
 412		}
 413		what, _ := obj.GetString("type")
 414		switch what {
 415		case "Follow":
 416			log.Printf("updating honker undo: %s", who)
 417			_, err = stmtUpdateFlavor.Exec("undub", user.ID, who, who, "dub")
 418			if err != nil {
 419				log.Printf("error updating honker: %s", err)
 420				return
 421			}
 422		case "Announce":
 423			xid, _ := obj.GetString("object")
 424			log.Printf("undo announce: %s", xid)
 425		case "Like":
 426		default:
 427			log.Printf("unknown undo: %s", what)
 428		}
 429	default:
 430		go xonksaver(user, j, origin)
 431	}
 432}
 433
 434func serverinbox(w http.ResponseWriter, r *http.Request) {
 435	if stealthmode(serverUID, r) {
 436		http.NotFound(w, r)
 437		return
 438	}
 439	var buf bytes.Buffer
 440	io.Copy(&buf, r.Body)
 441	payload := buf.Bytes()
 442	j, err := junk.FromBytes(payload)
 443	if err != nil {
 444		log.Printf("bad payload: %s", err)
 445		io.WriteString(os.Stdout, "bad payload\n")
 446		os.Stdout.Write(payload)
 447		io.WriteString(os.Stdout, "\n")
 448		return
 449	}
 450	if crappola(j) {
 451		return
 452	}
 453	keyname, err := httpsig.VerifyRequest(r, payload, zaggy)
 454	if err != nil {
 455		log.Printf("inbox message failed signature for %s from %s", keyname, r.Header.Get("X-Forwarded-For"))
 456		if keyname != "" {
 457			log.Printf("bad signature from %s", keyname)
 458			io.WriteString(os.Stdout, "bad payload\n")
 459			os.Stdout.Write(payload)
 460			io.WriteString(os.Stdout, "\n")
 461		}
 462		http.Error(w, "what did you call me?", http.StatusTeapot)
 463		return
 464	}
 465	user := getserveruser()
 466	who, _ := j.GetString("actor")
 467	origin := keymatch(keyname, who)
 468	if origin == "" {
 469		log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
 470		return
 471	}
 472	if rejectactor(user.ID, who) {
 473		return
 474	}
 475	re_ont := regexp.MustCompile("https://" + serverName + "/o/([[:alnum:]]+)")
 476	what, _ := j.GetString("type")
 477	log.Printf("server got a %s", what)
 478	switch what {
 479	case "Follow":
 480		obj, _ := j.GetString("object")
 481		if obj == user.URL {
 482			log.Printf("can't follow the server!")
 483			return
 484		}
 485		m := re_ont.FindStringSubmatch(obj)
 486		if len(m) != 2 {
 487			log.Printf("not sure how to handle this")
 488			return
 489		}
 490		ont := "#" + m[1]
 491		log.Printf("%s wants to follow %s", who, ont)
 492		db := opendatabase()
 493		row := db.QueryRow("select xid from honkers where name = ? and xid = ? and userid = ? and flavor in ('dub', 'undub')", ont, who, user.ID)
 494		var x string
 495		err = row.Scan(&x)
 496		if err != sql.ErrNoRows {
 497			log.Printf("duplicate follow request: %s", who)
 498			_, err = stmtUpdateFlavor.Exec("dub", user.ID, who, ont, "undub")
 499			if err != nil {
 500				log.Printf("error updating honker: %s", err)
 501			}
 502		} else {
 503			stmtSaveDub.Exec(user.ID, ont, who, "dub")
 504		}
 505		go rubadubdub(user, j)
 506	case "Undo":
 507		obj, ok := j.GetMap("object")
 508		if !ok {
 509			log.Printf("unknown undo no object")
 510			return
 511		}
 512		what, _ := obj.GetString("type")
 513		if what != "Follow" {
 514			log.Printf("unknown undo: %s", what)
 515		}
 516		targ, _ := obj.GetString("object")
 517		m := re_ont.FindStringSubmatch(targ)
 518		if len(m) != 2 {
 519			log.Printf("not sure how to handle this")
 520			return
 521		}
 522		ont := "#" + m[1]
 523		log.Printf("updating honker undo: %s %s", who, ont)
 524		_, err = stmtUpdateFlavor.Exec("undub", user.ID, who, ont, "dub")
 525		if err != nil {
 526			log.Printf("error updating honker: %s", err)
 527			return
 528		}
 529	}
 530}
 531
 532func serveractor(w http.ResponseWriter, r *http.Request) {
 533	if stealthmode(serverUID, r) {
 534		http.NotFound(w, r)
 535		return
 536	}
 537	user := getserveruser()
 538	j := junkuser(user)
 539	w.Write(j)
 540}
 541
 542func ximport(w http.ResponseWriter, r *http.Request) {
 543	u := login.GetUserInfo(r)
 544	xid := strings.TrimSpace(r.FormValue("xid"))
 545	xonk := getxonk(u.UserID, xid)
 546	if xonk == nil {
 547		p, _ := investigate(xid)
 548		if p != nil {
 549			xid = p.XID
 550		}
 551		j, err := GetJunk(xid)
 552		if err != nil {
 553			http.Error(w, "error getting external object", http.StatusInternalServerError)
 554			log.Printf("error getting external object: %s", err)
 555			return
 556		}
 557		log.Printf("importing %s", xid)
 558		user, _ := butwhatabout(u.Username)
 559
 560		info, _ := somethingabout(j)
 561		if info == nil {
 562			xonk = xonksaver(user, j, originate(xid))
 563		} else if info.What == SomeActor {
 564			outbox, _ := j.GetString("outbox")
 565			gimmexonks(user, outbox)
 566			http.Redirect(w, r, "/h?xid="+url.QueryEscape(xid), http.StatusSeeOther)
 567			return
 568		} else if info.What == SomeCollection {
 569			gimmexonks(user, xid)
 570			http.Redirect(w, r, "/xzone", http.StatusSeeOther)
 571			return
 572		}
 573	}
 574	convoy := ""
 575	if xonk != nil {
 576		convoy = xonk.Convoy
 577	}
 578	http.Redirect(w, r, "/t?c="+url.QueryEscape(convoy), http.StatusSeeOther)
 579}
 580
 581func xzone(w http.ResponseWriter, r *http.Request) {
 582	u := login.GetUserInfo(r)
 583	rows, err := stmtRecentHonkers.Query(u.UserID, u.UserID)
 584	if err != nil {
 585		log.Printf("query err: %s", err)
 586		return
 587	}
 588	defer rows.Close()
 589	var honkers []Honker
 590	for rows.Next() {
 591		var xid string
 592		rows.Scan(&xid)
 593		honkers = append(honkers, Honker{XID: xid})
 594	}
 595	rows.Close()
 596	for i, _ := range honkers {
 597		_, honkers[i].Handle = handles(honkers[i].XID)
 598	}
 599	templinfo := getInfo(r)
 600	templinfo["XCSRF"] = login.GetCSRF("ximport", r)
 601	templinfo["Honkers"] = honkers
 602	err = readviews.Execute(w, "xzone.html", templinfo)
 603	if err != nil {
 604		log.Print(err)
 605	}
 606}
 607
 608var oldoutbox = cache.New(cache.Options{Filler: func(name string) ([]byte, bool) {
 609	user, err := butwhatabout(name)
 610	if err != nil {
 611		return nil, false
 612	}
 613	honks := gethonksbyuser(name, false, 0)
 614	if len(honks) > 20 {
 615		honks = honks[0:20]
 616	}
 617
 618	var jonks []junk.Junk
 619	for _, h := range honks {
 620		j, _ := jonkjonk(user, h)
 621		jonks = append(jonks, j)
 622	}
 623
 624	j := junk.New()
 625	j["@context"] = itiswhatitis
 626	j["id"] = user.URL + "/outbox"
 627	j["attributedTo"] = user.URL
 628	j["type"] = "OrderedCollection"
 629	j["totalItems"] = len(jonks)
 630	j["orderedItems"] = jonks
 631
 632	return j.ToBytes(), true
 633}, Duration: 1 * time.Minute})
 634
 635func outbox(w http.ResponseWriter, r *http.Request) {
 636	name := mux.Vars(r)["name"]
 637	user, err := butwhatabout(name)
 638	if err != nil {
 639		http.NotFound(w, r)
 640		return
 641	}
 642	if stealthmode(user.ID, r) {
 643		http.NotFound(w, r)
 644		return
 645	}
 646	var j []byte
 647	ok := oldoutbox.Get(name, &j)
 648	if ok {
 649		w.Header().Set("Content-Type", theonetruename)
 650		w.Write(j)
 651	} else {
 652		http.NotFound(w, r)
 653	}
 654}
 655
 656var oldempties = cache.New(cache.Options{Filler: func(url string) ([]byte, bool) {
 657	colname := "/followers"
 658	if strings.HasSuffix(url, "/following") {
 659		colname = "/following"
 660	}
 661	user := fmt.Sprintf("https://%s%s", serverName, url[:len(url)-10])
 662	j := junk.New()
 663	j["@context"] = itiswhatitis
 664	j["id"] = user + colname
 665	j["attributedTo"] = user
 666	j["type"] = "OrderedCollection"
 667	j["totalItems"] = 0
 668	j["orderedItems"] = []junk.Junk{}
 669
 670	return j.ToBytes(), true
 671}})
 672
 673func emptiness(w http.ResponseWriter, r *http.Request) {
 674	name := mux.Vars(r)["name"]
 675	user, err := butwhatabout(name)
 676	if err != nil {
 677		http.NotFound(w, r)
 678		return
 679	}
 680	if stealthmode(user.ID, r) {
 681		http.NotFound(w, r)
 682		return
 683	}
 684	var j []byte
 685	ok := oldempties.Get(r.URL.Path, &j)
 686	if ok {
 687		w.Header().Set("Content-Type", theonetruename)
 688		w.Write(j)
 689	} else {
 690		http.NotFound(w, r)
 691	}
 692}
 693
 694func showuser(w http.ResponseWriter, r *http.Request) {
 695	name := mux.Vars(r)["name"]
 696	user, err := butwhatabout(name)
 697	if err != nil {
 698		log.Printf("user not found %s: %s", name, err)
 699		http.NotFound(w, r)
 700		return
 701	}
 702	if stealthmode(user.ID, r) {
 703		http.NotFound(w, r)
 704		return
 705	}
 706	if friendorfoe(r.Header.Get("Accept")) {
 707		j, ok := asjonker(name)
 708		if ok {
 709			w.Header().Set("Content-Type", theonetruename)
 710			w.Write(j)
 711		} else {
 712			http.NotFound(w, r)
 713		}
 714		return
 715	}
 716	u := login.GetUserInfo(r)
 717	honks := gethonksbyuser(name, u != nil && u.Username == name, 0)
 718	templinfo := getInfo(r)
 719	templinfo["Name"] = user.Name
 720	whatabout := user.About
 721	whatabout = markitzero(user.About)
 722	templinfo["WhatAbout"] = template.HTML(whatabout)
 723	templinfo["ServerMessage"] = ""
 724	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 725	honkpage(w, u, honks, templinfo)
 726}
 727
 728func showhonker(w http.ResponseWriter, r *http.Request) {
 729	u := login.GetUserInfo(r)
 730	name := mux.Vars(r)["name"]
 731	var honks []*Honk
 732	if name == "" {
 733		name = r.FormValue("xid")
 734		honks = gethonksbyxonker(u.UserID, name, 0)
 735	} else {
 736		honks = gethonksbyhonker(u.UserID, name, 0)
 737	}
 738	msg := templates.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>`, name, name)
 739	templinfo := getInfo(r)
 740	templinfo["PageName"] = "honker"
 741	templinfo["PageArg"] = name
 742	templinfo["ServerMessage"] = msg
 743	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 744	honkpage(w, u, honks, templinfo)
 745}
 746
 747func showcombo(w http.ResponseWriter, r *http.Request) {
 748	name := mux.Vars(r)["name"]
 749	u := login.GetUserInfo(r)
 750	honks := gethonksbycombo(u.UserID, name, 0)
 751	honks = osmosis(honks, u.UserID, true)
 752	templinfo := getInfo(r)
 753	templinfo["PageName"] = "combo"
 754	templinfo["PageArg"] = name
 755	templinfo["ServerMessage"] = "honks by combo: " + name
 756	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 757	honkpage(w, u, honks, templinfo)
 758}
 759func showconvoy(w http.ResponseWriter, r *http.Request) {
 760	c := r.FormValue("c")
 761	u := login.GetUserInfo(r)
 762	honks := gethonksbyconvoy(u.UserID, c, 0)
 763	templinfo := getInfo(r)
 764	if len(honks) > 0 {
 765		templinfo["TopHID"] = honks[0].ID
 766	}
 767	honks = osmosis(honks, u.UserID, false)
 768	reversehonks(honks)
 769	templinfo["PageName"] = "convoy"
 770	templinfo["PageArg"] = c
 771	templinfo["ServerMessage"] = "honks in convoy: " + c
 772	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 773	honkpage(w, u, honks, templinfo)
 774}
 775func showsearch(w http.ResponseWriter, r *http.Request) {
 776	q := r.FormValue("q")
 777	u := login.GetUserInfo(r)
 778	honks := gethonksbysearch(u.UserID, q, 0)
 779	templinfo := getInfo(r)
 780	templinfo["PageName"] = "search"
 781	templinfo["PageArg"] = q
 782	templinfo["ServerMessage"] = "honks for search: " + q
 783	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 784	honkpage(w, u, honks, templinfo)
 785}
 786func showontology(w http.ResponseWriter, r *http.Request) {
 787	name := mux.Vars(r)["name"]
 788	u := login.GetUserInfo(r)
 789	var userid int64 = -1
 790	if u != nil {
 791		userid = u.UserID
 792	}
 793	honks := gethonksbyontology(userid, "#"+name, 0)
 794	if friendorfoe(r.Header.Get("Accept")) {
 795		if len(honks) > 40 {
 796			honks = honks[0:40]
 797		}
 798
 799		var xids []string
 800		for _, h := range honks {
 801			xids = append(xids, h.XID)
 802		}
 803
 804		user := getserveruser()
 805
 806		j := junk.New()
 807		j["@context"] = itiswhatitis
 808		j["id"] = fmt.Sprintf("https://%s/o/%s", serverName, name)
 809		j["name"] = "#" + name
 810		j["attributedTo"] = user.URL
 811		j["type"] = "OrderedCollection"
 812		j["totalItems"] = len(xids)
 813		j["orderedItems"] = xids
 814
 815		j.Write(w)
 816		return
 817	}
 818
 819	templinfo := getInfo(r)
 820	templinfo["ServerMessage"] = "honks by ontology: " + name
 821	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 822	honkpage(w, u, honks, templinfo)
 823}
 824
 825type Ont struct {
 826	Name  string
 827	Count int64
 828}
 829
 830func thelistingoftheontologies(w http.ResponseWriter, r *http.Request) {
 831	u := login.GetUserInfo(r)
 832	var userid int64 = -1
 833	if u != nil {
 834		userid = u.UserID
 835	}
 836	rows, err := stmtAllOnts.Query(userid)
 837	if err != nil {
 838		log.Printf("selection error: %s", err)
 839		return
 840	}
 841	defer rows.Close()
 842	var onts []Ont
 843	for rows.Next() {
 844		var o Ont
 845		err := rows.Scan(&o.Name, &o.Count)
 846		if err != nil {
 847			log.Printf("error scanning ont: %s", err)
 848			continue
 849		}
 850		if len(o.Name) > 24 {
 851			continue
 852		}
 853		o.Name = o.Name[1:]
 854		onts = append(onts, o)
 855	}
 856	sort.Slice(onts, func(i, j int) bool {
 857		return onts[i].Name < onts[j].Name
 858	})
 859	if u == nil {
 860		w.Header().Set("Cache-Control", "max-age=300")
 861	}
 862	templinfo := getInfo(r)
 863	templinfo["Onts"] = onts
 864	err = readviews.Execute(w, "onts.html", templinfo)
 865	if err != nil {
 866		log.Print(err)
 867	}
 868}
 869
 870type Track struct {
 871	xid string
 872	who string
 873}
 874
 875func savetracks(tracks map[string][]string) {
 876	db := opendatabase()
 877	tx, err := db.Begin()
 878	if err != nil {
 879		log.Printf("savetracks begin error: %s", err)
 880		return
 881	}
 882	defer func() {
 883		err := tx.Commit()
 884		if err != nil {
 885			log.Printf("savetracks commit error: %s", err)
 886		}
 887
 888	}()
 889	stmtGetTracks, err := tx.Prepare("select fetches from tracks where xid = ?")
 890	if err != nil {
 891		log.Printf("savetracks error: %s", err)
 892		return
 893	}
 894	stmtNewTracks, err := tx.Prepare("insert into tracks (xid, fetches) values (?, ?)")
 895	if err != nil {
 896		log.Printf("savetracks error: %s", err)
 897		return
 898	}
 899	stmtUpdateTracks, err := tx.Prepare("update tracks set fetches = ? where xid = ?")
 900	if err != nil {
 901		log.Printf("savetracks error: %s", err)
 902		return
 903	}
 904	count := 0
 905	for xid, f := range tracks {
 906		count += len(f)
 907		var prev string
 908		row := stmtGetTracks.QueryRow(xid)
 909		err := row.Scan(&prev)
 910		if err == sql.ErrNoRows {
 911			f = oneofakind(f)
 912			stmtNewTracks.Exec(xid, strings.Join(f, " "))
 913		} else if err == nil {
 914			all := append(strings.Split(prev, " "), f...)
 915			all = oneofakind(all)
 916			stmtUpdateTracks.Exec(strings.Join(all, " "))
 917		} else {
 918			log.Printf("savetracks error: %s", err)
 919		}
 920	}
 921	log.Printf("saved %d new fetches", count)
 922}
 923
 924var trackchan = make(chan Track)
 925
 926func tracker() {
 927	timeout := 4 * time.Minute
 928	sleeper := time.NewTimer(timeout)
 929	tracks := make(map[string][]string)
 930	for {
 931		select {
 932		case track := <-trackchan:
 933			tracks[track.xid] = append(tracks[track.xid], track.who)
 934		case <-sleeper.C:
 935			if len(tracks) > 0 {
 936				go savetracks(tracks)
 937				tracks = make(map[string][]string)
 938			}
 939			sleeper.Reset(timeout)
 940		case <-endoftheworld:
 941			if len(tracks) > 0 {
 942				savetracks(tracks)
 943			}
 944			readyalready <- true
 945		}
 946	}
 947}
 948
 949var re_keyholder = regexp.MustCompile(`keyId="([^"]+)"`)
 950
 951func trackback(xid string, r *http.Request) {
 952	agent := r.UserAgent()
 953	who := originate(agent)
 954	sig := r.Header.Get("Signature")
 955	if sig != "" {
 956		m := re_keyholder.FindStringSubmatch(sig)
 957		if len(m) == 2 {
 958			who = m[1]
 959		}
 960	}
 961	if who != "" {
 962		trackchan <- Track{xid: xid, who: who}
 963	}
 964}
 965
 966func showonehonk(w http.ResponseWriter, r *http.Request) {
 967	name := mux.Vars(r)["name"]
 968	user, err := butwhatabout(name)
 969	if err != nil {
 970		http.NotFound(w, r)
 971		return
 972	}
 973	if stealthmode(user.ID, r) {
 974		http.NotFound(w, r)
 975		return
 976	}
 977	xid := fmt.Sprintf("https://%s%s", serverName, r.URL.Path)
 978
 979	if friendorfoe(r.Header.Get("Accept")) {
 980		j, ok := gimmejonk(xid)
 981		if ok {
 982			trackback(xid, r)
 983			w.Header().Set("Content-Type", theonetruename)
 984			w.Write(j)
 985		} else {
 986			http.NotFound(w, r)
 987		}
 988		return
 989	}
 990	honk := getxonk(user.ID, xid)
 991	if honk == nil {
 992		http.NotFound(w, r)
 993		return
 994	}
 995	u := login.GetUserInfo(r)
 996	if u != nil && u.UserID != user.ID {
 997		u = nil
 998	}
 999	if !honk.Public {
1000		if u == nil {
1001			http.NotFound(w, r)
1002			return
1003
1004		}
1005		templinfo := getInfo(r)
1006		templinfo["ServerMessage"] = "one honk maybe more"
1007		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1008		honkpage(w, u, []*Honk{honk}, templinfo)
1009		return
1010	}
1011	rawhonks := gethonksbyconvoy(honk.UserID, honk.Convoy, 0)
1012	reversehonks(rawhonks)
1013	var honks []*Honk
1014	for _, h := range rawhonks {
1015		if h.XID == xid && len(honks) != 0 {
1016			h.Style += " glow"
1017		}
1018		if h.Public && (h.Whofore == 2 || h.IsAcked()) {
1019			honks = append(honks, h)
1020		}
1021	}
1022
1023	templinfo := getInfo(r)
1024	templinfo["ServerMessage"] = "one honk maybe more"
1025	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1026	honkpage(w, u, honks, templinfo)
1027}
1028
1029func honkpage(w http.ResponseWriter, u *login.UserInfo, honks []*Honk, templinfo map[string]interface{}) {
1030	var userid int64 = -1
1031	if u != nil {
1032		userid = u.UserID
1033	}
1034	reverbolate(userid, honks)
1035	templinfo["Honks"] = honks
1036	if templinfo["TopHID"] == nil {
1037		if len(honks) > 0 {
1038			templinfo["TopHID"] = honks[0].ID
1039		} else {
1040			templinfo["TopHID"] = 0
1041		}
1042	}
1043	if u == nil {
1044		w.Header().Set("Cache-Control", "max-age=60")
1045	}
1046	err := readviews.Execute(w, "honkpage.html", templinfo)
1047	if err != nil {
1048		log.Print(err)
1049	}
1050}
1051
1052func saveuser(w http.ResponseWriter, r *http.Request) {
1053	whatabout := r.FormValue("whatabout")
1054	u := login.GetUserInfo(r)
1055	db := opendatabase()
1056	var options UserOptions
1057	if r.FormValue("skinny") == "skinny" {
1058		options.SkinnyCSS = true
1059	}
1060	j, err := jsonify(options)
1061	if err == nil {
1062		_, err = db.Exec("update users set about = ?, options = ? where username = ?", whatabout, j, u.Username)
1063	}
1064	if err != nil {
1065		log.Printf("error bouting what: %s", err)
1066	}
1067	somenamedusers.Clear(u.Username)
1068	somenumberedusers.Clear(u.UserID)
1069
1070	http.Redirect(w, r, "/account", http.StatusSeeOther)
1071}
1072
1073func submitbonk(w http.ResponseWriter, r *http.Request) {
1074	xid := r.FormValue("xid")
1075	userinfo := login.GetUserInfo(r)
1076	user, _ := butwhatabout(userinfo.Username)
1077
1078	log.Printf("bonking %s", xid)
1079
1080	xonk := getxonk(userinfo.UserID, xid)
1081	if xonk == nil {
1082		return
1083	}
1084	if !xonk.Public {
1085		return
1086	}
1087	if xonk.IsBonked() {
1088		return
1089	}
1090	donksforhonks([]*Honk{xonk})
1091
1092	_, err := stmtUpdateFlags.Exec(flagIsBonked, xonk.ID)
1093	if err != nil {
1094		log.Printf("error acking bonk: %s", err)
1095	}
1096
1097	oonker := xonk.Oonker
1098	if oonker == "" {
1099		oonker = xonk.Honker
1100	}
1101	dt := time.Now().UTC()
1102	bonk := &Honk{
1103		UserID:   userinfo.UserID,
1104		Username: userinfo.Username,
1105		What:     "bonk",
1106		Honker:   user.URL,
1107		Oonker:   oonker,
1108		XID:      xonk.XID,
1109		RID:      xonk.RID,
1110		Noise:    xonk.Noise,
1111		Precis:   xonk.Precis,
1112		URL:      xonk.URL,
1113		Date:     dt,
1114		Donks:    xonk.Donks,
1115		Whofore:  2,
1116		Convoy:   xonk.Convoy,
1117		Audience: []string{thewholeworld, oonker},
1118		Public:   true,
1119		Format:   "html",
1120		Place:    xonk.Place,
1121		Onts:     xonk.Onts,
1122		Time:     xonk.Time,
1123	}
1124
1125	err = savehonk(bonk)
1126	if err != nil {
1127		log.Printf("uh oh")
1128		return
1129	}
1130
1131	go honkworldwide(user, bonk)
1132
1133	if r.FormValue("js") != "1" {
1134		templinfo := getInfo(r)
1135		templinfo["ServerMessage"] = "Bonked!"
1136		err = readviews.Execute(w, "msg.html", templinfo)
1137		if err != nil {
1138			log.Print(err)
1139		}
1140	}
1141}
1142
1143func sendzonkofsorts(xonk *Honk, user *WhatAbout, what string) {
1144	zonk := &Honk{
1145		What:     what,
1146		XID:      xonk.XID,
1147		Date:     time.Now().UTC(),
1148		Audience: oneofakind(xonk.Audience),
1149	}
1150	zonk.Public = loudandproud(zonk.Audience)
1151
1152	log.Printf("announcing %sed honk: %s", what, xonk.XID)
1153	go honkworldwide(user, zonk)
1154}
1155
1156func zonkit(w http.ResponseWriter, r *http.Request) {
1157	wherefore := r.FormValue("wherefore")
1158	what := r.FormValue("what")
1159	userinfo := login.GetUserInfo(r)
1160	user, _ := butwhatabout(userinfo.Username)
1161
1162	if wherefore == "save" {
1163		xonk := getxonk(userinfo.UserID, what)
1164		if xonk != nil {
1165			_, err := stmtUpdateFlags.Exec(flagIsSaved, xonk.ID)
1166			if err != nil {
1167				log.Printf("error saving: %s", err)
1168			}
1169		}
1170		return
1171	}
1172
1173	if wherefore == "unsave" {
1174		xonk := getxonk(userinfo.UserID, what)
1175		if xonk != nil {
1176			_, err := stmtClearFlags.Exec(flagIsSaved, xonk.ID)
1177			if err != nil {
1178				log.Printf("error unsaving: %s", err)
1179			}
1180		}
1181		return
1182	}
1183
1184	// my hammer is too big, oh well
1185	defer oldjonks.Flush()
1186
1187	if wherefore == "ack" {
1188		xonk := getxonk(userinfo.UserID, what)
1189		if xonk != nil && !xonk.IsAcked() {
1190			_, err := stmtUpdateFlags.Exec(flagIsAcked, xonk.ID)
1191			if err != nil {
1192				log.Printf("error acking: %s", err)
1193			}
1194			sendzonkofsorts(xonk, user, "ack")
1195		}
1196		return
1197	}
1198
1199	if wherefore == "deack" {
1200		xonk := getxonk(userinfo.UserID, what)
1201		if xonk != nil && xonk.IsAcked() {
1202			_, err := stmtClearFlags.Exec(flagIsAcked, xonk.ID)
1203			if err != nil {
1204				log.Printf("error deacking: %s", err)
1205			}
1206			sendzonkofsorts(xonk, user, "deack")
1207		}
1208		return
1209	}
1210
1211	if wherefore == "unbonk" {
1212		xonk := getbonk(userinfo.UserID, what)
1213		if xonk != nil {
1214			deletehonk(xonk.ID)
1215			xonk = getxonk(userinfo.UserID, what)
1216			_, err := stmtClearFlags.Exec(flagIsBonked, xonk.ID)
1217			if err != nil {
1218				log.Printf("error unbonking: %s", err)
1219			}
1220			sendzonkofsorts(xonk, user, "unbonk")
1221		}
1222		return
1223	}
1224
1225	if wherefore == "untag" {
1226		xonk := getxonk(userinfo.UserID, what)
1227		if xonk != nil {
1228			_, err := stmtUpdateFlags.Exec(flagIsUntagged, xonk.ID)
1229			if err != nil {
1230				log.Printf("error untagging: %s", err)
1231			}
1232		}
1233		var badparents map[string]bool
1234		untagged.GetAndLock(userinfo.UserID, &badparents)
1235		badparents[what] = true
1236		untagged.Unlock()
1237		return
1238	}
1239
1240	log.Printf("zonking %s %s", wherefore, what)
1241	if wherefore == "zonk" {
1242		xonk := getxonk(userinfo.UserID, what)
1243		if xonk != nil {
1244			deletehonk(xonk.ID)
1245			if xonk.Whofore == 2 || xonk.Whofore == 3 {
1246				sendzonkofsorts(xonk, user, "zonk")
1247			}
1248		}
1249	}
1250	_, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
1251	if err != nil {
1252		log.Printf("error saving zonker: %s", err)
1253		return
1254	}
1255}
1256
1257func edithonkpage(w http.ResponseWriter, r *http.Request) {
1258	u := login.GetUserInfo(r)
1259	user, _ := butwhatabout(u.Username)
1260	xid := r.FormValue("xid")
1261	honk := getxonk(u.UserID, xid)
1262	if !canedithonk(user, honk) {
1263		http.Error(w, "no editing that please", http.StatusInternalServerError)
1264		return
1265	}
1266
1267	noise := honk.Noise
1268
1269	honks := []*Honk{honk}
1270	donksforhonks(honks)
1271	reverbolate(u.UserID, honks)
1272	templinfo := getInfo(r)
1273	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1274	templinfo["Honks"] = honks
1275	templinfo["Noise"] = noise
1276	templinfo["SavedPlace"] = honk.Place
1277	templinfo["ServerMessage"] = "honk edit"
1278	templinfo["IsPreview"] = true
1279	templinfo["UpdateXID"] = honk.XID
1280	if len(honk.Donks) > 0 {
1281		templinfo["SavedFile"] = honk.Donks[0].XID
1282	}
1283	err := readviews.Execute(w, "honkpage.html", templinfo)
1284	if err != nil {
1285		log.Print(err)
1286	}
1287}
1288
1289func newhonkpage(w http.ResponseWriter, r *http.Request) {
1290	u := login.GetUserInfo(r)
1291	rid := r.FormValue("rid")
1292	noise := ""
1293
1294	xonk := getxonk(u.UserID, rid)
1295	if xonk != nil {
1296		_, replto := handles(xonk.Honker)
1297		if replto != "" {
1298			noise = "@" + replto + " "
1299		}
1300	}
1301
1302	templinfo := getInfo(r)
1303	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1304	templinfo["InReplyTo"] = rid
1305	templinfo["Noise"] = noise
1306	templinfo["ServerMessage"] = "compose honk"
1307	templinfo["IsPreview"] = true
1308	err := readviews.Execute(w, "honkpage.html", templinfo)
1309	if err != nil {
1310		log.Print(err)
1311	}
1312}
1313
1314func canedithonk(user *WhatAbout, honk *Honk) bool {
1315	if honk == nil || honk.Honker != user.URL || honk.What == "bonk" {
1316		return false
1317	}
1318	return true
1319}
1320
1321func submitwebhonk(w http.ResponseWriter, r *http.Request) {
1322	submithonk(w, r, false)
1323}
1324
1325// what a hot mess this function is
1326func submithonk(w http.ResponseWriter, r *http.Request, isAPI bool) {
1327	rid := r.FormValue("rid")
1328	noise := r.FormValue("noise")
1329
1330	userinfo := login.GetUserInfo(r)
1331	user, _ := butwhatabout(userinfo.Username)
1332
1333	dt := time.Now().UTC()
1334	updatexid := r.FormValue("updatexid")
1335	var honk *Honk
1336	if updatexid != "" {
1337		honk = getxonk(userinfo.UserID, updatexid)
1338		if !canedithonk(user, honk) {
1339			http.Error(w, "no editing that please", http.StatusInternalServerError)
1340			return
1341		}
1342		honk.Date = dt
1343		honk.What = "update"
1344		honk.Format = "markdown"
1345	} else {
1346		xid := fmt.Sprintf("%s/%s/%s", user.URL, honkSep, xfiltrate())
1347		what := "honk"
1348		if rid != "" {
1349			what = "tonk"
1350		}
1351		honk = &Honk{
1352			UserID:   userinfo.UserID,
1353			Username: userinfo.Username,
1354			What:     what,
1355			Honker:   user.URL,
1356			XID:      xid,
1357			Date:     dt,
1358			Format:   "markdown",
1359		}
1360	}
1361
1362	noise = strings.Replace(noise, "\r", "", -1)
1363	noise = quickrename(noise, userinfo.UserID)
1364	noise = hooterize(noise)
1365	honk.Noise = noise
1366	translate(honk, false)
1367
1368	var convoy string
1369	if rid != "" {
1370		xonk := getxonk(userinfo.UserID, rid)
1371		if xonk != nil {
1372			if xonk.Public {
1373				honk.Audience = append(honk.Audience, xonk.Audience...)
1374			}
1375			convoy = xonk.Convoy
1376		} else {
1377			xonkaud, c := whosthere(rid)
1378			honk.Audience = append(honk.Audience, xonkaud...)
1379			convoy = c
1380		}
1381		for i, a := range honk.Audience {
1382			if a == thewholeworld {
1383				honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
1384				break
1385			}
1386		}
1387		honk.RID = rid
1388	} else {
1389		honk.Audience = []string{thewholeworld}
1390	}
1391	if honk.Noise != "" && honk.Noise[0] == '@' {
1392		honk.Audience = append(grapevine(honk.Noise), honk.Audience...)
1393	} else {
1394		honk.Audience = append(honk.Audience, grapevine(honk.Noise)...)
1395	}
1396
1397	if convoy == "" {
1398		convoy = "data:,electrichonkytonk-" + xfiltrate()
1399	}
1400	butnottooloud(honk.Audience)
1401	honk.Audience = oneofakind(honk.Audience)
1402	if len(honk.Audience) == 0 {
1403		log.Printf("honk to nowhere")
1404		http.Error(w, "honk to nowhere...", http.StatusNotFound)
1405		return
1406	}
1407	honk.Public = loudandproud(honk.Audience)
1408	honk.Convoy = convoy
1409
1410	donkxid := r.FormValue("donkxid")
1411	if donkxid == "" {
1412		file, filehdr, err := r.FormFile("donk")
1413		if err == nil {
1414			var buf bytes.Buffer
1415			io.Copy(&buf, file)
1416			file.Close()
1417			data := buf.Bytes()
1418			xid := xfiltrate()
1419			var media, name string
1420			img, err := shrinkit(data)
1421			if err == nil {
1422				data = img.Data
1423				format := img.Format
1424				media = "image/" + format
1425				if format == "jpeg" {
1426					format = "jpg"
1427				}
1428				name = xid + "." + format
1429				xid = name
1430			} else {
1431				ct := http.DetectContentType(data)
1432				switch ct {
1433				case "application/pdf":
1434					maxsize := 1000000
1435					if len(data) > maxsize {
1436						log.Printf("bad image: %s too much pdf: %d", err, len(data))
1437						http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1438						return
1439					}
1440					media = ct
1441					xid += ".pdf"
1442					name = filehdr.Filename
1443					if name == "" {
1444						name = xid
1445					}
1446				default:
1447					maxsize := 100000
1448					if len(data) > maxsize {
1449						log.Printf("bad image: %s too much text: %d", err, len(data))
1450						http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1451						return
1452					}
1453					for i := 0; i < len(data); i++ {
1454						if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
1455							log.Printf("bad image: %s not text: %d", err, data[i])
1456							http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1457							return
1458						}
1459					}
1460					media = "text/plain"
1461					xid += ".txt"
1462					name = filehdr.Filename
1463					if name == "" {
1464						name = xid
1465					}
1466				}
1467			}
1468			desc := strings.TrimSpace(r.FormValue("donkdesc"))
1469			if desc == "" {
1470				desc = name
1471			}
1472			url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1473			fileid, err := savefile(xid, name, desc, url, media, true, data)
1474			if err != nil {
1475				log.Printf("unable to save image: %s", err)
1476				return
1477			}
1478			d := &Donk{
1479				FileID: fileid,
1480				XID:    xid,
1481				Desc:   desc,
1482				URL:    url,
1483				Local:  true,
1484			}
1485			honk.Donks = append(honk.Donks, d)
1486			donkxid = xid
1487		}
1488	} else {
1489		xid := donkxid
1490		url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1491		donk := finddonk(url)
1492		if donk != nil {
1493			honk.Donks = append(honk.Donks, donk)
1494		} else {
1495			log.Printf("can't find file: %s", xid)
1496		}
1497	}
1498	herd := herdofemus(noise)
1499	for _, e := range herd {
1500		donk := savedonk(e.ID, e.Name, e.Name, "image/png", true)
1501		if donk != nil {
1502			donk.Name = e.Name
1503			honk.Donks = append(honk.Donks, donk)
1504		}
1505	}
1506	memetize(honk)
1507	imaginate(honk)
1508
1509	placename := strings.TrimSpace(r.FormValue("placename"))
1510	placelat := strings.TrimSpace(r.FormValue("placelat"))
1511	placelong := strings.TrimSpace(r.FormValue("placelong"))
1512	placeurl := strings.TrimSpace(r.FormValue("placeurl"))
1513	if placename != "" || placelat != "" || placelong != "" || placeurl != "" {
1514		p := new(Place)
1515		p.Name = placename
1516		p.Latitude, _ = strconv.ParseFloat(placelat, 64)
1517		p.Longitude, _ = strconv.ParseFloat(placelong, 64)
1518		p.Url = placeurl
1519		honk.Place = p
1520	}
1521	timestart := strings.TrimSpace(r.FormValue("timestart"))
1522	if timestart != "" {
1523		t := new(Time)
1524		now := time.Now().Local()
1525		for _, layout := range []string{"2006-01-02 3:04pm", "2006-01-02 15:04", "3:04pm", "15:04"} {
1526			start, err := time.ParseInLocation(layout, timestart, now.Location())
1527			if err == nil {
1528				if start.Year() == 0 {
1529					start = time.Date(now.Year(), now.Month(), now.Day(), start.Hour(), start.Minute(), 0, 0, now.Location())
1530				}
1531				t.StartTime = start
1532				break
1533			}
1534		}
1535		timeend := r.FormValue("timeend")
1536		dur := parseDuration(timeend)
1537		if dur != 0 {
1538			t.Duration = Duration(dur)
1539		}
1540		if !t.StartTime.IsZero() {
1541			honk.What = "event"
1542			honk.Time = t
1543		}
1544	}
1545
1546	if honk.Public {
1547		honk.Whofore = 2
1548	} else {
1549		honk.Whofore = 3
1550	}
1551
1552	// back to markdown
1553	honk.Noise = noise
1554
1555	if r.FormValue("preview") == "preview" {
1556		honks := []*Honk{honk}
1557		reverbolate(userinfo.UserID, honks)
1558		templinfo := getInfo(r)
1559		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1560		templinfo["Honks"] = honks
1561		templinfo["InReplyTo"] = r.FormValue("rid")
1562		templinfo["Noise"] = r.FormValue("noise")
1563		templinfo["SavedFile"] = donkxid
1564		templinfo["IsPreview"] = true
1565		templinfo["UpdateXID"] = updatexid
1566		templinfo["ServerMessage"] = "honk preview"
1567		err := readviews.Execute(w, "honkpage.html", templinfo)
1568		if err != nil {
1569			log.Print(err)
1570		}
1571		return
1572	}
1573
1574	if updatexid != "" {
1575		updatehonk(honk)
1576		oldjonks.Clear(honk.XID)
1577	} else {
1578		err := savehonk(honk)
1579		if err != nil {
1580			log.Printf("uh oh")
1581			return
1582		}
1583	}
1584
1585	// reload for consistency
1586	honk.Donks = nil
1587	donksforhonks([]*Honk{honk})
1588
1589	go honkworldwide(user, honk)
1590
1591	if isAPI {
1592		w.Write([]byte(honk.XID))
1593	} else {
1594		http.Redirect(w, r, honk.XID[len(serverName)+8:], http.StatusSeeOther)
1595	}
1596}
1597
1598func showhonkers(w http.ResponseWriter, r *http.Request) {
1599	userinfo := login.GetUserInfo(r)
1600	templinfo := getInfo(r)
1601	templinfo["Honkers"] = gethonkers(userinfo.UserID)
1602	templinfo["HonkerCSRF"] = login.GetCSRF("submithonker", r)
1603	err := readviews.Execute(w, "honkers.html", templinfo)
1604	if err != nil {
1605		log.Print(err)
1606	}
1607}
1608
1609var combocache = cache.New(cache.Options{Filler: func(userid int64) ([]string, bool) {
1610	honkers := gethonkers(userid)
1611	var combos []string
1612	for _, h := range honkers {
1613		combos = append(combos, h.Combos...)
1614	}
1615	for i, c := range combos {
1616		if c == "-" {
1617			combos[i] = ""
1618		}
1619	}
1620	combos = oneofakind(combos)
1621	sort.Strings(combos)
1622	return combos, true
1623}, Invalidator: &honkerinvalidator})
1624
1625func showcombos(w http.ResponseWriter, r *http.Request) {
1626	userinfo := login.GetUserInfo(r)
1627	var combos []string
1628	combocache.Get(userinfo.UserID, &combos)
1629	templinfo := getInfo(r)
1630	err := readviews.Execute(w, "combos.html", templinfo)
1631	if err != nil {
1632		log.Print(err)
1633	}
1634}
1635
1636func submithonker(w http.ResponseWriter, r *http.Request) {
1637	u := login.GetUserInfo(r)
1638	name := strings.TrimSpace(r.FormValue("name"))
1639	url := strings.TrimSpace(r.FormValue("url"))
1640	peep := r.FormValue("peep")
1641	combos := strings.TrimSpace(r.FormValue("combos"))
1642	combos = " " + combos + " "
1643	honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1644
1645	defer honkerinvalidator.Clear(u.UserID)
1646
1647	if honkerid > 0 {
1648		goodbye := r.FormValue("goodbye")
1649		if goodbye == "F" {
1650			db := opendatabase()
1651			row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ? and flavor in ('sub')",
1652				honkerid, u.UserID)
1653			err := row.Scan(&url)
1654			if err != nil {
1655				log.Printf("can't get honker xid: %s", err)
1656				return
1657			}
1658			log.Printf("unsubscribing from %s", url)
1659			user, _ := butwhatabout(u.Username)
1660			_, err = stmtUpdateFlavor.Exec("unsub", u.UserID, url, name, "sub")
1661			if err != nil {
1662				log.Printf("error updating honker: %s", err)
1663				return
1664			}
1665			go itakeitallback(user, url)
1666
1667			http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1668			return
1669		}
1670		if goodbye == "X" {
1671			var owner string
1672			db := opendatabase()
1673			row := db.QueryRow("select xid, owner from honkers where honkerid = ? and userid = ? and flavor in ('unsub', 'peep')",
1674				honkerid, u.UserID)
1675			err := row.Scan(&url, &owner)
1676			if err != nil {
1677				log.Printf("can't get honker xid: %s", err)
1678				return
1679			}
1680			log.Printf("resubscribing to %s", url)
1681			user, _ := butwhatabout(u.Username)
1682			_, err = stmtUpdateFlavor.Exec("presub", u.UserID, url, name, "unsub")
1683			if err == nil {
1684				_, err = stmtUpdateFlavor.Exec("presub", u.UserID, url, name, "peep")
1685			}
1686			if err != nil {
1687				log.Printf("error updating honker: %s", err)
1688				return
1689			}
1690			go subsub(user, url, owner)
1691
1692			http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1693			return
1694		}
1695		_, err := stmtUpdateHonker.Exec(name, combos, honkerid, u.UserID)
1696		if err != nil {
1697			log.Printf("update honker err: %s", err)
1698			return
1699		}
1700		http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1701		return
1702	}
1703
1704	if url == "" {
1705		http.Error(w, "subscribing to nothing?", http.StatusInternalServerError)
1706		return
1707	}
1708
1709	flavor := "presub"
1710	if peep == "peep" {
1711		flavor = "peep"
1712	}
1713
1714	if url[0] == '#' {
1715		flavor = "peep"
1716		if name == "" {
1717			name = url
1718		}
1719		_, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos, url)
1720		if err != nil {
1721			log.Print(err)
1722			return
1723		}
1724		http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1725		return
1726	}
1727
1728	info, err := investigate(url)
1729	if err != nil {
1730		http.Error(w, "error investigating: "+err.Error(), http.StatusInternalServerError)
1731		log.Printf("failed to investigate honker: %s", err)
1732		return
1733	}
1734	url = info.XID
1735
1736	db := opendatabase()
1737	row := db.QueryRow("select xid from honkers where xid = ? and userid = ? and flavor in ('sub', 'unsub', 'peep')", url, u.UserID)
1738	var x string
1739	err = row.Scan(&x)
1740	if err != sql.ErrNoRows {
1741		http.Error(w, "it seems you are already subscribed to them", http.StatusInternalServerError)
1742		if err != nil {
1743			log.Printf("honker scan err: %s", err)
1744		}
1745		return
1746	}
1747
1748	if name == "" {
1749		name = info.Name
1750	}
1751	_, err = stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos, info.Owner)
1752	if err != nil {
1753		log.Print(err)
1754		return
1755	}
1756	if flavor == "presub" {
1757		user, _ := butwhatabout(u.Username)
1758		go subsub(user, url, info.Owner)
1759	}
1760	http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1761}
1762
1763func hfcspage(w http.ResponseWriter, r *http.Request) {
1764	userinfo := login.GetUserInfo(r)
1765
1766	filters := getfilters(userinfo.UserID, filtAny)
1767
1768	templinfo := getInfo(r)
1769	templinfo["Filters"] = filters
1770	templinfo["FilterCSRF"] = login.GetCSRF("filter", r)
1771	err := readviews.Execute(w, "hfcs.html", templinfo)
1772	if err != nil {
1773		log.Print(err)
1774	}
1775}
1776
1777func savehfcs(w http.ResponseWriter, r *http.Request) {
1778	userinfo := login.GetUserInfo(r)
1779	itsok := r.FormValue("itsok")
1780	if itsok == "iforgiveyou" {
1781		hfcsid, _ := strconv.ParseInt(r.FormValue("hfcsid"), 10, 0)
1782		_, err := stmtDeleteFilter.Exec(userinfo.UserID, hfcsid)
1783		if err != nil {
1784			log.Printf("error deleting filter: %s", err)
1785		}
1786		filtcache.Clear(userinfo.UserID)
1787		http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
1788		return
1789	}
1790
1791	filt := new(Filter)
1792	filt.Name = strings.TrimSpace(r.FormValue("name"))
1793	filt.Date = time.Now().UTC()
1794	filt.Actor = strings.TrimSpace(r.FormValue("actor"))
1795	filt.IncludeAudience = r.FormValue("incaud") == "yes"
1796	filt.Text = strings.TrimSpace(r.FormValue("filttext"))
1797	filt.IsAnnounce = r.FormValue("isannounce") == "yes"
1798	filt.AnnounceOf = strings.TrimSpace(r.FormValue("announceof"))
1799	filt.Reject = r.FormValue("doreject") == "yes"
1800	filt.SkipMedia = r.FormValue("doskipmedia") == "yes"
1801	filt.Hide = r.FormValue("dohide") == "yes"
1802	filt.Collapse = r.FormValue("docollapse") == "yes"
1803	filt.Rewrite = strings.TrimSpace(r.FormValue("filtrewrite"))
1804	filt.Replace = strings.TrimSpace(r.FormValue("filtreplace"))
1805	if dur := parseDuration(r.FormValue("filtduration")); dur > 0 {
1806		filt.Expiration = time.Now().UTC().Add(dur)
1807	}
1808
1809	if filt.Actor == "" && filt.Text == "" {
1810		log.Printf("blank filter")
1811		return
1812	}
1813
1814	j, err := jsonify(filt)
1815	if err == nil {
1816		_, err = stmtSaveFilter.Exec(userinfo.UserID, j)
1817	}
1818	if err != nil {
1819		log.Printf("error saving filter: %s", err)
1820	}
1821
1822	filtcache.Clear(userinfo.UserID)
1823	http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
1824}
1825
1826func accountpage(w http.ResponseWriter, r *http.Request) {
1827	u := login.GetUserInfo(r)
1828	user, _ := butwhatabout(u.Username)
1829	templinfo := getInfo(r)
1830	templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1831	templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1832	templinfo["User"] = user
1833	err := readviews.Execute(w, "account.html", templinfo)
1834	if err != nil {
1835		log.Print(err)
1836	}
1837}
1838
1839func dochpass(w http.ResponseWriter, r *http.Request) {
1840	err := login.ChangePassword(w, r)
1841	if err != nil {
1842		log.Printf("error changing password: %s", err)
1843	}
1844	http.Redirect(w, r, "/account", http.StatusSeeOther)
1845}
1846
1847func fingerlicker(w http.ResponseWriter, r *http.Request) {
1848	orig := r.FormValue("resource")
1849
1850	log.Printf("finger lick: %s", orig)
1851
1852	if strings.HasPrefix(orig, "acct:") {
1853		orig = orig[5:]
1854	}
1855
1856	name := orig
1857	idx := strings.LastIndexByte(name, '/')
1858	if idx != -1 {
1859		name = name[idx+1:]
1860		if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
1861			log.Printf("foreign request rejected")
1862			name = ""
1863		}
1864	} else {
1865		idx = strings.IndexByte(name, '@')
1866		if idx != -1 {
1867			name = name[:idx]
1868			if name+"@"+serverName != orig {
1869				log.Printf("foreign request rejected")
1870				name = ""
1871			}
1872		}
1873	}
1874	user, err := butwhatabout(name)
1875	if err != nil {
1876		http.NotFound(w, r)
1877		return
1878	}
1879	if stealthmode(user.ID, r) {
1880		http.NotFound(w, r)
1881		return
1882	}
1883
1884	j := junk.New()
1885	j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1886	j["aliases"] = []string{user.URL}
1887	l := junk.New()
1888	l["rel"] = "self"
1889	l["type"] = `application/activity+json`
1890	l["href"] = user.URL
1891	j["links"] = []junk.Junk{l}
1892
1893	w.Header().Set("Content-Type", "application/jrd+json")
1894	j.Write(w)
1895}
1896
1897func somedays() string {
1898	secs := 432000 + notrand.Int63n(432000)
1899	return fmt.Sprintf("%d", secs)
1900}
1901
1902func avatate(w http.ResponseWriter, r *http.Request) {
1903	n := r.FormValue("a")
1904	a := avatar(n)
1905	w.Header().Set("Cache-Control", "max-age="+somedays())
1906	w.Write(a)
1907}
1908
1909func serveasset(w http.ResponseWriter, r *http.Request) {
1910	w.Header().Set("Cache-Control", "max-age=7776000")
1911	dir := viewDir
1912	if r.URL.Path == "/local.css" {
1913		dir = dataDir
1914	}
1915	http.ServeFile(w, r, dir+"/views"+r.URL.Path)
1916}
1917func servehelp(w http.ResponseWriter, r *http.Request) {
1918	name := mux.Vars(r)["name"]
1919	w.Header().Set("Cache-Control", "max-age=3600")
1920	http.ServeFile(w, r, viewDir+"/docs/"+name)
1921}
1922func servehtml(w http.ResponseWriter, r *http.Request) {
1923	u := login.GetUserInfo(r)
1924	templinfo := getInfo(r)
1925	templinfo["AboutMsg"] = aboutMsg
1926	templinfo["LoginMsg"] = loginMsg
1927	templinfo["HonkVersion"] = softwareVersion
1928	if r.URL.Path == "/about" {
1929		templinfo["Sensors"] = getSensors()
1930	}
1931	if u == nil {
1932		w.Header().Set("Cache-Control", "max-age=60")
1933	}
1934	err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1935	if err != nil {
1936		log.Print(err)
1937	}
1938}
1939func serveemu(w http.ResponseWriter, r *http.Request) {
1940	xid := mux.Vars(r)["xid"]
1941	w.Header().Set("Cache-Control", "max-age="+somedays())
1942	http.ServeFile(w, r, dataDir+"/emus/"+xid)
1943}
1944func servememe(w http.ResponseWriter, r *http.Request) {
1945	xid := mux.Vars(r)["xid"]
1946	w.Header().Set("Cache-Control", "max-age="+somedays())
1947	http.ServeFile(w, r, dataDir+"/memes/"+xid)
1948}
1949
1950func servefile(w http.ResponseWriter, r *http.Request) {
1951	xid := mux.Vars(r)["xid"]
1952	row := stmtGetFileData.QueryRow(xid)
1953	var media string
1954	var data []byte
1955	err := row.Scan(&media, &data)
1956	if err != nil {
1957		log.Printf("error loading file: %s", err)
1958		http.NotFound(w, r)
1959		return
1960	}
1961	w.Header().Set("Content-Type", media)
1962	w.Header().Set("X-Content-Type-Options", "nosniff")
1963	w.Header().Set("Cache-Control", "max-age="+somedays())
1964	w.Write(data)
1965}
1966
1967func nomoroboto(w http.ResponseWriter, r *http.Request) {
1968	io.WriteString(w, "User-agent: *\n")
1969	io.WriteString(w, "Disallow: /a\n")
1970	io.WriteString(w, "Disallow: /d\n")
1971	io.WriteString(w, "Disallow: /meme\n")
1972	io.WriteString(w, "Disallow: /o\n")
1973	for _, u := range allusers() {
1974		fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
1975	}
1976}
1977
1978func webhydra(w http.ResponseWriter, r *http.Request) {
1979	u := login.GetUserInfo(r)
1980	userid := u.UserID
1981	templinfo := getInfo(r)
1982	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1983	page := r.FormValue("page")
1984
1985	wanted, _ := strconv.ParseInt(r.FormValue("tophid"), 10, 0)
1986
1987	var honks []*Honk
1988	switch page {
1989	case "atme":
1990		honks = gethonksforme(userid, wanted)
1991		honks = osmosis(honks, userid, false)
1992		templinfo["ServerMessage"] = "at me!"
1993	case "home":
1994		honks = gethonksforuser(userid, wanted)
1995		honks = osmosis(honks, userid, true)
1996		templinfo["ServerMessage"] = serverMsg
1997	case "first":
1998		honks = gethonksforuserfirstclass(userid, wanted)
1999		honks = osmosis(honks, userid, true)
2000		templinfo["ServerMessage"] = "first class only"
2001	case "saved":
2002		honks = getsavedhonks(userid, wanted)
2003		templinfo["PageName"] = "saved"
2004		templinfo["ServerMessage"] = "saved honks"
2005	case "combo":
2006		c := r.FormValue("c")
2007		honks = gethonksbycombo(userid, c, wanted)
2008		honks = osmosis(honks, userid, false)
2009		templinfo["ServerMessage"] = "honks by combo: " + c
2010	case "convoy":
2011		c := r.FormValue("c")
2012		honks = gethonksbyconvoy(userid, c, wanted)
2013		honks = osmosis(honks, userid, false)
2014		templinfo["ServerMessage"] = "honks in convoy: " + c
2015	case "honker":
2016		xid := r.FormValue("xid")
2017		if strings.IndexByte(xid, '@') != -1 {
2018			xid = gofish(xid)
2019		}
2020		honks = gethonksbyxonker(userid, xid, wanted)
2021		msg := templates.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>`, xid, xid)
2022		templinfo["ServerMessage"] = msg
2023	default:
2024		http.NotFound(w, r)
2025	}
2026	if len(honks) > 0 {
2027		templinfo["TopHID"] = honks[0].ID
2028	} else {
2029		templinfo["TopHID"] = wanted
2030	}
2031	reverbolate(userid, honks)
2032	templinfo["Honks"] = honks
2033	w.Header().Set("Content-Type", "text/html; charset=utf-8")
2034	err := readviews.Execute(w, "honkfrags.html", templinfo)
2035	if err != nil {
2036		log.Printf("frag error: %s", err)
2037	}
2038}
2039
2040func apihandler(w http.ResponseWriter, r *http.Request) {
2041	u := login.GetUserInfo(r)
2042	action := r.FormValue("action")
2043	log.Printf("api request '%s' on behalf of %s", action, u.Username)
2044	switch action {
2045	case "honk":
2046		submithonk(w, r, true)
2047	default:
2048		http.Error(w, "unknown action", http.StatusNotFound)
2049	}
2050}
2051
2052var endoftheworld = make(chan bool)
2053var readyalready = make(chan bool)
2054
2055func enditall() {
2056	sig := make(chan os.Signal)
2057	signal.Notify(sig, os.Interrupt)
2058	signal.Notify(sig, syscall.SIGTERM)
2059	<-sig
2060	count := 0
2061	log.Printf("stopping...")
2062sendloop:
2063	for {
2064		select {
2065		case endoftheworld <- true:
2066			count++
2067		default:
2068			break sendloop
2069		}
2070	}
2071	log.Printf("waiting...")
2072	for i := 0; i < count; i++ {
2073		<-readyalready
2074	}
2075	log.Printf("apocalypse")
2076	os.Exit(0)
2077}
2078
2079func serve() {
2080	db := opendatabase()
2081	login.Init(db)
2082
2083	listener, err := openListener()
2084	if err != nil {
2085		log.Fatal(err)
2086	}
2087	startBackendServer()
2088	go enditall()
2089	go redeliverator()
2090	go tracker()
2091
2092	debug := false
2093	getconfig("debug", &debug)
2094	readviews = templates.Load(debug,
2095		viewDir+"/views/honkpage.html",
2096		viewDir+"/views/honkfrags.html",
2097		viewDir+"/views/honkers.html",
2098		viewDir+"/views/hfcs.html",
2099		viewDir+"/views/combos.html",
2100		viewDir+"/views/honkform.html",
2101		viewDir+"/views/honk.html",
2102		viewDir+"/views/account.html",
2103		viewDir+"/views/about.html",
2104		viewDir+"/views/funzone.html",
2105		viewDir+"/views/login.html",
2106		viewDir+"/views/xzone.html",
2107		viewDir+"/views/msg.html",
2108		viewDir+"/views/header.html",
2109		viewDir+"/views/onts.html",
2110		viewDir+"/views/honkpage.js",
2111	)
2112	if !debug {
2113		assets := []string{viewDir + "/views/style.css", dataDir + "/views/local.css", viewDir + "/views/honkpage.js"}
2114		for _, s := range assets {
2115			savedassetparams[s] = getassetparam(s)
2116		}
2117	}
2118
2119	mux := mux.NewRouter()
2120	mux.Use(login.Checker)
2121
2122	mux.Handle("/api", login.TokenRequired(http.HandlerFunc(apihandler)))
2123
2124	posters := mux.Methods("POST").Subrouter()
2125	getters := mux.Methods("GET").Subrouter()
2126
2127	getters.HandleFunc("/", homepage)
2128	getters.HandleFunc("/home", homepage)
2129	getters.HandleFunc("/front", homepage)
2130	getters.HandleFunc("/events", homepage)
2131	getters.HandleFunc("/robots.txt", nomoroboto)
2132	getters.HandleFunc("/rss", showrss)
2133	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}", showuser)
2134	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/"+honkSep+"/{xid:[[:alnum:]]+}", showonehonk)
2135	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/rss", showrss)
2136	posters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/inbox", inbox)
2137	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/outbox", outbox)
2138	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/followers", emptiness)
2139	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/following", emptiness)
2140	getters.HandleFunc("/a", avatate)
2141	getters.HandleFunc("/o", thelistingoftheontologies)
2142	getters.HandleFunc("/o/{name:.+}", showontology)
2143	getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
2144	getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
2145	getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
2146	getters.HandleFunc("/.well-known/webfinger", fingerlicker)
2147
2148	getters.HandleFunc("/server", serveractor)
2149	posters.HandleFunc("/server/inbox", serverinbox)
2150
2151	getters.HandleFunc("/style.css", serveasset)
2152	getters.HandleFunc("/local.css", serveasset)
2153	getters.HandleFunc("/honkpage.js", serveasset)
2154	getters.HandleFunc("/about", servehtml)
2155	getters.HandleFunc("/login", servehtml)
2156	posters.HandleFunc("/dologin", login.LoginFunc)
2157	getters.HandleFunc("/logout", login.LogoutFunc)
2158	getters.HandleFunc("/help/{name:[[:alnum:]_.-]+}", servehelp)
2159
2160	loggedin := mux.NewRoute().Subrouter()
2161	loggedin.Use(login.Required)
2162	loggedin.HandleFunc("/first", homepage)
2163	loggedin.HandleFunc("/saved", homepage)
2164	loggedin.HandleFunc("/account", accountpage)
2165	loggedin.HandleFunc("/funzone", showfunzone)
2166	loggedin.HandleFunc("/chpass", dochpass)
2167	loggedin.HandleFunc("/atme", homepage)
2168	loggedin.HandleFunc("/hfcs", hfcspage)
2169	loggedin.HandleFunc("/xzone", xzone)
2170	loggedin.HandleFunc("/newhonk", newhonkpage)
2171	loggedin.HandleFunc("/edit", edithonkpage)
2172	loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(submitwebhonk)))
2173	loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(submitbonk)))
2174	loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
2175	loggedin.Handle("/savehfcs", login.CSRFWrap("filter", http.HandlerFunc(savehfcs)))
2176	loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
2177	loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
2178	loggedin.HandleFunc("/honkers", showhonkers)
2179	loggedin.HandleFunc("/h/{name:[[:alnum:]_.-]+}", showhonker)
2180	loggedin.HandleFunc("/h", showhonker)
2181	loggedin.HandleFunc("/c/{name:[[:alnum:]_.-]+}", showcombo)
2182	loggedin.HandleFunc("/c", showcombos)
2183	loggedin.HandleFunc("/t", showconvoy)
2184	loggedin.HandleFunc("/q", showsearch)
2185	loggedin.HandleFunc("/hydra", webhydra)
2186	loggedin.Handle("/submithonker", login.CSRFWrap("submithonker", http.HandlerFunc(submithonker)))
2187
2188	err = http.Serve(listener, mux)
2189	if err != nil {
2190		log.Fatal(err)
2191	}
2192}