all repos — honk @ 5cacee23cdde3009ac4304827b4e52b6c8c5d476

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