all repos — honk @ ff67fc32e5ac9213e7ff1e7324be06f5642b5e69

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