all repos — honk @ a03fe08a61518d59be749d9918337cd86b1fd7f1

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