all repos — honk @ 65617bf87caad4e41d86a52e4f25a5a7150f9f78

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